chao 3 months ago
commit
afdde451d0
100 changed files with 33741 additions and 0 deletions
  1. BIN
      .DS_Store
  2. 4 0
      .gitignore
  3. 5 0
      .jshintrc
  4. 621 0
      COPYING
  5. 17 0
      DBoutage.html
  6. 675 0
      LICENSE
  7. 24 0
      README
  8. 22 0
      TODO
  9. 47 0
      TOS.html
  10. 202 0
      _soy/COPYING
  11. 45 0
      _soy/README
  12. BIN
      _soy/SoyMsgExtractor.jar
  13. BIN
      _soy/SoyToJsSrcCompiler.jar
  14. 2767 0
      _soy/soyutils.js
  15. 10 0
      blockly/.eslintignore
  16. 28 0
      blockly/.eslintrc
  17. 8 0
      blockly/.gitignore
  18. 6 0
      blockly/.jshintignore
  19. 177 0
      blockly/COPYING
  20. 177 0
      blockly/LICENSE
  21. 8 0
      blockly/README.md
  22. 52 0
      blockly/accessible/README
  23. 64 0
      blockly/accessible/app.component.js
  24. 153 0
      blockly/accessible/clipboard.service.js
  25. 136 0
      blockly/accessible/field.component.js
  26. 321 0
      blockly/accessible/libs/Rx.umd.min.js
  27. 0 0
      blockly/accessible/libs/angular2-all.umd.min.js
  28. 0 0
      blockly/accessible/libs/angular2-polyfills.min.js
  29. 9 0
      blockly/accessible/libs/es6-shim.min.js
  30. 68 0
      blockly/accessible/messages.js
  31. 159 0
      blockly/accessible/toolbox-tree.component.js
  32. 135 0
      blockly/accessible/toolbox.component.js
  33. 34 0
      blockly/accessible/translate.pipe.js
  34. 357 0
      blockly/accessible/tree.service.js
  35. 69 0
      blockly/accessible/utils.service.js
  36. 285 0
      blockly/accessible/workspace-tree.component.js
  37. 91 0
      blockly/accessible/workspace.component.js
  38. 44 0
      blockly/appengine/README.txt
  39. 82 0
      blockly/appengine/app.yaml
  40. BIN
      blockly/appengine/apple-touch-icon.png
  41. BIN
      blockly/appengine/favicon.ico
  42. 11 0
      blockly/appengine/index.yaml
  43. 2 0
      blockly/appengine/index_redirect.py
  44. 68 0
      blockly/appengine/redirect.html
  45. 2 0
      blockly/appengine/robots.txt
  46. 194 0
      blockly/appengine/storage.js
  47. 85 0
      blockly/appengine/storage.py
  48. 1488 0
      blockly/blockly_compressed.js
  49. 586 0
      blockly/blockly_uncompressed.js
  50. 15 0
      blockly/blocks/blockscad_changes
  51. 113 0
      blockly/blocks/colour.js
  52. 320 0
      blockly/blocks/geom_set_ops.js
  53. 706 0
      blockly/blocks/lists.js
  54. 433 0
      blockly/blocks/logic.js
  55. 412 0
      blockly/blocks/loops.js
  56. 465 0
      blockly/blocks/math.js
  57. 1893 0
      blockly/blocks/primitives.js
  58. 1474 0
      blockly/blocks/procedures.js
  59. 666 0
      blockly/blocks/text.js
  60. 574 0
      blockly/blocks/variables.js
  61. 255 0
      blockly/blocks_compressed.js
  62. 465 0
      blockly/build.py
  63. 1554 0
      blockly/core/block.js
  64. 969 0
      blockly/core/block_render_svg.js
  65. 1845 0
      blockly/core/block_svg.js
  66. 590 0
      blockly/core/blockly.js
  67. 33 0
      blockly/core/blocks.js
  68. 579 0
      blockly/core/bubble.js
  69. 284 0
      blockly/core/comment.js
  70. 623 0
      blockly/core/connection.js
  71. 310 0
      blockly/core/connection_db.js
  72. 202 0
      blockly/core/constants.js
  73. 148 0
      blockly/core/contextmenu.js
  74. 821 0
      blockly/core/css.js
  75. 912 0
      blockly/core/events.js
  76. 498 0
      blockly/core/field.js
  77. 294 0
      blockly/core/field_angle.js
  78. 170 0
      blockly/core/field_checkbox.js
  79. 262 0
      blockly/core/field_colour.js
  80. 346 0
      blockly/core/field_date.js
  81. 320 0
      blockly/core/field_dropdown.js
  82. 171 0
      blockly/core/field_image.js
  83. 223 0
      blockly/core/field_label.js
  84. 101 0
      blockly/core/field_number.js
  85. 322 0
      blockly/core/field_textinput.js
  86. 167 0
      blockly/core/field_variable.js
  87. 1248 0
      blockly/core/flyout.js
  88. 369 0
      blockly/core/generator.js
  89. 221 0
      blockly/core/icon.js
  90. 380 0
      blockly/core/inject.js
  91. 241 0
      blockly/core/input.js
  92. 62 0
      blockly/core/msg.js
  93. 388 0
      blockly/core/mutator.js
  94. 60 0
      blockly/core/mutator_minus.js
  95. 62 0
      blockly/core/mutator_plus.js
  96. 143 0
      blockly/core/names.js
  97. 233 0
      blockly/core/options.js
  98. 287 0
      blockly/core/procedures.js
  99. 424 0
      blockly/core/rendered_connection.js
  100. 750 0
      blockly/core/scrollbar.js

BIN
.DS_Store


+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+
+auth.js
+
+node_modules/*

+ 5 - 0
.jshintrc

@@ -0,0 +1,5 @@
+{
+  "browser": true,
+  "devel": true,
+  "jquery": true
+}

+ 621 - 0
COPYING

@@ -0,0 +1,621 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS

+ 17 - 0
DBoutage.html

@@ -0,0 +1,17 @@
+<div id="outage-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="about-modal" aria-hidden="true">
+  <div class="modal-dialog">
+    <div class="modal-content">
+		  <div class="modal-header">
+		    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+		    <h3 class="modal-title">Scheduled Database Maintenance Dec. 27</h3>
+		  </div>
+		  <div class="modal-body">
+		    <div>
+		      On Dec. 27, 2016, we will be upgrading the BlocksCAD project database. During this time, login will be disabled and you won't have access to your account or your saved projects. If you plan to work on specific projects that day, be sure to download them to your computer before Dec. 27. Any edits to these projects or any new creations will need to be saved to your computer that day as well.
+		    </div>
+		  </div>
+		  <div class="modal-footer">
+		  </div>
+		</div>
+  </div>
+</div>

+ 675 - 0
LICENSE

@@ -0,0 +1,675 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {one line to give the program's name and a brief idea of what it does.}
+    Copyright (C) {year}  {name of author}
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    {project}  Copyright (C) {year}  {fullname}
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+

+ 24 - 0
README

@@ -0,0 +1,24 @@
+BlocksCAD - Copyright 2014-2015 H3XL, Inc
+BlocksCAD is a trademark of H3XL, Inc.
+
+Released under GPLv3, incorporating a number of BSD, MIT, and Apache-licensed works. Contributions to those works released under their respective licences, unless otherwise indicated.
+
+BlocksCAD must be run on a webserver for the text block to work, rather than being run on the local filesystem.  If you have Python installed, you can use their simpleHTTPserver:
+
+cd /path/to/mydir-with-index.html-in-it
+python -m SimpleHTTPServer 9000
+Then point your browser to:
+
+http://localhost:9000/
+
+
+
+Developed with the sponsorship of the Defense Advanced Research Projects Agency (DARPA) and delivered to the U.S. Government with Unlimited Rights as defined in DFARS 252.227-7013. Approved for Public Release, Distribution Unlimited.
+
+THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS, SPONSORS, DEVELOPERS, CONTRIBUTORS, OR COPYRIGHT HOLDERS BE LIABLE 
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE CODE OR THE 
+USE OR OTHER DEALINGS IN THE CODE.

+ 22 - 0
TODO

@@ -0,0 +1,22 @@
+FEATURES TO ADD AND FIX AND STUFF
+[ ]hull glitch
+[ ]clean up the repository (there's lots of unused stuff still laying around)
+[ ]icons for operations (unions, translates, etc)
+[ ]fix safari download STL problem
+[x]touchscreen-friendly resize
+[ ]add blocks to library (and get block from library, and library implementation)
+[ ]project sharing
+[ ]STEP/IGES support
+[ ]Arrays
+[ ]aria stuff
+[ ]Implement openscad children()
+[ ]Storing color as a variable
+[x]Text (and strings)
+[ ]Draft
+[ ]Minkowski Sum
+[ ]Switch to three.js
+[ ]Transparency (like openscad's #, or alpha channel in color)
+[ ]Internationalization support (bring back from blockly)
+[ ]Simplified mode (elementary-friendly, hiding distracting fancy stuff)
+[ ]SVG/DXF import
+[x]STL import

+ 47 - 0
TOS.html

@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+	<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+	<title>BlocksCAD Privacy Policy</title>
+</head>
+
+<body dir="ltr" style="max-width:8.5in;margin-top:0.775in; margin-bottom:0.775in; margin-left:0.775in; margin-right:0.775in; writing-mode:lr-tb; ">
+	<h1>Terms &amp; Conditions of Use for BlocksCAD</h1>
+	<h3>Acceptance of Terms and Conditions</h3>
+	<p>By using this website you are indicating your agreement to these Terms and Conditions of Use ("Terms of Use").</p>
+	<p>If you do not agree to these Terms of Use, please do not use the Web site and exit now.</p>
+	<p>Throughout these Terms of Use, the words "we," "us," "our," and "Einstein's Workshop" refer to H3XL, Inc., DBA Einstein's Workshop, 25 Adams Street, Burlington, MA 01803. (781) 202-5645.</p>
+	<p>The BlocksCAD core software and website have been developed with the sponsorship of the Defense Advanced Research Projects Agency (“DARPA”) and the contracting agent is the Air Force Research Laboratory (“AFRL”). DARPA and AFRL are agencies of the United States government. DARPA and AFRL are referred to as the “site sponsors” in these Terms of Use.</p>
+	<p>We may revise these Terms of Use at any time without prior notice by updating this page and such revisions will be effective upon posting to this page. Please check this page periodically for any changes. Your continued use of this website following the posting of any revisions to these Terms of Use will mean you accept those changes. We reserve the right to alter, suspend or discontinue any aspect of the BlocksCAD website, including your access to it. Unless explicitly stated, any new features will be subject to these Terms of Use.</p>
+	<h3>Privacy</h3>
+	<p>See our complete <a href="privacy.html">privacy policy</a>.</p>
+	<h2>Copyright, Trademark and Other Intellectual Property</h2>
+	<h3>Protection</h3>
+	<p>Except as otherwise indicated, this website and its entire contents (including, but not limited to, the text, photographs, information, software, graphics, images, sound, and animation) are owned by Einstein's Workshop and are protected by domestic and international copyright, trademark, patent, and other intellectual property laws.</p>
+	<p>We hereby give you permission to download, retain, and print materials from this Web site for any use, provided that such use is not defamatory towards, or otherwise harmful to, Einstein's Workshop, and that all applicable copyright and other intellectual property notices and licenses are respected.</p>
+	<h3>Notice</h3>
+	<p>All trademarks, service marks, and trade names are proprietary to us or other respective owners that have granted us the right and license to use their marks.</p>
+	<h2>Indemnification and Release</h2>
+	<p>By accessing this website, you agree to indemnify Einstein's Workshop and any parent, subsidiary, sponsor or affiliated entities, officers and employees of them (the “indemnitees”), and hold the indemnitees harmless from any and all claims and expenses, including attorney's fees, arising from your use of this website including any material (including third-party material) that you post on the website and any services or products available through this website. In addition, you hereby release the indemnitees from any and all claims, demands, debts, obligations, damages (actual or consequential), costs, and expenses of any kind or nature whatsoever, whether known or unknown, suspected or unsuspected, disclosed or undisclosed, that you may have against them arising out of or in any way related to such disputes and/or to any services or products available on this website. You hereby agree to waive all laws or rights that may limit the efficacy of such releases.</p>
+	<h2>Disclaimer</h2>
+	<p>THE MATERIALS AND SERVICES ON THIS WEBSITE ARE PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, SPONSORS, DEVELOPERS, CONTRIBUTORS, OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE WEBSITE OR THE USE OR OTHER DEALINGS IN THE WEBSITE.</p>
+	<p>Documents, graphics and other materials appearing at this website may include technical inaccuracies, typographical errors, and out-of-date information and use of such documents, graphics or other materials is at your own risk.</p>
+	<h2>Materials Posted by Visitors</h2>
+	<p>Except as otherwise specified, by posting, uploading or otherwise sending any information or material to us you grant (or warrant that the owner of such rights has expressly granted) to Einstein's Workshop a perpetual, royalty-free, irrevocable, non-exclusive right and license to use, reproduce and publish such such work purely as necessary to provide the expected services, such as providing a saved project to a logged-in user.</p>
+	<h2>Conduct</h2>
+	<p>You are allowed to register with this website as many times as desired, and you are under no obligation to provide true or accurate registration information. Be aware, in the event you forget your username and/or password and no longer have access to the email address associated with your account, you will have permanently lost access to the account. We will not reset any information associated with an account, as there is no way to definitively prove to us that you are the current rightful owner of said account. You agree to not hold Einstein's Workshop, or any of its employees or associates, past or present, liable for loss as a result of an inaccessible account.</p>
+	<p>You agree not to disrupt, modify or interfere with the functioning of this website or any services provided on or through this website or with any associated software, hardware or servers in any way and you agree not to impede or interfere with others' use of this website. You also agree not to alter or tamper with any information or materials on, or associated with this website or services provided on or through this site.</p>
+	<p>You agree that no impediment exists to you joining this website, and your participation in this website will not interfere with your performance of any other agreement or obligation which has been or will be made with any third party.</p>
+	<h2>Choice of Law and Forum</h2>
+	<p>These Terms of Use are governed by the laws of the Commonwealth of Massachusetts. You hereby agree to submit to the exclusive jurisdiction of the courts of the Commonwealth of Massachusetts. To the extent that applicable laws have mandatory application to this agreement or give you the right to bring action in any other courts, the above limitations may not apply to you.</p>
+	<h2>Severability and Enforceability</h2>
+	<p>If any provision or portion of these Terms of Use is held illegal, invalid, or unenforceable, in whole or in part, it shall be modified to the minimum extent necessary to correct any deficiencies or replaced with a provision which is as close as is legally permissible to the provision found invalid or unenforceable and shall not affect the legality, validity or enforceability of any other provisions or portions of these Terms of Use.</p>
+	<h2>Termination/Exclusion</h2>
+	<p>We reserve the right, in our sole discretion, to revoke any and all privileges associated with accessing and/or competing on this website, and to take any other action we deem appropriate including but not limited to terminating or suspending your use of the website, for no reason or any reason whatsoever, including improper use of this website or failure to comply with these Terms of Use.</p>
+	<h2>General</h2>
+	<p>We may assign, novate or subcontract any or all of our rights and obligations under these Terms of Use at any time.</p>
+	<p>If you have any questions regarding these Terms of Use, contact us at <a href="mailto://blockscad@einsteinsworkshop.com">blockscad@einsteinsworkshop.com</a>. This Policy is effective as of August 6, 2015.</p>
+</body>
+
+</html>

+ 202 - 0
_soy/COPYING

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.

+ 45 - 0
_soy/README

@@ -0,0 +1,45 @@
+// Copyright 2009 Google Inc.
+//
+// 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.
+
+
+Contents:
+
++ SoyToJsSrcCompiler.jar
+    Executable jar that compiles template files into JavaScript files.
+
++ SoyMsgExtractor.jar
+    Executable jar that extracts messages from template files into XLF files.
+
++ soyutils.js
+    Helper utilities required by all JavaScript code that SoyToJsSrcCompiler
+    generates. Equivalent functionality to soyutils_usegoog.js, but this
+    version does not need Closure Library.
+
+
+Instructions:
+
++ A simple Hello World for JavaScript:
+    http://code.google.com/closure/templates/docs/helloworld_js.html
+
++ Complete documentation:
+    http://code.google.com/closure/templates/
+
++ Closure Templates project on Google Code:
+    http://code.google.com/p/closure-templates/
+
+
+Notes:
+
++ Closure Templates requires Java 6 or higher:
+    http://www.java.com/

BIN
_soy/SoyMsgExtractor.jar


BIN
_soy/SoyToJsSrcCompiler.jar


+ 2767 - 0
_soy/soyutils.js

@@ -0,0 +1,2767 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * 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
+ * Utility functions and classes for Soy.
+ *
+ * <p>
+ * The top portion of this file contains utilities for Soy users:<ul>
+ *   <li> soy.StringBuilder: Compatible with the 'stringbuilder' code style.
+ *   <li> soy.renderElement: Render template and set as innerHTML of an element.
+ *   <li> soy.renderAsFragment: Render template and return as HTML fragment.
+ * </ul>
+ *
+ * <p>
+ * The bottom portion of this file contains utilities that should only be called
+ * by Soy-generated JS code. Please do not use these functions directly from
+ * your hand-writen code. Their names all start with '$$'.
+ *
+ * @author Garrett Boyer
+ * @author Mike Samuel
+ * @author Kai Huang
+ * @author Aharon Lanin
+ */
+
+
+// COPIED FROM nogoog_shim.js
+
+// Create closure namespaces.
+var goog = goog || {};
+
+
+goog.DEBUG = false;
+
+
+goog.inherits = function(childCtor, parentCtor) {
+  /** @constructor */
+  function tempCtor() {}
+  tempCtor.prototype = parentCtor.prototype;
+  childCtor.superClass_ = parentCtor.prototype;
+  childCtor.prototype = new tempCtor();
+  childCtor.prototype.constructor = childCtor;
+};
+
+
+// Just enough browser detection for this file.
+if (!goog.userAgent) {
+  goog.userAgent = (function() {
+    var userAgent = "";
+    if ("undefined" !== typeof navigator && navigator
+        && "string" == typeof navigator.userAgent) {
+      userAgent = navigator.userAgent;
+    }
+    var isOpera = userAgent.indexOf('Opera') == 0;
+    return {
+      jscript: {
+        /**
+         * @type {boolean}
+         */
+        HAS_JSCRIPT: 'ScriptEngine' in this
+      },
+      /**
+       * @type {boolean}
+       */
+      OPERA: isOpera,
+      /**
+       * @type {boolean}
+       */
+      IE: !isOpera && userAgent.indexOf('MSIE') != -1,
+      /**
+       * @type {boolean}
+       */
+      WEBKIT: !isOpera && userAgent.indexOf('WebKit') != -1
+    };
+  })();
+}
+
+if (!goog.asserts) {
+  goog.asserts = {
+    /**
+     * @param {*} condition Condition to check.
+     */
+    assert: function (condition) {
+      if (!condition) {
+        throw Error('Assertion error');
+      }
+    },
+    /**
+     * @param {...*} var_args
+     */
+    fail: function (var_args) {}
+  };
+}
+
+
+// Stub out the document wrapper used by renderAs*.
+if (!goog.dom) {
+  goog.dom = {};
+  /**
+   * @param {Document=} d
+   * @constructor
+   */
+  goog.dom.DomHelper = function(d) {
+    this.document_ = d || document;
+  };
+  /**
+   * @return {!Document}
+   */
+  goog.dom.DomHelper.prototype.getDocument = function() {
+    return this.document_;
+  };
+  /**
+   * Creates a new element.
+   * @param {string} name Tag name.
+   * @return {!Element}
+   */
+  goog.dom.DomHelper.prototype.createElement = function(name) {
+    return this.document_.createElement(name);
+  };
+  /**
+   * Creates a new document fragment.
+   * @return {!DocumentFragment}
+   */
+  goog.dom.DomHelper.prototype.createDocumentFragment = function() {
+    return this.document_.createDocumentFragment();
+  };
+}
+
+
+if (!goog.format) {
+  goog.format = {
+    insertWordBreaks: function(str, maxCharsBetweenWordBreaks) {
+      str = String(str);
+
+      var resultArr = [];
+      var resultArrLen = 0;
+
+      // These variables keep track of important state inside str.
+      var isInTag = false;  // whether we're inside an HTML tag
+      var isMaybeInEntity = false;  // whether we might be inside an HTML entity
+      var numCharsWithoutBreak = 0;  // number of chars since last word break
+      var flushIndex = 0;  // index of first char not yet flushed to resultArr
+
+      for (var i = 0, n = str.length; i < n; ++i) {
+        var charCode = str.charCodeAt(i);
+
+        // If hit maxCharsBetweenWordBreaks, and not space next, then add <wbr>.
+        if (numCharsWithoutBreak >= maxCharsBetweenWordBreaks &&
+            // space
+            charCode != 32) {
+          resultArr[resultArrLen++] = str.substring(flushIndex, i);
+          flushIndex = i;
+          resultArr[resultArrLen++] = goog.format.WORD_BREAK;
+          numCharsWithoutBreak = 0;
+        }
+
+        if (isInTag) {
+          // If inside an HTML tag and we see '>', it's the end of the tag.
+          if (charCode == 62) {
+            isInTag = false;
+          }
+
+        } else if (isMaybeInEntity) {
+          switch (charCode) {
+            // Inside an entity, a ';' is the end of the entity.
+            // The entity that just ended counts as one char, so increment
+            // numCharsWithoutBreak.
+          case 59:  // ';'
+            isMaybeInEntity = false;
+            ++numCharsWithoutBreak;
+            break;
+            // If maybe inside an entity and we see '<', we weren't actually in
+            // an entity. But now we're inside and HTML tag.
+          case 60:  // '<'
+            isMaybeInEntity = false;
+            isInTag = true;
+            break;
+            // If maybe inside an entity and we see ' ', we weren't actually in
+            // an entity. Just correct the state and reset the
+            // numCharsWithoutBreak since we just saw a space.
+          case 32:  // ' '
+            isMaybeInEntity = false;
+            numCharsWithoutBreak = 0;
+            break;
+          }
+
+        } else {  // !isInTag && !isInEntity
+          switch (charCode) {
+            // When not within a tag or an entity and we see '<', we're now
+            // inside an HTML tag.
+          case 60:  // '<'
+            isInTag = true;
+            break;
+            // When not within a tag or an entity and we see '&', we might be
+            // inside an entity.
+          case 38:  // '&'
+            isMaybeInEntity = true;
+            break;
+            // When we see a space, reset the numCharsWithoutBreak count.
+          case 32:  // ' '
+            numCharsWithoutBreak = 0;
+            break;
+            // When we see a non-space, increment the numCharsWithoutBreak.
+          default:
+            ++numCharsWithoutBreak;
+            break;
+          }
+        }
+      }
+
+      // Flush the remaining chars at the end of the string.
+      resultArr[resultArrLen++] = str.substring(flushIndex);
+
+      return resultArr.join('');
+    },
+    /**
+     * String inserted as a word break by insertWordBreaks(). Safari requires
+     * <wbr></wbr>, Opera needs the 'shy' entity, though this will give a
+     * visible hyphen at breaks. Other browsers just use <wbr>.
+     * @type {string}
+     * @private
+     */
+    WORD_BREAK: goog.userAgent.WEBKIT
+        ? '<wbr></wbr>' : goog.userAgent.OPERA ? '&shy;' : '<wbr>'
+  };
+}
+
+
+if (!goog.i18n) {
+  goog.i18n = {
+    bidi: {
+      /**
+       * Check the directionality of a piece of text, return true if the piece
+       * of text should be laid out in RTL direction.
+       * @param {string} text The piece of text that need to be detected.
+       * @param {boolean=} opt_isHtml Whether {@code text} is HTML/HTML-escaped.
+       *     Default: false.
+       * @return {boolean}
+       * @private
+       */
+      detectRtlDirectionality: function(text, opt_isHtml) {
+        text = soyshim.$$bidiStripHtmlIfNecessary_(text, opt_isHtml);
+        return soyshim.$$bidiRtlWordRatio_(text)
+            > soyshim.$$bidiRtlDetectionThreshold_;
+      }
+    }
+  };
+}
+
+/**
+ * Directionality enum.
+ * @enum {number}
+ */
+goog.i18n.bidi.Dir = {
+  RTL: -1,
+  UNKNOWN: 0,
+  LTR: 1
+};
+
+
+/**
+ * Convert a directionality given in various formats to a goog.i18n.bidi.Dir
+ * constant. Useful for interaction with different standards of directionality
+ * representation.
+ *
+ * @param {goog.i18n.bidi.Dir|number|boolean} givenDir Directionality given in
+ *     one of the following formats:
+ *     1. A goog.i18n.bidi.Dir constant.
+ *     2. A number (positive = LRT, negative = RTL, 0 = unknown).
+ *     3. A boolean (true = RTL, false = LTR).
+ * @return {goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the given
+ *     directionality.
+ */
+goog.i18n.bidi.toDir = function(givenDir) {
+  if (typeof givenDir == 'number') {
+    return givenDir > 0 ? goog.i18n.bidi.Dir.LTR :
+        givenDir < 0 ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.UNKNOWN;
+  } else {
+    return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
+  }
+};
+
+
+/**
+ * Utility class for formatting text for display in a potentially
+ * opposite-directionality context without garbling. Provides the following
+ * functionality:
+ *
+ * @param {goog.i18n.bidi.Dir|number|boolean} dir The context
+ *     directionality as a number
+ *     (positive = LRT, negative = RTL, 0 = unknown).
+ * @constructor
+ */
+goog.i18n.BidiFormatter = function(dir) {
+  this.dir_ = goog.i18n.bidi.toDir(dir);
+};
+
+
+/**
+ * Returns 'dir="ltr"' or 'dir="rtl"', depending on {@code text}'s estimated
+ * directionality, if it is not the same as the context directionality.
+ * Otherwise, returns the empty string.
+ *
+ * @param {string} text Text whose directionality is to be estimated.
+ * @param {boolean=} opt_isHtml Whether {@code text} is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {string} 'dir="rtl"' for RTL text in non-RTL context; 'dir="ltr"' for
+ *     LTR text in non-LTR context; else, the empty string.
+ */
+goog.i18n.BidiFormatter.prototype.dirAttr = function (text, opt_isHtml) {
+  var dir = soy.$$bidiTextDir(text, opt_isHtml);
+  return dir && dir != this.dir_ ? dir < 0 ? 'dir="rtl"' : 'dir="ltr"' : '';
+};
+
+/**
+ * Returns the trailing horizontal edge, i.e. "right" or "left", depending on
+ * the global bidi directionality.
+ * @return {string} "left" for RTL context and "right" otherwise.
+ */
+goog.i18n.BidiFormatter.prototype.endEdge = function () {
+  return this.dir_ < 0 ? 'left' : 'right';
+};
+
+/**
+ * Returns the Unicode BiDi mark matching the context directionality (LRM for
+ * LTR context directionality, RLM for RTL context directionality), or the
+ * empty string for neutral / unknown context directionality.
+ *
+ * @return {string} LRM for LTR context directionality and RLM for RTL context
+ *     directionality.
+ */
+goog.i18n.BidiFormatter.prototype.mark = function () {
+  return (
+      (this.dir_ > 0) ? '\u200E' /*LRM*/ :
+      (this.dir_ < 0) ? '\u200F' /*RLM*/ :
+      '');
+};
+
+/**
+ * Returns a Unicode BiDi mark matching the context directionality (LRM or RLM)
+ * if the directionality or the exit directionality of {@code text} are opposite
+ * to the context directionality. Otherwise returns the empty string.
+ *
+ * @param {string} text The input text.
+ * @param {boolean=} opt_isHtml Whether {@code text} is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {string} A Unicode bidi mark matching the global directionality or
+ *     the empty string.
+ */
+goog.i18n.BidiFormatter.prototype.markAfter = function (text, opt_isHtml) {
+  var dir = soy.$$bidiTextDir(text, opt_isHtml);
+  return soyshim.$$bidiMarkAfterKnownDir_(this.dir_, dir, text, opt_isHtml);
+};
+
+/**
+ * Formats a string of unknown directionality for use in HTML output of the
+ * context directionality, so an opposite-directionality string is neither
+ * garbled nor garbles what follows it.
+ *
+ * @param {string} str The input text.
+ * @param {boolean=} placeholder This argument exists for consistency with the
+ *     Closure Library. Specifying it has no effect.
+ * @return {string} Input text after applying the above processing.
+ */
+goog.i18n.BidiFormatter.prototype.spanWrap = function(str, placeholder) {
+  str = String(str);
+  var textDir = soy.$$bidiTextDir(str, true);
+  var reset = soyshim.$$bidiMarkAfterKnownDir_(this.dir_, textDir, str, true);
+  if (textDir > 0 && this.dir_ <= 0) {
+    str = '<span dir="ltr">' + str + '</span>';
+  } else if (textDir < 0 && this.dir_ >= 0) {
+    str = '<span dir="rtl">' + str + '</span>';
+  }
+  return str + reset;
+};
+
+/**
+ * Returns the leading horizontal edge, i.e. "left" or "right", depending on
+ * the global bidi directionality.
+ * @return {string} "right" for RTL context and "left" otherwise.
+ */
+goog.i18n.BidiFormatter.prototype.startEdge = function () {
+  return this.dir_ < 0 ? 'right' : 'left';
+};
+
+/**
+ * Formats a string of unknown directionality for use in plain-text output of
+ * the context directionality, so an opposite-directionality string is neither
+ * garbled nor garbles what follows it.
+ * As opposed to {@link #spanWrap}, this makes use of unicode BiDi formatting
+ * characters. In HTML, its *only* valid use is inside of elements that do not
+ * allow mark-up, e.g. an 'option' tag.
+ *
+ * @param {string} str The input text.
+ * @param {boolean=} placeholder This argument exists for consistency with the
+ *     Closure Library. Specifying it has no effect.
+ * @return {string} Input text after applying the above processing.
+ */
+goog.i18n.BidiFormatter.prototype.unicodeWrap = function(str, placeholder) {
+  str = String(str);
+  var textDir = soy.$$bidiTextDir(str, true);
+  var reset = soyshim.$$bidiMarkAfterKnownDir_(this.dir_, textDir, str, true);
+  if (textDir > 0 && this.dir_ <= 0) {
+    str = '\u202A' + str + '\u202C';
+  } else if (textDir < 0 && this.dir_ >= 0) {
+    str = '\u202B' + str + '\u202C';
+  }
+  return str + reset;
+};
+
+
+if (!goog.string) {
+  goog.string = {
+    /**
+     * Converts \r\n, \r, and \n to <br>s
+     * @param {*} str The string in which to convert newlines.
+     * @param {boolean=} opt_xml Whether to use XML compatible tags.
+     * @return {string} A copy of {@code str} with converted newlines.
+     */
+    newLineToBr: function(str, opt_xml) {
+
+      str = String(str);
+
+      // This quick test helps in the case when there are no chars to replace,
+      // in the worst case this makes barely a difference to the time taken.
+      if (!goog.string.NEWLINE_TO_BR_RE_.test(str)) {
+        return str;
+      }
+
+      return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
+    },
+    urlEncode: encodeURIComponent,
+    /**
+     * Regular expression used within newlineToBr().
+     * @type {RegExp}
+     * @private
+     */
+    NEWLINE_TO_BR_RE_: /[\r\n]/
+  };
+}
+
+/**
+ * Utility class to facilitate much faster string concatenation in IE,
+ * using Array.join() rather than the '+' operator. For other browsers
+ * we simply use the '+' operator.
+ *
+ * @param {Object|number|string|boolean=} opt_a1 Optional first initial item
+ *     to append.
+ * @param {...Object|number|string|boolean} var_args Other initial items to
+ *     append, e.g., new goog.string.StringBuffer('foo', 'bar').
+ * @constructor
+ */
+goog.string.StringBuffer = function(opt_a1, var_args) {
+  /**
+   * Internal buffer for the string to be concatenated.
+   * @type {string|Array}
+   * @private
+   */
+  this.buffer_ = goog.userAgent.jscript.HAS_JSCRIPT ? [] : '';
+
+  if (opt_a1 != null) {
+    this.append.apply(this, arguments);
+  }
+};
+
+
+/**
+ * Length of internal buffer (faster than calling buffer_.length).
+ * Only used for IE.
+ * @type {number}
+ * @private
+ */
+goog.string.StringBuffer.prototype.bufferLength_ = 0;
+
+/**
+ * Appends one or more items to the string.
+ *
+ * Calling this with null, undefined, or empty arguments is an error.
+ *
+ * @param {Object|number|string|boolean} a1 Required first string.
+ * @param {Object|number|string|boolean=} opt_a2 Optional second string.
+ * @param {...Object|number|string|boolean} var_args Other items to append,
+ *     e.g., sb.append('foo', 'bar', 'baz').
+ * @return {goog.string.StringBuffer} This same StringBuilder object.
+ */
+goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
+
+  if (goog.userAgent.jscript.HAS_JSCRIPT) {
+    if (opt_a2 == null) {  // no second argument (note: undefined == null)
+      // Array assignment is 2x faster than Array push. Also, use a1
+      // directly to avoid arguments instantiation, another 2x improvement.
+      this.buffer_[this.bufferLength_++] = a1;
+    } else {
+      var arr = /**@type {Array.<number|string|boolean>}*/(this.buffer_);
+      arr.push.apply(arr, arguments);
+      this.bufferLength_ = this.buffer_.length;
+    }
+
+  } else {
+
+    // Use a1 directly to avoid arguments instantiation for single-arg case.
+    this.buffer_ += a1;
+    if (opt_a2 != null) {  // no second argument (note: undefined == null)
+      for (var i = 1; i < arguments.length; i++) {
+        this.buffer_ += arguments[i];
+      }
+    }
+  }
+
+  return this;
+};
+
+
+/**
+ * Clears the string.
+ */
+goog.string.StringBuffer.prototype.clear = function() {
+
+  if (goog.userAgent.jscript.HAS_JSCRIPT) {
+     this.buffer_.length = 0;  // reuse array to avoid creating new object
+     this.bufferLength_ = 0;
+
+   } else {
+     this.buffer_ = '';
+   }
+};
+
+
+/**
+ * Returns the concatenated string.
+ *
+ * @return {string} The concatenated string.
+ */
+goog.string.StringBuffer.prototype.toString = function() {
+
+  if (goog.userAgent.jscript.HAS_JSCRIPT) {
+    var str = this.buffer_.join('');
+    // Given a string with the entire contents, simplify the StringBuilder by
+    // setting its contents to only be this string, rather than many fragments.
+    this.clear();
+    if (str) {
+      this.append(str);
+    }
+    return str;
+
+  } else {
+    return /** @type {string} */ (this.buffer_);
+  }
+};
+
+
+if (!goog.soy) goog.soy = {
+  /**
+   * Helper function to render a Soy template and then set the
+   * output string as the innerHTML of an element. It is recommended
+   * to use this helper function instead of directly setting
+   * innerHTML in your hand-written code, so that it will be easier
+   * to audit the code for cross-site scripting vulnerabilities.
+   *
+   * @param {Function} template The Soy template defining element's content.
+   * @param {Object=} opt_templateData The data for the template.
+   * @param {Object=} opt_injectedData The injected data for the template.
+   * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM
+   *     nodes will be created.
+   */
+  renderAsElement: function(
+    template, opt_templateData, opt_injectedData, opt_dom) {
+    return /** @type {!Element} */ (soyshim.$$renderWithWrapper_(
+        template, opt_templateData, opt_dom, true /* asElement */,
+        opt_injectedData));
+  },
+  /**
+   * Helper function to render a Soy template into a single node or
+   * a document fragment. If the rendered HTML string represents a
+   * single node, then that node is returned (note that this is
+   * *not* a fragment, despite them name of the method). Otherwise a
+   * document fragment is returned containing the rendered nodes.
+   *
+   * @param {Function} template The Soy template defining element's content.
+   * @param {Object=} opt_templateData The data for the template.
+   * @param {Object=} opt_injectedData The injected data for the template.
+   * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM
+   *     nodes will be created.
+   * @return {!Node} The resulting node or document fragment.
+   */
+  renderAsFragment: function(
+    template, opt_templateData, opt_injectedData, opt_dom) {
+    return soyshim.$$renderWithWrapper_(
+        template, opt_templateData, opt_dom, false /* asElement */,
+        opt_injectedData);
+  },
+  /**
+   * Helper function to render a Soy template and then set the output string as
+   * the innerHTML of an element. It is recommended to use this helper function
+   * instead of directly setting innerHTML in your hand-written code, so that it
+   * will be easier to audit the code for cross-site scripting vulnerabilities.
+   *
+   * NOTE: New code should consider using goog.soy.renderElement instead.
+   *
+   * @param {Element} element The element whose content we are rendering.
+   * @param {Function} template The Soy template defining the element's content.
+   * @param {Object=} opt_templateData The data for the template.
+   * @param {Object=} opt_injectedData The injected data for the template.
+   */
+  renderElement: function(
+      element, template, opt_templateData, opt_injectedData) {
+    element.innerHTML = template(opt_templateData, null, opt_injectedData);
+  },
+  data: {}
+};
+
+
+/**
+ * A type of textual content.
+ *
+ * This is an enum of type Object so that these values are unforgeable.
+ *
+ * @enum {!Object}
+ */
+goog.soy.data.SanitizedContentKind = {
+
+  /**
+   * A snippet of HTML that does not start or end inside a tag, comment, entity,
+   * or DOCTYPE; and that does not contain any executable code
+   * (JS, {@code <object>}s, etc.) from a different trust domain.
+   */
+  HTML: {},
+
+  /**
+   * Executable Javascript code or expression, safe for insertion in a
+   * script-tag or event handler context, known to be free of any
+   * attacker-controlled scripts. This can either be side-effect-free
+   * Javascript (such as JSON) or Javascript that entirely under Google's
+   * control.
+   */
+  JS: goog.DEBUG ? {sanitizedContentJsStrChars: true} : {},
+
+  /**
+   * A sequence of code units that can appear between quotes (either kind) in a
+   * JS program without causing a parse error, and without causing any side
+   * effects.
+   * <p>
+   * The content should not contain unescaped quotes, newlines, or anything else
+   * that would cause parsing to fail or to cause a JS parser to finish the
+   * string its parsing inside the content.
+   * <p>
+   * The content must also not end inside an escape sequence ; no partial octal
+   * escape sequences or odd number of '{@code \}'s at the end.
+   */
+  JS_STR_CHARS: {},
+
+  /** A properly encoded portion of a URI. */
+  URI: {},
+
+  /**
+   * Repeated attribute names and values. For example,
+   * {@code dir="ltr" foo="bar" onclick="trustedFunction()" checked}.
+   */
+  ATTRIBUTES: goog.DEBUG ? {sanitizedContentHtmlAttribute: true} : {},
+
+  // TODO: Consider separating rules, declarations, and values into
+  // separate types, but for simplicity, we'll treat explicitly blessed
+  // SanitizedContent as allowed in all of these contexts.
+  /**
+   * A CSS3 declaration, property, value or group of semicolon separated
+   * declarations.
+   */
+  CSS: {},
+
+  /**
+   * Unsanitized plain-text content.
+   *
+   * This is effectively the "null" entry of this enum, and is sometimes used
+   * to explicitly mark content that should never be used unescaped. Since any
+   * string is safe to use as text, being of ContentKind.TEXT makes no
+   * guarantees about its safety in any other context such as HTML.
+   */
+  TEXT: {}
+};
+
+
+
+/**
+ * A string-like object that carries a content-type.
+ *
+ * IMPORTANT! Do not create these directly, nor instantiate the subclasses.
+ * Instead, use a trusted, centrally reviewed library as endorsed by your team
+ * to generate these objects. Otherwise, you risk accidentally creating
+ * SanitizedContent that is attacker-controlled and gets evaluated unescaped in
+ * templates.
+ *
+ * @constructor
+ */
+goog.soy.data.SanitizedContent = function() {
+  throw Error('Do not instantiate directly');
+};
+
+
+/**
+ * The context in which this content is safe from XSS attacks.
+ * @type {goog.soy.data.SanitizedContentKind}
+ */
+goog.soy.data.SanitizedContent.prototype.contentKind;
+
+
+/**
+ * The already-safe content.
+ * @type {string}
+ */
+goog.soy.data.SanitizedContent.prototype.content;
+
+
+/** @override */
+goog.soy.data.SanitizedContent.prototype.toString = function() {
+  return this.content;
+};
+
+
+var soy = { esc: {} };
+var soydata = {};
+soydata.VERY_UNSAFE = {};
+var soyshim = { $$DEFAULT_TEMPLATE_DATA_: {} };
+/**
+ * Helper function to render a Soy template into a single node or a document
+ * fragment. If the rendered HTML string represents a single node, then that
+ * node is returned. Otherwise a document fragment is created and returned
+ * (wrapped in a DIV element if #opt_singleNode is true).
+ *
+ * @param {Function} template The Soy template defining the element's content.
+ * @param {Object=} opt_templateData The data for the template.
+ * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM
+ *     nodes will be created.
+ * @param {boolean=} opt_asElement Whether to wrap the fragment in an
+ *     element if the template does not render a single element. If true,
+ *     result is always an Element.
+ * @param {Object=} opt_injectedData The injected data for the template.
+ * @return {!Node} The resulting node or document fragment.
+ * @private
+ */
+soyshim.$$renderWithWrapper_ = function(
+    template, opt_templateData, opt_dom, opt_asElement, opt_injectedData) {
+
+  var dom = opt_dom || document;
+  var wrapper = dom.createElement('div');
+  wrapper.innerHTML = template(
+    opt_templateData || soyshim.$$DEFAULT_TEMPLATE_DATA_, undefined,
+    opt_injectedData);
+
+  // If the template renders as a single element, return it.
+  if (wrapper.childNodes.length == 1) {
+    var firstChild = wrapper.firstChild;
+    if (!opt_asElement || firstChild.nodeType == 1 /* Element */) {
+      return /** @type {!Node} */ (firstChild);
+    }
+  }
+
+  // If we're forcing it to be a single element, return the wrapper DIV.
+  if (opt_asElement) {
+    return wrapper;
+  }
+
+  // Otherwise, create and return a fragment.
+  var fragment = dom.createDocumentFragment();
+  while (wrapper.firstChild) {
+    fragment.appendChild(wrapper.firstChild);
+  }
+  return fragment;
+};
+
+
+/**
+ * Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the
+ * directionality or the exit directionality of text are opposite to
+ * bidiGlobalDir. Otherwise returns the empty string.
+ * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
+ * in text, making the logic suitable for HTML and HTML-escaped text.
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
+ *     if rtl, 0 if unknown.
+ * @param {number} dir text's directionality: 1 if ltr, -1 if rtl, 0 if unknown.
+ * @param {string} text The text whose directionality is to be estimated.
+ * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
+ *     Default: false.
+ * @return {string} A Unicode bidi mark matching bidiGlobalDir, or
+ *     the empty string when text's overall and exit directionalities both match
+ *     bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
+ * @private
+ */
+soyshim.$$bidiMarkAfterKnownDir_ = function(
+    bidiGlobalDir, dir, text, opt_isHtml) {
+  return (
+      bidiGlobalDir > 0 && (dir < 0 ||
+          soyshim.$$bidiIsRtlExitText_(text, opt_isHtml)) ? '\u200E' : // LRM
+      bidiGlobalDir < 0 && (dir > 0 ||
+          soyshim.$$bidiIsLtrExitText_(text, opt_isHtml)) ? '\u200F' : // RLM
+      '');
+};
+
+
+/**
+ * Strips str of any HTML mark-up and escapes. Imprecise in several ways, but
+ * precision is not very important, since the result is only meant to be used
+ * for directionality detection.
+ * @param {string} str The string to be stripped.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {string} The stripped string.
+ * @private
+ */
+soyshim.$$bidiStripHtmlIfNecessary_ = function(str, opt_isHtml) {
+  return opt_isHtml ? str.replace(soyshim.$$BIDI_HTML_SKIP_RE_, ' ') : str;
+};
+
+
+/**
+ * Simplified regular expression for am HTML tag (opening or closing) or an HTML
+ * escape - the things we want to skip over in order to ignore their ltr
+ * characters.
+ * @type {RegExp}
+ * @private
+ */
+soyshim.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g;
+
+
+/**
+ * A practical pattern to identify strong LTR character. This pattern is not
+ * theoretically correct according to unicode standard. It is simplified for
+ * performance and small code size.
+ * @type {string}
+ * @private
+ */
+soyshim.$$bidiLtrChars_ =
+    'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
+    '\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
+
+
+/**
+ * A practical pattern to identify strong neutral and weak character. This
+ * pattern is not theoretically correct according to unicode standard. It is
+ * simplified for performance and small code size.
+ * @type {string}
+ * @private
+ */
+soyshim.$$bidiNeutralChars_ =
+    '\u0000-\u0020!-@[-`{-\u00BF\u00D7\u00F7\u02B9-\u02FF\u2000-\u2BFF';
+
+
+/**
+ * A practical pattern to identify strong RTL character. This pattern is not
+ * theoretically correct according to unicode standard. It is simplified for
+ * performance and small code size.
+ * @type {string}
+ * @private
+ */
+soyshim.$$bidiRtlChars_ = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
+
+
+/**
+ * Regular expressions to check if a piece of text is of RTL directionality
+ * on first character with strong directionality.
+ * @type {RegExp}
+ * @private
+ */
+soyshim.$$bidiRtlDirCheckRe_ = new RegExp(
+    '^[^' + soyshim.$$bidiLtrChars_ + ']*[' + soyshim.$$bidiRtlChars_ + ']');
+
+
+/**
+ * Regular expressions to check if a piece of text is of neutral directionality.
+ * Url are considered as neutral.
+ * @type {RegExp}
+ * @private
+ */
+soyshim.$$bidiNeutralDirCheckRe_ = new RegExp(
+    '^[' + soyshim.$$bidiNeutralChars_ + ']*$|^http://');
+
+
+/**
+ * Check the directionality of the a piece of text based on the first character
+ * with strong directionality.
+ * @param {string} str string being checked.
+ * @return {boolean} return true if rtl directionality is being detected.
+ * @private
+ */
+soyshim.$$bidiIsRtlText_ = function(str) {
+  return soyshim.$$bidiRtlDirCheckRe_.test(str);
+};
+
+
+/**
+ * Check the directionality of the a piece of text based on the first character
+ * with strong directionality.
+ * @param {string} str string being checked.
+ * @return {boolean} true if all characters have neutral directionality.
+ * @private
+ */
+soyshim.$$bidiIsNeutralText_ = function(str) {
+  return soyshim.$$bidiNeutralDirCheckRe_.test(str);
+};
+
+
+/**
+ * This constant controls threshold of rtl directionality.
+ * @type {number}
+ * @private
+ */
+soyshim.$$bidiRtlDetectionThreshold_ = 0.40;
+
+
+/**
+ * Returns the RTL ratio based on word count.
+ * @param {string} str the string that need to be checked.
+ * @return {number} the ratio of RTL words among all words with directionality.
+ * @private
+ */
+soyshim.$$bidiRtlWordRatio_ = function(str) {
+  var rtlCount = 0;
+  var totalCount = 0;
+  var tokens = str.split(' ');
+  for (var i = 0; i < tokens.length; i++) {
+    if (soyshim.$$bidiIsRtlText_(tokens[i])) {
+      rtlCount++;
+      totalCount++;
+    } else if (!soyshim.$$bidiIsNeutralText_(tokens[i])) {
+      totalCount++;
+    }
+  }
+
+  return totalCount == 0 ? 0 : rtlCount / totalCount;
+};
+
+
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is LTR.
+ * @type {RegExp}
+ * @private
+ */
+soyshim.$$bidiLtrExitDirCheckRe_ = new RegExp(
+    '[' + soyshim.$$bidiLtrChars_ + '][^' + soyshim.$$bidiRtlChars_ + ']*$');
+
+
+/**
+ * Regular expressions to check if the last strongly-directional character in a
+ * piece of text is RTL.
+ * @type {RegExp}
+ * @private
+ */
+soyshim.$$bidiRtlExitDirCheckRe_ = new RegExp(
+    '[' + soyshim.$$bidiRtlChars_ + '][^' + soyshim.$$bidiLtrChars_ + ']*$');
+
+
+/**
+ * Check if the exit directionality a piece of text is LTR, i.e. if the last
+ * strongly-directional character in the string is LTR.
+ * @param {string} str string being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether LTR exit directionality was detected.
+ * @private
+ */
+soyshim.$$bidiIsLtrExitText_ = function(str, opt_isHtml) {
+  str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
+  return soyshim.$$bidiLtrExitDirCheckRe_.test(str);
+};
+
+
+/**
+ * Check if the exit directionality a piece of text is RTL, i.e. if the last
+ * strongly-directional character in the string is RTL.
+ * @param {string} str string being checked.
+ * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
+ *     Default: false.
+ * @return {boolean} Whether RTL exit directionality was detected.
+ * @private
+ */
+soyshim.$$bidiIsRtlExitText_ = function(str, opt_isHtml) {
+  str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
+  return soyshim.$$bidiRtlExitDirCheckRe_.test(str);
+};
+
+
+// =============================================================================
+// COPIED FROM soyutils_usegoog.js
+
+
+// -----------------------------------------------------------------------------
+// StringBuilder (compatible with the 'stringbuilder' code style).
+
+
+/**
+ * Utility class to facilitate much faster string concatenation in IE,
+ * using Array.join() rather than the '+' operator. For other browsers
+ * we simply use the '+' operator.
+ *
+ * @param {Object} var_args Initial items to append,
+ *     e.g., new soy.StringBuilder('foo', 'bar').
+ * @constructor
+ */
+soy.StringBuilder = goog.string.StringBuffer;
+
+
+// -----------------------------------------------------------------------------
+// soydata: Defines typed strings, e.g. an HTML string {@code "a<b>c"} is
+// semantically distinct from the plain text string {@code "a<b>c"} and smart
+// templates can take that distinction into account.
+
+/**
+ * A type of textual content.
+ *
+ * This is an enum of type Object so that these values are unforgeable.
+ *
+ * @enum {!Object}
+ */
+soydata.SanitizedContentKind = goog.soy.data.SanitizedContentKind;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.HTML}.
+ *
+ * The content is a string of HTML that can safely be embedded in a PCDATA
+ * context in your app.  If you would be surprised to find that an HTML
+ * sanitizer produced {@code s} (e.g.  it runs code or fetches bad URLs) and
+ * you wouldn't write a template that produces {@code s} on security or privacy
+ * grounds, then don't pass {@code s} here.
+ *
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.SanitizedHtml = function() {
+  goog.soy.data.SanitizedContent.call(this);  // Throws an exception.
+};
+goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.JS}.
+ *
+ * The content is Javascript source that when evaluated does not execute any
+ * attacker-controlled scripts.
+ *
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.SanitizedJs = function() {
+  goog.soy.data.SanitizedContent.call(this);  // Throws an exception.
+};
+goog.inherits(soydata.SanitizedJs, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.SanitizedJs.prototype.contentKind =
+    soydata.SanitizedContentKind.JS;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}.
+ *
+ * The content can be safely inserted as part of a single- or double-quoted
+ * string without terminating the string.
+ *
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.SanitizedJsStrChars = function() {
+  goog.soy.data.SanitizedContent.call(this);  // Throws an exception.
+};
+goog.inherits(soydata.SanitizedJsStrChars, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.SanitizedJsStrChars.prototype.contentKind =
+    soydata.SanitizedContentKind.JS_STR_CHARS;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.URI}.
+ *
+ * The content is a URI chunk that the caller knows is safe to emit in a
+ * template.
+ *
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.SanitizedUri = function() {
+  goog.soy.data.SanitizedContent.call(this);  // Throws an exception.
+};
+goog.inherits(soydata.SanitizedUri, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.ATTRIBUTES}.
+ *
+ * The content should be safely embeddable within an open tag, such as a
+ * key="value" pair.
+ *
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.SanitizedHtmlAttribute = function() {
+  goog.soy.data.SanitizedContent.call(this);  // Throws an exception.
+};
+goog.inherits(soydata.SanitizedHtmlAttribute, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.SanitizedHtmlAttribute.prototype.contentKind =
+    soydata.SanitizedContentKind.ATTRIBUTES;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.CSS}.
+ *
+ * The content is non-attacker-exploitable CSS, such as {@code color:#c3d9ff}.
+ *
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.SanitizedCss = function() {
+  goog.soy.data.SanitizedContent.call(this);  // Throws an exception.
+};
+goog.inherits(soydata.SanitizedCss, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.SanitizedCss.prototype.contentKind =
+    soydata.SanitizedContentKind.CSS;
+
+
+/**
+ * Unsanitized plain text string.
+ *
+ * While all strings are effectively safe to use as a plain text, there are no
+ * guarantees about safety in any other context such as HTML. This is
+ * sometimes used to mark that should never be used unescaped.
+ *
+ * @param {*} content Plain text with no guarantees.
+ * @constructor
+ * @extends {goog.soy.data.SanitizedContent}
+ */
+soydata.UnsanitizedText = function(content) {
+  /** @override */
+  this.content = String(content);
+};
+goog.inherits(soydata.UnsanitizedText, goog.soy.data.SanitizedContent);
+
+/** @override */
+soydata.UnsanitizedText.prototype.contentKind =
+    soydata.SanitizedContentKind.TEXT;
+
+
+/**
+ * Creates a factory for SanitizedContent types.
+ *
+ * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can
+ * instantiate Sanitized* classes, without making the Sanitized* constructors
+ * publicly usable. Requiring all construction to use the VERY_UNSAFE names
+ * helps callers and their reviewers easily tell that creating SanitizedContent
+ * is not always safe and calls for careful review.
+ *
+ * @param {function(new: T, string)} ctor A constructor.
+ * @return {!function(*): T} A factory that takes content and returns a
+ *     new instance.
+ * @template T
+ * @private
+ */
+soydata.$$makeSanitizedContentFactory_ = function(ctor) {
+  /** @constructor */
+  function InstantiableCtor() {}
+  InstantiableCtor.prototype = ctor.prototype;
+  return function(content) {
+    var result = new InstantiableCtor();
+    result.content = String(content);
+    return result;
+  };
+};
+
+
+// -----------------------------------------------------------------------------
+// Sanitized content ordainers. Please use these with extreme caution (with the
+// exception of markUnsanitizedText). A good recommendation is to limit usage
+// of these to just a handful of files in your source tree where usages can be
+// carefully audited.
+
+
+/**
+ * Protects a string from being used in an noAutoescaped context.
+ *
+ * This is useful for content where there is significant risk of accidental
+ * unescaped usage in a Soy template. A great case is for user-controlled
+ * data that has historically been a source of vulernabilities.
+ *
+ * @param {*} content Text to protect.
+ * @return {!soydata.UnsanitizedText} A wrapper that is rejected by the
+ *     Soy noAutoescape print directive.
+ */
+soydata.markUnsanitizedText = function(content) {
+  return new soydata.UnsanitizedText(content);
+};
+
+
+/**
+ * Takes a leap of faith that the provided content is "safe" HTML.
+ *
+ * @param {*} content A string of HTML that can safely be embedded in
+ *     a PCDATA context in your app. If you would be surprised to find that an
+ *     HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs)
+ *     and you wouldn't write a template that produces {@code s} on security or
+ *     privacy grounds, then don't pass {@code s} here.
+ * @return {!soydata.SanitizedHtml} Sanitized content wrapper that
+ *     indicates to Soy not to escape when printed as HTML.
+ */
+soydata.VERY_UNSAFE.ordainSanitizedHtml =
+    soydata.$$makeSanitizedContentFactory_(soydata.SanitizedHtml);
+
+
+/**
+ * Takes a leap of faith that the provided content is "safe" (non-attacker-
+ * controlled, XSS-free) Javascript.
+ *
+ * @param {*} content Javascript source that when evaluated does not
+ *     execute any attacker-controlled scripts.
+ * @return {!soydata.SanitizedJs} Sanitized content wrapper that indicates to
+ *     Soy not to escape when printed as Javascript source.
+ */
+soydata.VERY_UNSAFE.ordainSanitizedJs =
+    soydata.$$makeSanitizedContentFactory_(soydata.SanitizedJs);
+
+
+// TODO: This function is probably necessary, either externally or internally
+// as an implementation detail. Generally, plain text will always work here,
+// as there's no harm to unescaping the string and then re-escaping when
+// finally printed.
+/**
+ * Takes a leap of faith that the provided content can be safely embedded in
+ * a Javascript string without re-esacping.
+ *
+ * @param {*} content Content that can be safely inserted as part of a
+ *     single- or double-quoted string without terminating the string.
+ * @return {!soydata.SanitizedJsStrChars} Sanitized content wrapper that
+ *     indicates to Soy not to escape when printed in a JS string.
+ */
+soydata.VERY_UNSAFE.ordainSanitizedJsStrChars =
+    soydata.$$makeSanitizedContentFactory_(soydata.SanitizedJsStrChars);
+
+
+/**
+ * Takes a leap of faith that the provided content is "safe" to use as a URI
+ * in a Soy template.
+ *
+ * This creates a Soy SanitizedContent object which indicates to Soy there is
+ * no need to escape it when printed as a URI (e.g. in an href or src
+ * attribute), such as if it's already been encoded or  if it's a Javascript:
+ * URI.
+ *
+ * @param {*} content A chunk of URI that the caller knows is safe to
+ *     emit in a template.
+ * @return {!soydata.SanitizedUri} Sanitized content wrapper that indicates to
+ *     Soy not to escape or filter when printed in URI context.
+ */
+soydata.VERY_UNSAFE.ordainSanitizedUri =
+    soydata.$$makeSanitizedContentFactory_(soydata.SanitizedUri);
+
+
+/**
+ * Takes a leap of faith that the provided content is "safe" to use as an
+ * HTML attribute.
+ *
+ * @param {*} content An attribute name and value, such as
+ *     {@code dir="ltr"}.
+ * @return {!soydata.SanitizedHtmlAttribute} Sanitized content wrapper that
+ *     indicates to Soy not to escape when printed as an HTML attribute.
+ */
+soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute =
+    soydata.$$makeSanitizedContentFactory_(soydata.SanitizedHtmlAttribute);
+
+
+/**
+ * Takes a leap of faith that the provided content is "safe" to use as CSS
+ * in a style attribute or block.
+ *
+ * @param {*} content CSS, such as {@code color:#c3d9ff}.
+ * @return {!soydata.SanitizedCss} Sanitized CSS wrapper that indicates to
+ *     Soy there is no need to escape or filter when printed in CSS context.
+ */
+soydata.VERY_UNSAFE.ordainSanitizedCss =
+    soydata.$$makeSanitizedContentFactory_(soydata.SanitizedCss);
+
+
+// -----------------------------------------------------------------------------
+// Public utilities.
+
+
+/**
+ * Helper function to render a Soy template and then set the output string as
+ * the innerHTML of an element. It is recommended to use this helper function
+ * instead of directly setting innerHTML in your hand-written code, so that it
+ * will be easier to audit the code for cross-site scripting vulnerabilities.
+ *
+ * NOTE: New code should consider using goog.soy.renderElement instead.
+ *
+ * @param {Element} element The element whose content we are rendering.
+ * @param {Function} template The Soy template defining the element's content.
+ * @param {Object=} opt_templateData The data for the template.
+ * @param {Object=} opt_injectedData The injected data for the template.
+ */
+soy.renderElement = goog.soy.renderElement;
+
+
+/**
+ * Helper function to render a Soy template into a single node or a document
+ * fragment. If the rendered HTML string represents a single node, then that
+ * node is returned (note that this is *not* a fragment, despite them name of
+ * the method). Otherwise a document fragment is returned containing the
+ * rendered nodes.
+ *
+ * NOTE: New code should consider using goog.soy.renderAsFragment
+ * instead (note that the arguments are different).
+ *
+ * @param {Function} template The Soy template defining the element's content.
+ * @param {Object=} opt_templateData The data for the template.
+ * @param {Document=} opt_document The document used to create DOM nodes. If not
+ *     specified, global document object is used.
+ * @param {Object=} opt_injectedData The injected data for the template.
+ * @return {!Node} The resulting node or document fragment.
+ */
+soy.renderAsFragment = function(
+    template, opt_templateData, opt_document, opt_injectedData) {
+  return goog.soy.renderAsFragment(
+      template, opt_templateData, opt_injectedData,
+      new goog.dom.DomHelper(opt_document));
+};
+
+
+/**
+ * Helper function to render a Soy template into a single node. If the rendered
+ * HTML string represents a single node, then that node is returned. Otherwise,
+ * a DIV element is returned containing the rendered nodes.
+ *
+ * NOTE: New code should consider using goog.soy.renderAsElement
+ * instead (note that the arguments are different).
+ *
+ * @param {Function} template The Soy template defining the element's content.
+ * @param {Object=} opt_templateData The data for the template.
+ * @param {Document=} opt_document The document used to create DOM nodes. If not
+ *     specified, global document object is used.
+ * @param {Object=} opt_injectedData The injected data for the template.
+ * @return {!Element} Rendered template contents, wrapped in a parent DIV
+ *     element if necessary.
+ */
+soy.renderAsElement = function(
+    template, opt_templateData, opt_document, opt_injectedData) {
+  return goog.soy.renderAsElement(
+      template, opt_templateData, opt_injectedData,
+      new goog.dom.DomHelper(opt_document));
+};
+
+
+// -----------------------------------------------------------------------------
+// Below are private utilities to be used by Soy-generated code only.
+
+
+/**
+ * Builds an augmented map. The returned map will contain mappings from both
+ * the base map and the additional map. If the same key appears in both, then
+ * the value from the additional map will be visible, while the value from the
+ * base map will be hidden. The base map will be used, but not modified.
+ *
+ * @param {!Object} baseMap The original map to augment.
+ * @param {!Object} additionalMap A map containing the additional mappings.
+ * @return {!Object} An augmented map containing both the original and
+ *     additional mappings.
+ */
+soy.$$augmentMap = function(baseMap, additionalMap) {
+
+  // Create a new map whose '__proto__' field is set to baseMap.
+  /** @constructor */
+  function TempCtor() {}
+  TempCtor.prototype = baseMap;
+  var augmentedMap = new TempCtor();
+
+  // Add the additional mappings to the new map.
+  for (var key in additionalMap) {
+    augmentedMap[key] = additionalMap[key];
+  }
+
+  return augmentedMap;
+};
+
+
+/**
+ * Checks that the given map key is a string.
+ * @param {*} key Key to check.
+ * @return {string} The given key.
+ */
+soy.$$checkMapKey = function(key) {
+  if ((typeof key) != 'string') {
+    throw Error(
+        'Map literal\'s key expression must evaluate to string' +
+        ' (encountered type "' + (typeof key) + '").');
+  }
+  return key;
+};
+
+
+/**
+ * Gets the keys in a map as an array. There are no guarantees on the order.
+ * @param {Object} map The map to get the keys of.
+ * @return {Array.<string>} The array of keys in the given map.
+ */
+soy.$$getMapKeys = function(map) {
+  var mapKeys = [];
+  for (var key in map) {
+    mapKeys.push(key);
+  }
+  return mapKeys;
+};
+
+
+/**
+ * Gets a consistent unique id for the given delegate template name. Two calls
+ * to this function will return the same id if and only if the input names are
+ * the same.
+ *
+ * <p> Important: This function must always be called with a string constant.
+ *
+ * <p> If Closure Compiler is not being used, then this is just this identity
+ * function. If Closure Compiler is being used, then each call to this function
+ * will be replaced with a short string constant, which will be consistent per
+ * input name.
+ *
+ * @param {string} delTemplateName The delegate template name for which to get a
+ *     consistent unique id.
+ * @return {string} A unique id that is consistent per input name.
+ *
+ * @consistentIdGenerator
+ */
+soy.$$getDelTemplateId = function(delTemplateName) {
+  return delTemplateName;
+};
+
+
+/**
+ * Map from registered delegate template key to the priority of the
+ * implementation.
+ * @type {Object}
+ * @private
+ */
+soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {};
+
+/**
+ * Map from registered delegate template key to the implementation function.
+ * @type {Object}
+ * @private
+ */
+soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {};
+
+
+/**
+ * Registers a delegate implementation. If the same delegate template key (id
+ * and variant) has been registered previously, then priority values are
+ * compared and only the higher priority implementation is stored (if
+ * priorities are equal, an error is thrown).
+ *
+ * @param {string} delTemplateId The delegate template id.
+ * @param {string} delTemplateVariant The delegate template variant (can be
+ *     empty string).
+ * @param {number} delPriority The implementation's priority value.
+ * @param {Function} delFn The implementation function.
+ */
+soy.$$registerDelegateFn = function(
+    delTemplateId, delTemplateVariant, delPriority, delFn) {
+
+  var mapKey = 'key_' + delTemplateId + ':' + delTemplateVariant;
+  var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey];
+  if (currPriority === undefined || delPriority > currPriority) {
+    // Registering new or higher-priority function: replace registry entry.
+    soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey] = delPriority;
+    soy.$$DELEGATE_REGISTRY_FUNCTIONS_[mapKey] = delFn;
+  } else if (delPriority == currPriority) {
+    // Registering same-priority function: error.
+    throw Error(
+        'Encountered two active delegates with the same priority ("' +
+            delTemplateId + ':' + delTemplateVariant + '").');
+  } else {
+    // Registering lower-priority function: do nothing.
+  }
+};
+
+
+/**
+ * Retrieves the (highest-priority) implementation that has been registered for
+ * a given delegate template key (id and variant). If no implementation has
+ * been registered for the key, then the fallback is the same id with empty
+ * variant. If the fallback is also not registered, and allowsEmptyDefault is
+ * true, then returns an implementation that is equivalent to an empty template
+ * (i.e. rendered output would be empty string).
+ *
+ * @param {string} delTemplateId The delegate template id.
+ * @param {string} delTemplateVariant The delegate template variant (can be
+ *     empty string).
+ * @param {boolean} allowsEmptyDefault Whether to default to the empty template
+ *     function if there's no active implementation.
+ * @return {Function} The retrieved implementation function.
+ */
+soy.$$getDelegateFn = function(
+    delTemplateId, delTemplateVariant, allowsEmptyDefault) {
+
+  var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_[
+      'key_' + delTemplateId + ':' + delTemplateVariant];
+  if (! delFn && delTemplateVariant != '') {
+    // Fallback to empty variant.
+    delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':'];
+  }
+
+  if (delFn) {
+    return delFn;
+  } else if (allowsEmptyDefault) {
+    return soy.$$EMPTY_TEMPLATE_FN_;
+  } else {
+    throw Error(
+        'Found no active impl for delegate call to "' + delTemplateId + ':' +
+            delTemplateVariant + '" (and not allowemptydefault="true").');
+  }
+};
+
+
+/**
+ * Private helper soy.$$getDelegateFn(). This is the empty template function
+ * that is returned whenever there's no delegate implementation found.
+ *
+ * @param {Object.<string, *>=} opt_data
+ * @param {soy.StringBuilder=} opt_sb
+ * @param {Object.<string, *>=} opt_ijData
+ * @return {string}
+ * @private
+ */
+soy.$$EMPTY_TEMPLATE_FN_ = function(opt_data, opt_sb, opt_ijData) {
+  return '';
+};
+
+
+// -----------------------------------------------------------------------------
+// Escape/filter/normalize.
+
+
+/**
+ * Escapes HTML special characters in a string. Escapes double quote '"' in
+ * addition to '&', '<', and '>' so that a string can be included in an HTML
+ * tag attribute value within double quotes.
+ * Will emit known safe HTML as-is.
+ *
+ * @param {*} value The string-like value to be escaped. May not be a string,
+ *     but the value will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeHtml = function(value) {
+  // TODO: Perhaps we should just ignore the contentKind property and instead
+  // look only at the constructor.
+  if (value && value.contentKind &&
+      value.contentKind === goog.soy.data.SanitizedContentKind.HTML) {
+    goog.asserts.assert(
+        value.constructor === soydata.SanitizedHtml);
+    return value.content;
+  }
+  return soy.esc.$$escapeHtmlHelper(value);
+};
+
+
+/**
+ * Strips unsafe tags to convert a string of untrusted HTML into HTML that
+ * is safe to embed.
+ *
+ * @param {*} value The string-like value to be escaped. May not be a string,
+ *     but the value will be coerced to a string.
+ * @return {string} A sanitized and normalized version of value.
+ */
+soy.$$cleanHtml = function(value) {
+  if (value && value.contentKind &&
+      value.contentKind === goog.soy.data.SanitizedContentKind.HTML) {
+    goog.asserts.assert(
+        value.constructor === soydata.SanitizedHtml);
+    return value.content;
+  }
+  return soy.$$stripHtmlTags(value, soy.esc.$$SAFE_TAG_WHITELIST_);
+};
+
+
+/**
+ * Escapes HTML special characters in a string so that it can be embedded in
+ * RCDATA.
+ * <p>
+ * Escapes HTML special characters so that the value will not prematurely end
+ * the body of a tag like {@code <textarea>} or {@code <title>}. RCDATA tags
+ * cannot contain other HTML entities, so it is not strictly necessary to escape
+ * HTML special characters except when part of that text looks like an HTML
+ * entity or like a close tag : {@code </textarea>}.
+ * <p>
+ * Will normalize known safe HTML to make sure that sanitized HTML (which could
+ * contain an innocuous {@code </textarea>} don't prematurely end an RCDATA
+ * element.
+ *
+ * @param {*} value The string-like value to be escaped. May not be a string,
+ *     but the value will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeHtmlRcdata = function(value) {
+  if (value && value.contentKind &&
+      value.contentKind === goog.soy.data.SanitizedContentKind.HTML) {
+    goog.asserts.assert(
+        value.constructor === soydata.SanitizedHtml);
+    return soy.esc.$$normalizeHtmlHelper(value.content);
+  }
+  return soy.esc.$$escapeHtmlHelper(value);
+};
+
+
+/**
+ * Matches any/only HTML5 void elements' start tags.
+ * See http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
+ * @type {RegExp}
+ * @private
+ */
+soy.$$HTML5_VOID_ELEMENTS_ = new RegExp(
+    '^<(?:area|base|br|col|command|embed|hr|img|input' +
+    '|keygen|link|meta|param|source|track|wbr)\\b');
+
+
+/**
+ * Removes HTML tags from a string of known safe HTML.
+ * If opt_tagWhitelist is not specified or is empty, then
+ * the result can be used as an attribute value.
+ *
+ * @param {*} value The HTML to be escaped. May not be a string, but the
+ *     value will be coerced to a string.
+ * @param {Object.<string, number>=} opt_tagWhitelist Has an own property whose
+ *     name is a lower-case tag name and whose value is {@code 1} for
+ *     each element that is allowed in the output.
+ * @return {string} A representation of value without disallowed tags,
+ *     HTML comments, or other non-text content.
+ */
+soy.$$stripHtmlTags = function(value, opt_tagWhitelist) {
+  if (!opt_tagWhitelist) {
+    // If we have no white-list, then use a fast track which elides all tags.
+    return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '')
+        // This is just paranoia since callers should normalize the result
+        // anyway, but if they didn't, it would be necessary to ensure that
+        // after the first replace non-tag uses of < do not recombine into
+        // tags as in "<<foo>script>alert(1337)</<foo>script>".
+        .replace(soy.esc.$$LT_REGEX_, '&lt;');
+  }
+
+  // Escapes '[' so that we can use [123] below to mark places where tags
+  // have been removed.
+  var html = String(value).replace(/\[/g, '&#91;');
+
+  // Consider all uses of '<' and replace whitelisted tags with markers like
+  // [1] which are indices into a list of approved tag names.
+  // Replace all other uses of < and > with entities.
+  var tags = [];
+  html = html.replace(
+    soy.esc.$$HTML_TAG_REGEX_,
+    function(tok, tagName) {
+      if (tagName) {
+        tagName = tagName.toLowerCase();
+        if (opt_tagWhitelist.hasOwnProperty(tagName) &&
+            opt_tagWhitelist[tagName]) {
+          var start = tok.charAt(1) === '/' ? '</' : '<';
+          var index = tags.length;
+          tags[index] = start + tagName + '>';
+          return '[' + index + ']';
+        }
+      }
+      return '';
+    });
+
+  // Escape HTML special characters. Now there are no '<' in html that could
+  // start a tag.
+  html = soy.esc.$$normalizeHtmlHelper(html);
+
+  var finalCloseTags = soy.$$balanceTags_(tags);
+
+  // Now html contains no tags or less-than characters that could become
+  // part of a tag via a replacement operation and tags only contains
+  // approved tags.
+  // Reinsert the white-listed tags.
+  html = html.replace(
+       /\[(\d+)\]/g, function(_, index) { return tags[index]; });
+
+  // Close any still open tags.
+  // This prevents unclosed formatting elements like <ol> and <table> from
+  // breaking the layout of containing HTML.
+  return html + finalCloseTags;
+};
+
+
+/**
+ * Throw out any close tags that don't correspond to start tags.
+ * If {@code <table>} is used for formatting, embedded HTML shouldn't be able
+ * to use a mismatched {@code </table>} to break page layout.
+ *
+ * @param {Array.<string>} tags an array of tags that will be modified in place
+ *    include tags, the empty string, or concatenations of empty tags.
+ * @return {string} zero or more closed tags that close all elements that are
+ *    opened in tags but not closed.
+ * @private
+ */
+soy.$$balanceTags_ = function(tags) {
+  var open = [];
+  for (var i = 0, n = tags.length; i < n; ++i) {
+    var tag = tags[i];
+    if (tag.charAt(1) === '/') {
+      var openTagIndex = open.length - 1;
+      // NOTE: This is essentially lastIndexOf, but it's not supported in IE.
+      while (openTagIndex >= 0 && open[openTagIndex] != tag) {
+        openTagIndex--;
+      }
+      if (openTagIndex < 0) {
+        tags[i] = '';  // Drop close tag.
+      } else {
+        tags[i] = open.slice(openTagIndex).reverse().join('');
+        open.length = openTagIndex;
+      }
+    } else if (!soy.$$HTML5_VOID_ELEMENTS_.test(tag)) {
+      open.push('</' + tag.substring(1));
+    }
+  }
+  return open.reverse().join('');
+};
+
+
+/**
+ * Escapes HTML special characters in an HTML attribute value.
+ *
+ * @param {*} value The HTML to be escaped. May not be a string, but the
+ *     value will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeHtmlAttribute = function(value) {
+  if (value && value.contentKind) {
+    // NOTE: We don't accept ATTRIBUTES here because ATTRIBUTES is
+    // actually not the attribute value context, but instead k/v pairs.
+    if (value.contentKind === goog.soy.data.SanitizedContentKind.HTML) {
+      // NOTE: After removing tags, we also escape quotes ("normalize") so that
+      // the HTML can be embedded in attribute context.
+      goog.asserts.assert(
+          value.constructor === soydata.SanitizedHtml);
+      return soy.esc.$$normalizeHtmlHelper(soy.$$stripHtmlTags(value.content));
+    }
+  }
+  return soy.esc.$$escapeHtmlHelper(value);
+};
+
+
+/**
+ * Escapes HTML special characters in a string including space and other
+ * characters that can end an unquoted HTML attribute value.
+ *
+ * @param {*} value The HTML to be escaped. May not be a string, but the
+ *     value will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeHtmlAttributeNospace = function(value) {
+  if (value && value.contentKind) {
+    if (value.contentKind === goog.soy.data.SanitizedContentKind.HTML) {
+      goog.asserts.assert(value.constructor ===
+          soydata.SanitizedHtml);
+      return soy.esc.$$normalizeHtmlNospaceHelper(
+          soy.$$stripHtmlTags(value.content));
+    }
+  }
+  return soy.esc.$$escapeHtmlNospaceHelper(value);
+};
+
+
+/**
+ * Filters out strings that cannot be a substring of a valid HTML attribute.
+ *
+ * Note the input is expected to be key=value pairs.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} A valid HTML attribute name part or name/value pair.
+ *     {@code "zSoyz"} if the input is invalid.
+ */
+soy.$$filterHtmlAttributes = function(value) {
+  // NOTE: Explicitly no support for SanitizedContentKind.HTML, since that is
+  // meaningless in this context, which is generally *between* html attributes.
+  if (value &&
+      value.contentKind === goog.soy.data.SanitizedContentKind.ATTRIBUTES) {
+    goog.asserts.assert(value.constructor ===
+        soydata.SanitizedHtmlAttribute);
+    // Add a space at the end to ensure this won't get merged into following
+    // attributes, unless the interpretation is unambiguous (ending with quotes
+    // or a space).
+    return value.content.replace(/([^"'\s])$/, '$1 ');
+  }
+  // TODO: Dynamically inserting attributes that aren't marked as trusted is
+  // probably unnecessary.  Any filtering done here will either be inadequate
+  // for security or not flexible enough.  Having clients use kind="attributes"
+  // in parameters seems like a wiser idea.
+  return soy.esc.$$filterHtmlAttributesHelper(value);
+};
+
+
+/**
+ * Filters out strings that cannot be a substring of a valid HTML element name.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} A valid HTML element name part.
+ *     {@code "zSoyz"} if the input is invalid.
+ */
+soy.$$filterHtmlElementName = function(value) {
+  // NOTE: We don't accept any SanitizedContent here. HTML indicates valid
+  // PCDATA, not tag names. A sloppy developer shouldn't be able to cause an
+  // exploit:
+  // ... {let userInput}script src=http://evil.com/evil.js{/let} ...
+  // ... {param tagName kind="html"}{$userInput}{/param} ...
+  // ... <{$tagName}>Hello World</{$tagName}>
+  return soy.esc.$$filterHtmlElementNameHelper(value);
+};
+
+
+/**
+ * Escapes characters in the value to make it valid content for a JS string
+ * literal.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ * @deprecated
+ */
+soy.$$escapeJs = function(value) {
+  return soy.$$escapeJsString(value);
+};
+
+
+/**
+ * Escapes characters in the value to make it valid content for a JS string
+ * literal.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeJsString = function(value) {
+  if (value &&
+      value.contentKind === goog.soy.data.SanitizedContentKind.JS_STR_CHARS) {
+    // TODO: It might still be worthwhile to normalize it to remove
+    // unescaped quotes, null, etc: replace(/(?:^|[^\])['"]/g, '\\$
+    goog.asserts.assert(value.constructor ===
+        soydata.SanitizedJsStrChars);
+    return value.content;
+  }
+  return soy.esc.$$escapeJsStringHelper(value);
+};
+
+
+/**
+ * Encodes a value as a JavaScript literal.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} A JavaScript code representation of the input.
+ */
+soy.$$escapeJsValue = function(value) {
+  // We surround values with spaces so that they can't be interpolated into
+  // identifiers by accident.
+  // We could use parentheses but those might be interpreted as a function call.
+  if (value == null) {  // Intentionally matches undefined.
+    // Java returns null from maps where there is no corresponding key while
+    // JS returns undefined.
+    // We always output null for compatibility with Java which does not have a
+    // distinct undefined value.
+    return ' null ';
+  }
+  if (value.contentKind == goog.soy.data.SanitizedContentKind.JS) {
+    goog.asserts.assert(value.constructor ===
+        soydata.SanitizedJs);
+    return value.content;
+  }
+  switch (typeof value) {
+    case 'boolean': case 'number':
+      return ' ' + value + ' ';
+    default:
+      return "'" + soy.esc.$$escapeJsStringHelper(String(value)) + "'";
+  }
+};
+
+
+/**
+ * Escapes characters in the string to make it valid content for a JS regular
+ * expression literal.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeJsRegex = function(value) {
+  return soy.esc.$$escapeJsRegexHelper(value);
+};
+
+
+/**
+ * Matches all URI mark characters that conflict with HTML attribute delimiters
+ * or that cannot appear in a CSS uri.
+ * From <a href="http://www.w3.org/TR/CSS2/grammar.html">G.2: CSS grammar</a>
+ * <pre>
+ *     url        ([!#$%&*-~]|{nonascii}|{escape})*
+ * </pre>
+ *
+ * @type {RegExp}
+ * @private
+ */
+soy.$$problematicUriMarks_ = /['()]/g;
+
+/**
+ * @param {string} ch A single character in {@link soy.$$problematicUriMarks_}.
+ * @return {string}
+ * @private
+ */
+soy.$$pctEncode_ = function(ch) {
+  return '%' + ch.charCodeAt(0).toString(16);
+};
+
+/**
+ * Escapes a string so that it can be safely included in a URI.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeUri = function(value) {
+  if (value && value.contentKind === goog.soy.data.SanitizedContentKind.URI) {
+    goog.asserts.assert(value.constructor ===
+        soydata.SanitizedUri);
+    return soy.$$normalizeUri(value);
+  }
+  // Apostophes and parentheses are not matched by encodeURIComponent.
+  // They are technically special in URIs, but only appear in the obsolete mark
+  // production in Appendix D.2 of RFC 3986, so can be encoded without changing
+  // semantics.
+  var encoded = soy.esc.$$escapeUriHelper(value);
+  soy.$$problematicUriMarks_.lastIndex = 0;
+  if (soy.$$problematicUriMarks_.test(encoded)) {
+    return encoded.replace(soy.$$problematicUriMarks_, soy.$$pctEncode_);
+  }
+  return encoded;
+};
+
+
+/**
+ * Removes rough edges from a URI by escaping any raw HTML/JS string delimiters.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$normalizeUri = function(value) {
+  return soy.esc.$$normalizeUriHelper(value);
+};
+
+
+/**
+ * Vets a URI's protocol and removes rough edges from a URI by escaping
+ * any raw HTML/JS string delimiters.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$filterNormalizeUri = function(value) {
+  if (value && value.contentKind == goog.soy.data.SanitizedContentKind.URI) {
+    goog.asserts.assert(value.constructor ===
+        soydata.SanitizedUri);
+    return soy.$$normalizeUri(value);
+  }
+  return soy.esc.$$filterNormalizeUriHelper(value);
+};
+
+
+/**
+ * Escapes a string so it can safely be included inside a quoted CSS string.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} An escaped version of value.
+ */
+soy.$$escapeCssString = function(value) {
+  return soy.esc.$$escapeCssStringHelper(value);
+};
+
+
+/**
+ * Encodes a value as a CSS identifier part, keyword, or quantity.
+ *
+ * @param {*} value The value to escape. May not be a string, but the value
+ *     will be coerced to a string.
+ * @return {string} A safe CSS identifier part, keyword, or quanitity.
+ */
+soy.$$filterCssValue = function(value) {
+  if (value && value.contentKind === goog.soy.data.SanitizedContentKind.CSS) {
+    goog.asserts.assert(value.constructor ===
+        soydata.SanitizedCss);
+    return value.content;
+  }
+  // Uses == to intentionally match null and undefined for Java compatibility.
+  if (value == null) {
+    return '';
+  }
+  return soy.esc.$$filterCssValueHelper(value);
+};
+
+
+/**
+ * Sanity-checks noAutoescape input for explicitly tainted content.
+ *
+ * SanitizedContentKind.TEXT is used to explicitly mark input that was never
+ * meant to be used unescaped.
+ *
+ * @param {*} value The value to filter.
+ * @return {string} The value, that we dearly hope will not cause an attack.
+ */
+soy.$$filterNoAutoescape = function(value) {
+  if (value && value.contentKind === goog.soy.data.SanitizedContentKind.TEXT) {
+    // Fail in development mode.
+    goog.asserts.fail(
+        'Tainted SanitizedContentKind.TEXT for |noAutoescape: `%s`',
+        [value.content]);
+    // Return innocuous data in production.
+    return 'zSoyz';
+  }
+  return String(value);
+};
+
+
+// -----------------------------------------------------------------------------
+// Basic directives/functions.
+
+
+/**
+ * Converts \r\n, \r, and \n to <br>s
+ * @param {*} str The string in which to convert newlines.
+ * @return {string} A copy of {@code str} with converted newlines.
+ */
+soy.$$changeNewlineToBr = function(str) {
+  return goog.string.newLineToBr(String(str), false);
+};
+
+
+/**
+ * Inserts word breaks ('wbr' tags) into a HTML string at a given interval. The
+ * counter is reset if a space is encountered. Word breaks aren't inserted into
+ * HTML tags or entities. Entites count towards the character count; HTML tags
+ * do not.
+ *
+ * @param {*} str The HTML string to insert word breaks into. Can be other
+ *     types, but the value will be coerced to a string.
+ * @param {number} maxCharsBetweenWordBreaks Maximum number of non-space
+ *     characters to allow before adding a word break.
+ * @return {string} The string including word breaks.
+ */
+soy.$$insertWordBreaks = function(str, maxCharsBetweenWordBreaks) {
+  return goog.format.insertWordBreaks(String(str), maxCharsBetweenWordBreaks);
+};
+
+
+/**
+ * Truncates a string to a given max length (if it's currently longer),
+ * optionally adding ellipsis at the end.
+ *
+ * @param {*} str The string to truncate. Can be other types, but the value will
+ *     be coerced to a string.
+ * @param {number} maxLen The maximum length of the string after truncation
+ *     (including ellipsis, if applicable).
+ * @param {boolean} doAddEllipsis Whether to add ellipsis if the string needs
+ *     truncation.
+ * @return {string} The string after truncation.
+ */
+soy.$$truncate = function(str, maxLen, doAddEllipsis) {
+
+  str = String(str);
+  if (str.length <= maxLen) {
+    return str;  // no need to truncate
+  }
+
+  // If doAddEllipsis, either reduce maxLen to compensate, or else if maxLen is
+  // too small, just turn off doAddEllipsis.
+  if (doAddEllipsis) {
+    if (maxLen > 3) {
+      maxLen -= 3;
+    } else {
+      doAddEllipsis = false;
+    }
+  }
+
+  // Make sure truncating at maxLen doesn't cut up a unicode surrogate pair.
+  if (soy.$$isHighSurrogate_(str.charAt(maxLen - 1)) &&
+      soy.$$isLowSurrogate_(str.charAt(maxLen))) {
+    maxLen -= 1;
+  }
+
+  // Truncate.
+  str = str.substring(0, maxLen);
+
+  // Add ellipsis.
+  if (doAddEllipsis) {
+    str += '...';
+  }
+
+  return str;
+};
+
+/**
+ * Private helper for $$truncate() to check whether a char is a high surrogate.
+ * @param {string} ch The char to check.
+ * @return {boolean} Whether the given char is a unicode high surrogate.
+ * @private
+ */
+soy.$$isHighSurrogate_ = function(ch) {
+  return 0xD800 <= ch && ch <= 0xDBFF;
+};
+
+/**
+ * Private helper for $$truncate() to check whether a char is a low surrogate.
+ * @param {string} ch The char to check.
+ * @return {boolean} Whether the given char is a unicode low surrogate.
+ * @private
+ */
+soy.$$isLowSurrogate_ = function(ch) {
+  return 0xDC00 <= ch && ch <= 0xDFFF;
+};
+
+
+// -----------------------------------------------------------------------------
+// Bidi directives/functions.
+
+
+/**
+ * Cache of bidi formatter by context directionality, so we don't keep on
+ * creating new objects.
+ * @type {!Object.<!goog.i18n.BidiFormatter>}
+ * @private
+ */
+soy.$$bidiFormatterCache_ = {};
+
+
+/**
+ * Returns cached bidi formatter for bidiGlobalDir, or creates a new one.
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
+ *     if rtl, 0 if unknown.
+ * @return {goog.i18n.BidiFormatter} A formatter for bidiGlobalDir.
+ * @private
+ */
+soy.$$getBidiFormatterInstance_ = function(bidiGlobalDir) {
+  return soy.$$bidiFormatterCache_[bidiGlobalDir] ||
+         (soy.$$bidiFormatterCache_[bidiGlobalDir] =
+             new goog.i18n.BidiFormatter(bidiGlobalDir));
+};
+
+
+/**
+ * Estimate the overall directionality of text. If opt_isHtml, makes sure to
+ * ignore the LTR nature of the mark-up and escapes in text, making the logic
+ * suitable for HTML and HTML-escaped text.
+ * @param {string} text The text whose directionality is to be estimated.
+ * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
+ *     Default: false.
+ * @return {number} 1 if text is LTR, -1 if it is RTL, and 0 if it is neutral.
+ */
+soy.$$bidiTextDir = function(text, opt_isHtml) {
+  if (!text) {
+    return 0;
+  }
+  return goog.i18n.bidi.detectRtlDirectionality(text, opt_isHtml) ? -1 : 1;
+};
+
+
+/**
+ * Returns 'dir="ltr"' or 'dir="rtl"', depending on text's estimated
+ * directionality, if it is not the same as bidiGlobalDir.
+ * Otherwise, returns the empty string.
+ * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
+ * in text, making the logic suitable for HTML and HTML-escaped text.
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
+ *     if rtl, 0 if unknown.
+ * @param {string} text The text whose directionality is to be estimated.
+ * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
+ *     Default: false.
+ * @return {soydata.SanitizedHtmlAttribute} 'dir="rtl"' for RTL text in non-RTL
+ *     context; 'dir="ltr"' for LTR text in non-LTR context;
+ *     else, the empty string.
+ */
+soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
+  return soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute(
+      soy.$$getBidiFormatterInstance_(bidiGlobalDir).dirAttr(text, opt_isHtml));
+};
+
+
+/**
+ * Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the
+ * directionality or the exit directionality of text are opposite to
+ * bidiGlobalDir. Otherwise returns the empty string.
+ * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
+ * in text, making the logic suitable for HTML and HTML-escaped text.
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
+ *     if rtl, 0 if unknown.
+ * @param {string} text The text whose directionality is to be estimated.
+ * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
+ *     Default: false.
+ * @return {string} A Unicode bidi mark matching bidiGlobalDir, or the empty
+ *     string when text's overall and exit directionalities both match
+ *     bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
+ */
+soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) {
+  var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
+  return formatter.markAfter(text, opt_isHtml);
+};
+
+
+/**
+ * Returns str wrapped in a <span dir="ltr|rtl"> according to its directionality
+ * - but only if that is neither neutral nor the same as the global context.
+ * Otherwise, returns str unchanged.
+ * Always treats str as HTML/HTML-escaped, i.e. ignores mark-up and escapes when
+ * estimating str's directionality.
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
+ *     if rtl, 0 if unknown.
+ * @param {*} str The string to be wrapped. Can be other types, but the value
+ *     will be coerced to a string.
+ * @return {string} The wrapped string.
+ */
+soy.$$bidiSpanWrap = function(bidiGlobalDir, str) {
+  var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
+  return formatter.spanWrap(str + '', true);
+};
+
+
+/**
+ * Returns str wrapped in Unicode BiDi formatting characters according to its
+ * directionality, i.e. either LRE or RLE at the beginning and PDF at the end -
+ * but only if str's directionality is neither neutral nor the same as the
+ * global context. Otherwise, returns str unchanged.
+ * Always treats str as HTML/HTML-escaped, i.e. ignores mark-up and escapes when
+ * estimating str's directionality.
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
+ *     if rtl, 0 if unknown.
+ * @param {*} str The string to be wrapped. Can be other types, but the value
+ *     will be coerced to a string.
+ * @return {string} The wrapped string.
+ */
+soy.$$bidiUnicodeWrap = function(bidiGlobalDir, str) {
+  var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
+  return formatter.unicodeWrap(str + '', true);
+};
+
+
+// -----------------------------------------------------------------------------
+// Generated code.
+
+
+
+
+// START GENERATED CODE FOR ESCAPERS.
+
+/**
+ * @type {function (*) : string}
+ */
+soy.esc.$$escapeUriHelper = function(v) {
+  return encodeURIComponent(String(v));
+};
+
+/**
+ * Maps charcters to the escaped versions for the named escape directives.
+ * @type {Object.<string, string>}
+ * @private
+ */
+soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = {
+  '\x00': '\x26#0;',
+  '\x22': '\x26quot;',
+  '\x26': '\x26amp;',
+  '\x27': '\x26#39;',
+  '\x3c': '\x26lt;',
+  '\x3e': '\x26gt;',
+  '\x09': '\x26#9;',
+  '\x0a': '\x26#10;',
+  '\x0b': '\x26#11;',
+  '\x0c': '\x26#12;',
+  '\x0d': '\x26#13;',
+  ' ': '\x26#32;',
+  '-': '\x26#45;',
+  '\/': '\x26#47;',
+  '\x3d': '\x26#61;',
+  '`': '\x26#96;',
+  '\x85': '\x26#133;',
+  '\xa0': '\x26#160;',
+  '\u2028': '\x26#8232;',
+  '\u2029': '\x26#8233;'
+};
+
+/**
+ * A function that can be used with String.replace..
+ * @param {string} ch A single character matched by a compatible matcher.
+ * @return {string} A token in the output language.
+ * @private
+ */
+soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = function(ch) {
+  return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_[ch];
+};
+
+/**
+ * Maps charcters to the escaped versions for the named escape directives.
+ * @type {Object.<string, string>}
+ * @private
+ */
+soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = {
+  '\x00': '\\x00',
+  '\x08': '\\x08',
+  '\x09': '\\t',
+  '\x0a': '\\n',
+  '\x0b': '\\x0b',
+  '\x0c': '\\f',
+  '\x0d': '\\r',
+  '\x22': '\\x22',
+  '\x26': '\\x26',
+  '\x27': '\\x27',
+  '\/': '\\\/',
+  '\x3c': '\\x3c',
+  '\x3d': '\\x3d',
+  '\x3e': '\\x3e',
+  '\\': '\\\\',
+  '\x85': '\\x85',
+  '\u2028': '\\u2028',
+  '\u2029': '\\u2029',
+  '$': '\\x24',
+  '(': '\\x28',
+  ')': '\\x29',
+  '*': '\\x2a',
+  '+': '\\x2b',
+  ',': '\\x2c',
+  '-': '\\x2d',
+  '.': '\\x2e',
+  ':': '\\x3a',
+  '?': '\\x3f',
+  '[': '\\x5b',
+  ']': '\\x5d',
+  '^': '\\x5e',
+  '{': '\\x7b',
+  '|': '\\x7c',
+  '}': '\\x7d'
+};
+
+/**
+ * A function that can be used with String.replace..
+ * @param {string} ch A single character matched by a compatible matcher.
+ * @return {string} A token in the output language.
+ * @private
+ */
+soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = function(ch) {
+  return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_[ch];
+};
+
+/**
+ * Maps charcters to the escaped versions for the named escape directives.
+ * @type {Object.<string, string>}
+ * @private
+ */
+soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_ = {
+  '\x00': '\\0 ',
+  '\x08': '\\8 ',
+  '\x09': '\\9 ',
+  '\x0a': '\\a ',
+  '\x0b': '\\b ',
+  '\x0c': '\\c ',
+  '\x0d': '\\d ',
+  '\x22': '\\22 ',
+  '\x26': '\\26 ',
+  '\x27': '\\27 ',
+  '(': '\\28 ',
+  ')': '\\29 ',
+  '*': '\\2a ',
+  '\/': '\\2f ',
+  ':': '\\3a ',
+  ';': '\\3b ',
+  '\x3c': '\\3c ',
+  '\x3d': '\\3d ',
+  '\x3e': '\\3e ',
+  '@': '\\40 ',
+  '\\': '\\5c ',
+  '{': '\\7b ',
+  '}': '\\7d ',
+  '\x85': '\\85 ',
+  '\xa0': '\\a0 ',
+  '\u2028': '\\2028 ',
+  '\u2029': '\\2029 '
+};
+
+/**
+ * A function that can be used with String.replace..
+ * @param {string} ch A single character matched by a compatible matcher.
+ * @return {string} A token in the output language.
+ * @private
+ */
+soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_ = function(ch) {
+  return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_[ch];
+};
+
+/**
+ * Maps charcters to the escaped versions for the named escape directives.
+ * @type {Object.<string, string>}
+ * @private
+ */
+soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = {
+  '\x00': '%00',
+  '\x01': '%01',
+  '\x02': '%02',
+  '\x03': '%03',
+  '\x04': '%04',
+  '\x05': '%05',
+  '\x06': '%06',
+  '\x07': '%07',
+  '\x08': '%08',
+  '\x09': '%09',
+  '\x0a': '%0A',
+  '\x0b': '%0B',
+  '\x0c': '%0C',
+  '\x0d': '%0D',
+  '\x0e': '%0E',
+  '\x0f': '%0F',
+  '\x10': '%10',
+  '\x11': '%11',
+  '\x12': '%12',
+  '\x13': '%13',
+  '\x14': '%14',
+  '\x15': '%15',
+  '\x16': '%16',
+  '\x17': '%17',
+  '\x18': '%18',
+  '\x19': '%19',
+  '\x1a': '%1A',
+  '\x1b': '%1B',
+  '\x1c': '%1C',
+  '\x1d': '%1D',
+  '\x1e': '%1E',
+  '\x1f': '%1F',
+  ' ': '%20',
+  '\x22': '%22',
+  '\x27': '%27',
+  '(': '%28',
+  ')': '%29',
+  '\x3c': '%3C',
+  '\x3e': '%3E',
+  '\\': '%5C',
+  '{': '%7B',
+  '}': '%7D',
+  '\x7f': '%7F',
+  '\x85': '%C2%85',
+  '\xa0': '%C2%A0',
+  '\u2028': '%E2%80%A8',
+  '\u2029': '%E2%80%A9',
+  '\uff01': '%EF%BC%81',
+  '\uff03': '%EF%BC%83',
+  '\uff04': '%EF%BC%84',
+  '\uff06': '%EF%BC%86',
+  '\uff07': '%EF%BC%87',
+  '\uff08': '%EF%BC%88',
+  '\uff09': '%EF%BC%89',
+  '\uff0a': '%EF%BC%8A',
+  '\uff0b': '%EF%BC%8B',
+  '\uff0c': '%EF%BC%8C',
+  '\uff0f': '%EF%BC%8F',
+  '\uff1a': '%EF%BC%9A',
+  '\uff1b': '%EF%BC%9B',
+  '\uff1d': '%EF%BC%9D',
+  '\uff1f': '%EF%BC%9F',
+  '\uff20': '%EF%BC%A0',
+  '\uff3b': '%EF%BC%BB',
+  '\uff3d': '%EF%BC%BD'
+};
+
+/**
+ * A function that can be used with String.replace..
+ * @param {string} ch A single character matched by a compatible matcher.
+ * @return {string} A token in the output language.
+ * @private
+ */
+soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = function(ch) {
+  return soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_[ch];
+};
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_ESCAPE_HTML_ = /[\x00\x22\x26\x27\x3c\x3e]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_ = /[\x00\x22\x27\x3c\x3e]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x26\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_ = /[\x00\x08-\x0d\x22\x26\x27\/\x3c-\x3e\\\x85\u2028\u2029]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_ = /[\x00\x08-\x0d\x22\x24\x26-\/\x3a\x3c-\x3f\x5b-\x5e\x7b-\x7d\x85\u2028\u2029]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_ = /[\x00\x08-\x0d\x22\x26-\x2a\/\x3a-\x3e@\\\x7b\x7d\x85\xa0\u2028\u2029]/g;
+
+/**
+ * Matches characters that need to be escaped for the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = /[\x00- \x22\x27-\x29\x3c\x3e\\\x7b\x7d\x7f\x85\xa0\u2028\u2029\uff01\uff03\uff04\uff06-\uff0c\uff0f\uff1a\uff1b\uff1d\uff1f\uff20\uff3b\uff3d]/g;
+
+/**
+ * A pattern that vets values produced by the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_ = /^(?!-*(?:expression|(?:moz-)?binding))(?:[.#]?-?(?:[_a-z0-9-]+)(?:-[_a-z0-9-]+)*-?|-?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[a-z]{1,2}|%)?|!important|)$/i;
+
+/**
+ * A pattern that vets values produced by the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_ = /^(?:(?:https?|mailto):|[^&:\/?#]*(?:[\/?#]|$))/i;
+
+/**
+ * A pattern that vets values produced by the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_ = /^(?!style|on|action|archive|background|cite|classid|codebase|data|dsync|href|longdesc|src|usemap)(?:[a-z0-9_$:-]*)$/i;
+
+/**
+ * A pattern that vets values produced by the named directives.
+ * @type RegExp
+ * @private
+ */
+soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_ = /^(?!script|style|title|textarea|xmp|no)[a-z0-9_$:-]*$/i;
+
+/**
+ * A helper for the Soy directive |escapeHtml
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$escapeHtmlHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_ESCAPE_HTML_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
+};
+
+/**
+ * A helper for the Soy directive |normalizeHtml
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$normalizeHtmlHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
+};
+
+/**
+ * A helper for the Soy directive |escapeHtmlNospace
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$escapeHtmlNospaceHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
+};
+
+/**
+ * A helper for the Soy directive |normalizeHtmlNospace
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$normalizeHtmlNospaceHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
+};
+
+/**
+ * A helper for the Soy directive |escapeJsString
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$escapeJsStringHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_);
+};
+
+/**
+ * A helper for the Soy directive |escapeJsRegex
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$escapeJsRegexHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_);
+};
+
+/**
+ * A helper for the Soy directive |escapeCssString
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$escapeCssStringHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_,
+      soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_);
+};
+
+/**
+ * A helper for the Soy directive |filterCssValue
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$filterCssValueHelper = function(value) {
+  var str = String(value);
+  if (!soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_.test(str)) {
+    return 'zSoyz';
+  }
+  return str;
+};
+
+/**
+ * A helper for the Soy directive |normalizeUri
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$normalizeUriHelper = function(value) {
+  var str = String(value);
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_,
+      soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_);
+};
+
+/**
+ * A helper for the Soy directive |filterNormalizeUri
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$filterNormalizeUriHelper = function(value) {
+  var str = String(value);
+  if (!soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_.test(str)) {
+    return '#zSoyz';
+  }
+  return str.replace(
+      soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_,
+      soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_);
+};
+
+/**
+ * A helper for the Soy directive |filterHtmlAttributes
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$filterHtmlAttributesHelper = function(value) {
+  var str = String(value);
+  if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_.test(str)) {
+    return 'zSoyz';
+  }
+  return str;
+};
+
+/**
+ * A helper for the Soy directive |filterHtmlElementName
+ * @param {*} value Can be of any type but will be coerced to a string.
+ * @return {string} The escaped text.
+ */
+soy.esc.$$filterHtmlElementNameHelper = function(value) {
+  var str = String(value);
+  if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_.test(str)) {
+    return 'zSoyz';
+  }
+  return str;
+};
+
+/**
+ * Matches all tags, HTML comments, and DOCTYPEs in tag soup HTML.
+ * By removing these, and replacing any '<' or '>' characters with
+ * entities we guarantee that the result can be embedded into a
+ * an attribute without introducing a tag boundary.
+ *
+ * @type {RegExp}
+ * @private
+ */
+soy.esc.$$HTML_TAG_REGEX_ = /<(?:!|\/?([a-zA-Z][a-zA-Z0-9:\-]*))(?:[^>'"]|"[^"]*"|'[^']*')*>/g;
+
+/**
+ * Matches all occurrences of '<'.
+ *
+ * @type {RegExp}
+ * @private
+ */
+soy.esc.$$LT_REGEX_ = /</g;
+
+/**
+ * Maps lower-case names of innocuous tags to 1.
+ *
+ * @type {Object.<string,number>}
+ * @private
+ */
+soy.esc.$$SAFE_TAG_WHITELIST_ = {'b': 1, 'br': 1, 'em': 1, 'i': 1, 's': 1, 'sub': 1, 'sup': 1, 'u': 1};
+
+// END GENERATED CODE

+ 10 - 0
blockly/.eslintignore

@@ -0,0 +1,10 @@
+*_compressed*.js
+*_uncompressed*.js
+/msg/*
+/core/css.js
+/tests/jsunit/*
+/tests/generators/*
+/generators/*
+/demos/*
+/accessible/*
+/appengine/*

+ 28 - 0
blockly/.eslintrc

@@ -0,0 +1,28 @@
+{
+    "rules": {
+        "curly": ["error", "multi-line"],
+        "eol-last": ["error"],
+        "indent": ["error", 2, {"SwitchCase": 1}], # Blockly/Google use 2-space indents
+        "linebreak-style": ["error", "unix"],
+        "max-len": ["error", 120, 4],
+        "no-trailing-spaces": ["error", { "skipBlankLines": true }],
+        "no-unused-vars": ["error", {"args": "after-used", "varsIgnorePattern": "^_"}],
+        "no-use-before-define": ["error"],
+        "quotes": ["off"], # Blockly mixes single and double quotes
+        "semi": ["error", "always"],
+        "space-before-function-paren": ["error", "never"], # Blockly doesn't have space before function paren
+        "strict": ["off"], # Blockly uses 'use strict' in files
+        "no-cond-assign": ["off"], # Blockly often uses cond-assignment in loops
+        "no-redeclare": ["off"], # Closure style allows redeclarations
+        "valid-jsdoc": ["error", {"requireReturn": false}],
+        "no-console": ["off"]
+    },
+    "env": {
+        "browser": true
+    },
+    "globals": {
+        "Blockly": true, # Blockly global
+        "goog": true # goog closure libraries/includes
+    },
+    "extends": "eslint:recommended"
+}

+ 8 - 0
blockly/.gitignore

@@ -0,0 +1,8 @@
+node_modules
+npm-debug.log
+.DS_Store
+.settings
+.project
+*.pyc
+*.komodoproject
+/nbproject/private/

+ 6 - 0
blockly/.jshintignore

@@ -0,0 +1,6 @@
+node_modules/
+tests/
+demos/
+**/*_compressed.js
+**/*_uncompressed.js
+**/*_test.js

+ 177 - 0
blockly/COPYING

@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2011
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS

+ 177 - 0
blockly/LICENSE

@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2011
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS

+ 8 - 0
blockly/README.md

@@ -0,0 +1,8 @@
+# Blockly
+
+Google's Blockly is a web-based, visual programming editor.  Users can drag
+blocks together to build programs.  All code is free and open source.
+
+**The project page is https://developers.google.com/blockly/**
+
+![](https://developers.google.com/blockly/sample.png)

+ 52 - 0
blockly/accessible/README

@@ -0,0 +1,52 @@
+Accessible Blockly
+==================
+
+Google's Blockly is a web-based, visual programming editor that is accessible
+to blind users.
+
+The code in this directory renders a version of the Blockly toolbox and
+workspace that is fully keyboard-navigable, and compatible with JAWS/NVDA
+screen readers on Firefox for Windows. (We chose this combination because JAWS
+and NVDA are the most robust screen readers, and are compatible with many more
+aria tags than other screen readers.)
+
+In the future, Accessible Blockly may be modified to suit accessibility needs
+other than visual impairments. Note that deaf users are expected to continue
+using Blockly over Accessible Blockly.
+
+
+Using Accessible Blockly in Your Web App
+----------------------------------------
+The demo at blockly/demos/accessible covers the absolute minimum required to
+import Accessible Blockly into your web app. You will need to import the files
+in the same order as in the demo: utils.service.js will need to be the first
+Angular file imported.
+
+When the DOMContentLoaded event fires, call ng.platform.browser.bootstrap() on
+the main component to be loaded. This will usually be blocklyApp.AppView, but
+if you have another component that wraps it, use that one instead.
+
+
+Customizing the Toolbar
+-----------------------
+The Accessible Blockly workspace comes with a customizable toolbar.
+
+To customize the toolbar, you will need to declare an ACCESSIBLE_GLOBALS object
+in the global scope that looks like this:
+
+    var ACCESSIBLE_GLOBALS = {
+      toolbarButtonConfig: []
+    };
+
+The value corresponding to 'toolbarButtonConfig' can be modified by adding
+objects representing buttons on the toolbar. Each of these objects should have
+two keys:
+
+  - 'text' (the text to display on the button)
+  - 'action' (the function that gets run when the button is clicked)
+
+
+Limitations
+-----------
+- We do not support having multiple Accessible Blockly apps in a single webpage.
+- Accessible Blockly does not support the use of shadow blocks.

+ 64 - 0
blockly/accessible/app.component.js

@@ -0,0 +1,64 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Component that details how the AccessibleBlockly
+ * app is rendered on the page.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.workspace = new Blockly.Workspace();
+
+blocklyApp.AppView = ng.core
+  .Component({
+    selector: 'blockly-app',
+    template: `
+    <table>
+      <tr>
+        <td class="blocklyTable">
+          <blockly-toolbox>{{'TOOLBOX_LOAD'|translate}}</blockly-toolbox>
+        </td>
+        <td class="blocklyTable">
+          <blockly-workspace>{{'WORKSPACE_LOAD'|translate}}</blockly-workspace>
+        </td>
+      </tr>
+    </table>
+
+    <label aria-hidden="true" hidden id="blockly-argument-block-menu">{{'ARGUMENT_BLOCK_ACTION_LIST'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-argument-input">{{'ARGUMENT_INPUT'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-argument-menu">{{'ARGUMENT_OPTIONS_LIST'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-argument-text">{{'TEXT'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-block-menu">{{'BLOCK_ACTION_LIST'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-block-summary">{{'BLOCK_SUMMARY'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-button">{{'BUTTON'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-disabled">{{'UNAVAILABLE'|translate}}</label>
+    <label aria-hidden="true" hidden id="blockly-menu">{{'OPTION_LIST'|translate}}</label>
+    `,
+    directives: [blocklyApp.ToolboxComponent, blocklyApp.WorkspaceComponent],
+    pipes: [blocklyApp.TranslatePipe],
+    // The clipboard, tree and utils services are declared here, so that all
+    // components in the application use the same instance of the service.
+    // https://www.sitepoint.com/angular-2-components-providers-classes-factories-values/
+    providers: [
+        blocklyApp.ClipboardService, blocklyApp.TreeService,
+        blocklyApp.UtilsService]
+  })
+  .Class({
+    constructor: [function() {}]
+  });

+ 153 - 0
blockly/accessible/clipboard.service.js

@@ -0,0 +1,153 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Service that handles the clipboard and marked spots.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.ClipboardService = ng.core
+  .Class({
+    constructor: function() {
+      this.clipboardBlockXml_ = null;
+      this.clipboardBlockSuperiorConnection_ = null;
+      this.clipboardBlockNextConnection_ = null;
+      this.markedConnection_ = null;
+    },
+    areConnectionsCompatible_: function(blockConnection, connection) {
+      // Check that both connections exist, that it's the right kind of
+      // connection, and that the types match.
+      return Boolean(
+          connection && blockConnection &&
+          Blockly.OPPOSITE_TYPE[blockConnection.type] == connection.type &&
+          connection.checkType_(blockConnection));
+    },
+    isCompatibleWithClipboard: function(connection) {
+      var superiorConnection = this.clipboardBlockSuperiorConnection_;
+      var nextConnection = this.clipboardBlockNextConnection_;
+      return Boolean(
+          this.areConnectionsCompatible_(connection, superiorConnection) ||
+          this.areConnectionsCompatible_(connection, nextConnection));
+    },
+    isMovableToMarkedConnection: function(block) {
+      // It should not be possible to move any ancestor of the block containing
+      // the marked spot to the marked spot.
+      if (!this.markedConnection_) {
+        return false;
+      }
+
+      var markedSpotAncestorBlock = this.markedConnection_.getSourceBlock();
+      while (markedSpotAncestorBlock) {
+        if (markedSpotAncestorBlock.id == block.id) {
+          return false;
+        }
+        markedSpotAncestorBlock = markedSpotAncestorBlock.getParent();
+      }
+
+      return this.canBeCopiedToMarkedConnection(block);
+    },
+    canBeCopiedToMarkedConnection: function(block) {
+      if (!this.markedConnection_ ||
+          !this.markedConnection_.getSourceBlock().workspace) {
+        return false;
+      }
+
+      var potentialConnections = [
+          block.outputConnection,
+          block.previousConnection,
+          block.nextConnection
+      ];
+
+      var that = this;
+      return potentialConnections.some(function(connection) {
+        return that.areConnectionsCompatible_(
+            connection, that.markedConnection_);
+      });
+    },
+    markConnection: function(connection) {
+      this.markedConnection_ = connection;
+      alert(Blockly.Msg.MARKED_SPOT_MSG);
+    },
+    cut: function(block) {
+      var blockSummary = block.toString();
+      this.copy(block, false);
+      block.dispose(true);
+      alert(Blockly.Msg.CUT_BLOCK_MSG + blockSummary);
+    },
+    copy: function(block, announce) {
+      this.clipboardBlockXml_ = Blockly.Xml.blockToDom(block);
+      this.clipboardBlockSuperiorConnection_ = block.outputConnection ||
+          block.previousConnection;
+      this.clipboardBlockNextConnection_ = block.nextConnection;
+      if (announce) {
+        alert(Blockly.Msg.COPIED_BLOCK_MSG + block.toString());
+      }
+    },
+    pasteFromClipboard: function(connection) {
+      var reconstitutedBlock = Blockly.Xml.domToBlock(blocklyApp.workspace,
+          this.clipboardBlockXml_);
+      switch (connection.type) {
+        case Blockly.NEXT_STATEMENT:
+          connection.connect(reconstitutedBlock.previousConnection);
+          break;
+        case Blockly.PREVIOUS_STATEMENT:
+          connection.connect(reconstitutedBlock.nextConnection);
+          break;
+        default:
+          connection.connect(reconstitutedBlock.outputConnection);
+      }
+      alert(
+          Blockly.Msg.PASTED_BLOCK_FROM_CLIPBOARD_MSG +
+          reconstitutedBlock.toString());
+    },
+    pasteToMarkedConnection: function(block, announce) {
+      var xml = Blockly.Xml.blockToDom(block);
+      var reconstitutedBlock = Blockly.Xml.domToBlock(
+          blocklyApp.workspace, xml);
+
+      var potentialConnections = [
+          reconstitutedBlock.outputConnection,
+          reconstitutedBlock.previousConnection,
+          reconstitutedBlock.nextConnection
+      ];
+
+      var connectionSuccessful = false;
+      for (var i = 0; i < potentialConnections.length; i++) {
+        if (this.areConnectionsCompatible_(
+            this.markedConnection_, potentialConnections[i])) {
+          this.markedConnection_.connect(potentialConnections[i]);
+          connectionSuccessful = true;
+          break;
+        }
+      }
+
+      if (!connectionSuccessful) {
+        console.error('ERROR: Could not connect block to marked spot.');
+        return;
+      }
+
+      if (announce) {
+        alert(
+            Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG +
+            reconstitutedBlock.toString());
+      }
+
+      this.markedConnection_ = null;
+    }
+  });

+ 136 - 0
blockly/accessible/field.component.js

@@ -0,0 +1,136 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Component that details how a Blockly.Field is
+ * rendered in the toolbox in AccessibleBlockly. Also handles any interactions
+ * with the field.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.FieldComponent = ng.core
+  .Component({
+    selector: 'blockly-field',
+    template: `
+    <li [id]="idMap['listItem']" role="treeitem" *ngIf="isTextInput()"
+        [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-argument-input', idMap['input'])"
+        [attr.aria-level]="level" aria-selected=false>
+      <input [id]="idMap['input']" [ngModel]="field.getValue()" (ngModelChange)="field.setValue($event)">
+    </li>
+    <li [id]="idMap['listItem']" role="treeitem" *ngIf="isDropdown()"
+        [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-argument-menu', idMap['label'])"
+        [attr.aria-level]="level" aria-selected=false>
+      <label [id]="idMap['label']">{{'CURRENT_ARGUMENT_VALUE'|translate}} {{field.getText()}}</label>
+      <ol role="group" [attr.aria-level]="level+1">
+        <li [id]="idMap[optionValue]" role="treeitem" *ngFor="#optionValue of getOptions()"
+            [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap[optionValue + 'Button'], 'blockly-button')"
+            [attr.aria-level]="level+1" aria-selected=false>
+          <button [id]="idMap[optionValue + 'Button']" (click)="handleDropdownChange(field, optionValue)">
+            {{optionText[optionValue]}}
+          </button>
+        </li>
+      </ol>
+    </li>
+    <li [id]="idMap['listItem']" role="treeitem" *ngIf="isCheckbox()"
+        [attr.aria-level]="level" aria-selected=false>
+      // Checkboxes are not currently supported.
+    </li>
+    <li [id]="idMap['listItem']" role="treeitem" *ngIf="isTextField() && hasVisibleText()"
+        [attr.aria-labelledBy]="utilsService.generateAriaLabelledByAttr('blockly-argument-text', idMap['label'])"
+        [attr.aria-level]="level" aria-selected=false>
+      <label [id]="idMap['label']">
+        {{field.getText()}}
+      </label>
+    </li>
+    `,
+    inputs: ['field', 'level', 'index', 'parentId'],
+    pipes: [blocklyApp.TranslatePipe]
+  })
+  .Class({
+    constructor: [blocklyApp.UtilsService, function(_utilsService) {
+      this.optionText = {
+        keys: []
+      };
+      this.utilsService = _utilsService;
+    }],
+    ngOnInit: function() {
+      var elementsNeedingIds = this.generateElementNames(this.field);
+      // Warning: this assumes that the elements returned by
+      // this.generateElementNames() are unique.
+      this.idMap = this.utilsService.generateIds(elementsNeedingIds);
+    },
+    generateAriaLabelledByAttr: function(mainLabel, secondLabel) {
+      return mainLabel + ' ' + secondLabel;
+    },
+    generateElementNames: function() {
+      var elementNames = ['listItem'];
+      if (this.isTextInput()) {
+        elementNames.push('input');
+      } else if (this.isDropdown()) {
+        elementNames.push('label');
+        var keys = this.getOptions();
+        for (var i = 0; i < keys.length; i++){
+          elementNames.push(keys[i], keys[i] + 'Button');
+        }
+      } else if (this.isTextField() && this.hasVisibleText()) {
+        elementNames.push('label');
+      }
+      return elementNames;
+    },
+    isTextInput: function() {
+      return this.field instanceof Blockly.FieldTextInput;
+    },
+    isDropdown: function() {
+      return this.field instanceof Blockly.FieldDropdown;
+    },
+    isCheckbox: function() {
+      return this.field instanceof Blockly.FieldCheckbox;
+    },
+    isTextField: function() {
+      return !(this.field instanceof Blockly.FieldTextInput) &&
+          !(this.field instanceof Blockly.FieldDropdown) &&
+          !(this.field instanceof Blockly.FieldCheckbox);
+    },
+    hasVisibleText: function() {
+      var text = this.field.getText().trim();
+      return !!text;
+    },
+    getOptions: function() {
+      if (this.optionText.keys.length) {
+        return this.optionText.keys;
+      }
+      var options = this.field.getOptions_();
+      for (var i = 0; i < options.length; i++) {
+        var tuple = options[i];
+        this.optionText[tuple[1]] = tuple[0];
+        this.optionText.keys.push(tuple[1]);
+      }
+      return this.optionText.keys;
+    },
+    handleDropdownChange: function(field, text) {
+      if (text == 'NO_ACTION') {
+        return;
+      }
+      if (this.field instanceof Blockly.FieldVariable) {
+        Blockly.FieldVariable.dropdownChange.call(this.field, text);
+      } else {
+        this.field.setValue(text);
+      }
+    }
+  });

+ 321 - 0
blockly/accessible/libs/Rx.umd.min.js

@@ -0,0 +1,321 @@
+(function(t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"===typeof define&&define.amd?define([],t):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).Rx=t()})(function(){return function a(b,e,h){function k(f,d){if(!e[f]){if(!b[f]){var c="function"==typeof require&&require;if(!d&&c)return c(f,!0);if(m)return m(f,!0);c=Error("Cannot find module '"+f+"'");throw c.code="MODULE_NOT_FOUND",c;}c=e[f]={exports:{}};
+b[f][0].call(c.exports,function(a){var c=b[f][1][a];return k(c?c:a)},c,c.exports,a,b,e,h)}return e[f].exports}for(var m="function"==typeof require&&require,l=0;l<h.length;l++)k(h[l]);return k}({1:[function(a,b,e){var h=this&&this.__extends||function(a,b){function l(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(l.prototype=b.prototype,new l)};a=function(a){function b(l,f,d){a.call(this);this.parent=l;this.outerValue=f;this.outerIndex=d;
+this.index=0}h(b,a);b.prototype._next=function(a){var f=this.index++;this.parent.notifyNext(this.outerValue,a,this.outerIndex,f)};b.prototype._error=function(a){this.parent.notifyError(a,this)};b.prototype._complete=function(){this.parent.notifyComplete(this)};return b}(a("./Subscriber").Subscriber);e.InnerSubscriber=a},{"./Subscriber":7}],2:[function(a,b,e){var h=a("./Observable");a=function(){function a(b,l,f){this.kind=b;this.value=l;this.exception=f;this.hasValue="N"===b}a.prototype.observe=function(a){switch(this.kind){case "N":return a.next(this.value);
+case "E":return a.error(this.exception);case "C":return a.complete()}};a.prototype.do=function(a,b,f){switch(this.kind){case "N":return a(this.value);case "E":return b(this.exception);case "C":return f()}};a.prototype.accept=function(a,b,f){return a&&"function"===typeof a.next?this.observe(a):this.do(a,b,f)};a.prototype.toObservable=function(){switch(this.kind){case "N":return h.Observable.of(this.value);case "E":return h.Observable.throw(this.exception);case "C":return h.Observable.empty()}};a.createNext=
+function(b){return"undefined"!==typeof b?new a("N",b):this.undefinedValueNotification};a.createError=function(b){return new a("E",void 0,b)};a.createComplete=function(){return this.completeNotification};a.completeNotification=new a("C");a.undefinedValueNotification=new a("N",void 0);return a}();e.Notification=a},{"./Observable":3}],3:[function(a,b,e){var h=a("./Subscriber"),k=a("./util/root"),m=a("./util/SymbolShim"),l=a("./symbol/rxSubscriber");a=function(){function a(d){this._isScalar=!1;d&&(this._subscribe=
+d)}a.prototype.lift=function(d){var c=new a;c.source=this;c.operator=d;return c};a.prototype[m.SymbolShim.observable]=function(){return this};a.prototype.subscribe=function(a,c,g){a=a&&"object"===typeof a?a instanceof h.Subscriber?a:a[l.rxSubscriber]?a[l.rxSubscriber]():new h.Subscriber(a):h.Subscriber.create(a,c,g);a.add(this._subscribe(a));return a};a.prototype.forEach=function(a,c,g){g||(k.root.Rx&&k.root.Rx.config&&k.root.Rx.config.Promise?g=k.root.Rx.config.Promise:k.root.Promise&&(g=k.root.Promise));
+if(!g)throw Error("no Promise impl found");var n;c?(n=function q(a){return q.next.call(q.thisArg,a)},n.thisArg=c,n.next=a):n=a;a=function q(a,c){q.source.subscribe(q.nextHandler,c,a)};a.source=this;a.nextHandler=n;return new g(a)};a.prototype._subscribe=function(a){return this.source._subscribe(this.operator.call(a))};a.create=function(d){return new a(d)};return a}();e.Observable=a},{"./Subscriber":7,"./symbol/rxSubscriber":221,"./util/SymbolShim":229,"./util/root":238}],4:[function(a,b,e){var h=
+this&&this.__extends||function(a,b){function l(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(l.prototype=b.prototype,new l)};a=function(a){function b(){a.apply(this,arguments)}h(b,a);b.prototype.notifyComplete=function(a){this.destination.complete()};b.prototype.notifyNext=function(a,f,d,c){this.destination.next(f)};b.prototype.notifyError=function(a,f){this.destination.error(a)};return b}(a("./Subscriber").Subscriber);e.OuterSubscriber=
+a},{"./Subscriber":7}],5:[function(a,b,e){b=a("./Subject");e.Subject=b.Subject;b=a("./Observable");e.Observable=b.Observable;a("./add/operator/combineLatest-static");a("./add/operator/concat-static");a("./add/operator/merge-static");a("./add/observable/bindCallback");a("./add/observable/defer");a("./add/observable/empty");a("./add/observable/forkJoin");a("./add/observable/from");a("./add/observable/fromArray");a("./add/observable/fromEvent");a("./add/observable/fromEventPattern");a("./add/observable/fromPromise");
+a("./add/observable/interval");a("./add/observable/never");a("./add/observable/range");a("./add/observable/throw");a("./add/observable/timer");a("./add/operator/zip-static");a("./add/operator/buffer");a("./add/operator/bufferCount");a("./add/operator/bufferTime");a("./add/operator/bufferToggle");a("./add/operator/bufferWhen");a("./add/operator/catch");a("./add/operator/combineAll");a("./add/operator/combineLatest");a("./add/operator/concat");a("./add/operator/concatAll");a("./add/operator/concatMap");
+a("./add/operator/concatMapTo");a("./add/operator/count");a("./add/operator/dematerialize");a("./add/operator/debounce");a("./add/operator/debounceTime");a("./add/operator/defaultIfEmpty");a("./add/operator/delay");a("./add/operator/distinctUntilChanged");a("./add/operator/do");a("./add/operator/expand");a("./add/operator/filter");a("./add/operator/finally");a("./add/operator/first");a("./add/operator/groupBy");a("./add/operator/ignoreElements");a("./add/operator/every");a("./add/operator/last");
+a("./add/operator/map");a("./add/operator/mapTo");a("./add/operator/materialize");a("./add/operator/merge");a("./add/operator/mergeAll");a("./add/operator/mergeMap");a("./add/operator/mergeMapTo");a("./add/operator/multicast");a("./add/operator/observeOn");a("./add/operator/partition");a("./add/operator/publish");a("./add/operator/publishBehavior");a("./add/operator/publishReplay");a("./add/operator/publishLast");a("./add/operator/reduce");a("./add/operator/repeat");a("./add/operator/retry");a("./add/operator/retryWhen");
+a("./add/operator/sample");a("./add/operator/sampleTime");a("./add/operator/scan");a("./add/operator/share");a("./add/operator/single");a("./add/operator/skip");a("./add/operator/skipUntil");a("./add/operator/skipWhile");a("./add/operator/startWith");a("./add/operator/subscribeOn");a("./add/operator/switch");a("./add/operator/switchMap");a("./add/operator/switchMapTo");a("./add/operator/take");a("./add/operator/takeUntil");a("./add/operator/takeWhile");a("./add/operator/throttle");a("./add/operator/throttleTime");
+a("./add/operator/timeout");a("./add/operator/timeoutWith");a("./add/operator/toArray");a("./add/operator/toPromise");a("./add/operator/window");a("./add/operator/windowCount");a("./add/operator/windowTime");a("./add/operator/windowToggle");a("./add/operator/windowWhen");a("./add/operator/withLatestFrom");a("./add/operator/zip");a("./add/operator/zipAll");b=a("./Subscription");e.Subscription=b.Subscription;b=a("./Subscriber");e.Subscriber=b.Subscriber;b=a("./subject/AsyncSubject");e.AsyncSubject=
+b.AsyncSubject;b=a("./subject/ReplaySubject");e.ReplaySubject=b.ReplaySubject;b=a("./subject/BehaviorSubject");e.BehaviorSubject=b.BehaviorSubject;b=a("./observable/ConnectableObservable");e.ConnectableObservable=b.ConnectableObservable;b=a("./Notification");e.Notification=b.Notification;b=a("./util/EmptyError");e.EmptyError=b.EmptyError;b=a("./util/ArgumentOutOfRangeError");e.ArgumentOutOfRangeError=b.ArgumentOutOfRangeError;b=a("./util/ObjectUnsubscribedError");e.ObjectUnsubscribedError=b.ObjectUnsubscribedError;
+b=a("./scheduler/asap");var h=a("./scheduler/queue");a=a("./symbol/rxSubscriber");e.Scheduler={asap:b.asap,queue:h.queue};e.Symbol={rxSubscriber:a.rxSubscriber}},{"./Notification":2,"./Observable":3,"./Subject":6,"./Subscriber":7,"./Subscription":8,"./add/observable/bindCallback":9,"./add/observable/defer":10,"./add/observable/empty":11,"./add/observable/forkJoin":12,"./add/observable/from":13,"./add/observable/fromArray":14,"./add/observable/fromEvent":15,"./add/observable/fromEventPattern":16,"./add/observable/fromPromise":17,
+"./add/observable/interval":18,"./add/observable/never":19,"./add/observable/range":20,"./add/observable/throw":21,"./add/observable/timer":22,"./add/operator/buffer":23,"./add/operator/bufferCount":24,"./add/operator/bufferTime":25,"./add/operator/bufferToggle":26,"./add/operator/bufferWhen":27,"./add/operator/catch":28,"./add/operator/combineAll":29,"./add/operator/combineLatest":31,"./add/operator/combineLatest-static":30,"./add/operator/concat":33,"./add/operator/concat-static":32,"./add/operator/concatAll":34,
+"./add/operator/concatMap":35,"./add/operator/concatMapTo":36,"./add/operator/count":37,"./add/operator/debounce":38,"./add/operator/debounceTime":39,"./add/operator/defaultIfEmpty":40,"./add/operator/delay":41,"./add/operator/dematerialize":42,"./add/operator/distinctUntilChanged":43,"./add/operator/do":44,"./add/operator/every":45,"./add/operator/expand":46,"./add/operator/filter":47,"./add/operator/finally":48,"./add/operator/first":49,"./add/operator/groupBy":50,"./add/operator/ignoreElements":51,
+"./add/operator/last":52,"./add/operator/map":53,"./add/operator/mapTo":54,"./add/operator/materialize":55,"./add/operator/merge":57,"./add/operator/merge-static":56,"./add/operator/mergeAll":58,"./add/operator/mergeMap":59,"./add/operator/mergeMapTo":60,"./add/operator/multicast":61,"./add/operator/observeOn":62,"./add/operator/partition":63,"./add/operator/publish":64,"./add/operator/publishBehavior":65,"./add/operator/publishLast":66,"./add/operator/publishReplay":67,"./add/operator/reduce":68,
+"./add/operator/repeat":69,"./add/operator/retry":70,"./add/operator/retryWhen":71,"./add/operator/sample":72,"./add/operator/sampleTime":73,"./add/operator/scan":74,"./add/operator/share":75,"./add/operator/single":76,"./add/operator/skip":77,"./add/operator/skipUntil":78,"./add/operator/skipWhile":79,"./add/operator/startWith":80,"./add/operator/subscribeOn":81,"./add/operator/switch":82,"./add/operator/switchMap":83,"./add/operator/switchMapTo":84,"./add/operator/take":85,"./add/operator/takeUntil":86,
+"./add/operator/takeWhile":87,"./add/operator/throttle":88,"./add/operator/throttleTime":89,"./add/operator/timeout":90,"./add/operator/timeoutWith":91,"./add/operator/toArray":92,"./add/operator/toPromise":93,"./add/operator/window":94,"./add/operator/windowCount":95,"./add/operator/windowTime":96,"./add/operator/windowToggle":97,"./add/operator/windowWhen":98,"./add/operator/withLatestFrom":99,"./add/operator/zip":101,"./add/operator/zip-static":100,"./add/operator/zipAll":102,"./observable/ConnectableObservable":103,
+"./scheduler/asap":215,"./scheduler/queue":216,"./subject/AsyncSubject":217,"./subject/BehaviorSubject":218,"./subject/ReplaySubject":219,"./symbol/rxSubscriber":221,"./util/ArgumentOutOfRangeError":222,"./util/EmptyError":223,"./util/ObjectUnsubscribedError":228}],6:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)};b=a("./Observable");var k=
+a("./Subscriber"),m=a("./Subscription"),l=a("./subject/SubjectSubscription"),f=a("./symbol/rxSubscriber"),d=m.Subscription.prototype.add,c=m.Subscription.prototype.remove,g=m.Subscription.prototype.unsubscribe,n=k.Subscriber.prototype.next,p=k.Subscriber.prototype.error,q=k.Subscriber.prototype.complete,w=k.Subscriber.prototype._next,x=k.Subscriber.prototype._error,r=k.Subscriber.prototype._complete;a=function(a){function n(){a.apply(this,arguments);this.observers=[];this.completeSignal=this.errorSignal=
+this.dispatching=this.isUnsubscribed=!1}h(n,a);n.prototype[f.rxSubscriber]=function(){return this};n.create=function(a,c){return new v(a,c)};n.prototype.lift=function(a){var c=new v(this,this.destination||this);c.operator=a;return c};n.prototype._subscribe=function(a){if(!a.isUnsubscribed)if(this.errorSignal)a.error(this.errorInstance);else if(this.completeSignal)a.complete();else{if(this.isUnsubscribed)throw Error("Cannot subscribe to a disposed Subject.");this.observers.push(a);return new l.SubjectSubscription(this,
+a)}};n.prototype.add=function(a){d.call(this,a)};n.prototype.remove=function(a){c.call(this,a)};n.prototype.unsubscribe=function(){this.observers=void 0;g.call(this)};n.prototype.next=function(a){this.isUnsubscribed||(this.dispatching=!0,this._next(a),this.dispatching=!1,this.errorSignal?this.error(this.errorInstance):this.completeSignal&&this.complete())};n.prototype.error=function(a){this.isUnsubscribed||this.completeSignal||(this.errorSignal=!0,this.errorInstance=a,this.dispatching||(this._error(a),
+this.unsubscribe()))};n.prototype.complete=function(){this.isUnsubscribed||this.errorSignal||(this.completeSignal=!0,this.dispatching||(this._complete(),this.unsubscribe()))};n.prototype._next=function(a){for(var c=-1,g=this.observers.slice(0),n=g.length;++c<n;)g[c].next(a)};n.prototype._error=function(a){var c=-1,g=this.observers,n=g.length;this.observers=void 0;for(this.isUnsubscribed=!0;++c<n;)g[c].error(a);this.isUnsubscribed=!1};n.prototype._complete=function(){var a=-1,c=this.observers,g=c.length;
+this.observers=void 0;for(this.isUnsubscribed=!0;++a<g;)c[a].complete();this.isUnsubscribed=!1};return n}(b.Observable);e.Subject=a;var v=function(a){function c(g,n){a.call(this);this.source=g;this.destination=n}h(c,a);c.prototype._subscribe=function(a){var c=this.operator;return this.source._subscribe.call(this.source,c?c.call(a):a)};c.prototype.next=function(a){n.call(this,a)};c.prototype.error=function(a){p.call(this,a)};c.prototype.complete=function(){q.call(this)};c.prototype._next=function(a){w.call(this,
+a)};c.prototype._error=function(a){x.call(this,a)};c.prototype._complete=function(){r.call(this)};return c}(a)},{"./Observable":3,"./Subscriber":7,"./Subscription":8,"./subject/SubjectSubscription":220,"./symbol/rxSubscriber":221}],7:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var n in c)c.hasOwnProperty(n)&&(a[n]=c[n]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)},k=a("./util/noop"),m=a("./util/throwError"),l=a("./util/tryOrOnError");
+b=a("./Subscription");var f=a("./symbol/rxSubscriber");a=function(a){function c(g){a.call(this);this.destination=g;this._isUnsubscribed=!1;if(this.destination){var n=g._subscription;n?this._subscription=n:g instanceof c&&(this._subscription=g)}}h(c,a);c.prototype[f.rxSubscriber]=function(){return this};Object.defineProperty(c.prototype,"isUnsubscribed",{get:function(){var a=this._subscription;return a?this._isUnsubscribed||a.isUnsubscribed:this._isUnsubscribed},set:function(a){var c=this._subscription;
+c?c.isUnsubscribed=Boolean(a):this._isUnsubscribed=Boolean(a)},enumerable:!0,configurable:!0});c.create=function(a,n,d){var f=new c;f._next="function"===typeof a&&l.tryOrOnError(a)||k.noop;f._error="function"===typeof n&&n||m.throwError;f._complete="function"===typeof d&&d||k.noop;return f};c.prototype.add=function(c){var n=this._subscription;n?n.add(c):a.prototype.add.call(this,c)};c.prototype.remove=function(c){this._subscription?this._subscription.remove(c):a.prototype.remove.call(this,c)};c.prototype.unsubscribe=
+function(){this._isUnsubscribed||(this._subscription?this._isUnsubscribed=!0:a.prototype.unsubscribe.call(this))};c.prototype._next=function(a){var c=this.destination;c.next&&c.next(a)};c.prototype._error=function(a){var c=this.destination;c.error&&c.error(a)};c.prototype._complete=function(){var a=this.destination;a.complete&&a.complete()};c.prototype.next=function(a){this.isUnsubscribed||this._next(a)};c.prototype.error=function(a){this.isUnsubscribed||(this._error(a),this.unsubscribe())};c.prototype.complete=
+function(){this.isUnsubscribed||(this._complete(),this.unsubscribe())};return c}(b.Subscription);e.Subscriber=a},{"./Subscription":8,"./symbol/rxSubscriber":221,"./util/noop":236,"./util/throwError":240,"./util/tryOrOnError":242}],8:[function(a,b,e){var h=a("./util/noop");a=function(){function a(b){this.isUnsubscribed=!1;b&&(this._unsubscribe=b)}a.prototype._unsubscribe=function(){h.noop()};a.prototype.unsubscribe=function(){if(!this.isUnsubscribed){this.isUnsubscribed=!0;var a=this._unsubscribe,
+b=this._subscriptions;this._subscriptions=void 0;a&&a.call(this);if(null!=b)for(var a=-1,f=b.length;++a<f;)b[a].unsubscribe()}};a.prototype.add=function(b){if(b&&b!==this&&b!==a.EMPTY){var l=b;switch(typeof b){case "function":l=new a(b);case "object":if(l.isUnsubscribed||"function"!==typeof l.unsubscribe)break;else this.isUnsubscribed?l.unsubscribe():(this._subscriptions||(this._subscriptions=[])).push(l);break;default:throw Error("Unrecognized subscription "+b+" added to Subscription.");}}};a.prototype.remove=
+function(b){if(null!=b&&b!==this&&b!==a.EMPTY){var l=this._subscriptions;l&&(b=l.indexOf(b),-1!==b&&l.splice(b,1))}};a.EMPTY=function(a){a.isUnsubscribed=!0;return a}(new a);return a}();e.Subscription=a},{"./util/noop":236}],9:[function(a,b,e){b=a("../../Observable");a=a("../../observable/bindCallback");b.Observable.bindCallback=a.BoundCallbackObservable.create},{"../../Observable":3,"../../observable/bindCallback":107}],10:[function(a,b,e){b=a("../../Observable");a=a("../../observable/defer");b.Observable.defer=
+a.DeferObservable.create},{"../../Observable":3,"../../observable/defer":108}],11:[function(a,b,e){b=a("../../Observable");a=a("../../observable/empty");b.Observable.empty=a.EmptyObservable.create},{"../../Observable":3,"../../observable/empty":109}],12:[function(a,b,e){b=a("../../Observable");a=a("../../observable/forkJoin");b.Observable.forkJoin=a.ForkJoinObservable.create},{"../../Observable":3,"../../observable/forkJoin":110}],13:[function(a,b,e){b=a("../../Observable");a=a("../../observable/from");
+b.Observable.from=a.FromObservable.create},{"../../Observable":3,"../../observable/from":111}],14:[function(a,b,e){b=a("../../Observable");a=a("../../observable/fromArray");b.Observable.fromArray=a.ArrayObservable.create;b.Observable.of=a.ArrayObservable.of},{"../../Observable":3,"../../observable/fromArray":112}],15:[function(a,b,e){b=a("../../Observable");a=a("../../observable/fromEvent");b.Observable.fromEvent=a.FromEventObservable.create},{"../../Observable":3,"../../observable/fromEvent":113}],
+16:[function(a,b,e){b=a("../../Observable");a=a("../../observable/fromEventPattern");b.Observable.fromEventPattern=a.FromEventPatternObservable.create},{"../../Observable":3,"../../observable/fromEventPattern":114}],17:[function(a,b,e){b=a("../../Observable");a=a("../../observable/fromPromise");b.Observable.fromPromise=a.PromiseObservable.create},{"../../Observable":3,"../../observable/fromPromise":115}],18:[function(a,b,e){b=a("../../Observable");a=a("../../observable/interval");b.Observable.interval=
+a.IntervalObservable.create},{"../../Observable":3,"../../observable/interval":116}],19:[function(a,b,e){b=a("../../Observable");a=a("../../observable/never");b.Observable.never=a.InfiniteObservable.create},{"../../Observable":3,"../../observable/never":117}],20:[function(a,b,e){b=a("../../Observable");a=a("../../observable/range");b.Observable.range=a.RangeObservable.create},{"../../Observable":3,"../../observable/range":118}],21:[function(a,b,e){b=a("../../Observable");a=a("../../observable/throw");
+b.Observable.throw=a.ErrorObservable.create},{"../../Observable":3,"../../observable/throw":119}],22:[function(a,b,e){b=a("../../Observable");a=a("../../observable/timer");b.Observable.timer=a.TimerObservable.create},{"../../Observable":3,"../../observable/timer":120}],23:[function(a,b,e){b=a("../../Observable");a=a("../../operator/buffer");b.Observable.prototype.buffer=a.buffer},{"../../Observable":3,"../../operator/buffer":121}],24:[function(a,b,e){b=a("../../Observable");a=a("../../operator/bufferCount");
+b.Observable.prototype.bufferCount=a.bufferCount},{"../../Observable":3,"../../operator/bufferCount":122}],25:[function(a,b,e){b=a("../../Observable");a=a("../../operator/bufferTime");b.Observable.prototype.bufferTime=a.bufferTime},{"../../Observable":3,"../../operator/bufferTime":123}],26:[function(a,b,e){b=a("../../Observable");a=a("../../operator/bufferToggle");b.Observable.prototype.bufferToggle=a.bufferToggle},{"../../Observable":3,"../../operator/bufferToggle":124}],27:[function(a,b,e){b=a("../../Observable");
+a=a("../../operator/bufferWhen");b.Observable.prototype.bufferWhen=a.bufferWhen},{"../../Observable":3,"../../operator/bufferWhen":125}],28:[function(a,b,e){b=a("../../Observable");a=a("../../operator/catch");b.Observable.prototype.catch=a._catch},{"../../Observable":3,"../../operator/catch":126}],29:[function(a,b,e){b=a("../../Observable");a=a("../../operator/combineAll");b.Observable.prototype.combineAll=a.combineAll},{"../../Observable":3,"../../operator/combineAll":127}],30:[function(a,b,e){b=
+a("../../Observable");a=a("../../operator/combineLatest-static");b.Observable.combineLatest=a.combineLatest},{"../../Observable":3,"../../operator/combineLatest-static":128}],31:[function(a,b,e){b=a("../../Observable");a=a("../../operator/combineLatest");b.Observable.prototype.combineLatest=a.combineLatest},{"../../Observable":3,"../../operator/combineLatest":130}],32:[function(a,b,e){b=a("../../Observable");a=a("../../operator/concat-static");b.Observable.concat=a.concat},{"../../Observable":3,"../../operator/concat-static":131}],
+33:[function(a,b,e){b=a("../../Observable");a=a("../../operator/concat");b.Observable.prototype.concat=a.concat},{"../../Observable":3,"../../operator/concat":132}],34:[function(a,b,e){b=a("../../Observable");a=a("../../operator/concatAll");b.Observable.prototype.concatAll=a.concatAll},{"../../Observable":3,"../../operator/concatAll":133}],35:[function(a,b,e){b=a("../../Observable");a=a("../../operator/concatMap");b.Observable.prototype.concatMap=a.concatMap},{"../../Observable":3,"../../operator/concatMap":134}],
+36:[function(a,b,e){b=a("../../Observable");a=a("../../operator/concatMapTo");b.Observable.prototype.concatMapTo=a.concatMapTo},{"../../Observable":3,"../../operator/concatMapTo":135}],37:[function(a,b,e){b=a("../../Observable");a=a("../../operator/count");b.Observable.prototype.count=a.count},{"../../Observable":3,"../../operator/count":136}],38:[function(a,b,e){b=a("../../Observable");a=a("../../operator/debounce");b.Observable.prototype.debounce=a.debounce},{"../../Observable":3,"../../operator/debounce":137}],
+39:[function(a,b,e){b=a("../../Observable");a=a("../../operator/debounceTime");b.Observable.prototype.debounceTime=a.debounceTime},{"../../Observable":3,"../../operator/debounceTime":138}],40:[function(a,b,e){b=a("../../Observable");a=a("../../operator/defaultIfEmpty");b.Observable.prototype.defaultIfEmpty=a.defaultIfEmpty},{"../../Observable":3,"../../operator/defaultIfEmpty":139}],41:[function(a,b,e){b=a("../../Observable");a=a("../../operator/delay");b.Observable.prototype.delay=a.delay},{"../../Observable":3,
+"../../operator/delay":140}],42:[function(a,b,e){b=a("../../Observable");a=a("../../operator/dematerialize");b.Observable.prototype.dematerialize=a.dematerialize},{"../../Observable":3,"../../operator/dematerialize":141}],43:[function(a,b,e){b=a("../../Observable");a=a("../../operator/distinctUntilChanged");b.Observable.prototype.distinctUntilChanged=a.distinctUntilChanged},{"../../Observable":3,"../../operator/distinctUntilChanged":142}],44:[function(a,b,e){b=a("../../Observable");a=a("../../operator/do");
+b.Observable.prototype.do=a._do},{"../../Observable":3,"../../operator/do":143}],45:[function(a,b,e){b=a("../../Observable");a=a("../../operator/every");b.Observable.prototype.every=a.every},{"../../Observable":3,"../../operator/every":144}],46:[function(a,b,e){b=a("../../Observable");a=a("../../operator/expand");b.Observable.prototype.expand=a.expand},{"../../Observable":3,"../../operator/expand":146}],47:[function(a,b,e){b=a("../../Observable");a=a("../../operator/filter");b.Observable.prototype.filter=
+a.filter},{"../../Observable":3,"../../operator/filter":147}],48:[function(a,b,e){b=a("../../Observable");a=a("../../operator/finally");b.Observable.prototype.finally=a._finally},{"../../Observable":3,"../../operator/finally":148}],49:[function(a,b,e){b=a("../../Observable");a=a("../../operator/first");b.Observable.prototype.first=a.first},{"../../Observable":3,"../../operator/first":149}],50:[function(a,b,e){b=a("../../Observable");a=a("../../operator/groupBy");b.Observable.prototype.groupBy=a.groupBy},
+{"../../Observable":3,"../../operator/groupBy":151}],51:[function(a,b,e){b=a("../../Observable");a=a("../../operator/ignoreElements");b.Observable.prototype.ignoreElements=a.ignoreElements},{"../../Observable":3,"../../operator/ignoreElements":152}],52:[function(a,b,e){b=a("../../Observable");a=a("../../operator/last");b.Observable.prototype.last=a.last},{"../../Observable":3,"../../operator/last":153}],53:[function(a,b,e){b=a("../../Observable");a=a("../../operator/map");b.Observable.prototype.map=
+a.map},{"../../Observable":3,"../../operator/map":154}],54:[function(a,b,e){b=a("../../Observable");a=a("../../operator/mapTo");b.Observable.prototype.mapTo=a.mapTo},{"../../Observable":3,"../../operator/mapTo":155}],55:[function(a,b,e){b=a("../../Observable");a=a("../../operator/materialize");b.Observable.prototype.materialize=a.materialize},{"../../Observable":3,"../../operator/materialize":156}],56:[function(a,b,e){b=a("../../Observable");a=a("../../operator/merge-static");b.Observable.merge=a.merge},
+{"../../Observable":3,"../../operator/merge-static":157}],57:[function(a,b,e){b=a("../../Observable");a=a("../../operator/merge");b.Observable.prototype.merge=a.merge},{"../../Observable":3,"../../operator/merge":158}],58:[function(a,b,e){b=a("../../Observable");a=a("../../operator/mergeAll");b.Observable.prototype.mergeAll=a.mergeAll},{"../../Observable":3,"../../operator/mergeAll":160}],59:[function(a,b,e){b=a("../../Observable");a=a("../../operator/mergeMap");b.Observable.prototype.mergeMap=a.mergeMap;
+b.Observable.prototype.flatMap=a.mergeMap},{"../../Observable":3,"../../operator/mergeMap":162}],60:[function(a,b,e){b=a("../../Observable");a=a("../../operator/mergeMapTo");b.Observable.prototype.mergeMapTo=a.mergeMapTo},{"../../Observable":3,"../../operator/mergeMapTo":164}],61:[function(a,b,e){b=a("../../Observable");a=a("../../operator/multicast");b.Observable.prototype.multicast=a.multicast},{"../../Observable":3,"../../operator/multicast":165}],62:[function(a,b,e){b=a("../../Observable");a=
+a("../../operator/observeOn");b.Observable.prototype.observeOn=a.observeOn},{"../../Observable":3,"../../operator/observeOn":167}],63:[function(a,b,e){b=a("../../Observable");a=a("../../operator/partition");b.Observable.prototype.partition=a.partition},{"../../Observable":3,"../../operator/partition":168}],64:[function(a,b,e){b=a("../../Observable");a=a("../../operator/publish");b.Observable.prototype.publish=a.publish},{"../../Observable":3,"../../operator/publish":169}],65:[function(a,b,e){b=a("../../Observable");
+a=a("../../operator/publishBehavior");b.Observable.prototype.publishBehavior=a.publishBehavior},{"../../Observable":3,"../../operator/publishBehavior":170}],66:[function(a,b,e){b=a("../../Observable");a=a("../../operator/publishLast");b.Observable.prototype.publishLast=a.publishLast},{"../../Observable":3,"../../operator/publishLast":171}],67:[function(a,b,e){b=a("../../Observable");a=a("../../operator/publishReplay");b.Observable.prototype.publishReplay=a.publishReplay},{"../../Observable":3,"../../operator/publishReplay":172}],
+68:[function(a,b,e){b=a("../../Observable");a=a("../../operator/reduce");b.Observable.prototype.reduce=a.reduce},{"../../Observable":3,"../../operator/reduce":174}],69:[function(a,b,e){b=a("../../Observable");a=a("../../operator/repeat");b.Observable.prototype.repeat=a.repeat},{"../../Observable":3,"../../operator/repeat":175}],70:[function(a,b,e){b=a("../../Observable");a=a("../../operator/retry");b.Observable.prototype.retry=a.retry},{"../../Observable":3,"../../operator/retry":176}],71:[function(a,
+b,e){b=a("../../Observable");a=a("../../operator/retryWhen");b.Observable.prototype.retryWhen=a.retryWhen},{"../../Observable":3,"../../operator/retryWhen":177}],72:[function(a,b,e){b=a("../../Observable");a=a("../../operator/sample");b.Observable.prototype.sample=a.sample},{"../../Observable":3,"../../operator/sample":178}],73:[function(a,b,e){b=a("../../Observable");a=a("../../operator/sampleTime");b.Observable.prototype.sampleTime=a.sampleTime},{"../../Observable":3,"../../operator/sampleTime":179}],
+74:[function(a,b,e){b=a("../../Observable");a=a("../../operator/scan");b.Observable.prototype.scan=a.scan},{"../../Observable":3,"../../operator/scan":180}],75:[function(a,b,e){b=a("../../Observable");a=a("../../operator/share");b.Observable.prototype.share=a.share},{"../../Observable":3,"../../operator/share":181}],76:[function(a,b,e){b=a("../../Observable");a=a("../../operator/single");b.Observable.prototype.single=a.single},{"../../Observable":3,"../../operator/single":182}],77:[function(a,b,e){b=
+a("../../Observable");a=a("../../operator/skip");b.Observable.prototype.skip=a.skip},{"../../Observable":3,"../../operator/skip":183}],78:[function(a,b,e){b=a("../../Observable");a=a("../../operator/skipUntil");b.Observable.prototype.skipUntil=a.skipUntil},{"../../Observable":3,"../../operator/skipUntil":184}],79:[function(a,b,e){b=a("../../Observable");a=a("../../operator/skipWhile");b.Observable.prototype.skipWhile=a.skipWhile},{"../../Observable":3,"../../operator/skipWhile":185}],80:[function(a,
+b,e){b=a("../../Observable");a=a("../../operator/startWith");b.Observable.prototype.startWith=a.startWith},{"../../Observable":3,"../../operator/startWith":186}],81:[function(a,b,e){b=a("../../Observable");a=a("../../operator/subscribeOn");b.Observable.prototype.subscribeOn=a.subscribeOn},{"../../Observable":3,"../../operator/subscribeOn":187}],82:[function(a,b,e){b=a("../../Observable");a=a("../../operator/switch");b.Observable.prototype.switch=a._switch},{"../../Observable":3,"../../operator/switch":188}],
+83:[function(a,b,e){b=a("../../Observable");a=a("../../operator/switchMap");b.Observable.prototype.switchMap=a.switchMap},{"../../Observable":3,"../../operator/switchMap":189}],84:[function(a,b,e){b=a("../../Observable");a=a("../../operator/switchMapTo");b.Observable.prototype.switchMapTo=a.switchMapTo},{"../../Observable":3,"../../operator/switchMapTo":190}],85:[function(a,b,e){b=a("../../Observable");a=a("../../operator/take");b.Observable.prototype.take=a.take},{"../../Observable":3,"../../operator/take":191}],
+86:[function(a,b,e){b=a("../../Observable");a=a("../../operator/takeUntil");b.Observable.prototype.takeUntil=a.takeUntil},{"../../Observable":3,"../../operator/takeUntil":192}],87:[function(a,b,e){b=a("../../Observable");a=a("../../operator/takeWhile");b.Observable.prototype.takeWhile=a.takeWhile},{"../../Observable":3,"../../operator/takeWhile":193}],88:[function(a,b,e){b=a("../../Observable");a=a("../../operator/throttle");b.Observable.prototype.throttle=a.throttle},{"../../Observable":3,"../../operator/throttle":194}],
+89:[function(a,b,e){b=a("../../Observable");a=a("../../operator/throttleTime");b.Observable.prototype.throttleTime=a.throttleTime},{"../../Observable":3,"../../operator/throttleTime":195}],90:[function(a,b,e){b=a("../../Observable");a=a("../../operator/timeout");b.Observable.prototype.timeout=a.timeout},{"../../Observable":3,"../../operator/timeout":196}],91:[function(a,b,e){b=a("../../Observable");a=a("../../operator/timeoutWith");b.Observable.prototype.timeoutWith=a.timeoutWith},{"../../Observable":3,
+"../../operator/timeoutWith":197}],92:[function(a,b,e){b=a("../../Observable");a=a("../../operator/toArray");b.Observable.prototype.toArray=a.toArray},{"../../Observable":3,"../../operator/toArray":198}],93:[function(a,b,e){b=a("../../Observable");a=a("../../operator/toPromise");b.Observable.prototype.toPromise=a.toPromise},{"../../Observable":3,"../../operator/toPromise":199}],94:[function(a,b,e){b=a("../../Observable");a=a("../../operator/window");b.Observable.prototype.window=a.window},{"../../Observable":3,
+"../../operator/window":200}],95:[function(a,b,e){b=a("../../Observable");a=a("../../operator/windowCount");b.Observable.prototype.windowCount=a.windowCount},{"../../Observable":3,"../../operator/windowCount":201}],96:[function(a,b,e){b=a("../../Observable");a=a("../../operator/windowTime");b.Observable.prototype.windowTime=a.windowTime},{"../../Observable":3,"../../operator/windowTime":202}],97:[function(a,b,e){b=a("../../Observable");a=a("../../operator/windowToggle");b.Observable.prototype.windowToggle=
+a.windowToggle},{"../../Observable":3,"../../operator/windowToggle":203}],98:[function(a,b,e){b=a("../../Observable");a=a("../../operator/windowWhen");b.Observable.prototype.windowWhen=a.windowWhen},{"../../Observable":3,"../../operator/windowWhen":204}],99:[function(a,b,e){b=a("../../Observable");a=a("../../operator/withLatestFrom");b.Observable.prototype.withLatestFrom=a.withLatestFrom},{"../../Observable":3,"../../operator/withLatestFrom":205}],100:[function(a,b,e){b=a("../../Observable");a=a("../../operator/zip-static");
+b.Observable.zip=a.zip},{"../../Observable":3,"../../operator/zip-static":206}],101:[function(a,b,e){b=a("../../Observable");a=a("../../operator/zip");b.Observable.prototype.zip=a.zipProto},{"../../Observable":3,"../../operator/zip":208}],102:[function(a,b,e){b=a("../../Observable");a=a("../../operator/zipAll");b.Observable.prototype.zipAll=a.zipAll},{"../../Observable":3,"../../operator/zipAll":209}],103:[function(a,b,e){var h=this&&this.__extends||function(a,g){function n(){this.constructor=a}for(var d in g)g.hasOwnProperty(d)&&
+(a[d]=g[d]);a.prototype=null===g?Object.create(g):(n.prototype=g.prototype,new n)};b=a("../Observable");var k=a("../Subscription");a=a("../Subscriber");var m=function(a){function g(n,g){a.call(this);this.source=n;this.subjectFactory=g}h(g,a);g.prototype._subscribe=function(a){return this._getSubject().subscribe(a)};g.prototype._getSubject=function(){var a=this.subject;return a&&!a.isUnsubscribed?a:this.subject=this.subjectFactory()};g.prototype.connect=function(){var a=this.subscription;if(a&&!a.isUnsubscribed)return a;
+a=this.source.subscribe(this._getSubject());a.add(new l(this));return this.subscription=a};g.prototype.refCount=function(){return new f(this)};return g}(b.Observable);e.ConnectableObservable=m;var l=function(a){function g(g){a.call(this);this.connectable=g}h(g,a);g.prototype._unsubscribe=function(){var a=this.connectable;a.subject=void 0;this.connectable=a.subscription=void 0};return g}(k.Subscription),f=function(a){function g(g,d){void 0===d&&(d=0);a.call(this);this.connectable=g;this.refCount=d}
+h(g,a);g.prototype._subscribe=function(a){var c=this.connectable;a=new d(a,this);var g=c.subscribe(a);g.isUnsubscribed||1!==++this.refCount||(a.connection=this.connection=c.connect());return g};return g}(b.Observable),d=function(a){function g(g,d){a.call(this,null);this.destination=g;this.refCountObservable=d;this.connection=d.connection;g.add(this)}h(g,a);g.prototype._next=function(a){this.destination.next(a)};g.prototype._error=function(a){this._resetConnectable();this.destination.error(a)};g.prototype._complete=
+function(){this._resetConnectable();this.destination.complete()};g.prototype._resetConnectable=function(){var a=this.refCountObservable,c=a.connection,g=this.connection;g&&g===c&&(a.refCount=0,c.unsubscribe(),a.connection=void 0,this.unsubscribe())};g.prototype._unsubscribe=function(){var a=this.refCountObservable;if(0!==a.refCount&&0===--a.refCount){var c=a.connection,g=this.connection;g&&g===c&&(c.unsubscribe(),a.connection=void 0)}};return g}(a.Subscriber)},{"../Observable":3,"../Subscriber":7,
+"../Subscription":8}],104:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)};b=a("../Observable");var k=a("../util/root"),m=a("../util/SymbolShim"),l=a("../util/tryCatch"),f=a("../util/errorObject");a=function(a){function g(f,b,p,l){a.call(this);this.project=b;this.thisArg=p;this.scheduler=l;if(null==f)throw Error("iterator cannot be null.");
+if(b&&"function"!==typeof b)throw Error("When provided, `project` must be a function.");if((b=f[m.SymbolShim.iterator])||"string"!==typeof f)if(b||void 0===f.length){if(!b)throw new TypeError("Object is not iterable");f=f[m.SymbolShim.iterator]()}else f=new c(f);else f=new d(f);this.iterator=f}h(g,a);g.create=function(a,c,d,n){return new g(a,c,d,n)};g.dispatch=function(a){var c=a.index,g=a.thisArg,d=a.project,n=a.iterator,b=a.subscriber;a.hasError?b.error(a.error):(n=n.next(),n.done?b.complete():
+(d?(n=l.tryCatch(d).call(g,n.value,c),n===f.errorObject?(a.error=f.errorObject.e,a.hasError=!0):(b.next(n),a.index=c+1)):(b.next(n.value),a.index=c+1),b.isUnsubscribed||this.schedule(a)))};g.prototype._subscribe=function(a){var c=0,d=this.iterator,n=this.project,b=this.thisArg,m=this.scheduler;if(m)a.add(m.schedule(g.dispatch,0,{index:c,thisArg:b,project:n,iterator:d,subscriber:a}));else{do{m=d.next();if(m.done){a.complete();break}else if(n){m=l.tryCatch(n).call(b,m.value,c++);if(m===f.errorObject){a.error(f.errorObject.e);
+break}a.next(m)}else a.next(m.value);if(a.isUnsubscribed)break}while(1)}};return g}(b.Observable);e.IteratorObservable=a;var d=function(){function a(c,g,d){void 0===g&&(g=0);void 0===d&&(d=c.length);this.str=c;this.idx=g;this.len=d}a.prototype[m.SymbolShim.iterator]=function(){return this};a.prototype.next=function(){return this.idx<this.len?{done:!1,value:this.str.charAt(this.idx++)}:{done:!0,value:void 0}};return a}(),c=function(){function a(c,d,n){void 0===d&&(d=0);if(void 0===n)if(n=+c.length,
+isNaN(n))n=0;else if(0!==n&&"number"===typeof n&&k.root.isFinite(n)){var f;f=+n;f=0===f?f:isNaN(f)?f:0>f?-1:1;n=f*Math.floor(Math.abs(n));n=0>=n?0:n>g?g:n}this.arr=c;this.idx=d;this.len=n}a.prototype[m.SymbolShim.iterator]=function(){return this};a.prototype.next=function(){return this.idx<this.len?{done:!1,value:this.arr[this.idx++]}:{done:!0,value:void 0}};return a}(),g=Math.pow(2,53)-1},{"../Observable":3,"../util/SymbolShim":229,"../util/errorObject":230,"../util/root":238,"../util/tryCatch":241}],
+105:[function(a,b,e){var h=this&&this.__extends||function(a,g){function d(){this.constructor=a}for(var f in g)g.hasOwnProperty(f)&&(a[f]=g[f]);a.prototype=null===g?Object.create(g):(d.prototype=g.prototype,new d)};b=a("../Observable");var k=a("../util/tryCatch"),m=a("../util/errorObject"),l=a("./throw"),f=a("./empty"),d=function(a){function g(g,d){a.call(this);this.value=g;this.scheduler=d;this._isScalar=!0}h(g,a);g.create=function(a,c){return new g(a,c)};g.dispatch=function(a){var c=a.value,g=a.subscriber;
+a.done?g.complete():(g.next(c),g.isUnsubscribed||(a.done=!0,this.schedule(a)))};g.prototype._subscribe=function(a){var c=this.value,d=this.scheduler;d?a.add(d.schedule(g.dispatch,0,{done:!1,value:c,subscriber:a})):(a.next(c),a.isUnsubscribed||a.complete())};return g}(b.Observable);e.ScalarObservable=d;a=d.prototype;a.map=function(a,g){return k.tryCatch(a).call(g||this,this.value,0)===m.errorObject?new l.ErrorObservable(m.errorObject.e):new d(a.call(g||this,this.value,0))};a.filter=function(a,g){var d=
+k.tryCatch(a).call(g||this,this.value,0);return d===m.errorObject?new l.ErrorObservable(m.errorObject.e):d?this:new f.EmptyObservable};a.reduce=function(a,g){if("undefined"===typeof g)return this;var n=k.tryCatch(a)(g,this.value);return n===m.errorObject?new l.ErrorObservable(m.errorObject.e):new d(n)};a.scan=function(a,g){return this.reduce(a,g)};a.count=function(a){return a?(a=k.tryCatch(a).call(this,this.value,0,this),a===m.errorObject?new l.ErrorObservable(m.errorObject.e):new d(a?1:0)):new d(1)};
+a.skip=function(a){return 0<a?new f.EmptyObservable:this};a.take=function(a){return 0<a?this:new f.EmptyObservable}},{"../Observable":3,"../util/errorObject":230,"../util/tryCatch":241,"./empty":109,"./throw":119}],106:[function(a,b,e){var h=this&&this.__extends||function(a,f){function d(){this.constructor=a}for(var c in f)f.hasOwnProperty(c)&&(a[c]=f[c]);a.prototype=null===f?Object.create(f):(d.prototype=f.prototype,new d)};b=a("../Observable");var k=a("../scheduler/asap"),m=a("../util/isNumeric");
+a=function(a){function f(d,c,g){void 0===c&&(c=0);void 0===g&&(g=k.asap);a.call(this);this.source=d;this.delayTime=c;this.scheduler=g;if(!m.isNumeric(c)||0>c)this.delayTime=0;g&&"function"===typeof g.schedule||(this.scheduler=k.asap)}h(f,a);f.create=function(a,c,g){void 0===c&&(c=0);void 0===g&&(g=k.asap);return new f(a,c,g)};f.dispatch=function(a){return a.source.subscribe(a.subscriber)};f.prototype._subscribe=function(a){a.add(this.scheduler.schedule(f.dispatch,this.delayTime,{source:this.source,
+subscriber:a}))};return f}(b.Observable);e.SubscribeOnObservable=a},{"../Observable":3,"../scheduler/asap":215,"../util/isNumeric":233}],107:[function(a,b,e){function h(a){var n=a.source;a=a.subscriber;var b=n.callbackFunc,l=n.args,e=n.scheduler,h=n.subject;if(!h){var h=n.subject=new c.AsyncSubject,r=function u(){for(var a=[],c=0;c<arguments.length;c++)a[c-0]=arguments[c];var g=u.source,c=g.selector,g=g.subject;c?(a=f.tryCatch(c).apply(this,a),a===d.errorObject?g.add(e.schedule(m,0,{err:d.errorObject.e,
+subject:g})):g.add(e.schedule(k,0,{value:a,subject:g}))):g.add(e.schedule(k,0,{value:1===a.length?a[0]:a,subject:g}))};r.source=n;f.tryCatch(b).apply(this,l.concat(r))===d.errorObject&&h.error(d.errorObject.e)}this.add(h.subscribe(a))}function k(a){var c=a.subject;c.next(a.value);c.complete()}function m(a){a.subject.error(a.err)}var l=this&&this.__extends||function(a,c){function d(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(d.prototype=
+c.prototype,new d)};b=a("../Observable");var f=a("../util/tryCatch"),d=a("../util/errorObject"),c=a("../subject/AsyncSubject");a=function(a){function n(c,d,n,f){a.call(this);this.callbackFunc=c;this.selector=d;this.args=n;this.scheduler=f}l(n,a);n.create=function(a,c,g){void 0===c&&(c=void 0);return function(){for(var d=[],f=0;f<arguments.length;f++)d[f-0]=arguments[f];return new n(a,c,d,g)}};n.prototype._subscribe=function(a){var g=this.callbackFunc,n=this.args,b=this.scheduler,l=this.subject;if(b)return a.add(b.schedule(h,
+0,{source:this,subscriber:a})),a;l||(l=this.subject=new c.AsyncSubject,b=function u(){for(var a=[],c=0;c<arguments.length;c++)a[c-0]=arguments[c];var g=u.source,c=g.selector,g=g.subject;c?(a=f.tryCatch(c).apply(this,a),a===d.errorObject?g.error(d.errorObject.e):(g.next(a),g.complete())):(g.next(1===a.length?a[0]:a),g.complete())},b.source=this,f.tryCatch(g).apply(this,n.concat(b))===d.errorObject&&l.error(d.errorObject.e));return l.subscribe(a)};return n}(b.Observable);e.BoundCallbackObservable=a},
+{"../Observable":3,"../subject/AsyncSubject":217,"../util/errorObject":230,"../util/tryCatch":241}],108:[function(a,b,e){var h=this&&this.__extends||function(a,f){function d(){this.constructor=a}for(var c in f)f.hasOwnProperty(c)&&(a[c]=f[c]);a.prototype=null===f?Object.create(f):(d.prototype=f.prototype,new d)};b=a("../Observable");var k=a("../util/tryCatch"),m=a("../util/errorObject");a=function(a){function f(d){a.call(this);this.observableFactory=d}h(f,a);f.create=function(a){return new f(a)};
+f.prototype._subscribe=function(a){var c=k.tryCatch(this.observableFactory)();c===m.errorObject?a.error(m.errorObject.e):c.subscribe(a)};return f}(b.Observable);e.DeferObservable=a},{"../Observable":3,"../util/errorObject":230,"../util/tryCatch":241}],109:[function(a,b,e){var h=this&&this.__extends||function(a,b){function l(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(l.prototype=b.prototype,new l)};a=function(a){function b(l){a.call(this);
+this.scheduler=l}h(b,a);b.create=function(a){return new b(a)};b.dispatch=function(a){a.subscriber.complete()};b.prototype._subscribe=function(a){var f=this.scheduler;f?a.add(f.schedule(b.dispatch,0,{subscriber:a})):a.complete()};return b}(a("../Observable").Observable);e.EmptyObservable=a},{"../Observable":3}],110:[function(a,b,e){function h(a){return null!==a}var k=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d]);a.prototype=null===
+c?Object.create(c):(g.prototype=c.prototype,new g)},m=a("../Observable");b=a("../Subscriber");var l=a("./fromPromise"),f=a("./empty"),d=a("../util/isPromise"),c=a("../util/isArray");a=function(a){function b(c,g){a.call(this);this.sources=c;this.resultSelector=g}k(b,a);b.create=function(){for(var a=[],g=0;g<arguments.length;g++)a[g-0]=arguments[g];if(null===a||0===arguments.length)return new f.EmptyObservable;g=null;"function"===typeof a[a.length-1]&&(g=a.pop());1===a.length&&c.isArray(a[0])&&(a=a[0]);
+return new b(a,g)};b.prototype._subscribe=function(a){for(var c=this.sources,n=c.length,f=[],b=0;b<n;b++)f.push(null);f={completed:0,total:n,values:f,selector:this.resultSelector};for(b=0;b<n;b++){var p=c[b];d.isPromise(p)&&(p=new l.PromiseObservable(p));p.subscribe(new g(a,b,f))}};return b}(m.Observable);e.ForkJoinObservable=a;var g=function(a){function c(g,d,f){a.call(this,g);this.index=d;this.context=f;this._value=null}k(c,a);c.prototype._next=function(a){this._value=a};c.prototype._complete=function(){var a=
+this.destination;null==this._value&&a.complete();var c=this.context;c.completed++;c.values[this.index]=this._value;var g=c.values;c.completed===g.length&&(g.every(h)&&(c=c.selector?c.selector.apply(this,g):g,a.next(c)),a.complete())};return c}(b.Subscriber)},{"../Observable":3,"../Subscriber":7,"../util/isArray":231,"../util/isPromise":234,"./empty":109,"./fromPromise":115}],111:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var d in c)c.hasOwnProperty(d)&&
+(a[d]=c[d]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)},k=a("./fromPromise"),m=a("./IteratorObservable"),l=a("./fromArray"),f=a("../util/SymbolShim"),d=a("../Observable"),c=a("../operator/observeOn-support"),g=a("../scheduler/queue"),n=Array.isArray;a=function(a){function b(c,g){a.call(this,null);this.ish=c;this.scheduler=g}h(b,a);b.create=function(a,c){void 0===c&&(c=g.queue);if(a){if(n(a))return new l.ArrayObservable(a,c);if("function"===typeof a.then)return new k.PromiseObservable(a,
+c);if("function"===typeof a[f.SymbolShim.observable])return a instanceof d.Observable?a:new b(a,c);if("function"===typeof a[f.SymbolShim.iterator])return new m.IteratorObservable(a,null,null,c)}throw new TypeError(typeof a+" is not observable");};b.prototype._subscribe=function(a){var d=this.ish,n=this.scheduler;return n===g.queue?d[f.SymbolShim.observable]().subscribe(a):d[f.SymbolShim.observable]().subscribe(new c.ObserveOnSubscriber(a,n,0))};return b}(d.Observable);e.FromObservable=a},{"../Observable":3,
+"../operator/observeOn-support":166,"../scheduler/queue":216,"../util/SymbolShim":229,"./IteratorObservable":104,"./fromArray":112,"./fromPromise":115}],112:[function(a,b,e){var h=this&&this.__extends||function(a,d){function c(){this.constructor=a}for(var g in d)d.hasOwnProperty(g)&&(a[g]=d[g]);a.prototype=null===d?Object.create(d):(c.prototype=d.prototype,new c)};b=a("../Observable");var k=a("./ScalarObservable"),m=a("./empty"),l=a("../util/isScheduler");a=function(a){function d(c,g){a.call(this);
+this.array=c;this.scheduler=g;g||1!==c.length||(this._isScalar=!0,this.value=c[0])}h(d,a);d.create=function(a,g){return new d(a,g)};d.of=function(){for(var a=[],g=0;g<arguments.length;g++)a[g-0]=arguments[g];g=a[a.length-1];l.isScheduler(g)?a.pop():g=void 0;var n=a.length;return 1<n?new d(a,g):1===n?new k.ScalarObservable(a[0],g):new m.EmptyObservable(g)};d.dispatch=function(a){var g=a.array,d=a.index,f=a.subscriber;d>=a.count?f.complete():(f.next(g[d]),f.isUnsubscribed||(a.index=d+1,this.schedule(a)))};
+d.prototype._subscribe=function(a){var g=this.array,n=g.length,f=this.scheduler;if(f)a.add(f.schedule(d.dispatch,0,{array:g,index:0,count:n,subscriber:a}));else{for(f=0;f<n&&!a.isUnsubscribed;f++)a.next(g[f]);a.complete()}};return d}(b.Observable);e.ArrayObservable=a},{"../Observable":3,"../util/isScheduler":235,"./ScalarObservable":105,"./empty":109}],113:[function(a,b,e){var h=this&&this.__extends||function(a,d){function c(){this.constructor=a}for(var g in d)d.hasOwnProperty(g)&&(a[g]=d[g]);a.prototype=
+null===d?Object.create(d):(c.prototype=d.prototype,new c)};b=a("../Observable");var k=a("../util/tryCatch"),m=a("../util/errorObject"),l=a("../Subscription");a=function(a){function d(c,g,d){a.call(this);this.sourceObj=c;this.eventName=g;this.selector=d}h(d,a);d.create=function(a,g,n){return new d(a,g,n)};d.setupSubscription=function(a,g,n,f){var b,e=a.toString();if("[object NodeList]"===e||"[object HTMLCollection]"===e)for(var e=0,m=a.length;e<m;e++)d.setupSubscription(a[e],g,n,f);else"function"===
+typeof a.addEventListener&&"function"===typeof a.removeEventListener?(a.addEventListener(g,n),b=function(){return a.removeEventListener(g,n)}):"function"===typeof a.on&&"function"===typeof a.off?(a.on(g,n),b=function(){return a.off(g,n)}):"function"===typeof a.addListener&&"function"===typeof a.removeListener&&(a.addListener(g,n),b=function(){return a.removeListener(g,n)});f.add(new l.Subscription(b))};d.prototype._subscribe=function(a){var g=this.selector;d.setupSubscription(this.sourceObj,this.eventName,
+g?function(d){d=k.tryCatch(g)(d);d===m.errorObject?a.error(d.e):a.next(d)}:function(g){return a.next(g)},a)};return d}(b.Observable);e.FromEventObservable=a},{"../Observable":3,"../Subscription":8,"../util/errorObject":230,"../util/tryCatch":241}],114:[function(a,b,e){var h=this&&this.__extends||function(a,d){function c(){this.constructor=a}for(var g in d)d.hasOwnProperty(g)&&(a[g]=d[g]);a.prototype=null===d?Object.create(d):(c.prototype=d.prototype,new c)};b=a("../Observable");var k=a("../Subscription"),
+m=a("../util/tryCatch"),l=a("../util/errorObject");a=function(a){function d(c,g,d){a.call(this);this.addHandler=c;this.removeHandler=g;this.selector=d}h(d,a);d.create=function(a,g,n){return new d(a,g,n)};d.prototype._subscribe=function(a){var g=this.removeHandler,d=this.selector,f=d?function(g){var f=m.tryCatch(d).apply(null,arguments);f===l.errorObject?a.error(f.e):a.next(f)}:function(g){a.next(g)},b=m.tryCatch(this.addHandler)(f);b===l.errorObject&&a.error(b.e);a.add(new k.Subscription(function(){g(f)}))};
+return d}(b.Observable);e.FromEventPatternObservable=a},{"../Observable":3,"../Subscription":8,"../util/errorObject":230,"../util/tryCatch":241}],115:[function(a,b,e){function h(a){var c=a.subscriber;c.next(a.value);c.complete()}function k(a){a.subscriber.error(a.err)}var m=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var n in c)c.hasOwnProperty(n)&&(a[n]=c[n]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)};b=a("../Observable");var l=a("../Subscription"),
+f=a("../scheduler/queue");a=function(a){function c(c,n){void 0===n&&(n=f.queue);a.call(this);this.promise=c;this.scheduler=n;this._isScalar=!1}m(c,a);c.create=function(a,d){void 0===d&&(d=f.queue);return new c(a,d)};c.prototype._subscribe=function(a){var c=this,d=this.scheduler,b=this.promise;if(d===f.queue)this._isScalar?(a.next(this.value),a.complete()):b.then(function(d){c._isScalar=!0;c.value=d;a.next(d);a.complete()},function(c){return a.error(c)}).then(null,function(a){setTimeout(function(){throw a;
+})});else{var e=new l.Subscription;this._isScalar?e.add(d.schedule(h,0,{value:this.value,subscriber:a})):b.then(function(f){c._isScalar=!0;c.value=f;e.add(d.schedule(h,0,{value:f,subscriber:a}))},function(c){return e.add(d.schedule(k,0,{err:c,subscriber:a}))}).then(null,function(a){d.schedule(function(){throw a;})});return e}};return c}(b.Observable);e.PromiseObservable=a},{"../Observable":3,"../Subscription":8,"../scheduler/queue":216}],116:[function(a,b,e){var h=this&&this.__extends||function(a,
+f){function d(){this.constructor=a}for(var c in f)f.hasOwnProperty(c)&&(a[c]=f[c]);a.prototype=null===f?Object.create(f):(d.prototype=f.prototype,new d)},k=a("../util/isNumeric");b=a("../Observable");var m=a("../scheduler/asap");a=function(a){function f(d,c){void 0===d&&(d=0);void 0===c&&(c=m.asap);a.call(this);this.period=d;this.scheduler=c;if(!k.isNumeric(d)||0>d)this.period=0;c&&"function"===typeof c.schedule||(this.scheduler=m.asap)}h(f,a);f.create=function(a,c){void 0===a&&(a=0);void 0===c&&
+(c=m.asap);return new f(a,c)};f.dispatch=function(a){var c=a.subscriber,g=a.period;c.next(a.index);c.isUnsubscribed||(a.index+=1,this.schedule(a,g))};f.prototype._subscribe=function(a){var c=this.period;a.add(this.scheduler.schedule(f.dispatch,c,{index:0,subscriber:a,period:c}))};return f}(b.Observable);e.IntervalObservable=a},{"../Observable":3,"../scheduler/asap":215,"../util/isNumeric":233}],117:[function(a,b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&
+(a[d]=b[d]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Observable");var k=a("../util/noop");a=function(a){function b(){a.call(this)}h(b,a);b.create=function(){return new b};b.prototype._subscribe=function(a){k.noop()};return b}(b.Observable);e.InfiniteObservable=a},{"../Observable":3,"../util/noop":236}],118:[function(a,b,e){var h=this&&this.__extends||function(a,b){function l(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===
+b?Object.create(b):(l.prototype=b.prototype,new l)};a=function(a){function b(l,f,d){a.call(this);this.start=l;this.end=f;this.scheduler=d}h(b,a);b.create=function(a,f,d){void 0===a&&(a=0);void 0===f&&(f=0);return new b(a,f,d)};b.dispatch=function(a){var b=a.start,d=a.index,c=a.subscriber;d>=a.end?c.complete():(c.next(b),c.isUnsubscribed||(a.index=d+1,a.start=b+1,this.schedule(a)))};b.prototype._subscribe=function(a){var f=0,d=this.start,c=this.end,g=this.scheduler;if(g)a.add(g.schedule(b.dispatch,
+0,{index:f,end:c,start:d,subscriber:a}));else{do{if(f++>=c){a.complete();break}a.next(d++);if(a.isUnsubscribed)break}while(1)}};return b}(a("../Observable").Observable);e.RangeObservable=a},{"../Observable":3}],119:[function(a,b,e){var h=this&&this.__extends||function(a,b){function l(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(l.prototype=b.prototype,new l)};a=function(a){function b(l,f){a.call(this);this.error=l;this.scheduler=f}h(b,
+a);b.create=function(a,f){return new b(a,f)};b.dispatch=function(a){a.subscriber.error(a.error)};b.prototype._subscribe=function(a){var f=this.error,d=this.scheduler;d?a.add(d.schedule(b.dispatch,0,{error:f,subscriber:a})):a.error(f)};return b}(a("../Observable").Observable);e.ErrorObservable=a},{"../Observable":3}],120:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var n in c)c.hasOwnProperty(n)&&(a[n]=c[n]);a.prototype=null===c?Object.create(c):(g.prototype=
+c.prototype,new g)},k=a("../util/isNumeric");b=a("../Observable");var m=a("../scheduler/asap"),l=a("../util/isScheduler"),f=a("../util/isDate");a=function(a){function c(c,n,b){void 0===c&&(c=0);a.call(this);this.period=n;this.scheduler=b;this.dueTime=0;k.isNumeric(n)?this._period=1>Number(n)&&1||Number(n):l.isScheduler(n)&&(b=n);l.isScheduler(b)||(b=m.asap);this.scheduler=b;this.dueTime=f.isDate(c)?+c-this.scheduler.now():c}h(c,a);c.create=function(a,d,b){void 0===a&&(a=0);return new c(a,d,b)};c.dispatch=
+function(a){var d=a.index,b=a.period,f=a.subscriber;f.next(d);"undefined"===typeof b?f.complete():f.isUnsubscribed||("undefined"===typeof this.delay?this.add(this.scheduler.schedule(c.dispatch,b,{index:d+1,period:b,subscriber:f})):(a.index=d+1,this.schedule(a,b)))};c.prototype._subscribe=function(a){a.add(this.scheduler.schedule(c.dispatch,this.dueTime,{index:0,period:this._period,subscriber:a}))};return c}(b.Observable);e.TimerObservable=a},{"../Observable":3,"../scheduler/asap":215,"../util/isDate":232,
+"../util/isNumeric":233,"../util/isScheduler":235}],121:[function(a,b,e){var h=this&&this.__extends||function(a,d){function c(){this.constructor=a}for(var g in d)d.hasOwnProperty(g)&&(a[g]=d[g]);a.prototype=null===d?Object.create(d):(c.prototype=d.prototype,new c)};a=a("../Subscriber");e.buffer=function(a){return this.lift(new k(a))};var k=function(){function a(d){this.closingNotifier=d}a.prototype.call=function(a){return new m(a,this.closingNotifier)};return a}(),m=function(a){function d(c,g){a.call(this,
+c);this.buffer=[];this.notifierSubscriber=null;this.notifierSubscriber=new l(this);this.add(g._subscribe(this.notifierSubscriber))}h(d,a);d.prototype._next=function(a){this.buffer.push(a)};d.prototype._error=function(a){this.destination.error(a)};d.prototype._complete=function(){this.destination.complete()};d.prototype.flushBuffer=function(){var a=this.buffer;this.buffer=[];this.destination.next(a);this.isUnsubscribed&&this.notifierSubscriber.unsubscribe()};return d}(a.Subscriber),l=function(a){function d(c){a.call(this,
+null);this.parent=c}h(d,a);d.prototype._next=function(a){this.parent.flushBuffer()};d.prototype._error=function(a){this.parent.error(a)};d.prototype._complete=function(){this.parent.complete()};return d}(a.Subscriber)},{"../Subscriber":7}],122:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};a=a("../Subscriber");e.bufferCount=function(a,b){void 0===
+b&&(b=null);return this.lift(new k(a,b))};var k=function(){function a(b,d){this.bufferSize=b;this.startBufferEvery=d}a.prototype.call=function(a){return new m(a,this.bufferSize,this.startBufferEvery)};return a}(),m=function(a){function b(d,c,g){a.call(this,d);this.bufferSize=c;this.startBufferEvery=g;this.buffers=[[]];this.count=0}h(b,a);b.prototype._next=function(a){var c=this.count+=1,g=this.destination,n=this.bufferSize,b=this.buffers,f=b.length,l=-1;0===c%(null==this.startBufferEvery?n:this.startBufferEvery)&&
+b.push([]);for(c=0;c<f;c++){var e=b[c];e.push(a);e.length===n&&(l=c,g.next(e))}-1!==l&&b.splice(l,1)};b.prototype._error=function(a){this.destination.error(a)};b.prototype._complete=function(){for(var a=this.destination,c=this.buffers;0<c.length;){var g=c.shift();0<g.length&&a.next(g)}a.complete()};return b}(a.Subscriber)},{"../Subscriber":7}],123:[function(a,b,e){function h(a){var c=a.subscriber,d=a.buffer;d&&c.closeBuffer(d);a.buffer=c.openBuffer();c.isUnsubscribed||this.schedule(a,a.bufferTimeSpan)}
+function k(a){var c=a.bufferCreationInterval,d=a.bufferTimeSpan,b=a.subscriber,f=a.scheduler,l=b.openBuffer();b.isUnsubscribed||(this.add(f.schedule(m,d,{subscriber:b,buffer:l})),this.schedule(a,c))}function m(a){a.subscriber.closeBuffer(a.buffer)}var l=this&&this.__extends||function(a,c){function d(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);a.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)};b=a("../Subscriber");var f=a("../scheduler/asap");e.bufferTime=
+function(a,c,b){void 0===c&&(c=null);void 0===b&&(b=f.asap);return this.lift(new d(a,c,b))};var d=function(){function a(c,d,g){this.bufferTimeSpan=c;this.bufferCreationInterval=d;this.scheduler=g}a.prototype.call=function(a){return new c(a,this.bufferTimeSpan,this.bufferCreationInterval,this.scheduler)};return a}(),c=function(a){function c(d,b,n,f){a.call(this,d);this.bufferTimeSpan=b;this.bufferCreationInterval=n;this.scheduler=f;this.buffers=[];d=this.openBuffer();if(null!==n&&0<=n){var l={bufferTimeSpan:b,
+bufferCreationInterval:n,subscriber:this,scheduler:f};this.add(f.schedule(m,b,{subscriber:this,buffer:d}));this.add(f.schedule(k,n,l))}else this.add(f.schedule(h,b,{subscriber:this,buffer:d,bufferTimeSpan:b}))}l(c,a);c.prototype._next=function(a){for(var c=this.buffers,d=c.length,g=0;g<d;g++)c[g].push(a)};c.prototype._error=function(a){this.buffers.length=0;this.destination.error(a)};c.prototype._complete=function(){for(var a=this.buffers;0<a.length;)this.destination.next(a.shift());this.destination.complete()};
+c.prototype.openBuffer=function(){var a=[];this.buffers.push(a);return a};c.prototype.closeBuffer=function(a){this.destination.next(a);var c=this.buffers;c.splice(c.indexOf(a),1)};return c}(b.Subscriber)},{"../Subscriber":7,"../scheduler/asap":215}],124:[function(a,b,e){var h=this&&this.__extends||function(a,c){function d(){this.constructor=a}for(var g in c)c.hasOwnProperty(g)&&(a[g]=c[g]);a.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)};b=a("../Subscriber");var k=a("../Subscription"),
+m=a("../util/tryCatch"),l=a("../util/errorObject");e.bufferToggle=function(a,c){return this.lift(new f(a,c))};var f=function(){function a(c,d){this.openings=c;this.closingSelector=d}a.prototype.call=function(a){return new d(a,this.openings,this.closingSelector)};return a}(),d=function(a){function d(g,b,f){a.call(this,g);this.openings=b;this.closingSelector=f;this.contexts=[];this.add(this.openings._subscribe(new c(this)))}h(d,a);d.prototype._next=function(a){for(var c=this.contexts,d=c.length,g=0;g<
+d;g++)c[g].buffer.push(a)};d.prototype._error=function(a){for(var c=this.contexts;0<c.length;){var d=c.shift();d.subscription.unsubscribe();d.buffer=null;d.subscription=null}this.contexts=null;this.destination.error(a)};d.prototype._complete=function(){for(var a=this.contexts;0<a.length;){var c=a.shift();this.destination.next(c.buffer);c.subscription.unsubscribe();c.buffer=null;c.subscription=null}this.contexts=null;this.destination.complete()};d.prototype.openBuffer=function(a){var c=this.contexts,
+d=m.tryCatch(this.closingSelector)(a);d===l.errorObject?this._error(d.e):(a={buffer:[],subscription:new k.Subscription},c.push(a),c=new g(this,a),c=d._subscribe(c),a.subscription.add(c),this.add(c))};d.prototype.closeBuffer=function(a){var c=this.contexts;if(null!==c){var d=a.subscription;this.destination.next(a.buffer);c.splice(c.indexOf(a),1);this.remove(d);d.unsubscribe()}};return d}(b.Subscriber),c=function(a){function c(d){a.call(this,null);this.parent=d}h(c,a);c.prototype._next=function(a){this.parent.openBuffer(a)};
+c.prototype._error=function(a){this.parent.error(a)};c.prototype._complete=function(){};return c}(b.Subscriber),g=function(a){function c(d,g){a.call(this,null);this.parent=d;this.context=g}h(c,a);c.prototype._next=function(){this.parent.closeBuffer(this.context)};c.prototype._error=function(a){this.parent.error(a)};c.prototype._complete=function(){this.parent.closeBuffer(this.context)};return c}(b.Subscriber)},{"../Subscriber":7,"../Subscription":8,"../util/errorObject":230,"../util/tryCatch":241}],
+125:[function(a,b,e){var h=this&&this.__extends||function(a,d){function b(){this.constructor=a}for(var f in d)d.hasOwnProperty(f)&&(a[f]=d[f]);a.prototype=null===d?Object.create(d):(b.prototype=d.prototype,new b)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e.bufferWhen=function(a){return this.lift(new l(a))};var l=function(){function a(c){this.closingSelector=c}a.prototype.call=function(a){return new f(a,this.closingSelector)};return a}(),f=function(a){function g(d,
+g){a.call(this,d);this.closingSelector=g;this.openBuffer()}h(g,a);g.prototype._next=function(a){this.buffer.push(a)};g.prototype._error=function(a){this.buffer=null;this.destination.error(a)};g.prototype._complete=function(){this.destination.next(this.buffer);this.buffer=null;this.destination.complete()};g.prototype.openBuffer=function(){var a=this.closingNotification;a&&(this.remove(a),a.unsubscribe());(a=this.buffer)&&this.destination.next(a);this.buffer=[];a=k.tryCatch(this.closingSelector)();
+a===m.errorObject?(a=a.e,this.buffer=null,this.destination.error(a)):this.add(this.closingNotification=a._subscribe(new d(this)))};return g}(b.Subscriber),d=function(a){function d(g){a.call(this,null);this.parent=g}h(d,a);d.prototype._next=function(){this.parent.openBuffer()};d.prototype._error=function(a){this.parent.error(a)};d.prototype._complete=function(){this.parent.openBuffer()};return d}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],126:[function(a,b,
+e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e._catch=function(a){a=new l(a);var c=this.lift(a);return a.caught=c};var l=function(){function a(c){this.selector=c}a.prototype.call=function(a){return new f(a,this.selector,this.caught)};return a}(),f=function(a){function c(c,b,
+f){a.call(this,null);this.destination=c;this.selector=b;this.caught=f;this.lastSubscription=this;this.destination.add(this)}h(c,a);c.prototype._next=function(a){this.destination.next(a)};c.prototype._error=function(a){a=k.tryCatch(this.selector)(a,this.caught);a===m.errorObject?this.destination.error(m.errorObject.e):(this.lastSubscription.unsubscribe(),this.lastSubscription=a.subscribe(this.destination))};c.prototype._complete=function(){this.lastSubscription.unsubscribe();this.destination.complete()};
+c.prototype._unsubscribe=function(){this.lastSubscription.unsubscribe()};return c}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],127:[function(a,b,e){var h=a("./combineLatest-support");e.combineAll=function(a){return this.lift(new h.CombineLatestOperator(a))}},{"./combineLatest-support":129}],128:[function(a,b,e){var h=a("../observable/fromArray"),k=a("./combineLatest-support"),m=a("../util/isScheduler"),l=a("../util/isArray");e.combineLatest=function(){for(var a=
+[],d=0;d<arguments.length;d++)a[d-0]=arguments[d];var c=d=null;m.isScheduler(a[a.length-1])&&(c=a.pop());"function"===typeof a[a.length-1]&&(d=a.pop());1===a.length&&l.isArray(a[0])&&(a=a[0]);return(new h.ArrayObservable(a,c)).lift(new k.CombineLatestOperator(d))}},{"../observable/fromArray":112,"../util/isArray":231,"../util/isScheduler":235,"./combineLatest-support":129}],129:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&
+(a[b]=c[b]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)},k=a("../util/tryCatch"),m=a("../util/errorObject");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");a=function(){function a(c){this.project=c}a.prototype.call=function(a){return new f(a,this.project)};return a}();e.CombineLatestOperator=a;var f=function(a){function c(c,b){a.call(this,c);this.project=b;this.active=0;this.values=[];this.observables=[];this.toRespond=[]}h(c,a);c.prototype._next=function(a){var c=
+this.toRespond;c.push(c.length);this.observables.push(a)};c.prototype._complete=function(){var a=this.observables,c=a.length;if(0===c)this.destination.complete();else{this.active=c;for(var d=0;d<c;d++){var b=a[d];this.add(l.subscribeToResult(this,b,b,d))}}};c.prototype.notifyComplete=function(a){0===--this.active&&this.destination.complete()};c.prototype.notifyNext=function(a,c,d,b){a=this.values;a[d]=c;c=this.toRespond;0<c.length&&(d=c.indexOf(d),-1!==d&&c.splice(d,1));0===c.length&&(c=this.project,
+d=this.destination,c?(a=k.tryCatch(c).apply(this,a),a===m.errorObject?d.error(m.errorObject.e):d.next(a)):d.next(a))};return c}(b.OuterSubscriber);e.CombineLatestSubscriber=f},{"../OuterSubscriber":4,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],130:[function(a,b,e){var h=a("../observable/fromArray"),k=a("./combineLatest-support"),m=a("../util/isArray");e.combineLatest=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];b=null;"function"===typeof a[a.length-
+1]&&(b=a.pop());1===a.length&&m.isArray(a[0])&&(a=a[0]);a.unshift(this);return(new h.ArrayObservable(a)).lift(new k.CombineLatestOperator(b))}},{"../observable/fromArray":112,"../util/isArray":231,"./combineLatest-support":129}],131:[function(a,b,e){var h=a("../scheduler/queue"),k=a("./mergeAll-support"),m=a("../observable/fromArray"),l=a("../util/isScheduler");e.concat=function(){for(var a=[],d=0;d<arguments.length;d++)a[d-0]=arguments[d];d=h.queue;l.isScheduler(a[a.length-1])&&(d=a.pop());return(new m.ArrayObservable(a,
+d)).lift(new k.MergeAllOperator(1))}},{"../observable/fromArray":112,"../scheduler/queue":216,"../util/isScheduler":235,"./mergeAll-support":159}],132:[function(a,b,e){var h=a("../util/isScheduler"),k=a("../observable/fromArray"),m=a("./mergeAll-support");e.concat=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];a.unshift(this);b=null;h.isScheduler(a[a.length-1])&&(b=a.pop());return(new k.ArrayObservable(a,b)).lift(new m.MergeAllOperator(1))}},{"../observable/fromArray":112,
+"../util/isScheduler":235,"./mergeAll-support":159}],133:[function(a,b,e){var h=a("./mergeAll-support");e.concatAll=function(){return this.lift(new h.MergeAllOperator(1))}},{"./mergeAll-support":159}],134:[function(a,b,e){var h=a("./mergeMap-support");e.concatMap=function(a,b){return this.lift(new h.MergeMapOperator(a,b,1))}},{"./mergeMap-support":161}],135:[function(a,b,e){var h=a("./mergeMapTo-support");e.concatMapTo=function(a,b){return this.lift(new h.MergeMapToOperator(a,b,1))}},{"./mergeMapTo-support":163}],
+136:[function(a,b,e){var h=this&&this.__extends||function(a,c){function g(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e.count=function(a){return this.lift(new l(a,this))};var l=function(){function a(c,d){this.predicate=c;this.source=d}a.prototype.call=function(a){return new f(a,this.predicate,this.source)};return a}(),f=function(a){function c(c,
+b,f){a.call(this,c);this.predicate=b;this.source=f;this.index=this.count=0}h(c,a);c.prototype._next=function(a){var c=this.predicate,d=!0;if(c&&(d=k.tryCatch(c)(a,this.index++,this.source),d===m.errorObject)){this.destination.error(d.e);return}d&&(this.count+=1)};c.prototype._complete=function(){this.destination.next(this.count);this.destination.complete()};return c}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],137:[function(a,b,e){var h=this&&this.__extends||
+function(a,c){function d(){this.constructor=a}for(var g in c)c.hasOwnProperty(g)&&(a[g]=c[g]);a.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)},k=a("../observable/fromPromise");b=a("../Subscriber");var m=a("../util/tryCatch"),l=a("../util/isPromise"),f=a("../util/errorObject");e.debounce=function(a){return this.lift(new d(a))};var d=function(){function a(c){this.durationSelector=c}a.prototype.call=function(a){return new c(a,this.durationSelector)};return a}(),c=function(a){function c(d,
+g){a.call(this,d);this.durationSelector=g;this.lastValue=this.debouncedSubscription=null;this._index=0}h(c,a);Object.defineProperty(c.prototype,"index",{get:function(){return this._index},enumerable:!0,configurable:!0});c.prototype._next=function(a){var c=this.destination,d=++this._index,b=m.tryCatch(this.durationSelector)(a);b===f.errorObject?c.error(f.errorObject.e):(l.isPromise(b)&&(b=k.PromiseObservable.create(b)),this.lastValue=a,this.clearDebounce(),this.add(this.debouncedSubscription=b._subscribe(new g(this,
+d))))};c.prototype._complete=function(){this.debouncedNext();this.destination.complete()};c.prototype.debouncedNext=function(){this.clearDebounce();null!=this.lastValue&&(this.destination.next(this.lastValue),this.lastValue=null)};c.prototype.clearDebounce=function(){var a=this.debouncedSubscription;a&&(a.unsubscribe(),this.remove(a),this.debouncedSubscription=null)};return c}(b.Subscriber),g=function(a){function c(d,g){a.call(this,null);this.parent=d;this.currentIndex=g}h(c,a);c.prototype.debounceNext=
+function(){var a=this.parent;this.currentIndex===a.index&&(a.debouncedNext(),this.isUnsubscribed||this.unsubscribe())};c.prototype._next=function(a){this.debounceNext()};c.prototype._error=function(a){this.parent.error(a)};c.prototype._complete=function(){this.debounceNext()};return c}(b.Subscriber)},{"../Subscriber":7,"../observable/fromPromise":115,"../util/errorObject":230,"../util/isPromise":234,"../util/tryCatch":241}],138:[function(a,b,e){function h(a){a.debouncedNext()}var k=this&&this.__extends||
+function(a,c){function g(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);a.prototype=null===c?Object.create(c):(g.prototype=c.prototype,new g)};b=a("../Subscriber");var m=a("../scheduler/asap");e.debounceTime=function(a,c){void 0===c&&(c=m.asap);return this.lift(new l(a,c))};var l=function(){function a(c,d){this.dueTime=c;this.scheduler=d}a.prototype.call=function(a){return new f(a,this.dueTime,this.scheduler)};return a}(),f=function(a){function c(c,b,f){a.call(this,c);this.dueTime=
+b;this.scheduler=f;this.lastValue=this.debouncedSubscription=null}k(c,a);c.prototype._next=function(a){this.clearDebounce();this.lastValue=a;this.add(this.debouncedSubscription=this.scheduler.schedule(h,this.dueTime,this))};c.prototype._complete=function(){this.debouncedNext();this.destination.complete()};c.prototype.debouncedNext=function(){this.clearDebounce();null!=this.lastValue&&(this.destination.next(this.lastValue),this.lastValue=null)};c.prototype.clearDebounce=function(){var a=this.debouncedSubscription;
+null!==a&&(this.remove(a),a.unsubscribe(),this.debouncedSubscription=null)};return c}(b.Subscriber)},{"../Subscriber":7,"../scheduler/asap":215}],139:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};a=a("../Subscriber");e.defaultIfEmpty=function(a){void 0===a&&(a=null);return this.lift(new k(a))};var k=function(){function a(b){this.defaultValue=
+b}a.prototype.call=function(a){return new m(a,this.defaultValue)};return a}(),m=function(a){function b(d,c){a.call(this,d);this.defaultValue=c;this.isEmpty=!0}h(b,a);b.prototype._next=function(a){this.isEmpty=!1;this.destination.next(a)};b.prototype._complete=function(){this.isEmpty&&this.destination.next(this.defaultValue);this.destination.complete()};return b}(a.Subscriber)},{"../Subscriber":7}],140:[function(a,b,e){var h=this&&this.__extends||function(a,c){function d(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&
+(a[b]=c[b]);a.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)};b=a("../Subscriber");var k=a("../Notification"),m=a("../scheduler/queue"),l=a("../util/isDate");e.delay=function(a,c){void 0===c&&(c=m.queue);var d=l.isDate(a)?+a-c.now():a;return this.lift(new f(d,c))};var f=function(){function a(c,d){this.delay=c;this.scheduler=d}a.prototype.call=function(a){return new d(a,this.delay,this.scheduler)};return a}(),d=function(a){function d(c,b,f){a.call(this,c);this.delay=b;this.scheduler=
+f;this.queue=[];this.errored=this.active=!1}h(d,a);d.dispatch=function(a){for(var c=a.source,d=c.queue,b=a.scheduler,g=a.destination;0<d.length&&0>=d[0].time-b.now();)d.shift().notification.observe(g);0<d.length?(c=Math.max(0,d[0].time-b.now()),this.schedule(a,c)):c.active=!1};d.prototype._schedule=function(a){this.active=!0;this.add(a.schedule(d.dispatch,this.delay,{source:this,destination:this.destination,scheduler:a}))};d.prototype.scheduleNotification=function(a){if(!0!==this.errored){var d=this.scheduler;
+a=new c(d.now()+this.delay,a);this.queue.push(a);!1===this.active&&this._schedule(d)}};d.prototype._next=function(a){this.scheduleNotification(k.Notification.createNext(a))};d.prototype._error=function(a){this.errored=!0;this.queue=[];this.destination.error(a)};d.prototype._complete=function(){this.scheduleNotification(k.Notification.createComplete())};return d}(b.Subscriber),c=function(){return function(a,c){this.time=a;this.notification=c}}()},{"../Notification":2,"../Subscriber":7,"../scheduler/queue":216,
+"../util/isDate":232}],141:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};a=a("../Subscriber");e.dematerialize=function(){return this.lift(new k)};var k=function(){function a(){}a.prototype.call=function(a){return new m(a)};return a}(),m=function(a){function b(d){a.call(this,d)}h(b,a);b.prototype._next=function(a){a.observe(this.destination)};
+return b}(a.Subscriber)},{"../Subscriber":7}],142:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e.distinctUntilChanged=function(a){return this.lift(new l(a))};var l=function(){function a(c){this.compare=c}a.prototype.call=function(a){return new f(a,this.compare)};
+return a}(),f=function(a){function c(c,b){a.call(this,c);this.hasValue=!1;"function"===typeof b&&(this.compare=b)}h(c,a);c.prototype.compare=function(a,c){return a===c};c.prototype._next=function(a){var c=!1;if(this.hasValue){if(c=k.tryCatch(this.compare)(this.value,a),c===m.errorObject){this.destination.error(m.errorObject.e);return}}else this.hasValue=!0;!1===Boolean(c)&&(this.value=a,this.destination.next(a))};return c}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],
+143:[function(a,b,e){var h=this&&this.__extends||function(a,d){function b(){this.constructor=a}for(var f in d)d.hasOwnProperty(f)&&(a[f]=d[f]);a.prototype=null===d?Object.create(d):(b.prototype=d.prototype,new b)};b=a("../Subscriber");var k=a("../util/noop"),m=a("../util/tryCatch"),l=a("../util/errorObject");e._do=function(a,d,b){var l;a&&"object"===typeof a?(l=a.next,d=a.error,b=a.complete):l=a;return this.lift(new f(l||k.noop,d||k.noop,b||k.noop))};var f=function(){function a(c,d,b){this.next=c;
+this.error=d;this.complete=b}a.prototype.call=function(a){return new d(a,this.next,this.error,this.complete)};return a}(),d=function(a){function d(b,g,f,l){a.call(this,b);this.__next=g;this.__error=f;this.__complete=l}h(d,a);d.prototype._next=function(a){m.tryCatch(this.__next)(a)===l.errorObject?this.destination.error(l.errorObject.e):this.destination.next(a)};d.prototype._error=function(a){m.tryCatch(this.__error)(a)===l.errorObject?this.destination.error(l.errorObject.e):this.destination.error(a)};
+d.prototype._complete=function(){m.tryCatch(this.__complete)()===l.errorObject?this.destination.error(l.errorObject.e):this.destination.complete()};return d}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/noop":236,"../util/tryCatch":241}],144:[function(a,b,e){var h=this&&this.__extends||function(a,c){function d(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);a.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)},k=a("../observable/ScalarObservable"),
+m=a("../observable/fromArray"),l=a("../observable/throw");b=a("../Subscriber");var f=a("../util/tryCatch"),d=a("../util/errorObject");e.every=function(a,b){var g;return this._isScalar?(g=f.tryCatch(a).call(b||this,this.value,0,this),g===d.errorObject?new l.ErrorObservable(d.errorObject.e,this.scheduler):new k.ScalarObservable(g,this.scheduler)):this instanceof m.ArrayObservable?(g=this.array,g=f.tryCatch(function(a,c,d){return a.every(c,d)})(g,a,b),g===d.errorObject?new l.ErrorObservable(d.errorObject.e,
+this.scheduler):new k.ScalarObservable(g,this.scheduler)):this.lift(new c(a,b,this))};var c=function(){function a(c,d,b){this.predicate=c;this.thisArg=d;this.source=b}a.prototype.call=function(a){return new g(a,this.predicate,this.thisArg,this.source)};return a}(),g=function(a){function c(d,b,g,f){a.call(this,d);this.predicate=b;this.thisArg=g;this.source=f;this.index=0}h(c,a);c.prototype.notifyComplete=function(a){this.destination.next(a);this.destination.complete()};c.prototype._next=function(a){a=
+f.tryCatch(this.predicate).call(this.thisArg||this,a,this.index++,this.source);a===d.errorObject?this.destination.error(a.e):a||this.notifyComplete(!1)};c.prototype._complete=function(){this.notifyComplete(!0)};return c}(b.Subscriber)},{"../Subscriber":7,"../observable/ScalarObservable":105,"../observable/fromArray":112,"../observable/throw":119,"../util/errorObject":230,"../util/tryCatch":241}],145:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&
+(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)},k=a("../util/tryCatch"),m=a("../util/errorObject");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");a=function(){function a(c,d,b){this.project=c;this.concurrent=d;this.scheduler=b}a.prototype.call=function(a){return new f(a,this.project,this.concurrent,this.scheduler)};return a}();e.ExpandOperator=a;var f=function(a){function c(c,b,f,l){a.call(this,c);this.project=b;this.concurrent=f;this.scheduler=l;
+this.active=this.index=0;this.hasCompleted=!1;f<Number.POSITIVE_INFINITY&&(this.buffer=[])}h(c,a);c.dispatch=function(a){a.subscriber.subscribeToProjection(a.result,a.value,a.index)};c.prototype._next=function(a){var d=this.destination;if(d.isUnsubscribed)this._complete();else{var b=this.index++;if(this.active<this.concurrent){d.next(a);var f=k.tryCatch(this.project)(a,b);f===m.errorObject?d.error(f.e):this.scheduler?this.add(this.scheduler.schedule(c.dispatch,0,{subscriber:this,result:f,value:a,
+index:b})):this.subscribeToProjection(f,a,b)}else this.buffer.push(a)}};c.prototype.subscribeToProjection=function(a,c,d){a._isScalar?this._next(a.value):(this.active++,this.add(l.subscribeToResult(this,a,c,d)))};c.prototype._complete=function(){(this.hasCompleted=!0,0===this.active)&&this.destination.complete()};c.prototype.notifyComplete=function(a){var c=this.buffer;this.remove(a);this.active--;c&&0<c.length&&this._next(c.shift());this.hasCompleted&&0===this.active&&this.destination.complete()};
+c.prototype.notifyNext=function(a,c,d,b){this._next(c)};return c}(b.OuterSubscriber);e.ExpandSubscriber=f},{"../OuterSubscriber":4,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],146:[function(a,b,e){var h=a("./expand-support");e.expand=function(a,b,l){void 0===b&&(b=Number.POSITIVE_INFINITY);void 0===l&&(l=void 0);b=1>(b||0)?Number.POSITIVE_INFINITY:b;return this.lift(new h.ExpandOperator(a,b,l))}},{"./expand-support":145}],147:[function(a,b,e){var h=this&&this.__extends||
+function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e.filter=function(a,c){return this.lift(new l(a,c))};var l=function(){function a(c,d){this.select=c;this.thisArg=d}a.prototype.call=function(a){return new f(a,this.select,this.thisArg)};return a}(),f=function(a){function c(c,b,f){a.call(this,c);this.thisArg=f;
+this.count=0;this.select=b}h(c,a);c.prototype._next=function(a){var c=k.tryCatch(this.select).call(this.thisArg||this,a,this.count++);c===m.errorObject?this.destination.error(m.errorObject.e):Boolean(c)&&this.destination.next(a)};return c}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],148:[function(a,b,e){var h=this&&this.__extends||function(a,d){function c(){this.constructor=a}for(var b in d)d.hasOwnProperty(b)&&(a[b]=d[b]);a.prototype=null===d?Object.create(d):
+(c.prototype=d.prototype,new c)};b=a("../Subscriber");var k=a("../Subscription");e._finally=function(a){return this.lift(new m(a))};var m=function(){function a(d){this.finallySelector=d}a.prototype.call=function(a){return new l(a,this.finallySelector)};return a}(),l=function(a){function d(c,d){a.call(this,c);this.add(new k.Subscription(d))}h(d,a);return d}(b.Subscriber)},{"../Subscriber":7,"../Subscription":8}],149:[function(a,b,e){var h=this&&this.__extends||function(a,d){function b(){this.constructor=
+a}for(var f in d)d.hasOwnProperty(f)&&(a[f]=d[f]);a.prototype=null===d?Object.create(d):(b.prototype=d.prototype,new b)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject"),l=a("../util/EmptyError");e.first=function(a,d,b){return this.lift(new f(a,d,b,this))};var f=function(){function a(c,d,b,f){this.predicate=c;this.resultSelector=d;this.defaultValue=b;this.source=f}a.prototype.call=function(a){return new d(a,this.predicate,this.resultSelector,this.defaultValue,this.source)};
+return a}(),d=function(a){function d(b,g,f,l,e){a.call(this,b);this.predicate=g;this.resultSelector=f;this.defaultValue=l;this.source=e;this.index=0;this.hasCompleted=!1}h(d,a);d.prototype._next=function(a){var c=this.destination,d=this.predicate,b=this.resultSelector,g=this.index++,f=!0;if(d&&(f=k.tryCatch(d)(a,g,this.source),f===m.errorObject)){c.error(m.errorObject.e);return}if(f){if(b&&(a=k.tryCatch(b)(a,g),a===m.errorObject)){c.error(m.errorObject.e);return}c.next(a);c.complete();this.hasCompleted=
+!0}};d.prototype._complete=function(){var a=this.destination;this.hasCompleted||"undefined"===typeof this.defaultValue?this.hasCompleted||a.error(new l.EmptyError):(a.next(this.defaultValue),a.complete())};return d}(b.Subscriber)},{"../Subscriber":7,"../util/EmptyError":223,"../util/errorObject":230,"../util/tryCatch":241}],150:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):
+(d.prototype=b.prototype,new d)},k=a("../Subscription");a=a("../Observable");b=function(a){function b(){a.call(this);this.attemptedToUnsubscribePrimary=!1;this.count=0}h(b,a);b.prototype.setPrimary=function(a){this.primary=a};b.prototype.unsubscribe=function(){this.isUnsubscribed||this.attemptedToUnsubscribePrimary||(this.attemptedToUnsubscribePrimary=!0,0===this.count&&(a.prototype.unsubscribe.call(this),this.primary.unsubscribe()))};return b}(k.Subscription);e.RefCountSubscription=b;a=function(a){function b(d,
+c,g){a.call(this);this.key=d;this.groupSubject=c;this.refCountSubscription=g}h(b,a);b.prototype._subscribe=function(a){var c=new k.Subscription;this.refCountSubscription&&!this.refCountSubscription.isUnsubscribed&&c.add(new m(this.refCountSubscription));c.add(this.groupSubject.subscribe(a));return c};return b}(a.Observable);e.GroupedObservable=a;var m=function(a){function b(d){a.call(this);this.parent=d;d.count++}h(b,a);b.prototype.unsubscribe=function(){this.parent.isUnsubscribed||this.isUnsubscribed||
+(a.prototype.unsubscribe.call(this),this.parent.count--,0===this.parent.count&&this.parent.attemptedToUnsubscribePrimary&&(this.parent.unsubscribe(),this.parent.primary.unsubscribe()))};return b}(k.Subscription);e.InnerRefCountSubscription=m},{"../Observable":3,"../Subscription":8}],151:[function(a,b,e){var h=this&&this.__extends||function(a,c){function d(){this.constructor=a}for(var b in c)c.hasOwnProperty(b)&&(a[b]=c[b]);a.prototype=null===c?Object.create(c):(d.prototype=c.prototype,new d)};b=a("../Subscriber");
+var k=a("../Observable"),m=a("../Subject"),l=a("../util/Map"),f=a("../util/FastMap"),d=a("./groupBy-support"),c=a("../util/tryCatch"),g=a("../util/errorObject");e.groupBy=function(a,c,d){return new n(this,a,c,d)};var n=function(a){function c(d,b,g,f){a.call(this);this.source=d;this.keySelector=b;this.elementSelector=g;this.durationSelector=f}h(c,a);c.prototype._subscribe=function(a){var c=new d.RefCountSubscription;a=new p(a,c,this.keySelector,this.elementSelector,this.durationSelector);c.setPrimary(this.source.subscribe(a));
+return c};return c}(k.Observable);e.GroupByObservable=n;var p=function(a){function b(c,d,g,f,n){a.call(this);this.refCountSubscription=d;this.keySelector=g;this.elementSelector=f;this.durationSelector=n;this.groups=null;this.destination=c;this.add(c)}h(b,a);b.prototype._next=function(a){var b=c.tryCatch(this.keySelector)(a);if(b===g.errorObject)this.error(b.e);else{var n=this.groups,e=this.elementSelector,k=this.durationSelector;n||(n=this.groups="string"===typeof b?new f.FastMap:new l.Map);var h=
+n.get(b);h||(n.set(b,h=new m.Subject),n=new d.GroupedObservable(b,h,this.refCountSubscription),k&&(k=c.tryCatch(k)(new d.GroupedObservable(b,h)),k===g.errorObject?this.error(k.e):this.add(k._subscribe(new q(b,h,this)))),this.destination.next(n));e?(a=c.tryCatch(e)(a),a===g.errorObject?this.error(a.e):h.next(a)):h.next(a)}};b.prototype._error=function(a){var c=this,b=this.groups;b&&b.forEach(function(b,d){b.error(a);c.removeGroup(d)});this.destination.error(a)};b.prototype._complete=function(){var a=
+this,c=this.groups;c&&c.forEach(function(c,b){c.complete();a.removeGroup(c)});this.destination.complete()};b.prototype.removeGroup=function(a){this.groups.delete(a)};return b}(b.Subscriber),q=function(a){function c(b,d,g){a.call(this,null);this.key=b;this.group=d;this.parent=g}h(c,a);c.prototype._next=function(a){this.group.complete();this.parent.removeGroup(this.key)};c.prototype._error=function(a){this.group.error(a);this.parent.removeGroup(this.key)};c.prototype._complete=function(){this.group.complete();
+this.parent.removeGroup(this.key)};return c}(b.Subscriber)},{"../Observable":3,"../Subject":6,"../Subscriber":7,"../util/FastMap":224,"../util/Map":226,"../util/errorObject":230,"../util/tryCatch":241,"./groupBy-support":150}],152:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../util/noop");e.ignoreElements=
+function(){return this.lift(new m)};var m=function(){function a(){}a.prototype.call=function(a){return new l(a)};return a}(),l=function(a){function b(){a.apply(this,arguments)}h(b,a);b.prototype._next=function(a){k.noop()};return b}(b.Subscriber)},{"../Subscriber":7,"../util/noop":236}],153:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};
+b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject"),l=a("../util/EmptyError");e.last=function(a,b,d){return this.lift(new f(a,b,d,this))};var f=function(){function a(c,b,d,f){this.predicate=c;this.resultSelector=b;this.defaultValue=d;this.source=f}a.prototype.call=function(a){return new d(a,this.predicate,this.resultSelector,this.defaultValue,this.source)};return a}(),d=function(a){function b(d,g,f,e,l){a.call(this,d);this.predicate=g;this.resultSelector=f;this.defaultValue=
+e;this.source=l;this.hasValue=!1;this.index=0;"undefined"!==typeof e&&(this.lastValue=e,this.hasValue=!0)}h(b,a);b.prototype._next=function(a){var c=this.predicate,b=this.resultSelector,d=this.destination,g=this.index++;if(c)if(c=k.tryCatch(c)(a,g,this.source),c===m.errorObject)d.error(m.errorObject.e);else{if(c){if(b&&(a=k.tryCatch(b)(a,g),a===m.errorObject)){d.error(m.errorObject.e);return}this.lastValue=a;this.hasValue=!0}}else this.lastValue=a,this.hasValue=!0};b.prototype._complete=function(){var a=
+this.destination;this.hasValue?(a.next(this.lastValue),a.complete()):a.error(new l.EmptyError)};return b}(b.Subscriber)},{"../Subscriber":7,"../util/EmptyError":223,"../util/errorObject":230,"../util/tryCatch":241}],154:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");
+e.map=function(a,c){if("function"!==typeof a)throw new TypeError("argument is not a function. Are you looking for `mapTo()`?");return this.lift(new l(a,c))};var l=function(){function a(c,b){this.project=c;this.thisArg=b}a.prototype.call=function(a){return new f(a,this.project,this.thisArg)};return a}(),f=function(a){function c(c,b,f){a.call(this,c);this.project=b;this.thisArg=f;this.count=0}h(c,a);c.prototype._next=function(a){a=k.tryCatch(this.project).call(this.thisArg||this,a,this.count++);a===
+m.errorObject?this.error(m.errorObject.e):this.destination.next(a)};return c}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],155:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};a=a("../Subscriber");e.mapTo=function(a){return this.lift(new k(a))};var k=function(){function a(b){this.value=b}a.prototype.call=
+function(a){return new m(a,this.value)};return a}(),m=function(a){function b(d,c){a.call(this,d);this.value=c}h(b,a);b.prototype._next=function(a){this.destination.next(this.value)};return b}(a.Subscriber)},{"../Subscriber":7}],156:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../Notification");e.materialize=
+function(){return this.lift(new m)};var m=function(){function a(){}a.prototype.call=function(a){return new l(a)};return a}(),l=function(a){function b(c){a.call(this,c)}h(b,a);b.prototype._next=function(a){this.destination.next(k.Notification.createNext(a))};b.prototype._error=function(a){var b=this.destination;b.next(k.Notification.createError(a));b.complete()};b.prototype._complete=function(){var a=this.destination;a.next(k.Notification.createComplete());a.complete()};return b}(b.Subscriber)},{"../Notification":2,
+"../Subscriber":7}],157:[function(a,b,e){var h=a("../observable/fromArray"),k=a("./mergeAll-support"),m=a("../scheduler/queue"),l=a("../util/isScheduler");e.merge=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];var b=Number.POSITIVE_INFINITY,c=m.queue,g=a[a.length-1];l.isScheduler(g)?(c=a.pop(),1<a.length&&"number"===typeof a[a.length-1]&&(b=a.pop())):"number"===typeof g&&(b=a.pop());return 1===a.length?a[0]:(new h.ArrayObservable(a,c)).lift(new k.MergeAllOperator(b))}},{"../observable/fromArray":112,
+"../scheduler/queue":216,"../util/isScheduler":235,"./mergeAll-support":159}],158:[function(a,b,e){var h=a("./merge-static");e.merge=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];a.unshift(this);return h.merge.apply(this,a)}},{"./merge-static":157}],159:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};b=a("../OuterSubscriber");
+var k=a("../util/subscribeToResult");a=function(){function a(b){this.concurrent=b}a.prototype.call=function(a){return new m(a,this.concurrent)};return a}();e.MergeAllOperator=a;var m=function(a){function b(d,c){a.call(this,d);this.concurrent=c;this.hasCompleted=!1;this.buffer=[];this.active=0}h(b,a);b.prototype._next=function(a){this.active<this.concurrent?a._isScalar?this.destination.next(a.value):(this.active++,this.add(k.subscribeToResult(this,a))):this.buffer.push(a)};b.prototype._complete=function(){this.hasCompleted=
+!0;0===this.active&&0===this.buffer.length&&this.destination.complete()};b.prototype.notifyComplete=function(a){var c=this.buffer;this.remove(a);this.active--;0<c.length?this._next(c.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()};return b}(b.OuterSubscriber);e.MergeAllSubscriber=m},{"../OuterSubscriber":4,"../util/subscribeToResult":239}],160:[function(a,b,e){var h=a("./mergeAll-support");e.mergeAll=function(a){void 0===a&&(a=Number.POSITIVE_INFINITY);return this.lift(new h.MergeAllOperator(a))}},
+{"./mergeAll-support":159}],161:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)},k=a("../util/tryCatch"),m=a("../util/errorObject"),l=a("../util/subscribeToResult");a=a("../OuterSubscriber");b=function(){function a(c,b,d){void 0===d&&(d=Number.POSITIVE_INFINITY);this.project=c;this.resultSelector=b;this.concurrent=d}a.prototype.call=function(a){return new f(a,
+this.project,this.resultSelector,this.concurrent)};return a}();e.MergeMapOperator=b;var f=function(a){function c(c,b,f,e){void 0===e&&(e=Number.POSITIVE_INFINITY);a.call(this,c);this.project=b;this.resultSelector=f;this.concurrent=e;this.hasCompleted=!1;this.buffer=[];this.index=this.active=0}h(c,a);c.prototype._next=function(a){if(this.active<this.concurrent){var c=this.index++,b=k.tryCatch(this.project)(a,c),d=this.destination;b===m.errorObject?d.error(b.e):(this.active++,this._innerSub(b,a,c))}else this.buffer.push(a)};
+c.prototype._innerSub=function(a,c,b){this.add(l.subscribeToResult(this,a,c,b))};c.prototype._complete=function(){this.hasCompleted=!0;0===this.active&&0===this.buffer.length&&this.destination.complete()};c.prototype.notifyNext=function(a,c,b,d){var f=this.destination,e=this.resultSelector;e?(a=k.tryCatch(e)(a,c,b,d),a===m.errorObject?f.error(m.errorObject.e):f.next(a)):f.next(c)};c.prototype.notifyComplete=function(a){var c=this.buffer;this.remove(a);this.active--;0<c.length?this._next(c.shift()):
+0===this.active&&this.hasCompleted&&this.destination.complete()};return c}(a.OuterSubscriber);e.MergeMapSubscriber=f},{"../OuterSubscriber":4,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],162:[function(a,b,e){var h=a("./mergeMap-support");e.mergeMap=function(a,b,e){void 0===e&&(e=Number.POSITIVE_INFINITY);return this.lift(new h.MergeMapOperator(a,b,e))}},{"./mergeMap-support":161}],163:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=
+a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)},k=a("../util/tryCatch"),m=a("../util/errorObject");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");a=function(){function a(c,b,d){void 0===d&&(d=Number.POSITIVE_INFINITY);this.ish=c;this.resultSelector=b;this.concurrent=d}a.prototype.call=function(a){return new f(a,this.ish,this.resultSelector,this.concurrent)};return a}();e.MergeMapToOperator=a;var f=function(a){function c(c,
+b,f,e){void 0===e&&(e=Number.POSITIVE_INFINITY);a.call(this,c);this.ish=b;this.resultSelector=f;this.concurrent=e;this.hasCompleted=!1;this.buffer=[];this.index=this.active=0}h(c,a);c.prototype._next=function(a){if(this.active<this.concurrent){var c=this.resultSelector,b=this.index++,d=this.ish,f=this.destination;this.active++;this._innerSub(d,f,c,a,b)}else this.buffer.push(a)};c.prototype._innerSub=function(a,c,b,d,f){this.add(l.subscribeToResult(this,a,d,f))};c.prototype._complete=function(){this.hasCompleted=
+!0;0===this.active&&0===this.buffer.length&&this.destination.complete()};c.prototype.notifyNext=function(a,c,b,d){var f=this.resultSelector,e=this.destination;f?(a=k.tryCatch(f)(a,c,b,d),a===m.errorObject?e.error(m.errorObject.e):e.next(a)):e.next(c)};c.prototype.notifyError=function(a){this.destination.error(a)};c.prototype.notifyComplete=function(a){var c=this.buffer;this.remove(a);this.active--;0<c.length?this._next(c.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()};return c}(b.OuterSubscriber);
+e.MergeMapToSubscriber=f},{"../OuterSubscriber":4,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],164:[function(a,b,e){var h=a("./mergeMapTo-support");e.mergeMapTo=function(a,b,e){void 0===e&&(e=Number.POSITIVE_INFINITY);return this.lift(new h.MergeMapToOperator(a,b,e))}},{"./mergeMapTo-support":163}],165:[function(a,b,e){var h=a("../observable/ConnectableObservable");e.multicast=function(a){return new h.ConnectableObservable(this,"function"===typeof a?a:function(){return a})}},
+{"../observable/ConnectableObservable":103}],166:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../Notification");a=function(){function a(b,c){void 0===c&&(c=0);this.scheduler=b;this.delay=c}a.prototype.call=function(a){return new m(a,this.scheduler,this.delay)};return a}();e.ObserveOnOperator=a;var m=function(a){function b(c,
+d,n){void 0===n&&(n=0);a.call(this,c);this.scheduler=d;this.delay=n}h(b,a);b.dispatch=function(a){a.notification.observe(a.destination)};b.prototype.scheduleMessage=function(a){this.add(this.scheduler.schedule(b.dispatch,this.delay,new l(a,this.destination)))};b.prototype._next=function(a){this.scheduleMessage(k.Notification.createNext(a))};b.prototype._error=function(a){this.scheduleMessage(k.Notification.createError(a))};b.prototype._complete=function(){this.scheduleMessage(k.Notification.createComplete())};
+return b}(b.Subscriber);e.ObserveOnSubscriber=m;var l=function(){return function(a,b){this.notification=a;this.destination=b}}()},{"../Notification":2,"../Subscriber":7}],167:[function(a,b,e){var h=a("./observeOn-support");e.observeOn=function(a,b){void 0===b&&(b=0);return this.lift(new h.ObserveOnOperator(a,b))}},{"./observeOn-support":166}],168:[function(a,b,e){var h=a("../util/not"),k=a("./filter");e.partition=function(a,b){return[k.filter.call(this,a),k.filter.call(this,h.not(a,b))]}},{"../util/not":237,
+"./filter":147}],169:[function(a,b,e){var h=a("../Subject"),k=a("./multicast");e.publish=function(){return k.multicast.call(this,new h.Subject)}},{"../Subject":6,"./multicast":165}],170:[function(a,b,e){var h=a("../subject/BehaviorSubject"),k=a("./multicast");e.publishBehavior=function(a){return k.multicast.call(this,new h.BehaviorSubject(a))}},{"../subject/BehaviorSubject":218,"./multicast":165}],171:[function(a,b,e){var h=a("../subject/AsyncSubject"),k=a("./multicast");e.publishLast=function(){return k.multicast.call(this,
+new h.AsyncSubject)}},{"../subject/AsyncSubject":217,"./multicast":165}],172:[function(a,b,e){var h=a("../subject/ReplaySubject"),k=a("./multicast");e.publishReplay=function(a,b,f){void 0===a&&(a=Number.POSITIVE_INFINITY);void 0===b&&(b=Number.POSITIVE_INFINITY);return k.multicast.call(this,new h.ReplaySubject(a,b,f))}},{"../subject/ReplaySubject":219,"./multicast":165}],173:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&
+(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");a=function(){function a(b,c){this.project=b;this.seed=c}a.prototype.call=function(a){return new l(a,this.project,this.seed)};return a}();e.ReduceOperator=a;var l=function(a){function b(c,d,n){a.call(this,c);this.hasValue=!1;this.acc=n;this.project=d;this.hasSeed="undefined"!==typeof n}h(b,a);b.prototype._next=function(a){this.hasValue||(this.hasValue=
+this.hasSeed)?(a=k.tryCatch(this.project).call(this,this.acc,a),a===m.errorObject?this.destination.error(m.errorObject.e):this.acc=a):(this.acc=a,this.hasValue=!0)};b.prototype._complete=function(){(this.hasValue||this.hasSeed)&&this.destination.next(this.acc);this.destination.complete()};return b}(b.Subscriber);e.ReduceSubscriber=l},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],174:[function(a,b,e){var h=a("./reduce-support");e.reduce=function(a,b){return this.lift(new h.ReduceOperator(a,
+b))}},{"./reduce-support":173}],175:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var k=a("../observable/empty");e.repeat=function(a){void 0===a&&(a=-1);return 0===a?new k.EmptyObservable:this.lift(new m(a,this))};var m=function(){function a(c,b){this.count=c;this.source=b}a.prototype.call=function(a){return new l(a,
+this.count,this.source)};return a}(),l=function(a){function c(c,b,f){a.call(this);this.destination=c;this.count=b;this.source=f;c.add(this);this.lastSubscription=this}h(c,a);c.prototype._next=function(a){this.destination.next(a)};c.prototype._error=function(a){this.destination.error(a)};c.prototype.complete=function(){this.isUnsubscribed||this.resubscribe(this.count)};c.prototype.unsubscribe=function(){var c=this.lastSubscription;c===this?a.prototype.unsubscribe.call(this):c.unsubscribe()};c.prototype.resubscribe=
+function(a){var c=this.destination,b=this.lastSubscription;c.remove(b);b.unsubscribe();0===a-1?c.complete():(a=new f(this,a-1),this.lastSubscription=this.source.subscribe(a),c.add(this.lastSubscription))};return c}(b.Subscriber),f=function(a){function c(c,b){a.call(this);this.parent=c;this.count=b}h(c,a);c.prototype._next=function(a){this.parent.destination.next(a)};c.prototype._error=function(a){this.parent.destination.error(a)};c.prototype._complete=function(){var a=this.count;this.parent.resubscribe(0>
+a?-1:a)};return c}(b.Subscriber)},{"../Subscriber":7,"../observable/empty":109}],176:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};a=a("../Subscriber");e.retry=function(a){void 0===a&&(a=0);return this.lift(new k(a,this))};var k=function(){function a(b,c){this.count=b;this.source=c}a.prototype.call=function(a){return new m(a,this.count,
+this.source)};return a}(),m=function(a){function b(c,d,n){a.call(this);this.destination=c;this.count=d;this.source=n;c.add(this);this.lastSubscription=this}h(b,a);b.prototype._next=function(a){this.destination.next(a)};b.prototype.error=function(a){this.isUnsubscribed||(this.unsubscribe(),this.resubscribe())};b.prototype._complete=function(){this.unsubscribe();this.destination.complete()};b.prototype.resubscribe=function(a){void 0===a&&(a=0);var b=this.lastSubscription,d=this.destination;d.remove(b);
+b.unsubscribe();a=new l(this,this.count,a+1);this.lastSubscription=this.source.subscribe(a);d.add(this.lastSubscription)};return b}(a.Subscriber),l=function(a){function b(c,d,n){void 0===n&&(n=0);a.call(this,null);this.parent=c;this.count=d;this.retried=n}h(b,a);b.prototype._next=function(a){this.parent.destination.next(a)};b.prototype._error=function(a){var b=this.parent,d=this.retried,f=this.count;f&&d===f?b.destination.error(a):b.resubscribe(d)};b.prototype._complete=function(){this.parent.destination.complete()};
+return b}(a.Subscriber)},{"../Subscriber":7}],177:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var k=a("../Subject"),m=a("../util/tryCatch"),l=a("../util/errorObject");e.retryWhen=function(a){return this.lift(new f(a,this))};var f=function(){function a(c,b){this.notifier=c;this.source=b}a.prototype.call=function(a){return new d(a,
+this.notifier,this.source)};return a}(),d=function(a){function b(c,d,g){a.call(this);this.destination=c;this.notifier=d;this.source=g;c.add(this);this.lastSubscription=this}h(b,a);b.prototype._next=function(a){this.destination.next(a)};b.prototype.error=function(c){var b=this.destination;if(!this.isUnsubscribed){a.prototype.unsubscribe.call(this);if(!this.retryNotifications){this.errors=new k.Subject;var d=m.tryCatch(this.notifier).call(this,this.errors);if(d===l.errorObject)b.error(l.errorObject.e);
+else{this.retryNotifications=d;var f=new g(this);this.notificationSubscription=d.subscribe(f);b.add(this.notificationSubscription)}}this.errors.next(c)}};b.prototype.destinationError=function(a){this.tearDown();this.destination.error(a)};b.prototype._complete=function(){this.destinationComplete()};b.prototype.destinationComplete=function(){this.tearDown();this.destination.complete()};b.prototype.unsubscribe=function(){this.lastSubscription===this?a.prototype.unsubscribe.call(this):this.tearDown()};
+b.prototype.tearDown=function(){a.prototype.unsubscribe.call(this);this.lastSubscription.unsubscribe();var c=this.notificationSubscription;c&&c.unsubscribe()};b.prototype.resubscribe=function(){var a=this.destination,b=this.lastSubscription;a.remove(b);b.unsubscribe();b=new c(this);this.lastSubscription=this.source.subscribe(b);a.add(this.lastSubscription)};return b}(b.Subscriber),c=function(a){function c(b){a.call(this,null);this.parent=b}h(c,a);c.prototype._next=function(a){this.parent.destination.next(a)};
+c.prototype._error=function(a){this.parent.errors.next(a)};c.prototype._complete=function(){this.parent.destinationComplete()};return c}(b.Subscriber),g=function(a){function c(b){a.call(this,null);this.parent=b}h(c,a);c.prototype._next=function(a){this.parent.resubscribe()};c.prototype._error=function(a){this.parent.destinationError(a)};c.prototype._complete=function(){this.parent.destinationComplete()};return c}(b.Subscriber)},{"../Subject":6,"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],
+178:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};a=a("../Subscriber");e.sample=function(a){return this.lift(new k(a))};var k=function(){function a(b){this.notifier=b}a.prototype.call=function(a){return new m(a,this.notifier)};return a}(),m=function(a){function b(c,d){a.call(this,c);this.notifier=d;this.hasValue=!1;this.add(d._subscribe(new l(this)))}
+h(b,a);b.prototype._next=function(a){this.lastValue=a;this.hasValue=!0};b.prototype.notifyNext=function(){this.hasValue&&(this.hasValue=!1,this.destination.next(this.lastValue))};return b}(a.Subscriber),l=function(a){function b(c){a.call(this,null);this.parent=c}h(b,a);b.prototype._next=function(){this.parent.notifyNext()};b.prototype._error=function(a){this.parent.error(a)};b.prototype._complete=function(){this.parent.notifyNext()};return b}(a.Subscriber)},{"../Subscriber":7}],179:[function(a,b,
+e){function h(a){var c=a.delay;a.subscriber.notifyNext();this.schedule(a,c)}var k=this&&this.__extends||function(a,c){function b(){this.constructor=a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var m=a("../scheduler/asap");e.sampleTime=function(a,c){void 0===c&&(c=m.asap);return this.lift(new l(a,c))};var l=function(){function a(c,b){this.delay=c;this.scheduler=b}a.prototype.call=function(a){return new f(a,
+this.delay,this.scheduler)};return a}(),f=function(a){function c(c,b,f){a.call(this,c);this.delay=b;this.scheduler=f;this.hasValue=!1;this.add(f.schedule(h,b,{subscriber:this,delay:b}))}k(c,a);c.prototype._next=function(a){this.lastValue=a;this.hasValue=!0};c.prototype.notifyNext=function(){this.hasValue&&(this.hasValue=!1,this.destination.next(this.lastValue))};return c}(b.Subscriber)},{"../Subscriber":7,"../scheduler/asap":215}],180:[function(a,b,e){var h=this&&this.__extends||function(a,c){function b(){this.constructor=
+a}for(var f in c)c.hasOwnProperty(f)&&(a[f]=c[f]);a.prototype=null===c?Object.create(c):(b.prototype=c.prototype,new b)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e.scan=function(a,c){return this.lift(new l(a,c))};var l=function(){function a(c,b){this.accumulator=c;this.seed=b}a.prototype.call=function(a){return new f(a,this.accumulator,this.seed)};return a}(),f=function(a){function c(c,b,f){a.call(this,c);this.accumulator=b;this.accumulatorSet=!1;this.seed=f;this.accumulator=
+b;this.accumulatorSet="undefined"!==typeof f}h(c,a);Object.defineProperty(c.prototype,"seed",{get:function(){return this._seed},set:function(a){this.accumulatorSet=!0;this._seed=a},enumerable:!0,configurable:!0});c.prototype._next=function(a){this.accumulatorSet?(a=k.tryCatch(this.accumulator).call(this,this.seed,a),a===m.errorObject?this.destination.error(m.errorObject.e):(this.seed=a,this.destination.next(this.seed))):(this.seed=a,this.destination.next(a))};return c}(b.Subscriber)},{"../Subscriber":7,
+"../util/errorObject":230,"../util/tryCatch":241}],181:[function(a,b,e){function h(){return new m.Subject}var k=a("./multicast"),m=a("../Subject");e.share=function(){return k.multicast.call(this,h).refCount()}},{"../Subject":6,"./multicast":165}],182:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};b=a("../Subscriber");var k=a("../util/tryCatch"),
+m=a("../util/errorObject"),l=a("../util/EmptyError");e.single=function(a){return this.lift(new f(a,this))};var f=function(){function a(b,c){this.predicate=b;this.source=c}a.prototype.call=function(a){return new d(a,this.predicate,this.source)};return a}(),d=function(a){function b(d,g,f){a.call(this,d);this.predicate=g;this.source=f;this.seenValue=!1;this.index=0}h(b,a);b.prototype.applySingleValue=function(a){this.seenValue?this.destination.error("Sequence contains more than one element"):(this.seenValue=
+!0,this.singleValue=a)};b.prototype._next=function(a){var b=this.predicate,c=this.index++;b?(b=k.tryCatch(b)(a,c,this.source),b===m.errorObject?this.destination.error(b.e):b&&this.applySingleValue(a)):this.applySingleValue(a)};b.prototype._complete=function(){var a=this.destination;0<this.index?(a.next(this.seenValue?this.singleValue:void 0),a.complete()):a.error(new l.EmptyError)};return b}(b.Subscriber)},{"../Subscriber":7,"../util/EmptyError":223,"../util/errorObject":230,"../util/tryCatch":241}],
+183:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};a=a("../Subscriber");e.skip=function(a){return this.lift(new k(a))};var k=function(){function a(b){this.total=b}a.prototype.call=function(a){return new m(a,this.total)};return a}(),m=function(a){function b(d,c){a.call(this,d);this.total=c;this.count=0}h(b,a);b.prototype._next=function(a){++this.count>
+this.total&&this.destination.next(a)};return b}(a.Subscriber)},{"../Subscriber":7}],184:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};a=a("../Subscriber");e.skipUntil=function(a){return this.lift(new k(a))};var k=function(){function a(b){this.notifier=b}a.prototype.call=function(a){return new m(a,this.notifier)};return a}(),m=function(a){function b(c,
+d){a.call(this,c);this.notifier=d;this.notificationSubscriber=null;this.notificationSubscriber=new l(this);this.add(this.notifier.subscribe(this.notificationSubscriber))}h(b,a);b.prototype._next=function(a){this.notificationSubscriber.hasValue&&this.destination.next(a)};b.prototype._error=function(a){this.destination.error(a)};b.prototype._complete=function(){this.notificationSubscriber.hasCompleted&&this.destination.complete();this.notificationSubscriber.unsubscribe()};b.prototype.unsubscribe=function(){this._isUnsubscribed||
+(this._subscription?(this._subscription.unsubscribe(),this._isUnsubscribed=!0):a.prototype.unsubscribe.call(this))};return b}(a.Subscriber),l=function(a){function b(c){a.call(this,null);this.parent=c;this.hasCompleted=this.hasValue=!1}h(b,a);b.prototype._next=function(a){this.hasValue=!0};b.prototype._error=function(a){this.parent.error(a);this.hasValue=!0};b.prototype._complete=function(){this.hasCompleted=!0};return b}(a.Subscriber)},{"../Subscriber":7}],185:[function(a,b,e){var h=this&&this.__extends||
+function(a,b){function g(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(g.prototype=b.prototype,new g)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");e.skipWhile=function(a){return this.lift(new l(a))};var l=function(){function a(b){this.predicate=b}a.prototype.call=function(a){return new f(a,this.predicate)};return a}(),f=function(a){function b(c,f){a.call(this,c);this.predicate=f;this.skipping=!0;this.index=
+0}h(b,a);b.prototype._next=function(a){var b=this.destination;if(!0===this.skipping){var c=this.index++,c=k.tryCatch(this.predicate)(a,c);c===m.errorObject?b.error(c.e):this.skipping=Boolean(c)}!1===this.skipping&&b.next(a)};return b}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],186:[function(a,b,e){var h=a("../observable/fromArray"),k=a("../observable/ScalarObservable"),m=a("../observable/empty"),l=a("./concat-static"),f=a("../util/isScheduler");e.startWith=
+function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];b=a[a.length-1];f.isScheduler(b)?a.pop():b=void 0;var g=a.length;return 1===g?l.concat(new k.ScalarObservable(a[0],b),this):1<g?l.concat(new h.ArrayObservable(a,b),this):l.concat(new m.EmptyObservable(b),this)}},{"../observable/ScalarObservable":105,"../observable/empty":109,"../observable/fromArray":112,"../util/isScheduler":235,"./concat-static":131}],187:[function(a,b,e){var h=a("../observable/SubscribeOnObservable");e.subscribeOn=
+function(a,b){void 0===b&&(b=0);return new h.SubscribeOnObservable(this,b,a)}},{"../observable/SubscribeOnObservable":106}],188:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../OuterSubscriber");var k=a("../util/subscribeToResult");e._switch=function(){return this.lift(new m)};var m=function(){function a(){}a.prototype.call=function(a){return new l(a)};
+return a}(),l=function(a){function b(c){a.call(this,c);this.active=0;this.hasCompleted=!1}h(b,a);b.prototype._next=function(a){this.unsubscribeInner();this.active++;this.add(this.innerSubscription=k.subscribeToResult(this,a))};b.prototype._complete=function(){this.hasCompleted=!0;0===this.active&&this.destination.complete()};b.prototype.unsubscribeInner=function(){this.active=0<this.active?this.active-1:0;var a=this.innerSubscription;a&&(a.unsubscribe(),this.remove(a))};b.prototype.notifyNext=function(a,
+b){this.destination.next(b)};b.prototype.notifyError=function(a){this.destination.error(a)};b.prototype.notifyComplete=function(){this.unsubscribeInner();this.hasCompleted&&0===this.active&&this.destination.complete()};return b}(b.OuterSubscriber)},{"../OuterSubscriber":4,"../util/subscribeToResult":239}],189:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(d.prototype=
+b.prototype,new d)},k=a("../util/tryCatch"),m=a("../util/errorObject");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");e.switchMap=function(a,b){return this.lift(new f(a,b))};var f=function(){function a(b,c){this.project=b;this.resultSelector=c}a.prototype.call=function(a){return new d(a,this.project,this.resultSelector)};return a}(),d=function(a){function b(d,g,f){a.call(this,d);this.project=g;this.resultSelector=f;this.hasCompleted=!1;this.index=0}h(b,a);b.prototype._next=function(a){var b=
+this.index++,c=this.destination,d=k.tryCatch(this.project)(a,b);d===m.errorObject?c.error(d.e):((c=this.innerSubscription)&&c.unsubscribe(),this.add(this.innerSubscription=l.subscribeToResult(this,d,a,b)))};b.prototype._complete=function(){var a=this.innerSubscription;this.hasCompleted=!0;a&&!a.isUnsubscribed||this.destination.complete()};b.prototype.notifyComplete=function(a){this.remove(a);(a=this.innerSubscription)&&a.unsubscribe();this.innerSubscription=null;this.hasCompleted&&this.destination.complete()};
+b.prototype.notifyError=function(a){this.destination.error(a)};b.prototype.notifyNext=function(a,b,c,d){var g=this.resultSelector,f=this.destination;g?(a=k.tryCatch(g)(a,b,c,d),a===m.errorObject?f.error(m.errorObject.e):f.next(a)):f.next(b)};return b}(b.OuterSubscriber)},{"../OuterSubscriber":4,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],190:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&
+(a[f]=b[f]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)},k=a("../util/tryCatch"),m=a("../util/errorObject");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");e.switchMapTo=function(a,b){return this.lift(new f(a,b))};var f=function(){function a(b,c){this.observable=b;this.resultSelector=c}a.prototype.call=function(a){return new d(a,this.observable,this.resultSelector)};return a}(),d=function(a){function b(d,f,g){a.call(this,d);this.inner=f;this.resultSelector=
+g;this.hasCompleted=!1;this.index=0}h(b,a);b.prototype._next=function(a){var b=this.index++,c=this.innerSubscription;c&&c.unsubscribe();this.add(this.innerSubscription=l.subscribeToResult(this,this.inner,a,b))};b.prototype._complete=function(){var a=this.innerSubscription;this.hasCompleted=!0;a&&!a.isUnsubscribed||this.destination.complete()};b.prototype.notifyComplete=function(a){this.remove(a);(a=this.innerSubscription)&&a.unsubscribe();this.innerSubscription=null;this.hasCompleted&&this.destination.complete()};
+b.prototype.notifyError=function(a){this.destination.error(a)};b.prototype.notifyNext=function(a,b,c,d){var f=this.resultSelector,g=this.destination;f?(a=k.tryCatch(f)(a,b,c,d),a===m.errorObject?g.error(m.errorObject.e):g.next(a)):g.next(b)};return b}(b.OuterSubscriber)},{"../OuterSubscriber":4,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],191:[function(a,b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var e in b)b.hasOwnProperty(e)&&
+(a[e]=b[e]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Subscriber");var k=a("../util/ArgumentOutOfRangeError"),m=a("../observable/empty");e.take=function(a){return 0===a?new m.EmptyObservable:this.lift(new l(a))};var l=function(){function a(b){this.total=b;if(0>this.total)throw new k.ArgumentOutOfRangeError;}a.prototype.call=function(a){return new f(a,this.total)};return a}(),f=function(a){function b(c,f){a.call(this,c);this.total=f;this.count=0}h(b,a);b.prototype._next=
+function(a){var b=this.total;++this.count<=b&&(this.destination.next(a),this.count===b&&this.destination.complete())};return b}(b.Subscriber)},{"../Subscriber":7,"../observable/empty":109,"../util/ArgumentOutOfRangeError":222}],192:[function(a,b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Subscriber");var k=a("../util/noop");e.takeUntil=function(a){return this.lift(new m(a))};
+var m=function(){function a(b){this.notifier=b}a.prototype.call=function(a){return new l(a,this.notifier)};return a}(),l=function(a){function b(c,e){a.call(this,c);this.notifier=e;this.notificationSubscriber=null;this.notificationSubscriber=new f(c);this.add(e.subscribe(this.notificationSubscriber))}h(b,a);b.prototype._complete=function(){this.destination.complete();this.notificationSubscriber.unsubscribe()};return b}(b.Subscriber),f=function(a){function b(c){a.call(this,null);this.destination=c}
+h(b,a);b.prototype._next=function(a){this.destination.complete()};b.prototype._error=function(a){this.destination.error(a)};b.prototype._complete=function(){k.noop()};return b}(b.Subscriber)},{"../Subscriber":7,"../util/noop":236}],193:[function(a,b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject");
+e.takeWhile=function(a){return this.lift(new l(a))};var l=function(){function a(b){this.predicate=b}a.prototype.call=function(a){return new f(a,this.predicate)};return a}(),f=function(a){function b(c,f){a.call(this,c);this.predicate=f;this.index=0}h(b,a);b.prototype._next=function(a){var b=this.destination,c=k.tryCatch(this.predicate)(a,this.index++);c==m.errorObject?b.error(c.e):Boolean(c)?b.next(a):b.complete()};return b}(b.Subscriber)},{"../Subscriber":7,"../util/errorObject":230,"../util/tryCatch":241}],
+194:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)},k=a("../observable/fromPromise");b=a("../Subscriber");var m=a("../util/tryCatch"),l=a("../util/isPromise"),f=a("../util/errorObject");e.throttle=function(a){return this.lift(new d(a))};var d=function(){function a(b){this.durationSelector=b}a.prototype.call=function(a){return new c(a,this.durationSelector)};
+return a}(),c=function(a){function b(c,d){a.call(this,c);this.durationSelector=d}h(b,a);b.prototype._next=function(a){if(!this.throttled){var b=this.destination,c=m.tryCatch(this.durationSelector)(a);c===f.errorObject?b.error(f.errorObject.e):(l.isPromise(c)&&(c=k.PromiseObservable.create(c)),this.add(this.throttled=c._subscribe(new g(this))),b.next(a))}};b.prototype._error=function(b){this.clearThrottle();a.prototype._error.call(this,b)};b.prototype._complete=function(){this.clearThrottle();a.prototype._complete.call(this)};
+b.prototype.clearThrottle=function(){var a=this.throttled;a&&(a.unsubscribe(),this.remove(a),this.throttled=null)};return b}(b.Subscriber),g=function(a){function b(c){a.call(this,null);this.parent=c}h(b,a);b.prototype._next=function(a){this.parent.clearThrottle()};b.prototype._error=function(a){this.parent.error(a)};b.prototype._complete=function(){this.parent.clearThrottle()};return b}(b.Subscriber)},{"../Subscriber":7,"../observable/fromPromise":115,"../util/errorObject":230,"../util/isPromise":234,
+"../util/tryCatch":241}],195:[function(a,b,e){function h(a){a.subscriber.clearThrottle()}var k=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Subscriber");var m=a("../scheduler/asap");e.throttleTime=function(a,b){void 0===b&&(b=m.asap);return this.lift(new l(a,b))};var l=function(){function a(b,d){this.delay=b;this.scheduler=d}a.prototype.call=function(a){return new f(a,
+this.delay,this.scheduler)};return a}(),f=function(a){function b(c,f,e){a.call(this,c);this.delay=f;this.scheduler=e}k(b,a);b.prototype._next=function(a){this.throttled||(this.add(this.throttled=this.scheduler.schedule(h,this.delay,{subscriber:this})),this.destination.next(a))};b.prototype.clearThrottle=function(){var a=this.throttled;a&&(a.unsubscribe(),this.remove(a),this.throttled=null)};return b}(b.Subscriber)},{"../Subscriber":7,"../scheduler/asap":215}],196:[function(a,b,e){var h=this&&this.__extends||
+function(a,b){function f(){this.constructor=a}for(var e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Subscriber");var k=a("../scheduler/queue"),m=a("../util/isDate");e.timeout=function(a,b,f){void 0===b&&(b=null);void 0===f&&(f=k.queue);var e=m.isDate(a);a=e?+a-f.now():a;return this.lift(new l(a,e,b,f))};var l=function(){function a(b,d,f,e){this.waitFor=b;this.absoluteTimeout=d;this.errorToSend=f;this.scheduler=e}a.prototype.call=
+function(a){return new f(a,this.absoluteTimeout,this.waitFor,this.errorToSend,this.scheduler)};return a}(),f=function(a){function b(c,f,e,l,k){a.call(this,c);this.absoluteTimeout=f;this.waitFor=e;this.errorToSend=l;this.scheduler=k;this._previousIndex=this.index=0;this._hasCompleted=!1;this.scheduleTimeout()}h(b,a);Object.defineProperty(b.prototype,"previousIndex",{get:function(){return this._previousIndex},enumerable:!0,configurable:!0});Object.defineProperty(b.prototype,"hasCompleted",{get:function(){return this._hasCompleted},
+enumerable:!0,configurable:!0});b.dispatchTimeout=function(a){var b=a.subscriber;a=a.index;b.hasCompleted||b.previousIndex!==a||b.notifyTimeout()};b.prototype.scheduleTimeout=function(){var a=this.index;this.scheduler.schedule(b.dispatchTimeout,this.waitFor,{subscriber:this,index:a});this.index++;this._previousIndex=a};b.prototype._next=function(a){this.destination.next(a);this.absoluteTimeout||this.scheduleTimeout()};b.prototype._error=function(a){this.destination.error(a);this._hasCompleted=!0};
+b.prototype._complete=function(){this.destination.complete();this._hasCompleted=!0};b.prototype.notifyTimeout=function(){this.error(this.errorToSend||Error("timeout"))};return b}(b.Subscriber)},{"../Subscriber":7,"../scheduler/queue":216,"../util/isDate":232}],197:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)},k=a("../scheduler/queue"),
+m=a("../util/isDate");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");e.timeoutWith=function(a,b,d){void 0===d&&(d=k.queue);var e=m.isDate(a);a=e?+a-d.now():a;return this.lift(new f(a,e,b,d))};var f=function(){function a(b,c,d,f){this.waitFor=b;this.absoluteTimeout=c;this.withObservable=d;this.scheduler=f}a.prototype.call=function(a){return new d(a,this.absoluteTimeout,this.waitFor,this.withObservable,this.scheduler)};return a}(),d=function(a){function b(d,f,g,e,l){a.call(this,null);
+this.destination=d;this.absoluteTimeout=f;this.waitFor=g;this.withObservable=e;this.scheduler=l;this.timeoutSubscription=void 0;this._previousIndex=this.index=0;this._hasCompleted=!1;d.add(this);this.scheduleTimeout()}h(b,a);Object.defineProperty(b.prototype,"previousIndex",{get:function(){return this._previousIndex},enumerable:!0,configurable:!0});Object.defineProperty(b.prototype,"hasCompleted",{get:function(){return this._hasCompleted},enumerable:!0,configurable:!0});b.dispatchTimeout=function(a){var b=
+a.subscriber;a=a.index;b.hasCompleted||b.previousIndex!==a||b.handleTimeout()};b.prototype.scheduleTimeout=function(){var a=this.index;this.scheduler.schedule(b.dispatchTimeout,this.waitFor,{subscriber:this,index:a});this.index++;this._previousIndex=a};b.prototype._next=function(a){this.destination.next(a);this.absoluteTimeout||this.scheduleTimeout()};b.prototype._error=function(a){this.destination.error(a);this._hasCompleted=!0};b.prototype._complete=function(){this.destination.complete();this._hasCompleted=
+!0};b.prototype.handleTimeout=function(){if(!this.isUnsubscribed){var a=this.withObservable;this.unsubscribe();this.destination.add(this.timeoutSubscription=l.subscribeToResult(this,a))}};return b}(b.OuterSubscriber)},{"../OuterSubscriber":4,"../scheduler/queue":216,"../util/isDate":232,"../util/subscribeToResult":239}],198:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):
+(d.prototype=b.prototype,new d)};a=a("../Subscriber");e.toArray=function(){return this.lift(new k)};var k=function(){function a(){}a.prototype.call=function(a){return new m(a)};return a}(),m=function(a){function b(d){a.call(this,d);this.array=[]}h(b,a);b.prototype._next=function(a){this.array.push(a)};b.prototype._complete=function(){this.destination.next(this.array);this.destination.complete()};return b}(a.Subscriber)},{"../Subscriber":7}],199:[function(a,b,e){var h=a("../util/root");e.toPromise=
+function(a){var b=this;a||(h.root.Rx&&h.root.Rx.config&&h.root.Rx.config.Promise?a=h.root.Rx.config.Promise:h.root.Promise&&(a=h.root.Promise));if(!a)throw Error("no Promise impl found");return new a(function(a,f){var d;b.subscribe(function(a){return d=a},function(a){return f(a)},function(){return a(d)})})}},{"../util/root":238}],200:[function(a,b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);a.prototype=null===b?Object.create(b):
+(f.prototype=b.prototype,new f)};b=a("../Subscriber");var k=a("../Subject");e.window=function(a){return this.lift(new m(a))};var m=function(){function a(b){this.closingNotifier=b}a.prototype.call=function(a){return new l(a,this.closingNotifier)};return a}(),l=function(a){function b(c,e){a.call(this,c);this.destination=c;this.closingNotifier=e;this.add(e._subscribe(new f(this)));this.openWindow()}h(b,a);b.prototype._next=function(a){this.window.next(a)};b.prototype._error=function(a){this.window.error(a);
+this.destination.error(a)};b.prototype._complete=function(){this.window.complete();this.destination.complete()};b.prototype.openWindow=function(){var a=this.window;a&&a.complete();var a=this.destination,b=this.window=new k.Subject;a.add(b);a.next(b)};return b}(b.Subscriber),f=function(a){function b(c){a.call(this,null);this.parent=c}h(b,a);b.prototype._next=function(){this.parent.openWindow()};b.prototype._error=function(a){this.parent._error(a)};b.prototype._complete=function(){this.parent._complete()};
+return b}(b.Subscriber)},{"../Subject":6,"../Subscriber":7}],201:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var g in b)b.hasOwnProperty(g)&&(a[g]=b[g]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../Subject");e.windowCount=function(a,b){void 0===b&&(b=0);return this.lift(new m(a,b))};var m=function(){function a(b,c){this.windowSize=b;this.startWindowEvery=c}a.prototype.call=function(a){return new l(a,
+this.windowSize,this.startWindowEvery)};return a}(),l=function(a){function b(c,d,e){a.call(this,c);this.destination=c;this.windowSize=d;this.startWindowEvery=e;this.windows=[new k.Subject];this.count=0;d=this.windows[0];c.add(d);c.next(d)}h(b,a);b.prototype._next=function(a){for(var b=0<this.startWindowEvery?this.startWindowEvery:this.windowSize,d=this.destination,f=this.windowSize,e=this.windows,l=e.length,h=0;h<l;h++)e[h].next(a);a=this.count-f+1;0<=a&&0===a%b&&e.shift().complete();0===++this.count%
+b&&(b=new k.Subject,e.push(b),d.add(b),d.next(b))};b.prototype._error=function(a){for(var b=this.windows;0<b.length;)b.shift().error(a);this.destination.error(a)};b.prototype._complete=function(){for(var a=this.windows;0<a.length;)a.shift().complete();this.destination.complete()};return b}(b.Subscriber)},{"../Subject":6,"../Subscriber":7}],202:[function(a,b,e){function h(a){var b=a.subscriber,c=a.windowTimeSpan,d=a.window;d&&d.complete();a.window=b.openWindow();this.schedule(a,c)}function k(a){var b=
+a.windowTimeSpan,c=a.subscriber,d=a.scheduler,f=a.windowCreationInterval,e=c.openWindow(),g={action:this,subscription:null};g.subscription=d.schedule(m,b,{subscriber:c,window:e,context:g});this.add(g.subscription);this.schedule(a,f)}function m(a){var b=a.subscriber,c=a.window;(a=a.context)&&a.action&&a.subscription&&a.action.remove(a.subscription);b.closeWindow(c)}var l=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);a.prototype=
+null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var f=a("../Subject"),d=a("../scheduler/asap");e.windowTime=function(a,b,f){void 0===b&&(b=null);void 0===f&&(f=d.asap);return this.lift(new c(a,b,f))};var c=function(){function a(b,c,d){this.windowTimeSpan=b;this.windowCreationInterval=c;this.scheduler=d}a.prototype.call=function(a){return new g(a,this.windowTimeSpan,this.windowCreationInterval,this.scheduler)};return a}(),g=function(a){function b(c,d,f,e){a.call(this,
+c);this.destination=c;this.windowTimeSpan=d;this.windowCreationInterval=f;this.scheduler=e;this.windows=[];if(null!==f&&0<=f){c={subscriber:this,window:this.openWindow(),context:null};var g={windowTimeSpan:d,windowCreationInterval:f,subscriber:this,scheduler:e};this.add(e.schedule(m,d,c));this.add(e.schedule(k,f,g))}else f={subscriber:this,window:this.openWindow(),windowTimeSpan:d},this.add(e.schedule(h,d,f))}l(b,a);b.prototype._next=function(a){for(var b=this.windows,c=b.length,d=0;d<c;d++)b[d].next(a)};
+b.prototype._error=function(a){for(var b=this.windows;0<b.length;)b.shift().error(a);this.destination.error(a)};b.prototype._complete=function(){for(var a=this.windows;0<a.length;)a.shift().complete();this.destination.complete()};b.prototype.openWindow=function(){var a=new f.Subject;this.windows.push(a);var b=this.destination;b.add(a);b.next(a);return a};b.prototype.closeWindow=function(a){a.complete();var b=this.windows;b.splice(b.indexOf(a),1)};return b}(b.Subscriber)},{"../Subject":6,"../Subscriber":7,
+"../scheduler/asap":215}],203:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../Subject"),m=a("../Subscription"),l=a("../util/tryCatch"),f=a("../util/errorObject");e.windowToggle=function(a,b){return this.lift(new d(a,b))};var d=function(){function a(b,c){this.openings=b;this.closingSelector=c}a.prototype.call=
+function(a){return new c(a,this.openings,this.closingSelector)};return a}(),c=function(a){function b(c,d,f){a.call(this,c);this.destination=c;this.openings=d;this.closingSelector=f;this.contexts=[];this.add(this.openings._subscribe(new n(this)))}h(b,a);b.prototype._next=function(a){for(var b=this.contexts,c=b.length,d=0;d<c;d++)b[d].window.next(a)};b.prototype._error=function(a){for(var b=this.contexts;0<b.length;)b.shift().window.error(a);this.destination.error(a)};b.prototype._complete=function(){for(var a=
+this.contexts;0<a.length;){var b=a.shift();b.window.complete();b.subscription.unsubscribe()}this.destination.complete()};b.prototype.openWindow=function(a){var b=l.tryCatch(this.closingSelector)(a);if(b===f.errorObject)this.error(b.e);else{a=this.destination;var c=new k.Subject,d=new m.Subscription,e={window:c,subscription:d};this.contexts.push(e);e=new g(this,e);b=b._subscribe(e);d.add(b);a.add(d);a.add(c);a.next(c)}};b.prototype.closeWindow=function(a){var b=a.window,c=a.subscription,d=this.contexts,
+f=this.destination;d.splice(d.indexOf(a),1);b.complete();f.remove(c);f.remove(b);c.unsubscribe()};return b}(b.Subscriber),g=function(a){function b(c,d){a.call(this,null);this.parent=c;this.windowContext=d}h(b,a);b.prototype._next=function(){this.parent.closeWindow(this.windowContext)};b.prototype._error=function(a){this.parent.error(a)};b.prototype._complete=function(){this.parent.closeWindow(this.windowContext)};return b}(b.Subscriber),n=function(a){function b(c){a.call(this);this.parent=c}h(b,a);
+b.prototype._next=function(a){this.parent.openWindow(a)};b.prototype._error=function(a){this.parent.error(a)};b.prototype._complete=function(){};return b}(b.Subscriber)},{"../Subject":6,"../Subscriber":7,"../Subscription":8,"../util/errorObject":230,"../util/tryCatch":241}],204:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");
+var k=a("../Subject"),m=a("../Subscription"),l=a("../util/tryCatch"),f=a("../util/errorObject");e.windowWhen=function(a){return this.lift(new d(a))};var d=function(){function a(b){this.closingSelector=b}a.prototype.call=function(a){return new c(a,this.closingSelector)};return a}(),c=function(a){function b(c,d){a.call(this,c);this.destination=c;this.closingSelector=d;this.openWindow()}h(b,a);b.prototype._next=function(a){this.window.next(a)};b.prototype._error=function(a){this.window.error(a);this.destination.error(a);
+this._unsubscribeClosingNotification()};b.prototype._complete=function(){this.window.complete();this.destination.complete();this._unsubscribeClosingNotification()};b.prototype.unsubscribe=function(){a.prototype.unsubscribe.call(this);this._unsubscribeClosingNotification()};b.prototype._unsubscribeClosingNotification=function(){var a=this.closingNotification;a&&a.unsubscribe()};b.prototype.openWindow=function(){var a=this.closingNotification;a&&(this.remove(a),a.unsubscribe());(a=this.window)&&a.complete();
+a=this.window=new k.Subject;this.destination.next(a);var b=l.tryCatch(this.closingSelector)();if(b===f.errorObject)a=b.e,this.destination.error(a),this.window.error(a);else{var c=this.closingNotification=new m.Subscription;c.add(b._subscribe(new g(this)));this.add(c);this.add(a)}};return b}(b.Subscriber),g=function(a){function b(c){a.call(this,null);this.parent=c}h(b,a);b.prototype._next=function(){this.parent.openWindow()};b.prototype._error=function(a){this.parent.error(a)};b.prototype._complete=
+function(){this.parent.openWindow()};return b}(b.Subscriber)},{"../Subject":6,"../Subscriber":7,"../Subscription":8,"../util/errorObject":230,"../util/tryCatch":241}],205:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)},k=a("../util/tryCatch"),m=a("../util/errorObject");b=a("../OuterSubscriber");var l=a("../util/subscribeToResult");e.withLatestFrom=
+function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];var d;"function"===typeof a[a.length-1]&&(d=a.pop());return this.lift(new f(a,d))};var f=function(){function a(b,c){this.observables=b;this.project=c}a.prototype.call=function(a){return new d(a,this.observables,this.project)};return a}(),d=function(a){function b(d,f,e){a.call(this,d);this.observables=f;this.project=e;this.toRespond=[];d=f.length;this.values=Array(d);for(e=0;e<d;e++)this.toRespond.push(e);for(e=0;e<d;e++){var g=
+f[e];this.add(l.subscribeToResult(this,g,g,e))}}h(b,a);b.prototype.notifyNext=function(a,b,c,d){this.values[c]=b;a=this.toRespond;0<a.length&&(c=a.indexOf(c),-1!==c&&a.splice(c,1))};b.prototype.notifyComplete=function(){};b.prototype._next=function(a){if(0===this.toRespond.length){var b=this.destination,c=this.project;a=[a].concat(this.values);c?(c=k.tryCatch(this.project).apply(this,a),c===m.errorObject?b.error(c.e):b.next(c)):b.next(a)}};return b}(b.OuterSubscriber)},{"../OuterSubscriber":4,"../util/errorObject":230,
+"../util/subscribeToResult":239,"../util/tryCatch":241}],206:[function(a,b,e){var h=a("../observable/fromArray"),k=a("./zip-support");e.zip=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];b=a[a.length-1];"function"===typeof b&&a.pop();return(new h.ArrayObservable(a)).lift(new k.ZipOperator(b))}},{"../observable/fromArray":112,"./zip-support":207}],207:[function(a,b,e){var h=this&&this.__extends||function(a,b){function c(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&
+(a[d]=b[d]);a.prototype=null===b?Object.create(b):(c.prototype=b.prototype,new c)};b=a("../Subscriber");var k=a("../util/tryCatch"),m=a("../util/errorObject"),l=a("../OuterSubscriber"),f=a("../util/subscribeToResult"),d=a("../util/SymbolShim"),c=Array.isArray;a=function(){function a(b){this.project=b}a.prototype.call=function(a){return new g(a,this.project)};return a}();e.ZipOperator=a;var g=function(a){function b(c,d,f){void 0===f&&(f=Object.create(null));a.call(this,c);this.index=0;this.iterators=
+[];this.active=0;this.project="function"===typeof d?d:null;this.values=f}h(b,a);b.prototype._next=function(a){var b=this.iterators,f=this.index++;c(a)?b.push(new p(a)):"function"===typeof a[d.SymbolShim.iterator]?b.push(new n(a[d.SymbolShim.iterator]())):b.push(new q(this.destination,this,a,f))};b.prototype._complete=function(){var a=this.iterators,b=a.length;this.active=b;for(var c=0;c<b;c++){var d=a[c];d.stillUnsubscribed?d.subscribe(d,c):this.active--}};b.prototype.notifyInactive=function(){this.active--;
+0===this.active&&this.destination.complete()};b.prototype.checkIterators=function(){for(var a=this.iterators,b=a.length,c=this.destination,d=0;d<b;d++){var f=a[d];if("function"===typeof f.hasValue&&!f.hasValue())return}for(var e=!1,g=[],d=0;d<b;d++){var f=a[d],l=f.next();f.hasCompleted()&&(e=!0);if(l.done){c.complete();return}g.push(l.value)}(a=this.project)?(l=k.tryCatch(a).apply(this,g),l===m.errorObject?c.error(m.errorObject.e):c.next(l)):c.next(g);e&&c.complete()};return b}(b.Subscriber);e.ZipSubscriber=
+g;var n=function(){function a(b){this.iterator=b;this.nextResult=b.next()}a.prototype.hasValue=function(){return!0};a.prototype.next=function(){var a=this.nextResult;this.nextResult=this.iterator.next();return a};a.prototype.hasCompleted=function(){var a=this.nextResult;return a&&a.done};return a}(),p=function(){function a(b){this.array=b;this.length=this.index=0;this.length=b.length}a.prototype[d.SymbolShim.iterator]=function(){return this};a.prototype.next=function(a){a=this.index++;var b=this.array;
+return a<this.length?{value:b[a],done:!1}:{done:!0}};a.prototype.hasValue=function(){return this.array.length>this.index};a.prototype.hasCompleted=function(){return this.array.length===this.index};return a}(),q=function(a){function b(c,d,f,e){a.call(this,c);this.parent=d;this.observable=f;this.index=e;this.stillUnsubscribed=!0;this.buffer=[];this.isComplete=!1}h(b,a);b.prototype[d.SymbolShim.iterator]=function(){return this};b.prototype.next=function(){var a=this.buffer;return 0===a.length&&this.isComplete?
+{done:!0}:{value:a.shift(),done:!1}};b.prototype.hasValue=function(){return 0<this.buffer.length};b.prototype.hasCompleted=function(){return 0===this.buffer.length&&this.isComplete};b.prototype.notifyComplete=function(){0<this.buffer.length?(this.isComplete=!0,this.parent.notifyInactive()):this.destination.complete()};b.prototype.notifyNext=function(a,b,c,d){this.buffer.push(b);this.parent.checkIterators()};b.prototype.subscribe=function(a,b){this.add(f.subscribeToResult(this,this.observable,this,
+b))};return b}(l.OuterSubscriber)},{"../OuterSubscriber":4,"../Subscriber":7,"../util/SymbolShim":229,"../util/errorObject":230,"../util/subscribeToResult":239,"../util/tryCatch":241}],208:[function(a,b,e){var h=a("./zip-static");e.zipProto=function(){for(var a=[],b=0;b<arguments.length;b++)a[b-0]=arguments[b];a.unshift(this);return h.zip.apply(this,a)}},{"./zip-static":206}],209:[function(a,b,e){var h=a("./zip-support");e.zipAll=function(a){return this.lift(new h.ZipOperator(a))}},{"./zip-support":207}],
+210:[function(a,b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)},k=a("../util/Immediate");a=function(a){function b(){a.apply(this,arguments)}h(b,a);b.prototype.schedule=function(a){var b=this;if(this.isUnsubscribed)return this;this.state=a;a=this.scheduler;a.actions.push(this);a.scheduled||(a.scheduled=!0,this.id=k.Immediate.setImmediate(function(){b.id=
+null;b.scheduler.scheduled=!1;b.scheduler.flush()}));return this};b.prototype.unsubscribe=function(){var b=this.id,d=this.scheduler;a.prototype.unsubscribe.call(this);0===d.actions.length&&(d.active=!1,d.scheduled=!1);b&&(this.id=null,k.Immediate.clearImmediate(b))};return b}(a("./QueueAction").QueueAction);e.AsapAction=a},{"../util/Immediate":225,"./QueueAction":213}],211:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&
+(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};b=a("./QueueScheduler");var k=a("./AsapAction"),m=a("./QueueAction");a=function(a){function b(){a.apply(this,arguments)}h(b,a);b.prototype.scheduleNow=function(a,b){return(this.scheduled?new m.QueueAction(this,a):new k.AsapAction(this,a)).schedule(b)};return b}(b.QueueScheduler);e.AsapScheduler=a},{"./AsapAction":210,"./QueueAction":213,"./QueueScheduler":214}],212:[function(a,b,e){var h=this&&this.__extends||function(a,
+b){function e(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(e.prototype=b.prototype,new e)};a=function(a){function b(e,f){a.call(this,e,f);this.scheduler=e;this.work=f}h(b,a);b.prototype.schedule=function(a,b){var d=this;void 0===b&&(b=0);if(this.isUnsubscribed)return this;this.delay=b;this.state=a;var c=this.id;null!=c&&(this.id=void 0,clearTimeout(c));var e=this.scheduler;this.id=setTimeout(function(){d.id=void 0;e.actions.push(d);e.flush()},
+this.delay);return this};b.prototype.unsubscribe=function(){var b=this.id;null!=b&&(this.id=void 0,clearTimeout(b));a.prototype.unsubscribe.call(this)};return b}(a("./QueueAction").QueueAction);e.FutureAction=a},{"./QueueAction":213}],213:[function(a,b,e){var h=this&&this.__extends||function(a,b){function e(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(e.prototype=b.prototype,new e)};a=function(a){function b(e,f){a.call(this);this.scheduler=
+e;this.work=f}h(b,a);b.prototype.schedule=function(a){if(this.isUnsubscribed)return this;this.state=a;a=this.scheduler;a.actions.push(this);a.flush();return this};b.prototype.execute=function(){if(this.isUnsubscribed)throw Error("How did did we execute a canceled Action?");this.work(this.state)};b.prototype.unsubscribe=function(){var b=this.scheduler.actions,f=b.indexOf(this);this.scheduler=this.state=this.work=void 0;-1!==f&&b.splice(f,1);a.prototype.unsubscribe.call(this)};return b}(a("../Subscription").Subscription);
+e.QueueAction=a},{"../Subscription":8}],214:[function(a,b,e){var h=a("./QueueAction"),k=a("./FutureAction");a=function(){function a(){this.actions=[];this.scheduled=this.active=!1}a.prototype.now=function(){return Date.now()};a.prototype.flush=function(){if(!this.active&&!this.scheduled){this.active=!0;for(var a=this.actions,b=void 0;b=a.shift();)b.execute();this.active=!1}};a.prototype.schedule=function(a,b,d){void 0===b&&(b=0);return 0>=b?this.scheduleNow(a,d):this.scheduleLater(a,b,d)};a.prototype.scheduleNow=
+function(a,b){return(new h.QueueAction(this,a)).schedule(b)};a.prototype.scheduleLater=function(a,b,d){return(new k.FutureAction(this,a)).schedule(d,b)};return a}();e.QueueScheduler=a},{"./FutureAction":212,"./QueueAction":213}],215:[function(a,b,e){a=a("./AsapScheduler");e.asap=new a.AsapScheduler},{"./AsapScheduler":211}],216:[function(a,b,e){a=a("./QueueScheduler");e.queue=new a.QueueScheduler},{"./QueueScheduler":214}],217:[function(a,b,e){var h=this&&this.__extends||function(a,b){function e(){this.constructor=
+a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===b?Object.create(b):(e.prototype=b.prototype,new e)};a=function(a){function b(){a.call(this);this._value=void 0;this._isScalar=this._hasNext=!1}h(b,a);b.prototype._subscribe=function(b){this.completeSignal&&this._hasNext&&b.next(this._value);return a.prototype._subscribe.call(this,b)};b.prototype._next=function(a){this._value=a;this._hasNext=!0};b.prototype._complete=function(){var a=-1,b=this.observers,d=b.length;this.observers=
+void 0;this.isUnsubscribed=!0;if(this._hasNext)for(;++a<d;){var c=b[a];c.next(this._value);c.complete()}else for(;++a<d;)b[a].complete();this.isUnsubscribed=!1};return b}(a("../Subject").Subject);e.AsyncSubject=a},{"../Subject":6}],218:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};b=a("../Subject");var k=a("../util/throwError"),m=a("../util/ObjectUnsubscribedError");
+a=function(a){function b(d){a.call(this);this._value=d;this._hasError=!1}h(b,a);b.prototype.getValue=function(){if(this._hasError)k.throwError(this._err);else if(this.isUnsubscribed)k.throwError(new m.ObjectUnsubscribedError);else return this._value};Object.defineProperty(b.prototype,"value",{get:function(){return this.getValue()},enumerable:!0,configurable:!0});b.prototype._subscribe=function(b){var c=a.prototype._subscribe.call(this,b);if(c)return c.isUnsubscribed||b.next(this._value),c};b.prototype._next=
+function(b){a.prototype._next.call(this,this._value=b)};b.prototype._error=function(b){this._hasError=!0;a.prototype._error.call(this,this._err=b)};return b}(b.Subject);e.BehaviorSubject=a},{"../Subject":6,"../util/ObjectUnsubscribedError":228,"../util/throwError":240}],219:[function(a,b,e){var h=this&&this.__extends||function(a,b){function d(){this.constructor=a}for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);a.prototype=null===b?Object.create(b):(d.prototype=b.prototype,new d)};b=a("../Subject");
+var k=a("../scheduler/queue");a=function(a){function b(d,c,f){void 0===d&&(d=Number.POSITIVE_INFINITY);void 0===c&&(c=Number.POSITIVE_INFINITY);a.call(this);this.events=[];this.bufferSize=1>d?1:d;this._windowTime=1>c?1:c;this.scheduler=f}h(b,a);b.prototype._next=function(b){var c=this._getNow();this.events.push(new m(c,b));this._trimBufferThenGetEvents(c);a.prototype._next.call(this,b)};b.prototype._subscribe=function(b){for(var c=this._trimBufferThenGetEvents(this._getNow()),f=-1,e=c.length;!b.isUnsubscribed&&
+++f<e;)b.next(c[f].value);return a.prototype._subscribe.call(this,b)};b.prototype._getNow=function(){return(this.scheduler||k.queue).now()};b.prototype._trimBufferThenGetEvents=function(a){for(var b=this.bufferSize,f=this._windowTime,e=this.events,h=e.length,k=0;k<h&&!(a-e[k].time<f);)k+=1;h>b&&(k=Math.max(k,h-b));0<k&&e.splice(0,k);return e};return b}(b.Subject);e.ReplaySubject=a;var m=function(){return function(a,b){this.time=a;this.value=b}}()},{"../Subject":6,"../scheduler/queue":216}],220:[function(a,
+b,e){var h=this&&this.__extends||function(a,b){function f(){this.constructor=a}for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);a.prototype=null===b?Object.create(b):(f.prototype=b.prototype,new f)};b=a("../Subscription");var k=a("../Subscriber");a=function(a){function b(f,d){a.call(this);this.subject=f;this.observer=d;this.isUnsubscribed=!1}h(b,a);b.prototype.unsubscribe=function(){if(!this.isUnsubscribed){this.isUnsubscribed=!0;var a=this.subject,b=a.observers;this.subject=void 0;b&&0!==b.length&&
+!a.isUnsubscribed&&(this.observer instanceof k.Subscriber&&this.observer.unsubscribe(),a=b.indexOf(this.observer),-1!==a&&b.splice(a,1))}};return b}(b.Subscription);e.SubjectSubscription=a},{"../Subscriber":7,"../Subscription":8}],221:[function(a,b,e){a=a("../util/SymbolShim");e.rxSubscriber=a.SymbolShim.for("rxSubscriber")},{"../util/SymbolShim":229}],222:[function(a,b,e){a=function(){return function(){this.name="ArgumentOutOfRangeError";this.message="argument out of range"}}();e.ArgumentOutOfRangeError=
+a},{}],223:[function(a,b,e){a=function(){return function(){this.name="EmptyError";this.message="no elements in sequence"}}();e.EmptyError=a},{}],224:[function(a,b,e){a=function(){function a(){this.values={}}a.prototype.delete=function(a){this.values[a]=null;return!0};a.prototype.set=function(a,b){this.values[a]=b;return this};a.prototype.get=function(a){return this.values[a]};a.prototype.forEach=function(a,b){var e=this.values,f;for(f in e)e.hasOwnProperty(f)&&null!==e[f]&&a.call(b,e[f],f)};a.prototype.clear=
+function(){this.values={}};return a}();e.FastMap=a},{}],225:[function(a,b,e){a=a("./root");b=function(){function a(b){this.root=b;b.setImmediate?(this.setImmediate=b.setImmediate,this.clearImmediate=b.clearImmediate):(this.nextHandle=1,this.tasksByHandle={},this.currentlyRunningATask=!1,this.canUseProcessNextTick()?this.setImmediate=this.createProcessNextTickSetImmediate():this.canUsePostMessage()?this.setImmediate=this.createPostMessageSetImmediate():this.canUseMessageChannel()?this.setImmediate=
+this.createMessageChannelSetImmediate():this.canUseReadyStateChange()?this.setImmediate=this.createReadyStateChangeSetImmediate():this.setImmediate=this.createSetTimeoutSetImmediate(),b=function l(a){delete l.instance.tasksByHandle[a]},b.instance=this,this.clearImmediate=b)}a.prototype.identify=function(a){return this.root.Object.prototype.toString.call(a)};a.prototype.canUseProcessNextTick=function(){return"[object process]"===this.identify(this.root.process)};a.prototype.canUseMessageChannel=function(){return Boolean(this.root.MessageChannel)};
+a.prototype.canUseReadyStateChange=function(){var a=this.root.document;return Boolean(a&&"onreadystatechange"in a.createElement("script"))};a.prototype.canUsePostMessage=function(){var a=this.root;if(a.postMessage&&!a.importScripts){var b=!0,e=a.onmessage;a.onmessage=function(){b=!1};a.postMessage("","*");a.onmessage=e;return b}return!1};a.prototype.partiallyApplied=function(a){for(var b=[],e=1;e<arguments.length;e++)b[e-1]=arguments[e];e=function d(){var a=d.handler,b=d.args;"function"===typeof a?
+a.apply(void 0,b):(new Function(""+a))()};e.handler=a;e.args=b;return e};a.prototype.addFromSetImmediateArguments=function(a){this.tasksByHandle[this.nextHandle]=this.partiallyApplied.apply(void 0,a);return this.nextHandle++};a.prototype.createProcessNextTickSetImmediate=function(){var a=function l(){var a=l.instance,b=a.addFromSetImmediateArguments(arguments);a.root.process.nextTick(a.partiallyApplied(a.runIfPresent,b));return b};a.instance=this;return a};a.prototype.createPostMessageSetImmediate=
+function(){var a=this.root,b="setImmediate$"+a.Math.random()+"$",e=function d(c){var e=d.instance;c.source===a&&"string"===typeof c.data&&0===c.data.indexOf(b)&&e.runIfPresent(+c.data.slice(b.length))};e.instance=this;a.addEventListener("message",e,!1);e=function c(){var a=c,b=a.messagePrefix,a=a.instance,e=a.addFromSetImmediateArguments(arguments);a.root.postMessage(b+e,"*");return e};e.instance=this;e.messagePrefix=b;return e};a.prototype.runIfPresent=function(a){if(this.currentlyRunningATask)this.root.setTimeout(this.partiallyApplied(this.runIfPresent,
+a),0);else{var b=this.tasksByHandle[a];if(b){this.currentlyRunningATask=!0;try{b()}finally{this.clearImmediate(a),this.currentlyRunningATask=!1}}}};a.prototype.createMessageChannelSetImmediate=function(){var a=this,b=new this.root.MessageChannel;b.port1.onmessage=function(b){a.runIfPresent(b.data)};var e=function d(){var a=d,b=a.channel,a=a.instance.addFromSetImmediateArguments(arguments);b.port2.postMessage(a);return a};e.channel=b;e.instance=this;return e};a.prototype.createReadyStateChangeSetImmediate=
+function(){var a=function l(){var a=l.instance,b=a.root.document,c=b.documentElement,e=a.addFromSetImmediateArguments(arguments),h=b.createElement("script");h.onreadystatechange=function(){a.runIfPresent(e);h.onreadystatechange=null;c.removeChild(h);h=null};c.appendChild(h);return e};a.instance=this;return a};a.prototype.createSetTimeoutSetImmediate=function(){var a=function l(){var a=l.instance,b=a.addFromSetImmediateArguments(arguments);a.root.setTimeout(a.partiallyApplied(a.runIfPresent,b),0);
+return b};a.instance=this;return a};return a}();e.ImmediateDefinition=b;e.Immediate=new b(a.root)},{"./root":238}],226:[function(a,b,e){b=a("./root");a=a("./MapPolyfill");e.Map=b.root.Map||a.MapPolyfill},{"./MapPolyfill":227,"./root":238}],227:[function(a,b,e){a=function(){function a(){this.size=0;this._values=[];this._keys=[]}a.prototype.get=function(a){a=this._keys.indexOf(a);return-1===a?void 0:this._values[a]};a.prototype.set=function(a,b){var e=this._keys.indexOf(a);-1===e?(this._keys.push(a),
+this._values.push(b),this.size++):this._values[e]=b;return this};a.prototype.delete=function(a){a=this._keys.indexOf(a);if(-1===a)return!1;this._values.splice(a,1);this._keys.splice(a,1);this.size--;return!0};a.prototype.forEach=function(a,b){for(var e=0;e<this.size;e++)a.call(b,this._values[e],this._keys[e])};return a}();e.MapPolyfill=a},{}],228:[function(a,b,e){var h=this&&this.__extends||function(a,b){function e(){this.constructor=a}for(var f in b)b.hasOwnProperty(f)&&(a[f]=b[f]);a.prototype=null===
+b?Object.create(b):(e.prototype=b.prototype,new e)};a=function(a){function b(){a.call(this,"object unsubscribed");this.name="ObjectUnsubscribedError"}h(b,a);return b}(Error);e.ObjectUnsubscribedError=a},{}],229:[function(a,b,e){function h(a){var b=m(a);f(b,a);d(b);k(b);return b}function k(a){a.for||(a.for=l)}function m(a){a.Symbol||(a.Symbol=function(a){return"@@Symbol("+a+"):"+c++});return a.Symbol}function l(a){return"@@"+a}function f(a,b){if(!a.iterator)if("function"===typeof a.for)a.iterator=
+a.for("iterator");else if(b.Set&&"function"===typeof(new b.Set)["@@iterator"])a.iterator="@@iterator";else if(b.Map)for(var c=Object.getOwnPropertyNames(b.Map.prototype),d=0;d<c.length;++d){var e=c[d];if("entries"!==e&&"size"!==e&&b.Map.prototype[e]===b.Map.prototype.entries){a.iterator=e;break}}else a.iterator="@@iterator"}function d(a){a.observable||(a.observable="function"===typeof a.for?a.for("observable"):"@@observable")}a=a("./root");e.polyfillSymbol=h;e.ensureFor=k;var c=0;e.ensureSymbol=m;
+e.symbolForPolyfill=l;e.ensureIterator=f;e.ensureObservable=d;e.SymbolShim=h(a.root)},{"./root":238}],230:[function(a,b,e){e.errorObject={e:{}}},{}],231:[function(a,b,e){e.isArray=Array.isArray||function(a){return a&&"number"===typeof a.length}},{}],232:[function(a,b,e){e.isDate=function(a){return a instanceof Date&&!isNaN(+a)}},{}],233:[function(a,b,e){var h=Array.isArray;e.isNumeric=function(a){return!h(a)&&0<=a-parseFloat(a)+1}},{}],234:[function(a,b,e){e.isPromise=function(a){return a&&"function"!==
+typeof a.subscribe&&"function"===typeof a.then}},{}],235:[function(a,b,e){e.isScheduler=function(a){return a&&"function"===typeof a.schedule}},{}],236:[function(a,b,e){e.noop=function(){}},{}],237:[function(a,b,e){e.not=function(a,b){function e(){return!e.pred.apply(e.thisArg,arguments)}e.pred=a;e.thisArg=b;return e}},{}],238:[function(a,b,e){a="undefined"!==typeof global?global:"undefined"!==typeof self?self:"undefined"!==typeof window?window:{};b={"boolean":!1,"function":!0,object:!0,number:!1,
+string:!1,undefined:!1};e.root=b[typeof self]&&self||b[typeof window]&&window;!(a=b[typeof a]&&a)||a.global!==a&&a.window!==a||(e.root=a)},{}],239:[function(a,b,e){var h=a("../Observable"),k=a("../util/SymbolShim"),m=a("../InnerSubscriber"),l=Array.isArray;e.subscribeToResult=function(a,b,c,e){var n=new m.InnerSubscriber(a,c,e);if(!n.isUnsubscribed){if(b instanceof h.Observable){if(b._isScalar){n.next(b.value);n.complete();return}return b.subscribe(n)}if(l(b)){a=0;for(c=b.length;a<c&&!n.isUnsubscribed;a++)n.next(b[a]);
+n.isUnsubscribed||n.complete()}else{if("function"===typeof b.then)return b.then(function(a){n.isUnsubscribed||(n.next(a),n.complete())},function(a){return n.error(a)}).then(null,function(a){setTimeout(function(){throw a;})}),n;if("function"===typeof b[k.SymbolShim.iterator]){for(a=0;a<b.length&&(n.next(b[a]),!n.isUnsubscribed);a++);n.isUnsubscribed||n.complete()}else if("function"===typeof b[k.SymbolShim.observable])if(b=b[k.SymbolShim.observable](),"function"!==typeof b.subscribe)n.error("invalid observable");
+else return b.subscribe(new m.InnerSubscriber(a,c,e));else n.error(new TypeError("unknown type returned"))}}}},{"../InnerSubscriber":1,"../Observable":3,"../util/SymbolShim":229}],240:[function(a,b,e){e.throwError=function(a){throw a;}},{}],241:[function(a,b,e){function h(){try{return m.apply(this,arguments)}catch(a){return k.errorObject.e=a,k.errorObject}}var k=a("./errorObject"),m;e.tryCatch=function(a){m=a;return h}},{"./errorObject":230}],242:[function(a,b,e){e.tryOrOnError=function(a){function b(){try{b.target.apply(this,
+arguments)}catch(a){this.error(a)}}b.target=a;return b}},{}]},{},[5])(5)});

File diff suppressed because it is too large
+ 0 - 0
blockly/accessible/libs/angular2-all.umd.min.js


File diff suppressed because it is too large
+ 0 - 0
blockly/accessible/libs/angular2-polyfills.min.js


File diff suppressed because it is too large
+ 9 - 0
blockly/accessible/libs/es6-shim.min.js


+ 68 - 0
blockly/accessible/messages.js

@@ -0,0 +1,68 @@
+/**
+ * @license
+ * Visual Blocks Language
+ *
+ * Copyright 2016 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 Accessible strings.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+'use strict';
+
+// The following are all Accessible Blockly strings.
+// None of the alert messages have periods on them. This is because the user
+// will have their punctuation setting set to 'all', which will result in any
+// punctuation being read out to them.
+Blockly.Msg.RUN_CODE = 'Run Code';
+Blockly.Msg.CLEAR_WORKSPACE = 'Clear Workspace';
+Blockly.Msg.BLOCK_ACTION_LIST = 'block action list';
+Blockly.Msg.CUT_BLOCK = 'cut block';
+Blockly.Msg.COPY_BLOCK = 'copy block';
+Blockly.Msg.PASTE_BELOW = 'paste below';
+Blockly.Msg.PASTE_ABOVE = 'paste above';
+Blockly.Msg.MARK_SPOT_BELOW = 'mark spot below';
+Blockly.Msg.MARK_SPOT_ABOVE = 'mark spot above';
+Blockly.Msg.MOVE_TO_MARKED_SPOT = 'move to marked spot';
+Blockly.Msg.DELETE = 'delete';
+Blockly.Msg.MARK_THIS_SPOT = 'mark this spot';
+Blockly.Msg.PASTE = 'paste';
+Blockly.Msg.TOOLBOX_LOAD_MSG = 'Loading Toolbox…';
+Blockly.Msg.WORKSPACE_LOAD_MSG = 'Loading Workspace…';
+Blockly.Msg.BLOCK_SUMMARY = 'block summary';
+Blockly.Msg.OPTION_LIST = 'option list';
+Blockly.Msg.ARGUMENT_OPTIONS_LIST = 'argument options list';
+Blockly.Msg.ARGUMENT_INPUT = 'argument input';
+Blockly.Msg.ARGUMENT_BLOCK_ACTION_LIST = 'argument block action list';
+Blockly.Msg.TEXT = 'text';
+Blockly.Msg.BUTTON = 'button';
+Blockly.Msg.UNAVAILABLE = 'unavailable';
+Blockly.Msg.CURRENT_ARGUMENT_VALUE = 'current argument value:';
+Blockly.Msg.COPY_TO_WORKSPACE = 'copy to workspace';
+Blockly.Msg.COPY_TO_CLIPBOARD = 'copy to clipboard';
+Blockly.Msg.COPY_TO_MARKED_SPOT = 'copy to marked spot';
+Blockly.Msg.TOOLBOX = 'Toolbox';
+Blockly.Msg.WORKSPACE = 'Workspace';
+Blockly.Msg.ANY = 'any';
+Blockly.Msg.STATEMENT = 'statement';
+Blockly.Msg.VALUE = 'value';
+Blockly.Msg.CUT_BLOCK_MSG = 'Cut block: ';
+Blockly.Msg.COPIED_BLOCK_MSG = 'Copied block to clipboard: ';
+Blockly.Msg.PASTED_BLOCK_FROM_CLIPBOARD_MSG = 'Pasted block from clipboard: ';
+Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG = 'Pasted block to marked spot: ';
+Blockly.Msg.MARKED_SPOT_MSG = 'Marked spot';
+Blockly.Msg.BLOCK_MOVED_TO_MARKED_SPOT_MSB = 'Block moved to marked spot: ';

+ 159 - 0
blockly/accessible/toolbox-tree.component.js

@@ -0,0 +1,159 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Component that details how blocks are
+ * rendered in the toolbox in AccessibleBlockly. Also handles any interactions
+ * with the blocks.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.ToolboxTreeComponent = ng.core
+  .Component({
+    selector: 'blockly-toolbox-tree',
+    template: `
+    <li #parentList [id]="idMap['parentList']" role="treeitem"
+        [ngClass]="{blocklyHasChildren: displayBlockMenu || block.inputList.length > 0, blocklyActiveDescendant: index == 0 && noCategories}"
+        [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-block-summary', idMap['blockSummaryLabel'])"
+        [attr.aria-selected]="index == 0 && tree.getAttribute('aria-activedescendant') == 'blockly-toolbox-tree-node0'"
+        [attr.aria-level]="level">
+      <label #blockSummaryLabel [id]="idMap['blockSummaryLabel']">{{block.toString()}}</label>
+      <ol role="group" *ngIf="displayBlockMenu || block.inputList.length > 0"
+          [attr.aria-level]="level+1">
+        <li #listItem class="blocklyHasChildren" [id]="idMap['listItem']"
+            [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-block-menu', idMap['blockSummaryLabel'])"
+            *ngIf="displayBlockMenu" role="treeitem"
+            aria-selected=false [attr.aria-level]="level+1">
+          <label #label [id]="idMap['label']">{{'BLOCK_ACTION_LIST'|translate}}</label>
+          <ol role="group" *ngIf="displayBlockMenu"  [attr.aria-level]="level+2">
+            <li #workspaceCopy [id]="idMap['workspaceCopy']" role="treeitem"
+                [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['workspaceCopyButton'], 'blockly-button')"
+                [attr.aria-level]="level+2" aria-selected=false>
+              <button #workspaceCopyButton [id]="idMap['workspaceCopyButton']"
+                      (click)="copyToWorkspace(block)">
+                {{'COPY_TO_WORKSPACE'|translate}}
+              </button>
+            </li>
+            <li #blockCopy [id]="idMap['blockCopy']" role="treeitem"
+                [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['blockCopyButton'], 'blockly-button')"
+                [attr.aria-level]="level+2" aria-selected=false>
+              <button #blockCopyButton [id]="idMap['blockCopyButton']"
+                      (click)="clipboardService.copy(block, true)">
+                {{'COPY_TO_CLIPBOARD'|translate}}
+              </button>
+            </li>
+            <li #sendToSelected [id]="idMap['sendToSelected']" role="treeitem"
+                [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['sendToSelectedButton'], 'blockly-button', !canBeCopiedToMarkedConnection(block))"
+                [attr.aria-level]="level+2" aria-selected=false>
+              <button #sendToSelectedButton
+                      [id]="idMap['sendToSelectedButton']"
+                      (click)="copyToMarked(block)"
+                      [disabled]="!canBeCopiedToMarkedConnection(block)">
+                {{'COPY_TO_MARKED_SPOT'|translate}}
+              </button>
+            </li>
+          </ol>
+        </li>
+        <div *ngFor="#inputBlock of block.inputList; #i=index">
+          <blockly-field *ngFor="#field of inputBlock.fieldRow; #j=index"
+                      [attr.aria-level]="level+1" [field]="field"
+                      [level]="level+1">
+          </blockly-field>
+          <blockly-toolbox-tree *ngIf="inputBlock.connection && inputBlock.connection.targetBlock()"
+                                [block]="inputBlock.connection.targetBlock()"
+                                [displayBlockMenu]="false"
+                                [level]="level+1">
+          </blockly-toolbox-tree>
+          <li #listItem1 [id]="idMap['listItem' + i]" role="treeitem"
+              *ngIf="inputBlock.connection && !inputBlock.connection.targetBlock()"
+              [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-argument-text', idMap['listItem' + i + 'Label'])"
+              [attr.aria-level]="level+1" aria-selected=false>
+            <!--TODO(madeeha): i18n here will need to happen in a different way due to the way grammar changes based on language.-->
+            <label #label [id]="idMap['listItem' + i + 'Label']">
+              {{utilsService.getInputTypeLabel(inputBlock.connection)}}
+              {{utilsService.getBlockTypeLabel(inputBlock)}} needed:
+            </label>
+          </li>
+        </div>
+      </ol>
+    </li>
+    <blockly-toolbox-tree *ngIf= "block.nextConnection && block.nextConnection.targetBlock()"
+                          [level]="level"
+                          [block]="block.nextConnection.targetBlock()"
+                          [displayBlockMenu]="false">
+    </blockly-toolbox-tree>
+    `,
+    directives: [blocklyApp.FieldComponent, ng.core.forwardRef(function() {
+      return blocklyApp.ToolboxTreeComponent;
+    })],
+    inputs: [
+        'block', 'displayBlockMenu', 'level', 'index', 'tree', 'noCategories', 'isTopLevel'],
+    pipes: [blocklyApp.TranslatePipe]
+  })
+  .Class({
+    constructor: [
+        blocklyApp.ClipboardService, blocklyApp.TreeService, blocklyApp.UtilsService,
+        function(_clipboardService, _treeService, _utilsService) {
+      // ClipboardService and UtilsService are app-wide singleton services.
+      // TreeService is from the parent ToolboxComponent.
+      this.infoBlocks = Object.create(null);
+      this.clipboardService = _clipboardService;
+      this.treeService = _treeService;
+      this.utilsService = _utilsService;
+    }],
+    ngOnInit: function() {
+      var elementsNeedingIds = ['blockSummaryLabel'];
+      if (this.displayBlockMenu || this.block.inputList.length){
+        elementsNeedingIds = elementsNeedingIds.concat(['listItem', 'label',
+            'workspaceCopy', 'workspaceCopyButton', 'blockCopy',
+            'blockCopyButton', 'sendToSelected', 'sendToSelectedButton']);
+      }
+      for (var i = 0; i < this.block.inputList.length; i++){
+        elementsNeedingIds.push('listItem' + i, 'listItem' + i + 'Label')
+      }
+      this.idMap = this.utilsService.generateIds(elementsNeedingIds);
+      if (this.isTopLevel) {
+        this.idMap['parentList'] = 'blockly-toolbox-tree-node0';
+      } else {
+        this.idMap['parentList'] = this.utilsService.generateUniqueId();
+      }
+    },
+    generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
+      return this.utilsService.generateAriaLabelledByAttr(
+          mainLabel, secondLabel, isDisabled);
+    },
+    canBeCopiedToMarkedConnection: function(block) {
+      return this.clipboardService.canBeCopiedToMarkedConnection(block);
+    },
+    copyToWorkspace: function(block) {
+      var xml = Blockly.Xml.blockToDom(block);
+      Blockly.Xml.domToBlock(blocklyApp.workspace, xml);
+      alert('Added block to workspace: ' + block.toString());
+    },
+    copyToClipboard: function(block) {
+      if (this.clipboardService) {
+        this.clipboardService.copy(block, true);
+      }
+    },
+    copyToMarked: function(block) {
+      if (this.clipboardService) {
+        this.clipboardService.pasteToMarkedConnection(block);
+      }
+    }
+  });

+ 135 - 0
blockly/accessible/toolbox.component.js

@@ -0,0 +1,135 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Component that details how a toolbox is rendered
+ * in AccessibleBlockly. Also handles any interactions with the toolbox.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.ToolboxComponent = ng.core
+  .Component({
+    selector: 'blockly-toolbox',
+    template: `
+      <h3 #toolboxTitle id="blockly-toolbox-title">Toolbox</h3>
+      <ol #tree
+          id="blockly-toolbox-tree" role="group" class="blocklyTree"
+          *ngIf="toolboxCategories && toolboxCategories.length > 0" tabIndex="0"
+          [attr.aria-labelledby]="toolboxTitle.id"
+          [attr.aria-activedescendant]="tree.getAttribute('aria-activedescendant') || tree.id + '-node0' "
+          (keydown)="treeService.onKeypress($event, tree)">
+        <template [ngIf]="xmlHasCategories">
+          <li #parent
+              [id]="idMap['Parent' + i]" role="treeitem"
+              [ngClass]="{blocklyHasChildren: true, blocklyActiveDescendant: tree.getAttribute('aria-activedescendant') == idMap['Parent' + i]}"
+              *ngFor="#category of toolboxCategories; #i=index"
+              aria-level="1" aria-selected=false
+              [attr.aria-label]="category.attributes.name.value">
+            <div *ngIf="category && category.attributes">
+              <label [id]="idMap['Label' + i]" #name>
+                {{category.attributes.name.value}}
+              </label>
+              <ol role="group" *ngIf="getToolboxWorkspace(category).topBlocks_.length > 0">
+                <blockly-toolbox-tree *ngFor="#block of getToolboxWorkspace(category).topBlocks_"
+                                      [level]=2 [block]="block"
+                                      [displayBlockMenu]="true"
+                                      [tree]="tree">
+                </blockly-toolbox-tree>
+              </ol>
+            </div>
+          </li>
+        </template>
+        <div *ngIf="!xmlHasCategories">
+          <blockly-toolbox-tree *ngFor="#block of getToolboxWorkspace(toolboxCategories[0]).topBlocks_; #i=index"
+                                [level]=1 [block]="block"
+                                [displayBlockMenu]="true"
+                                [index]="i" [tree]="tree"
+                                [noCategories]="true"
+                                [isTopLevel]="true">
+          </blockly-toolbox-tree>
+        </div>
+      </ol>
+    `,
+    directives: [blocklyApp.ToolboxTreeComponent]
+  })
+  .Class({
+    constructor: [
+        blocklyApp.TreeService, blocklyApp.UtilsService,
+        function(_treeService, _utilsService) {
+      this.toolboxCategories = [];
+      this.toolboxWorkspaces = Object.create(null);
+      this.treeService = _treeService;
+      this.utilsService = _utilsService;
+
+      this.xmlHasCategories = false;
+    }],
+    ngOnInit: function() {
+      // Note that sometimes the toolbox may not have categories; it may
+      // display individual blocks directly (which is often the case in,
+      // e.g., Blockly Games).
+      var xmlToolboxElt = document.getElementById('blockly-toolbox-xml');
+      var xmlCategoryElts = xmlToolboxElt.getElementsByTagName('category');
+      if (xmlCategoryElts.length) {
+        this.xmlHasCategories = true;
+        this.toolboxCategories = Array.from(xmlCategoryElts);
+
+        var elementsNeedingIds = [];
+        for (var i = 0; i < this.toolboxCategories.length; i++) {
+          elementsNeedingIds.push('Parent' + i, 'Label' + i);
+        }
+        this.idMap = this.utilsService.generateIds(elementsNeedingIds);
+        for (var i = 0; i < this.toolboxCategories.length; i++) {
+          this.idMap['Parent' + i] = 'blockly-toolbox-tree-node' + i;
+        }
+      } else {
+        // Create a single category with all the top-level blocks.
+        this.xmlHasCategories = false;
+        this.toolboxCategories = [Array.from(xmlToolboxElt.children)];
+      }
+    },
+    ngAfterViewInit: function() {
+      // If this is a top-level tree in the toolbox, set its active
+      // descendant after the ids have been computed.
+      if (this.xmlHasCategories) {
+        this.treeService.setActiveDesc(
+            'blockly-toolbox-tree-node0', 'blockly-toolbox-tree');
+      }
+    },
+    getToolboxWorkspace: function(categoryNode) {
+      if (categoryNode.attributes && categoryNode.attributes.name) {
+        var categoryName = categoryNode.attributes.name.value;
+      } else {
+        var categoryName = 'no-category';
+      }
+      if (this.toolboxWorkspaces[categoryName]) {
+        return this.toolboxWorkspaces[categoryName];
+      } else {
+        var categoryWorkspace = new Blockly.Workspace();
+        if (categoryName == 'no-category') {
+          for (var i = 0; i < categoryNode.length; i++) {
+            Blockly.Xml.domToBlock(categoryWorkspace, categoryNode[i]);
+          }
+        } else {
+          Blockly.Xml.domToWorkspace(categoryNode, categoryWorkspace);
+        }
+        this.toolboxWorkspaces[categoryName] = categoryWorkspace;
+        return this.toolboxWorkspaces[categoryName];
+      }
+    }
+  });

+ 34 - 0
blockly/accessible/translate.pipe.js

@@ -0,0 +1,34 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Pipe for internationalizing Blockly message strings.
+ * @author sll@google.com (Sean Lip)
+ */
+
+blocklyApp.TranslatePipe = ng.core
+  .Pipe({
+    name: 'translate'
+  })
+  .Class({
+    constructor: function() {},
+    transform: function(messageId) {
+      return Blockly.Msg[messageId];
+    }
+  });

+ 357 - 0
blockly/accessible/tree.service.js

@@ -0,0 +1,357 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Service that handles tree keyboard navigation.
+ * This is a singleton service for the entire application.
+ *
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.TreeService = ng.core
+  .Class({
+    constructor: function() {
+      // Stores active descendant ids for each tree in the page.
+      this.activeDescendantIds_ = {};
+    },
+    getToolboxTreeNode_: function() {
+      return document.getElementById('blockly-toolbox-tree');
+    },
+    getWorkspaceToolbarButtonNodes_: function() {
+      return Array.from(document.querySelectorAll(
+          'button.blocklyWorkspaceToolbarButton'));
+    },
+    // Returns a list of all top-level workspace tree nodes on the page.
+    getWorkspaceTreeNodes_: function() {
+      return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree'));
+    },
+    // Returns a list of all top-level tree nodes on the page.
+    getAllTreeNodes_: function() {
+      var treeNodes = [this.getToolboxTreeNode_()];
+      treeNodes = treeNodes.concat(this.getWorkspaceToolbarButtonNodes_());
+      treeNodes = treeNodes.concat(this.getWorkspaceTreeNodes_());
+      return treeNodes;
+    },
+    isTopLevelWorkspaceTree: function(treeId) {
+      return this.getWorkspaceTreeNodes_().some(function(tree) {
+        return tree.id == treeId;
+      });
+    },
+    getNodeToFocusOnWhenTreeIsDeleted: function(deletedTreeId) {
+      // This returns the node to focus on after the deletion happens.
+      // We shift focus to the next tree (if it exists), otherwise we shift
+      // focus to the previous tree.
+      var trees = this.getAllTreeNodes_();
+      for (var i = 0; i < trees.length; i++) {
+        if (trees[i].id == deletedTreeId) {
+          if (i + 1 < trees.length) {
+            return trees[i + 1];
+          } else if (i > 0) {
+            return trees[i - 1];
+          }
+        }
+      }
+
+      return this.getToolboxTreeNode_();
+    },
+    focusOnCurrentTree_: function(treeId) {
+      var trees = this.getAllTreeNodes_();
+      for (var i = 0; i < trees.length; i++) {
+        if (trees[i].id == treeId) {
+          trees[i].focus();
+          return true;
+        }
+      }
+      return false;
+    },
+    focusOnNextTree_: function(treeId) {
+      var trees = this.getAllTreeNodes_();
+      for (var i = 0; i < trees.length - 1; i++) {
+        if (trees[i].id == treeId) {
+          trees[i + 1].focus();
+          return true;
+        }
+      }
+      return false;
+    },
+    focusOnPreviousTree_: function(treeId) {
+      var trees = this.getAllTreeNodes_();
+      for (var i = trees.length - 1; i > 0; i--) {
+        if (trees[i].id == treeId) {
+          trees[i - 1].focus();
+          return true;
+        }
+      }
+      return false;
+    },
+    getActiveDescId: function(treeId) {
+      return this.activeDescendantIds_[treeId] || '';
+    },
+    unmarkActiveDesc_: function(activeDescId) {
+      var activeDesc = document.getElementById(activeDescId);
+      if (activeDesc) {
+        activeDesc.classList.remove('blocklyActiveDescendant');
+        activeDesc.setAttribute('aria-selected', 'false');
+      }
+    },
+    markActiveDesc_: function(activeDescId) {
+      var newActiveDesc = document.getElementById(activeDescId);
+      newActiveDesc.classList.add('blocklyActiveDescendant');
+      newActiveDesc.setAttribute('aria-selected', 'true');
+    },
+    // Runs the given function while preserving the focus and active descendant
+    // for the given tree.
+    runWhilePreservingFocus: function(func, treeId, optionalNewActiveDescId) {
+      var oldDescId = this.getActiveDescId(treeId);
+      var newDescId = optionalNewActiveDescId || oldDescId;
+      this.unmarkActiveDesc_(oldDescId);
+      func();
+
+      // The timeout is needed in order to give the DOM time to stabilize
+      // before setting the new active descendant, especially in cases like
+      // pasteAbove().
+      var that = this;
+      setTimeout(function() {
+        that.markActiveDesc_(newDescId);
+        that.activeDescendantIds_[treeId] = newDescId;
+        document.getElementById(treeId).focus();
+      }, 0);
+    },
+    // Make a given node the active descendant of a given tree.
+    setActiveDesc: function(newActiveDescId, treeId) {
+      this.unmarkActiveDesc_(this.getActiveDescId(treeId));
+      this.markActiveDesc_(newActiveDescId);
+      this.activeDescendantIds_[treeId] = newActiveDescId;
+    },
+    onWorkspaceToolbarKeypress: function(e, treeId) {
+      if (e.keyCode == 9) {
+        // Tab key.
+        if (e.shiftKey) {
+          this.focusOnPreviousTree_(treeId);
+        } else {
+          this.focusOnNextTree_(treeId);
+        }
+        e.preventDefault();
+        e.stopPropagation();
+      }
+    },
+    isButtonOrFieldNode_: function(node) {
+      return ['BUTTON', 'INPUT'].indexOf(node.tagName) != -1;
+    },
+    getNextActiveDescWhenBlockIsDeleted: function(blockRootNode) {
+      // Go up a level, if possible.
+      var nextNode = blockRootNode.parentNode;
+      while (nextNode && nextNode.tagName != 'LI') {
+        nextNode = nextNode.parentNode;
+      }
+      if (nextNode) {
+        return nextNode;
+      }
+
+      // Otherwise, go to the next sibling.
+      var nextSibling = this.getNextSibling(blockRootNode);
+      if (nextSibling) {
+        return nextSibling;
+      }
+
+      // Otherwise, go to the previous sibling.
+      var previousSibling = this.getPreviousSibling(blockRootNode);
+      if (previousSibling) {
+        return previousSibling;
+      }
+
+      // Otherwise, this is a top-level isolated block, which means that
+      // something's gone wrong and this function should not have been called
+      // in the first place.
+      console.error('Could not handle deletion of block.' + blockRootNode);
+    },
+    onKeypress: function(e, tree) {
+      var treeId = tree.id;
+      var activeDesc = document.getElementById(this.getActiveDescId(treeId));
+      if (!activeDesc) {
+        console.error('ERROR: no active descendant for current tree.');
+
+        // TODO(sll): Generalize this to other trees (outside the workspace).
+        var workspaceTreeNodes = this.getWorkspaceTreeNodes_();
+        for (var i = 0; i < workspaceTreeNodes.length; i++) {
+          if (workspaceTreeNodes[i].id == treeId) {
+            // Set the active desc to the first child in this tree.
+            this.setActiveDesc(
+                this.getFirstChild(workspaceTreeNodes[i]).id, treeId);
+            break;
+          }
+        }
+        return;
+      }
+
+      var isFocusingIntoField = false;
+
+      if (e.keyCode == 13) {
+        // Enter key. The user wants to interact with a child.
+        if (activeDesc.children.length == 1) {
+          var child = activeDesc.children[0];
+          if (child.tagName == 'BUTTON') {
+            child.click();
+            this.isFocusingIntoField = true;
+          } else if (child.tagName == 'INPUT') {
+            child.focus();
+          }
+        }
+      } else if (e.keyCode == 9) {
+        // Tab key.
+        if (e.shiftKey) {
+          this.focusOnPreviousTree_(treeId);
+        } else {
+          this.focusOnNextTree_(treeId);
+        }
+        e.preventDefault();
+        e.stopPropagation();
+      } else if (e.keyCode >= 37 && e.keyCode <= 40) {
+        // Arrow keys.
+        if (e.keyCode == 37) {
+          // Left arrow key. Go up a level, if possible.
+          var nextNode = activeDesc.parentNode;
+          if (this.isButtonOrFieldNode_(activeDesc)) {
+            nextNode = nextNode.parentNode;
+          }
+          while (nextNode && nextNode.tagName != 'LI') {
+            nextNode = nextNode.parentNode;
+          }
+          if (nextNode) {
+            this.setActiveDesc(nextNode.id, treeId);
+          }
+        } else if (e.keyCode == 38) {
+          // Up arrow key. Go to the previous sibling, if possible.
+          var prevSibling = this.getPreviousSibling(activeDesc);
+          if (prevSibling) {
+            this.setActiveDesc(prevSibling.id, treeId);
+          }
+        } else if (e.keyCode == 39) {
+          // Right arrow key. Go down a level, if possible.
+          var firstChild = this.getFirstChild(activeDesc);
+          if (firstChild) {
+            this.setActiveDesc(firstChild.id, treeId);
+          }
+        } else if (e.keyCode == 40) {
+          // Down arrow key. Go to the next sibling, if possible.
+          var nextSibling = this.getNextSibling(activeDesc);
+          if (nextSibling) {
+            this.setActiveDesc(nextSibling.id, treeId);
+          }
+        }
+
+        e.preventDefault();
+        e.stopPropagation();
+      }
+    },
+    getFirstChild: function(element) {
+      if (!element) {
+        return element;
+      } else {
+        var childList = element.children;
+        for (var i = 0; i < childList.length; i++) {
+          if (childList[i].tagName == 'LI') {
+            return childList[i];
+          } else {
+            var potentialElement = this.getFirstChild(childList[i]);
+            if (potentialElement) {
+              return potentialElement;
+            }
+          }
+        }
+        return null;
+      }
+    },
+    getNextSibling: function(element) {
+      if (element.nextElementSibling) {
+        // If there is a sibling, find the list element child of the sibling.
+        var node = element.nextElementSibling;
+        if (node.tagName == 'LI') {
+          return node;
+        } else {
+          // getElementsByTagName returns in DFS order, therefore the first
+          // element is the first relevant list child.
+          return node.getElementsByTagName('li')[0];
+        }
+      } else {
+        var parent = element.parentNode;
+        while (parent && parent.tagName != 'OL') {
+          if (parent.nextElementSibling) {
+            var node = parent.nextElementSibling;
+            if (node.tagName == 'LI') {
+              return node;
+            } else {
+              return this.getFirstChild(node);
+            }
+          } else {
+            parent = parent.parentNode;
+          }
+        }
+        return null;
+      }
+    },
+    getPreviousSibling: function(element) {
+      if (element.previousElementSibling) {
+        var sibling = element.previousElementSibling;
+        if (sibling.tagName == 'LI') {
+          return sibling;
+        } else {
+          return this.getLastChild(sibling);
+        }
+      } else {
+        var parent = element.parentNode;
+        while (parent) {
+          if (parent.tagName == 'OL') {
+            break;
+          }
+          if (parent.previousElementSibling) {
+            var node = parent.previousElementSibling;
+            if (node.tagName == 'LI') {
+              return node;
+            } else {
+              // Find the last list element child of the sibling of the parent.
+              return this.getLastChild(node);
+            }
+          } else {
+            parent = parent.parentNode;
+          }
+        }
+        return null;
+      }
+    },
+    getLastChild: function(element) {
+      if (!element) {
+        return element;
+      } else {
+        var childList = element.children;
+        for (var i = childList.length - 1; i >= 0; i--) {
+          // Find the last child that is a list element.
+          if (childList[i].tagName == 'LI') {
+            return childList[i];
+          } else {
+            var potentialElement = this.getLastChild(childList[i]);
+            if (potentialElement) {
+              return potentialElement;
+            }
+          }
+        }
+        return null;
+      }
+    }
+});

+ 69 - 0
blockly/accessible/utils.service.js

@@ -0,0 +1,69 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 utility service for multiple components. All
+ * functions in this service should be stateless, since this is a singleton
+ * service that is used for the entire application.
+ *
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+var blocklyApp = {};
+
+blocklyApp.UtilsService = ng.core
+  .Class({
+    constructor: function() {},
+    generateUniqueId: function() {
+      return 'blockly-' + Blockly.genUid();
+    },
+    generateIds: function(elementsList) {
+      var idMap = {};
+      for (var i = 0; i < elementsList.length; i++){
+        idMap[elementsList[i]] = this.generateUniqueId();
+      }
+      return idMap;
+    },
+    generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
+      var attrValue = mainLabel + ' ' + secondLabel;
+      if (isDisabled) {
+        attrValue += ' blockly-disabled';
+      }
+      return attrValue;
+    },
+    getInputTypeLabel: function(connection) {
+      // Returns an upper case string in the case of official input type names.
+      // Returns the lower case string 'any' if any official input type qualifies.
+      // The differentiation between upper and lower case signifies the difference
+      // between an input type (BOOLEAN, LIST, etc) and the colloquial english term
+      // 'any'.
+      if (connection.check_) {
+        return connection.check_.join(', ').toUpperCase();
+      } else {
+        return Blockly.Msg.ANY;
+      }
+    },
+    getBlockTypeLabel: function(inputBlock) {
+      if (inputBlock.type == Blockly.NEXT_STATEMENT) {
+        return Blockly.Msg.STATEMENT;
+      } else {
+        return Blockly.Msg.VALUE;
+      }
+    }
+  });

+ 285 - 0
blockly/accessible/workspace-tree.component.js

@@ -0,0 +1,285 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Component that details how Blockly.Block's are
+ * rendered in the workspace in AccessibleBlockly. Also handles any
+ * interactions with the blocks.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.WorkspaceTreeComponent = ng.core
+  .Component({
+    selector: 'blockly-workspace-tree',
+    template: `
+    <li [id]="idMap['blockRoot']" role="treeitem" class="blocklyHasChildren"
+        [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-block-summary', idMap['blockSummary'])"
+        [attr.aria-level]="level" aria-selected="false">
+      <label [id]="idMap['blockSummary']">{{block.toString()}}</label>
+
+      <ol role="group"  [attr.aria-level]="level + 1">
+        <li [id]="idMap['listItem']" class="blocklyHasChildren" role="treeitem"
+            [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-block-menu', idMap['blockSummary'])"
+            [attr.aria-level]="level+1" aria-selected="false">
+          <label [id]="idMap['label']">{{'BLOCK_ACTION_LIST'|translate}}</label>
+          <ol role="group"  [attr.aria-level]="level + 2">
+            <li *ngFor="#buttonInfo of actionButtonsInfo"
+                [id]="idMap[buttonInfo.baseIdKey]" role="treeitem"
+                [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap[buttonInfo.baseIdKey + 'Button'], 'blockly-button', buttonInfo.isDisabled())"
+                [attr.aria-level]="level + 2" aria-selected="false">
+              <button [id]="idMap[buttonInfo.baseIdKey + 'Button']" (click)="buttonInfo.action()"
+                      [disabled]="buttonInfo.isDisabled()">
+                {{buttonInfo.translationIdForText|translate}}
+              </button>
+            </li>
+          </ol>
+        </li>
+
+        <div *ngFor="#inputBlock of block.inputList; #i = index">
+          <blockly-field *ngFor="#field of inputBlock.fieldRow" [field]="field"></blockly-field>
+          <blockly-workspace-tree *ngIf="inputBlock.connection && inputBlock.connection.targetBlock()"
+                                  [block]="inputBlock.connection.targetBlock()" [level]="level"
+                                  [tree]="tree">
+          </blockly-workspace-tree>
+          <li #inputList [attr.aria-level]="level + 1" [id]="idMap['inputList' + i]"
+              [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-menu', idMap['inputMenuLabel' + i])"
+              *ngIf="inputBlock.connection && !inputBlock.connection.targetBlock()" (keydown)="treeService.onKeypress($event, tree)">
+            <!-- TODO(madeeha): i18n here will need to happen in a different way due to the way grammar changes based on language. -->
+            <label [id]="idMap['inputMenuLabel' + i]"> {{utilsService.getInputTypeLabel(inputBlock.connection)}} {{utilsService.getBlockTypeLabel(inputBlock)}} needed: </label>
+            <ol role="group"  [attr.aria-level]="level + 2">
+              <li [id]="idMap['markSpot' + i]" role="treeitem"
+                  [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['markButton' + i], 'blockly-button')"
+                  [attr.aria-level]="level + 2" aria-selected=false>
+                <button [id]="idMap['markSpotButton + i']" (click)="clipboardService.markConnection(inputBlock.connection)">{{'MARK_THIS_SPOT'|translate}}</button>
+              </li>
+              <li [id]="idMap['paste' + i]" role="treeitem"
+                  [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['pasteButton' + i], 'blockly-button', !isCompatibleWithClipboard(inputBlock.connection))"
+                  [attr.aria-level]="level+2" aria-selected=false>
+                <button [id]="idMap['pasteButton' + i]" (click)="clipboardService.pasteFromClipboard(inputBlock.connection)"
+                        [disabled]="!isCompatibleWithClipboard(inputBlock.connection)">
+                  {{'PASTE'|translate}}
+                </button>
+              </li>
+            </ol>
+          </li>
+        </div>
+      </ol>
+    </li>
+
+    <blockly-workspace-tree *ngIf= "block.nextConnection && block.nextConnection.targetBlock()"
+                            [block]="block.nextConnection.targetBlock()"
+                            [level]="level" [tree]="tree">
+    </blockly-workspace-tree>
+    `,
+    directives: [blocklyApp.FieldComponent, ng.core.forwardRef(function() {
+      return blocklyApp.WorkspaceTreeComponent;
+    })],
+    inputs: ['block', 'level', 'tree', 'isTopLevel'],
+    pipes: [blocklyApp.TranslatePipe]
+  })
+  .Class({
+    constructor: [
+        blocklyApp.ClipboardService, blocklyApp.TreeService,
+        blocklyApp.UtilsService,
+        function(_clipboardService, _treeService, _utilsService) {
+      this.infoBlocks = Object.create(null);
+      this.clipboardService = _clipboardService;
+      this.treeService = _treeService;
+      this.utilsService = _utilsService;
+    }],
+    isIsolatedTopLevelBlock_: function(block) {
+      // Returns whether the given block is at the top level, and has no
+      // siblings.
+      return Boolean(
+          !block.nextConnection.targetConnection &&
+          !block.previousConnection.targetConnection &&
+          blocklyApp.workspace.topBlocks_.some(function(topBlock) {
+            return topBlock.id == block.id;
+          }));
+    },
+    removeBlockAndSetFocus_: function(block, deleteBlockFunc) {
+      // This method runs the given function and then does one of two things:
+      // - If the block is an isolated top-level block, it shifts the tree
+      //   focus.
+      // - Otherwise, it sets the correct new active desc for the current tree.
+      if (this.isIsolatedTopLevelBlock_(block)) {
+        var nextNodeToFocusOn =
+            this.treeService.getNodeToFocusOnWhenTreeIsDeleted(this.tree.id);
+        deleteBlockFunc();
+        nextNodeToFocusOn.focus();
+      } else {
+        var blockRootNode = document.getElementById(this.idMap['blockRoot']);
+        var nextActiveDesc =
+            this.treeService.getNextActiveDescWhenBlockIsDeleted(
+                blockRootNode);
+        this.treeService.runWhilePreservingFocus(
+            deleteBlockFunc, this.tree.id, nextActiveDesc.id);
+      }
+    },
+    cutBlock_: function() {
+      var that = this;
+      this.removeBlockAndSetFocus_(this.block, function() {
+        that.clipboardService.cut(that.block);
+      });
+    },
+    deleteBlock_: function() {
+      var that = this;
+      this.removeBlockAndSetFocus_(this.block, function() {
+        that.block.dispose(true);
+      });
+    },
+    pasteToConnection_: function(connection) {
+      var that = this;
+      this.treeService.runWhilePreservingFocus(function() {
+        // If the connection is a 'previousConnection' and that connection is
+        // already joined to something, use the 'nextConnection' of the
+        // previous block instead in order to do an insertion.
+        if (connection.type == Blockly.PREVIOUS_STATEMENT &&
+            connection.isConnected()) {
+          that.clipboardService.pasteFromClipboard(
+              connection.targetConnection);
+        } else {
+          that.clipboardService.pasteFromClipboard(connection);
+        }
+      }, this.tree.id);
+    },
+    sendToMarkedSpot_: function() {
+      this.clipboardService.pasteToMarkedConnection(this.block, false);
+
+      var that = this;
+      this.removeBlockAndSetFocus_(this.block, function() {
+        that.block.dispose(true);
+      });
+
+      alert('Block moved to marked spot: ' + this.block.toString());
+    },
+    ngOnInit: function() {
+      var that = this;
+
+      // Generate a list of action buttons.
+      this.actionButtonsInfo = [{
+        baseIdKey: 'cut',
+        translationIdForText: 'CUT_BLOCK',
+        action: that.cutBlock_.bind(that),
+        isDisabled: function() {
+          return false;
+        }
+      }, {
+        baseIdKey: 'copy',
+        translationIdForText: 'COPY_BLOCK',
+        action: that.clipboardService.copy.bind(
+            that.clipboardService, that.block, true),
+        isDisabled: function() {
+          return false;
+        }
+      }, {
+        baseIdKey: 'pasteBelow',
+        translationIdForText: 'PASTE_BELOW',
+        action: that.pasteToConnection_.bind(that, that.block.nextConnection),
+        isDisabled: function() {
+          return Boolean(
+              !that.block.nextConnection ||
+              !that.isCompatibleWithClipboard(that.block.nextConnection));
+        }
+      }, {
+        baseIdKey: 'pasteAbove',
+        translationIdForText: 'PASTE_ABOVE',
+        action: that.pasteToConnection_.bind(
+            that, that.block.previousConnection),
+        isDisabled: function() {
+          return Boolean(
+              !that.block.previousConnection ||
+              !that.isCompatibleWithClipboard(that.block.previousConnection));
+        }
+      }, {
+        baseIdKey: 'markBelow',
+        translationIdForText: 'MARK_SPOT_BELOW',
+        action: that.clipboardService.markConnection.bind(
+            that.clipboardService, that.block.nextConnection),
+        isDisabled: function() {
+          return !that.block.nextConnection;
+        }
+      }, {
+        baseIdKey: 'markAbove',
+        translationIdForText: 'MARK_SPOT_ABOVE',
+        action: that.clipboardService.markConnection.bind(
+            that.clipboardService, that.block.previousConnection),
+        isDisabled: function() {
+          return !that.block.previousConnection;
+        }
+      }, {
+        baseIdKey: 'sendToMarkedSpot',
+        translationIdForText: 'MOVE_TO_MARKED_SPOT',
+        action: that.sendToMarkedSpot_.bind(that),
+        isDisabled: function() {
+          return !that.clipboardService.isMovableToMarkedConnection(
+              that.block);
+        }
+      }, {
+        baseIdKey: 'delete',
+        translationIdForText: 'DELETE',
+        action: that.deleteBlock_.bind(that),
+        isDisabled: function() {
+          return false;
+        }
+      }];
+
+      // Make a list of all the id keys.
+      this.idKeys = ['blockRoot', 'blockSummary', 'listItem', 'label'];
+      this.actionButtonsInfo.forEach(function(buttonInfo) {
+        that.idKeys.push(buttonInfo.baseIdKey, buttonInfo.baseIdKey + 'Button');
+      });
+      for (var i = 0; i < this.block.inputList.length; i++) {
+        var inputBlock = this.block.inputList[i];
+        if (inputBlock.connection && !inputBlock.connection.targetBlock()) {
+          that.idKeys.push(
+              'inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i,
+              'markSpotButton' + i, 'paste' + i, 'pasteButton' + i);
+        }
+      }
+    },
+    ngDoCheck: function() {
+      // Generate a unique id for each id key. This needs to be done every time
+      // changes happen, but after the first ng-init, in order to force the
+      // element ids to change in cases where, e.g., a block is inserted in the
+      // middle of a sequence of blocks.
+      this.idMap = {};
+      for (var i = 0; i < this.idKeys.length; i++) {
+        this.idMap[this.idKeys[i]] = this.block.id + this.idKeys[i];
+      }
+    },
+    ngAfterViewInit: function() {
+      // If this is a top-level tree in the workspace, set its id and active
+      // descendant.
+      if (this.tree && this.isTopLevel && !this.tree.id) {
+        this.tree.id = this.utilsService.generateUniqueId();
+      }
+      if (this.tree && this.isTopLevel &&
+          !this.treeService.getActiveDescId(this.tree.id)) {
+        this.treeService.setActiveDesc(this.idMap['blockRoot'], this.tree.id);
+      }
+    },
+    generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
+      return this.utilsService.generateAriaLabelledByAttr(
+          mainLabel, secondLabel, isDisabled);
+    },
+    isCompatibleWithClipboard: function(connection) {
+      return this.clipboardService.isCompatibleWithClipboard(connection);
+    }
+  });

+ 91 - 0
blockly/accessible/workspace.component.js

@@ -0,0 +1,91 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 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 Angular2 Component that details how a Blockly.Workspace is
+ * rendered in AccessibleBlockly.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.WorkspaceComponent = ng.core
+  .Component({
+    selector: 'blockly-workspace',
+    template: `
+    <label>
+      <h3 #workspaceTitle id="blockly-workspace-title">{{'WORKSPACE'|translate}}</h3>
+    </label>
+
+    <div id="blockly-workspace-toolbar" (keydown)="onWorkspaceToolbarKeypress($event)">
+      <span *ngFor="#buttonConfig of toolbarButtonConfig">
+        <button (click)="buttonConfig.action()"
+                class="blocklyTree blocklyWorkspaceToolbarButton">
+          {{buttonConfig.text}}
+        </button>
+      </span>
+      <button id="clear-workspace" (click)="clearWorkspace()"
+              [attr.aria-disabled]="isWorkspaceEmpty()"
+              class="blocklyTree blocklyWorkspaceToolbarButton">
+        {{'CLEAR_WORKSPACE'|translate}}
+      </button>
+    </div>
+
+    <div *ngIf="workspace">
+      <ol #tree *ngFor="#block of workspace.topBlocks_; #i = index"
+          tabIndex="0" role="group" class="blocklyTree blocklyWorkspaceTree"
+          [attr.aria-activedescendant]="getActiveDescId(tree.id)"
+          [attr.aria-labelledby]="workspaceTitle.id"
+          (keydown)="onKeypress($event, tree)">
+        <blockly-workspace-tree [level]=1 [block]="block" [tree]="tree" [isTopLevel]="true">
+        </blockly-workspace-tree>
+      </ol>
+    </div>
+    `,
+    directives: [blocklyApp.WorkspaceTreeComponent],
+    pipes: [blocklyApp.TranslatePipe]
+  })
+  .Class({
+    constructor: [blocklyApp.TreeService, function(_treeService) {
+      // ACCESSIBLE_GLOBALS is a global variable defined by the containing
+      // page. It should contain a key, toolbarButtonConfig, whose
+      // corresponding value is an Array with two keys: 'text' and 'action'.
+      // The first is the text to display on the button, and the second is the
+      // function that gets run when the button is clicked.
+      this.toolbarButtonConfig =
+          ACCESSIBLE_GLOBALS && ACCESSIBLE_GLOBALS.toolbarButtonConfig ?
+          ACCESSIBLE_GLOBALS.toolbarButtonConfig : [];
+      this.workspace = blocklyApp.workspace;
+      this.treeService = _treeService;
+    }],
+    clearWorkspace: function() {
+      this.workspace.clear();
+    },
+    getActiveDescId: function(tree) {
+      return this.treeService.getActiveDescId(tree.id);
+    },
+    onWorkspaceToolbarKeypress: function(e) {
+      this.treeService.onWorkspaceToolbarKeypress(
+          e, document.activeElement.id);
+    },
+    onKeypress: function(e, tree) {
+      this.treeService.onKeypress(e, tree);
+    },
+    isWorkspaceEmpty: function() {
+      return !this.workspace.topBlocks_.length;
+    }
+  });

+ 44 - 0
blockly/appengine/README.txt

@@ -0,0 +1,44 @@
+
+  Running an App Engine server
+
+This directory contains the files needed to setup the optional Blockly server.
+Although Blockly itself is 100% client-side, the server enables cloud storage
+and sharing.  Store your programs in Datastore and get a unique URL that allows
+you to load the program on any computer.
+
+To run your own App Engine instance you'll need to create this directory
+structure:
+
+blockly/
+ |- app.yaml
+ |- index.yaml
+ |- index_redirect.py
+ |- README.txt
+ |- storage.js
+ |- storage.py
+ |- closure-library/  (Optional)
+ `- static/
+     |- blocks/
+     |- core/
+     |- demos/
+     |- generators/
+     |- media/
+     |- msg/
+     |- tests/
+     |- blockly_compressed.js
+     |- blockly_uncompressed.js  (Optional)
+     |- blocks_compressed.js
+     |- dart_compressed.js
+     |- javascript_compressed.js
+     |- lua_compressed.js
+     |- php_compressed.js
+     `- python_compressed.js
+
+Instructions for fetching the optional Closure library may be found here:
+  https://developers.google.com/blockly/guides/modify/web/closure
+
+Go to https://appengine.google.com/ and create your App Engine application.
+Modify the 'application' name of app.yaml to your App Engine application name.
+
+Finally, upload this directory structure to your App Engine account,
+wait a minute, then go to http://YOURAPPNAME.appspot.com/

+ 82 - 0
blockly/appengine/app.yaml

@@ -0,0 +1,82 @@
+application: blockly-demo
+version: 1
+runtime: python27
+api_version: 1
+threadsafe: no
+
+handlers:
+# Redirect obsolete URLs.
+# Blockly files moved from /blockly to /static on 5 Dec 2012.
+- url: /blockly/.*
+  static_files: redirect.html
+  upload: redirect.html
+# Code, Maze and Turtle moved from demos on 29 Dec 2012.
+- url: /static/demos/(maze|turtle)/.*
+  static_files: redirect.html
+  upload: redirect.html
+# Apps was disbanded on 20 Nov 2014.
+- url: /static/apps/.*
+  static_files: redirect.html
+  upload: redirect.html
+
+
+# Storage API.
+- url: /storage
+  script: storage.py
+  secure: always
+- url: /storage\.js
+  static_files: storage.js
+  upload: storage\.js
+  secure: always
+
+# Blockly files.
+- url: /static
+  static_dir: static
+  secure: always
+
+# Closure library for uncompiled Blockly.
+- url: /closure-library
+  static_dir: closure-library
+  secure: always
+
+# Redirect for root directory.
+- url: /
+  script: index_redirect.py
+  secure: always
+
+# Favicon.
+- url: /favicon\.ico
+  static_files: favicon.ico
+  upload: favicon\.ico
+  secure: always
+  expiration: "30d"
+
+# Apple icon.
+- url: /apple-touch-icon\.png
+  static_files: apple-touch-icon.png
+  upload: apple-touch-icon\.png
+  secure: always
+  expiration: "30d"
+
+# robot.txt
+- url: /robots\.txt
+  static_files: robots.txt
+  upload: robots\.txt
+  secure: always
+
+
+skip_files:
+# App Engine default patterns.
+- ^(.*/)?#.*#$
+- ^(.*/)?.*~$
+- ^(.*/)?.*\.py[co]$
+- ^(.*/)?.*/RCS/.*$
+- ^(.*/)?\..*$
+# Custom skip patterns.
+- ^static/appengine/.*$
+- ^static/demos/plane/soy/.+\.jar$
+- ^static/demos/plane/template.soy$
+- ^static/demos/plane/xlf/.*$
+- ^static/i18n/.*$
+- ^static/msg/json/.*$
+- ^.+\.soy$

BIN
blockly/appengine/apple-touch-icon.png


BIN
blockly/appengine/favicon.ico


+ 11 - 0
blockly/appengine/index.yaml

@@ -0,0 +1,11 @@
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run.  If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED").  If you want to manage some indexes
+# manually, move them above the marker line.  The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.

+ 2 - 0
blockly/appengine/index_redirect.py

@@ -0,0 +1,2 @@
+print("Status: 302")
+print("Location: /static/demos/index.html")

+ 68 - 0
blockly/appengine/redirect.html

@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script>
+var loc = location.href;
+
+// Blockly files moved from /blockly to /static on 5 Dec 2012.
+if (loc.match('/blockly/')) {
+  loc = loc.replace('/blockly/', '/static/');
+}
+
+// Maze and Turtle moved from demos to apps on 29 Dec 2012.
+if (loc.match(/\/demos\/(maze|turtle)\//)) {
+  loc = loc.replace('/demos/', '/apps/');
+}
+
+// Vietnamese apps moved from vn to vi on 9 Jun 2012.
+if (loc.match('/vn.html')) {
+  loc = loc.replace('/vn.html', '/vi.html');
+}
+
+if (loc.match('/code/code.html')) {
+  // Code moved to index.html on 7 Aug 2013.
+  loc = loc.replace('/code.html', '/index.html');
+} else if (loc.match('/apps/code/zh_tw.html')) {
+  // zh-tw was changed to zh-hans on 25 Nov 2013.
+  loc = loc.replace('/zh_tw.html', '/index.html?lang=zh-hans');
+} else if (loc.match('/apps/code/index.html')) {
+  // NOP.
+} else if (loc.match(/\/apps\/code\/[-a-z]+\.html/)) {
+  // Code became language-agnostic on 20 Jul 2013.
+  loc = loc.replace(/\/([-a-z]+)\.html/, '/index.html?lang=$1');
+}
+
+if (loc.match('/apps/plane/plane.html')) {
+  // Plane moved to index.html on 7 Aug 2013.
+  loc = loc.replace('/plane.html', '/index.html');
+} else if (loc.match('/apps/code/plane.html')) {
+  // NOP.
+} else if (loc.match(/\/apps\/plane\/[\d_]*[-a-z]+\.html/)) {
+  // Plane became language-agnostic on 20 Jul 2013.
+  loc = loc.replace('vn.html', 'vi.html');
+  if (location.search) {
+    loc = loc.replace(/\/[\d_]*([-a-z]+)\.html\?/, '/index.html?lang=$1&');
+  } else {
+    loc = loc.replace(/\/[\d_]*([-a-z]+)\.html/, '/index.html?lang=$1');
+  }
+}
+
+if (loc.match('/apps/puzzle/')) {
+  // Puzzle moved to Blockly Games on 15 Oct 2014.
+  loc = 'https://blockly-games.appspot.com/puzzle';
+} else if (loc.match('/apps/maze/')) {
+  // Maze moved to Blockly Games on 10 Nov 2014.
+  loc = 'https://blockly-games.appspot.com/maze';
+} else if (loc.match('/apps/turtle/')) {
+  // Turtle moved to Blockly Games on 10 Nov 2014.
+  loc = 'https://blockly-games.appspot.com/turtle';
+} else if (loc.match('/apps/')) {
+  // Remaining apps moved to demos on 20 Nov 2014.
+  loc = loc.replace('/apps/', '/demos/');
+}
+
+location = loc;
+
+    </script>
+  </head>
+</html>

+ 2 - 0
blockly/appengine/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /storage

+ 194 - 0
blockly/appengine/storage.js

@@ -0,0 +1,194 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Loading and saving blocks with localStorage and cloud storage.
+ * @author q.neutron@gmail.com (Quynh Neutron)
+ */
+'use strict';
+
+// Create a namespace.
+var BlocklyStorage = {};
+
+/**
+ * Backup code blocks to localStorage.
+ * @param {!Blockly.WorkspaceSvg} workspace Workspace.
+ * @private
+ */
+BlocklyStorage.backupBlocks_ = function(workspace) {
+  if ('localStorage' in window) {
+    var xml = Blockly.Xml.workspaceToDom(workspace);
+    // Gets the current URL, not including the hash.
+    var url = window.location.href.split('#')[0];
+    window.localStorage.setItem(url, Blockly.Xml.domToText(xml));
+  }
+};
+
+/**
+ * Bind the localStorage backup function to the unload event.
+ * @param {Blockly.WorkspaceSvg=} opt_workspace Workspace.
+ */
+BlocklyStorage.backupOnUnload = function(opt_workspace) {
+  var workspace = opt_workspace || Blockly.getMainWorkspace();
+  window.addEventListener('unload',
+      function() {BlocklyStorage.backupBlocks_(workspace);}, false);
+};
+
+/**
+ * Restore code blocks from localStorage.
+ * @param {Blockly.WorkspaceSvg=} opt_workspace Workspace.
+ */
+BlocklyStorage.restoreBlocks = function(opt_workspace) {
+  var url = window.location.href.split('#')[0];
+  if ('localStorage' in window && window.localStorage[url]) {
+    var workspace = opt_workspace || Blockly.getMainWorkspace();
+    var xml = Blockly.Xml.textToDom(window.localStorage[url]);
+    Blockly.Xml.domToWorkspace(xml, workspace);
+  }
+};
+
+/**
+ * Save blocks to database and return a link containing key to XML.
+ * @param {Blockly.WorkspaceSvg=} opt_workspace Workspace.
+ */
+BlocklyStorage.link = function(opt_workspace) {
+  var workspace = opt_workspace || Blockly.getMainWorkspace();
+  var xml = Blockly.Xml.workspaceToDom(workspace);
+  var data = Blockly.Xml.domToText(xml);
+  BlocklyStorage.makeRequest_('/storage', 'xml', data, workspace);
+};
+
+/**
+ * Retrieve XML text from database using given key.
+ * @param {string} key Key to XML, obtained from href.
+ * @param {Blockly.WorkspaceSvg=} opt_workspace Workspace.
+ */
+BlocklyStorage.retrieveXml = function(key, opt_workspace) {
+  var workspace = opt_workspace || Blockly.getMainWorkspace();
+  BlocklyStorage.makeRequest_('/storage', 'key', key, workspace);
+};
+
+/**
+ * Global reference to current AJAX request.
+ * @type {XMLHttpRequest}
+ * @private
+ */
+BlocklyStorage.httpRequest_ = null;
+
+/**
+ * Fire a new AJAX request.
+ * @param {string} url URL to fetch.
+ * @param {string} name Name of parameter.
+ * @param {string} content Content of parameter.
+ * @param {!Blockly.WorkspaceSvg} workspace Workspace.
+ * @private
+ */
+BlocklyStorage.makeRequest_ = function(url, name, content, workspace) {
+  if (BlocklyStorage.httpRequest_) {
+    // AJAX call is in-flight.
+    BlocklyStorage.httpRequest_.abort();
+  }
+  BlocklyStorage.httpRequest_ = new XMLHttpRequest();
+  BlocklyStorage.httpRequest_.name = name;
+  BlocklyStorage.httpRequest_.onreadystatechange =
+      BlocklyStorage.handleRequest_;
+  BlocklyStorage.httpRequest_.open('POST', url);
+  BlocklyStorage.httpRequest_.setRequestHeader('Content-Type',
+      'application/x-www-form-urlencoded');
+  BlocklyStorage.httpRequest_.send(name + '=' + encodeURIComponent(content));
+  BlocklyStorage.httpRequest_.workspace = workspace;
+};
+
+/**
+ * Callback function for AJAX call.
+ * @private
+ */
+BlocklyStorage.handleRequest_ = function() {
+  if (BlocklyStorage.httpRequest_.readyState == 4) {
+    if (BlocklyStorage.httpRequest_.status != 200) {
+      BlocklyStorage.alert(BlocklyStorage.HTTPREQUEST_ERROR + '\n' +
+          'httpRequest_.status: ' + BlocklyStorage.httpRequest_.status);
+    } else {
+      var data = BlocklyStorage.httpRequest_.responseText.trim();
+      if (BlocklyStorage.httpRequest_.name == 'xml') {
+        window.location.hash = data;
+        BlocklyStorage.alert(BlocklyStorage.LINK_ALERT.replace('%1',
+            window.location.href));
+      } else if (BlocklyStorage.httpRequest_.name == 'key') {
+        if (!data.length) {
+          BlocklyStorage.alert(BlocklyStorage.HASH_ERROR.replace('%1',
+              window.location.hash));
+        } else {
+          BlocklyStorage.loadXml_(data, BlocklyStorage.httpRequest_.workspace);
+        }
+      }
+      BlocklyStorage.monitorChanges_(BlocklyStorage.httpRequest_.workspace);
+    }
+    BlocklyStorage.httpRequest_ = null;
+  }
+};
+
+/**
+ * Start monitoring the workspace.  If a change is made that changes the XML,
+ * clear the key from the URL.  Stop monitoring the workspace once such a
+ * change is detected.
+ * @param {!Blockly.WorkspaceSvg} workspace Workspace.
+ * @private
+ */
+BlocklyStorage.monitorChanges_ = function(workspace) {
+  var startXmlDom = Blockly.Xml.workspaceToDom(workspace);
+  var startXmlText = Blockly.Xml.domToText(startXmlDom);
+  function change() {
+    var xmlDom = Blockly.Xml.workspaceToDom(workspace);
+    var xmlText = Blockly.Xml.domToText(xmlDom);
+    if (startXmlText != xmlText) {
+      window.location.hash = '';
+      workspace.removeChangeListener(bindData);
+    }
+  }
+  var bindData = workspace.addChangeListener(change);
+};
+
+/**
+ * Load blocks from XML.
+ * @param {string} xml Text representation of XML.
+ * @param {!Blockly.WorkspaceSvg} workspace Workspace.
+ * @private
+ */
+BlocklyStorage.loadXml_ = function(xml, workspace) {
+  try {
+    xml = Blockly.Xml.textToDom(xml);
+  } catch (e) {
+    BlocklyStorage.alert(BlocklyStorage.XML_ERROR + '\nXML: ' + xml);
+    return;
+  }
+  // Clear the workspace to avoid merge.
+  workspace.clear();
+  Blockly.Xml.domToWorkspace(xml, workspace);
+};
+
+/**
+ * Present a text message to the user.
+ * Designed to be overridden if an app has custom dialogs, or a butter bar.
+ * @param {string} message Text to alert.
+ */
+BlocklyStorage.alert = function(message) {
+  window.alert(message);
+};

+ 85 - 0
blockly/appengine/storage.py

@@ -0,0 +1,85 @@
+"""Blockly Demo: Storage
+
+Copyright 2012 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.
+"""
+
+"""Store and retrieve XML with App Engine.
+"""
+
+__author__ = "q.neutron@gmail.com (Quynh Neutron)"
+
+import cgi
+from random import randint
+from google.appengine.ext import db
+from google.appengine.api import memcache
+import logging
+
+print "Content-Type: text/plain\n"
+
+def keyGen():
+  # Generate a random string of length KEY_LEN.
+  KEY_LEN = 6
+  CHARS = "abcdefghijkmnopqrstuvwxyz23456789" # Exclude l, 0, 1.
+  max_index = len(CHARS) - 1
+  return "".join([CHARS[randint(0, max_index)] for x in range(KEY_LEN)])
+
+class Xml(db.Model):
+  # A row in the database.
+  xml_hash = db.IntegerProperty()
+  xml_content = db.TextProperty()
+
+forms = cgi.FieldStorage()
+if "xml" in forms:
+  # Store XML and return a generated key.
+  xml_content = forms["xml"].value
+  xml_hash = hash(xml_content)
+  lookup_query = db.Query(Xml)
+  lookup_query.filter("xml_hash =", xml_hash)
+  lookup_result = lookup_query.get()
+  if lookup_result:
+    xml_key = lookup_result.key().name()
+  else:
+    trials = 0
+    result = True
+    while result:
+      trials += 1
+      if trials == 100:
+        raise Exception("Sorry, the generator failed to get a key for you.")
+      xml_key = keyGen()
+      result = db.get(db.Key.from_path("Xml", xml_key))
+    xml = db.Text(xml_content, encoding="utf_8")
+    row = Xml(key_name = xml_key, xml_hash = xml_hash, xml_content = xml)
+    row.put()
+  print xml_key
+
+if "key" in forms:
+  # Retrieve stored XML based on the provided key.
+  key_provided = forms["key"].value
+  # Normalize the string.
+  key_provided = key_provided.lower().strip()
+  # Check memcache for a quick match.
+  xml = memcache.get("XML_" + key_provided)
+  if xml is None:
+    # Check datastore for a definitive match.
+    result = db.get(db.Key.from_path("Xml", key_provided))
+    if not result:
+      xml = ""
+    else:
+      xml = result.xml_content
+    # Save to memcache for next hit.
+    if not memcache.add("XML_" + key_provided, xml, 3600):
+      logging.error("Memcache set failed.")
+  print xml.encode("utf-8")

+ 1488 - 0
blockly/blockly_compressed.js

@@ -0,0 +1,1488 @@
+// Do not edit this file; automatically generated by build.py.
+'use strict';
+
+var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]?c[d]:c[d]={}};
+goog.define=function(a,b){var c=b;COMPILED||(goog.global.CLOSURE_UNCOMPILED_DEFINES&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_UNCOMPILED_DEFINES,a)?c=goog.global.CLOSURE_UNCOMPILED_DEFINES[a]:goog.global.CLOSURE_DEFINES&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_DEFINES,a)&&(c=goog.global.CLOSURE_DEFINES[a]));goog.exportPath_(a,c)};goog.DEBUG=!1;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;
+goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1;goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};
+goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
+goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.");if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");
+goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED)return goog.isProvided_(a)?a in goog.loadedModules_?goog.loadedModules_[a]:goog.getObjectByName(a):null};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_};
+goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0};
+goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0});
+goog.getObjectByName=function(a,b){for(var c=a.split("."),d=b||goog.global,e;e=c.shift();)if(goog.isDefAndNotNull(d[e]))d=d[e];else return null;return d};goog.globalize=function(a,b){var c=b||goog.global,d;for(d in a)c[d]=a[d]};
+goog.addDependency=function(a,b,c,d){if(goog.DEPENDENCIES_ENABLED){var e;a=a.replace(/\\/g,"/");var f=goog.dependencies_;d&&"boolean"!==typeof d||(d=d?{module:"goog"}:{});for(var g=0;e=b[g];g++)f.nameToPath[e]=a,f.loadFlags[a]=d;for(d=0;b=c[d];d++)a in f.requires||(f.requires[a]={}),f.requires[a][b]=!0}};goog.ENABLE_DEBUG_LOADER=!0;goog.logToConsole_=function(a){goog.global.console&&goog.global.console.error(a)};
+goog.require=function(a){if(!COMPILED){goog.ENABLE_DEBUG_LOADER&&goog.IS_OLD_IE_&&goog.maybeProcessDeferredDep_(a);if(goog.isProvided_(a)){if(goog.isInModuleLoader_())return goog.module.getInternal_(a)}else if(goog.ENABLE_DEBUG_LOADER){var b=goog.getPathFromDeps_(a);if(b)goog.writeScripts_(b);else throw a="goog.require could not find: "+a,goog.logToConsole_(a),Error(a);}return null}};goog.basePath="";goog.nullFunction=function(){};
+goog.abstractMethod=function(){throw Error("unimplemented abstract method");};goog.addSingletonGetter=function(a){a.getInstance=function(){if(a.instance_)return a.instance_;goog.DEBUG&&(goog.instantiatedSingletons_[goog.instantiatedSingletons_.length]=a);return a.instance_=new a}};goog.instantiatedSingletons_=[];goog.LOAD_MODULE_USING_EVAL=!0;goog.SEAL_MODULE_EXPORTS=goog.DEBUG;goog.loadedModules_={};goog.DEPENDENCIES_ENABLED=!COMPILED&&goog.ENABLE_DEBUG_LOADER;goog.TRANSPILE="detect";
+goog.TRANSPILER="transpile.js";
+goog.DEPENDENCIES_ENABLED&&(goog.dependencies_={loadFlags:{},nameToPath:{},requires:{},visited:{},written:{},deferred:{}},goog.inHtmlDocument_=function(){var a=goog.global.document;return null!=a&&"write"in a},goog.findBasePath_=function(){if(goog.isDef(goog.global.CLOSURE_BASE_PATH))goog.basePath=goog.global.CLOSURE_BASE_PATH;else if(goog.inHtmlDocument_())for(var a=goog.global.document.getElementsByTagName("SCRIPT"),b=a.length-1;0<=b;--b){var c=a[b].src,d=c.lastIndexOf("?"),d=-1==d?c.length:d;if("base.js"==
+c.substr(d-7,7)){goog.basePath=c.substr(0,d-7);break}}},goog.importScript_=function(a,b){(goog.global.CLOSURE_IMPORT_SCRIPT||goog.writeScriptTag_)(a,b)&&(goog.dependencies_.written[a]=!0)},goog.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.importProcessedScript_=function(a,b,c){goog.importScript_("",'goog.retrieveAndExec_("'+a+'", '+b+", "+c+");")},goog.queuedModules_=[],goog.wrapModule_=function(a,b){return goog.LOAD_MODULE_USING_EVAL&&goog.isDef(goog.global.JSON)?
+"goog.loadModule("+goog.global.JSON.stringify(b+"\n//# sourceURL="+a+"\n")+");":'goog.loadModule(function(exports) {"use strict";'+b+"\n;return exports});\n//# sourceURL="+a+"\n"},goog.loadQueuedModules_=function(){var a=goog.queuedModules_.length;if(0<a){var b=goog.queuedModules_;goog.queuedModules_=[];for(var c=0;c<a;c++)goog.maybeProcessDeferredPath_(b[c])}},goog.maybeProcessDeferredDep_=function(a){goog.isDeferredModule_(a)&&goog.allDepsAreAvailable_(a)&&(a=goog.getPathFromDeps_(a),goog.maybeProcessDeferredPath_(goog.basePath+
+a))},goog.isDeferredModule_=function(a){var b=(a=goog.getPathFromDeps_(a))&&goog.dependencies_.loadFlags[a]||{},c=b.lang||"es3";return a&&("goog"==b.module||goog.needsTranspile_(c))?goog.basePath+a in goog.dependencies_.deferred:!1},goog.allDepsAreAvailable_=function(a){if((a=goog.getPathFromDeps_(a))&&a in goog.dependencies_.requires)for(var b in goog.dependencies_.requires[a])if(!goog.isProvided_(b)&&!goog.isDeferredModule_(b))return!1;return!0},goog.maybeProcessDeferredPath_=function(a){if(a in
+goog.dependencies_.deferred){var b=goog.dependencies_.deferred[a];delete goog.dependencies_.deferred[a];goog.globalEval(b)}},goog.loadModuleFromUrl=function(a){goog.retrieveAndExec_(a,!0,!1)},goog.writeScriptSrcNode_=function(a){goog.global.document.write('<script type="text/javascript" src="'+a+'">\x3c/script>')},goog.appendScriptSrcNode_=function(a){var b=goog.global.document,c=b.createElement("script");c.type="text/javascript";c.src=a;c.defer=!1;c.async=!1;b.head.appendChild(c)},goog.writeScriptTag_=
+function(a,b){if(goog.inHtmlDocument_()){var c=goog.global.document;if(!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING&&"complete"==c.readyState){if(/\bdeps.js$/.test(a))return!1;throw Error('Cannot write "'+a+'" after document load');}if(void 0===b)if(goog.IS_OLD_IE_){var d=" onreadystatechange='goog.onScriptLoad_(this, "+ ++goog.lastNonModuleScriptIndex_+")' ";c.write('<script type="text/javascript" src="'+a+'"'+d+">\x3c/script>")}else goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING?goog.appendScriptSrcNode_(a):
+goog.writeScriptSrcNode_(a);else c.write('<script type="text/javascript">'+goog.protectScriptTag_(b)+"\x3c/script>");return!0}return!1},goog.protectScriptTag_=function(a){return a.replace(/<\/(SCRIPT)/ig,"\\x3c\\$1")},goog.needsTranspile_=function(a){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;goog.requiresTranspilation_||(goog.requiresTranspilation_=goog.createRequiresTranspilation_());if(a in goog.requiresTranspilation_)return goog.requiresTranspilation_[a];throw Error("Unknown language mode: "+
+a);},goog.requiresTranspilation_=null,goog.lastNonModuleScriptIndex_=0,goog.onScriptLoad_=function(a,b){"complete"==a.readyState&&goog.lastNonModuleScriptIndex_==b&&goog.loadQueuedModules_();return!0},goog.writeScripts_=function(a){function b(a){if(!(a in e.written||a in e.visited)){e.visited[a]=!0;if(a in e.requires)for(var f in e.requires[a])if(!goog.isProvided_(f))if(f in e.nameToPath)b(e.nameToPath[f]);else throw Error("Undefined nameToPath for "+f);a in d||(d[a]=!0,c.push(a))}}var c=[],d={},
+e=goog.dependencies_;b(a);for(a=0;a<c.length;a++){var f=c[a];goog.dependencies_.written[f]=!0}var g=goog.moduleLoaderState_;goog.moduleLoaderState_=null;for(a=0;a<c.length;a++)if(f=c[a]){var h=e.loadFlags[f]||{},k=goog.needsTranspile_(h.lang||"es3");"goog"==h.module||k?goog.importProcessedScript_(goog.basePath+f,"goog"==h.module,k):goog.importScript_(goog.basePath+f)}else throw goog.moduleLoaderState_=g,Error("Undefined script input");goog.moduleLoaderState_=g},goog.getPathFromDeps_=function(a){return a in
+goog.dependencies_.nameToPath?goog.dependencies_.nameToPath[a]:null},goog.findBasePath_(),goog.global.CLOSURE_NO_DEPS||goog.importScript_(goog.basePath+"deps.js"));
+goog.loadModule=function(a){var b=goog.moduleLoaderState_;try{goog.moduleLoaderState_={moduleName:void 0,declareLegacyNamespace:!1};var c;if(goog.isFunction(a))c=a.call(void 0,{});else if(goog.isString(a))c=goog.loadModuleFromSource_.call(void 0,a);else throw Error("Invalid module definition");var d=goog.moduleLoaderState_.moduleName;if(!goog.isString(d)||!d)throw Error('Invalid module name "'+d+'"');goog.moduleLoaderState_.declareLegacyNamespace?goog.constructNamespace_(d,c):goog.SEAL_MODULE_EXPORTS&&
+Object.seal&&goog.isObject(c)&&Object.seal(c);goog.loadedModules_[d]=c}finally{goog.moduleLoaderState_=b}};goog.loadModuleFromSource_=function(a){eval(a);return{}};goog.normalizePath_=function(a){a=a.split("/");for(var b=0;b<a.length;)"."==a[b]?a.splice(b,1):b&&".."==a[b]&&a[b-1]&&".."!=a[b-1]?a.splice(--b,2):b++;return a.join("/")};
+goog.loadFileSync_=function(a){if(goog.global.CLOSURE_LOAD_FILE_SYNC)return goog.global.CLOSURE_LOAD_FILE_SYNC(a);try{var b=new goog.global.XMLHttpRequest;b.open("get",a,!1);b.send();return 0==b.status||200==b.status?b.responseText:null}catch(c){return null}};
+goog.retrieveAndExec_=function(a,b,c){if(!COMPILED){var d=a;a=goog.normalizePath_(a);var e=goog.global.CLOSURE_IMPORT_SCRIPT||goog.writeScriptTag_,f=goog.loadFileSync_(a);if(null==f)throw Error('Load of "'+a+'" failed');c&&(f=goog.transpile_.call(goog.global,f,a));f=b?goog.wrapModule_(a,f):f+("\n//# sourceURL="+a);goog.IS_OLD_IE_?(goog.dependencies_.deferred[d]=f,goog.queuedModules_.push(d)):e(a,f)}};
+goog.transpile_=function(a,b){var c=goog.global.$jscomp;c||(goog.global.$jscomp=c={});var d=c.transpile;if(!d){var e=goog.basePath+goog.TRANSPILER,f=goog.loadFileSync_(e);if(f){eval(f+"\n//# sourceURL="+e);if(goog.global.$gwtExport&&goog.global.$gwtExport.$jscomp&&!goog.global.$gwtExport.$jscomp.transpile)throw Error('The transpiler did not properly export the "transpile" method. $gwtExport: '+JSON.stringify(goog.global.$gwtExport));goog.global.$jscomp.transpile=goog.global.$gwtExport.$jscomp.transpile;
+c=goog.global.$jscomp;d=c.transpile}}d||(d=c.transpile=function(a,b){goog.logToConsole_(b+" requires transpilation but no transpiler was found.");return a});return d(a,b)};
+goog.typeOf=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if("function"==b&&"undefined"==typeof a.call)return"object";return b};goog.isNull=function(a){return null===a};goog.isDefAndNotNull=function(a){return null!=a};goog.isArray=function(a){return"array"==goog.typeOf(a)};goog.isArrayLike=function(a){var b=goog.typeOf(a);return"array"==b||"object"==b&&"number"==typeof a.length};goog.isDateLike=function(a){return goog.isObject(a)&&"function"==typeof a.getFullYear};goog.isString=function(a){return"string"==typeof a};
+goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a};goog.isFunction=function(a){return"function"==goog.typeOf(a)};goog.isObject=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b};goog.getUid=function(a){return a[goog.UID_PROPERTY_]||(a[goog.UID_PROPERTY_]=++goog.uidCounter_)};goog.hasUid=function(a){return!!a[goog.UID_PROPERTY_]};
+goog.removeUid=function(a){null!==a&&"removeAttribute"in a&&a.removeAttribute(goog.UID_PROPERTY_);try{delete a[goog.UID_PROPERTY_]}catch(b){}};goog.UID_PROPERTY_="closure_uid_"+(1E9*Math.random()>>>0);goog.uidCounter_=0;goog.getHashCode=goog.getUid;goog.removeHashCode=goog.removeUid;goog.cloneObject=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if(a.clone)return a.clone();var b="array"==b?[]:{},c;for(c in a)b[c]=goog.cloneObject(a[c]);return b}return a};
+goog.bindNative_=function(a,b,c){return a.call.apply(a.bind,arguments)};goog.bindJs_=function(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}};
+goog.bind=function(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?goog.bind=goog.bindNative_:goog.bind=goog.bindJs_;return goog.bind.apply(null,arguments)};goog.partial=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}};goog.mixin=function(a,b){for(var c in b)a[c]=b[c]};goog.now=goog.TRUSTED_SITE&&Date.now||function(){return+new Date};
+goog.globalEval=function(a){if(goog.global.execScript)goog.global.execScript(a,"JavaScript");else if(goog.global.eval){if(null==goog.evalWorksForGlobals_)if(goog.global.eval("var _evalTest_ = 1;"),"undefined"!=typeof goog.global._evalTest_){try{delete goog.global._evalTest_}catch(d){}goog.evalWorksForGlobals_=!0}else goog.evalWorksForGlobals_=!1;if(goog.evalWorksForGlobals_)goog.global.eval(a);else{var b=goog.global.document,c=b.createElement("SCRIPT");c.type="text/javascript";c.defer=!1;c.appendChild(b.createTextNode(a));
+b.body.appendChild(c);b.body.removeChild(c)}}else throw Error("goog.globalEval not available");};goog.evalWorksForGlobals_=null;
+goog.getCssName=function(a,b){if("."==String(a).charAt(0))throw Error('className passed in goog.getCssName must not start with ".". You passed: '+a);var c=function(a){return goog.cssNameMapping_[a]||a},d=function(a){a=a.split("-");for(var b=[],d=0;d<a.length;d++)b.push(c(a[d]));return b.join("-")},d=goog.cssNameMapping_?"BY_WHOLE"==goog.cssNameMappingStyle_?c:d:function(a){return a},d=b?a+"-"+d(b):d(a);return goog.global.CLOSURE_CSS_NAME_MAP_FN?goog.global.CLOSURE_CSS_NAME_MAP_FN(d):d};
+goog.setCssNameMapping=function(a,b){goog.cssNameMapping_=a;goog.cssNameMappingStyle_=b};!COMPILED&&goog.global.CLOSURE_CSS_NAME_MAPPING&&(goog.cssNameMapping_=goog.global.CLOSURE_CSS_NAME_MAPPING);goog.getMsg=function(a,b){b&&(a=a.replace(/\{\$([^}]+)}/g,function(a,d){return null!=b&&d in b?b[d]:a}));return a};goog.getMsgWithFallback=function(a,b){return a};goog.exportSymbol=function(a,b,c){goog.exportPath_(a,b,c)};goog.exportProperty=function(a,b,c){a[b]=c};
+goog.inherits=function(a,b){function c(){}c.prototype=b.prototype;a.superClass_=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.base=function(a,c,f){for(var d=Array(arguments.length-2),e=2;e<arguments.length;e++)d[e-2]=arguments[e];return b.prototype[c].apply(a,d)}};
+goog.base=function(a,b,c){var d=arguments.callee.caller;if(goog.STRICT_MODE_COMPATIBLE||goog.DEBUG&&!d)throw Error("arguments.caller not defined.  goog.base() cannot be used with strict mode code. See http://www.ecma-international.org/ecma-262/5.1/#sec-C");if(d.superClass_){for(var e=Array(arguments.length-1),f=1;f<arguments.length;f++)e[f-1]=arguments[f];return d.superClass_.constructor.apply(a,e)}e=Array(arguments.length-2);for(f=2;f<arguments.length;f++)e[f-2]=arguments[f];for(var f=!1,g=a.constructor;g;g=
+g.superClass_&&g.superClass_.constructor)if(g.prototype[b]===d)f=!0;else if(f)return g.prototype[b].apply(a,e);if(a[b]===d)return a.constructor.prototype[b].apply(a,e);throw Error("goog.base called from a method of one name to a method of a different name");};goog.scope=function(a){if(goog.isInModuleLoader_())throw Error("goog.scope is not supported within a goog.module.");a.call(goog.global)};COMPILED||(goog.global.COMPILED=COMPILED);
+goog.defineClass=function(a,b){var c=b.constructor,d=b.statics;c&&c!=Object.prototype.constructor||(c=function(){throw Error("cannot instantiate an interface (no constructor defined).");});c=goog.defineClass.createSealingConstructor_(c,a);a&&goog.inherits(c,a);delete b.constructor;delete b.statics;goog.defineClass.applyProperties_(c.prototype,b);null!=d&&(d instanceof Function?d(c):goog.defineClass.applyProperties_(c,d));return c};goog.defineClass.SEAL_CLASS_INSTANCES=goog.DEBUG;
+goog.defineClass.createSealingConstructor_=function(a,b){if(!goog.defineClass.SEAL_CLASS_INSTANCES)return a;var c=!goog.defineClass.isUnsealable_(b),d=function(){var b=a.apply(this,arguments)||this;b[goog.UID_PROPERTY_]=b[goog.UID_PROPERTY_];this.constructor===d&&c&&Object.seal instanceof Function&&Object.seal(b);return b};return d};goog.defineClass.isUnsealable_=function(a){return a&&a.prototype&&a.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]};goog.defineClass.OBJECT_PROTOTYPE_FIELDS_="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");
+goog.defineClass.applyProperties_=function(a,b){for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c]);for(var d=0;d<goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length;d++)c=goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[d],Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c])};goog.tagUnsealableClass=function(a){!COMPILED&&goog.defineClass.SEAL_CLASS_INSTANCES&&(a.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]=!0)};goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_="goog_defineClass_legacy_unsealable";
+goog.createRequiresTranspilation_=function(){function a(a,b){d?c[a]=!0:b()?c[a]=!1:d=c[a]=!0}function b(a){try{return!!eval(a)}catch(f){return!1}}var c={es3:!1},d=!1;a("es5",function(){return b("[1,].length==1")});a("es6",function(){return b('(()=>{"use strict";class X{constructor(){if(new.target!=String)throw 1;this.x=42}}let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a==2)continue;function f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()==3}})()')});
+a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 == 4")});a("es8",function(){return b("async () => 1, true")});return c};goog.debug={};goog.debug.Error=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,goog.debug.Error);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a));this.reportErrorToServer=!0};goog.inherits(goog.debug.Error,Error);goog.debug.Error.prototype.name="CustomError";goog.dom={};goog.dom.NodeType={ELEMENT:1,ATTRIBUTE:2,TEXT:3,CDATA_SECTION:4,ENTITY_REFERENCE:5,ENTITY:6,PROCESSING_INSTRUCTION:7,COMMENT:8,DOCUMENT:9,DOCUMENT_TYPE:10,DOCUMENT_FRAGMENT:11,NOTATION:12};goog.string={};goog.string.DETECT_DOUBLE_ESCAPING=!1;goog.string.FORCE_NON_DOM_HTML_UNESCAPING=!1;goog.string.Unicode={NBSP:"\u00a0"};goog.string.startsWith=function(a,b){return 0==a.lastIndexOf(b,0)};goog.string.endsWith=function(a,b){var c=a.length-b.length;return 0<=c&&a.indexOf(b,c)==c};goog.string.caseInsensitiveStartsWith=function(a,b){return 0==goog.string.caseInsensitiveCompare(b,a.substr(0,b.length))};
+goog.string.caseInsensitiveEndsWith=function(a,b){return 0==goog.string.caseInsensitiveCompare(b,a.substr(a.length-b.length,b.length))};goog.string.caseInsensitiveEquals=function(a,b){return a.toLowerCase()==b.toLowerCase()};goog.string.subs=function(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1<c.length;)d+=c.shift()+e.shift();return d+c.join("%s")};goog.string.collapseWhitespace=function(a){return a.replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")};
+goog.string.isEmptyOrWhitespace=function(a){return/^[\s\xa0]*$/.test(a)};goog.string.isEmptyString=function(a){return 0==a.length};goog.string.isEmpty=goog.string.isEmptyOrWhitespace;goog.string.isEmptyOrWhitespaceSafe=function(a){return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(a))};goog.string.isEmptySafe=goog.string.isEmptyOrWhitespaceSafe;goog.string.isBreakingWhitespace=function(a){return!/[^\t\n\r ]/.test(a)};goog.string.isAlpha=function(a){return!/[^a-zA-Z]/.test(a)};
+goog.string.isNumeric=function(a){return!/[^0-9]/.test(a)};goog.string.isAlphaNumeric=function(a){return!/[^a-zA-Z0-9]/.test(a)};goog.string.isSpace=function(a){return" "==a};goog.string.isUnicodeChar=function(a){return 1==a.length&&" "<=a&&"~">=a||"\u0080"<=a&&"\ufffd">=a};goog.string.stripNewlines=function(a){return a.replace(/(\r\n|\r|\n)+/g," ")};goog.string.canonicalizeNewlines=function(a){return a.replace(/(\r\n|\r|\n)/g,"\n")};
+goog.string.normalizeWhitespace=function(a){return a.replace(/\xa0|\s/g," ")};goog.string.normalizeSpaces=function(a){return a.replace(/\xa0|[ \t]+/g," ")};goog.string.collapseBreakingSpaces=function(a){return a.replace(/[\t\r\n ]+/g," ").replace(/^[\t\r\n ]+|[\t\r\n ]+$/g,"")};goog.string.trim=goog.TRUSTED_SITE&&String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};goog.string.trimLeft=function(a){return a.replace(/^[\s\xa0]+/,"")};
+goog.string.trimRight=function(a){return a.replace(/[\s\xa0]+$/,"")};goog.string.caseInsensitiveCompare=function(a,b){var c=String(a).toLowerCase(),d=String(b).toLowerCase();return c<d?-1:c==d?0:1};
+goog.string.numberAwareCompare_=function(a,b,c){if(a==b)return 0;if(!a)return-1;if(!b)return 1;for(var d=a.toLowerCase().match(c),e=b.toLowerCase().match(c),f=Math.min(d.length,e.length),g=0;g<f;g++){c=d[g];var h=e[g];if(c!=h)return a=parseInt(c,10),!isNaN(a)&&(b=parseInt(h,10),!isNaN(b)&&a-b)?a-b:c<h?-1:1}return d.length!=e.length?d.length-e.length:a<b?-1:1};goog.string.intAwareCompare=function(a,b){return goog.string.numberAwareCompare_(a,b,/\d+|\D+/g)};
+goog.string.floatAwareCompare=function(a,b){return goog.string.numberAwareCompare_(a,b,/\d+|\.\d+|\D+/g)};goog.string.numerateCompare=goog.string.floatAwareCompare;goog.string.urlEncode=function(a){return encodeURIComponent(String(a))};goog.string.urlDecode=function(a){return decodeURIComponent(a.replace(/\+/g," "))};goog.string.newLineToBr=function(a,b){return a.replace(/(\r\n|\r|\n)/g,b?"<br />":"<br>")};
+goog.string.htmlEscape=function(a,b){if(b)a=a.replace(goog.string.AMP_RE_,"&amp;").replace(goog.string.LT_RE_,"&lt;").replace(goog.string.GT_RE_,"&gt;").replace(goog.string.QUOT_RE_,"&quot;").replace(goog.string.SINGLE_QUOTE_RE_,"&#39;").replace(goog.string.NULL_RE_,"&#0;"),goog.string.DETECT_DOUBLE_ESCAPING&&(a=a.replace(goog.string.E_RE_,"&#101;"));else{if(!goog.string.ALL_RE_.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(goog.string.AMP_RE_,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(goog.string.LT_RE_,
+"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(goog.string.GT_RE_,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(goog.string.QUOT_RE_,"&quot;"));-1!=a.indexOf("'")&&(a=a.replace(goog.string.SINGLE_QUOTE_RE_,"&#39;"));-1!=a.indexOf("\x00")&&(a=a.replace(goog.string.NULL_RE_,"&#0;"));goog.string.DETECT_DOUBLE_ESCAPING&&-1!=a.indexOf("e")&&(a=a.replace(goog.string.E_RE_,"&#101;"))}return a};goog.string.AMP_RE_=/&/g;goog.string.LT_RE_=/</g;goog.string.GT_RE_=/>/g;goog.string.QUOT_RE_=/"/g;
+goog.string.SINGLE_QUOTE_RE_=/'/g;goog.string.NULL_RE_=/\x00/g;goog.string.E_RE_=/e/g;goog.string.ALL_RE_=goog.string.DETECT_DOUBLE_ESCAPING?/[\x00&<>"'e]/:/[\x00&<>"']/;goog.string.unescapeEntities=function(a){return goog.string.contains(a,"&")?!goog.string.FORCE_NON_DOM_HTML_UNESCAPING&&"document"in goog.global?goog.string.unescapeEntitiesUsingDom_(a):goog.string.unescapePureXmlEntities_(a):a};
+goog.string.unescapeEntitiesWithDocument=function(a,b){return goog.string.contains(a,"&")?goog.string.unescapeEntitiesUsingDom_(a,b):a};
+goog.string.unescapeEntitiesUsingDom_=function(a,b){var c={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"'},d;d=b?b.createElement("div"):goog.global.document.createElement("div");return a.replace(goog.string.HTML_ENTITY_PATTERN_,function(a,b){var e=c[a];if(e)return e;if("#"==b.charAt(0)){var f=Number("0"+b.substr(1));isNaN(f)||(e=String.fromCharCode(f))}e||(d.innerHTML=a+" ",e=d.firstChild.nodeValue.slice(0,-1));return c[a]=e})};
+goog.string.unescapePureXmlEntities_=function(a){return a.replace(/&([^;]+);/g,function(a,c){switch(c){case "amp":return"&";case "lt":return"<";case "gt":return">";case "quot":return'"';default:if("#"==c.charAt(0)){var b=Number("0"+c.substr(1));if(!isNaN(b))return String.fromCharCode(b)}return a}})};goog.string.HTML_ENTITY_PATTERN_=/&([^;\s<&]+);?/g;goog.string.whitespaceEscape=function(a,b){return goog.string.newLineToBr(a.replace(/  /g," &#160;"),b)};
+goog.string.preserveSpaces=function(a){return a.replace(/(^|[\n ]) /g,"$1"+goog.string.Unicode.NBSP)};goog.string.stripQuotes=function(a,b){for(var c=b.length,d=0;d<c;d++){var e=1==c?b:b.charAt(d);if(a.charAt(0)==e&&a.charAt(a.length-1)==e)return a.substring(1,a.length-1)}return a};goog.string.truncate=function(a,b,c){c&&(a=goog.string.unescapeEntities(a));a.length>b&&(a=a.substring(0,b-3)+"...");c&&(a=goog.string.htmlEscape(a));return a};
+goog.string.truncateMiddle=function(a,b,c,d){c&&(a=goog.string.unescapeEntities(a));if(d&&a.length>b){d>b&&(d=b);var e=a.length-d;a=a.substring(0,b-d)+"..."+a.substring(e)}else a.length>b&&(d=Math.floor(b/2),e=a.length-d,a=a.substring(0,d+b%2)+"..."+a.substring(e));c&&(a=goog.string.htmlEscape(a));return a};goog.string.specialEscapeChars_={"\x00":"\\0","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\x0B":"\\x0B",'"':'\\"',"\\":"\\\\","<":"<"};goog.string.jsEscapeCache_={"'":"\\'"};
+goog.string.quote=function(a){a=String(a);for(var b=['"'],c=0;c<a.length;c++){var d=a.charAt(c),e=d.charCodeAt(0);b[c+1]=goog.string.specialEscapeChars_[d]||(31<e&&127>e?d:goog.string.escapeChar(d))}b.push('"');return b.join("")};goog.string.escapeString=function(a){for(var b=[],c=0;c<a.length;c++)b[c]=goog.string.escapeChar(a.charAt(c));return b.join("")};
+goog.string.escapeChar=function(a){if(a in goog.string.jsEscapeCache_)return goog.string.jsEscapeCache_[a];if(a in goog.string.specialEscapeChars_)return goog.string.jsEscapeCache_[a]=goog.string.specialEscapeChars_[a];var b,c=a.charCodeAt(0);if(31<c&&127>c)b=a;else{if(256>c){if(b="\\x",16>c||256<c)b+="0"}else b="\\u",4096>c&&(b+="0");b+=c.toString(16).toUpperCase()}return goog.string.jsEscapeCache_[a]=b};goog.string.contains=function(a,b){return-1!=a.indexOf(b)};
+goog.string.caseInsensitiveContains=function(a,b){return goog.string.contains(a.toLowerCase(),b.toLowerCase())};goog.string.countOf=function(a,b){return a&&b?a.split(b).length-1:0};goog.string.removeAt=function(a,b,c){var d=a;0<=b&&b<a.length&&0<c&&(d=a.substr(0,b)+a.substr(b+c,a.length-b-c));return d};goog.string.remove=function(a,b){return a.replace(b,"")};goog.string.removeAll=function(a,b){var c=new RegExp(goog.string.regExpEscape(b),"g");return a.replace(c,"")};
+goog.string.replaceAll=function(a,b,c){b=new RegExp(goog.string.regExpEscape(b),"g");return a.replace(b,c.replace(/\$/g,"$$$$"))};goog.string.regExpEscape=function(a){return String(a).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")};goog.string.repeat=String.prototype.repeat?function(a,b){return a.repeat(b)}:function(a,b){return Array(b+1).join(a)};
+goog.string.padNumber=function(a,b,c){a=goog.isDef(c)?a.toFixed(c):String(a);c=a.indexOf(".");-1==c&&(c=a.length);return goog.string.repeat("0",Math.max(0,b-c))+a};goog.string.makeSafe=function(a){return null==a?"":String(a)};goog.string.buildString=function(a){return Array.prototype.join.call(arguments,"")};goog.string.getRandomString=function(){return Math.floor(2147483648*Math.random()).toString(36)+Math.abs(Math.floor(2147483648*Math.random())^goog.now()).toString(36)};
+goog.string.compareVersions=function(a,b){for(var c=0,d=goog.string.trim(String(a)).split("."),e=goog.string.trim(String(b)).split("."),f=Math.max(d.length,e.length),g=0;0==c&&g<f;g++){var h=d[g]||"",k=e[g]||"";do{h=/(\d*)(\D*)(.*)/.exec(h)||["","","",""];k=/(\d*)(\D*)(.*)/.exec(k)||["","","",""];if(0==h[0].length&&0==k[0].length)break;var c=0==h[1].length?0:parseInt(h[1],10),l=0==k[1].length?0:parseInt(k[1],10),c=goog.string.compareElements_(c,l)||goog.string.compareElements_(0==h[2].length,0==k[2].length)||
+goog.string.compareElements_(h[2],k[2]),h=h[3],k=k[3]}while(0==c)}return c};goog.string.compareElements_=function(a,b){return a<b?-1:a>b?1:0};goog.string.hashCode=function(a){for(var b=0,c=0;c<a.length;++c)b=31*b+a.charCodeAt(c)>>>0;return b};goog.string.uniqueStringCounter_=2147483648*Math.random()|0;goog.string.createUniqueString=function(){return"goog_"+goog.string.uniqueStringCounter_++};goog.string.toNumber=function(a){var b=Number(a);return 0==b&&goog.string.isEmptyOrWhitespace(a)?NaN:b};
+goog.string.isLowerCamelCase=function(a){return/^[a-z]+([A-Z][a-z]*)*$/.test(a)};goog.string.isUpperCamelCase=function(a){return/^([A-Z][a-z]*)+$/.test(a)};goog.string.toCamelCase=function(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})};goog.string.toSelectorCase=function(a){return String(a).replace(/([A-Z])/g,"-$1").toLowerCase()};
+goog.string.toTitleCase=function(a,b){var c=goog.isString(b)?goog.string.regExpEscape(b):"\\s";return a.replace(new RegExp("(^"+(c?"|["+c+"]+":"")+")([a-z])","g"),function(a,b,c){return b+c.toUpperCase()})};goog.string.capitalize=function(a){return String(a.charAt(0)).toUpperCase()+String(a.substr(1)).toLowerCase()};goog.string.parseInt=function(a){isFinite(a)&&(a=String(a));return goog.isString(a)?/^\s*-?0x/i.test(a)?parseInt(a,16):parseInt(a,10):NaN};
+goog.string.splitLimit=function(a,b,c){a=a.split(b);for(var d=[];0<c&&a.length;)d.push(a.shift()),c--;a.length&&d.push(a.join(b));return d};goog.string.lastComponent=function(a,b){if(b)"string"==typeof b&&(b=[b]);else return a;for(var c=-1,d=0;d<b.length;d++)if(""!=b[d]){var e=a.lastIndexOf(b[d]);e>c&&(c=e)}return-1==c?a:a.slice(c+1)};
+goog.string.editDistance=function(a,b){var c=[],d=[];if(a==b)return 0;if(!a.length||!b.length)return Math.max(a.length,b.length);for(var e=0;e<b.length+1;e++)c[e]=e;for(e=0;e<a.length;e++){d[0]=e+1;for(var f=0;f<b.length;f++)d[f+1]=Math.min(d[f]+1,c[f+1]+1,c[f]+Number(a[e]!=b[f]));for(f=0;f<c.length;f++)c[f]=d[f]}return d[b.length]};goog.asserts={};goog.asserts.ENABLE_ASSERTS=goog.DEBUG;goog.asserts.AssertionError=function(a,b){b.unshift(a);goog.debug.Error.call(this,goog.string.subs.apply(null,b));b.shift();this.messagePattern=a};goog.inherits(goog.asserts.AssertionError,goog.debug.Error);goog.asserts.AssertionError.prototype.name="AssertionError";goog.asserts.DEFAULT_ERROR_HANDLER=function(a){throw a;};goog.asserts.errorHandler_=goog.asserts.DEFAULT_ERROR_HANDLER;
+goog.asserts.doAssertFailure_=function(a,b,c,d){var e="Assertion failed";if(c)var e=e+(": "+c),f=d;else a&&(e+=": "+a,f=b);a=new goog.asserts.AssertionError(""+e,f||[]);goog.asserts.errorHandler_(a)};goog.asserts.setErrorHandler=function(a){goog.asserts.ENABLE_ASSERTS&&(goog.asserts.errorHandler_=a)};goog.asserts.assert=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!a&&goog.asserts.doAssertFailure_("",null,b,Array.prototype.slice.call(arguments,2));return a};
+goog.asserts.fail=function(a,b){goog.asserts.ENABLE_ASSERTS&&goog.asserts.errorHandler_(new goog.asserts.AssertionError("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1)))};goog.asserts.assertNumber=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!goog.isNumber(a)&&goog.asserts.doAssertFailure_("Expected number but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};
+goog.asserts.assertString=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!goog.isString(a)&&goog.asserts.doAssertFailure_("Expected string but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};goog.asserts.assertFunction=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!goog.isFunction(a)&&goog.asserts.doAssertFailure_("Expected function but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};
+goog.asserts.assertObject=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!goog.isObject(a)&&goog.asserts.doAssertFailure_("Expected object but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};goog.asserts.assertArray=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!goog.isArray(a)&&goog.asserts.doAssertFailure_("Expected array but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};
+goog.asserts.assertBoolean=function(a,b,c){goog.asserts.ENABLE_ASSERTS&&!goog.isBoolean(a)&&goog.asserts.doAssertFailure_("Expected boolean but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};goog.asserts.assertElement=function(a,b,c){!goog.asserts.ENABLE_ASSERTS||goog.isObject(a)&&a.nodeType==goog.dom.NodeType.ELEMENT||goog.asserts.doAssertFailure_("Expected Element but got %s: %s.",[goog.typeOf(a),a],b,Array.prototype.slice.call(arguments,2));return a};
+goog.asserts.assertInstanceof=function(a,b,c,d){!goog.asserts.ENABLE_ASSERTS||a instanceof b||goog.asserts.doAssertFailure_("Expected instanceof %s but got %s.",[goog.asserts.getType_(b),goog.asserts.getType_(a)],c,Array.prototype.slice.call(arguments,3));return a};goog.asserts.assertObjectPrototypeIsIntact=function(){for(var a in Object.prototype)goog.asserts.fail(a+" should not be enumerable in Object.prototype.")};
+goog.asserts.getType_=function(a){return a instanceof Function?a.displayName||a.name||"unknown type name":a instanceof Object?a.constructor.displayName||a.constructor.name||Object.prototype.toString.call(a):null===a?"null":typeof a};goog.array={};goog.NATIVE_ARRAY_PROTOTYPES=goog.TRUSTED_SITE;goog.array.ASSUME_NATIVE_FUNCTIONS=!1;goog.array.peek=function(a){return a[a.length-1]};goog.array.last=goog.array.peek;
+goog.array.indexOf=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.indexOf)?function(a,b,c){goog.asserts.assert(null!=a.length);return Array.prototype.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(goog.isString(a))return goog.isString(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1};
+goog.array.lastIndexOf=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.lastIndexOf)?function(a,b,c){goog.asserts.assert(null!=a.length);return Array.prototype.lastIndexOf.call(a,b,null==c?a.length-1:c)}:function(a,b,c){c=null==c?a.length-1:c;0>c&&(c=Math.max(0,a.length+c));if(goog.isString(a))return goog.isString(b)&&1==b.length?a.lastIndexOf(b,c):-1;for(;0<=c;c--)if(c in a&&a[c]===b)return c;return-1};
+goog.array.forEach=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.forEach)?function(a,b,c){goog.asserts.assert(null!=a.length);Array.prototype.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)};goog.array.forEachRight=function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,d=d-1;0<=d;--d)d in e&&b.call(c,e[d],d,a)};
+goog.array.filter=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.filter)?function(a,b,c){goog.asserts.assert(null!=a.length);return Array.prototype.filter.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=[],f=0,g=goog.isString(a)?a.split(""):a,h=0;h<d;h++)if(h in g){var k=g[h];b.call(c,k,h,a)&&(e[f++]=k)}return e};
+goog.array.map=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.map)?function(a,b,c){goog.asserts.assert(null!=a.length);return Array.prototype.map.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=Array(d),f=goog.isString(a)?a.split(""):a,g=0;g<d;g++)g in f&&(e[g]=b.call(c,f[g],g,a));return e};
+goog.array.reduce=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.reduce)?function(a,b,c,d){goog.asserts.assert(null!=a.length);d&&(b=goog.bind(b,d));return Array.prototype.reduce.call(a,b,c)}:function(a,b,c,d){var e=c;goog.array.forEach(a,function(c,g){e=b.call(d,e,c,g,a)});return e};
+goog.array.reduceRight=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.reduceRight)?function(a,b,c,d){goog.asserts.assert(null!=a.length);goog.asserts.assert(null!=b);d&&(b=goog.bind(b,d));return Array.prototype.reduceRight.call(a,b,c)}:function(a,b,c,d){var e=c;goog.array.forEachRight(a,function(c,g){e=b.call(d,e,c,g,a)});return e};
+goog.array.some=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.some)?function(a,b,c){goog.asserts.assert(null!=a.length);return Array.prototype.some.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return!0;return!1};
+goog.array.every=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.every)?function(a,b,c){goog.asserts.assert(null!=a.length);return Array.prototype.every.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&!b.call(c,e[f],f,a))return!1;return!0};goog.array.count=function(a,b,c){var d=0;goog.array.forEach(a,function(a,f,g){b.call(c,a,f,g)&&++d},c);return d};
+goog.array.find=function(a,b,c){b=goog.array.findIndex(a,b,c);return 0>b?null:goog.isString(a)?a.charAt(b):a[b]};goog.array.findIndex=function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return f;return-1};goog.array.findRight=function(a,b,c){b=goog.array.findIndexRight(a,b,c);return 0>b?null:goog.isString(a)?a.charAt(b):a[b]};
+goog.array.findIndexRight=function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,d=d-1;0<=d;d--)if(d in e&&b.call(c,e[d],d,a))return d;return-1};goog.array.contains=function(a,b){return 0<=goog.array.indexOf(a,b)};goog.array.isEmpty=function(a){return 0==a.length};goog.array.clear=function(a){if(!goog.isArray(a))for(var b=a.length-1;0<=b;b--)delete a[b];a.length=0};goog.array.insert=function(a,b){goog.array.contains(a,b)||a.push(b)};
+goog.array.insertAt=function(a,b,c){goog.array.splice(a,c,0,b)};goog.array.insertArrayAt=function(a,b,c){goog.partial(goog.array.splice,a,c,0).apply(null,b)};goog.array.insertBefore=function(a,b,c){var d;2==arguments.length||0>(d=goog.array.indexOf(a,c))?a.push(b):goog.array.insertAt(a,b,d)};goog.array.remove=function(a,b){var c=goog.array.indexOf(a,b),d;(d=0<=c)&&goog.array.removeAt(a,c);return d};
+goog.array.removeLast=function(a,b){var c=goog.array.lastIndexOf(a,b);return 0<=c?(goog.array.removeAt(a,c),!0):!1};goog.array.removeAt=function(a,b){goog.asserts.assert(null!=a.length);return 1==Array.prototype.splice.call(a,b,1).length};goog.array.removeIf=function(a,b,c){b=goog.array.findIndex(a,b,c);return 0<=b?(goog.array.removeAt(a,b),!0):!1};goog.array.removeAllIf=function(a,b,c){var d=0;goog.array.forEachRight(a,function(e,f){b.call(c,e,f,a)&&goog.array.removeAt(a,f)&&d++});return d};
+goog.array.concat=function(a){return Array.prototype.concat.apply(Array.prototype,arguments)};goog.array.join=function(a){return Array.prototype.concat.apply(Array.prototype,arguments)};goog.array.toArray=function(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]};goog.array.clone=goog.array.toArray;
+goog.array.extend=function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(goog.isArrayLike(d)){var e=a.length||0,f=d.length||0;a.length=e+f;for(var g=0;g<f;g++)a[e+g]=d[g]}else a.push(d)}};goog.array.splice=function(a,b,c,d){goog.asserts.assert(null!=a.length);return Array.prototype.splice.apply(a,goog.array.slice(arguments,1))};
+goog.array.slice=function(a,b,c){goog.asserts.assert(null!=a.length);return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};goog.array.removeDuplicates=function(a,b,c){b=b||a;var d=function(a){return goog.isObject(a)?"o"+goog.getUid(a):(typeof a).charAt(0)+a};c=c||d;for(var d={},e=0,f=0;f<a.length;){var g=a[f++],h=c(g);Object.prototype.hasOwnProperty.call(d,h)||(d[h]=!0,b[e++]=g)}b.length=e};
+goog.array.binarySearch=function(a,b,c){return goog.array.binarySearch_(a,c||goog.array.defaultCompare,!1,b)};goog.array.binarySelect=function(a,b,c){return goog.array.binarySearch_(a,b,!0,void 0,c)};goog.array.binarySearch_=function(a,b,c,d,e){for(var f=0,g=a.length,h;f<g;){var k=f+g>>1,l;l=c?b.call(e,a[k],k,a):b(d,a[k]);0<l?f=k+1:(g=k,h=!l)}return h?f:~f};goog.array.sort=function(a,b){a.sort(b||goog.array.defaultCompare)};
+goog.array.stableSort=function(a,b){for(var c=Array(a.length),d=0;d<a.length;d++)c[d]={index:d,value:a[d]};var e=b||goog.array.defaultCompare;goog.array.sort(c,function(a,b){return e(a.value,b.value)||a.index-b.index});for(d=0;d<a.length;d++)a[d]=c[d].value};goog.array.sortByKey=function(a,b,c){var d=c||goog.array.defaultCompare;goog.array.sort(a,function(a,c){return d(b(a),b(c))})};goog.array.sortObjectsByKey=function(a,b,c){goog.array.sortByKey(a,function(a){return a[b]},c)};
+goog.array.isSorted=function(a,b,c){b=b||goog.array.defaultCompare;for(var d=1;d<a.length;d++){var e=b(a[d-1],a[d]);if(0<e||0==e&&c)return!1}return!0};goog.array.equals=function(a,b,c){if(!goog.isArrayLike(a)||!goog.isArrayLike(b)||a.length!=b.length)return!1;var d=a.length;c=c||goog.array.defaultCompareEquality;for(var e=0;e<d;e++)if(!c(a[e],b[e]))return!1;return!0};
+goog.array.compare3=function(a,b,c){c=c||goog.array.defaultCompare;for(var d=Math.min(a.length,b.length),e=0;e<d;e++){var f=c(a[e],b[e]);if(0!=f)return f}return goog.array.defaultCompare(a.length,b.length)};goog.array.defaultCompare=function(a,b){return a>b?1:a<b?-1:0};goog.array.inverseDefaultCompare=function(a,b){return-goog.array.defaultCompare(a,b)};goog.array.defaultCompareEquality=function(a,b){return a===b};
+goog.array.binaryInsert=function(a,b,c){c=goog.array.binarySearch(a,b,c);return 0>c?(goog.array.insertAt(a,b,-(c+1)),!0):!1};goog.array.binaryRemove=function(a,b,c){b=goog.array.binarySearch(a,b,c);return 0<=b?goog.array.removeAt(a,b):!1};goog.array.bucket=function(a,b,c){for(var d={},e=0;e<a.length;e++){var f=a[e],g=b.call(c,f,e,a);goog.isDef(g)&&(d[g]||(d[g]=[])).push(f)}return d};goog.array.toObject=function(a,b,c){var d={};goog.array.forEach(a,function(e,f){d[b.call(c,e,f,a)]=e});return d};
+goog.array.range=function(a,b,c){var d=[],e=0,f=a;c=c||1;void 0!==b&&(e=a,f=b);if(0>c*(f-e))return[];if(0<c)for(a=e;a<f;a+=c)d.push(a);else for(a=e;a>f;a+=c)d.push(a);return d};goog.array.repeat=function(a,b){for(var c=[],d=0;d<b;d++)c[d]=a;return c};goog.array.flatten=function(a){for(var b=[],c=0;c<arguments.length;c++){var d=arguments[c];if(goog.isArray(d))for(var e=0;e<d.length;e+=8192)for(var f=goog.array.slice(d,e,e+8192),f=goog.array.flatten.apply(null,f),g=0;g<f.length;g++)b.push(f[g]);else b.push(d)}return b};
+goog.array.rotate=function(a,b){goog.asserts.assert(null!=a.length);a.length&&(b%=a.length,0<b?Array.prototype.unshift.apply(a,a.splice(-b,b)):0>b&&Array.prototype.push.apply(a,a.splice(0,-b)));return a};goog.array.moveItem=function(a,b,c){goog.asserts.assert(0<=b&&b<a.length);goog.asserts.assert(0<=c&&c<a.length);b=Array.prototype.splice.call(a,b,1);Array.prototype.splice.call(a,c,0,b[0])};
+goog.array.zip=function(a){if(!arguments.length)return[];for(var b=[],c=arguments[0].length,d=1;d<arguments.length;d++)arguments[d].length<c&&(c=arguments[d].length);for(d=0;d<c;d++){for(var e=[],f=0;f<arguments.length;f++)e.push(arguments[f][d]);b.push(e)}return b};goog.array.shuffle=function(a,b){for(var c=b||Math.random,d=a.length-1;0<d;d--){var e=Math.floor(c()*(d+1)),f=a[d];a[d]=a[e];a[e]=f}};goog.array.copyByIndex=function(a,b){var c=[];goog.array.forEach(b,function(b){c.push(a[b])});return c};
+goog.array.concatMap=function(a,b,c){return goog.array.concat.apply([],goog.array.map(a,b,c))};goog.math={};goog.math.randomInt=function(a){return Math.floor(Math.random()*a)};goog.math.uniformRandom=function(a,b){return a+Math.random()*(b-a)};goog.math.clamp=function(a,b,c){return Math.min(Math.max(a,b),c)};goog.math.modulo=function(a,b){var c=a%b;return 0>c*b?c+b:c};goog.math.lerp=function(a,b,c){return a+c*(b-a)};goog.math.nearlyEquals=function(a,b,c){return Math.abs(a-b)<=(c||1E-6)};goog.math.standardAngle=function(a){return goog.math.modulo(a,360)};
+goog.math.standardAngleInRadians=function(a){return goog.math.modulo(a,2*Math.PI)};goog.math.toRadians=function(a){return a*Math.PI/180};goog.math.toDegrees=function(a){return 180*a/Math.PI};goog.math.angleDx=function(a,b){return b*Math.cos(goog.math.toRadians(a))};goog.math.angleDy=function(a,b){return b*Math.sin(goog.math.toRadians(a))};goog.math.angle=function(a,b,c,d){return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(d-b,c-a)))};
+goog.math.angleDifference=function(a,b){var c=goog.math.standardAngle(b)-goog.math.standardAngle(a);180<c?c-=360:-180>=c&&(c=360+c);return c};goog.math.sign=function(a){return 0<a?1:0>a?-1:a};
+goog.math.longestCommonSubsequence=function(a,b,c,d){c=c||function(a,b){return a==b};d=d||function(b,c){return a[b]};for(var e=a.length,f=b.length,g=[],h=0;h<e+1;h++)g[h]=[],g[h][0]=0;for(var k=0;k<f+1;k++)g[0][k]=0;for(h=1;h<=e;h++)for(k=1;k<=f;k++)c(a[h-1],b[k-1])?g[h][k]=g[h-1][k-1]+1:g[h][k]=Math.max(g[h-1][k],g[h][k-1]);for(var l=[],h=e,k=f;0<h&&0<k;)c(a[h-1],b[k-1])?(l.unshift(d(h-1,k-1)),h--,k--):g[h-1][k]>g[h][k-1]?h--:k--;return l};
+goog.math.sum=function(a){return goog.array.reduce(arguments,function(a,c){return a+c},0)};goog.math.average=function(a){return goog.math.sum.apply(null,arguments)/arguments.length};goog.math.sampleVariance=function(a){var b=arguments.length;if(2>b)return 0;var c=goog.math.average.apply(null,arguments);return goog.math.sum.apply(null,goog.array.map(arguments,function(a){return Math.pow(a-c,2)}))/(b-1)};goog.math.standardDeviation=function(a){return Math.sqrt(goog.math.sampleVariance.apply(null,arguments))};
+goog.math.isInt=function(a){return isFinite(a)&&0==a%1};goog.math.isFiniteNumber=function(a){return isFinite(a)&&!isNaN(a)};goog.math.isNegativeZero=function(a){return 0==a&&0>1/a};goog.math.log10Floor=function(a){if(0<a){var b=Math.round(Math.log(a)*Math.LOG10E);return b-(parseFloat("1e"+b)>a?1:0)}return 0==a?-Infinity:NaN};goog.math.safeFloor=function(a,b){goog.asserts.assert(!goog.isDef(b)||0<b);return Math.floor(a+(b||2E-15))};
+goog.math.safeCeil=function(a,b){goog.asserts.assert(!goog.isDef(b)||0<b);return Math.ceil(a-(b||2E-15))};goog.labs={};goog.labs.userAgent={};goog.labs.userAgent.util={};goog.labs.userAgent.util.getNativeUserAgentString_=function(){var a=goog.labs.userAgent.util.getNavigator_();return a&&(a=a.userAgent)?a:""};goog.labs.userAgent.util.getNavigator_=function(){return goog.global.navigator};goog.labs.userAgent.util.userAgent_=goog.labs.userAgent.util.getNativeUserAgentString_();goog.labs.userAgent.util.setUserAgent=function(a){goog.labs.userAgent.util.userAgent_=a||goog.labs.userAgent.util.getNativeUserAgentString_()};
+goog.labs.userAgent.util.getUserAgent=function(){return goog.labs.userAgent.util.userAgent_};goog.labs.userAgent.util.matchUserAgent=function(a){var b=goog.labs.userAgent.util.getUserAgent();return goog.string.contains(b,a)};goog.labs.userAgent.util.matchUserAgentIgnoreCase=function(a){var b=goog.labs.userAgent.util.getUserAgent();return goog.string.caseInsensitiveContains(b,a)};
+goog.labs.userAgent.util.extractVersionTuples=function(a){for(var b=RegExp("(\\w[\\w ]+)/([^\\s]+)\\s*(?:\\((.*?)\\))?","g"),c=[],d;d=b.exec(a);)c.push([d[1],d[2],d[3]||void 0]);return c};goog.object={};goog.object.is=function(a,b){return a===b?0!==a||1/a===1/b:a!==a&&b!==b};goog.object.forEach=function(a,b,c){for(var d in a)b.call(c,a[d],d,a)};goog.object.filter=function(a,b,c){var d={},e;for(e in a)b.call(c,a[e],e,a)&&(d[e]=a[e]);return d};goog.object.map=function(a,b,c){var d={},e;for(e in a)d[e]=b.call(c,a[e],e,a);return d};goog.object.some=function(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return!0;return!1};
+goog.object.every=function(a,b,c){for(var d in a)if(!b.call(c,a[d],d,a))return!1;return!0};goog.object.getCount=function(a){var b=0,c;for(c in a)b++;return b};goog.object.getAnyKey=function(a){for(var b in a)return b};goog.object.getAnyValue=function(a){for(var b in a)return a[b]};goog.object.contains=function(a,b){return goog.object.containsValue(a,b)};goog.object.getValues=function(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b};
+goog.object.getKeys=function(a){var b=[],c=0,d;for(d in a)b[c++]=d;return b};goog.object.getValueByKeys=function(a,b){for(var c=goog.isArrayLike(b),d=c?b:arguments,c=c?0:1;c<d.length&&(a=a[d[c]],goog.isDef(a));c++);return a};goog.object.containsKey=function(a,b){return null!==a&&b in a};goog.object.containsValue=function(a,b){for(var c in a)if(a[c]==b)return!0;return!1};goog.object.findKey=function(a,b,c){for(var d in a)if(b.call(c,a[d],d,a))return d};
+goog.object.findValue=function(a,b,c){return(b=goog.object.findKey(a,b,c))&&a[b]};goog.object.isEmpty=function(a){for(var b in a)return!1;return!0};goog.object.clear=function(a){for(var b in a)delete a[b]};goog.object.remove=function(a,b){var c;(c=b in a)&&delete a[b];return c};goog.object.add=function(a,b,c){if(null!==a&&b in a)throw Error('The object already contains the key "'+b+'"');goog.object.set(a,b,c)};goog.object.get=function(a,b,c){return null!==a&&b in a?a[b]:c};
+goog.object.set=function(a,b,c){a[b]=c};goog.object.setIfUndefined=function(a,b,c){return b in a?a[b]:a[b]=c};goog.object.setWithReturnValueIfNotSet=function(a,b,c){if(b in a)return a[b];c=c();return a[b]=c};goog.object.equals=function(a,b){for(var c in a)if(!(c in b)||a[c]!==b[c])return!1;for(c in b)if(!(c in a))return!1;return!0};goog.object.clone=function(a){var b={},c;for(c in a)b[c]=a[c];return b};
+goog.object.unsafeClone=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if(goog.isFunction(a.clone))return a.clone();var b="array"==b?[]:{},c;for(c in a)b[c]=goog.object.unsafeClone(a[c]);return b}return a};goog.object.transpose=function(a){var b={},c;for(c in a)b[a[c]]=c;return b};goog.object.PROTOTYPE_FIELDS_="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");
+goog.object.extend=function(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var f=0;f<goog.object.PROTOTYPE_FIELDS_.length;f++)c=goog.object.PROTOTYPE_FIELDS_[f],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};
+goog.object.create=function(a){var b=arguments.length;if(1==b&&goog.isArray(arguments[0]))return goog.object.create.apply(null,arguments[0]);if(b%2)throw Error("Uneven number of arguments");for(var c={},d=0;d<b;d+=2)c[arguments[d]]=arguments[d+1];return c};goog.object.createSet=function(a){var b=arguments.length;if(1==b&&goog.isArray(arguments[0]))return goog.object.createSet.apply(null,arguments[0]);for(var c={},d=0;d<b;d++)c[arguments[d]]=!0;return c};
+goog.object.createImmutableView=function(a){var b=a;Object.isFrozen&&!Object.isFrozen(a)&&(b=Object.create(a),Object.freeze(b));return b};goog.object.isImmutableView=function(a){return!!Object.isFrozen&&Object.isFrozen(a)};goog.object.getAllPropertyNames=function(a,b){if(!a)return[];if(!Object.getOwnPropertyNames||!Object.getPrototypeOf)return goog.object.getKeys(a);for(var c={},d=a;d&&(d!==Object.prototype||b);){for(var e=Object.getOwnPropertyNames(d),f=0;f<e.length;f++)c[e[f]]=!0;d=Object.getPrototypeOf(d)}return goog.object.getKeys(c)};goog.labs.userAgent.browser={};goog.labs.userAgent.browser.matchOpera_=function(){return goog.labs.userAgent.util.matchUserAgent("Opera")};goog.labs.userAgent.browser.matchIE_=function(){return goog.labs.userAgent.util.matchUserAgent("Trident")||goog.labs.userAgent.util.matchUserAgent("MSIE")};goog.labs.userAgent.browser.matchEdge_=function(){return goog.labs.userAgent.util.matchUserAgent("Edge")};goog.labs.userAgent.browser.matchFirefox_=function(){return goog.labs.userAgent.util.matchUserAgent("Firefox")};
+goog.labs.userAgent.browser.matchSafari_=function(){return goog.labs.userAgent.util.matchUserAgent("Safari")&&!(goog.labs.userAgent.browser.matchChrome_()||goog.labs.userAgent.browser.matchCoast_()||goog.labs.userAgent.browser.matchOpera_()||goog.labs.userAgent.browser.matchEdge_()||goog.labs.userAgent.browser.isSilk()||goog.labs.userAgent.util.matchUserAgent("Android"))};goog.labs.userAgent.browser.matchCoast_=function(){return goog.labs.userAgent.util.matchUserAgent("Coast")};
+goog.labs.userAgent.browser.matchIosWebview_=function(){return(goog.labs.userAgent.util.matchUserAgent("iPad")||goog.labs.userAgent.util.matchUserAgent("iPhone"))&&!goog.labs.userAgent.browser.matchSafari_()&&!goog.labs.userAgent.browser.matchChrome_()&&!goog.labs.userAgent.browser.matchCoast_()&&goog.labs.userAgent.util.matchUserAgent("AppleWebKit")};
+goog.labs.userAgent.browser.matchChrome_=function(){return(goog.labs.userAgent.util.matchUserAgent("Chrome")||goog.labs.userAgent.util.matchUserAgent("CriOS"))&&!goog.labs.userAgent.browser.matchEdge_()};goog.labs.userAgent.browser.matchAndroidBrowser_=function(){return goog.labs.userAgent.util.matchUserAgent("Android")&&!(goog.labs.userAgent.browser.isChrome()||goog.labs.userAgent.browser.isFirefox()||goog.labs.userAgent.browser.isOpera()||goog.labs.userAgent.browser.isSilk())};
+goog.labs.userAgent.browser.isOpera=goog.labs.userAgent.browser.matchOpera_;goog.labs.userAgent.browser.isIE=goog.labs.userAgent.browser.matchIE_;goog.labs.userAgent.browser.isEdge=goog.labs.userAgent.browser.matchEdge_;goog.labs.userAgent.browser.isFirefox=goog.labs.userAgent.browser.matchFirefox_;goog.labs.userAgent.browser.isSafari=goog.labs.userAgent.browser.matchSafari_;goog.labs.userAgent.browser.isCoast=goog.labs.userAgent.browser.matchCoast_;goog.labs.userAgent.browser.isIosWebview=goog.labs.userAgent.browser.matchIosWebview_;
+goog.labs.userAgent.browser.isChrome=goog.labs.userAgent.browser.matchChrome_;goog.labs.userAgent.browser.isAndroidBrowser=goog.labs.userAgent.browser.matchAndroidBrowser_;goog.labs.userAgent.browser.isSilk=function(){return goog.labs.userAgent.util.matchUserAgent("Silk")};
+goog.labs.userAgent.browser.getVersion=function(){function a(a){a=goog.array.find(a,d);return c[a]||""}var b=goog.labs.userAgent.util.getUserAgent();if(goog.labs.userAgent.browser.isIE())return goog.labs.userAgent.browser.getIEVersion_(b);var b=goog.labs.userAgent.util.extractVersionTuples(b),c={};goog.array.forEach(b,function(a){c[a[0]]=a[1]});var d=goog.partial(goog.object.containsKey,c);return goog.labs.userAgent.browser.isOpera()?a(["Version","Opera"]):goog.labs.userAgent.browser.isEdge()?a(["Edge"]):
+goog.labs.userAgent.browser.isChrome()?a(["Chrome","CriOS"]):(b=b[2])&&b[1]||""};goog.labs.userAgent.browser.isVersionOrHigher=function(a){return 0<=goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),a)};
+goog.labs.userAgent.browser.getIEVersion_=function(a){var b=/rv: *([\d\.]*)/.exec(a);if(b&&b[1])return b[1];var b="",c=/MSIE +([\d\.]+)/.exec(a);if(c&&c[1])if(a=/Trident\/(\d.\d)/.exec(a),"7.0"==c[1])if(a&&a[1])switch(a[1]){case "4.0":b="8.0";break;case "5.0":b="9.0";break;case "6.0":b="10.0";break;case "7.0":b="11.0"}else b="7.0";else b=c[1];return b};goog.labs.userAgent.engine={};goog.labs.userAgent.engine.isPresto=function(){return goog.labs.userAgent.util.matchUserAgent("Presto")};goog.labs.userAgent.engine.isTrident=function(){return goog.labs.userAgent.util.matchUserAgent("Trident")||goog.labs.userAgent.util.matchUserAgent("MSIE")};goog.labs.userAgent.engine.isEdge=function(){return goog.labs.userAgent.util.matchUserAgent("Edge")};
+goog.labs.userAgent.engine.isWebKit=function(){return goog.labs.userAgent.util.matchUserAgentIgnoreCase("WebKit")&&!goog.labs.userAgent.engine.isEdge()};goog.labs.userAgent.engine.isGecko=function(){return goog.labs.userAgent.util.matchUserAgent("Gecko")&&!goog.labs.userAgent.engine.isWebKit()&&!goog.labs.userAgent.engine.isTrident()&&!goog.labs.userAgent.engine.isEdge()};
+goog.labs.userAgent.engine.getVersion=function(){var a=goog.labs.userAgent.util.getUserAgent();if(a){var a=goog.labs.userAgent.util.extractVersionTuples(a),b=goog.labs.userAgent.engine.getEngineTuple_(a);if(b)return"Gecko"==b[0]?goog.labs.userAgent.engine.getVersionForKey_(a,"Firefox"):b[1];var a=a[0],c;if(a&&(c=a[2])&&(c=/Trident\/([^\s;]+)/.exec(c)))return c[1]}return""};
+goog.labs.userAgent.engine.getEngineTuple_=function(a){if(!goog.labs.userAgent.engine.isEdge())return a[1];for(var b=0;b<a.length;b++){var c=a[b];if("Edge"==c[0])return c}};goog.labs.userAgent.engine.isVersionOrHigher=function(a){return 0<=goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),a)};goog.labs.userAgent.engine.getVersionForKey_=function(a,b){var c=goog.array.find(a,function(a){return b==a[0]});return c&&c[1]||""};goog.labs.userAgent.platform={};goog.labs.userAgent.platform.isAndroid=function(){return goog.labs.userAgent.util.matchUserAgent("Android")};goog.labs.userAgent.platform.isIpod=function(){return goog.labs.userAgent.util.matchUserAgent("iPod")};goog.labs.userAgent.platform.isIphone=function(){return goog.labs.userAgent.util.matchUserAgent("iPhone")&&!goog.labs.userAgent.util.matchUserAgent("iPod")&&!goog.labs.userAgent.util.matchUserAgent("iPad")};goog.labs.userAgent.platform.isIpad=function(){return goog.labs.userAgent.util.matchUserAgent("iPad")};
+goog.labs.userAgent.platform.isIos=function(){return goog.labs.userAgent.platform.isIphone()||goog.labs.userAgent.platform.isIpad()||goog.labs.userAgent.platform.isIpod()};goog.labs.userAgent.platform.isMacintosh=function(){return goog.labs.userAgent.util.matchUserAgent("Macintosh")};goog.labs.userAgent.platform.isLinux=function(){return goog.labs.userAgent.util.matchUserAgent("Linux")};goog.labs.userAgent.platform.isWindows=function(){return goog.labs.userAgent.util.matchUserAgent("Windows")};
+goog.labs.userAgent.platform.isChromeOS=function(){return goog.labs.userAgent.util.matchUserAgent("CrOS")};
+goog.labs.userAgent.platform.getVersion=function(){var a=goog.labs.userAgent.util.getUserAgent(),b="";goog.labs.userAgent.platform.isWindows()?(b=/Windows (?:NT|Phone) ([0-9.]+)/,b=(a=b.exec(a))?a[1]:"0.0"):goog.labs.userAgent.platform.isIos()?(b=/(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/,b=(a=b.exec(a))&&a[1].replace(/_/g,".")):goog.labs.userAgent.platform.isMacintosh()?(b=/Mac OS X ([0-9_.]+)/,b=(a=b.exec(a))?a[1].replace(/_/g,"."):"10"):goog.labs.userAgent.platform.isAndroid()?(b=/Android\s+([^\);]+)(\)|;)/,
+b=(a=b.exec(a))&&a[1]):goog.labs.userAgent.platform.isChromeOS()&&(b=/(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/,b=(a=b.exec(a))&&a[1]);return b||""};goog.labs.userAgent.platform.isVersionOrHigher=function(a){return 0<=goog.string.compareVersions(goog.labs.userAgent.platform.getVersion(),a)};goog.reflect={};goog.reflect.object=function(a,b){return b};goog.reflect.objectProperty=function(a,b){return a};goog.reflect.sinkValue=function(a){goog.reflect.sinkValue[" "](a);return a};goog.reflect.sinkValue[" "]=goog.nullFunction;goog.reflect.canAccessProperty=function(a,b){try{return goog.reflect.sinkValue(a[b]),!0}catch(c){}return!1};goog.reflect.cache=function(a,b,c,d){d=d?d(b):b;return Object.prototype.hasOwnProperty.call(a,d)?a[d]:a[d]=c(b)};goog.userAgent={};goog.userAgent.ASSUME_IE=!1;goog.userAgent.ASSUME_EDGE=!1;goog.userAgent.ASSUME_GECKO=!1;goog.userAgent.ASSUME_WEBKIT=!1;goog.userAgent.ASSUME_MOBILE_WEBKIT=!1;goog.userAgent.ASSUME_OPERA=!1;goog.userAgent.ASSUME_ANY_VERSION=!1;goog.userAgent.BROWSER_KNOWN_=goog.userAgent.ASSUME_IE||goog.userAgent.ASSUME_EDGE||goog.userAgent.ASSUME_GECKO||goog.userAgent.ASSUME_MOBILE_WEBKIT||goog.userAgent.ASSUME_WEBKIT||goog.userAgent.ASSUME_OPERA;goog.userAgent.getUserAgentString=function(){return goog.labs.userAgent.util.getUserAgent()};
+goog.userAgent.getNavigator=function(){return goog.global.navigator||null};goog.userAgent.OPERA=goog.userAgent.BROWSER_KNOWN_?goog.userAgent.ASSUME_OPERA:goog.labs.userAgent.browser.isOpera();goog.userAgent.IE=goog.userAgent.BROWSER_KNOWN_?goog.userAgent.ASSUME_IE:goog.labs.userAgent.browser.isIE();goog.userAgent.EDGE=goog.userAgent.BROWSER_KNOWN_?goog.userAgent.ASSUME_EDGE:goog.labs.userAgent.engine.isEdge();goog.userAgent.EDGE_OR_IE=goog.userAgent.EDGE||goog.userAgent.IE;
+goog.userAgent.GECKO=goog.userAgent.BROWSER_KNOWN_?goog.userAgent.ASSUME_GECKO:goog.labs.userAgent.engine.isGecko();goog.userAgent.WEBKIT=goog.userAgent.BROWSER_KNOWN_?goog.userAgent.ASSUME_WEBKIT||goog.userAgent.ASSUME_MOBILE_WEBKIT:goog.labs.userAgent.engine.isWebKit();goog.userAgent.isMobile_=function(){return goog.userAgent.WEBKIT&&goog.labs.userAgent.util.matchUserAgent("Mobile")};goog.userAgent.MOBILE=goog.userAgent.ASSUME_MOBILE_WEBKIT||goog.userAgent.isMobile_();goog.userAgent.SAFARI=goog.userAgent.WEBKIT;
+goog.userAgent.determinePlatform_=function(){var a=goog.userAgent.getNavigator();return a&&a.platform||""};goog.userAgent.PLATFORM=goog.userAgent.determinePlatform_();goog.userAgent.ASSUME_MAC=!1;goog.userAgent.ASSUME_WINDOWS=!1;goog.userAgent.ASSUME_LINUX=!1;goog.userAgent.ASSUME_X11=!1;goog.userAgent.ASSUME_ANDROID=!1;goog.userAgent.ASSUME_IPHONE=!1;goog.userAgent.ASSUME_IPAD=!1;goog.userAgent.ASSUME_IPOD=!1;
+goog.userAgent.PLATFORM_KNOWN_=goog.userAgent.ASSUME_MAC||goog.userAgent.ASSUME_WINDOWS||goog.userAgent.ASSUME_LINUX||goog.userAgent.ASSUME_X11||goog.userAgent.ASSUME_ANDROID||goog.userAgent.ASSUME_IPHONE||goog.userAgent.ASSUME_IPAD||goog.userAgent.ASSUME_IPOD;goog.userAgent.MAC=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_MAC:goog.labs.userAgent.platform.isMacintosh();goog.userAgent.WINDOWS=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_WINDOWS:goog.labs.userAgent.platform.isWindows();
+goog.userAgent.isLegacyLinux_=function(){return goog.labs.userAgent.platform.isLinux()||goog.labs.userAgent.platform.isChromeOS()};goog.userAgent.LINUX=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_LINUX:goog.userAgent.isLegacyLinux_();goog.userAgent.isX11_=function(){var a=goog.userAgent.getNavigator();return!!a&&goog.string.contains(a.appVersion||"","X11")};goog.userAgent.X11=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_X11:goog.userAgent.isX11_();
+goog.userAgent.ANDROID=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_ANDROID:goog.labs.userAgent.platform.isAndroid();goog.userAgent.IPHONE=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_IPHONE:goog.labs.userAgent.platform.isIphone();goog.userAgent.IPAD=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_IPAD:goog.labs.userAgent.platform.isIpad();goog.userAgent.IPOD=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_IPOD:goog.labs.userAgent.platform.isIpod();
+goog.userAgent.IOS=goog.userAgent.PLATFORM_KNOWN_?goog.userAgent.ASSUME_IPHONE||goog.userAgent.ASSUME_IPAD||goog.userAgent.ASSUME_IPOD:goog.labs.userAgent.platform.isIos();goog.userAgent.determineVersion_=function(){var a="",b=goog.userAgent.getVersionRegexResult_();b&&(a=b?b[1]:"");return goog.userAgent.IE&&(b=goog.userAgent.getDocumentMode_(),null!=b&&b>parseFloat(a))?String(b):a};
+goog.userAgent.getVersionRegexResult_=function(){var a=goog.userAgent.getUserAgentString();if(goog.userAgent.GECKO)return/rv\:([^\);]+)(\)|;)/.exec(a);if(goog.userAgent.EDGE)return/Edge\/([\d\.]+)/.exec(a);if(goog.userAgent.IE)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(goog.userAgent.WEBKIT)return/WebKit\/(\S+)/.exec(a);if(goog.userAgent.OPERA)return/(?:Version)[ \/]?(\S+)/.exec(a)};goog.userAgent.getDocumentMode_=function(){var a=goog.global.document;return a?a.documentMode:void 0};
+goog.userAgent.VERSION=goog.userAgent.determineVersion_();goog.userAgent.compare=function(a,b){return goog.string.compareVersions(a,b)};goog.userAgent.isVersionOrHigherCache_={};goog.userAgent.isVersionOrHigher=function(a){return goog.userAgent.ASSUME_ANY_VERSION||goog.reflect.cache(goog.userAgent.isVersionOrHigherCache_,a,function(){return 0<=goog.string.compareVersions(goog.userAgent.VERSION,a)})};goog.userAgent.isVersion=goog.userAgent.isVersionOrHigher;
+goog.userAgent.isDocumentModeOrHigher=function(a){return Number(goog.userAgent.DOCUMENT_MODE)>=a};goog.userAgent.isDocumentMode=goog.userAgent.isDocumentModeOrHigher;goog.userAgent.DOCUMENT_MODE=function(){var a=goog.global.document,b=goog.userAgent.getDocumentMode_();if(a&&goog.userAgent.IE)return b||("CSS1Compat"==a.compatMode?parseInt(goog.userAgent.VERSION,10):5)}();goog.dom.BrowserFeature={CAN_ADD_NAME_OR_TYPE_ATTRIBUTES:!goog.userAgent.IE||goog.userAgent.isDocumentModeOrHigher(9),CAN_USE_CHILDREN_ATTRIBUTE:!goog.userAgent.GECKO&&!goog.userAgent.IE||goog.userAgent.IE&&goog.userAgent.isDocumentModeOrHigher(9)||goog.userAgent.GECKO&&goog.userAgent.isVersionOrHigher("1.9.1"),CAN_USE_INNER_TEXT:goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("9"),CAN_USE_PARENT_ELEMENT_PROPERTY:goog.userAgent.IE||goog.userAgent.OPERA||goog.userAgent.WEBKIT,INNER_HTML_NEEDS_SCOPED_ELEMENT:goog.userAgent.IE,
+LEGACY_IE_RANGES:goog.userAgent.IE&&!goog.userAgent.isDocumentModeOrHigher(9)};goog.dom.TagName=function(a){this.tagName_=a};goog.dom.TagName.prototype.toString=function(){return this.tagName_};goog.dom.TagName.A=new goog.dom.TagName("A");goog.dom.TagName.ABBR=new goog.dom.TagName("ABBR");goog.dom.TagName.ACRONYM=new goog.dom.TagName("ACRONYM");goog.dom.TagName.ADDRESS=new goog.dom.TagName("ADDRESS");goog.dom.TagName.APPLET=new goog.dom.TagName("APPLET");goog.dom.TagName.AREA=new goog.dom.TagName("AREA");goog.dom.TagName.ARTICLE=new goog.dom.TagName("ARTICLE");
+goog.dom.TagName.ASIDE=new goog.dom.TagName("ASIDE");goog.dom.TagName.AUDIO=new goog.dom.TagName("AUDIO");goog.dom.TagName.B=new goog.dom.TagName("B");goog.dom.TagName.BASE=new goog.dom.TagName("BASE");goog.dom.TagName.BASEFONT=new goog.dom.TagName("BASEFONT");goog.dom.TagName.BDI=new goog.dom.TagName("BDI");goog.dom.TagName.BDO=new goog.dom.TagName("BDO");goog.dom.TagName.BIG=new goog.dom.TagName("BIG");goog.dom.TagName.BLOCKQUOTE=new goog.dom.TagName("BLOCKQUOTE");goog.dom.TagName.BODY=new goog.dom.TagName("BODY");
+goog.dom.TagName.BR=new goog.dom.TagName("BR");goog.dom.TagName.BUTTON=new goog.dom.TagName("BUTTON");goog.dom.TagName.CANVAS=new goog.dom.TagName("CANVAS");goog.dom.TagName.CAPTION=new goog.dom.TagName("CAPTION");goog.dom.TagName.CENTER=new goog.dom.TagName("CENTER");goog.dom.TagName.CITE=new goog.dom.TagName("CITE");goog.dom.TagName.CODE=new goog.dom.TagName("CODE");goog.dom.TagName.COL=new goog.dom.TagName("COL");goog.dom.TagName.COLGROUP=new goog.dom.TagName("COLGROUP");
+goog.dom.TagName.COMMAND=new goog.dom.TagName("COMMAND");goog.dom.TagName.DATA=new goog.dom.TagName("DATA");goog.dom.TagName.DATALIST=new goog.dom.TagName("DATALIST");goog.dom.TagName.DD=new goog.dom.TagName("DD");goog.dom.TagName.DEL=new goog.dom.TagName("DEL");goog.dom.TagName.DETAILS=new goog.dom.TagName("DETAILS");goog.dom.TagName.DFN=new goog.dom.TagName("DFN");goog.dom.TagName.DIALOG=new goog.dom.TagName("DIALOG");goog.dom.TagName.DIR=new goog.dom.TagName("DIR");goog.dom.TagName.DIV=new goog.dom.TagName("DIV");
+goog.dom.TagName.DL=new goog.dom.TagName("DL");goog.dom.TagName.DT=new goog.dom.TagName("DT");goog.dom.TagName.EM=new goog.dom.TagName("EM");goog.dom.TagName.EMBED=new goog.dom.TagName("EMBED");goog.dom.TagName.FIELDSET=new goog.dom.TagName("FIELDSET");goog.dom.TagName.FIGCAPTION=new goog.dom.TagName("FIGCAPTION");goog.dom.TagName.FIGURE=new goog.dom.TagName("FIGURE");goog.dom.TagName.FONT=new goog.dom.TagName("FONT");goog.dom.TagName.FOOTER=new goog.dom.TagName("FOOTER");goog.dom.TagName.FORM=new goog.dom.TagName("FORM");
+goog.dom.TagName.FRAME=new goog.dom.TagName("FRAME");goog.dom.TagName.FRAMESET=new goog.dom.TagName("FRAMESET");goog.dom.TagName.H1=new goog.dom.TagName("H1");goog.dom.TagName.H2=new goog.dom.TagName("H2");goog.dom.TagName.H3=new goog.dom.TagName("H3");goog.dom.TagName.H4=new goog.dom.TagName("H4");goog.dom.TagName.H5=new goog.dom.TagName("H5");goog.dom.TagName.H6=new goog.dom.TagName("H6");goog.dom.TagName.HEAD=new goog.dom.TagName("HEAD");goog.dom.TagName.HEADER=new goog.dom.TagName("HEADER");
+goog.dom.TagName.HGROUP=new goog.dom.TagName("HGROUP");goog.dom.TagName.HR=new goog.dom.TagName("HR");goog.dom.TagName.HTML=new goog.dom.TagName("HTML");goog.dom.TagName.I=new goog.dom.TagName("I");goog.dom.TagName.IFRAME=new goog.dom.TagName("IFRAME");goog.dom.TagName.IMG=new goog.dom.TagName("IMG");goog.dom.TagName.INPUT=new goog.dom.TagName("INPUT");goog.dom.TagName.INS=new goog.dom.TagName("INS");goog.dom.TagName.ISINDEX=new goog.dom.TagName("ISINDEX");goog.dom.TagName.KBD=new goog.dom.TagName("KBD");
+goog.dom.TagName.KEYGEN=new goog.dom.TagName("KEYGEN");goog.dom.TagName.LABEL=new goog.dom.TagName("LABEL");goog.dom.TagName.LEGEND=new goog.dom.TagName("LEGEND");goog.dom.TagName.LI=new goog.dom.TagName("LI");goog.dom.TagName.LINK=new goog.dom.TagName("LINK");goog.dom.TagName.MAP=new goog.dom.TagName("MAP");goog.dom.TagName.MARK=new goog.dom.TagName("MARK");goog.dom.TagName.MATH=new goog.dom.TagName("MATH");goog.dom.TagName.MENU=new goog.dom.TagName("MENU");goog.dom.TagName.META=new goog.dom.TagName("META");
+goog.dom.TagName.METER=new goog.dom.TagName("METER");goog.dom.TagName.NAV=new goog.dom.TagName("NAV");goog.dom.TagName.NOFRAMES=new goog.dom.TagName("NOFRAMES");goog.dom.TagName.NOSCRIPT=new goog.dom.TagName("NOSCRIPT");goog.dom.TagName.OBJECT=new goog.dom.TagName("OBJECT");goog.dom.TagName.OL=new goog.dom.TagName("OL");goog.dom.TagName.OPTGROUP=new goog.dom.TagName("OPTGROUP");goog.dom.TagName.OPTION=new goog.dom.TagName("OPTION");goog.dom.TagName.OUTPUT=new goog.dom.TagName("OUTPUT");
+goog.dom.TagName.P=new goog.dom.TagName("P");goog.dom.TagName.PARAM=new goog.dom.TagName("PARAM");goog.dom.TagName.PRE=new goog.dom.TagName("PRE");goog.dom.TagName.PROGRESS=new goog.dom.TagName("PROGRESS");goog.dom.TagName.Q=new goog.dom.TagName("Q");goog.dom.TagName.RP=new goog.dom.TagName("RP");goog.dom.TagName.RT=new goog.dom.TagName("RT");goog.dom.TagName.RUBY=new goog.dom.TagName("RUBY");goog.dom.TagName.S=new goog.dom.TagName("S");goog.dom.TagName.SAMP=new goog.dom.TagName("SAMP");
+goog.dom.TagName.SCRIPT=new goog.dom.TagName("SCRIPT");goog.dom.TagName.SECTION=new goog.dom.TagName("SECTION");goog.dom.TagName.SELECT=new goog.dom.TagName("SELECT");goog.dom.TagName.SMALL=new goog.dom.TagName("SMALL");goog.dom.TagName.SOURCE=new goog.dom.TagName("SOURCE");goog.dom.TagName.SPAN=new goog.dom.TagName("SPAN");goog.dom.TagName.STRIKE=new goog.dom.TagName("STRIKE");goog.dom.TagName.STRONG=new goog.dom.TagName("STRONG");goog.dom.TagName.STYLE=new goog.dom.TagName("STYLE");
+goog.dom.TagName.SUB=new goog.dom.TagName("SUB");goog.dom.TagName.SUMMARY=new goog.dom.TagName("SUMMARY");goog.dom.TagName.SUP=new goog.dom.TagName("SUP");goog.dom.TagName.SVG=new goog.dom.TagName("SVG");goog.dom.TagName.TABLE=new goog.dom.TagName("TABLE");goog.dom.TagName.TBODY=new goog.dom.TagName("TBODY");goog.dom.TagName.TD=new goog.dom.TagName("TD");goog.dom.TagName.TEMPLATE=new goog.dom.TagName("TEMPLATE");goog.dom.TagName.TEXTAREA=new goog.dom.TagName("TEXTAREA");goog.dom.TagName.TFOOT=new goog.dom.TagName("TFOOT");
+goog.dom.TagName.TH=new goog.dom.TagName("TH");goog.dom.TagName.THEAD=new goog.dom.TagName("THEAD");goog.dom.TagName.TIME=new goog.dom.TagName("TIME");goog.dom.TagName.TITLE=new goog.dom.TagName("TITLE");goog.dom.TagName.TR=new goog.dom.TagName("TR");goog.dom.TagName.TRACK=new goog.dom.TagName("TRACK");goog.dom.TagName.TT=new goog.dom.TagName("TT");goog.dom.TagName.U=new goog.dom.TagName("U");goog.dom.TagName.UL=new goog.dom.TagName("UL");goog.dom.TagName.VAR=new goog.dom.TagName("VAR");
+goog.dom.TagName.VIDEO=new goog.dom.TagName("VIDEO");goog.dom.TagName.WBR=new goog.dom.TagName("WBR");goog.dom.tags={};goog.dom.tags.VOID_TAGS_={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0};goog.dom.tags.isVoidTag=function(a){return!0===goog.dom.tags.VOID_TAGS_[a]};goog.string.TypedString=function(){};goog.string.Const=function(){this.stringConstValueWithSecurityContract__googStringSecurityPrivate_="";this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_=goog.string.Const.TYPE_MARKER_};goog.string.Const.prototype.implementsGoogStringTypedString=!0;goog.string.Const.prototype.getTypedStringValue=function(){return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_};
+goog.string.Const.prototype.toString=function(){return"Const{"+this.stringConstValueWithSecurityContract__googStringSecurityPrivate_+"}"};goog.string.Const.unwrap=function(a){if(a instanceof goog.string.Const&&a.constructor===goog.string.Const&&a.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_===goog.string.Const.TYPE_MARKER_)return a.stringConstValueWithSecurityContract__googStringSecurityPrivate_;goog.asserts.fail("expected object of type Const, got '"+a+"'");return"type_error:Const"};
+goog.string.Const.from=function(a){return goog.string.Const.create__googStringSecurityPrivate_(a)};goog.string.Const.TYPE_MARKER_={};goog.string.Const.create__googStringSecurityPrivate_=function(a){var b=new goog.string.Const;b.stringConstValueWithSecurityContract__googStringSecurityPrivate_=a;return b};goog.string.Const.EMPTY=goog.string.Const.from("");goog.html={};goog.html.SafeScript=function(){this.privateDoNotAccessOrElseSafeScriptWrappedValue_="";this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_};goog.html.SafeScript.prototype.implementsGoogStringTypedString=!0;goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_={};goog.html.SafeScript.fromConstant=function(a){a=goog.string.Const.unwrap(a);return 0===a.length?goog.html.SafeScript.EMPTY:goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(a)};
+goog.html.SafeScript.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseSafeScriptWrappedValue_};goog.DEBUG&&(goog.html.SafeScript.prototype.toString=function(){return"SafeScript{"+this.privateDoNotAccessOrElseSafeScriptWrappedValue_+"}"});
+goog.html.SafeScript.unwrap=function(a){if(a instanceof goog.html.SafeScript&&a.constructor===goog.html.SafeScript&&a.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_===goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_)return a.privateDoNotAccessOrElseSafeScriptWrappedValue_;goog.asserts.fail("expected object of type SafeScript, got '"+a+"' of type "+goog.typeOf(a));return"type_error:SafeScript"};goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse=function(a){return(new goog.html.SafeScript).initSecurityPrivateDoNotAccessOrElse_(a)};
+goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_=function(a){this.privateDoNotAccessOrElseSafeScriptWrappedValue_=a;return this};goog.html.SafeScript.EMPTY=goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse("");goog.html.SafeStyle=function(){this.privateDoNotAccessOrElseSafeStyleWrappedValue_="";this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_};goog.html.SafeStyle.prototype.implementsGoogStringTypedString=!0;goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_={};
+goog.html.SafeStyle.fromConstant=function(a){a=goog.string.Const.unwrap(a);if(0===a.length)return goog.html.SafeStyle.EMPTY;goog.html.SafeStyle.checkStyle_(a);goog.asserts.assert(goog.string.endsWith(a,";"),"Last character of style string is not ';': "+a);goog.asserts.assert(goog.string.contains(a,":"),"Style string must contain at least one ':', to specify a \"name: value\" pair: "+a);return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(a)};
+goog.html.SafeStyle.checkStyle_=function(a){goog.asserts.assert(!/[<>]/.test(a),"Forbidden characters in style string: "+a)};goog.html.SafeStyle.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseSafeStyleWrappedValue_};goog.DEBUG&&(goog.html.SafeStyle.prototype.toString=function(){return"SafeStyle{"+this.privateDoNotAccessOrElseSafeStyleWrappedValue_+"}"});
+goog.html.SafeStyle.unwrap=function(a){if(a instanceof goog.html.SafeStyle&&a.constructor===goog.html.SafeStyle&&a.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_===goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_)return a.privateDoNotAccessOrElseSafeStyleWrappedValue_;goog.asserts.fail("expected object of type SafeStyle, got '"+a+"' of type "+goog.typeOf(a));return"type_error:SafeStyle"};goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse=function(a){return(new goog.html.SafeStyle).initSecurityPrivateDoNotAccessOrElse_(a)};
+goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_=function(a){this.privateDoNotAccessOrElseSafeStyleWrappedValue_=a;return this};goog.html.SafeStyle.EMPTY=goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse("");goog.html.SafeStyle.INNOCUOUS_STRING="zClosurez";
+goog.html.SafeStyle.create=function(a){var b="",c;for(c in a){if(!/^[-_a-zA-Z0-9]+$/.test(c))throw Error("Name allows only [-_a-zA-Z0-9], got: "+c);var d=a[c];null!=d&&(d instanceof goog.string.Const?(d=goog.string.Const.unwrap(d),goog.asserts.assert(!/[{;}]/.test(d),"Value does not allow [{;}].")):goog.html.SafeStyle.VALUE_RE_.test(d)?goog.html.SafeStyle.hasBalancedQuotes_(d)||(goog.asserts.fail("String value requires balanced quotes, got: "+d),d=goog.html.SafeStyle.INNOCUOUS_STRING):(goog.asserts.fail("String value allows only [-,.\"'%_!# a-zA-Z0-9], rgb() and rgba(), got: "+
+d),d=goog.html.SafeStyle.INNOCUOUS_STRING),b+=c+":"+d+";")}if(!b)return goog.html.SafeStyle.EMPTY;goog.html.SafeStyle.checkStyle_(b);return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(b)};goog.html.SafeStyle.hasBalancedQuotes_=function(a){for(var b=!0,c=!0,d=0;d<a.length;d++){var e=a.charAt(d);"'"==e&&c?b=!b:'"'==e&&b&&(c=!c)}return b&&c};goog.html.SafeStyle.VALUE_RE_=/^([-,."'%_!# a-zA-Z0-9]+|(?:rgb|hsl)a?\([0-9.%, ]+\))$/;
+goog.html.SafeStyle.concat=function(a){var b="",c=function(a){goog.isArray(a)?goog.array.forEach(a,c):b+=goog.html.SafeStyle.unwrap(a)};goog.array.forEach(arguments,c);return b?goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(b):goog.html.SafeStyle.EMPTY};goog.html.SafeStyleSheet=function(){this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_="";this.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_};goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString=!0;goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_={};
+goog.html.SafeStyleSheet.concat=function(a){var b="",c=function(a){goog.isArray(a)?goog.array.forEach(a,c):b+=goog.html.SafeStyleSheet.unwrap(a)};goog.array.forEach(arguments,c);return goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(b)};
+goog.html.SafeStyleSheet.fromConstant=function(a){a=goog.string.Const.unwrap(a);if(0===a.length)return goog.html.SafeStyleSheet.EMPTY;goog.asserts.assert(!goog.string.contains(a,"<"),"Forbidden '<' character in style sheet string: "+a);return goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(a)};goog.html.SafeStyleSheet.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_};
+goog.DEBUG&&(goog.html.SafeStyleSheet.prototype.toString=function(){return"SafeStyleSheet{"+this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_+"}"});
+goog.html.SafeStyleSheet.unwrap=function(a){if(a instanceof goog.html.SafeStyleSheet&&a.constructor===goog.html.SafeStyleSheet&&a.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_===goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_)return a.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;goog.asserts.fail("expected object of type SafeStyleSheet, got '"+a+"' of type "+goog.typeOf(a));return"type_error:SafeStyleSheet"};
+goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse=function(a){return(new goog.html.SafeStyleSheet).initSecurityPrivateDoNotAccessOrElse_(a)};goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_=function(a){this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_=a;return this};goog.html.SafeStyleSheet.EMPTY=goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse("");goog.fs={};goog.fs.url={};goog.fs.url.createObjectUrl=function(a){return goog.fs.url.getUrlObject_().createObjectURL(a)};goog.fs.url.revokeObjectUrl=function(a){goog.fs.url.getUrlObject_().revokeObjectURL(a)};goog.fs.url.getUrlObject_=function(){var a=goog.fs.url.findUrlObject_();if(null!=a)return a;throw Error("This browser doesn't seem to support blob URLs");};
+goog.fs.url.findUrlObject_=function(){return goog.isDef(goog.global.URL)&&goog.isDef(goog.global.URL.createObjectURL)?goog.global.URL:goog.isDef(goog.global.webkitURL)&&goog.isDef(goog.global.webkitURL.createObjectURL)?goog.global.webkitURL:goog.isDef(goog.global.createObjectURL)?goog.global:null};goog.fs.url.browserSupportsObjectUrls=function(){return null!=goog.fs.url.findUrlObject_()};goog.i18n={};goog.i18n.bidi={};goog.i18n.bidi.FORCE_RTL=!1;
+goog.i18n.bidi.IS_RTL=goog.i18n.bidi.FORCE_RTL||("ar"==goog.LOCALE.substring(0,2).toLowerCase()||"fa"==goog.LOCALE.substring(0,2).toLowerCase()||"he"==goog.LOCALE.substring(0,2).toLowerCase()||"iw"==goog.LOCALE.substring(0,2).toLowerCase()||"ps"==goog.LOCALE.substring(0,2).toLowerCase()||"sd"==goog.LOCALE.substring(0,2).toLowerCase()||"ug"==goog.LOCALE.substring(0,2).toLowerCase()||"ur"==goog.LOCALE.substring(0,2).toLowerCase()||"yi"==goog.LOCALE.substring(0,2).toLowerCase())&&(2==goog.LOCALE.length||
+"-"==goog.LOCALE.substring(2,3)||"_"==goog.LOCALE.substring(2,3))||3<=goog.LOCALE.length&&"ckb"==goog.LOCALE.substring(0,3).toLowerCase()&&(3==goog.LOCALE.length||"-"==goog.LOCALE.substring(3,4)||"_"==goog.LOCALE.substring(3,4));goog.i18n.bidi.Format={LRE:"\u202a",RLE:"\u202b",PDF:"\u202c",LRM:"\u200e",RLM:"\u200f"};goog.i18n.bidi.Dir={LTR:1,RTL:-1,NEUTRAL:0};goog.i18n.bidi.RIGHT="right";goog.i18n.bidi.LEFT="left";goog.i18n.bidi.I18N_RIGHT=goog.i18n.bidi.IS_RTL?goog.i18n.bidi.LEFT:goog.i18n.bidi.RIGHT;
+goog.i18n.bidi.I18N_LEFT=goog.i18n.bidi.IS_RTL?goog.i18n.bidi.RIGHT:goog.i18n.bidi.LEFT;goog.i18n.bidi.toDir=function(a,b){return"number"==typeof a?0<a?goog.i18n.bidi.Dir.LTR:0>a?goog.i18n.bidi.Dir.RTL:b?null:goog.i18n.bidi.Dir.NEUTRAL:null==a?null:a?goog.i18n.bidi.Dir.RTL:goog.i18n.bidi.Dir.LTR};goog.i18n.bidi.ltrChars_="A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02b8\u0300-\u0590\u0800-\u1fff\u200e\u2c00-\ufb1c\ufe00-\ufe6f\ufefd-\uffff";goog.i18n.bidi.rtlChars_="\u0591-\u06ef\u06fa-\u07ff\u200f\ufb1d-\ufdff\ufe70-\ufefc";
+goog.i18n.bidi.htmlSkipReg_=/<[^>]*>|&[^;]+;/g;goog.i18n.bidi.stripHtmlIfNeeded_=function(a,b){return b?a.replace(goog.i18n.bidi.htmlSkipReg_,""):a};goog.i18n.bidi.rtlCharReg_=new RegExp("["+goog.i18n.bidi.rtlChars_+"]");goog.i18n.bidi.ltrCharReg_=new RegExp("["+goog.i18n.bidi.ltrChars_+"]");goog.i18n.bidi.hasAnyRtl=function(a,b){return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(a,b))};goog.i18n.bidi.hasRtlChar=goog.i18n.bidi.hasAnyRtl;
+goog.i18n.bidi.hasAnyLtr=function(a,b){return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(a,b))};goog.i18n.bidi.ltrRe_=new RegExp("^["+goog.i18n.bidi.ltrChars_+"]");goog.i18n.bidi.rtlRe_=new RegExp("^["+goog.i18n.bidi.rtlChars_+"]");goog.i18n.bidi.isRtlChar=function(a){return goog.i18n.bidi.rtlRe_.test(a)};goog.i18n.bidi.isLtrChar=function(a){return goog.i18n.bidi.ltrRe_.test(a)};goog.i18n.bidi.isNeutralChar=function(a){return!goog.i18n.bidi.isLtrChar(a)&&!goog.i18n.bidi.isRtlChar(a)};
+goog.i18n.bidi.ltrDirCheckRe_=new RegExp("^[^"+goog.i18n.bidi.rtlChars_+"]*["+goog.i18n.bidi.ltrChars_+"]");goog.i18n.bidi.rtlDirCheckRe_=new RegExp("^[^"+goog.i18n.bidi.ltrChars_+"]*["+goog.i18n.bidi.rtlChars_+"]");goog.i18n.bidi.startsWithRtl=function(a,b){return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(a,b))};goog.i18n.bidi.isRtlText=goog.i18n.bidi.startsWithRtl;
+goog.i18n.bidi.startsWithLtr=function(a,b){return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(a,b))};goog.i18n.bidi.isLtrText=goog.i18n.bidi.startsWithLtr;goog.i18n.bidi.isRequiredLtrRe_=/^http:\/\/.*/;goog.i18n.bidi.isNeutralText=function(a,b){a=goog.i18n.bidi.stripHtmlIfNeeded_(a,b);return goog.i18n.bidi.isRequiredLtrRe_.test(a)||!goog.i18n.bidi.hasAnyLtr(a)&&!goog.i18n.bidi.hasAnyRtl(a)};
+goog.i18n.bidi.ltrExitDirCheckRe_=new RegExp("["+goog.i18n.bidi.ltrChars_+"][^"+goog.i18n.bidi.rtlChars_+"]*$");goog.i18n.bidi.rtlExitDirCheckRe_=new RegExp("["+goog.i18n.bidi.rtlChars_+"][^"+goog.i18n.bidi.ltrChars_+"]*$");goog.i18n.bidi.endsWithLtr=function(a,b){return goog.i18n.bidi.ltrExitDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(a,b))};goog.i18n.bidi.isLtrExitText=goog.i18n.bidi.endsWithLtr;
+goog.i18n.bidi.endsWithRtl=function(a,b){return goog.i18n.bidi.rtlExitDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(a,b))};goog.i18n.bidi.isRtlExitText=goog.i18n.bidi.endsWithRtl;goog.i18n.bidi.rtlLocalesRe_=/^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)/i;goog.i18n.bidi.isRtlLanguage=function(a){return goog.i18n.bidi.rtlLocalesRe_.test(a)};goog.i18n.bidi.bracketGuardTextRe_=/(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
+goog.i18n.bidi.guardBracketInText=function(a,b){var c=(void 0===b?goog.i18n.bidi.hasAnyRtl(a):b)?goog.i18n.bidi.Format.RLM:goog.i18n.bidi.Format.LRM;return a.replace(goog.i18n.bidi.bracketGuardTextRe_,c+"$&"+c)};goog.i18n.bidi.enforceRtlInHtml=function(a){return"<"==a.charAt(0)?a.replace(/<\w+/,"$& dir=rtl"):"\n<span dir=rtl>"+a+"</span>"};goog.i18n.bidi.enforceRtlInText=function(a){return goog.i18n.bidi.Format.RLE+a+goog.i18n.bidi.Format.PDF};
+goog.i18n.bidi.enforceLtrInHtml=function(a){return"<"==a.charAt(0)?a.replace(/<\w+/,"$& dir=ltr"):"\n<span dir=ltr>"+a+"</span>"};goog.i18n.bidi.enforceLtrInText=function(a){return goog.i18n.bidi.Format.LRE+a+goog.i18n.bidi.Format.PDF};goog.i18n.bidi.dimensionsRe_=/:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;goog.i18n.bidi.leftRe_=/left/gi;goog.i18n.bidi.rightRe_=/right/gi;goog.i18n.bidi.tempRe_=/%%%%/g;
+goog.i18n.bidi.mirrorCSS=function(a){return a.replace(goog.i18n.bidi.dimensionsRe_,":$1 $4 $3 $2").replace(goog.i18n.bidi.leftRe_,"%%%%").replace(goog.i18n.bidi.rightRe_,goog.i18n.bidi.LEFT).replace(goog.i18n.bidi.tempRe_,goog.i18n.bidi.RIGHT)};goog.i18n.bidi.doubleQuoteSubstituteRe_=/([\u0591-\u05f2])"/g;goog.i18n.bidi.singleQuoteSubstituteRe_=/([\u0591-\u05f2])'/g;
+goog.i18n.bidi.normalizeHebrewQuote=function(a){return a.replace(goog.i18n.bidi.doubleQuoteSubstituteRe_,"$1\u05f4").replace(goog.i18n.bidi.singleQuoteSubstituteRe_,"$1\u05f3")};goog.i18n.bidi.wordSeparatorRe_=/\s+/;goog.i18n.bidi.hasNumeralsRe_=/[\d\u06f0-\u06f9]/;goog.i18n.bidi.rtlDetectionThreshold_=.4;
+goog.i18n.bidi.estimateDirection=function(a,b){for(var c=0,d=0,e=!1,f=goog.i18n.bidi.stripHtmlIfNeeded_(a,b).split(goog.i18n.bidi.wordSeparatorRe_),g=0;g<f.length;g++){var h=f[g];goog.i18n.bidi.startsWithRtl(h)?(c++,d++):goog.i18n.bidi.isRequiredLtrRe_.test(h)?e=!0:goog.i18n.bidi.hasAnyLtr(h)?d++:goog.i18n.bidi.hasNumeralsRe_.test(h)&&(e=!0)}return 0==d?e?goog.i18n.bidi.Dir.LTR:goog.i18n.bidi.Dir.NEUTRAL:c/d>goog.i18n.bidi.rtlDetectionThreshold_?goog.i18n.bidi.Dir.RTL:goog.i18n.bidi.Dir.LTR};
+goog.i18n.bidi.detectRtlDirectionality=function(a,b){return goog.i18n.bidi.estimateDirection(a,b)==goog.i18n.bidi.Dir.RTL};goog.i18n.bidi.setElementDirAndAlign=function(a,b){a&&(b=goog.i18n.bidi.toDir(b))&&(a.style.textAlign=b==goog.i18n.bidi.Dir.RTL?goog.i18n.bidi.RIGHT:goog.i18n.bidi.LEFT,a.dir=b==goog.i18n.bidi.Dir.RTL?"rtl":"ltr")};
+goog.i18n.bidi.setElementDirByTextDirectionality=function(a,b){switch(goog.i18n.bidi.estimateDirection(b)){case goog.i18n.bidi.Dir.LTR:a.dir="ltr";break;case goog.i18n.bidi.Dir.RTL:a.dir="rtl";break;default:a.removeAttribute("dir")}};goog.i18n.bidi.DirectionalString=function(){};goog.html.SafeUrl=function(){this.privateDoNotAccessOrElseSafeHtmlWrappedValue_="";this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_};goog.html.SafeUrl.INNOCUOUS_STRING="about:invalid#zClosurez";goog.html.SafeUrl.prototype.implementsGoogStringTypedString=!0;goog.html.SafeUrl.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_};
+goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString=!0;goog.html.SafeUrl.prototype.getDirection=function(){return goog.i18n.bidi.Dir.LTR};goog.DEBUG&&(goog.html.SafeUrl.prototype.toString=function(){return"SafeUrl{"+this.privateDoNotAccessOrElseSafeHtmlWrappedValue_+"}"});
+goog.html.SafeUrl.unwrap=function(a){if(a instanceof goog.html.SafeUrl&&a.constructor===goog.html.SafeUrl&&a.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_===goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_)return a.privateDoNotAccessOrElseSafeHtmlWrappedValue_;goog.asserts.fail("expected object of type SafeUrl, got '"+a+"' of type "+goog.typeOf(a));return"type_error:SafeUrl"};goog.html.SafeUrl.fromConstant=function(a){return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(goog.string.Const.unwrap(a))};
+goog.html.SAFE_MIME_TYPE_PATTERN_=/^(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm))$/i;goog.html.SafeUrl.fromBlob=function(a){a=goog.html.SAFE_MIME_TYPE_PATTERN_.test(a.type)?goog.fs.url.createObjectUrl(a):goog.html.SafeUrl.INNOCUOUS_STRING;return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(a)};goog.html.DATA_URL_PATTERN_=/^data:([^;,]*);base64,[a-z0-9+\/]+=*$/i;
+goog.html.SafeUrl.fromDataUrl=function(a){var b=a.match(goog.html.DATA_URL_PATTERN_),b=b&&goog.html.SAFE_MIME_TYPE_PATTERN_.test(b[1]);return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(b?a:goog.html.SafeUrl.INNOCUOUS_STRING)};goog.html.SafeUrl.fromTelUrl=function(a){goog.string.caseInsensitiveStartsWith(a,"tel:")||(a=goog.html.SafeUrl.INNOCUOUS_STRING);return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(a)};goog.html.SAFE_URL_PATTERN_=/^(?:(?:https?|mailto|ftp):|[^&:/?#]*(?:[/?#]|$))/i;
+goog.html.SafeUrl.sanitize=function(a){if(a instanceof goog.html.SafeUrl)return a;a=a.implementsGoogStringTypedString?a.getTypedStringValue():String(a);goog.html.SAFE_URL_PATTERN_.test(a)||(a=goog.html.SafeUrl.INNOCUOUS_STRING);return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(a)};goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_={};
+goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse=function(a){var b=new goog.html.SafeUrl;b.privateDoNotAccessOrElseSafeHtmlWrappedValue_=a;return b};goog.html.SafeUrl.ABOUT_BLANK=goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse("about:blank");goog.html.TrustedResourceUrl=function(){this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_="";this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_};goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString=!0;goog.html.TrustedResourceUrl.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_};
+goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString=!0;goog.html.TrustedResourceUrl.prototype.getDirection=function(){return goog.i18n.bidi.Dir.LTR};goog.DEBUG&&(goog.html.TrustedResourceUrl.prototype.toString=function(){return"TrustedResourceUrl{"+this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_+"}"});
+goog.html.TrustedResourceUrl.unwrap=function(a){if(a instanceof goog.html.TrustedResourceUrl&&a.constructor===goog.html.TrustedResourceUrl&&a.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_===goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_)return a.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;goog.asserts.fail("expected object of type TrustedResourceUrl, got '"+a+"' of type "+goog.typeOf(a));return"type_error:TrustedResourceUrl"};
+goog.html.TrustedResourceUrl.format=function(a,b){var c=goog.string.Const.unwrap(a);if(!goog.html.TrustedResourceUrl.BASE_URL_.test(c))throw Error("Invalid TrustedResourceUrl format: "+c);var d=c.replace(goog.html.TrustedResourceUrl.FORMAT_MARKER_,function(a,d){if(!Object.prototype.hasOwnProperty.call(b,d))throw Error('Found marker, "'+d+'", in format string, "'+c+'", but no valid label mapping found in args: '+JSON.stringify(b));var e=b[d];return e instanceof goog.string.Const?goog.string.Const.unwrap(e):
+encodeURIComponent(String(e))});return goog.html.TrustedResourceUrl.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(d)};goog.html.TrustedResourceUrl.FORMAT_MARKER_=/%{(\w+)}/g;goog.html.TrustedResourceUrl.SCHEME_AND_ORIGIN_="(?:(?:https:)?//[0-9a-z.:[\\]-]+)?";goog.html.TrustedResourceUrl.BASE_ABSOLUTE_PATH_="(?:/[0-9a-z_~-]+(?:[/#?]|$))";
+goog.html.TrustedResourceUrl.BASE_URL_=new RegExp("^"+goog.html.TrustedResourceUrl.SCHEME_AND_ORIGIN_+goog.html.TrustedResourceUrl.BASE_ABSOLUTE_PATH_,"i");goog.html.TrustedResourceUrl.fromConstant=function(a){return goog.html.TrustedResourceUrl.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(goog.string.Const.unwrap(a))};goog.html.TrustedResourceUrl.fromConstants=function(a){for(var b="",c=0;c<a.length;c++)b+=goog.string.Const.unwrap(a[c]);return goog.html.TrustedResourceUrl.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(b)};
+goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_={};goog.html.TrustedResourceUrl.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse=function(a){var b=new goog.html.TrustedResourceUrl;b.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_=a;return b};goog.html.SafeHtml=function(){this.privateDoNotAccessOrElseSafeHtmlWrappedValue_="";this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;this.dir_=null};goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString=!0;goog.html.SafeHtml.prototype.getDirection=function(){return this.dir_};goog.html.SafeHtml.prototype.implementsGoogStringTypedString=!0;goog.html.SafeHtml.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_};
+goog.DEBUG&&(goog.html.SafeHtml.prototype.toString=function(){return"SafeHtml{"+this.privateDoNotAccessOrElseSafeHtmlWrappedValue_+"}"});
+goog.html.SafeHtml.unwrap=function(a){if(a instanceof goog.html.SafeHtml&&a.constructor===goog.html.SafeHtml&&a.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_===goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_)return a.privateDoNotAccessOrElseSafeHtmlWrappedValue_;goog.asserts.fail("expected object of type SafeHtml, got '"+a+"' of type "+goog.typeOf(a));return"type_error:SafeHtml"};
+goog.html.SafeHtml.htmlEscape=function(a){if(a instanceof goog.html.SafeHtml)return a;var b=null;a.implementsGoogI18nBidiDirectionalString&&(b=a.getDirection());a=a.implementsGoogStringTypedString?a.getTypedStringValue():String(a);return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(goog.string.htmlEscape(a),b)};
+goog.html.SafeHtml.htmlEscapePreservingNewlines=function(a){if(a instanceof goog.html.SafeHtml)return a;a=goog.html.SafeHtml.htmlEscape(a);return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(goog.string.newLineToBr(goog.html.SafeHtml.unwrap(a)),a.getDirection())};
+goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces=function(a){if(a instanceof goog.html.SafeHtml)return a;a=goog.html.SafeHtml.htmlEscape(a);return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(a)),a.getDirection())};goog.html.SafeHtml.from=goog.html.SafeHtml.htmlEscape;goog.html.SafeHtml.VALID_NAMES_IN_TAG_=/^[a-zA-Z0-9-]+$/;
+goog.html.SafeHtml.URL_ATTRIBUTES_={action:!0,cite:!0,data:!0,formaction:!0,href:!0,manifest:!0,poster:!0,src:!0};goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_={APPLET:!0,BASE:!0,EMBED:!0,IFRAME:!0,LINK:!0,MATH:!0,META:!0,OBJECT:!0,SCRIPT:!0,STYLE:!0,SVG:!0,TEMPLATE:!0};goog.html.SafeHtml.create=function(a,b,c){goog.html.SafeHtml.verifyTagName(String(a));return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(String(a),b,c)};
+goog.html.SafeHtml.verifyTagName=function(a){if(!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(a))throw Error("Invalid tag name <"+a+">.");if(a.toUpperCase()in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_)throw Error("Tag name <"+a+"> is not allowed for SafeHtml.");};
+goog.html.SafeHtml.createIframe=function(a,b,c,d){a&&goog.html.TrustedResourceUrl.unwrap(a);var e={};e.src=a||null;e.srcdoc=b&&goog.html.SafeHtml.unwrap(b);a=goog.html.SafeHtml.combineAttributes(e,{sandbox:""},c);return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse("iframe",a,d)};
+goog.html.SafeHtml.createSandboxIframe=function(a,b,c,d){if(!goog.html.SafeHtml.canUseSandboxIframe())throw Error("The browser does not support sandboxed iframes.");var e={};e.src=a?goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(a)):null;e.srcdoc=b||null;e.sandbox="";a=goog.html.SafeHtml.combineAttributes(e,{},c);return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse("iframe",a,d)};
+goog.html.SafeHtml.canUseSandboxIframe=function(){return goog.global.HTMLIFrameElement&&"sandbox"in goog.global.HTMLIFrameElement.prototype};goog.html.SafeHtml.createScriptSrc=function(a,b){goog.html.TrustedResourceUrl.unwrap(a);var c=goog.html.SafeHtml.combineAttributes({src:a},{},b);return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse("script",c)};
+goog.html.SafeHtml.createScript=function(a,b){for(var c in b){var d=c.toLowerCase();if("language"==d||"src"==d||"text"==d||"type"==d)throw Error('Cannot set "'+d+'" attribute');}c="";a=goog.array.concat(a);for(d=0;d<a.length;d++)c+=goog.html.SafeScript.unwrap(a[d]);c=goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(c,goog.i18n.bidi.Dir.NEUTRAL);return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse("script",b,c)};
+goog.html.SafeHtml.createStyle=function(a,b){var c=goog.html.SafeHtml.combineAttributes({type:"text/css"},{},b),d="";a=goog.array.concat(a);for(var e=0;e<a.length;e++)d+=goog.html.SafeStyleSheet.unwrap(a[e]);d=goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(d,goog.i18n.bidi.Dir.NEUTRAL);return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse("style",c,d)};
+goog.html.SafeHtml.createMetaRefresh=function(a,b){var c=goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(a));(goog.labs.userAgent.browser.isIE()||goog.labs.userAgent.browser.isEdge())&&goog.string.contains(c,";")&&(c="'"+c.replace(/'/g,"%27")+"'");return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse("meta",{"http-equiv":"refresh",content:(b||0)+"; url="+c})};
+goog.html.SafeHtml.getAttrNameAndValue_=function(a,b,c){if(c instanceof goog.string.Const)c=goog.string.Const.unwrap(c);else if("style"==b.toLowerCase())c=goog.html.SafeHtml.getStyleValue_(c);else{if(/^on/i.test(b))throw Error('Attribute "'+b+'" requires goog.string.Const value, "'+c+'" given.');if(b.toLowerCase()in goog.html.SafeHtml.URL_ATTRIBUTES_)if(c instanceof goog.html.TrustedResourceUrl)c=goog.html.TrustedResourceUrl.unwrap(c);else if(c instanceof goog.html.SafeUrl)c=goog.html.SafeUrl.unwrap(c);
+else if(goog.isString(c))c=goog.html.SafeUrl.sanitize(c).getTypedStringValue();else throw Error('Attribute "'+b+'" on tag "'+a+'" requires goog.html.SafeUrl, goog.string.Const, or string, value "'+c+'" given.');}c.implementsGoogStringTypedString&&(c=c.getTypedStringValue());goog.asserts.assert(goog.isString(c)||goog.isNumber(c),"String or number value expected, got "+typeof c+" with value: "+c);return b+'="'+goog.string.htmlEscape(String(c))+'"'};
+goog.html.SafeHtml.getStyleValue_=function(a){if(!goog.isObject(a))throw Error('The "style" attribute requires goog.html.SafeStyle or map of style properties, '+typeof a+" given: "+a);a instanceof goog.html.SafeStyle||(a=goog.html.SafeStyle.create(a));return goog.html.SafeStyle.unwrap(a)};goog.html.SafeHtml.createWithDir=function(a,b,c,d){b=goog.html.SafeHtml.create(b,c,d);b.dir_=a;return b};
+goog.html.SafeHtml.concat=function(a){var b=goog.i18n.bidi.Dir.NEUTRAL,c="",d=function(a){goog.isArray(a)?goog.array.forEach(a,d):(a=goog.html.SafeHtml.htmlEscape(a),c+=goog.html.SafeHtml.unwrap(a),a=a.getDirection(),b==goog.i18n.bidi.Dir.NEUTRAL?b=a:a!=goog.i18n.bidi.Dir.NEUTRAL&&b!=a&&(b=null))};goog.array.forEach(arguments,d);return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(c,b)};
+goog.html.SafeHtml.concatWithDir=function(a,b){var c=goog.html.SafeHtml.concat(goog.array.slice(arguments,1));c.dir_=a;return c};goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_={};goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse=function(a,b){return(new goog.html.SafeHtml).initSecurityPrivateDoNotAccessOrElse_(a,b)};goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_=function(a,b){this.privateDoNotAccessOrElseSafeHtmlWrappedValue_=a;this.dir_=b;return this};
+goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse=function(a,b,c){var d=null,e;e="<"+a+goog.html.SafeHtml.stringifyAttributes(a,b);goog.isDefAndNotNull(c)?goog.isArray(c)||(c=[c]):c=[];goog.dom.tags.isVoidTag(a.toLowerCase())?(goog.asserts.assert(!c.length,"Void tag <"+a+"> does not allow content."),e+=">"):(d=goog.html.SafeHtml.concat(c),e+=">"+goog.html.SafeHtml.unwrap(d)+"</"+a+">",d=d.getDirection());(a=b&&b.dir)&&(d=/^(ltr|rtl|auto)$/i.test(a)?goog.i18n.bidi.Dir.NEUTRAL:null);
+return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(e,d)};goog.html.SafeHtml.stringifyAttributes=function(a,b){var c="";if(b)for(var d in b){if(!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(d))throw Error('Invalid attribute name "'+d+'".');var e=b[d];goog.isDefAndNotNull(e)&&(c+=" "+goog.html.SafeHtml.getAttrNameAndValue_(a,d,e))}return c};
+goog.html.SafeHtml.combineAttributes=function(a,b,c){var d={},e;for(e in a)goog.asserts.assert(e.toLowerCase()==e,"Must be lower case"),d[e]=a[e];for(e in b)goog.asserts.assert(e.toLowerCase()==e,"Must be lower case"),d[e]=b[e];for(e in c){var f=e.toLowerCase();if(f in a)throw Error('Cannot override "'+f+'" attribute, got "'+e+'" with value "'+c[e]+'"');f in b&&delete d[f];d[e]=c[e]}return d};
+goog.html.SafeHtml.DOCTYPE_HTML=goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse("<!DOCTYPE html>",goog.i18n.bidi.Dir.NEUTRAL);goog.html.SafeHtml.EMPTY=goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse("",goog.i18n.bidi.Dir.NEUTRAL);goog.html.SafeHtml.BR=goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse("<br>",goog.i18n.bidi.Dir.NEUTRAL);goog.dom.safe={};goog.dom.safe.InsertAdjacentHtmlPosition={AFTERBEGIN:"afterbegin",AFTEREND:"afterend",BEFOREBEGIN:"beforebegin",BEFOREEND:"beforeend"};goog.dom.safe.insertAdjacentHtml=function(a,b,c){a.insertAdjacentHTML(b,goog.html.SafeHtml.unwrap(c))};goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_={MATH:!0,SCRIPT:!0,STYLE:!0,SVG:!0,TEMPLATE:!0};
+goog.dom.safe.setInnerHtml=function(a,b){if(goog.asserts.ENABLE_ASSERTS){var c=a.tagName.toUpperCase();if(goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_[c])throw Error("goog.dom.safe.setInnerHtml cannot be used to set content of "+a.tagName+".");}a.innerHTML=goog.html.SafeHtml.unwrap(b)};goog.dom.safe.setOuterHtml=function(a,b){a.outerHTML=goog.html.SafeHtml.unwrap(b)};goog.dom.safe.setStyle=function(a,b){a.style.cssText=goog.html.SafeStyle.unwrap(b)};goog.dom.safe.documentWrite=function(a,b){a.write(goog.html.SafeHtml.unwrap(b))};
+goog.dom.safe.setAnchorHref=function(a,b){goog.dom.safe.assertIsHTMLAnchorElement_(a);var c;c=b instanceof goog.html.SafeUrl?b:goog.html.SafeUrl.sanitize(b);a.href=goog.html.SafeUrl.unwrap(c)};goog.dom.safe.setImageSrc=function(a,b){goog.dom.safe.assertIsHTMLImageElement_(a);var c;c=b instanceof goog.html.SafeUrl?b:goog.html.SafeUrl.sanitize(b);a.src=goog.html.SafeUrl.unwrap(c)};goog.dom.safe.setEmbedSrc=function(a,b){goog.dom.safe.assertIsHTMLEmbedElement_(a);a.src=goog.html.TrustedResourceUrl.unwrap(b)};
+goog.dom.safe.setFrameSrc=function(a,b){goog.dom.safe.assertIsHTMLFrameElement_(a);a.src=goog.html.TrustedResourceUrl.unwrap(b)};goog.dom.safe.setIframeSrc=function(a,b){goog.dom.safe.assertIsHTMLIFrameElement_(a);a.src=goog.html.TrustedResourceUrl.unwrap(b)};goog.dom.safe.setIframeSrcdoc=function(a,b){goog.dom.safe.assertIsHTMLIFrameElement_(a);a.srcdoc=goog.html.SafeHtml.unwrap(b)};
+goog.dom.safe.setLinkHrefAndRel=function(a,b,c){goog.dom.safe.assertIsHTMLLinkElement_(a);a.rel=c;goog.string.caseInsensitiveContains(c,"stylesheet")?(goog.asserts.assert(b instanceof goog.html.TrustedResourceUrl,'URL must be TrustedResourceUrl because "rel" contains "stylesheet"'),a.href=goog.html.TrustedResourceUrl.unwrap(b)):a.href=b instanceof goog.html.TrustedResourceUrl?goog.html.TrustedResourceUrl.unwrap(b):b instanceof goog.html.SafeUrl?goog.html.SafeUrl.unwrap(b):goog.html.SafeUrl.sanitize(b).getTypedStringValue()};
+goog.dom.safe.setObjectData=function(a,b){goog.dom.safe.assertIsHTMLObjectElement_(a);a.data=goog.html.TrustedResourceUrl.unwrap(b)};goog.dom.safe.setScriptSrc=function(a,b){goog.dom.safe.assertIsHTMLScriptElement_(a);a.src=goog.html.TrustedResourceUrl.unwrap(b)};goog.dom.safe.setLocationHref=function(a,b){goog.dom.safe.assertIsLocation_(a);var c;c=b instanceof goog.html.SafeUrl?b:goog.html.SafeUrl.sanitize(b);a.href=goog.html.SafeUrl.unwrap(c)};
+goog.dom.safe.openInWindow=function(a,b,c,d,e){a=a instanceof goog.html.SafeUrl?a:goog.html.SafeUrl.sanitize(a);return(b||window).open(goog.html.SafeUrl.unwrap(a),c?goog.string.Const.unwrap(c):"",d,e)};
+goog.dom.safe.assertIsLocation_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof Location&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof Location||!(a instanceof Element)),"Argument is not a Location (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLAnchorElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLAnchorElement&&"undefined"!=typeof Location&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLAnchorElement||!(a instanceof Location||a instanceof Element)),"Argument is not a HTMLAnchorElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLLinkElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLLinkElement&&"undefined"!=typeof Location&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLLinkElement||!(a instanceof Location||a instanceof Element)),"Argument is not a HTMLLinkElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLImageElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLImageElement&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLImageElement||!(a instanceof Element)),"Argument is not a HTMLImageElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLEmbedElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLEmbedElement&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLEmbedElement||!(a instanceof Element)),"Argument is not a HTMLEmbedElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLFrameElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLFrameElement&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLFrameElement||!(a instanceof Element)),"Argument is not a HTMLFrameElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLIFrameElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLIFrameElement&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLIFrameElement||!(a instanceof Element)),"Argument is not a HTMLIFrameElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLObjectElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLObjectElement&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLObjectElement||!(a instanceof Element)),"Argument is not a HTMLObjectElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.assertIsHTMLScriptElement_=function(a){goog.asserts.ENABLE_ASSERTS&&"undefined"!=typeof HTMLScriptElement&&"undefined"!=typeof Element&&goog.asserts.assert(a&&(a instanceof HTMLScriptElement||!(a instanceof Element)),"Argument is not a HTMLScriptElement (or a non-Element mock); got: %s",goog.dom.safe.debugStringForType_(a));return a};
+goog.dom.safe.debugStringForType_=function(a){return goog.isObject(a)?a.constructor.displayName||a.constructor.name||Object.prototype.toString.call(a):void 0===a?"undefined":null===a?"null":typeof a};goog.html.uncheckedconversions={};goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract=function(a,b,c){goog.asserts.assertString(goog.string.Const.unwrap(a),"must provide justification");goog.asserts.assert(!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(a)),"must provide non-empty justification");return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(b,c||null)};
+goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract=function(a,b){goog.asserts.assertString(goog.string.Const.unwrap(a),"must provide justification");goog.asserts.assert(!goog.string.isEmpty(goog.string.Const.unwrap(a)),"must provide non-empty justification");return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(b)};
+goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract=function(a,b){goog.asserts.assertString(goog.string.Const.unwrap(a),"must provide justification");goog.asserts.assert(!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(a)),"must provide non-empty justification");return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(b)};
+goog.html.uncheckedconversions.safeStyleSheetFromStringKnownToSatisfyTypeContract=function(a,b){goog.asserts.assertString(goog.string.Const.unwrap(a),"must provide justification");goog.asserts.assert(!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(a)),"must provide non-empty justification");return goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(b)};
+goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract=function(a,b){goog.asserts.assertString(goog.string.Const.unwrap(a),"must provide justification");goog.asserts.assert(!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(a)),"must provide non-empty justification");return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(b)};
+goog.html.uncheckedconversions.trustedResourceUrlFromStringKnownToSatisfyTypeContract=function(a,b){goog.asserts.assertString(goog.string.Const.unwrap(a),"must provide justification");goog.asserts.assert(!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(a)),"must provide non-empty justification");return goog.html.TrustedResourceUrl.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(b)};goog.math.Coordinate=function(a,b){this.x=goog.isDef(a)?a:0;this.y=goog.isDef(b)?b:0};goog.math.Coordinate.prototype.clone=function(){return new goog.math.Coordinate(this.x,this.y)};goog.DEBUG&&(goog.math.Coordinate.prototype.toString=function(){return"("+this.x+", "+this.y+")"});goog.math.Coordinate.prototype.equals=function(a){return a instanceof goog.math.Coordinate&&goog.math.Coordinate.equals(this,a)};goog.math.Coordinate.equals=function(a,b){return a==b?!0:a&&b?a.x==b.x&&a.y==b.y:!1};
+goog.math.Coordinate.distance=function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)};goog.math.Coordinate.magnitude=function(a){return Math.sqrt(a.x*a.x+a.y*a.y)};goog.math.Coordinate.azimuth=function(a){return goog.math.angle(0,0,a.x,a.y)};goog.math.Coordinate.squaredDistance=function(a,b){var c=a.x-b.x,d=a.y-b.y;return c*c+d*d};goog.math.Coordinate.difference=function(a,b){return new goog.math.Coordinate(a.x-b.x,a.y-b.y)};
+goog.math.Coordinate.sum=function(a,b){return new goog.math.Coordinate(a.x+b.x,a.y+b.y)};goog.math.Coordinate.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};goog.math.Coordinate.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};goog.math.Coordinate.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};
+goog.math.Coordinate.prototype.translate=function(a,b){a instanceof goog.math.Coordinate?(this.x+=a.x,this.y+=a.y):(this.x+=Number(a),goog.isNumber(b)&&(this.y+=b));return this};goog.math.Coordinate.prototype.scale=function(a,b){var c=goog.isNumber(b)?b:a;this.x*=a;this.y*=c;return this};goog.math.Coordinate.prototype.rotateRadians=function(a,b){var c=b||new goog.math.Coordinate(0,0),d=this.x,e=this.y,f=Math.cos(a),g=Math.sin(a);this.x=(d-c.x)*f-(e-c.y)*g+c.x;this.y=(d-c.x)*g+(e-c.y)*f+c.y};
+goog.math.Coordinate.prototype.rotateDegrees=function(a,b){this.rotateRadians(goog.math.toRadians(a),b)};goog.math.Size=function(a,b){this.width=a;this.height=b};goog.math.Size.equals=function(a,b){return a==b?!0:a&&b?a.width==b.width&&a.height==b.height:!1};goog.math.Size.prototype.clone=function(){return new goog.math.Size(this.width,this.height)};goog.DEBUG&&(goog.math.Size.prototype.toString=function(){return"("+this.width+" x "+this.height+")"});goog.math.Size.prototype.getLongest=function(){return Math.max(this.width,this.height)};
+goog.math.Size.prototype.getShortest=function(){return Math.min(this.width,this.height)};goog.math.Size.prototype.area=function(){return this.width*this.height};goog.math.Size.prototype.perimeter=function(){return 2*(this.width+this.height)};goog.math.Size.prototype.aspectRatio=function(){return this.width/this.height};goog.math.Size.prototype.isEmpty=function(){return!this.area()};goog.math.Size.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};
+goog.math.Size.prototype.fitsInside=function(a){return this.width<=a.width&&this.height<=a.height};goog.math.Size.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};goog.math.Size.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};goog.math.Size.prototype.scale=function(a,b){var c=goog.isNumber(b)?b:a;this.width*=a;this.height*=c;return this};
+goog.math.Size.prototype.scaleToCover=function(a){a=this.aspectRatio()<=a.aspectRatio()?a.width/this.width:a.height/this.height;return this.scale(a)};goog.math.Size.prototype.scaleToFit=function(a){a=this.aspectRatio()>a.aspectRatio()?a.width/this.width:a.height/this.height;return this.scale(a)};goog.dom.ASSUME_QUIRKS_MODE=!1;goog.dom.ASSUME_STANDARDS_MODE=!1;goog.dom.COMPAT_MODE_KNOWN_=goog.dom.ASSUME_QUIRKS_MODE||goog.dom.ASSUME_STANDARDS_MODE;goog.dom.getDomHelper=function(a){return a?new goog.dom.DomHelper(goog.dom.getOwnerDocument(a)):goog.dom.defaultDomHelper_||(goog.dom.defaultDomHelper_=new goog.dom.DomHelper)};goog.dom.getDocument=function(){return document};goog.dom.getElement=function(a){return goog.dom.getElementHelper_(document,a)};
+goog.dom.getElementHelper_=function(a,b){return goog.isString(b)?a.getElementById(b):b};goog.dom.getRequiredElement=function(a){return goog.dom.getRequiredElementHelper_(document,a)};goog.dom.getRequiredElementHelper_=function(a,b){goog.asserts.assertString(b);var c=goog.dom.getElementHelper_(a,b);return c=goog.asserts.assertElement(c,"No element found with id: "+b)};goog.dom.$=goog.dom.getElement;goog.dom.getElementsByTagName=function(a,b){return(b||document).getElementsByTagName(String(a))};
+goog.dom.getElementsByTagNameAndClass=function(a,b,c){return goog.dom.getElementsByTagNameAndClass_(document,a,b,c)};goog.dom.getElementsByClass=function(a,b){var c=b||document;return goog.dom.canUseQuerySelector_(c)?c.querySelectorAll("."+a):goog.dom.getElementsByTagNameAndClass_(document,"*",a,b)};
+goog.dom.getElementByClass=function(a,b){var c=b||document;return(c.getElementsByClassName?c.getElementsByClassName(a)[0]:goog.dom.canUseQuerySelector_(c)?c.querySelector("."+a):goog.dom.getElementsByTagNameAndClass_(document,"*",a,b)[0])||null};goog.dom.getRequiredElementByClass=function(a,b){var c=goog.dom.getElementByClass(a,b);return goog.asserts.assert(c,"No element found with className: "+a)};goog.dom.canUseQuerySelector_=function(a){return!(!a.querySelectorAll||!a.querySelector)};
+goog.dom.getElementsByTagNameAndClass_=function(a,b,c,d){a=d||a;b=b&&"*"!=b?String(b).toUpperCase():"";if(goog.dom.canUseQuerySelector_(a)&&(b||c))return a.querySelectorAll(b+(c?"."+c:""));if(c&&a.getElementsByClassName){a=a.getElementsByClassName(c);if(b){d={};for(var e=0,f=0,g;g=a[f];f++)b==g.nodeName&&(d[e++]=g);d.length=e;return d}return a}a=a.getElementsByTagName(b||"*");if(c){d={};for(f=e=0;g=a[f];f++)b=g.className,"function"==typeof b.split&&goog.array.contains(b.split(/\s+/),c)&&(d[e++]=g);
+d.length=e;return d}return a};goog.dom.$$=goog.dom.getElementsByTagNameAndClass;goog.dom.setProperties=function(a,b){goog.object.forEach(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:goog.dom.DIRECT_ATTRIBUTE_MAP_.hasOwnProperty(d)?a.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[d],b):goog.string.startsWith(d,"aria-")||goog.string.startsWith(d,"data-")?a.setAttribute(d,b):a[d]=b})};
+goog.dom.DIRECT_ATTRIBUTE_MAP_={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",nonce:"nonce",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};goog.dom.getViewportSize=function(a){return goog.dom.getViewportSize_(a||window)};goog.dom.getViewportSize_=function(a){a=a.document;a=goog.dom.isCss1CompatMode_(a)?a.documentElement:a.body;return new goog.math.Size(a.clientWidth,a.clientHeight)};
+goog.dom.getDocumentHeight=function(){return goog.dom.getDocumentHeight_(window)};goog.dom.getDocumentHeightForWindow=function(a){return goog.dom.getDocumentHeight_(a)};
+goog.dom.getDocumentHeight_=function(a){var b=a.document,c=0;if(b){var c=b.body,d=b.documentElement;if(!d||!c)return 0;a=goog.dom.getViewportSize_(a).height;if(goog.dom.isCss1CompatMode_(b)&&d.scrollHeight)c=d.scrollHeight!=a?d.scrollHeight:d.offsetHeight;else{var b=d.scrollHeight,e=d.offsetHeight;d.clientHeight!=e&&(b=c.scrollHeight,e=c.offsetHeight);c=b>a?b>e?b:e:b<e?b:e}}return c};goog.dom.getPageScroll=function(a){return goog.dom.getDomHelper((a||goog.global||window).document).getDocumentScroll()};
+goog.dom.getDocumentScroll=function(){return goog.dom.getDocumentScroll_(document)};goog.dom.getDocumentScroll_=function(a){var b=goog.dom.getDocumentScrollElement_(a);a=goog.dom.getWindow_(a);return goog.userAgent.IE&&goog.userAgent.isVersionOrHigher("10")&&a.pageYOffset!=b.scrollTop?new goog.math.Coordinate(b.scrollLeft,b.scrollTop):new goog.math.Coordinate(a.pageXOffset||b.scrollLeft,a.pageYOffset||b.scrollTop)};goog.dom.getDocumentScrollElement=function(){return goog.dom.getDocumentScrollElement_(document)};
+goog.dom.getDocumentScrollElement_=function(a){return a.scrollingElement?a.scrollingElement:!goog.userAgent.WEBKIT&&goog.dom.isCss1CompatMode_(a)?a.documentElement:a.body||a.documentElement};goog.dom.getWindow=function(a){return a?goog.dom.getWindow_(a):window};goog.dom.getWindow_=function(a){return a.parentWindow||a.defaultView};goog.dom.createDom=function(a,b,c){return goog.dom.createDom_(document,arguments)};
+goog.dom.createDom_=function(a,b){var c=String(b[0]),d=b[1];if(!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES&&d&&(d.name||d.type)){c=["<",c];d.name&&c.push(' name="',goog.string.htmlEscape(d.name),'"');if(d.type){c.push(' type="',goog.string.htmlEscape(d.type),'"');var e={};goog.object.extend(e,d);delete e.type;d=e}c.push(">");c=c.join("")}c=a.createElement(c);d&&(goog.isString(d)?c.className=d:goog.isArray(d)?c.className=d.join(" "):goog.dom.setProperties(c,d));2<b.length&&goog.dom.append_(a,
+c,b,2);return c};goog.dom.append_=function(a,b,c,d){function e(c){c&&b.appendChild(goog.isString(c)?a.createTextNode(c):c)}for(;d<c.length;d++){var f=c[d];goog.isArrayLike(f)&&!goog.dom.isNodeLike(f)?goog.array.forEach(goog.dom.isNodeList(f)?goog.array.toArray(f):f,e):e(f)}};goog.dom.$dom=goog.dom.createDom;goog.dom.createElement=function(a){return goog.dom.createElement_(document,a)};goog.dom.createElement_=function(a,b){return a.createElement(String(b))};goog.dom.createTextNode=function(a){return document.createTextNode(String(a))};
+goog.dom.createTable=function(a,b,c){return goog.dom.createTable_(document,a,b,!!c)};goog.dom.createTable_=function(a,b,c,d){for(var e=goog.dom.createElement_(a,"TABLE"),f=e.appendChild(goog.dom.createElement_(a,"TBODY")),g=0;g<b;g++){for(var h=goog.dom.createElement_(a,"TR"),k=0;k<c;k++){var l=goog.dom.createElement_(a,"TD");d&&goog.dom.setTextContent(l,goog.string.Unicode.NBSP);h.appendChild(l)}f.appendChild(h)}return e};
+goog.dom.constHtmlToNode=function(a){var b=goog.array.map(arguments,goog.string.Const.unwrap),b=goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract(goog.string.Const.from("Constant HTML string, that gets turned into a Node later, so it will be automatically balanced."),b.join(""));return goog.dom.safeHtmlToNode(b)};goog.dom.safeHtmlToNode=function(a){return goog.dom.safeHtmlToNode_(document,a)};
+goog.dom.safeHtmlToNode_=function(a,b){var c=goog.dom.createElement_(a,"DIV");goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT?(goog.dom.safe.setInnerHtml(c,goog.html.SafeHtml.concat(goog.html.SafeHtml.BR,b)),c.removeChild(c.firstChild)):goog.dom.safe.setInnerHtml(c,b);return goog.dom.childrenToNode_(a,c)};goog.dom.childrenToNode_=function(a,b){if(1==b.childNodes.length)return b.removeChild(b.firstChild);for(var c=a.createDocumentFragment();b.firstChild;)c.appendChild(b.firstChild);return c};
+goog.dom.isCss1CompatMode=function(){return goog.dom.isCss1CompatMode_(document)};goog.dom.isCss1CompatMode_=function(a){return goog.dom.COMPAT_MODE_KNOWN_?goog.dom.ASSUME_STANDARDS_MODE:"CSS1Compat"==a.compatMode};goog.dom.canHaveChildren=function(a){if(a.nodeType!=goog.dom.NodeType.ELEMENT)return!1;switch(a.tagName){case "APPLET":case "AREA":case "BASE":case "BR":case "COL":case "COMMAND":case "EMBED":case "FRAME":case "HR":case "IMG":case "INPUT":case "IFRAME":case "ISINDEX":case "KEYGEN":case "LINK":case "NOFRAMES":case "NOSCRIPT":case "META":case "OBJECT":case "PARAM":case "SCRIPT":case "SOURCE":case "STYLE":case "TRACK":case "WBR":return!1}return!0};
+goog.dom.appendChild=function(a,b){a.appendChild(b)};goog.dom.append=function(a,b){goog.dom.append_(goog.dom.getOwnerDocument(a),a,arguments,1)};goog.dom.removeChildren=function(a){for(var b;b=a.firstChild;)a.removeChild(b)};goog.dom.insertSiblingBefore=function(a,b){b.parentNode&&b.parentNode.insertBefore(a,b)};goog.dom.insertSiblingAfter=function(a,b){b.parentNode&&b.parentNode.insertBefore(a,b.nextSibling)};goog.dom.insertChildAt=function(a,b,c){a.insertBefore(b,a.childNodes[c]||null)};
+goog.dom.removeNode=function(a){return a&&a.parentNode?a.parentNode.removeChild(a):null};goog.dom.replaceNode=function(a,b){var c=b.parentNode;c&&c.replaceChild(a,b)};goog.dom.flattenElement=function(a){var b,c=a.parentNode;if(c&&c.nodeType!=goog.dom.NodeType.DOCUMENT_FRAGMENT){if(a.removeNode)return a.removeNode(!1);for(;b=a.firstChild;)c.insertBefore(b,a);return goog.dom.removeNode(a)}};
+goog.dom.getChildren=function(a){return goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE&&void 0!=a.children?a.children:goog.array.filter(a.childNodes,function(a){return a.nodeType==goog.dom.NodeType.ELEMENT})};goog.dom.getFirstElementChild=function(a){return goog.isDef(a.firstElementChild)?a.firstElementChild:goog.dom.getNextElementNode_(a.firstChild,!0)};goog.dom.getLastElementChild=function(a){return goog.isDef(a.lastElementChild)?a.lastElementChild:goog.dom.getNextElementNode_(a.lastChild,!1)};
+goog.dom.getNextElementSibling=function(a){return goog.isDef(a.nextElementSibling)?a.nextElementSibling:goog.dom.getNextElementNode_(a.nextSibling,!0)};goog.dom.getPreviousElementSibling=function(a){return goog.isDef(a.previousElementSibling)?a.previousElementSibling:goog.dom.getNextElementNode_(a.previousSibling,!1)};goog.dom.getNextElementNode_=function(a,b){for(;a&&a.nodeType!=goog.dom.NodeType.ELEMENT;)a=b?a.nextSibling:a.previousSibling;return a};
+goog.dom.getNextNode=function(a){if(!a)return null;if(a.firstChild)return a.firstChild;for(;a&&!a.nextSibling;)a=a.parentNode;return a?a.nextSibling:null};goog.dom.getPreviousNode=function(a){if(!a)return null;if(!a.previousSibling)return a.parentNode;for(a=a.previousSibling;a&&a.lastChild;)a=a.lastChild;return a};goog.dom.isNodeLike=function(a){return goog.isObject(a)&&0<a.nodeType};goog.dom.isElement=function(a){return goog.isObject(a)&&a.nodeType==goog.dom.NodeType.ELEMENT};
+goog.dom.isWindow=function(a){return goog.isObject(a)&&a.window==a};goog.dom.getParentElement=function(a){var b;if(goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY&&!(goog.userAgent.IE&&goog.userAgent.isVersionOrHigher("9")&&!goog.userAgent.isVersionOrHigher("10")&&goog.global.SVGElement&&a instanceof goog.global.SVGElement)&&(b=a.parentElement))return b;b=a.parentNode;return goog.dom.isElement(b)?b:null};
+goog.dom.contains=function(a,b){if(!a||!b)return!1;if(a.contains&&b.nodeType==goog.dom.NodeType.ELEMENT)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a};
+goog.dom.compareNodeOrder=function(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(goog.userAgent.IE&&!goog.userAgent.isDocumentModeOrHigher(9)){if(a.nodeType==goog.dom.NodeType.DOCUMENT)return-1;if(b.nodeType==goog.dom.NodeType.DOCUMENT)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=a.nodeType==goog.dom.NodeType.ELEMENT,d=b.nodeType==goog.dom.NodeType.ELEMENT;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,
+f=b.parentNode;return e==f?goog.dom.compareSiblingOrder_(a,b):!c&&goog.dom.contains(e,b)?-1*goog.dom.compareParentsDescendantNodeIe_(a,b):!d&&goog.dom.contains(f,a)?goog.dom.compareParentsDescendantNodeIe_(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=goog.dom.getOwnerDocument(a);c=d.createRange();c.selectNode(a);c.collapse(!0);d=d.createRange();d.selectNode(b);d.collapse(!0);return c.compareBoundaryPoints(goog.global.Range.START_TO_END,d)};
+goog.dom.compareParentsDescendantNodeIe_=function(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return goog.dom.compareSiblingOrder_(d,a)};goog.dom.compareSiblingOrder_=function(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1};
+goog.dom.findCommonAncestor=function(a){var b,c=arguments.length;if(!c)return null;if(1==c)return arguments[0];var d=[],e=Infinity;for(b=0;b<c;b++){for(var f=[],g=arguments[b];g;)f.unshift(g),g=g.parentNode;d.push(f);e=Math.min(e,f.length)}f=null;for(b=0;b<e;b++){for(var g=d[0][b],h=1;h<c;h++)if(g!=d[h][b])return f;f=g}return f};goog.dom.getOwnerDocument=function(a){goog.asserts.assert(a,"Node cannot be null or undefined.");return a.nodeType==goog.dom.NodeType.DOCUMENT?a:a.ownerDocument||a.document};
+goog.dom.getFrameContentDocument=function(a){return a.contentDocument||a.contentWindow.document};goog.dom.getFrameContentWindow=function(a){try{return a.contentWindow||(a.contentDocument?goog.dom.getWindow(a.contentDocument):null)}catch(b){}return null};
+goog.dom.setTextContent=function(a,b){goog.asserts.assert(null!=a,"goog.dom.setTextContent expects a non-null value for node");if("textContent"in a)a.textContent=b;else if(a.nodeType==goog.dom.NodeType.TEXT)a.data=b;else if(a.firstChild&&a.firstChild.nodeType==goog.dom.NodeType.TEXT){for(;a.lastChild!=a.firstChild;)a.removeChild(a.lastChild);a.firstChild.data=b}else{goog.dom.removeChildren(a);var c=goog.dom.getOwnerDocument(a);a.appendChild(c.createTextNode(String(b)))}};
+goog.dom.getOuterHtml=function(a){goog.asserts.assert(null!==a,"goog.dom.getOuterHtml expects a non-null value for element");if("outerHTML"in a)return a.outerHTML;var b=goog.dom.getOwnerDocument(a),b=goog.dom.createElement_(b,"DIV");b.appendChild(a.cloneNode(!0));return b.innerHTML};goog.dom.findNode=function(a,b){var c=[];return goog.dom.findNodes_(a,b,c,!0)?c[0]:void 0};goog.dom.findNodes=function(a,b){var c=[];goog.dom.findNodes_(a,b,c,!1);return c};
+goog.dom.findNodes_=function(a,b,c,d){if(null!=a)for(a=a.firstChild;a;){if(b(a)&&(c.push(a),d)||goog.dom.findNodes_(a,b,c,d))return!0;a=a.nextSibling}return!1};goog.dom.TAGS_TO_IGNORE_={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1};goog.dom.PREDEFINED_TAG_VALUES_={IMG:" ",BR:"\n"};goog.dom.isFocusableTabIndex=function(a){return goog.dom.hasSpecifiedTabIndex_(a)&&goog.dom.isTabIndexFocusable_(a)};goog.dom.setFocusableTabIndex=function(a,b){b?a.tabIndex=0:(a.tabIndex=-1,a.removeAttribute("tabIndex"))};
+goog.dom.isFocusable=function(a){var b;return(b=goog.dom.nativelySupportsFocus_(a)?!a.disabled&&(!goog.dom.hasSpecifiedTabIndex_(a)||goog.dom.isTabIndexFocusable_(a)):goog.dom.isFocusableTabIndex(a))&&goog.userAgent.IE?goog.dom.hasNonZeroBoundingRect_(a):b};goog.dom.hasSpecifiedTabIndex_=function(a){return goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("9")?(a=a.getAttributeNode("tabindex"),goog.isDefAndNotNull(a)&&a.specified):a.hasAttribute("tabindex")};
+goog.dom.isTabIndexFocusable_=function(a){a=a.tabIndex;return goog.isNumber(a)&&0<=a&&32768>a};goog.dom.nativelySupportsFocus_=function(a){return"A"==a.tagName||"INPUT"==a.tagName||"TEXTAREA"==a.tagName||"SELECT"==a.tagName||"BUTTON"==a.tagName};goog.dom.hasNonZeroBoundingRect_=function(a){a=!goog.isFunction(a.getBoundingClientRect)||goog.userAgent.IE&&null==a.parentElement?{height:a.offsetHeight,width:a.offsetWidth}:a.getBoundingClientRect();return goog.isDefAndNotNull(a)&&0<a.height&&0<a.width};
+goog.dom.getTextContent=function(a){if(goog.dom.BrowserFeature.CAN_USE_INNER_TEXT&&null!==a&&"innerText"in a)a=goog.string.canonicalizeNewlines(a.innerText);else{var b=[];goog.dom.getTextContent_(a,b,!0);a=b.join("")}a=a.replace(/ \xAD /g," ").replace(/\xAD/g,"");a=a.replace(/\u200B/g,"");goog.dom.BrowserFeature.CAN_USE_INNER_TEXT||(a=a.replace(/ +/g," "));" "!=a&&(a=a.replace(/^\s*/,""));return a};goog.dom.getRawTextContent=function(a){var b=[];goog.dom.getTextContent_(a,b,!1);return b.join("")};
+goog.dom.getTextContent_=function(a,b,c){if(!(a.nodeName in goog.dom.TAGS_TO_IGNORE_))if(a.nodeType==goog.dom.NodeType.TEXT)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in goog.dom.PREDEFINED_TAG_VALUES_)b.push(goog.dom.PREDEFINED_TAG_VALUES_[a.nodeName]);else for(a=a.firstChild;a;)goog.dom.getTextContent_(a,b,c),a=a.nextSibling};goog.dom.getNodeTextLength=function(a){return goog.dom.getTextContent(a).length};
+goog.dom.getNodeTextOffset=function(a,b){for(var c=b||goog.dom.getOwnerDocument(a).body,d=[];a&&a!=c;){for(var e=a;e=e.previousSibling;)d.unshift(goog.dom.getTextContent(e));a=a.parentNode}return goog.string.trimLeft(d.join("")).replace(/ +/g," ").length};
+goog.dom.getNodeAtOffset=function(a,b,c){a=[a];for(var d=0,e=null;0<a.length&&d<b;)if(e=a.pop(),!(e.nodeName in goog.dom.TAGS_TO_IGNORE_))if(e.nodeType==goog.dom.NodeType.TEXT)var f=e.nodeValue.replace(/(\r\n|\r|\n)/g,"").replace(/ +/g," "),d=d+f.length;else if(e.nodeName in goog.dom.PREDEFINED_TAG_VALUES_)d+=goog.dom.PREDEFINED_TAG_VALUES_[e.nodeName].length;else for(f=e.childNodes.length-1;0<=f;f--)a.push(e.childNodes[f]);goog.isObject(c)&&(c.remainder=e?e.nodeValue.length+b-d-1:0,c.node=e);return e};
+goog.dom.isNodeList=function(a){if(a&&"number"==typeof a.length){if(goog.isObject(a))return"function"==typeof a.item||"string"==typeof a.item;if(goog.isFunction(a))return"function"==typeof a.item}return!1};goog.dom.getAncestorByTagNameAndClass=function(a,b,c,d){if(!b&&!c)return null;var e=b?String(b).toUpperCase():null;return goog.dom.getAncestor(a,function(a){return(!e||a.nodeName==e)&&(!c||goog.isString(a.className)&&goog.array.contains(a.className.split(/\s+/),c))},!0,d)};
+goog.dom.getAncestorByClass=function(a,b,c){return goog.dom.getAncestorByTagNameAndClass(a,null,b,c)};goog.dom.getAncestor=function(a,b,c,d){a&&!c&&(a=a.parentNode);for(c=0;a&&(null==d||c<=d);){goog.asserts.assert("parentNode"!=a.name);if(b(a))return a;a=a.parentNode;c++}return null};goog.dom.getActiveElement=function(a){try{return a&&a.activeElement}catch(b){}return null};
+goog.dom.getPixelRatio=function(){var a=goog.dom.getWindow();return goog.isDef(a.devicePixelRatio)?a.devicePixelRatio:a.matchMedia?goog.dom.matchesPixelRatio_(3)||goog.dom.matchesPixelRatio_(2)||goog.dom.matchesPixelRatio_(1.5)||goog.dom.matchesPixelRatio_(1)||.75:1};goog.dom.matchesPixelRatio_=function(a){return goog.dom.getWindow().matchMedia("(min-resolution: "+a+"dppx),(min--moz-device-pixel-ratio: "+a+"),(min-resolution: "+96*a+"dpi)").matches?a:0};goog.dom.getCanvasContext2D=function(a){return a.getContext("2d")};
+goog.dom.DomHelper=function(a){this.document_=a||goog.global.document||document};goog.dom.DomHelper.prototype.getDomHelper=goog.dom.getDomHelper;goog.dom.DomHelper.prototype.setDocument=function(a){this.document_=a};goog.dom.DomHelper.prototype.getDocument=function(){return this.document_};goog.dom.DomHelper.prototype.getElement=function(a){return goog.dom.getElementHelper_(this.document_,a)};
+goog.dom.DomHelper.prototype.getRequiredElement=function(a){return goog.dom.getRequiredElementHelper_(this.document_,a)};goog.dom.DomHelper.prototype.$=goog.dom.DomHelper.prototype.getElement;goog.dom.DomHelper.prototype.getElementsByTagName=function(a,b){return(b||this.document_).getElementsByTagName(String(a))};goog.dom.DomHelper.prototype.getElementsByTagNameAndClass=function(a,b,c){return goog.dom.getElementsByTagNameAndClass_(this.document_,a,b,c)};
+goog.dom.DomHelper.prototype.getElementsByClass=function(a,b){return goog.dom.getElementsByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.getElementByClass=function(a,b){return goog.dom.getElementByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.getRequiredElementByClass=function(a,b){return goog.dom.getRequiredElementByClass(a,b||this.document_)};goog.dom.DomHelper.prototype.$$=goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
+goog.dom.DomHelper.prototype.setProperties=goog.dom.setProperties;goog.dom.DomHelper.prototype.getViewportSize=function(a){return goog.dom.getViewportSize(a||this.getWindow())};goog.dom.DomHelper.prototype.getDocumentHeight=function(){return goog.dom.getDocumentHeight_(this.getWindow())};goog.dom.DomHelper.prototype.createDom=function(a,b,c){return goog.dom.createDom_(this.document_,arguments)};goog.dom.DomHelper.prototype.$dom=goog.dom.DomHelper.prototype.createDom;
+goog.dom.DomHelper.prototype.createElement=function(a){return goog.dom.createElement_(this.document_,a)};goog.dom.DomHelper.prototype.createTextNode=function(a){return this.document_.createTextNode(String(a))};goog.dom.DomHelper.prototype.createTable=function(a,b,c){return goog.dom.createTable_(this.document_,a,b,!!c)};goog.dom.DomHelper.prototype.safeHtmlToNode=function(a){return goog.dom.safeHtmlToNode_(this.document_,a)};goog.dom.DomHelper.prototype.isCss1CompatMode=function(){return goog.dom.isCss1CompatMode_(this.document_)};
+goog.dom.DomHelper.prototype.getWindow=function(){return goog.dom.getWindow_(this.document_)};goog.dom.DomHelper.prototype.getDocumentScrollElement=function(){return goog.dom.getDocumentScrollElement_(this.document_)};goog.dom.DomHelper.prototype.getDocumentScroll=function(){return goog.dom.getDocumentScroll_(this.document_)};goog.dom.DomHelper.prototype.getActiveElement=function(a){return goog.dom.getActiveElement(a||this.document_)};goog.dom.DomHelper.prototype.appendChild=goog.dom.appendChild;
+goog.dom.DomHelper.prototype.append=goog.dom.append;goog.dom.DomHelper.prototype.canHaveChildren=goog.dom.canHaveChildren;goog.dom.DomHelper.prototype.removeChildren=goog.dom.removeChildren;goog.dom.DomHelper.prototype.insertSiblingBefore=goog.dom.insertSiblingBefore;goog.dom.DomHelper.prototype.insertSiblingAfter=goog.dom.insertSiblingAfter;goog.dom.DomHelper.prototype.insertChildAt=goog.dom.insertChildAt;goog.dom.DomHelper.prototype.removeNode=goog.dom.removeNode;
+goog.dom.DomHelper.prototype.replaceNode=goog.dom.replaceNode;goog.dom.DomHelper.prototype.flattenElement=goog.dom.flattenElement;goog.dom.DomHelper.prototype.getChildren=goog.dom.getChildren;goog.dom.DomHelper.prototype.getFirstElementChild=goog.dom.getFirstElementChild;goog.dom.DomHelper.prototype.getLastElementChild=goog.dom.getLastElementChild;goog.dom.DomHelper.prototype.getNextElementSibling=goog.dom.getNextElementSibling;goog.dom.DomHelper.prototype.getPreviousElementSibling=goog.dom.getPreviousElementSibling;
+goog.dom.DomHelper.prototype.getNextNode=goog.dom.getNextNode;goog.dom.DomHelper.prototype.getPreviousNode=goog.dom.getPreviousNode;goog.dom.DomHelper.prototype.isNodeLike=goog.dom.isNodeLike;goog.dom.DomHelper.prototype.isElement=goog.dom.isElement;goog.dom.DomHelper.prototype.isWindow=goog.dom.isWindow;goog.dom.DomHelper.prototype.getParentElement=goog.dom.getParentElement;goog.dom.DomHelper.prototype.contains=goog.dom.contains;goog.dom.DomHelper.prototype.compareNodeOrder=goog.dom.compareNodeOrder;
+goog.dom.DomHelper.prototype.findCommonAncestor=goog.dom.findCommonAncestor;goog.dom.DomHelper.prototype.getOwnerDocument=goog.dom.getOwnerDocument;goog.dom.DomHelper.prototype.getFrameContentDocument=goog.dom.getFrameContentDocument;goog.dom.DomHelper.prototype.getFrameContentWindow=goog.dom.getFrameContentWindow;goog.dom.DomHelper.prototype.setTextContent=goog.dom.setTextContent;goog.dom.DomHelper.prototype.getOuterHtml=goog.dom.getOuterHtml;goog.dom.DomHelper.prototype.findNode=goog.dom.findNode;
+goog.dom.DomHelper.prototype.findNodes=goog.dom.findNodes;goog.dom.DomHelper.prototype.isFocusableTabIndex=goog.dom.isFocusableTabIndex;goog.dom.DomHelper.prototype.setFocusableTabIndex=goog.dom.setFocusableTabIndex;goog.dom.DomHelper.prototype.isFocusable=goog.dom.isFocusable;goog.dom.DomHelper.prototype.getTextContent=goog.dom.getTextContent;goog.dom.DomHelper.prototype.getNodeTextLength=goog.dom.getNodeTextLength;goog.dom.DomHelper.prototype.getNodeTextOffset=goog.dom.getNodeTextOffset;
+goog.dom.DomHelper.prototype.getNodeAtOffset=goog.dom.getNodeAtOffset;goog.dom.DomHelper.prototype.isNodeList=goog.dom.isNodeList;goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass=goog.dom.getAncestorByTagNameAndClass;goog.dom.DomHelper.prototype.getAncestorByClass=goog.dom.getAncestorByClass;goog.dom.DomHelper.prototype.getAncestor=goog.dom.getAncestor;goog.dom.DomHelper.prototype.getCanvasContext2D=goog.dom.getCanvasContext2D;goog.dom.vendor={};goog.dom.vendor.getVendorJsPrefix=function(){return goog.userAgent.WEBKIT?"Webkit":goog.userAgent.GECKO?"Moz":goog.userAgent.IE?"ms":goog.userAgent.OPERA?"O":null};goog.dom.vendor.getVendorPrefix=function(){return goog.userAgent.WEBKIT?"-webkit":goog.userAgent.GECKO?"-moz":goog.userAgent.IE?"-ms":goog.userAgent.OPERA?"-o":null};
+goog.dom.vendor.getPrefixedPropertyName=function(a,b){if(b&&a in b)return a;var c=goog.dom.vendor.getVendorJsPrefix();return c?(c=c.toLowerCase(),c+=goog.string.toTitleCase(a),!goog.isDef(b)||c in b?c:null):null};goog.dom.vendor.getPrefixedEventType=function(a){return((goog.dom.vendor.getVendorJsPrefix()||"")+a).toLowerCase()};goog.html.legacyconversions={};goog.html.legacyconversions.safeHtmlFromString=function(a){goog.html.legacyconversions.reportCallback_();return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(a,null)};goog.html.legacyconversions.safeStyleFromString=function(a){goog.html.legacyconversions.reportCallback_();return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(a)};
+goog.html.legacyconversions.safeStyleSheetFromString=function(a){goog.html.legacyconversions.reportCallback_();return goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(a)};goog.html.legacyconversions.safeUrlFromString=function(a){goog.html.legacyconversions.reportCallback_();return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(a)};goog.html.legacyconversions.trustedResourceUrlFromString=function(a){goog.html.legacyconversions.reportCallback_();return goog.html.TrustedResourceUrl.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(a)};
+goog.html.legacyconversions.reportCallback_=goog.nullFunction;goog.html.legacyconversions.setReportCallback=function(a){goog.html.legacyconversions.reportCallback_=a};goog.math.Box=function(a,b,c,d){this.top=a;this.right=b;this.bottom=c;this.left=d};goog.math.Box.boundingBox=function(a){for(var b=new goog.math.Box(arguments[0].y,arguments[0].x,arguments[0].y,arguments[0].x),c=1;c<arguments.length;c++)b.expandToIncludeCoordinate(arguments[c]);return b};goog.math.Box.prototype.getWidth=function(){return this.right-this.left};goog.math.Box.prototype.getHeight=function(){return this.bottom-this.top};
+goog.math.Box.prototype.clone=function(){return new goog.math.Box(this.top,this.right,this.bottom,this.left)};goog.DEBUG&&(goog.math.Box.prototype.toString=function(){return"("+this.top+"t, "+this.right+"r, "+this.bottom+"b, "+this.left+"l)"});goog.math.Box.prototype.contains=function(a){return goog.math.Box.contains(this,a)};
+goog.math.Box.prototype.expand=function(a,b,c,d){goog.isObject(a)?(this.top-=a.top,this.right+=a.right,this.bottom+=a.bottom,this.left-=a.left):(this.top-=a,this.right+=Number(b),this.bottom+=Number(c),this.left-=Number(d));return this};goog.math.Box.prototype.expandToInclude=function(a){this.left=Math.min(this.left,a.left);this.top=Math.min(this.top,a.top);this.right=Math.max(this.right,a.right);this.bottom=Math.max(this.bottom,a.bottom)};
+goog.math.Box.prototype.expandToIncludeCoordinate=function(a){this.top=Math.min(this.top,a.y);this.right=Math.max(this.right,a.x);this.bottom=Math.max(this.bottom,a.y);this.left=Math.min(this.left,a.x)};goog.math.Box.equals=function(a,b){return a==b?!0:a&&b?a.top==b.top&&a.right==b.right&&a.bottom==b.bottom&&a.left==b.left:!1};
+goog.math.Box.contains=function(a,b){return a&&b?b instanceof goog.math.Box?b.left>=a.left&&b.right<=a.right&&b.top>=a.top&&b.bottom<=a.bottom:b.x>=a.left&&b.x<=a.right&&b.y>=a.top&&b.y<=a.bottom:!1};goog.math.Box.relativePositionX=function(a,b){return b.x<a.left?b.x-a.left:b.x>a.right?b.x-a.right:0};goog.math.Box.relativePositionY=function(a,b){return b.y<a.top?b.y-a.top:b.y>a.bottom?b.y-a.bottom:0};
+goog.math.Box.distance=function(a,b){var c=goog.math.Box.relativePositionX(a,b),d=goog.math.Box.relativePositionY(a,b);return Math.sqrt(c*c+d*d)};goog.math.Box.intersects=function(a,b){return a.left<=b.right&&b.left<=a.right&&a.top<=b.bottom&&b.top<=a.bottom};goog.math.Box.intersectsWithPadding=function(a,b,c){return a.left<=b.right+c&&b.left<=a.right+c&&a.top<=b.bottom+c&&b.top<=a.bottom+c};
+goog.math.Box.prototype.ceil=function(){this.top=Math.ceil(this.top);this.right=Math.ceil(this.right);this.bottom=Math.ceil(this.bottom);this.left=Math.ceil(this.left);return this};goog.math.Box.prototype.floor=function(){this.top=Math.floor(this.top);this.right=Math.floor(this.right);this.bottom=Math.floor(this.bottom);this.left=Math.floor(this.left);return this};
+goog.math.Box.prototype.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this};goog.math.Box.prototype.translate=function(a,b){a instanceof goog.math.Coordinate?(this.left+=a.x,this.right+=a.x,this.top+=a.y,this.bottom+=a.y):(goog.asserts.assertNumber(a),this.left+=a,this.right+=a,goog.isNumber(b)&&(this.top+=b,this.bottom+=b));return this};
+goog.math.Box.prototype.scale=function(a,b){var c=goog.isNumber(b)?b:a;this.left*=a;this.right*=a;this.top*=c;this.bottom*=c;return this};goog.math.IRect=function(){};goog.math.Rect=function(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d};goog.math.Rect.prototype.clone=function(){return new goog.math.Rect(this.left,this.top,this.width,this.height)};goog.math.Rect.prototype.toBox=function(){return new goog.math.Box(this.top,this.left+this.width,this.top+this.height,this.left)};goog.math.Rect.createFromPositionAndSize=function(a,b){return new goog.math.Rect(a.x,a.y,b.width,b.height)};
+goog.math.Rect.createFromBox=function(a){return new goog.math.Rect(a.left,a.top,a.right-a.left,a.bottom-a.top)};goog.DEBUG&&(goog.math.Rect.prototype.toString=function(){return"("+this.left+", "+this.top+" - "+this.width+"w x "+this.height+"h)"});goog.math.Rect.equals=function(a,b){return a==b?!0:a&&b?a.left==b.left&&a.width==b.width&&a.top==b.top&&a.height==b.height:!1};
+goog.math.Rect.prototype.intersection=function(a){var b=Math.max(this.left,a.left),c=Math.min(this.left+this.width,a.left+a.width);if(b<=c){var d=Math.max(this.top,a.top);a=Math.min(this.top+this.height,a.top+a.height);if(d<=a)return this.left=b,this.top=d,this.width=c-b,this.height=a-d,!0}return!1};
+goog.math.Rect.intersection=function(a,b){var c=Math.max(a.left,b.left),d=Math.min(a.left+a.width,b.left+b.width);if(c<=d){var e=Math.max(a.top,b.top),f=Math.min(a.top+a.height,b.top+b.height);if(e<=f)return new goog.math.Rect(c,e,d-c,f-e)}return null};goog.math.Rect.intersects=function(a,b){return a.left<=b.left+b.width&&b.left<=a.left+a.width&&a.top<=b.top+b.height&&b.top<=a.top+a.height};goog.math.Rect.prototype.intersects=function(a){return goog.math.Rect.intersects(this,a)};
+goog.math.Rect.difference=function(a,b){var c=goog.math.Rect.intersection(a,b);if(!c||!c.height||!c.width)return[a.clone()];var c=[],d=a.top,e=a.height,f=a.left+a.width,g=a.top+a.height,h=b.left+b.width,k=b.top+b.height;b.top>a.top&&(c.push(new goog.math.Rect(a.left,a.top,a.width,b.top-a.top)),d=b.top,e-=b.top-a.top);k<g&&(c.push(new goog.math.Rect(a.left,k,a.width,g-k)),e=k-d);b.left>a.left&&c.push(new goog.math.Rect(a.left,d,b.left-a.left,e));h<f&&c.push(new goog.math.Rect(h,d,f-h,e));return c};
+goog.math.Rect.prototype.difference=function(a){return goog.math.Rect.difference(this,a)};goog.math.Rect.prototype.boundingRect=function(a){var b=Math.max(this.left+this.width,a.left+a.width),c=Math.max(this.top+this.height,a.top+a.height);this.left=Math.min(this.left,a.left);this.top=Math.min(this.top,a.top);this.width=b-this.left;this.height=c-this.top};goog.math.Rect.boundingRect=function(a,b){if(!a||!b)return null;var c=new goog.math.Rect(a.left,a.top,a.width,a.height);c.boundingRect(b);return c};
+goog.math.Rect.prototype.contains=function(a){return a instanceof goog.math.Coordinate?a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height:this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height};goog.math.Rect.prototype.squaredDistance=function(a){var b=a.x<this.left?this.left-a.x:Math.max(a.x-(this.left+this.width),0);a=a.y<this.top?this.top-a.y:Math.max(a.y-(this.top+this.height),0);return b*b+a*a};
+goog.math.Rect.prototype.distance=function(a){return Math.sqrt(this.squaredDistance(a))};goog.math.Rect.prototype.getSize=function(){return new goog.math.Size(this.width,this.height)};goog.math.Rect.prototype.getTopLeft=function(){return new goog.math.Coordinate(this.left,this.top)};goog.math.Rect.prototype.getCenter=function(){return new goog.math.Coordinate(this.left+this.width/2,this.top+this.height/2)};
+goog.math.Rect.prototype.getBottomRight=function(){return new goog.math.Coordinate(this.left+this.width,this.top+this.height)};goog.math.Rect.prototype.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};goog.math.Rect.prototype.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
+goog.math.Rect.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};goog.math.Rect.prototype.translate=function(a,b){a instanceof goog.math.Coordinate?(this.left+=a.x,this.top+=a.y):(this.left+=goog.asserts.assertNumber(a),goog.isNumber(b)&&(this.top+=b));return this};
+goog.math.Rect.prototype.scale=function(a,b){var c=goog.isNumber(b)?b:a;this.left*=a;this.width*=a;this.top*=c;this.height*=c;return this};goog.style={};goog.style.setStyle=function(a,b,c){if(goog.isString(b))goog.style.setStyle_(a,c,b);else for(var d in b)goog.style.setStyle_(a,b[d],d)};goog.style.setStyle_=function(a,b,c){(c=goog.style.getVendorJsStyleName_(a,c))&&(a.style[c]=b)};goog.style.styleNameCache_={};
+goog.style.getVendorJsStyleName_=function(a,b){var c=goog.style.styleNameCache_[b];if(!c){var d=goog.string.toCamelCase(b),c=d;void 0===a.style[d]&&(d=goog.dom.vendor.getVendorJsPrefix()+goog.string.toTitleCase(d),void 0!==a.style[d]&&(c=d));goog.style.styleNameCache_[b]=c}return c};
+goog.style.getVendorStyleName_=function(a,b){var c=goog.string.toCamelCase(b);return void 0===a.style[c]&&(c=goog.dom.vendor.getVendorJsPrefix()+goog.string.toTitleCase(c),void 0!==a.style[c])?goog.dom.vendor.getVendorPrefix()+"-"+b:b};goog.style.getStyle=function(a,b){var c=a.style[goog.string.toCamelCase(b)];return"undefined"!==typeof c?c:a.style[goog.style.getVendorJsStyleName_(a,b)]||""};
+goog.style.getComputedStyle=function(a,b){var c=goog.dom.getOwnerDocument(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""};goog.style.getCascadedStyle=function(a,b){return a.currentStyle?a.currentStyle[b]:null};goog.style.getStyle_=function(a,b){return goog.style.getComputedStyle(a,b)||goog.style.getCascadedStyle(a,b)||a.style&&a.style[b]};
+goog.style.getComputedBoxSizing=function(a){return goog.style.getStyle_(a,"boxSizing")||goog.style.getStyle_(a,"MozBoxSizing")||goog.style.getStyle_(a,"WebkitBoxSizing")||null};goog.style.getComputedPosition=function(a){return goog.style.getStyle_(a,"position")};goog.style.getBackgroundColor=function(a){return goog.style.getStyle_(a,"backgroundColor")};goog.style.getComputedOverflowX=function(a){return goog.style.getStyle_(a,"overflowX")};
+goog.style.getComputedOverflowY=function(a){return goog.style.getStyle_(a,"overflowY")};goog.style.getComputedZIndex=function(a){return goog.style.getStyle_(a,"zIndex")};goog.style.getComputedTextAlign=function(a){return goog.style.getStyle_(a,"textAlign")};goog.style.getComputedCursor=function(a){return goog.style.getStyle_(a,"cursor")};goog.style.getComputedTransform=function(a){var b=goog.style.getVendorStyleName_(a,"transform");return goog.style.getStyle_(a,b)||goog.style.getStyle_(a,"transform")};
+goog.style.setPosition=function(a,b,c){var d;b instanceof goog.math.Coordinate?(d=b.x,b=b.y):(d=b,b=c);a.style.left=goog.style.getPixelStyleValue_(d,!1);a.style.top=goog.style.getPixelStyleValue_(b,!1)};goog.style.getPosition=function(a){return new goog.math.Coordinate(a.offsetLeft,a.offsetTop)};
+goog.style.getClientViewportElement=function(a){a=a?goog.dom.getOwnerDocument(a):goog.dom.getDocument();return!goog.userAgent.IE||goog.userAgent.isDocumentModeOrHigher(9)||goog.dom.getDomHelper(a).isCss1CompatMode()?a.documentElement:a.body};goog.style.getViewportPageOffset=function(a){var b=a.body;a=a.documentElement;return new goog.math.Coordinate(b.scrollLeft||a.scrollLeft,b.scrollTop||a.scrollTop)};
+goog.style.getBoundingClientRect_=function(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}goog.userAgent.IE&&a.ownerDocument.body&&(a=a.ownerDocument,b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);return b};
+goog.style.getOffsetParent=function(a){if(goog.userAgent.IE&&!goog.userAgent.isDocumentModeOrHigher(8))return goog.asserts.assert(a&&"offsetParent"in a),a.offsetParent;var b=goog.dom.getOwnerDocument(a),c=goog.style.getStyle_(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(a.nodeType==goog.dom.NodeType.DOCUMENT_FRAGMENT&&a.host&&(a=a.host),c=goog.style.getStyle_(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||
+a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null};
+goog.style.getVisibleRectForElement=function(a){for(var b=new goog.math.Box(0,Infinity,Infinity,0),c=goog.dom.getDomHelper(a),d=c.getDocument().body,e=c.getDocument().documentElement,f=c.getDocumentScrollElement();a=goog.style.getOffsetParent(a);)if(!(goog.userAgent.IE&&0==a.clientWidth||goog.userAgent.WEBKIT&&0==a.clientHeight&&a==d)&&a!=d&&a!=e&&"visible"!=goog.style.getStyle_(a,"overflow")){var g=goog.style.getPageOffset(a),h=goog.style.getClientLeftTop(a);g.x+=h.x;g.y+=h.y;b.top=Math.max(b.top,
+g.y);b.right=Math.min(b.right,g.x+a.clientWidth);b.bottom=Math.min(b.bottom,g.y+a.clientHeight);b.left=Math.max(b.left,g.x)}d=f.scrollLeft;f=f.scrollTop;b.left=Math.max(b.left,d);b.top=Math.max(b.top,f);c=c.getViewportSize();b.right=Math.min(b.right,d+c.width);b.bottom=Math.min(b.bottom,f+c.height);return 0<=b.top&&0<=b.left&&b.bottom>b.top&&b.right>b.left?b:null};
+goog.style.getContainerOffsetToScrollInto=function(a,b,c){var d=b||goog.dom.getDocumentScrollElement(),e=goog.style.getPageOffset(a),f=goog.style.getPageOffset(d),g=goog.style.getBorderBox(d);d==goog.dom.getDocumentScrollElement()?(b=e.x-d.scrollLeft,e=e.y-d.scrollTop,goog.userAgent.IE&&!goog.userAgent.isDocumentModeOrHigher(10)&&(b+=g.left,e+=g.top)):(b=e.x-f.x-g.left,e=e.y-f.y-g.top);g=goog.style.getSizeWithDisplay_(a);a=d.clientWidth-g.width;g=d.clientHeight-g.height;f=d.scrollLeft;d=d.scrollTop;
+c?(f+=b-a/2,d+=e-g/2):(f+=Math.min(b,Math.max(b-a,0)),d+=Math.min(e,Math.max(e-g,0)));return new goog.math.Coordinate(f,d)};goog.style.scrollIntoContainerView=function(a,b,c){b=b||goog.dom.getDocumentScrollElement();a=goog.style.getContainerOffsetToScrollInto(a,b,c);b.scrollLeft=a.x;b.scrollTop=a.y};goog.style.getClientLeftTop=function(a){return new goog.math.Coordinate(a.clientLeft,a.clientTop)};
+goog.style.getPageOffset=function(a){var b=goog.dom.getOwnerDocument(a);goog.asserts.assertObject(a,"Parameter is required");var c=new goog.math.Coordinate(0,0),d=goog.style.getClientViewportElement(b);if(a==d)return c;a=goog.style.getBoundingClientRect_(a);b=goog.dom.getDomHelper(b).getDocumentScroll();c.x=a.left+b.x;c.y=a.top+b.y;return c};goog.style.getPageOffsetLeft=function(a){return goog.style.getPageOffset(a).x};goog.style.getPageOffsetTop=function(a){return goog.style.getPageOffset(a).y};
+goog.style.getFramedPageOffset=function(a,b){var c=new goog.math.Coordinate(0,0),d=goog.dom.getWindow(goog.dom.getOwnerDocument(a));if(!goog.reflect.canAccessProperty(d,"parent"))return c;var e=a;do{var f=d==b?goog.style.getPageOffset(e):goog.style.getClientPositionForElement_(goog.asserts.assert(e));c.x+=f.x;c.y+=f.y}while(d&&d!=b&&d!=d.parent&&(e=d.frameElement)&&(d=d.parent));return c};
+goog.style.translateRectForAnotherFrame=function(a,b,c){if(b.getDocument()!=c.getDocument()){var d=b.getDocument().body;c=goog.style.getFramedPageOffset(d,c.getWindow());c=goog.math.Coordinate.difference(c,goog.style.getPageOffset(d));!goog.userAgent.IE||goog.userAgent.isDocumentModeOrHigher(9)||b.isCss1CompatMode()||(c=goog.math.Coordinate.difference(c,b.getDocumentScroll()));a.left+=c.x;a.top+=c.y}};
+goog.style.getRelativePosition=function(a,b){var c=goog.style.getClientPosition(a),d=goog.style.getClientPosition(b);return new goog.math.Coordinate(c.x-d.x,c.y-d.y)};goog.style.getClientPositionForElement_=function(a){a=goog.style.getBoundingClientRect_(a);return new goog.math.Coordinate(a.left,a.top)};
+goog.style.getClientPosition=function(a){goog.asserts.assert(a);if(a.nodeType==goog.dom.NodeType.ELEMENT)return goog.style.getClientPositionForElement_(a);a=a.changedTouches?a.changedTouches[0]:a;return new goog.math.Coordinate(a.clientX,a.clientY)};goog.style.setPageOffset=function(a,b,c){var d=goog.style.getPageOffset(a);b instanceof goog.math.Coordinate&&(c=b.y,b=b.x);b=goog.asserts.assertNumber(b)-d.x;goog.style.setPosition(a,a.offsetLeft+b,a.offsetTop+(Number(c)-d.y))};
+goog.style.setSize=function(a,b,c){if(b instanceof goog.math.Size)c=b.height,b=b.width;else if(void 0==c)throw Error("missing height argument");goog.style.setWidth(a,b);goog.style.setHeight(a,c)};goog.style.getPixelStyleValue_=function(a,b){"number"==typeof a&&(a=(b?Math.round(a):a)+"px");return a};goog.style.setHeight=function(a,b){a.style.height=goog.style.getPixelStyleValue_(b,!0)};goog.style.setWidth=function(a,b){a.style.width=goog.style.getPixelStyleValue_(b,!0)};
+goog.style.getSize=function(a){return goog.style.evaluateWithTemporaryDisplay_(goog.style.getSizeWithDisplay_,a)};goog.style.evaluateWithTemporaryDisplay_=function(a,b){if("none"!=goog.style.getStyle_(b,"display"))return a(b);var c=b.style,d=c.display,e=c.visibility,f=c.position;c.visibility="hidden";c.position="absolute";c.display="inline";var g=a(b);c.display=d;c.position=f;c.visibility=e;return g};
+goog.style.getSizeWithDisplay_=function(a){var b=a.offsetWidth,c=a.offsetHeight,d=goog.userAgent.WEBKIT&&!b&&!c;return goog.isDef(b)&&!d||!a.getBoundingClientRect?new goog.math.Size(b,c):(a=goog.style.getBoundingClientRect_(a),new goog.math.Size(a.right-a.left,a.bottom-a.top))};goog.style.getTransformedSize=function(a){if(!a.getBoundingClientRect)return null;a=goog.style.evaluateWithTemporaryDisplay_(goog.style.getBoundingClientRect_,a);return new goog.math.Size(a.right-a.left,a.bottom-a.top)};
+goog.style.getBounds=function(a){var b=goog.style.getPageOffset(a);a=goog.style.getSize(a);return new goog.math.Rect(b.x,b.y,a.width,a.height)};goog.style.toCamelCase=function(a){return goog.string.toCamelCase(String(a))};goog.style.toSelectorCase=function(a){return goog.string.toSelectorCase(a)};
+goog.style.getOpacity=function(a){goog.asserts.assert(a);var b=a.style;a="";"opacity"in b?a=b.opacity:"MozOpacity"in b?a=b.MozOpacity:"filter"in b&&(b=b.filter.match(/alpha\(opacity=([\d.]+)\)/))&&(a=String(b[1]/100));return""==a?a:Number(a)};goog.style.setOpacity=function(a,b){goog.asserts.assert(a);var c=a.style;"opacity"in c?c.opacity=b:"MozOpacity"in c?c.MozOpacity=b:"filter"in c&&(c.filter=""===b?"":"alpha(opacity="+100*Number(b)+")")};
+goog.style.setTransparentBackgroundImage=function(a,b){var c=a.style;goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("8")?c.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+b+'", sizingMethod="crop")':(c.backgroundImage="url("+b+")",c.backgroundPosition="top left",c.backgroundRepeat="no-repeat")};goog.style.clearTransparentBackgroundImage=function(a){a=a.style;"filter"in a?a.filter="":a.backgroundImage="none"};
+goog.style.showElement=function(a,b){goog.style.setElementShown(a,b)};goog.style.setElementShown=function(a,b){a.style.display=b?"":"none"};goog.style.isElementShown=function(a){return"none"!=a.style.display};goog.style.installStyles=function(a,b){return goog.style.installSafeStyleSheet(goog.html.legacyconversions.safeStyleSheetFromString(a),b)};
+goog.style.installSafeStyleSheet=function(a,b){var c=goog.dom.getDomHelper(b),d,e=c.getDocument();goog.userAgent.IE&&e.createStyleSheet?(d=e.createStyleSheet(),goog.style.setSafeStyleSheet(d,a)):(e=c.getElementsByTagNameAndClass("HEAD")[0],e||(d=c.getElementsByTagNameAndClass("BODY")[0],e=c.createDom("HEAD"),d.parentNode.insertBefore(e,d)),d=c.createDom("STYLE"),goog.style.setSafeStyleSheet(d,a),c.appendChild(e,d));return d};
+goog.style.uninstallStyles=function(a){goog.dom.removeNode(a.ownerNode||a.owningElement||a)};goog.style.setStyles=function(a,b){goog.style.setSafeStyleSheet(a,goog.html.legacyconversions.safeStyleSheetFromString(b))};goog.style.setSafeStyleSheet=function(a,b){var c=goog.html.SafeStyleSheet.unwrap(b);goog.userAgent.IE&&goog.isDef(a.cssText)?a.cssText=c:a.innerHTML=c};
+goog.style.setPreWrap=function(a){a=a.style;goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("8")?(a.whiteSpace="pre",a.wordWrap="break-word"):a.whiteSpace=goog.userAgent.GECKO?"-moz-pre-wrap":"pre-wrap"};goog.style.setInlineBlock=function(a){a=a.style;a.position="relative";goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("8")?(a.zoom="1",a.display="inline"):a.display="inline-block"};goog.style.isRightToLeft=function(a){return"rtl"==goog.style.getStyle_(a,"direction")};
+goog.style.unselectableStyle_=goog.userAgent.GECKO?"MozUserSelect":goog.userAgent.WEBKIT||goog.userAgent.EDGE?"WebkitUserSelect":null;goog.style.isUnselectable=function(a){return goog.style.unselectableStyle_?"none"==a.style[goog.style.unselectableStyle_].toLowerCase():goog.userAgent.IE||goog.userAgent.OPERA?"on"==a.getAttribute("unselectable"):!1};
+goog.style.setUnselectable=function(a,b,c){c=c?null:a.getElementsByTagName("*");var d=goog.style.unselectableStyle_;if(d){if(b=b?"none":"",a.style&&(a.style[d]=b),c){a=0;for(var e;e=c[a];a++)e.style&&(e.style[d]=b)}}else if(goog.userAgent.IE||goog.userAgent.OPERA)if(b=b?"on":"",a.setAttribute("unselectable",b),c)for(a=0;e=c[a];a++)e.setAttribute("unselectable",b)};goog.style.getBorderBoxSize=function(a){return new goog.math.Size(a.offsetWidth,a.offsetHeight)};
+goog.style.setBorderBoxSize=function(a,b){var c=goog.dom.getOwnerDocument(a),d=goog.dom.getDomHelper(c).isCss1CompatMode();if(!goog.userAgent.IE||goog.userAgent.isVersionOrHigher("10")||d&&goog.userAgent.isVersionOrHigher("8"))goog.style.setBoxSizingSize_(a,b,"border-box");else if(c=a.style,d){var d=goog.style.getPaddingBox(a),e=goog.style.getBorderBox(a);c.pixelWidth=b.width-e.left-d.left-d.right-e.right;c.pixelHeight=b.height-e.top-d.top-d.bottom-e.bottom}else c.pixelWidth=b.width,c.pixelHeight=
+b.height};
+goog.style.getContentBoxSize=function(a){var b=goog.dom.getOwnerDocument(a),c=goog.userAgent.IE&&a.currentStyle;if(c&&goog.dom.getDomHelper(b).isCss1CompatMode()&&"auto"!=c.width&&"auto"!=c.height&&!c.boxSizing)return b=goog.style.getIePixelValue_(a,c.width,"width","pixelWidth"),a=goog.style.getIePixelValue_(a,c.height,"height","pixelHeight"),new goog.math.Size(b,a);c=goog.style.getBorderBoxSize(a);b=goog.style.getPaddingBox(a);a=goog.style.getBorderBox(a);return new goog.math.Size(c.width-a.left-
+b.left-b.right-a.right,c.height-a.top-b.top-b.bottom-a.bottom)};
+goog.style.setContentBoxSize=function(a,b){var c=goog.dom.getOwnerDocument(a),d=goog.dom.getDomHelper(c).isCss1CompatMode();if(!goog.userAgent.IE||goog.userAgent.isVersionOrHigher("10")||d&&goog.userAgent.isVersionOrHigher("8"))goog.style.setBoxSizingSize_(a,b,"content-box");else if(c=a.style,d)c.pixelWidth=b.width,c.pixelHeight=b.height;else{var d=goog.style.getPaddingBox(a),e=goog.style.getBorderBox(a);c.pixelWidth=b.width+e.left+d.left+d.right+e.right;c.pixelHeight=b.height+e.top+d.top+d.bottom+
+e.bottom}};goog.style.setBoxSizingSize_=function(a,b,c){a=a.style;goog.userAgent.GECKO?a.MozBoxSizing=c:goog.userAgent.WEBKIT?a.WebkitBoxSizing=c:a.boxSizing=c;a.width=Math.max(b.width,0)+"px";a.height=Math.max(b.height,0)+"px"};goog.style.getIePixelValue_=function(a,b,c,d){if(/^\d+px?$/.test(b))return parseInt(b,10);var e=a.style[c],f=a.runtimeStyle[c];a.runtimeStyle[c]=a.currentStyle[c];a.style[c]=b;b=a.style[d];a.style[c]=e;a.runtimeStyle[c]=f;return+b};
+goog.style.getIePixelDistance_=function(a,b){var c=goog.style.getCascadedStyle(a,b);return c?goog.style.getIePixelValue_(a,c,"left","pixelLeft"):0};
+goog.style.getBox_=function(a,b){if(goog.userAgent.IE){var c=goog.style.getIePixelDistance_(a,b+"Left"),d=goog.style.getIePixelDistance_(a,b+"Right"),e=goog.style.getIePixelDistance_(a,b+"Top"),f=goog.style.getIePixelDistance_(a,b+"Bottom");return new goog.math.Box(e,d,f,c)}c=goog.style.getComputedStyle(a,b+"Left");d=goog.style.getComputedStyle(a,b+"Right");e=goog.style.getComputedStyle(a,b+"Top");f=goog.style.getComputedStyle(a,b+"Bottom");return new goog.math.Box(parseFloat(e),parseFloat(d),parseFloat(f),
+parseFloat(c))};goog.style.getPaddingBox=function(a){return goog.style.getBox_(a,"padding")};goog.style.getMarginBox=function(a){return goog.style.getBox_(a,"margin")};goog.style.ieBorderWidthKeywords_={thin:2,medium:4,thick:6};
+goog.style.getIePixelBorder_=function(a,b){if("none"==goog.style.getCascadedStyle(a,b+"Style"))return 0;var c=goog.style.getCascadedStyle(a,b+"Width");return c in goog.style.ieBorderWidthKeywords_?goog.style.ieBorderWidthKeywords_[c]:goog.style.getIePixelValue_(a,c,"left","pixelLeft")};
+goog.style.getBorderBox=function(a){if(goog.userAgent.IE&&!goog.userAgent.isDocumentModeOrHigher(9)){var b=goog.style.getIePixelBorder_(a,"borderLeft"),c=goog.style.getIePixelBorder_(a,"borderRight"),d=goog.style.getIePixelBorder_(a,"borderTop");a=goog.style.getIePixelBorder_(a,"borderBottom");return new goog.math.Box(d,c,a,b)}b=goog.style.getComputedStyle(a,"borderLeftWidth");c=goog.style.getComputedStyle(a,"borderRightWidth");d=goog.style.getComputedStyle(a,"borderTopWidth");a=goog.style.getComputedStyle(a,
+"borderBottomWidth");return new goog.math.Box(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))};goog.style.getFontFamily=function(a){var b=goog.dom.getOwnerDocument(a),c="";if(b.body.createTextRange&&goog.dom.contains(b,a)){b=b.body.createTextRange();b.moveToElementText(a);try{c=b.queryCommandValue("FontName")}catch(d){c=""}}c||(c=goog.style.getStyle_(a,"fontFamily"));a=c.split(",");1<a.length&&(c=a[0]);return goog.string.stripQuotes(c,"\"'")};goog.style.lengthUnitRegex_=/[^\d]+$/;
+goog.style.getLengthUnits=function(a){return(a=a.match(goog.style.lengthUnitRegex_))&&a[0]||null};goog.style.ABSOLUTE_CSS_LENGTH_UNITS_={cm:1,"in":1,mm:1,pc:1,pt:1};goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_={em:1,ex:1};
+goog.style.getFontSize=function(a){var b=goog.style.getStyle_(a,"fontSize"),c=goog.style.getLengthUnits(b);if(b&&"px"==c)return parseInt(b,10);if(goog.userAgent.IE){if(String(c)in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_)return goog.style.getIePixelValue_(a,b,"left","pixelLeft");if(a.parentNode&&a.parentNode.nodeType==goog.dom.NodeType.ELEMENT&&String(c)in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_)return a=a.parentNode,c=goog.style.getStyle_(a,"fontSize"),goog.style.getIePixelValue_(a,b==c?"1em":b,
+"left","pixelLeft")}c=goog.dom.createDom("SPAN",{style:"visibility:hidden;position:absolute;line-height:0;padding:0;margin:0;border:0;height:1em;"});goog.dom.appendChild(a,c);b=c.offsetHeight;goog.dom.removeNode(c);return b};goog.style.parseStyleAttribute=function(a){var b={};goog.array.forEach(a.split(/\s*;\s*/),function(a){var c=a.match(/\s*([\w-]+)\s*\:(.+)/);c&&(a=c[1],c=goog.string.trim(c[2]),b[goog.string.toCamelCase(a.toLowerCase())]=c)});return b};
+goog.style.toStyleAttribute=function(a){var b=[];goog.object.forEach(a,function(a,d){b.push(goog.string.toSelectorCase(d),":",a,";")});return b.join("")};goog.style.setFloat=function(a,b){a.style[goog.userAgent.IE?"styleFloat":"cssFloat"]=b};goog.style.getFloat=function(a){return a.style[goog.userAgent.IE?"styleFloat":"cssFloat"]||""};
+goog.style.getScrollbarWidth=function(a){var b=goog.dom.createElement("DIV");a&&(b.className=a);b.style.cssText="overflow:auto;position:absolute;top:0;width:100px;height:100px";a=goog.dom.createElement("DIV");goog.style.setSize(a,"200px","200px");b.appendChild(a);goog.dom.appendChild(goog.dom.getDocument().body,b);a=b.offsetWidth-b.clientWidth;goog.dom.removeNode(b);return a};goog.style.MATRIX_TRANSLATION_REGEX_=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;
+goog.style.getCssTranslation=function(a){a=goog.style.getComputedTransform(a);return a?(a=a.match(goog.style.MATRIX_TRANSLATION_REGEX_))?new goog.math.Coordinate(parseFloat(a[1]),parseFloat(a[2])):new goog.math.Coordinate(0,0):new goog.math.Coordinate(0,0)};goog.debug.entryPointRegistry={};goog.debug.EntryPointMonitor=function(){};goog.debug.entryPointRegistry.refList_=[];goog.debug.entryPointRegistry.monitors_=[];goog.debug.entryPointRegistry.monitorsMayExist_=!1;goog.debug.entryPointRegistry.register=function(a){goog.debug.entryPointRegistry.refList_[goog.debug.entryPointRegistry.refList_.length]=a;if(goog.debug.entryPointRegistry.monitorsMayExist_)for(var b=goog.debug.entryPointRegistry.monitors_,c=0;c<b.length;c++)a(goog.bind(b[c].wrap,b[c]))};
+goog.debug.entryPointRegistry.monitorAll=function(a){goog.debug.entryPointRegistry.monitorsMayExist_=!0;for(var b=goog.bind(a.wrap,a),c=0;c<goog.debug.entryPointRegistry.refList_.length;c++)goog.debug.entryPointRegistry.refList_[c](b);goog.debug.entryPointRegistry.monitors_.push(a)};
+goog.debug.entryPointRegistry.unmonitorAllIfPossible=function(a){var b=goog.debug.entryPointRegistry.monitors_;goog.asserts.assert(a==b[b.length-1],"Only the most recent monitor can be unwrapped.");a=goog.bind(a.unwrap,a);for(var c=0;c<goog.debug.entryPointRegistry.refList_.length;c++)goog.debug.entryPointRegistry.refList_[c](a);b.length--};goog.events={};
+goog.events.BrowserFeature={HAS_W3C_BUTTON:!goog.userAgent.IE||goog.userAgent.isDocumentModeOrHigher(9),HAS_W3C_EVENT_SUPPORT:!goog.userAgent.IE||goog.userAgent.isDocumentModeOrHigher(9),SET_KEY_CODE_TO_PREVENT_DEFAULT:goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("9"),HAS_NAVIGATOR_ONLINE_PROPERTY:!goog.userAgent.WEBKIT||goog.userAgent.isVersionOrHigher("528"),HAS_HTML5_NETWORK_EVENT_SUPPORT:goog.userAgent.GECKO&&goog.userAgent.isVersionOrHigher("1.9b")||goog.userAgent.IE&&goog.userAgent.isVersionOrHigher("8")||
+goog.userAgent.OPERA&&goog.userAgent.isVersionOrHigher("9.5")||goog.userAgent.WEBKIT&&goog.userAgent.isVersionOrHigher("528"),HTML5_NETWORK_EVENTS_FIRE_ON_BODY:goog.userAgent.GECKO&&!goog.userAgent.isVersionOrHigher("8")||goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("9"),TOUCH_ENABLED:"ontouchstart"in goog.global||!!(goog.global.document&&document.documentElement&&"ontouchstart"in document.documentElement)||!(!goog.global.navigator||!goog.global.navigator.msMaxTouchPoints)};goog.disposable={};goog.disposable.IDisposable=function(){};goog.disposable.IDisposable.prototype.dispose=goog.abstractMethod;goog.disposable.IDisposable.prototype.isDisposed=goog.abstractMethod;goog.Disposable=function(){goog.Disposable.MONITORING_MODE!=goog.Disposable.MonitoringMode.OFF&&(goog.Disposable.INCLUDE_STACK_ON_CREATION&&(this.creationStack=Error().stack),goog.Disposable.instances_[goog.getUid(this)]=this);this.disposed_=this.disposed_;this.onDisposeCallbacks_=this.onDisposeCallbacks_};goog.Disposable.MonitoringMode={OFF:0,PERMANENT:1,INTERACTIVE:2};goog.Disposable.MONITORING_MODE=0;goog.Disposable.INCLUDE_STACK_ON_CREATION=!0;goog.Disposable.instances_={};
+goog.Disposable.getUndisposedObjects=function(){var a=[],b;for(b in goog.Disposable.instances_)goog.Disposable.instances_.hasOwnProperty(b)&&a.push(goog.Disposable.instances_[Number(b)]);return a};goog.Disposable.clearUndisposedObjects=function(){goog.Disposable.instances_={}};goog.Disposable.prototype.disposed_=!1;goog.Disposable.prototype.isDisposed=function(){return this.disposed_};goog.Disposable.prototype.getDisposed=goog.Disposable.prototype.isDisposed;
+goog.Disposable.prototype.dispose=function(){if(!this.disposed_&&(this.disposed_=!0,this.disposeInternal(),goog.Disposable.MONITORING_MODE!=goog.Disposable.MonitoringMode.OFF)){var a=goog.getUid(this);if(goog.Disposable.MONITORING_MODE==goog.Disposable.MonitoringMode.PERMANENT&&!goog.Disposable.instances_.hasOwnProperty(a))throw Error(this+" did not call the goog.Disposable base constructor or was disposed of after a clearUndisposedObjects call");delete goog.Disposable.instances_[a]}};
+goog.Disposable.prototype.registerDisposable=function(a){this.addOnDisposeCallback(goog.partial(goog.dispose,a))};goog.Disposable.prototype.addOnDisposeCallback=function(a,b){this.disposed_?goog.isDef(b)?a.call(b):a():(this.onDisposeCallbacks_||(this.onDisposeCallbacks_=[]),this.onDisposeCallbacks_.push(goog.isDef(b)?goog.bind(a,b):a))};goog.Disposable.prototype.disposeInternal=function(){if(this.onDisposeCallbacks_)for(;this.onDisposeCallbacks_.length;)this.onDisposeCallbacks_.shift()()};
+goog.Disposable.isDisposed=function(a){return a&&"function"==typeof a.isDisposed?a.isDisposed():!1};goog.dispose=function(a){a&&"function"==typeof a.dispose&&a.dispose()};goog.disposeAll=function(a){for(var b=0,c=arguments.length;b<c;++b){var d=arguments[b];goog.isArrayLike(d)?goog.disposeAll.apply(null,d):goog.dispose(d)}};goog.events.EventId=function(a){this.id=a};goog.events.EventId.prototype.toString=function(){return this.id};goog.events.Event=function(a,b){this.type=a instanceof goog.events.EventId?String(a):a;this.currentTarget=this.target=b;this.defaultPrevented=this.propagationStopped_=!1;this.returnValue_=!0};goog.events.Event.prototype.stopPropagation=function(){this.propagationStopped_=!0};goog.events.Event.prototype.preventDefault=function(){this.defaultPrevented=!0;this.returnValue_=!1};goog.events.Event.stopPropagation=function(a){a.stopPropagation()};goog.events.Event.preventDefault=function(a){a.preventDefault()};goog.events.getVendorPrefixedName_=function(a){return goog.userAgent.WEBKIT?"webkit"+a:goog.userAgent.OPERA?"o"+a.toLowerCase():a.toLowerCase()};
+goog.events.EventType={CLICK:"click",RIGHTCLICK:"rightclick",DBLCLICK:"dblclick",MOUSEDOWN:"mousedown",MOUSEUP:"mouseup",MOUSEOVER:"mouseover",MOUSEOUT:"mouseout",MOUSEMOVE:"mousemove",MOUSEENTER:"mouseenter",MOUSELEAVE:"mouseleave",SELECTIONCHANGE:"selectionchange",SELECTSTART:"selectstart",WHEEL:"wheel",KEYPRESS:"keypress",KEYDOWN:"keydown",KEYUP:"keyup",BLUR:"blur",FOCUS:"focus",DEACTIVATE:"deactivate",FOCUSIN:goog.userAgent.IE?"focusin":"DOMFocusIn",FOCUSOUT:goog.userAgent.IE?"focusout":"DOMFocusOut",
+CHANGE:"change",RESET:"reset",SELECT:"select",SUBMIT:"submit",INPUT:"input",PROPERTYCHANGE:"propertychange",DRAGSTART:"dragstart",DRAG:"drag",DRAGENTER:"dragenter",DRAGOVER:"dragover",DRAGLEAVE:"dragleave",DROP:"drop",DRAGEND:"dragend",TOUCHSTART:"touchstart",TOUCHMOVE:"touchmove",TOUCHEND:"touchend",TOUCHCANCEL:"touchcancel",BEFOREUNLOAD:"beforeunload",CONSOLEMESSAGE:"consolemessage",CONTEXTMENU:"contextmenu",DEVICEORIENTATION:"deviceorientation",DOMCONTENTLOADED:"DOMContentLoaded",ERROR:"error",
+HELP:"help",LOAD:"load",LOSECAPTURE:"losecapture",ORIENTATIONCHANGE:"orientationchange",READYSTATECHANGE:"readystatechange",RESIZE:"resize",SCROLL:"scroll",UNLOAD:"unload",CANPLAY:"canplay",CANPLAYTHROUGH:"canplaythrough",DURATIONCHANGE:"durationchange",EMPTIED:"emptied",ENDED:"ended",LOADEDDATA:"loadeddata",LOADEDMETADATA:"loadedmetadata",PAUSE:"pause",PLAY:"play",PLAYING:"playing",RATECHANGE:"ratechange",SEEKED:"seeked",SEEKING:"seeking",STALLED:"stalled",SUSPEND:"suspend",TIMEUPDATE:"timeupdate",
+VOLUMECHANGE:"volumechange",WAITING:"waiting",HASHCHANGE:"hashchange",PAGEHIDE:"pagehide",PAGESHOW:"pageshow",POPSTATE:"popstate",COPY:"copy",PASTE:"paste",CUT:"cut",BEFORECOPY:"beforecopy",BEFORECUT:"beforecut",BEFOREPASTE:"beforepaste",ONLINE:"online",OFFLINE:"offline",MESSAGE:"message",CONNECT:"connect",ANIMATIONSTART:goog.events.getVendorPrefixedName_("AnimationStart"),ANIMATIONEND:goog.events.getVendorPrefixedName_("AnimationEnd"),ANIMATIONITERATION:goog.events.getVendorPrefixedName_("AnimationIteration"),
+TRANSITIONEND:goog.events.getVendorPrefixedName_("TransitionEnd"),POINTERDOWN:"pointerdown",POINTERUP:"pointerup",POINTERCANCEL:"pointercancel",POINTERMOVE:"pointermove",POINTEROVER:"pointerover",POINTEROUT:"pointerout",POINTERENTER:"pointerenter",POINTERLEAVE:"pointerleave",GOTPOINTERCAPTURE:"gotpointercapture",LOSTPOINTERCAPTURE:"lostpointercapture",MSGESTURECHANGE:"MSGestureChange",MSGESTUREEND:"MSGestureEnd",MSGESTUREHOLD:"MSGestureHold",MSGESTURESTART:"MSGestureStart",MSGESTURETAP:"MSGestureTap",
+MSGOTPOINTERCAPTURE:"MSGotPointerCapture",MSINERTIASTART:"MSInertiaStart",MSLOSTPOINTERCAPTURE:"MSLostPointerCapture",MSPOINTERCANCEL:"MSPointerCancel",MSPOINTERDOWN:"MSPointerDown",MSPOINTERENTER:"MSPointerEnter",MSPOINTERHOVER:"MSPointerHover",MSPOINTERLEAVE:"MSPointerLeave",MSPOINTERMOVE:"MSPointerMove",MSPOINTEROUT:"MSPointerOut",MSPOINTEROVER:"MSPointerOver",MSPOINTERUP:"MSPointerUp",TEXT:"text",TEXTINPUT:"textInput",COMPOSITIONSTART:"compositionstart",COMPOSITIONUPDATE:"compositionupdate",COMPOSITIONEND:"compositionend",
+EXIT:"exit",LOADABORT:"loadabort",LOADCOMMIT:"loadcommit",LOADREDIRECT:"loadredirect",LOADSTART:"loadstart",LOADSTOP:"loadstop",RESPONSIVE:"responsive",SIZECHANGED:"sizechanged",UNRESPONSIVE:"unresponsive",VISIBILITYCHANGE:"visibilitychange",STORAGE:"storage",DOMSUBTREEMODIFIED:"DOMSubtreeModified",DOMNODEINSERTED:"DOMNodeInserted",DOMNODEREMOVED:"DOMNodeRemoved",DOMNODEREMOVEDFROMDOCUMENT:"DOMNodeRemovedFromDocument",DOMNODEINSERTEDINTODOCUMENT:"DOMNodeInsertedIntoDocument",DOMATTRMODIFIED:"DOMAttrModified",
+DOMCHARACTERDATAMODIFIED:"DOMCharacterDataModified",BEFOREPRINT:"beforeprint",AFTERPRINT:"afterprint"};goog.events.BrowserEvent=function(a,b){goog.events.Event.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.state=null;this.platformModifierKey=!1;this.event_=null;a&&this.init(a,b)};goog.inherits(goog.events.BrowserEvent,goog.events.Event);
+goog.events.BrowserEvent.MouseButton={LEFT:0,MIDDLE:1,RIGHT:2};goog.events.BrowserEvent.IEButtonMap=[1,4,2];
+goog.events.BrowserEvent.prototype.init=function(a,b){var c=this.type=a.type,d=a.changedTouches?a.changedTouches[0]:null;this.target=a.target||a.srcElement;this.currentTarget=b;var e=a.relatedTarget;e?goog.userAgent.GECKO&&(goog.reflect.canAccessProperty(e,"nodeName")||(e=null)):c==goog.events.EventType.MOUSEOVER?e=a.fromElement:c==goog.events.EventType.MOUSEOUT&&(e=a.toElement);this.relatedTarget=e;goog.isNull(d)?(this.offsetX=goog.userAgent.WEBKIT||void 0!==a.offsetX?a.offsetX:a.layerX,this.offsetY=
+goog.userAgent.WEBKIT||void 0!==a.offsetY?a.offsetY:a.layerY,this.clientX=void 0!==a.clientX?a.clientX:a.pageX,this.clientY=void 0!==a.clientY?a.clientY:a.pageY,this.screenX=a.screenX||0,this.screenY=a.screenY||0):(this.clientX=void 0!==d.clientX?d.clientX:d.pageX,this.clientY=void 0!==d.clientY?d.clientY:d.pageY,this.screenX=d.screenX||0,this.screenY=d.screenY||0);this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=
+a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.platformModifierKey=goog.userAgent.MAC?a.metaKey:a.ctrlKey;this.state=a.state;this.event_=a;a.defaultPrevented&&this.preventDefault()};goog.events.BrowserEvent.prototype.isButton=function(a){return goog.events.BrowserFeature.HAS_W3C_BUTTON?this.event_.button==a:"click"==this.type?a==goog.events.BrowserEvent.MouseButton.LEFT:!!(this.event_.button&goog.events.BrowserEvent.IEButtonMap[a])};
+goog.events.BrowserEvent.prototype.isMouseActionButton=function(){return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT)&&!(goog.userAgent.WEBKIT&&goog.userAgent.MAC&&this.ctrlKey)};goog.events.BrowserEvent.prototype.stopPropagation=function(){goog.events.BrowserEvent.superClass_.stopPropagation.call(this);this.event_.stopPropagation?this.event_.stopPropagation():this.event_.cancelBubble=!0};
+goog.events.BrowserEvent.prototype.preventDefault=function(){goog.events.BrowserEvent.superClass_.preventDefault.call(this);var a=this.event_;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};goog.events.BrowserEvent.prototype.getBrowserEvent=function(){return this.event_};goog.events.Listenable=function(){};goog.events.Listenable.IMPLEMENTED_BY_PROP="closure_listenable_"+(1E6*Math.random()|0);goog.events.Listenable.addImplementation=function(a){a.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP]=!0};goog.events.Listenable.isImplementedBy=function(a){return!(!a||!a[goog.events.Listenable.IMPLEMENTED_BY_PROP])};goog.events.ListenableKey=function(){};goog.events.ListenableKey.counter_=0;goog.events.ListenableKey.reserveKey=function(){return++goog.events.ListenableKey.counter_};goog.events.Listener=function(a,b,c,d,e,f){goog.events.Listener.ENABLE_MONITORING&&(this.creationStack=Error().stack);this.listener=a;this.proxy=b;this.src=c;this.type=d;this.capture=!!e;this.handler=f;this.key=goog.events.ListenableKey.reserveKey();this.removed=this.callOnce=!1};goog.events.Listener.ENABLE_MONITORING=!1;goog.events.Listener.prototype.markAsRemoved=function(){this.removed=!0;this.handler=this.src=this.proxy=this.listener=null};goog.events.ListenerMap=function(a){this.src=a;this.listeners={};this.typeCount_=0};goog.events.ListenerMap.prototype.getTypeCount=function(){return this.typeCount_};goog.events.ListenerMap.prototype.getListenerCount=function(){var a=0,b;for(b in this.listeners)a+=this.listeners[b].length;return a};
+goog.events.ListenerMap.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.listeners[f];a||(a=this.listeners[f]=[],this.typeCount_++);var g=goog.events.ListenerMap.findListenerIndex_(a,b,d,e);-1<g?(b=a[g],c||(b.callOnce=!1)):(b=new goog.events.Listener(b,null,this.src,f,!!d,e),b.callOnce=c,a.push(b));return b};
+goog.events.ListenerMap.prototype.remove=function(a,b,c,d){a=a.toString();if(!(a in this.listeners))return!1;var e=this.listeners[a];b=goog.events.ListenerMap.findListenerIndex_(e,b,c,d);return-1<b?(e[b].markAsRemoved(),goog.array.removeAt(e,b),0==e.length&&(delete this.listeners[a],this.typeCount_--),!0):!1};
+goog.events.ListenerMap.prototype.removeByKey=function(a){var b=a.type;if(!(b in this.listeners))return!1;var c=goog.array.remove(this.listeners[b],a);c&&(a.markAsRemoved(),0==this.listeners[b].length&&(delete this.listeners[b],this.typeCount_--));return c};goog.events.ListenerMap.prototype.removeAll=function(a){a=a&&a.toString();var b=0,c;for(c in this.listeners)if(!a||c==a){for(var d=this.listeners[c],e=0;e<d.length;e++)++b,d[e].markAsRemoved();delete this.listeners[c];this.typeCount_--}return b};
+goog.events.ListenerMap.prototype.getListeners=function(a,b){var c=this.listeners[a.toString()],d=[];if(c)for(var e=0;e<c.length;++e){var f=c[e];f.capture==b&&d.push(f)}return d};goog.events.ListenerMap.prototype.getListener=function(a,b,c,d){a=this.listeners[a.toString()];var e=-1;a&&(e=goog.events.ListenerMap.findListenerIndex_(a,b,c,d));return-1<e?a[e]:null};
+goog.events.ListenerMap.prototype.hasListener=function(a,b){var c=goog.isDef(a),d=c?a.toString():"",e=goog.isDef(b);return goog.object.some(this.listeners,function(a,g){for(var f=0;f<a.length;++f)if(!(c&&a[f].type!=d||e&&a[f].capture!=b))return!0;return!1})};goog.events.ListenerMap.findListenerIndex_=function(a,b,c,d){for(var e=0;e<a.length;++e){var f=a[e];if(!f.removed&&f.listener==b&&f.capture==!!c&&f.handler==d)return e}return-1};goog.events.LISTENER_MAP_PROP_="closure_lm_"+(1E6*Math.random()|0);goog.events.onString_="on";goog.events.onStringMap_={};goog.events.CaptureSimulationMode={OFF_AND_FAIL:0,OFF_AND_SILENT:1,ON:2};goog.events.CAPTURE_SIMULATION_MODE=2;goog.events.listenerCountEstimate_=0;
+goog.events.listen=function(a,b,c,d,e){if(goog.isArray(b)){for(var f=0;f<b.length;f++)goog.events.listen(a,b[f],c,d,e);return null}c=goog.events.wrapListener(c);return goog.events.Listenable.isImplementedBy(a)?a.listen(b,c,d,e):goog.events.listen_(a,b,c,!1,d,e)};
+goog.events.listen_=function(a,b,c,d,e,f){if(!b)throw Error("Invalid event type");var g=!!e;if(g&&!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT){if(goog.events.CAPTURE_SIMULATION_MODE==goog.events.CaptureSimulationMode.OFF_AND_FAIL)return goog.asserts.fail("Can not register capture listener in IE8-."),null;if(goog.events.CAPTURE_SIMULATION_MODE==goog.events.CaptureSimulationMode.OFF_AND_SILENT)return null}var h=goog.events.getListenerMap_(a);h||(a[goog.events.LISTENER_MAP_PROP_]=h=new goog.events.ListenerMap(a));
+c=h.add(b,c,d,e,f);if(c.proxy)return c;d=goog.events.getProxy();c.proxy=d;d.src=a;d.listener=c;if(a.addEventListener)a.addEventListener(b.toString(),d,g);else if(a.attachEvent)a.attachEvent(goog.events.getOnString_(b.toString()),d);else throw Error("addEventListener and attachEvent are unavailable.");goog.events.listenerCountEstimate_++;return c};
+goog.events.getProxy=function(){var a=goog.events.handleBrowserEvent_,b=goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT?function(c){return a.call(b.src,b.listener,c)}:function(c){c=a.call(b.src,b.listener,c);if(!c)return c};return b};
+goog.events.listenOnce=function(a,b,c,d,e){if(goog.isArray(b)){for(var f=0;f<b.length;f++)goog.events.listenOnce(a,b[f],c,d,e);return null}c=goog.events.wrapListener(c);return goog.events.Listenable.isImplementedBy(a)?a.listenOnce(b,c,d,e):goog.events.listen_(a,b,c,!0,d,e)};goog.events.listenWithWrapper=function(a,b,c,d,e){b.listen(a,c,d,e)};
+goog.events.unlisten=function(a,b,c,d,e){if(goog.isArray(b)){for(var f=0;f<b.length;f++)goog.events.unlisten(a,b[f],c,d,e);return null}c=goog.events.wrapListener(c);if(goog.events.Listenable.isImplementedBy(a))return a.unlisten(b,c,d,e);if(!a)return!1;d=!!d;if(a=goog.events.getListenerMap_(a))if(b=a.getListener(b,c,d,e))return goog.events.unlistenByKey(b);return!1};
+goog.events.unlistenByKey=function(a){if(goog.isNumber(a)||!a||a.removed)return!1;var b=a.src;if(goog.events.Listenable.isImplementedBy(b))return b.unlistenByKey(a);var c=a.type,d=a.proxy;b.removeEventListener?b.removeEventListener(c,d,a.capture):b.detachEvent&&b.detachEvent(goog.events.getOnString_(c),d);goog.events.listenerCountEstimate_--;(c=goog.events.getListenerMap_(b))?(c.removeByKey(a),0==c.getTypeCount()&&(c.src=null,b[goog.events.LISTENER_MAP_PROP_]=null)):a.markAsRemoved();return!0};
+goog.events.unlistenWithWrapper=function(a,b,c,d,e){b.unlisten(a,c,d,e)};goog.events.removeAll=function(a,b){if(!a)return 0;if(goog.events.Listenable.isImplementedBy(a))return a.removeAllListeners(b);var c=goog.events.getListenerMap_(a);if(!c)return 0;var d=0,e=b&&b.toString(),f;for(f in c.listeners)if(!e||f==e)for(var g=c.listeners[f].concat(),h=0;h<g.length;++h)goog.events.unlistenByKey(g[h])&&++d;return d};
+goog.events.getListeners=function(a,b,c){return goog.events.Listenable.isImplementedBy(a)?a.getListeners(b,c):a?(a=goog.events.getListenerMap_(a))?a.getListeners(b,c):[]:[]};goog.events.getListener=function(a,b,c,d,e){c=goog.events.wrapListener(c);d=!!d;return goog.events.Listenable.isImplementedBy(a)?a.getListener(b,c,d,e):a?(a=goog.events.getListenerMap_(a))?a.getListener(b,c,d,e):null:null};
+goog.events.hasListener=function(a,b,c){if(goog.events.Listenable.isImplementedBy(a))return a.hasListener(b,c);a=goog.events.getListenerMap_(a);return!!a&&a.hasListener(b,c)};goog.events.expose=function(a){var b=[],c;for(c in a)a[c]&&a[c].id?b.push(c+" = "+a[c]+" ("+a[c].id+")"):b.push(c+" = "+a[c]);return b.join("\n")};goog.events.getOnString_=function(a){return a in goog.events.onStringMap_?goog.events.onStringMap_[a]:goog.events.onStringMap_[a]=goog.events.onString_+a};
+goog.events.fireListeners=function(a,b,c,d){return goog.events.Listenable.isImplementedBy(a)?a.fireListeners(b,c,d):goog.events.fireListeners_(a,b,c,d)};goog.events.fireListeners_=function(a,b,c,d){var e=!0;if(a=goog.events.getListenerMap_(a))if(b=a.listeners[b.toString()])for(b=b.concat(),a=0;a<b.length;a++){var f=b[a];f&&f.capture==c&&!f.removed&&(f=goog.events.fireListener(f,d),e=e&&!1!==f)}return e};
+goog.events.fireListener=function(a,b){var c=a.listener,d=a.handler||a.src;a.callOnce&&goog.events.unlistenByKey(a);return c.call(d,b)};goog.events.getTotalListenerCount=function(){return goog.events.listenerCountEstimate_};goog.events.dispatchEvent=function(a,b){goog.asserts.assert(goog.events.Listenable.isImplementedBy(a),"Can not use goog.events.dispatchEvent with non-goog.events.Listenable instance.");return a.dispatchEvent(b)};
+goog.events.protectBrowserEventEntryPoint=function(a){goog.events.handleBrowserEvent_=a.protectEntryPoint(goog.events.handleBrowserEvent_)};
+goog.events.handleBrowserEvent_=function(a,b){if(a.removed)return!0;if(!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT){var c=b||goog.getObjectByName("window.event"),d=new goog.events.BrowserEvent(c,this),e=!0;if(goog.events.CAPTURE_SIMULATION_MODE==goog.events.CaptureSimulationMode.ON){if(!goog.events.isMarkedIeEvent_(c)){goog.events.markIeEvent_(c);for(var c=[],f=d.currentTarget;f;f=f.parentNode)c.push(f);for(var f=a.type,g=c.length-1;!d.propagationStopped_&&0<=g;g--){d.currentTarget=c[g];var h=
+goog.events.fireListeners_(c[g],f,!0,d),e=e&&h}for(g=0;!d.propagationStopped_&&g<c.length;g++)d.currentTarget=c[g],h=goog.events.fireListeners_(c[g],f,!1,d),e=e&&h}}else e=goog.events.fireListener(a,d);return e}return goog.events.fireListener(a,new goog.events.BrowserEvent(b,this))};goog.events.markIeEvent_=function(a){var b=!1;if(0==a.keyCode)try{a.keyCode=-1;return}catch(c){b=!0}if(b||void 0==a.returnValue)a.returnValue=!0};goog.events.isMarkedIeEvent_=function(a){return 0>a.keyCode||void 0!=a.returnValue};
+goog.events.uniqueIdCounter_=0;goog.events.getUniqueId=function(a){return a+"_"+goog.events.uniqueIdCounter_++};goog.events.getListenerMap_=function(a){a=a[goog.events.LISTENER_MAP_PROP_];return a instanceof goog.events.ListenerMap?a:null};goog.events.LISTENER_WRAPPER_PROP_="__closure_events_fn_"+(1E9*Math.random()>>>0);
+goog.events.wrapListener=function(a){goog.asserts.assert(a,"Listener can not be null.");if(goog.isFunction(a))return a;goog.asserts.assert(a.handleEvent,"An object listener must have handleEvent method.");a[goog.events.LISTENER_WRAPPER_PROP_]||(a[goog.events.LISTENER_WRAPPER_PROP_]=function(b){return a.handleEvent(b)});return a[goog.events.LISTENER_WRAPPER_PROP_]};goog.debug.entryPointRegistry.register(function(a){goog.events.handleBrowserEvent_=a(goog.events.handleBrowserEvent_)});goog.Thenable=function(){};goog.Thenable.prototype.then=function(a,b,c){};goog.Thenable.IMPLEMENTED_BY_PROP="$goog_Thenable";goog.Thenable.addImplementation=function(a){a.prototype.then=a.prototype.then;COMPILED?a.prototype[goog.Thenable.IMPLEMENTED_BY_PROP]=!0:a.prototype.$goog_Thenable=!0};goog.Thenable.isImplementedBy=function(a){if(!a)return!1;try{return COMPILED?!!a[goog.Thenable.IMPLEMENTED_BY_PROP]:!!a.$goog_Thenable}catch(b){return!1}};goog.async={};goog.async.FreeList=function(a,b,c){this.limit_=c;this.create_=a;this.reset_=b;this.occupants_=0;this.head_=null};goog.async.FreeList.prototype.get=function(){var a;0<this.occupants_?(this.occupants_--,a=this.head_,this.head_=a.next,a.next=null):a=this.create_();return a};goog.async.FreeList.prototype.put=function(a){this.reset_(a);this.occupants_<this.limit_&&(this.occupants_++,a.next=this.head_,this.head_=a)};goog.async.FreeList.prototype.occupants=function(){return this.occupants_};goog.async.WorkQueue=function(){this.workTail_=this.workHead_=null};goog.async.WorkQueue.DEFAULT_MAX_UNUSED=100;goog.async.WorkQueue.freelist_=new goog.async.FreeList(function(){return new goog.async.WorkItem},function(a){a.reset()},goog.async.WorkQueue.DEFAULT_MAX_UNUSED);goog.async.WorkQueue.prototype.add=function(a,b){var c=this.getUnusedItem_();c.set(a,b);this.workTail_?this.workTail_.next=c:(goog.asserts.assert(!this.workHead_),this.workHead_=c);this.workTail_=c};
+goog.async.WorkQueue.prototype.remove=function(){var a=null;this.workHead_&&(a=this.workHead_,this.workHead_=this.workHead_.next,this.workHead_||(this.workTail_=null),a.next=null);return a};goog.async.WorkQueue.prototype.returnUnused=function(a){goog.async.WorkQueue.freelist_.put(a)};goog.async.WorkQueue.prototype.getUnusedItem_=function(){return goog.async.WorkQueue.freelist_.get()};goog.async.WorkItem=function(){this.next=this.scope=this.fn=null};
+goog.async.WorkItem.prototype.set=function(a,b){this.fn=a;this.scope=b;this.next=null};goog.async.WorkItem.prototype.reset=function(){this.next=this.scope=this.fn=null};goog.functions={};goog.functions.constant=function(a){return function(){return a}};goog.functions.FALSE=goog.functions.constant(!1);goog.functions.TRUE=goog.functions.constant(!0);goog.functions.NULL=goog.functions.constant(null);goog.functions.identity=function(a,b){return a};goog.functions.error=function(a){return function(){throw Error(a);}};goog.functions.fail=function(a){return function(){throw a;}};
+goog.functions.lock=function(a,b){b=b||0;return function(){return a.apply(this,Array.prototype.slice.call(arguments,0,b))}};goog.functions.nth=function(a){return function(){return arguments[a]}};goog.functions.partialRight=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=Array.prototype.slice.call(arguments);b.push.apply(b,c);return a.apply(this,b)}};goog.functions.withReturnValue=function(a,b){return goog.functions.sequence(a,goog.functions.constant(b))};
+goog.functions.equalTo=function(a,b){return function(c){return b?a==c:a===c}};goog.functions.compose=function(a,b){var c=arguments,d=c.length;return function(){var a;d&&(a=c[d-1].apply(this,arguments));for(var b=d-2;0<=b;b--)a=c[b].call(this,a);return a}};goog.functions.sequence=function(a){var b=arguments,c=b.length;return function(){for(var a,e=0;e<c;e++)a=b[e].apply(this,arguments);return a}};
+goog.functions.and=function(a){var b=arguments,c=b.length;return function(){for(var a=0;a<c;a++)if(!b[a].apply(this,arguments))return!1;return!0}};goog.functions.or=function(a){var b=arguments,c=b.length;return function(){for(var a=0;a<c;a++)if(b[a].apply(this,arguments))return!0;return!1}};goog.functions.not=function(a){return function(){return!a.apply(this,arguments)}};
+goog.functions.create=function(a,b){var c=function(){};c.prototype=a.prototype;c=new c;a.apply(c,Array.prototype.slice.call(arguments,1));return c};goog.functions.CACHE_RETURN_VALUE=!0;goog.functions.cacheReturnValue=function(a){var b=!1,c;return function(){if(!goog.functions.CACHE_RETURN_VALUE)return a();b||(c=a(),b=!0);return c}};goog.functions.once=function(a){var b=a;return function(){if(b){var a=b;b=null;a()}}};
+goog.functions.debounce=function(a,b,c){c&&(a=goog.bind(a,c));var d=null;return function(c){goog.global.clearTimeout(d);var e=arguments;d=goog.global.setTimeout(function(){a.apply(null,e)},b)}};goog.functions.throttle=function(a,b,c){c&&(a=goog.bind(a,c));var d=null,e=!1,f=[],g=function(){d=null;e&&(e=!1,h())},h=function(){d=goog.global.setTimeout(g,b);a.apply(null,f)};return function(a){f=arguments;d?e=!0:h()}};goog.async.throwException=function(a){goog.global.setTimeout(function(){throw a;},0)};goog.async.nextTick=function(a,b,c){var d=a;b&&(d=goog.bind(a,b));d=goog.async.nextTick.wrapCallback_(d);goog.isFunction(goog.global.setImmediate)&&(c||goog.async.nextTick.useSetImmediate_())?goog.global.setImmediate(d):(goog.async.nextTick.setImmediate_||(goog.async.nextTick.setImmediate_=goog.async.nextTick.getSetImmediateEmulator_()),goog.async.nextTick.setImmediate_(d))};
+goog.async.nextTick.useSetImmediate_=function(){return goog.global.Window&&goog.global.Window.prototype&&!goog.labs.userAgent.browser.isEdge()&&goog.global.Window.prototype.setImmediate==goog.global.setImmediate?!1:!0};
+goog.async.nextTick.getSetImmediateEmulator_=function(){var a=goog.global.MessageChannel;"undefined"===typeof a&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&!goog.labs.userAgent.engine.isPresto()&&(a=function(){var a=document.createElement("IFRAME");a.style.display="none";a.src="";document.documentElement.appendChild(a);var b=a.contentWindow,a=b.document;a.open();a.write("");a.close();var c="callImmediate"+Math.random(),d="file:"==b.location.protocol?"*":b.location.protocol+
+"//"+b.location.host,a=goog.bind(function(a){if(("*"==d||a.origin==d)&&a.data==c)this.port1.onmessage()},this);b.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){b.postMessage(c,d)}}});if("undefined"!==typeof a&&!goog.labs.userAgent.browser.isIE()){var b=new a,c={},d=c;b.port1.onmessage=function(){if(goog.isDef(c.next)){c=c.next;var a=c.cb;c.cb=null;a()}};return function(a){d.next={cb:a};d=d.next;b.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in
+document.createElement("SCRIPT")?function(a){var b=document.createElement("SCRIPT");b.onreadystatechange=function(){b.onreadystatechange=null;b.parentNode.removeChild(b);b=null;a();a=null};document.documentElement.appendChild(b)}:function(a){goog.global.setTimeout(a,0)}};goog.async.nextTick.wrapCallback_=goog.functions.identity;goog.debug.entryPointRegistry.register(function(a){goog.async.nextTick.wrapCallback_=a});goog.async.run=function(a,b){goog.async.run.schedule_||goog.async.run.initializeRunner_();goog.async.run.workQueueScheduled_||(goog.async.run.schedule_(),goog.async.run.workQueueScheduled_=!0);goog.async.run.workQueue_.add(a,b)};goog.async.run.initializeRunner_=function(){var a=goog.global.Promise;if(-1!=String(a).indexOf("[native code]")){var b=a.resolve(void 0);goog.async.run.schedule_=function(){b.then(goog.async.run.processWorkQueue)}}else goog.async.run.schedule_=function(){goog.async.nextTick(goog.async.run.processWorkQueue)}};
+goog.async.run.forceNextTick=function(a){goog.async.run.schedule_=function(){goog.async.nextTick(goog.async.run.processWorkQueue);a&&a(goog.async.run.processWorkQueue)}};goog.async.run.workQueueScheduled_=!1;goog.async.run.workQueue_=new goog.async.WorkQueue;goog.DEBUG&&(goog.async.run.resetQueue=function(){goog.async.run.workQueueScheduled_=!1;goog.async.run.workQueue_=new goog.async.WorkQueue});
+goog.async.run.processWorkQueue=function(){for(var a;a=goog.async.run.workQueue_.remove();){try{a.fn.call(a.scope)}catch(b){goog.async.throwException(b)}goog.async.run.workQueue_.returnUnused(a)}goog.async.run.workQueueScheduled_=!1};goog.promise={};goog.promise.Resolver=function(){};goog.Promise=function(a,b){this.state_=goog.Promise.State_.PENDING;this.result_=void 0;this.callbackEntriesTail_=this.callbackEntries_=this.parent_=null;this.executing_=!1;0<goog.Promise.UNHANDLED_REJECTION_DELAY?this.unhandledRejectionId_=0:0==goog.Promise.UNHANDLED_REJECTION_DELAY&&(this.hadUnhandledRejection_=!1);goog.Promise.LONG_STACK_TRACES&&(this.stack_=[],this.addStackTrace_(Error("created")),this.currentStep_=0);if(a!=goog.nullFunction)try{var c=this;a.call(b,function(a){c.resolve_(goog.Promise.State_.FULFILLED,
+a)},function(a){if(goog.DEBUG&&!(a instanceof goog.Promise.CancellationError))try{if(a instanceof Error)throw a;throw Error("Promise rejected.");}catch(e){}c.resolve_(goog.Promise.State_.REJECTED,a)})}catch(d){this.resolve_(goog.Promise.State_.REJECTED,d)}};goog.Promise.LONG_STACK_TRACES=!1;goog.Promise.UNHANDLED_REJECTION_DELAY=0;goog.Promise.State_={PENDING:0,BLOCKED:1,FULFILLED:2,REJECTED:3};
+goog.Promise.CallbackEntry_=function(){this.next=this.context=this.onRejected=this.onFulfilled=this.child=null;this.always=!1};goog.Promise.CallbackEntry_.prototype.reset=function(){this.context=this.onRejected=this.onFulfilled=this.child=null;this.always=!1};goog.Promise.DEFAULT_MAX_UNUSED=100;goog.Promise.freelist_=new goog.async.FreeList(function(){return new goog.Promise.CallbackEntry_},function(a){a.reset()},goog.Promise.DEFAULT_MAX_UNUSED);
+goog.Promise.getCallbackEntry_=function(a,b,c){var d=goog.Promise.freelist_.get();d.onFulfilled=a;d.onRejected=b;d.context=c;return d};goog.Promise.returnEntry_=function(a){goog.Promise.freelist_.put(a)};goog.Promise.resolve=function(a){if(a instanceof goog.Promise)return a;var b=new goog.Promise(goog.nullFunction);b.resolve_(goog.Promise.State_.FULFILLED,a);return b};goog.Promise.reject=function(a){return new goog.Promise(function(b,c){c(a)})};
+goog.Promise.resolveThen_=function(a,b,c){goog.Promise.maybeThen_(a,b,c,null)||goog.async.run(goog.partial(b,a))};goog.Promise.race=function(a){return new goog.Promise(function(b,c){a.length||b(void 0);for(var d=0,e;d<a.length;d++)e=a[d],goog.Promise.resolveThen_(e,b,c)})};
+goog.Promise.all=function(a){return new goog.Promise(function(b,c){var d=a.length,e=[];if(d)for(var f=function(a,c){d--;e[a]=c;0==d&&b(e)},g=function(a){c(a)},h=0,k;h<a.length;h++)k=a[h],goog.Promise.resolveThen_(k,goog.partial(f,h),g);else b(e)})};
+goog.Promise.allSettled=function(a){return new goog.Promise(function(b,c){var d=a.length,e=[];if(d)for(var f=function(a,c,f){d--;e[a]=c?{fulfilled:!0,value:f}:{fulfilled:!1,reason:f};0==d&&b(e)},g=0,h;g<a.length;g++)h=a[g],goog.Promise.resolveThen_(h,goog.partial(f,g,!0),goog.partial(f,g,!1));else b(e)})};
+goog.Promise.firstFulfilled=function(a){return new goog.Promise(function(b,c){var d=a.length,e=[];if(d)for(var f=function(a){b(a)},g=function(a,b){d--;e[a]=b;0==d&&c(e)},h=0,k;h<a.length;h++)k=a[h],goog.Promise.resolveThen_(k,f,goog.partial(g,h));else b(void 0)})};goog.Promise.withResolver=function(){var a,b,c=new goog.Promise(function(c,e){a=c;b=e});return new goog.Promise.Resolver_(c,a,b)};
+goog.Promise.prototype.then=function(a,b,c){null!=a&&goog.asserts.assertFunction(a,"opt_onFulfilled should be a function.");null!=b&&goog.asserts.assertFunction(b,"opt_onRejected should be a function. Did you pass opt_context as the second argument instead of the third?");goog.Promise.LONG_STACK_TRACES&&this.addStackTrace_(Error("then"));return this.addChildPromise_(goog.isFunction(a)?a:null,goog.isFunction(b)?b:null,c)};goog.Thenable.addImplementation(goog.Promise);
+goog.Promise.prototype.thenVoid=function(a,b,c){null!=a&&goog.asserts.assertFunction(a,"opt_onFulfilled should be a function.");null!=b&&goog.asserts.assertFunction(b,"opt_onRejected should be a function. Did you pass opt_context as the second argument instead of the third?");goog.Promise.LONG_STACK_TRACES&&this.addStackTrace_(Error("then"));this.addCallbackEntry_(goog.Promise.getCallbackEntry_(a||goog.nullFunction,b||null,c))};
+goog.Promise.prototype.thenAlways=function(a,b){goog.Promise.LONG_STACK_TRACES&&this.addStackTrace_(Error("thenAlways"));var c=goog.Promise.getCallbackEntry_(a,a,b);c.always=!0;this.addCallbackEntry_(c);return this};goog.Promise.prototype.thenCatch=function(a,b){goog.Promise.LONG_STACK_TRACES&&this.addStackTrace_(Error("thenCatch"));return this.addChildPromise_(null,a,b)};
+goog.Promise.prototype.cancel=function(a){this.state_==goog.Promise.State_.PENDING&&goog.async.run(function(){var b=new goog.Promise.CancellationError(a);this.cancelInternal_(b)},this)};goog.Promise.prototype.cancelInternal_=function(a){this.state_==goog.Promise.State_.PENDING&&(this.parent_?(this.parent_.cancelChild_(this,a),this.parent_=null):this.resolve_(goog.Promise.State_.REJECTED,a))};
+goog.Promise.prototype.cancelChild_=function(a,b){if(this.callbackEntries_){for(var c=0,d=null,e=null,f=this.callbackEntries_;f&&(f.always||(c++,f.child==a&&(d=f),!(d&&1<c)));f=f.next)d||(e=f);d&&(this.state_==goog.Promise.State_.PENDING&&1==c?this.cancelInternal_(b):(e?this.removeEntryAfter_(e):this.popEntry_(),this.executeCallback_(d,goog.Promise.State_.REJECTED,b)))}};
+goog.Promise.prototype.addCallbackEntry_=function(a){this.hasEntry_()||this.state_!=goog.Promise.State_.FULFILLED&&this.state_!=goog.Promise.State_.REJECTED||this.scheduleCallbacks_();this.queueEntry_(a)};
+goog.Promise.prototype.addChildPromise_=function(a,b,c){var d=goog.Promise.getCallbackEntry_(null,null,null);d.child=new goog.Promise(function(e,f){d.onFulfilled=a?function(b){try{var d=a.call(c,b);e(d)}catch(k){f(k)}}:e;d.onRejected=b?function(a){try{var d=b.call(c,a);!goog.isDef(d)&&a instanceof goog.Promise.CancellationError?f(a):e(d)}catch(k){f(k)}}:f});d.child.parent_=this;this.addCallbackEntry_(d);return d.child};
+goog.Promise.prototype.unblockAndFulfill_=function(a){goog.asserts.assert(this.state_==goog.Promise.State_.BLOCKED);this.state_=goog.Promise.State_.PENDING;this.resolve_(goog.Promise.State_.FULFILLED,a)};goog.Promise.prototype.unblockAndReject_=function(a){goog.asserts.assert(this.state_==goog.Promise.State_.BLOCKED);this.state_=goog.Promise.State_.PENDING;this.resolve_(goog.Promise.State_.REJECTED,a)};
+goog.Promise.prototype.resolve_=function(a,b){this.state_==goog.Promise.State_.PENDING&&(this===b&&(a=goog.Promise.State_.REJECTED,b=new TypeError("Promise cannot resolve to itself")),this.state_=goog.Promise.State_.BLOCKED,goog.Promise.maybeThen_(b,this.unblockAndFulfill_,this.unblockAndReject_,this)||(this.result_=b,this.state_=a,this.parent_=null,this.scheduleCallbacks_(),a!=goog.Promise.State_.REJECTED||b instanceof goog.Promise.CancellationError||goog.Promise.addUnhandledRejection_(this,b)))};
+goog.Promise.maybeThen_=function(a,b,c,d){if(a instanceof goog.Promise)return a.thenVoid(b,c,d),!0;if(goog.Thenable.isImplementedBy(a))return a.then(b,c,d),!0;if(goog.isObject(a))try{var e=a.then;if(goog.isFunction(e))return goog.Promise.tryThen_(a,e,b,c,d),!0}catch(f){return c.call(d,f),!0}return!1};goog.Promise.tryThen_=function(a,b,c,d,e){var f=!1,g=function(a){f||(f=!0,c.call(e,a))},h=function(a){f||(f=!0,d.call(e,a))};try{b.call(a,g,h)}catch(k){h(k)}};
+goog.Promise.prototype.scheduleCallbacks_=function(){this.executing_||(this.executing_=!0,goog.async.run(this.executeCallbacks_,this))};goog.Promise.prototype.hasEntry_=function(){return!!this.callbackEntries_};goog.Promise.prototype.queueEntry_=function(a){goog.asserts.assert(null!=a.onFulfilled);this.callbackEntriesTail_?this.callbackEntriesTail_.next=a:this.callbackEntries_=a;this.callbackEntriesTail_=a};
+goog.Promise.prototype.popEntry_=function(){var a=null;this.callbackEntries_&&(a=this.callbackEntries_,this.callbackEntries_=a.next,a.next=null);this.callbackEntries_||(this.callbackEntriesTail_=null);null!=a&&goog.asserts.assert(null!=a.onFulfilled);return a};goog.Promise.prototype.removeEntryAfter_=function(a){goog.asserts.assert(this.callbackEntries_);goog.asserts.assert(null!=a);a.next==this.callbackEntriesTail_&&(this.callbackEntriesTail_=a);a.next=a.next.next};
+goog.Promise.prototype.executeCallbacks_=function(){for(var a;a=this.popEntry_();)goog.Promise.LONG_STACK_TRACES&&this.currentStep_++,this.executeCallback_(a,this.state_,this.result_);this.executing_=!1};
+goog.Promise.prototype.executeCallback_=function(a,b,c){b==goog.Promise.State_.REJECTED&&a.onRejected&&!a.always&&this.removeUnhandledRejection_();if(a.child)a.child.parent_=null,goog.Promise.invokeCallback_(a,b,c);else try{a.always?a.onFulfilled.call(a.context):goog.Promise.invokeCallback_(a,b,c)}catch(d){goog.Promise.handleRejection_.call(null,d)}goog.Promise.returnEntry_(a)};
+goog.Promise.invokeCallback_=function(a,b,c){b==goog.Promise.State_.FULFILLED?a.onFulfilled.call(a.context,c):a.onRejected&&a.onRejected.call(a.context,c)};goog.Promise.prototype.addStackTrace_=function(a){if(goog.Promise.LONG_STACK_TRACES&&goog.isString(a.stack)){var b=a.stack.split("\n",4)[3];a=a.message;a+=Array(11-a.length).join(" ");this.stack_.push(a+b)}};
+goog.Promise.prototype.appendLongStack_=function(a){if(goog.Promise.LONG_STACK_TRACES&&a&&goog.isString(a.stack)&&this.stack_.length){for(var b=["Promise trace:"],c=this;c;c=c.parent_){for(var d=this.currentStep_;0<=d;d--)b.push(c.stack_[d]);b.push("Value: ["+(c.state_==goog.Promise.State_.REJECTED?"REJECTED":"FULFILLED")+"] <"+String(c.result_)+">")}a.stack+="\n\n"+b.join("\n")}};
+goog.Promise.prototype.removeUnhandledRejection_=function(){if(0<goog.Promise.UNHANDLED_REJECTION_DELAY)for(var a=this;a&&a.unhandledRejectionId_;a=a.parent_)goog.global.clearTimeout(a.unhandledRejectionId_),a.unhandledRejectionId_=0;else if(0==goog.Promise.UNHANDLED_REJECTION_DELAY)for(a=this;a&&a.hadUnhandledRejection_;a=a.parent_)a.hadUnhandledRejection_=!1};
+goog.Promise.addUnhandledRejection_=function(a,b){0<goog.Promise.UNHANDLED_REJECTION_DELAY?a.unhandledRejectionId_=goog.global.setTimeout(function(){a.appendLongStack_(b);goog.Promise.handleRejection_.call(null,b)},goog.Promise.UNHANDLED_REJECTION_DELAY):0==goog.Promise.UNHANDLED_REJECTION_DELAY&&(a.hadUnhandledRejection_=!0,goog.async.run(function(){a.hadUnhandledRejection_&&(a.appendLongStack_(b),goog.Promise.handleRejection_.call(null,b))}))};goog.Promise.handleRejection_=goog.async.throwException;
+goog.Promise.setUnhandledRejectionHandler=function(a){goog.Promise.handleRejection_=a};goog.Promise.CancellationError=function(a){goog.debug.Error.call(this,a)};goog.inherits(goog.Promise.CancellationError,goog.debug.Error);goog.Promise.CancellationError.prototype.name="cancel";goog.Promise.Resolver_=function(a,b,c){this.promise=a;this.resolve=b;this.reject=c};goog.events.EventTarget=function(){goog.Disposable.call(this);this.eventTargetListeners_=new goog.events.ListenerMap(this);this.actualEventTarget_=this;this.parentEventTarget_=null};goog.inherits(goog.events.EventTarget,goog.Disposable);goog.events.Listenable.addImplementation(goog.events.EventTarget);goog.events.EventTarget.MAX_ANCESTORS_=1E3;goog.events.EventTarget.prototype.getParentEventTarget=function(){return this.parentEventTarget_};
+goog.events.EventTarget.prototype.setParentEventTarget=function(a){this.parentEventTarget_=a};goog.events.EventTarget.prototype.addEventListener=function(a,b,c,d){goog.events.listen(this,a,b,c,d)};goog.events.EventTarget.prototype.removeEventListener=function(a,b,c,d){goog.events.unlisten(this,a,b,c,d)};
+goog.events.EventTarget.prototype.dispatchEvent=function(a){this.assertInitialized_();var b,c=this.getParentEventTarget();if(c){b=[];for(var d=1;c;c=c.getParentEventTarget())b.push(c),goog.asserts.assert(++d<goog.events.EventTarget.MAX_ANCESTORS_,"infinite loop")}return goog.events.EventTarget.dispatchEventInternal_(this.actualEventTarget_,a,b)};
+goog.events.EventTarget.prototype.disposeInternal=function(){goog.events.EventTarget.superClass_.disposeInternal.call(this);this.removeAllListeners();this.parentEventTarget_=null};goog.events.EventTarget.prototype.listen=function(a,b,c,d){this.assertInitialized_();return this.eventTargetListeners_.add(String(a),b,!1,c,d)};goog.events.EventTarget.prototype.listenOnce=function(a,b,c,d){return this.eventTargetListeners_.add(String(a),b,!0,c,d)};
+goog.events.EventTarget.prototype.unlisten=function(a,b,c,d){return this.eventTargetListeners_.remove(String(a),b,c,d)};goog.events.EventTarget.prototype.unlistenByKey=function(a){return this.eventTargetListeners_.removeByKey(a)};goog.events.EventTarget.prototype.removeAllListeners=function(a){return this.eventTargetListeners_?this.eventTargetListeners_.removeAll(a):0};
+goog.events.EventTarget.prototype.fireListeners=function(a,b,c){a=this.eventTargetListeners_.listeners[String(a)];if(!a)return!0;a=a.concat();for(var d=!0,e=0;e<a.length;++e){var f=a[e];if(f&&!f.removed&&f.capture==b){var g=f.listener,h=f.handler||f.src;f.callOnce&&this.unlistenByKey(f);d=!1!==g.call(h,c)&&d}}return d&&0!=c.returnValue_};goog.events.EventTarget.prototype.getListeners=function(a,b){return this.eventTargetListeners_.getListeners(String(a),b)};
+goog.events.EventTarget.prototype.getListener=function(a,b,c,d){return this.eventTargetListeners_.getListener(String(a),b,c,d)};goog.events.EventTarget.prototype.hasListener=function(a,b){var c=goog.isDef(a)?String(a):void 0;return this.eventTargetListeners_.hasListener(c,b)};goog.events.EventTarget.prototype.setTargetForTesting=function(a){this.actualEventTarget_=a};goog.events.EventTarget.prototype.assertInitialized_=function(){goog.asserts.assert(this.eventTargetListeners_,"Event target is not initialized. Did you call the superclass (goog.events.EventTarget) constructor?")};
+goog.events.EventTarget.dispatchEventInternal_=function(a,b,c){var d=b.type||b;if(goog.isString(b))b=new goog.events.Event(b,a);else if(b instanceof goog.events.Event)b.target=b.target||a;else{var e=b;b=new goog.events.Event(d,a);goog.object.extend(b,e)}var e=!0,f;if(c)for(var g=c.length-1;!b.propagationStopped_&&0<=g;g--)f=b.currentTarget=c[g],e=f.fireListeners(d,!0,b)&&e;b.propagationStopped_||(f=b.currentTarget=a,e=f.fireListeners(d,!0,b)&&e,b.propagationStopped_||(e=f.fireListeners(d,!1,b)&&e));
+if(c)for(g=0;!b.propagationStopped_&&g<c.length;g++)f=b.currentTarget=c[g],e=f.fireListeners(d,!1,b)&&e;return e};goog.Timer=function(a,b){goog.events.EventTarget.call(this);this.interval_=a||1;this.timerObject_=b||goog.Timer.defaultTimerObject;this.boundTick_=goog.bind(this.tick_,this);this.last_=goog.now()};goog.inherits(goog.Timer,goog.events.EventTarget);goog.Timer.MAX_TIMEOUT_=2147483647;goog.Timer.INVALID_TIMEOUT_ID_=-1;goog.Timer.prototype.enabled=!1;goog.Timer.defaultTimerObject=goog.global;goog.Timer.intervalScale=.8;goog.Timer.prototype.timer_=null;goog.Timer.prototype.getInterval=function(){return this.interval_};
+goog.Timer.prototype.setInterval=function(a){this.interval_=a;this.timer_&&this.enabled?(this.stop(),this.start()):this.timer_&&this.stop()};
+goog.Timer.prototype.tick_=function(){if(this.enabled){var a=goog.now()-this.last_;0<a&&a<this.interval_*goog.Timer.intervalScale?this.timer_=this.timerObject_.setTimeout(this.boundTick_,this.interval_-a):(this.timer_&&(this.timerObject_.clearTimeout(this.timer_),this.timer_=null),this.dispatchTick(),this.enabled&&(this.timer_=this.timerObject_.setTimeout(this.boundTick_,this.interval_),this.last_=goog.now()))}};goog.Timer.prototype.dispatchTick=function(){this.dispatchEvent(goog.Timer.TICK)};
+goog.Timer.prototype.start=function(){this.enabled=!0;this.timer_||(this.timer_=this.timerObject_.setTimeout(this.boundTick_,this.interval_),this.last_=goog.now())};goog.Timer.prototype.stop=function(){this.enabled=!1;this.timer_&&(this.timerObject_.clearTimeout(this.timer_),this.timer_=null)};goog.Timer.prototype.disposeInternal=function(){goog.Timer.superClass_.disposeInternal.call(this);this.stop();delete this.timerObject_};goog.Timer.TICK="tick";
+goog.Timer.callOnce=function(a,b,c){if(goog.isFunction(a))c&&(a=goog.bind(a,c));else if(a&&"function"==typeof a.handleEvent)a=goog.bind(a.handleEvent,a);else throw Error("Invalid listener argument");return Number(b)>goog.Timer.MAX_TIMEOUT_?goog.Timer.INVALID_TIMEOUT_ID_:goog.Timer.defaultTimerObject.setTimeout(a,b||0)};goog.Timer.clear=function(a){goog.Timer.defaultTimerObject.clearTimeout(a)};
+goog.Timer.promise=function(a,b){var c=null;return(new goog.Promise(function(d,e){c=goog.Timer.callOnce(function(){d(b)},a);c==goog.Timer.INVALID_TIMEOUT_ID_&&e(Error("Failed to schedule timer."))})).thenCatch(function(a){goog.Timer.clear(c);throw a;})};goog.events.EventHandler=function(a){goog.Disposable.call(this);this.handler_=a;this.keys_={}};goog.inherits(goog.events.EventHandler,goog.Disposable);goog.events.EventHandler.typeArray_=[];goog.events.EventHandler.prototype.listen=function(a,b,c,d){return this.listen_(a,b,c,d)};goog.events.EventHandler.prototype.listenWithScope=function(a,b,c,d,e){return this.listen_(a,b,c,d,e)};
+goog.events.EventHandler.prototype.listen_=function(a,b,c,d,e){goog.isArray(b)||(b&&(goog.events.EventHandler.typeArray_[0]=b.toString()),b=goog.events.EventHandler.typeArray_);for(var f=0;f<b.length;f++){var g=goog.events.listen(a,b[f],c||this.handleEvent,d||!1,e||this.handler_||this);if(!g)break;this.keys_[g.key]=g}return this};goog.events.EventHandler.prototype.listenOnce=function(a,b,c,d){return this.listenOnce_(a,b,c,d)};
+goog.events.EventHandler.prototype.listenOnceWithScope=function(a,b,c,d,e){return this.listenOnce_(a,b,c,d,e)};goog.events.EventHandler.prototype.listenOnce_=function(a,b,c,d,e){if(goog.isArray(b))for(var f=0;f<b.length;f++)this.listenOnce_(a,b[f],c,d,e);else{a=goog.events.listenOnce(a,b,c||this.handleEvent,d,e||this.handler_||this);if(!a)return this;this.keys_[a.key]=a}return this};goog.events.EventHandler.prototype.listenWithWrapper=function(a,b,c,d){return this.listenWithWrapper_(a,b,c,d)};
+goog.events.EventHandler.prototype.listenWithWrapperAndScope=function(a,b,c,d,e){return this.listenWithWrapper_(a,b,c,d,e)};goog.events.EventHandler.prototype.listenWithWrapper_=function(a,b,c,d,e){b.listen(a,c,d,e||this.handler_||this,this);return this};goog.events.EventHandler.prototype.getListenerCount=function(){var a=0,b;for(b in this.keys_)Object.prototype.hasOwnProperty.call(this.keys_,b)&&a++;return a};
+goog.events.EventHandler.prototype.unlisten=function(a,b,c,d,e){if(goog.isArray(b))for(var f=0;f<b.length;f++)this.unlisten(a,b[f],c,d,e);else if(a=goog.events.getListener(a,b,c||this.handleEvent,d,e||this.handler_||this))goog.events.unlistenByKey(a),delete this.keys_[a.key];return this};goog.events.EventHandler.prototype.unlistenWithWrapper=function(a,b,c,d,e){b.unlisten(a,c,d,e||this.handler_||this,this);return this};
+goog.events.EventHandler.prototype.removeAll=function(){goog.object.forEach(this.keys_,function(a,b){this.keys_.hasOwnProperty(b)&&goog.events.unlistenByKey(a)},this);this.keys_={}};goog.events.EventHandler.prototype.disposeInternal=function(){goog.events.EventHandler.superClass_.disposeInternal.call(this);this.removeAll()};goog.events.EventHandler.prototype.handleEvent=function(a){throw Error("EventHandler.handleEvent not implemented");};goog.ui={};goog.ui.IdGenerator=function(){};goog.addSingletonGetter(goog.ui.IdGenerator);goog.ui.IdGenerator.prototype.nextId_=0;goog.ui.IdGenerator.prototype.getNextUniqueId=function(){return":"+(this.nextId_++).toString(36)};goog.ui.Component=function(a){goog.events.EventTarget.call(this);this.dom_=a||goog.dom.getDomHelper();this.rightToLeft_=goog.ui.Component.defaultRightToLeft_;this.id_=null;this.inDocument_=!1;this.element_=null;this.googUiComponentHandler_=void 0;this.childIndex_=this.children_=this.parent_=this.model_=null;this.wasDecorated_=!1};goog.inherits(goog.ui.Component,goog.events.EventTarget);goog.ui.Component.ALLOW_DETACHED_DECORATION=!1;goog.ui.Component.prototype.idGenerator_=goog.ui.IdGenerator.getInstance();
+goog.ui.Component.DEFAULT_BIDI_DIR=0;goog.ui.Component.defaultRightToLeft_=1==goog.ui.Component.DEFAULT_BIDI_DIR?!1:-1==goog.ui.Component.DEFAULT_BIDI_DIR?!0:null;
+goog.ui.Component.EventType={BEFORE_SHOW:"beforeshow",SHOW:"show",HIDE:"hide",DISABLE:"disable",ENABLE:"enable",HIGHLIGHT:"highlight",UNHIGHLIGHT:"unhighlight",ACTIVATE:"activate",DEACTIVATE:"deactivate",SELECT:"select",UNSELECT:"unselect",CHECK:"check",UNCHECK:"uncheck",FOCUS:"focus",BLUR:"blur",OPEN:"open",CLOSE:"close",ENTER:"enter",LEAVE:"leave",ACTION:"action",CHANGE:"change"};
+goog.ui.Component.Error={NOT_SUPPORTED:"Method not supported",DECORATE_INVALID:"Invalid element to decorate",ALREADY_RENDERED:"Component already rendered",PARENT_UNABLE_TO_BE_SET:"Unable to set parent component",CHILD_INDEX_OUT_OF_BOUNDS:"Child component index out of bounds",NOT_OUR_CHILD:"Child is not in parent component",NOT_IN_DOCUMENT:"Operation not supported while component is not in document",STATE_INVALID:"Invalid component state"};
+goog.ui.Component.State={ALL:255,DISABLED:1,HOVER:2,ACTIVE:4,SELECTED:8,CHECKED:16,FOCUSED:32,OPENED:64};
+goog.ui.Component.getStateTransitionEvent=function(a,b){switch(a){case goog.ui.Component.State.DISABLED:return b?goog.ui.Component.EventType.DISABLE:goog.ui.Component.EventType.ENABLE;case goog.ui.Component.State.HOVER:return b?goog.ui.Component.EventType.HIGHLIGHT:goog.ui.Component.EventType.UNHIGHLIGHT;case goog.ui.Component.State.ACTIVE:return b?goog.ui.Component.EventType.ACTIVATE:goog.ui.Component.EventType.DEACTIVATE;case goog.ui.Component.State.SELECTED:return b?goog.ui.Component.EventType.SELECT:
+goog.ui.Component.EventType.UNSELECT;case goog.ui.Component.State.CHECKED:return b?goog.ui.Component.EventType.CHECK:goog.ui.Component.EventType.UNCHECK;case goog.ui.Component.State.FOCUSED:return b?goog.ui.Component.EventType.FOCUS:goog.ui.Component.EventType.BLUR;case goog.ui.Component.State.OPENED:return b?goog.ui.Component.EventType.OPEN:goog.ui.Component.EventType.CLOSE}throw Error(goog.ui.Component.Error.STATE_INVALID);};
+goog.ui.Component.setDefaultRightToLeft=function(a){goog.ui.Component.defaultRightToLeft_=a};goog.ui.Component.prototype.getId=function(){return this.id_||(this.id_=this.idGenerator_.getNextUniqueId())};goog.ui.Component.prototype.setId=function(a){this.parent_&&this.parent_.childIndex_&&(goog.object.remove(this.parent_.childIndex_,this.id_),goog.object.add(this.parent_.childIndex_,a,this));this.id_=a};goog.ui.Component.prototype.getElement=function(){return this.element_};
+goog.ui.Component.prototype.getElementStrict=function(){var a=this.element_;goog.asserts.assert(a,"Can not call getElementStrict before rendering/decorating.");return a};goog.ui.Component.prototype.setElementInternal=function(a){this.element_=a};goog.ui.Component.prototype.getElementsByClass=function(a){return this.element_?this.dom_.getElementsByClass(a,this.element_):[]};goog.ui.Component.prototype.getElementByClass=function(a){return this.element_?this.dom_.getElementByClass(a,this.element_):null};
+goog.ui.Component.prototype.getRequiredElementByClass=function(a){var b=this.getElementByClass(a);goog.asserts.assert(b,"Expected element in component with class: %s",a);return b};goog.ui.Component.prototype.getHandler=function(){this.googUiComponentHandler_||(this.googUiComponentHandler_=new goog.events.EventHandler(this));return this.googUiComponentHandler_};
+goog.ui.Component.prototype.setParent=function(a){if(this==a)throw Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET);if(a&&this.parent_&&this.id_&&this.parent_.getChild(this.id_)&&this.parent_!=a)throw Error(goog.ui.Component.Error.PARENT_UNABLE_TO_BE_SET);this.parent_=a;goog.ui.Component.superClass_.setParentEventTarget.call(this,a)};goog.ui.Component.prototype.getParent=function(){return this.parent_};
+goog.ui.Component.prototype.setParentEventTarget=function(a){if(this.parent_&&this.parent_!=a)throw Error(goog.ui.Component.Error.NOT_SUPPORTED);goog.ui.Component.superClass_.setParentEventTarget.call(this,a)};goog.ui.Component.prototype.getDomHelper=function(){return this.dom_};goog.ui.Component.prototype.isInDocument=function(){return this.inDocument_};goog.ui.Component.prototype.createDom=function(){this.element_=this.dom_.createElement("DIV")};goog.ui.Component.prototype.render=function(a){this.render_(a)};
+goog.ui.Component.prototype.renderBefore=function(a){this.render_(a.parentNode,a)};goog.ui.Component.prototype.render_=function(a,b){if(this.inDocument_)throw Error(goog.ui.Component.Error.ALREADY_RENDERED);this.element_||this.createDom();a?a.insertBefore(this.element_,b||null):this.dom_.getDocument().body.appendChild(this.element_);this.parent_&&!this.parent_.isInDocument()||this.enterDocument()};
+goog.ui.Component.prototype.decorate=function(a){if(this.inDocument_)throw Error(goog.ui.Component.Error.ALREADY_RENDERED);if(a&&this.canDecorate(a)){this.wasDecorated_=!0;var b=goog.dom.getOwnerDocument(a);this.dom_&&this.dom_.getDocument()==b||(this.dom_=goog.dom.getDomHelper(a));this.decorateInternal(a);goog.ui.Component.ALLOW_DETACHED_DECORATION&&!goog.dom.contains(b,a)||this.enterDocument()}else throw Error(goog.ui.Component.Error.DECORATE_INVALID);};goog.ui.Component.prototype.canDecorate=function(a){return!0};
+goog.ui.Component.prototype.wasDecorated=function(){return this.wasDecorated_};goog.ui.Component.prototype.decorateInternal=function(a){this.element_=a};goog.ui.Component.prototype.enterDocument=function(){this.inDocument_=!0;this.forEachChild(function(a){!a.isInDocument()&&a.getElement()&&a.enterDocument()})};
+goog.ui.Component.prototype.exitDocument=function(){this.forEachChild(function(a){a.isInDocument()&&a.exitDocument()});this.googUiComponentHandler_&&this.googUiComponentHandler_.removeAll();this.inDocument_=!1};
+goog.ui.Component.prototype.disposeInternal=function(){this.inDocument_&&this.exitDocument();this.googUiComponentHandler_&&(this.googUiComponentHandler_.dispose(),delete this.googUiComponentHandler_);this.forEachChild(function(a){a.dispose()});!this.wasDecorated_&&this.element_&&goog.dom.removeNode(this.element_);this.parent_=this.model_=this.element_=this.childIndex_=this.children_=null;goog.ui.Component.superClass_.disposeInternal.call(this)};
+goog.ui.Component.prototype.makeId=function(a){return this.getId()+"."+a};goog.ui.Component.prototype.makeIds=function(a){var b={},c;for(c in a)b[c]=this.makeId(a[c]);return b};goog.ui.Component.prototype.getModel=function(){return this.model_};goog.ui.Component.prototype.setModel=function(a){this.model_=a};goog.ui.Component.prototype.getFragmentFromId=function(a){return a.substring(this.getId().length+1)};
+goog.ui.Component.prototype.getElementByFragment=function(a){if(!this.inDocument_)throw Error(goog.ui.Component.Error.NOT_IN_DOCUMENT);return this.dom_.getElement(this.makeId(a))};goog.ui.Component.prototype.addChild=function(a,b){this.addChildAt(a,this.getChildCount(),b)};
+goog.ui.Component.prototype.addChildAt=function(a,b,c){goog.asserts.assert(!!a,"Provided element must not be null.");if(a.inDocument_&&(c||!this.inDocument_))throw Error(goog.ui.Component.Error.ALREADY_RENDERED);if(0>b||b>this.getChildCount())throw Error(goog.ui.Component.Error.CHILD_INDEX_OUT_OF_BOUNDS);this.childIndex_&&this.children_||(this.childIndex_={},this.children_=[]);a.getParent()==this?(goog.object.set(this.childIndex_,a.getId(),a),goog.array.remove(this.children_,a)):goog.object.add(this.childIndex_,
+a.getId(),a);a.setParent(this);goog.array.insertAt(this.children_,a,b);a.inDocument_&&this.inDocument_&&a.getParent()==this?(c=this.getContentElement(),b=c.childNodes[b]||null,b!=a.getElement()&&c.insertBefore(a.getElement(),b)):c?(this.element_||this.createDom(),b=this.getChildAt(b+1),a.render_(this.getContentElement(),b?b.element_:null)):this.inDocument_&&!a.inDocument_&&a.element_&&a.element_.parentNode&&a.element_.parentNode.nodeType==goog.dom.NodeType.ELEMENT&&a.enterDocument()};
+goog.ui.Component.prototype.getContentElement=function(){return this.element_};goog.ui.Component.prototype.isRightToLeft=function(){null==this.rightToLeft_&&(this.rightToLeft_=goog.style.isRightToLeft(this.inDocument_?this.element_:this.dom_.getDocument().body));return this.rightToLeft_};goog.ui.Component.prototype.setRightToLeft=function(a){if(this.inDocument_)throw Error(goog.ui.Component.Error.ALREADY_RENDERED);this.rightToLeft_=a};
+goog.ui.Component.prototype.hasChildren=function(){return!!this.children_&&0!=this.children_.length};goog.ui.Component.prototype.getChildCount=function(){return this.children_?this.children_.length:0};goog.ui.Component.prototype.getChildIds=function(){var a=[];this.forEachChild(function(b){a.push(b.getId())});return a};goog.ui.Component.prototype.getChild=function(a){return this.childIndex_&&a?goog.object.get(this.childIndex_,a)||null:null};
+goog.ui.Component.prototype.getChildAt=function(a){return this.children_?this.children_[a]||null:null};goog.ui.Component.prototype.forEachChild=function(a,b){this.children_&&goog.array.forEach(this.children_,a,b)};goog.ui.Component.prototype.indexOfChild=function(a){return this.children_&&a?goog.array.indexOf(this.children_,a):-1};
+goog.ui.Component.prototype.removeChild=function(a,b){if(a){var c=goog.isString(a)?a:a.getId();a=this.getChild(c);c&&a&&(goog.object.remove(this.childIndex_,c),goog.array.remove(this.children_,a),b&&(a.exitDocument(),a.element_&&goog.dom.removeNode(a.element_)),a.setParent(null))}if(!a)throw Error(goog.ui.Component.Error.NOT_OUR_CHILD);return a};goog.ui.Component.prototype.removeChildAt=function(a,b){return this.removeChild(this.getChildAt(a),b)};
+goog.ui.Component.prototype.removeChildren=function(a){for(var b=[];this.hasChildren();)b.push(this.removeChildAt(0,a));return b};goog.a11y={};goog.a11y.aria={};
+goog.a11y.aria.Role={ALERT:"alert",ALERTDIALOG:"alertdialog",APPLICATION:"application",ARTICLE:"article",BANNER:"banner",BUTTON:"button",CHECKBOX:"checkbox",COLUMNHEADER:"columnheader",COMBOBOX:"combobox",COMPLEMENTARY:"complementary",CONTENTINFO:"contentinfo",DEFINITION:"definition",DIALOG:"dialog",DIRECTORY:"directory",DOCUMENT:"document",FORM:"form",GRID:"grid",GRIDCELL:"gridcell",GROUP:"group",HEADING:"heading",IMG:"img",LINK:"link",LIST:"list",LISTBOX:"listbox",LISTITEM:"listitem",LOG:"log",
+MAIN:"main",MARQUEE:"marquee",MATH:"math",MENU:"menu",MENUBAR:"menubar",MENU_ITEM:"menuitem",MENU_ITEM_CHECKBOX:"menuitemcheckbox",MENU_ITEM_RADIO:"menuitemradio",NAVIGATION:"navigation",NOTE:"note",OPTION:"option",PRESENTATION:"presentation",PROGRESSBAR:"progressbar",RADIO:"radio",RADIOGROUP:"radiogroup",REGION:"region",ROW:"row",ROWGROUP:"rowgroup",ROWHEADER:"rowheader",SCROLLBAR:"scrollbar",SEARCH:"search",SEPARATOR:"separator",SLIDER:"slider",SPINBUTTON:"spinbutton",STATUS:"status",TAB:"tab",
+TAB_LIST:"tablist",TAB_PANEL:"tabpanel",TEXTBOX:"textbox",TEXTINFO:"textinfo",TIMER:"timer",TOOLBAR:"toolbar",TOOLTIP:"tooltip",TREE:"tree",TREEGRID:"treegrid",TREEITEM:"treeitem"};goog.a11y.aria.State={ACTIVEDESCENDANT:"activedescendant",ATOMIC:"atomic",AUTOCOMPLETE:"autocomplete",BUSY:"busy",CHECKED:"checked",CONTROLS:"controls",DESCRIBEDBY:"describedby",DISABLED:"disabled",DROPEFFECT:"dropeffect",EXPANDED:"expanded",FLOWTO:"flowto",GRABBED:"grabbed",HASPOPUP:"haspopup",HIDDEN:"hidden",INVALID:"invalid",LABEL:"label",LABELLEDBY:"labelledby",LEVEL:"level",LIVE:"live",MULTILINE:"multiline",MULTISELECTABLE:"multiselectable",ORIENTATION:"orientation",OWNS:"owns",POSINSET:"posinset",
+PRESSED:"pressed",READONLY:"readonly",RELEVANT:"relevant",REQUIRED:"required",SELECTED:"selected",SETSIZE:"setsize",SORT:"sort",VALUEMAX:"valuemax",VALUEMIN:"valuemin",VALUENOW:"valuenow",VALUETEXT:"valuetext"};goog.a11y.aria.AutoCompleteValues={INLINE:"inline",LIST:"list",BOTH:"both",NONE:"none"};goog.a11y.aria.DropEffectValues={COPY:"copy",MOVE:"move",LINK:"link",EXECUTE:"execute",POPUP:"popup",NONE:"none"};goog.a11y.aria.LivePriority={OFF:"off",POLITE:"polite",ASSERTIVE:"assertive"};
+goog.a11y.aria.OrientationValues={VERTICAL:"vertical",HORIZONTAL:"horizontal"};goog.a11y.aria.RelevantValues={ADDITIONS:"additions",REMOVALS:"removals",TEXT:"text",ALL:"all"};goog.a11y.aria.SortValues={ASCENDING:"ascending",DESCENDING:"descending",NONE:"none",OTHER:"other"};goog.a11y.aria.CheckedValues={TRUE:"true",FALSE:"false",MIXED:"mixed",UNDEFINED:"undefined"};goog.a11y.aria.ExpandedValues={TRUE:"true",FALSE:"false",UNDEFINED:"undefined"};
+goog.a11y.aria.GrabbedValues={TRUE:"true",FALSE:"false",UNDEFINED:"undefined"};goog.a11y.aria.InvalidValues={FALSE:"false",TRUE:"true",GRAMMAR:"grammar",SPELLING:"spelling"};goog.a11y.aria.PressedValues={TRUE:"true",FALSE:"false",MIXED:"mixed",UNDEFINED:"undefined"};goog.a11y.aria.SelectedValues={TRUE:"true",FALSE:"false",UNDEFINED:"undefined"};goog.a11y.aria.datatables={};
+goog.a11y.aria.datatables.getDefaultValuesMap=function(){goog.a11y.aria.DefaultStateValueMap_||(goog.a11y.aria.DefaultStateValueMap_=goog.object.create(goog.a11y.aria.State.ATOMIC,!1,goog.a11y.aria.State.AUTOCOMPLETE,"none",goog.a11y.aria.State.DROPEFFECT,"none",goog.a11y.aria.State.HASPOPUP,!1,goog.a11y.aria.State.LIVE,"off",goog.a11y.aria.State.MULTILINE,!1,goog.a11y.aria.State.MULTISELECTABLE,!1,goog.a11y.aria.State.ORIENTATION,"vertical",goog.a11y.aria.State.READONLY,!1,goog.a11y.aria.State.RELEVANT,
+"additions text",goog.a11y.aria.State.REQUIRED,!1,goog.a11y.aria.State.SORT,"none",goog.a11y.aria.State.BUSY,!1,goog.a11y.aria.State.DISABLED,!1,goog.a11y.aria.State.HIDDEN,!1,goog.a11y.aria.State.INVALID,"false"));return goog.a11y.aria.DefaultStateValueMap_};goog.a11y.aria.ARIA_PREFIX_="aria-";goog.a11y.aria.ROLE_ATTRIBUTE_="role";goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_=goog.object.createSet("A AREA BUTTON HEAD INPUT LINK MENU META OPTGROUP OPTION PROGRESS STYLE SELECT SOURCE TEXTAREA TITLE TRACK".split(" "));
+goog.a11y.aria.CONTAINER_ROLES_=[goog.a11y.aria.Role.COMBOBOX,goog.a11y.aria.Role.GRID,goog.a11y.aria.Role.GROUP,goog.a11y.aria.Role.LISTBOX,goog.a11y.aria.Role.MENU,goog.a11y.aria.Role.MENUBAR,goog.a11y.aria.Role.RADIOGROUP,goog.a11y.aria.Role.ROW,goog.a11y.aria.Role.ROWGROUP,goog.a11y.aria.Role.TAB_LIST,goog.a11y.aria.Role.TEXTBOX,goog.a11y.aria.Role.TOOLBAR,goog.a11y.aria.Role.TREE,goog.a11y.aria.Role.TREEGRID];
+goog.a11y.aria.setRole=function(a,b){b?(goog.asserts.ENABLE_ASSERTS&&goog.asserts.assert(goog.object.containsValue(goog.a11y.aria.Role,b),"No such ARIA role "+b),a.setAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_,b)):goog.a11y.aria.removeRole(a)};goog.a11y.aria.getRole=function(a){return a.getAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_)||null};goog.a11y.aria.removeRole=function(a){a.removeAttribute(goog.a11y.aria.ROLE_ATTRIBUTE_)};
+goog.a11y.aria.setState=function(a,b,c){goog.isArray(c)&&(c=c.join(" "));var d=goog.a11y.aria.getAriaAttributeName_(b);""===c||void 0==c?(c=goog.a11y.aria.datatables.getDefaultValuesMap(),b in c?a.setAttribute(d,c[b]):a.removeAttribute(d)):a.setAttribute(d,c)};goog.a11y.aria.toggleState=function(a,b){var c=goog.a11y.aria.getState(a,b);goog.string.isEmptyOrWhitespace(goog.string.makeSafe(c))||"true"==c||"false"==c?goog.a11y.aria.setState(a,b,"true"==c?"false":"true"):goog.a11y.aria.removeState(a,b)};
+goog.a11y.aria.removeState=function(a,b){a.removeAttribute(goog.a11y.aria.getAriaAttributeName_(b))};goog.a11y.aria.getState=function(a,b){var c=a.getAttribute(goog.a11y.aria.getAriaAttributeName_(b));return null==c||void 0==c?"":String(c)};goog.a11y.aria.getActiveDescendant=function(a){var b=goog.a11y.aria.getState(a,goog.a11y.aria.State.ACTIVEDESCENDANT);return goog.dom.getOwnerDocument(a).getElementById(b)};
+goog.a11y.aria.setActiveDescendant=function(a,b){var c="";b&&(c=b.id,goog.asserts.assert(c,"The active element should have an id."));goog.a11y.aria.setState(a,goog.a11y.aria.State.ACTIVEDESCENDANT,c)};goog.a11y.aria.getLabel=function(a){return goog.a11y.aria.getState(a,goog.a11y.aria.State.LABEL)};goog.a11y.aria.setLabel=function(a,b){goog.a11y.aria.setState(a,goog.a11y.aria.State.LABEL,b)};
+goog.a11y.aria.assertRoleIsSetInternalUtil=function(a,b){if(!goog.a11y.aria.TAGS_WITH_ASSUMED_ROLES_[a.tagName]){var c=goog.a11y.aria.getRole(a);goog.asserts.assert(null!=c,"The element ARIA role cannot be null.");goog.asserts.assert(goog.array.contains(b,c),'Non existing or incorrect role set for element.The role set is "'+c+'". The role should be any of "'+b+'". Check the ARIA specification for more details http://www.w3.org/TR/wai-aria/roles.')}};
+goog.a11y.aria.getStateBoolean=function(a,b){var c=a.getAttribute(goog.a11y.aria.getAriaAttributeName_(b));goog.asserts.assert(goog.isBoolean(c)||null==c||"true"==c||"false"==c);return null==c?c:goog.isBoolean(c)?c:"true"==c};goog.a11y.aria.getStateNumber=function(a,b){var c=a.getAttribute(goog.a11y.aria.getAriaAttributeName_(b));goog.asserts.assert((null==c||!isNaN(Number(c)))&&!goog.isBoolean(c));return null==c?null:Number(c)};
+goog.a11y.aria.getStateString=function(a,b){var c=a.getAttribute(goog.a11y.aria.getAriaAttributeName_(b));goog.asserts.assert((null==c||goog.isString(c))&&(""==c||isNaN(Number(c)))&&"true"!=c&&"false"!=c);return null==c||""==c?null:c};goog.a11y.aria.getStringArrayStateInternalUtil=function(a,b){var c=a.getAttribute(goog.a11y.aria.getAriaAttributeName_(b));return goog.a11y.aria.splitStringOnWhitespace_(c)};goog.a11y.aria.hasState=function(a,b){return a.hasAttribute(goog.a11y.aria.getAriaAttributeName_(b))};
+goog.a11y.aria.isContainerRole=function(a){a=goog.a11y.aria.getRole(a);return goog.array.contains(goog.a11y.aria.CONTAINER_ROLES_,a)};goog.a11y.aria.splitStringOnWhitespace_=function(a){return a?a.split(/\s+/):[]};goog.a11y.aria.getAriaAttributeName_=function(a){goog.asserts.ENABLE_ASSERTS&&(goog.asserts.assert(a,"ARIA attribute cannot be empty."),goog.asserts.assert(goog.object.containsValue(goog.a11y.aria.State,a),"No such ARIA attribute "+a));return goog.a11y.aria.ARIA_PREFIX_+a};goog.events.KeyCodes={WIN_KEY_FF_LINUX:0,MAC_ENTER:3,BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PLUS_SIGN:43,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,FF_SEMICOLON:59,FF_EQUALS:61,FF_DASH:173,QUESTION_MARK:63,AT_SIGN:64,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,
+Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,META:91,WIN_KEY_RIGHT:92,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,NUMLOCK:144,SCROLL_LOCK:145,FIRST_MEDIA_KEY:166,LAST_MEDIA_KEY:183,SEMICOLON:186,DASH:189,EQUALS:187,COMMA:188,PERIOD:190,
+SLASH:191,APOSTROPHE:192,TILDE:192,SINGLE_QUOTE:222,OPEN_SQUARE_BRACKET:219,BACKSLASH:220,CLOSE_SQUARE_BRACKET:221,WIN_KEY:224,MAC_FF_META:224,MAC_WK_CMD_LEFT:91,MAC_WK_CMD_RIGHT:93,WIN_IME:229,VK_NONAME:252,PHANTOM:255};
+goog.events.KeyCodes.isTextModifyingKeyEvent=function(a){if(a.altKey&&!a.ctrlKey||a.metaKey||a.keyCode>=goog.events.KeyCodes.F1&&a.keyCode<=goog.events.KeyCodes.F12)return!1;switch(a.keyCode){case goog.events.KeyCodes.ALT:case goog.events.KeyCodes.CAPS_LOCK:case goog.events.KeyCodes.CONTEXT_MENU:case goog.events.KeyCodes.CTRL:case goog.events.KeyCodes.DOWN:case goog.events.KeyCodes.END:case goog.events.KeyCodes.ESC:case goog.events.KeyCodes.HOME:case goog.events.KeyCodes.INSERT:case goog.events.KeyCodes.LEFT:case goog.events.KeyCodes.MAC_FF_META:case goog.events.KeyCodes.META:case goog.events.KeyCodes.NUMLOCK:case goog.events.KeyCodes.NUM_CENTER:case goog.events.KeyCodes.PAGE_DOWN:case goog.events.KeyCodes.PAGE_UP:case goog.events.KeyCodes.PAUSE:case goog.events.KeyCodes.PHANTOM:case goog.events.KeyCodes.PRINT_SCREEN:case goog.events.KeyCodes.RIGHT:case goog.events.KeyCodes.SCROLL_LOCK:case goog.events.KeyCodes.SHIFT:case goog.events.KeyCodes.UP:case goog.events.KeyCodes.VK_NONAME:case goog.events.KeyCodes.WIN_KEY:case goog.events.KeyCodes.WIN_KEY_RIGHT:return!1;case goog.events.KeyCodes.WIN_KEY_FF_LINUX:return!goog.userAgent.GECKO;
+default:return a.keyCode<goog.events.KeyCodes.FIRST_MEDIA_KEY||a.keyCode>goog.events.KeyCodes.LAST_MEDIA_KEY}};
+goog.events.KeyCodes.firesKeyPressEvent=function(a,b,c,d,e,f){if(!(goog.userAgent.IE||goog.userAgent.EDGE||goog.userAgent.WEBKIT&&goog.userAgent.isVersionOrHigher("525")))return!0;if(goog.userAgent.MAC&&e)return goog.events.KeyCodes.isCharacterKey(a);if(e&&!d)return!1;goog.isNumber(b)&&(b=goog.events.KeyCodes.normalizeKeyCode(b));e=b==goog.events.KeyCodes.CTRL||b==goog.events.KeyCodes.ALT||goog.userAgent.MAC&&b==goog.events.KeyCodes.META;f=b==goog.events.KeyCodes.SHIFT&&(d||f);if((!c||goog.userAgent.MAC)&&
+e||goog.userAgent.MAC&&f)return!1;if((goog.userAgent.WEBKIT||goog.userAgent.EDGE)&&d&&c)switch(a){case goog.events.KeyCodes.BACKSLASH:case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:case goog.events.KeyCodes.TILDE:case goog.events.KeyCodes.SEMICOLON:case goog.events.KeyCodes.DASH:case goog.events.KeyCodes.EQUALS:case goog.events.KeyCodes.COMMA:case goog.events.KeyCodes.PERIOD:case goog.events.KeyCodes.SLASH:case goog.events.KeyCodes.APOSTROPHE:case goog.events.KeyCodes.SINGLE_QUOTE:return!1}if(goog.userAgent.IE&&
+d&&b==a)return!1;switch(a){case goog.events.KeyCodes.ENTER:return!0;case goog.events.KeyCodes.ESC:return!(goog.userAgent.WEBKIT||goog.userAgent.EDGE)}return goog.events.KeyCodes.isCharacterKey(a)};
+goog.events.KeyCodes.isCharacterKey=function(a){if(a>=goog.events.KeyCodes.ZERO&&a<=goog.events.KeyCodes.NINE||a>=goog.events.KeyCodes.NUM_ZERO&&a<=goog.events.KeyCodes.NUM_MULTIPLY||a>=goog.events.KeyCodes.A&&a<=goog.events.KeyCodes.Z||(goog.userAgent.WEBKIT||goog.userAgent.EDGE)&&0==a)return!0;switch(a){case goog.events.KeyCodes.SPACE:case goog.events.KeyCodes.PLUS_SIGN:case goog.events.KeyCodes.QUESTION_MARK:case goog.events.KeyCodes.AT_SIGN:case goog.events.KeyCodes.NUM_PLUS:case goog.events.KeyCodes.NUM_MINUS:case goog.events.KeyCodes.NUM_PERIOD:case goog.events.KeyCodes.NUM_DIVISION:case goog.events.KeyCodes.SEMICOLON:case goog.events.KeyCodes.FF_SEMICOLON:case goog.events.KeyCodes.DASH:case goog.events.KeyCodes.EQUALS:case goog.events.KeyCodes.FF_EQUALS:case goog.events.KeyCodes.COMMA:case goog.events.KeyCodes.PERIOD:case goog.events.KeyCodes.SLASH:case goog.events.KeyCodes.APOSTROPHE:case goog.events.KeyCodes.SINGLE_QUOTE:case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:case goog.events.KeyCodes.BACKSLASH:case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:return!0;default:return!1}};
+goog.events.KeyCodes.normalizeKeyCode=function(a){return goog.userAgent.GECKO?goog.events.KeyCodes.normalizeGeckoKeyCode(a):goog.userAgent.MAC&&goog.userAgent.WEBKIT?goog.events.KeyCodes.normalizeMacWebKitKeyCode(a):a};
+goog.events.KeyCodes.normalizeGeckoKeyCode=function(a){switch(a){case goog.events.KeyCodes.FF_EQUALS:return goog.events.KeyCodes.EQUALS;case goog.events.KeyCodes.FF_SEMICOLON:return goog.events.KeyCodes.SEMICOLON;case goog.events.KeyCodes.FF_DASH:return goog.events.KeyCodes.DASH;case goog.events.KeyCodes.MAC_FF_META:return goog.events.KeyCodes.META;case goog.events.KeyCodes.WIN_KEY_FF_LINUX:return goog.events.KeyCodes.WIN_KEY;default:return a}};
+goog.events.KeyCodes.normalizeMacWebKitKeyCode=function(a){switch(a){case goog.events.KeyCodes.MAC_WK_CMD_RIGHT:return goog.events.KeyCodes.META;default:return a}};goog.events.KeyHandler=function(a,b){goog.events.EventTarget.call(this);a&&this.attach(a,b)};goog.inherits(goog.events.KeyHandler,goog.events.EventTarget);goog.events.KeyHandler.prototype.element_=null;goog.events.KeyHandler.prototype.keyPressKey_=null;goog.events.KeyHandler.prototype.keyDownKey_=null;goog.events.KeyHandler.prototype.keyUpKey_=null;goog.events.KeyHandler.prototype.lastKey_=-1;goog.events.KeyHandler.prototype.keyCode_=-1;goog.events.KeyHandler.prototype.altKey_=!1;
+goog.events.KeyHandler.EventType={KEY:"key"};
+goog.events.KeyHandler.safariKey_={3:goog.events.KeyCodes.ENTER,12:goog.events.KeyCodes.NUMLOCK,63232:goog.events.KeyCodes.UP,63233:goog.events.KeyCodes.DOWN,63234:goog.events.KeyCodes.LEFT,63235:goog.events.KeyCodes.RIGHT,63236:goog.events.KeyCodes.F1,63237:goog.events.KeyCodes.F2,63238:goog.events.KeyCodes.F3,63239:goog.events.KeyCodes.F4,63240:goog.events.KeyCodes.F5,63241:goog.events.KeyCodes.F6,63242:goog.events.KeyCodes.F7,63243:goog.events.KeyCodes.F8,63244:goog.events.KeyCodes.F9,63245:goog.events.KeyCodes.F10,
+63246:goog.events.KeyCodes.F11,63247:goog.events.KeyCodes.F12,63248:goog.events.KeyCodes.PRINT_SCREEN,63272:goog.events.KeyCodes.DELETE,63273:goog.events.KeyCodes.HOME,63275:goog.events.KeyCodes.END,63276:goog.events.KeyCodes.PAGE_UP,63277:goog.events.KeyCodes.PAGE_DOWN,63289:goog.events.KeyCodes.NUMLOCK,63302:goog.events.KeyCodes.INSERT};
+goog.events.KeyHandler.keyIdentifier_={Up:goog.events.KeyCodes.UP,Down:goog.events.KeyCodes.DOWN,Left:goog.events.KeyCodes.LEFT,Right:goog.events.KeyCodes.RIGHT,Enter:goog.events.KeyCodes.ENTER,F1:goog.events.KeyCodes.F1,F2:goog.events.KeyCodes.F2,F3:goog.events.KeyCodes.F3,F4:goog.events.KeyCodes.F4,F5:goog.events.KeyCodes.F5,F6:goog.events.KeyCodes.F6,F7:goog.events.KeyCodes.F7,F8:goog.events.KeyCodes.F8,F9:goog.events.KeyCodes.F9,F10:goog.events.KeyCodes.F10,F11:goog.events.KeyCodes.F11,F12:goog.events.KeyCodes.F12,
+"U+007F":goog.events.KeyCodes.DELETE,Home:goog.events.KeyCodes.HOME,End:goog.events.KeyCodes.END,PageUp:goog.events.KeyCodes.PAGE_UP,PageDown:goog.events.KeyCodes.PAGE_DOWN,Insert:goog.events.KeyCodes.INSERT};goog.events.KeyHandler.USES_KEYDOWN_=goog.userAgent.IE||goog.userAgent.EDGE||goog.userAgent.WEBKIT&&goog.userAgent.isVersionOrHigher("525");goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_=goog.userAgent.MAC&&goog.userAgent.GECKO;
+goog.events.KeyHandler.prototype.handleKeyDown_=function(a){(goog.userAgent.WEBKIT||goog.userAgent.EDGE)&&(this.lastKey_==goog.events.KeyCodes.CTRL&&!a.ctrlKey||this.lastKey_==goog.events.KeyCodes.ALT&&!a.altKey||goog.userAgent.MAC&&this.lastKey_==goog.events.KeyCodes.META&&!a.metaKey)&&this.resetState();-1==this.lastKey_&&(a.ctrlKey&&a.keyCode!=goog.events.KeyCodes.CTRL?this.lastKey_=goog.events.KeyCodes.CTRL:a.altKey&&a.keyCode!=goog.events.KeyCodes.ALT?this.lastKey_=goog.events.KeyCodes.ALT:a.metaKey&&
+a.keyCode!=goog.events.KeyCodes.META&&(this.lastKey_=goog.events.KeyCodes.META));goog.events.KeyHandler.USES_KEYDOWN_&&!goog.events.KeyCodes.firesKeyPressEvent(a.keyCode,this.lastKey_,a.shiftKey,a.ctrlKey,a.altKey,a.metaKey)?this.handleEvent(a):(this.keyCode_=goog.events.KeyCodes.normalizeKeyCode(a.keyCode),goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_&&(this.altKey_=a.altKey))};goog.events.KeyHandler.prototype.resetState=function(){this.keyCode_=this.lastKey_=-1};
+goog.events.KeyHandler.prototype.handleKeyup_=function(a){this.resetState();this.altKey_=a.altKey};
+goog.events.KeyHandler.prototype.handleEvent=function(a){var b=a.getBrowserEvent(),c,d,e=b.altKey;goog.userAgent.IE&&a.type==goog.events.EventType.KEYPRESS?(c=this.keyCode_,d=c!=goog.events.KeyCodes.ENTER&&c!=goog.events.KeyCodes.ESC?b.keyCode:0):(goog.userAgent.WEBKIT||goog.userAgent.EDGE)&&a.type==goog.events.EventType.KEYPRESS?(c=this.keyCode_,d=0<=b.charCode&&63232>b.charCode&&goog.events.KeyCodes.isCharacterKey(c)?b.charCode:0):goog.userAgent.OPERA&&!goog.userAgent.WEBKIT?(c=this.keyCode_,d=
+goog.events.KeyCodes.isCharacterKey(c)?b.keyCode:0):(c=b.keyCode||this.keyCode_,d=b.charCode||0,goog.events.KeyHandler.SAVE_ALT_FOR_KEYPRESS_&&(e=this.altKey_),goog.userAgent.MAC&&d==goog.events.KeyCodes.QUESTION_MARK&&c==goog.events.KeyCodes.WIN_KEY&&(c=goog.events.KeyCodes.SLASH));var f=c=goog.events.KeyCodes.normalizeKeyCode(c);c?63232<=c&&c in goog.events.KeyHandler.safariKey_?f=goog.events.KeyHandler.safariKey_[c]:25==c&&a.shiftKey&&(f=9):b.keyIdentifier&&b.keyIdentifier in goog.events.KeyHandler.keyIdentifier_&&
+(f=goog.events.KeyHandler.keyIdentifier_[b.keyIdentifier]);a=f==this.lastKey_;this.lastKey_=f;b=new goog.events.KeyEvent(f,d,a,b);b.altKey=e;this.dispatchEvent(b)};goog.events.KeyHandler.prototype.getElement=function(){return this.element_};
+goog.events.KeyHandler.prototype.attach=function(a,b){this.keyUpKey_&&this.detach();this.element_=a;this.keyPressKey_=goog.events.listen(this.element_,goog.events.EventType.KEYPRESS,this,b);this.keyDownKey_=goog.events.listen(this.element_,goog.events.EventType.KEYDOWN,this.handleKeyDown_,b,this);this.keyUpKey_=goog.events.listen(this.element_,goog.events.EventType.KEYUP,this.handleKeyup_,b,this)};
+goog.events.KeyHandler.prototype.detach=function(){this.keyPressKey_&&(goog.events.unlistenByKey(this.keyPressKey_),goog.events.unlistenByKey(this.keyDownKey_),goog.events.unlistenByKey(this.keyUpKey_),this.keyUpKey_=this.keyDownKey_=this.keyPressKey_=null);this.element_=null;this.keyCode_=this.lastKey_=-1};goog.events.KeyHandler.prototype.disposeInternal=function(){goog.events.KeyHandler.superClass_.disposeInternal.call(this);this.detach()};
+goog.events.KeyEvent=function(a,b,c,d){goog.events.BrowserEvent.call(this,d);this.type=goog.events.KeyHandler.EventType.KEY;this.keyCode=a;this.charCode=b;this.repeat=c};goog.inherits(goog.events.KeyEvent,goog.events.BrowserEvent);goog.dom.classlist={};goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST=!1;goog.dom.classlist.get=function(a){if(goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST||a.classList)return a.classList;a=a.className;return goog.isString(a)&&a.match(/\S+/g)||[]};goog.dom.classlist.set=function(a,b){a.className=b};goog.dom.classlist.contains=function(a,b){return goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST||a.classList?a.classList.contains(b):goog.array.contains(goog.dom.classlist.get(a),b)};
+goog.dom.classlist.add=function(a,b){goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST||a.classList?a.classList.add(b):goog.dom.classlist.contains(a,b)||(a.className+=0<a.className.length?" "+b:b)};
+goog.dom.classlist.addAll=function(a,b){if(goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST||a.classList)goog.array.forEach(b,function(b){goog.dom.classlist.add(a,b)});else{var c={};goog.array.forEach(goog.dom.classlist.get(a),function(a){c[a]=!0});goog.array.forEach(b,function(a){c[a]=!0});a.className="";for(var d in c)a.className+=0<a.className.length?" "+d:d}};
+goog.dom.classlist.remove=function(a,b){goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST||a.classList?a.classList.remove(b):goog.dom.classlist.contains(a,b)&&(a.className=goog.array.filter(goog.dom.classlist.get(a),function(a){return a!=b}).join(" "))};
+goog.dom.classlist.removeAll=function(a,b){goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST||a.classList?goog.array.forEach(b,function(b){goog.dom.classlist.remove(a,b)}):a.className=goog.array.filter(goog.dom.classlist.get(a),function(a){return!goog.array.contains(b,a)}).join(" ")};goog.dom.classlist.enable=function(a,b,c){c?goog.dom.classlist.add(a,b):goog.dom.classlist.remove(a,b)};goog.dom.classlist.enableAll=function(a,b,c){(c?goog.dom.classlist.addAll:goog.dom.classlist.removeAll)(a,b)};
+goog.dom.classlist.swap=function(a,b,c){return goog.dom.classlist.contains(a,b)?(goog.dom.classlist.remove(a,b),goog.dom.classlist.add(a,c),!0):!1};goog.dom.classlist.toggle=function(a,b){var c=!goog.dom.classlist.contains(a,b);goog.dom.classlist.enable(a,b,c);return c};goog.dom.classlist.addRemove=function(a,b,c){goog.dom.classlist.remove(a,b);goog.dom.classlist.add(a,c)};goog.ui.registry={};goog.ui.registry.getDefaultRenderer=function(a){for(var b;a;){b=goog.getUid(a);if(b=goog.ui.registry.defaultRenderers_[b])break;a=a.superClass_?a.superClass_.constructor:null}return b?goog.isFunction(b.getInstance)?b.getInstance():new b:null};
+goog.ui.registry.setDefaultRenderer=function(a,b){if(!goog.isFunction(a))throw Error("Invalid component class "+a);if(!goog.isFunction(b))throw Error("Invalid renderer class "+b);var c=goog.getUid(a);goog.ui.registry.defaultRenderers_[c]=b};goog.ui.registry.getDecoratorByClassName=function(a){return a in goog.ui.registry.decoratorFunctions_?goog.ui.registry.decoratorFunctions_[a]():null};
+goog.ui.registry.setDecoratorByClassName=function(a,b){if(!a)throw Error("Invalid class name "+a);if(!goog.isFunction(b))throw Error("Invalid decorator function "+b);goog.ui.registry.decoratorFunctions_[a]=b};goog.ui.registry.getDecorator=function(a){goog.asserts.assert(a);for(var b=goog.dom.classlist.get(a),c=0,d=b.length;c<d;c++)if(a=goog.ui.registry.getDecoratorByClassName(b[c]))return a;return null};
+goog.ui.registry.reset=function(){goog.ui.registry.defaultRenderers_={};goog.ui.registry.decoratorFunctions_={}};goog.ui.registry.defaultRenderers_={};goog.ui.registry.decoratorFunctions_={};goog.ui.ContainerRenderer=function(a){this.ariaRole_=a};goog.addSingletonGetter(goog.ui.ContainerRenderer);goog.ui.ContainerRenderer.getCustomRenderer=function(a,b){var c=new a;c.getCssClass=function(){return b};return c};goog.ui.ContainerRenderer.CSS_CLASS="goog-container";goog.ui.ContainerRenderer.prototype.getAriaRole=function(){return this.ariaRole_};goog.ui.ContainerRenderer.prototype.enableTabIndex=function(a,b){a&&(a.tabIndex=b?0:-1)};
+goog.ui.ContainerRenderer.prototype.createDom=function(a){return a.getDomHelper().createDom("DIV",this.getClassNames(a).join(" "))};goog.ui.ContainerRenderer.prototype.getContentElement=function(a){return a};goog.ui.ContainerRenderer.prototype.canDecorate=function(a){return"DIV"==a.tagName};
+goog.ui.ContainerRenderer.prototype.decorate=function(a,b){b.id&&a.setId(b.id);var c=this.getCssClass(),d=!1,e=goog.dom.classlist.get(b);e&&goog.array.forEach(e,function(b){b==c?d=!0:b&&this.setStateFromClassName(a,b,c)},this);d||goog.dom.classlist.add(b,c);this.decorateChildren(a,this.getContentElement(b));return b};
+goog.ui.ContainerRenderer.prototype.setStateFromClassName=function(a,b,c){b==c+"-disabled"?a.setEnabled(!1):b==c+"-horizontal"?a.setOrientation(goog.ui.Container.Orientation.HORIZONTAL):b==c+"-vertical"&&a.setOrientation(goog.ui.Container.Orientation.VERTICAL)};
+goog.ui.ContainerRenderer.prototype.decorateChildren=function(a,b,c){if(b){c=c||b.firstChild;for(var d;c&&c.parentNode==b;){d=c.nextSibling;if(c.nodeType==goog.dom.NodeType.ELEMENT){var e=this.getDecoratorForChild(c);e&&(e.setElementInternal(c),a.isEnabled()||e.setEnabled(!1),a.addChild(e),e.decorate(c))}else c.nodeValue&&""!=goog.string.trim(c.nodeValue)||b.removeChild(c);c=d}}};goog.ui.ContainerRenderer.prototype.getDecoratorForChild=function(a){return goog.ui.registry.getDecorator(a)};
+goog.ui.ContainerRenderer.prototype.initializeDom=function(a){a=a.getElement();goog.asserts.assert(a,"The container DOM element cannot be null.");goog.style.setUnselectable(a,!0,goog.userAgent.GECKO);goog.userAgent.IE&&(a.hideFocus=!0);var b=this.getAriaRole();b&&goog.a11y.aria.setRole(a,b)};goog.ui.ContainerRenderer.prototype.getKeyEventTarget=function(a){return a.getElement()};goog.ui.ContainerRenderer.prototype.getCssClass=function(){return goog.ui.ContainerRenderer.CSS_CLASS};
+goog.ui.ContainerRenderer.prototype.getClassNames=function(a){var b=this.getCssClass(),c=a.getOrientation()==goog.ui.Container.Orientation.HORIZONTAL,c=[b,c?b+"-horizontal":b+"-vertical"];a.isEnabled()||c.push(b+"-disabled");return c};goog.ui.ContainerRenderer.prototype.getDefaultOrientation=function(){return goog.ui.Container.Orientation.VERTICAL};goog.ui.ControlRenderer=function(){};goog.addSingletonGetter(goog.ui.ControlRenderer);goog.tagUnsealableClass(goog.ui.ControlRenderer);goog.ui.ControlRenderer.getCustomRenderer=function(a,b){var c=new a;c.getCssClass=function(){return b};return c};goog.ui.ControlRenderer.CSS_CLASS="goog-control";goog.ui.ControlRenderer.IE6_CLASS_COMBINATIONS=[];
+goog.ui.ControlRenderer.TOGGLE_ARIA_STATE_MAP_=goog.object.create(goog.a11y.aria.Role.BUTTON,goog.a11y.aria.State.PRESSED,goog.a11y.aria.Role.CHECKBOX,goog.a11y.aria.State.CHECKED,goog.a11y.aria.Role.MENU_ITEM,goog.a11y.aria.State.SELECTED,goog.a11y.aria.Role.MENU_ITEM_CHECKBOX,goog.a11y.aria.State.CHECKED,goog.a11y.aria.Role.MENU_ITEM_RADIO,goog.a11y.aria.State.CHECKED,goog.a11y.aria.Role.RADIO,goog.a11y.aria.State.CHECKED,goog.a11y.aria.Role.TAB,goog.a11y.aria.State.SELECTED,goog.a11y.aria.Role.TREEITEM,
+goog.a11y.aria.State.SELECTED);goog.ui.ControlRenderer.prototype.getAriaRole=function(){};goog.ui.ControlRenderer.prototype.createDom=function(a){return a.getDomHelper().createDom("DIV",this.getClassNames(a).join(" "),a.getContent())};goog.ui.ControlRenderer.prototype.getContentElement=function(a){return a};
+goog.ui.ControlRenderer.prototype.enableClassName=function(a,b,c){if(a=a.getElement?a.getElement():a){var d=[b];goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("7")&&(d=this.getAppliedCombinedClassNames_(goog.dom.classlist.get(a),b),d.push(b));goog.dom.classlist.enableAll(a,d,c)}};goog.ui.ControlRenderer.prototype.enableExtraClassName=function(a,b,c){this.enableClassName(a,b,c)};goog.ui.ControlRenderer.prototype.canDecorate=function(a){return!0};
+goog.ui.ControlRenderer.prototype.decorate=function(a,b){b.id&&a.setId(b.id);var c=this.getContentElement(b);c&&c.firstChild?a.setContentInternal(c.firstChild.nextSibling?goog.array.clone(c.childNodes):c.firstChild):a.setContentInternal(null);var d=0,e=this.getCssClass(),f=this.getStructuralCssClass(),g=!1,h=!1,k=!1,l=goog.array.toArray(goog.dom.classlist.get(b));goog.array.forEach(l,function(a){g||a!=e?h||a!=f?d|=this.getStateFromClass(a):h=!0:(g=!0,f==e&&(h=!0));this.getStateFromClass(a)==goog.ui.Component.State.DISABLED&&
+(goog.asserts.assertElement(c),goog.dom.isFocusableTabIndex(c)&&goog.dom.setFocusableTabIndex(c,!1))},this);a.setStateInternal(d);g||(l.push(e),f==e&&(h=!0));h||l.push(f);var p=a.getExtraClassNames();p&&l.push.apply(l,p);if(goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("7")){var m=this.getAppliedCombinedClassNames_(l);0<m.length&&(l.push.apply(l,m),k=!0)}g&&h&&!p&&!k||goog.dom.classlist.set(b,l.join(" "));return b};
+goog.ui.ControlRenderer.prototype.initializeDom=function(a){a.isRightToLeft()&&this.setRightToLeft(a.getElement(),!0);a.isEnabled()&&this.setFocusable(a,a.isVisible())};goog.ui.ControlRenderer.prototype.setAriaRole=function(a,b){var c=b||this.getAriaRole();if(c){goog.asserts.assert(a,"The element passed as a first parameter cannot be null.");var d=goog.a11y.aria.getRole(a);c!=d&&goog.a11y.aria.setRole(a,c)}};
+goog.ui.ControlRenderer.prototype.setAriaStates=function(a,b){goog.asserts.assert(a);goog.asserts.assert(b);var c=a.getAriaLabel();goog.isDefAndNotNull(c)&&this.setAriaLabel(b,c);a.isVisible()||goog.a11y.aria.setState(b,goog.a11y.aria.State.HIDDEN,!a.isVisible());a.isEnabled()||this.updateAriaState(b,goog.ui.Component.State.DISABLED,!a.isEnabled());a.isSupportedState(goog.ui.Component.State.SELECTED)&&this.updateAriaState(b,goog.ui.Component.State.SELECTED,a.isSelected());a.isSupportedState(goog.ui.Component.State.CHECKED)&&
+this.updateAriaState(b,goog.ui.Component.State.CHECKED,a.isChecked());a.isSupportedState(goog.ui.Component.State.OPENED)&&this.updateAriaState(b,goog.ui.Component.State.OPENED,a.isOpen())};goog.ui.ControlRenderer.prototype.setAriaLabel=function(a,b){goog.a11y.aria.setLabel(a,b)};goog.ui.ControlRenderer.prototype.setAllowTextSelection=function(a,b){goog.style.setUnselectable(a,!b,!goog.userAgent.IE&&!goog.userAgent.OPERA)};
+goog.ui.ControlRenderer.prototype.setRightToLeft=function(a,b){this.enableClassName(a,this.getStructuralCssClass()+"-rtl",b)};goog.ui.ControlRenderer.prototype.isFocusable=function(a){var b;return a.isSupportedState(goog.ui.Component.State.FOCUSED)&&(b=a.getKeyEventTarget())?goog.dom.isFocusableTabIndex(b):!1};
+goog.ui.ControlRenderer.prototype.setFocusable=function(a,b){var c;if(a.isSupportedState(goog.ui.Component.State.FOCUSED)&&(c=a.getKeyEventTarget())){if(!b&&a.isFocused()){try{c.blur()}catch(d){}a.isFocused()&&a.handleBlur(null)}goog.dom.isFocusableTabIndex(c)!=b&&goog.dom.setFocusableTabIndex(c,b)}};goog.ui.ControlRenderer.prototype.setVisible=function(a,b){goog.style.setElementShown(a,b);a&&goog.a11y.aria.setState(a,goog.a11y.aria.State.HIDDEN,!b)};
+goog.ui.ControlRenderer.prototype.setState=function(a,b,c){var d=a.getElement();if(d){var e=this.getClassForState(b);e&&this.enableClassName(a,e,c);this.updateAriaState(d,b,c)}};
+goog.ui.ControlRenderer.prototype.updateAriaState=function(a,b,c){goog.ui.ControlRenderer.ariaAttributeMap_||(goog.ui.ControlRenderer.ariaAttributeMap_=goog.object.create(goog.ui.Component.State.DISABLED,goog.a11y.aria.State.DISABLED,goog.ui.Component.State.SELECTED,goog.a11y.aria.State.SELECTED,goog.ui.Component.State.CHECKED,goog.a11y.aria.State.CHECKED,goog.ui.Component.State.OPENED,goog.a11y.aria.State.EXPANDED));goog.asserts.assert(a,"The element passed as a first parameter cannot be null.");
+(b=goog.ui.ControlRenderer.getAriaStateForAriaRole_(a,goog.ui.ControlRenderer.ariaAttributeMap_[b]))&&goog.a11y.aria.setState(a,b,c)};goog.ui.ControlRenderer.getAriaStateForAriaRole_=function(a,b){var c=goog.a11y.aria.getRole(a);if(!c)return b;c=goog.ui.ControlRenderer.TOGGLE_ARIA_STATE_MAP_[c]||b;return goog.ui.ControlRenderer.isAriaState_(b)?c:b};goog.ui.ControlRenderer.isAriaState_=function(a){return a==goog.a11y.aria.State.CHECKED||a==goog.a11y.aria.State.SELECTED};
+goog.ui.ControlRenderer.prototype.setContent=function(a,b){var c=this.getContentElement(a);if(c&&(goog.dom.removeChildren(c),b))if(goog.isString(b))goog.dom.setTextContent(c,b);else{var d=function(a){if(a){var b=goog.dom.getOwnerDocument(c);c.appendChild(goog.isString(a)?b.createTextNode(a):a)}};goog.isArray(b)?goog.array.forEach(b,d):!goog.isArrayLike(b)||"nodeType"in b?d(b):goog.array.forEach(goog.array.clone(b),d)}};goog.ui.ControlRenderer.prototype.getKeyEventTarget=function(a){return a.getElement()};
+goog.ui.ControlRenderer.prototype.getCssClass=function(){return goog.ui.ControlRenderer.CSS_CLASS};goog.ui.ControlRenderer.prototype.getIe6ClassCombinations=function(){return[]};goog.ui.ControlRenderer.prototype.getStructuralCssClass=function(){return this.getCssClass()};
+goog.ui.ControlRenderer.prototype.getClassNames=function(a){var b=this.getCssClass(),c=[b],d=this.getStructuralCssClass();d!=b&&c.push(d);b=this.getClassNamesForState(a.getState());c.push.apply(c,b);(a=a.getExtraClassNames())&&c.push.apply(c,a);goog.userAgent.IE&&!goog.userAgent.isVersionOrHigher("7")&&c.push.apply(c,this.getAppliedCombinedClassNames_(c));return c};
+goog.ui.ControlRenderer.prototype.getAppliedCombinedClassNames_=function(a,b){var c=[];b&&(a=goog.array.concat(a,[b]));goog.array.forEach(this.getIe6ClassCombinations(),function(d){!goog.array.every(d,goog.partial(goog.array.contains,a))||b&&!goog.array.contains(d,b)||c.push(d.join("_"))});return c};goog.ui.ControlRenderer.prototype.getClassNamesForState=function(a){for(var b=[];a;){var c=a&-a;b.push(this.getClassForState(c));a&=~c}return b};
+goog.ui.ControlRenderer.prototype.getClassForState=function(a){this.classByState_||this.createClassByStateMap_();return this.classByState_[a]};goog.ui.ControlRenderer.prototype.getStateFromClass=function(a){this.stateByClass_||this.createStateByClassMap_();a=parseInt(this.stateByClass_[a],10);return isNaN(a)?0:a};
+goog.ui.ControlRenderer.prototype.createClassByStateMap_=function(){var a=this.getStructuralCssClass(),b=!goog.string.contains(goog.string.normalizeWhitespace(a)," ");goog.asserts.assert(b,"ControlRenderer has an invalid css class: '"+a+"'");this.classByState_=goog.object.create(goog.ui.Component.State.DISABLED,a+"-disabled",goog.ui.Component.State.HOVER,a+"-hover",goog.ui.Component.State.ACTIVE,a+"-active",goog.ui.Component.State.SELECTED,a+"-selected",goog.ui.Component.State.CHECKED,a+"-checked",
+goog.ui.Component.State.FOCUSED,a+"-focused",goog.ui.Component.State.OPENED,a+"-open")};goog.ui.ControlRenderer.prototype.createStateByClassMap_=function(){this.classByState_||this.createClassByStateMap_();this.stateByClass_=goog.object.transpose(this.classByState_)};goog.ui.Control=function(a,b,c){goog.ui.Component.call(this,c);this.renderer_=b||goog.ui.registry.getDefaultRenderer(this.constructor);this.setContentInternal(goog.isDef(a)?a:null);this.ariaLabel_=null};goog.inherits(goog.ui.Control,goog.ui.Component);goog.tagUnsealableClass(goog.ui.Control);goog.ui.Control.registerDecorator=goog.ui.registry.setDecoratorByClassName;goog.ui.Control.getDecorator=goog.ui.registry.getDecorator;goog.ui.Control.prototype.content_=null;goog.ui.Control.prototype.state_=0;
+goog.ui.Control.prototype.supportedStates_=goog.ui.Component.State.DISABLED|goog.ui.Component.State.HOVER|goog.ui.Component.State.ACTIVE|goog.ui.Component.State.FOCUSED;goog.ui.Control.prototype.autoStates_=goog.ui.Component.State.ALL;goog.ui.Control.prototype.statesWithTransitionEvents_=0;goog.ui.Control.prototype.visible_=!0;goog.ui.Control.prototype.extraClassNames_=null;goog.ui.Control.prototype.handleMouseEvents_=!0;goog.ui.Control.prototype.allowTextSelection_=!1;
+goog.ui.Control.prototype.preferredAriaRole_=null;goog.ui.Control.prototype.isHandleMouseEvents=function(){return this.handleMouseEvents_};goog.ui.Control.prototype.setHandleMouseEvents=function(a){this.isInDocument()&&a!=this.handleMouseEvents_&&this.enableMouseEventHandling_(a);this.handleMouseEvents_=a};goog.ui.Control.prototype.getKeyEventTarget=function(){return this.renderer_.getKeyEventTarget(this)};
+goog.ui.Control.prototype.getKeyHandler=function(){return this.keyHandler_||(this.keyHandler_=new goog.events.KeyHandler)};goog.ui.Control.prototype.getRenderer=function(){return this.renderer_};goog.ui.Control.prototype.setRenderer=function(a){if(this.isInDocument())throw Error(goog.ui.Component.Error.ALREADY_RENDERED);this.getElement()&&this.setElementInternal(null);this.renderer_=a};goog.ui.Control.prototype.getExtraClassNames=function(){return this.extraClassNames_};
+goog.ui.Control.prototype.addClassName=function(a){a&&(this.extraClassNames_?goog.array.contains(this.extraClassNames_,a)||this.extraClassNames_.push(a):this.extraClassNames_=[a],this.renderer_.enableExtraClassName(this,a,!0))};goog.ui.Control.prototype.removeClassName=function(a){a&&this.extraClassNames_&&goog.array.remove(this.extraClassNames_,a)&&(0==this.extraClassNames_.length&&(this.extraClassNames_=null),this.renderer_.enableExtraClassName(this,a,!1))};
+goog.ui.Control.prototype.enableClassName=function(a,b){b?this.addClassName(a):this.removeClassName(a)};goog.ui.Control.prototype.createDom=function(){var a=this.renderer_.createDom(this);this.setElementInternal(a);this.renderer_.setAriaRole(a,this.getPreferredAriaRole());this.isAllowTextSelection()||this.renderer_.setAllowTextSelection(a,!1);this.isVisible()||this.renderer_.setVisible(a,!1)};goog.ui.Control.prototype.getPreferredAriaRole=function(){return this.preferredAriaRole_};
+goog.ui.Control.prototype.setPreferredAriaRole=function(a){this.preferredAriaRole_=a};goog.ui.Control.prototype.getAriaLabel=function(){return this.ariaLabel_};goog.ui.Control.prototype.setAriaLabel=function(a){this.ariaLabel_=a;var b=this.getElement();b&&this.renderer_.setAriaLabel(b,a)};goog.ui.Control.prototype.getContentElement=function(){return this.renderer_.getContentElement(this.getElement())};goog.ui.Control.prototype.canDecorate=function(a){return this.renderer_.canDecorate(a)};
+goog.ui.Control.prototype.decorateInternal=function(a){a=this.renderer_.decorate(this,a);this.setElementInternal(a);this.renderer_.setAriaRole(a,this.getPreferredAriaRole());this.isAllowTextSelection()||this.renderer_.setAllowTextSelection(a,!1);this.visible_="none"!=a.style.display};
+goog.ui.Control.prototype.enterDocument=function(){goog.ui.Control.superClass_.enterDocument.call(this);this.renderer_.setAriaStates(this,this.getElementStrict());this.renderer_.initializeDom(this);if(this.supportedStates_&~goog.ui.Component.State.DISABLED&&(this.isHandleMouseEvents()&&this.enableMouseEventHandling_(!0),this.isSupportedState(goog.ui.Component.State.FOCUSED))){var a=this.getKeyEventTarget();if(a){var b=this.getKeyHandler();b.attach(a);this.getHandler().listen(b,goog.events.KeyHandler.EventType.KEY,
+this.handleKeyEvent).listen(a,goog.events.EventType.FOCUS,this.handleFocus).listen(a,goog.events.EventType.BLUR,this.handleBlur)}}};
+goog.ui.Control.prototype.enableMouseEventHandling_=function(a){var b=this.getHandler(),c=this.getElement();a?(b.listen(c,goog.events.EventType.MOUSEOVER,this.handleMouseOver).listen(c,goog.events.EventType.MOUSEDOWN,this.handleMouseDown).listen(c,goog.events.EventType.MOUSEUP,this.handleMouseUp).listen(c,goog.events.EventType.MOUSEOUT,this.handleMouseOut),this.handleContextMenu!=goog.nullFunction&&b.listen(c,goog.events.EventType.CONTEXTMENU,this.handleContextMenu),goog.userAgent.IE&&(goog.userAgent.isVersionOrHigher(9)||
+b.listen(c,goog.events.EventType.DBLCLICK,this.handleDblClick),this.ieMouseEventSequenceSimulator_||(this.ieMouseEventSequenceSimulator_=new goog.ui.Control.IeMouseEventSequenceSimulator_(this),this.registerDisposable(this.ieMouseEventSequenceSimulator_)))):(b.unlisten(c,goog.events.EventType.MOUSEOVER,this.handleMouseOver).unlisten(c,goog.events.EventType.MOUSEDOWN,this.handleMouseDown).unlisten(c,goog.events.EventType.MOUSEUP,this.handleMouseUp).unlisten(c,goog.events.EventType.MOUSEOUT,this.handleMouseOut),
+this.handleContextMenu!=goog.nullFunction&&b.unlisten(c,goog.events.EventType.CONTEXTMENU,this.handleContextMenu),goog.userAgent.IE&&(goog.userAgent.isVersionOrHigher(9)||b.unlisten(c,goog.events.EventType.DBLCLICK,this.handleDblClick),goog.dispose(this.ieMouseEventSequenceSimulator_),this.ieMouseEventSequenceSimulator_=null))};
+goog.ui.Control.prototype.exitDocument=function(){goog.ui.Control.superClass_.exitDocument.call(this);this.keyHandler_&&this.keyHandler_.detach();this.isVisible()&&this.isEnabled()&&this.renderer_.setFocusable(this,!1)};goog.ui.Control.prototype.disposeInternal=function(){goog.ui.Control.superClass_.disposeInternal.call(this);this.keyHandler_&&(this.keyHandler_.dispose(),delete this.keyHandler_);delete this.renderer_;this.ieMouseEventSequenceSimulator_=this.extraClassNames_=this.content_=null};
+goog.ui.Control.prototype.getContent=function(){return this.content_};goog.ui.Control.prototype.setContent=function(a){this.renderer_.setContent(this.getElement(),a);this.setContentInternal(a)};goog.ui.Control.prototype.setContentInternal=function(a){this.content_=a};goog.ui.Control.prototype.getCaption=function(){var a=this.getContent();if(!a)return"";a=goog.isString(a)?a:goog.isArray(a)?goog.array.map(a,goog.dom.getRawTextContent).join(""):goog.dom.getTextContent(a);return goog.string.collapseBreakingSpaces(a)};
+goog.ui.Control.prototype.setCaption=function(a){this.setContent(a)};goog.ui.Control.prototype.setRightToLeft=function(a){goog.ui.Control.superClass_.setRightToLeft.call(this,a);var b=this.getElement();b&&this.renderer_.setRightToLeft(b,a)};goog.ui.Control.prototype.isAllowTextSelection=function(){return this.allowTextSelection_};goog.ui.Control.prototype.setAllowTextSelection=function(a){this.allowTextSelection_=a;var b=this.getElement();b&&this.renderer_.setAllowTextSelection(b,a)};
+goog.ui.Control.prototype.isVisible=function(){return this.visible_};goog.ui.Control.prototype.setVisible=function(a,b){if(b||this.visible_!=a&&this.dispatchEvent(a?goog.ui.Component.EventType.SHOW:goog.ui.Component.EventType.HIDE)){var c=this.getElement();c&&this.renderer_.setVisible(c,a);this.isEnabled()&&this.renderer_.setFocusable(this,a);this.visible_=a;return!0}return!1};goog.ui.Control.prototype.isEnabled=function(){return!this.hasState(goog.ui.Component.State.DISABLED)};
+goog.ui.Control.prototype.isParentDisabled_=function(){var a=this.getParent();return!!a&&"function"==typeof a.isEnabled&&!a.isEnabled()};goog.ui.Control.prototype.setEnabled=function(a){!this.isParentDisabled_()&&this.isTransitionAllowed(goog.ui.Component.State.DISABLED,!a)&&(a||(this.setActive(!1),this.setHighlighted(!1)),this.isVisible()&&this.renderer_.setFocusable(this,a),this.setState(goog.ui.Component.State.DISABLED,!a,!0))};goog.ui.Control.prototype.isHighlighted=function(){return this.hasState(goog.ui.Component.State.HOVER)};
+goog.ui.Control.prototype.setHighlighted=function(a){this.isTransitionAllowed(goog.ui.Component.State.HOVER,a)&&this.setState(goog.ui.Component.State.HOVER,a)};goog.ui.Control.prototype.isActive=function(){return this.hasState(goog.ui.Component.State.ACTIVE)};goog.ui.Control.prototype.setActive=function(a){this.isTransitionAllowed(goog.ui.Component.State.ACTIVE,a)&&this.setState(goog.ui.Component.State.ACTIVE,a)};goog.ui.Control.prototype.isSelected=function(){return this.hasState(goog.ui.Component.State.SELECTED)};
+goog.ui.Control.prototype.setSelected=function(a){this.isTransitionAllowed(goog.ui.Component.State.SELECTED,a)&&this.setState(goog.ui.Component.State.SELECTED,a)};goog.ui.Control.prototype.isChecked=function(){return this.hasState(goog.ui.Component.State.CHECKED)};goog.ui.Control.prototype.setChecked=function(a){this.isTransitionAllowed(goog.ui.Component.State.CHECKED,a)&&this.setState(goog.ui.Component.State.CHECKED,a)};goog.ui.Control.prototype.isFocused=function(){return this.hasState(goog.ui.Component.State.FOCUSED)};
+goog.ui.Control.prototype.setFocused=function(a){this.isTransitionAllowed(goog.ui.Component.State.FOCUSED,a)&&this.setState(goog.ui.Component.State.FOCUSED,a)};goog.ui.Control.prototype.isOpen=function(){return this.hasState(goog.ui.Component.State.OPENED)};goog.ui.Control.prototype.setOpen=function(a){this.isTransitionAllowed(goog.ui.Component.State.OPENED,a)&&this.setState(goog.ui.Component.State.OPENED,a)};goog.ui.Control.prototype.getState=function(){return this.state_};
+goog.ui.Control.prototype.hasState=function(a){return!!(this.state_&a)};goog.ui.Control.prototype.setState=function(a,b,c){c||a!=goog.ui.Component.State.DISABLED?this.isSupportedState(a)&&b!=this.hasState(a)&&(this.renderer_.setState(this,a,b),this.state_=b?this.state_|a:this.state_&~a):this.setEnabled(!b)};goog.ui.Control.prototype.setStateInternal=function(a){this.state_=a};goog.ui.Control.prototype.isSupportedState=function(a){return!!(this.supportedStates_&a)};
+goog.ui.Control.prototype.setSupportedState=function(a,b){if(this.isInDocument()&&this.hasState(a)&&!b)throw Error(goog.ui.Component.Error.ALREADY_RENDERED);!b&&this.hasState(a)&&this.setState(a,!1);this.supportedStates_=b?this.supportedStates_|a:this.supportedStates_&~a};goog.ui.Control.prototype.isAutoState=function(a){return!!(this.autoStates_&a)&&this.isSupportedState(a)};goog.ui.Control.prototype.setAutoStates=function(a,b){this.autoStates_=b?this.autoStates_|a:this.autoStates_&~a};
+goog.ui.Control.prototype.isDispatchTransitionEvents=function(a){return!!(this.statesWithTransitionEvents_&a)&&this.isSupportedState(a)};goog.ui.Control.prototype.setDispatchTransitionEvents=function(a,b){this.statesWithTransitionEvents_=b?this.statesWithTransitionEvents_|a:this.statesWithTransitionEvents_&~a};
+goog.ui.Control.prototype.isTransitionAllowed=function(a,b){return this.isSupportedState(a)&&this.hasState(a)!=b&&(!(this.statesWithTransitionEvents_&a)||this.dispatchEvent(goog.ui.Component.getStateTransitionEvent(a,b)))&&!this.isDisposed()};goog.ui.Control.prototype.handleMouseOver=function(a){!goog.ui.Control.isMouseEventWithinElement_(a,this.getElement())&&this.dispatchEvent(goog.ui.Component.EventType.ENTER)&&this.isEnabled()&&this.isAutoState(goog.ui.Component.State.HOVER)&&this.setHighlighted(!0)};
+goog.ui.Control.prototype.handleMouseOut=function(a){!goog.ui.Control.isMouseEventWithinElement_(a,this.getElement())&&this.dispatchEvent(goog.ui.Component.EventType.LEAVE)&&(this.isAutoState(goog.ui.Component.State.ACTIVE)&&this.setActive(!1),this.isAutoState(goog.ui.Component.State.HOVER)&&this.setHighlighted(!1))};goog.ui.Control.prototype.handleContextMenu=goog.nullFunction;goog.ui.Control.isMouseEventWithinElement_=function(a,b){return!!a.relatedTarget&&goog.dom.contains(b,a.relatedTarget)};
+goog.ui.Control.prototype.handleMouseDown=function(a){this.isEnabled()&&(this.isAutoState(goog.ui.Component.State.HOVER)&&this.setHighlighted(!0),a.isMouseActionButton()&&(this.isAutoState(goog.ui.Component.State.ACTIVE)&&this.setActive(!0),this.renderer_&&this.renderer_.isFocusable(this)&&this.getKeyEventTarget().focus()));!this.isAllowTextSelection()&&a.isMouseActionButton()&&a.preventDefault()};
+goog.ui.Control.prototype.handleMouseUp=function(a){this.isEnabled()&&(this.isAutoState(goog.ui.Component.State.HOVER)&&this.setHighlighted(!0),this.isActive()&&this.performActionInternal(a)&&this.isAutoState(goog.ui.Component.State.ACTIVE)&&this.setActive(!1))};goog.ui.Control.prototype.handleDblClick=function(a){this.isEnabled()&&this.performActionInternal(a)};
+goog.ui.Control.prototype.performActionInternal=function(a){this.isAutoState(goog.ui.Component.State.CHECKED)&&this.setChecked(!this.isChecked());this.isAutoState(goog.ui.Component.State.SELECTED)&&this.setSelected(!0);this.isAutoState(goog.ui.Component.State.OPENED)&&this.setOpen(!this.isOpen());var b=new goog.events.Event(goog.ui.Component.EventType.ACTION,this);a&&(b.altKey=a.altKey,b.ctrlKey=a.ctrlKey,b.metaKey=a.metaKey,b.shiftKey=a.shiftKey,b.platformModifierKey=a.platformModifierKey);return this.dispatchEvent(b)};
+goog.ui.Control.prototype.handleFocus=function(a){this.isAutoState(goog.ui.Component.State.FOCUSED)&&this.setFocused(!0)};goog.ui.Control.prototype.handleBlur=function(a){this.isAutoState(goog.ui.Component.State.ACTIVE)&&this.setActive(!1);this.isAutoState(goog.ui.Component.State.FOCUSED)&&this.setFocused(!1)};goog.ui.Control.prototype.handleKeyEvent=function(a){return this.isVisible()&&this.isEnabled()&&this.handleKeyEventInternal(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};
+goog.ui.Control.prototype.handleKeyEventInternal=function(a){return a.keyCode==goog.events.KeyCodes.ENTER&&this.performActionInternal(a)};goog.ui.registry.setDefaultRenderer(goog.ui.Control,goog.ui.ControlRenderer);goog.ui.registry.setDecoratorByClassName(goog.ui.ControlRenderer.CSS_CLASS,function(){return new goog.ui.Control(null)});
+goog.ui.Control.IeMouseEventSequenceSimulator_=function(a){goog.Disposable.call(this);this.control_=a;this.clickExpected_=!1;this.handler_=new goog.events.EventHandler(this);this.registerDisposable(this.handler_);a=this.control_.getElementStrict();this.handler_.listen(a,goog.events.EventType.MOUSEDOWN,this.handleMouseDown_).listen(a,goog.events.EventType.MOUSEUP,this.handleMouseUp_).listen(a,goog.events.EventType.CLICK,this.handleClick_)};
+goog.inherits(goog.ui.Control.IeMouseEventSequenceSimulator_,goog.Disposable);goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_=!goog.userAgent.IE||goog.userAgent.isDocumentModeOrHigher(9);goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseDown_=function(){this.clickExpected_=!1};goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleMouseUp_=function(){this.clickExpected_=!0};
+goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_=function(a,b){if(!goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_)return a.button=goog.events.BrowserEvent.MouseButton.LEFT,a.type=b,a;var c=document.createEvent("MouseEvents");c.initMouseEvent(b,a.bubbles,a.cancelable,a.view||null,a.detail,a.screenX,a.screenY,a.clientX,a.clientY,a.ctrlKey,a.altKey,a.shiftKey,a.metaKey,goog.events.BrowserEvent.MouseButton.LEFT,a.relatedTarget||null);return c};
+goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.handleClick_=function(a){if(this.clickExpected_)this.clickExpected_=!1;else{var b=a.getBrowserEvent(),c=b.button,d=b.type,e=goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_(b,goog.events.EventType.MOUSEDOWN);this.control_.handleMouseDown(new goog.events.BrowserEvent(e,a.currentTarget));e=goog.ui.Control.IeMouseEventSequenceSimulator_.makeLeftMouseEvent_(b,goog.events.EventType.MOUSEUP);this.control_.handleMouseUp(new goog.events.BrowserEvent(e,
+a.currentTarget));goog.ui.Control.IeMouseEventSequenceSimulator_.SYNTHETIC_EVENTS_||(b.button=c,b.type=d)}};goog.ui.Control.IeMouseEventSequenceSimulator_.prototype.disposeInternal=function(){this.control_=null;goog.ui.Control.IeMouseEventSequenceSimulator_.superClass_.disposeInternal.call(this)};goog.ui.Container=function(a,b,c){goog.ui.Component.call(this,c);this.renderer_=b||goog.ui.ContainerRenderer.getInstance();this.orientation_=a||this.renderer_.getDefaultOrientation()};goog.inherits(goog.ui.Container,goog.ui.Component);goog.tagUnsealableClass(goog.ui.Container);goog.ui.Container.EventType={AFTER_SHOW:"aftershow",AFTER_HIDE:"afterhide"};goog.ui.Container.Orientation={HORIZONTAL:"horizontal",VERTICAL:"vertical"};goog.ui.Container.prototype.keyEventTarget_=null;
+goog.ui.Container.prototype.keyHandler_=null;goog.ui.Container.prototype.renderer_=null;goog.ui.Container.prototype.orientation_=null;goog.ui.Container.prototype.visible_=!0;goog.ui.Container.prototype.enabled_=!0;goog.ui.Container.prototype.focusable_=!0;goog.ui.Container.prototype.highlightedIndex_=-1;goog.ui.Container.prototype.openItem_=null;goog.ui.Container.prototype.mouseButtonPressed_=!1;goog.ui.Container.prototype.allowFocusableChildren_=!1;
+goog.ui.Container.prototype.openFollowsHighlight_=!0;goog.ui.Container.prototype.childElementIdMap_=null;goog.ui.Container.prototype.getKeyEventTarget=function(){return this.keyEventTarget_||this.renderer_.getKeyEventTarget(this)};
+goog.ui.Container.prototype.setKeyEventTarget=function(a){if(this.focusable_){var b=this.getKeyEventTarget(),c=this.isInDocument();this.keyEventTarget_=a;var d=this.getKeyEventTarget();c&&(this.keyEventTarget_=b,this.enableFocusHandling_(!1),this.keyEventTarget_=a,this.getKeyHandler().attach(d),this.enableFocusHandling_(!0))}else throw Error("Can't set key event target for container that doesn't support keyboard focus!");};
+goog.ui.Container.prototype.getKeyHandler=function(){return this.keyHandler_||(this.keyHandler_=new goog.events.KeyHandler(this.getKeyEventTarget()))};goog.ui.Container.prototype.getRenderer=function(){return this.renderer_};goog.ui.Container.prototype.setRenderer=function(a){if(this.getElement())throw Error(goog.ui.Component.Error.ALREADY_RENDERED);this.renderer_=a};goog.ui.Container.prototype.createDom=function(){this.setElementInternal(this.renderer_.createDom(this))};
+goog.ui.Container.prototype.getContentElement=function(){return this.renderer_.getContentElement(this.getElement())};goog.ui.Container.prototype.canDecorate=function(a){return this.renderer_.canDecorate(a)};goog.ui.Container.prototype.decorateInternal=function(a){this.setElementInternal(this.renderer_.decorate(this,a));"none"==a.style.display&&(this.visible_=!1)};
+goog.ui.Container.prototype.enterDocument=function(){goog.ui.Container.superClass_.enterDocument.call(this);this.forEachChild(function(a){a.isInDocument()&&this.registerChildId_(a)},this);var a=this.getElement();this.renderer_.initializeDom(this);this.setVisible(this.visible_,!0);this.getHandler().listen(this,goog.ui.Component.EventType.ENTER,this.handleEnterItem).listen(this,goog.ui.Component.EventType.HIGHLIGHT,this.handleHighlightItem).listen(this,goog.ui.Component.EventType.UNHIGHLIGHT,this.handleUnHighlightItem).listen(this,
+goog.ui.Component.EventType.OPEN,this.handleOpenItem).listen(this,goog.ui.Component.EventType.CLOSE,this.handleCloseItem).listen(a,goog.events.EventType.MOUSEDOWN,this.handleMouseDown).listen(goog.dom.getOwnerDocument(a),goog.events.EventType.MOUSEUP,this.handleDocumentMouseUp).listen(a,[goog.events.EventType.MOUSEDOWN,goog.events.EventType.MOUSEUP,goog.events.EventType.MOUSEOVER,goog.events.EventType.MOUSEOUT,goog.events.EventType.CONTEXTMENU],this.handleChildMouseEvents);this.isFocusable()&&this.enableFocusHandling_(!0)};
+goog.ui.Container.prototype.enableFocusHandling_=function(a){var b=this.getHandler(),c=this.getKeyEventTarget();a?b.listen(c,goog.events.EventType.FOCUS,this.handleFocus).listen(c,goog.events.EventType.BLUR,this.handleBlur).listen(this.getKeyHandler(),goog.events.KeyHandler.EventType.KEY,this.handleKeyEvent):b.unlisten(c,goog.events.EventType.FOCUS,this.handleFocus).unlisten(c,goog.events.EventType.BLUR,this.handleBlur).unlisten(this.getKeyHandler(),goog.events.KeyHandler.EventType.KEY,this.handleKeyEvent)};
+goog.ui.Container.prototype.exitDocument=function(){this.setHighlightedIndex(-1);this.openItem_&&this.openItem_.setOpen(!1);this.mouseButtonPressed_=!1;goog.ui.Container.superClass_.exitDocument.call(this)};goog.ui.Container.prototype.disposeInternal=function(){goog.ui.Container.superClass_.disposeInternal.call(this);this.keyHandler_&&(this.keyHandler_.dispose(),this.keyHandler_=null);this.renderer_=this.openItem_=this.childElementIdMap_=this.keyEventTarget_=null};
+goog.ui.Container.prototype.handleEnterItem=function(a){return!0};
+goog.ui.Container.prototype.handleHighlightItem=function(a){var b=this.indexOfChild(a.target);if(-1<b&&b!=this.highlightedIndex_){var c=this.getHighlighted();c&&c.setHighlighted(!1);this.highlightedIndex_=b;c=this.getHighlighted();this.isMouseButtonPressed()&&c.setActive(!0);this.openFollowsHighlight_&&this.openItem_&&c!=this.openItem_&&(c.isSupportedState(goog.ui.Component.State.OPENED)?c.setOpen(!0):this.openItem_.setOpen(!1))}b=this.getElement();goog.asserts.assert(b,"The DOM element for the container cannot be null.");
+null!=a.target.getElement()&&goog.a11y.aria.setState(b,goog.a11y.aria.State.ACTIVEDESCENDANT,a.target.getElement().id)};goog.ui.Container.prototype.handleUnHighlightItem=function(a){a.target==this.getHighlighted()&&(this.highlightedIndex_=-1);a=this.getElement();goog.asserts.assert(a,"The DOM element for the container cannot be null.");goog.a11y.aria.removeState(a,goog.a11y.aria.State.ACTIVEDESCENDANT)};
+goog.ui.Container.prototype.handleOpenItem=function(a){(a=a.target)&&a!=this.openItem_&&a.getParent()==this&&(this.openItem_&&this.openItem_.setOpen(!1),this.openItem_=a)};goog.ui.Container.prototype.handleCloseItem=function(a){a.target==this.openItem_&&(this.openItem_=null);var b=this.getElement(),c=a.target.getElement();b&&a.target.isHighlighted()&&c&&goog.a11y.aria.setActiveDescendant(b,c)};
+goog.ui.Container.prototype.handleMouseDown=function(a){this.enabled_&&this.setMouseButtonPressed(!0);var b=this.getKeyEventTarget();b&&goog.dom.isFocusableTabIndex(b)?b.focus():a.preventDefault()};goog.ui.Container.prototype.handleDocumentMouseUp=function(a){this.setMouseButtonPressed(!1)};
+goog.ui.Container.prototype.handleChildMouseEvents=function(a){var b=this.getOwnerControl(a.target);if(b)switch(a.type){case goog.events.EventType.MOUSEDOWN:b.handleMouseDown(a);break;case goog.events.EventType.MOUSEUP:b.handleMouseUp(a);break;case goog.events.EventType.MOUSEOVER:b.handleMouseOver(a);break;case goog.events.EventType.MOUSEOUT:b.handleMouseOut(a);break;case goog.events.EventType.CONTEXTMENU:b.handleContextMenu(a)}};
+goog.ui.Container.prototype.getOwnerControl=function(a){if(this.childElementIdMap_)for(var b=this.getElement();a&&a!==b;){var c=a.id;if(c in this.childElementIdMap_)return this.childElementIdMap_[c];a=a.parentNode}return null};goog.ui.Container.prototype.handleFocus=function(a){};goog.ui.Container.prototype.handleBlur=function(a){this.setHighlightedIndex(-1);this.setMouseButtonPressed(!1);this.openItem_&&this.openItem_.setOpen(!1)};
+goog.ui.Container.prototype.handleKeyEvent=function(a){return this.isEnabled()&&this.isVisible()&&(0!=this.getChildCount()||this.keyEventTarget_)&&this.handleKeyEventInternal(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};
+goog.ui.Container.prototype.handleKeyEventInternal=function(a){var b=this.getHighlighted();if(b&&"function"==typeof b.handleKeyEvent&&b.handleKeyEvent(a)||this.openItem_&&this.openItem_!=b&&"function"==typeof this.openItem_.handleKeyEvent&&this.openItem_.handleKeyEvent(a))return!0;if(a.shiftKey||a.ctrlKey||a.metaKey||a.altKey)return!1;switch(a.keyCode){case goog.events.KeyCodes.ESC:if(this.isFocusable())this.getKeyEventTarget().blur();else return!1;break;case goog.events.KeyCodes.HOME:this.highlightFirst();
+break;case goog.events.KeyCodes.END:this.highlightLast();break;case goog.events.KeyCodes.UP:if(this.orientation_==goog.ui.Container.Orientation.VERTICAL)this.highlightPrevious();else return!1;break;case goog.events.KeyCodes.LEFT:if(this.orientation_==goog.ui.Container.Orientation.HORIZONTAL)this.isRightToLeft()?this.highlightNext():this.highlightPrevious();else return!1;break;case goog.events.KeyCodes.DOWN:if(this.orientation_==goog.ui.Container.Orientation.VERTICAL)this.highlightNext();else return!1;
+break;case goog.events.KeyCodes.RIGHT:if(this.orientation_==goog.ui.Container.Orientation.HORIZONTAL)this.isRightToLeft()?this.highlightPrevious():this.highlightNext();else return!1;break;default:return!1}return!0};goog.ui.Container.prototype.registerChildId_=function(a){var b=a.getElement(),b=b.id||(b.id=a.getId());this.childElementIdMap_||(this.childElementIdMap_={});this.childElementIdMap_[b]=a};
+goog.ui.Container.prototype.addChild=function(a,b){goog.asserts.assertInstanceof(a,goog.ui.Control,"The child of a container must be a control");goog.ui.Container.superClass_.addChild.call(this,a,b)};
+goog.ui.Container.prototype.addChildAt=function(a,b,c){goog.asserts.assertInstanceof(a,goog.ui.Control);a.setDispatchTransitionEvents(goog.ui.Component.State.HOVER,!0);a.setDispatchTransitionEvents(goog.ui.Component.State.OPENED,!0);!this.isFocusable()&&this.isFocusableChildrenAllowed()||a.setSupportedState(goog.ui.Component.State.FOCUSED,!1);a.setHandleMouseEvents(!1);var d=a.getParent()==this?this.indexOfChild(a):-1;goog.ui.Container.superClass_.addChildAt.call(this,a,b,c);a.isInDocument()&&this.isInDocument()&&
+this.registerChildId_(a);this.updateHighlightedIndex_(d,b)};goog.ui.Container.prototype.updateHighlightedIndex_=function(a,b){-1==a&&(a=this.getChildCount());a==this.highlightedIndex_?this.highlightedIndex_=Math.min(this.getChildCount()-1,b):a>this.highlightedIndex_&&b<=this.highlightedIndex_?this.highlightedIndex_++:a<this.highlightedIndex_&&b>this.highlightedIndex_&&this.highlightedIndex_--};
+goog.ui.Container.prototype.removeChild=function(a,b){a=goog.isString(a)?this.getChild(a):a;goog.asserts.assertInstanceof(a,goog.ui.Control);if(a){var c=this.indexOfChild(a);-1!=c&&(c==this.highlightedIndex_?(a.setHighlighted(!1),this.highlightedIndex_=-1):c<this.highlightedIndex_&&this.highlightedIndex_--);(c=a.getElement())&&c.id&&this.childElementIdMap_&&goog.object.remove(this.childElementIdMap_,c.id)}a=goog.ui.Container.superClass_.removeChild.call(this,a,b);a.setHandleMouseEvents(!0);return a};
+goog.ui.Container.prototype.getOrientation=function(){return this.orientation_};goog.ui.Container.prototype.setOrientation=function(a){if(this.getElement())throw Error(goog.ui.Component.Error.ALREADY_RENDERED);this.orientation_=a};goog.ui.Container.prototype.isVisible=function(){return this.visible_};
+goog.ui.Container.prototype.setVisible=function(a,b){if(b||this.visible_!=a&&this.dispatchEvent(a?goog.ui.Component.EventType.SHOW:goog.ui.Component.EventType.HIDE)){this.visible_=a;var c=this.getElement();c&&(goog.style.setElementShown(c,a),this.isFocusable()&&this.renderer_.enableTabIndex(this.getKeyEventTarget(),this.enabled_&&this.visible_),b||this.dispatchEvent(this.visible_?goog.ui.Container.EventType.AFTER_SHOW:goog.ui.Container.EventType.AFTER_HIDE));return!0}return!1};
+goog.ui.Container.prototype.isEnabled=function(){return this.enabled_};
+goog.ui.Container.prototype.setEnabled=function(a){this.enabled_!=a&&this.dispatchEvent(a?goog.ui.Component.EventType.ENABLE:goog.ui.Component.EventType.DISABLE)&&(a?(this.enabled_=!0,this.forEachChild(function(a){a.wasDisabled?delete a.wasDisabled:a.setEnabled(!0)})):(this.forEachChild(function(a){a.isEnabled()?a.setEnabled(!1):a.wasDisabled=!0}),this.enabled_=!1,this.setMouseButtonPressed(!1)),this.isFocusable()&&this.renderer_.enableTabIndex(this.getKeyEventTarget(),a&&this.visible_))};
+goog.ui.Container.prototype.isFocusable=function(){return this.focusable_};goog.ui.Container.prototype.setFocusable=function(a){a!=this.focusable_&&this.isInDocument()&&this.enableFocusHandling_(a);this.focusable_=a;this.enabled_&&this.visible_&&this.renderer_.enableTabIndex(this.getKeyEventTarget(),a)};goog.ui.Container.prototype.isFocusableChildrenAllowed=function(){return this.allowFocusableChildren_};
+goog.ui.Container.prototype.setFocusableChildrenAllowed=function(a){this.allowFocusableChildren_=a};goog.ui.Container.prototype.isOpenFollowsHighlight=function(){return this.openFollowsHighlight_};goog.ui.Container.prototype.setOpenFollowsHighlight=function(a){this.openFollowsHighlight_=a};goog.ui.Container.prototype.getHighlightedIndex=function(){return this.highlightedIndex_};
+goog.ui.Container.prototype.setHighlightedIndex=function(a){(a=this.getChildAt(a))?a.setHighlighted(!0):-1<this.highlightedIndex_&&this.getHighlighted().setHighlighted(!1)};goog.ui.Container.prototype.setHighlighted=function(a){this.setHighlightedIndex(this.indexOfChild(a))};goog.ui.Container.prototype.getHighlighted=function(){return this.getChildAt(this.highlightedIndex_)};
+goog.ui.Container.prototype.highlightFirst=function(){this.highlightHelper(function(a,b){return(a+1)%b},this.getChildCount()-1)};goog.ui.Container.prototype.highlightLast=function(){this.highlightHelper(function(a,b){a--;return 0>a?b-1:a},0)};goog.ui.Container.prototype.highlightNext=function(){this.highlightHelper(function(a,b){return(a+1)%b},this.highlightedIndex_)};goog.ui.Container.prototype.highlightPrevious=function(){this.highlightHelper(function(a,b){a--;return 0>a?b-1:a},this.highlightedIndex_)};
+goog.ui.Container.prototype.highlightHelper=function(a,b){for(var c=0>b?this.indexOfChild(this.openItem_):b,d=this.getChildCount(),c=a.call(this,c,d),e=0;e<=d;){var f=this.getChildAt(c);if(f&&this.canHighlightItem(f))return this.setHighlightedIndexFromKeyEvent(c),!0;e++;c=a.call(this,c,d)}return!1};goog.ui.Container.prototype.canHighlightItem=function(a){return a.isVisible()&&a.isEnabled()&&a.isSupportedState(goog.ui.Component.State.HOVER)};
+goog.ui.Container.prototype.setHighlightedIndexFromKeyEvent=function(a){this.setHighlightedIndex(a)};goog.ui.Container.prototype.getOpenItem=function(){return this.openItem_};goog.ui.Container.prototype.isMouseButtonPressed=function(){return this.mouseButtonPressed_};goog.ui.Container.prototype.setMouseButtonPressed=function(a){this.mouseButtonPressed_=a};goog.ui.MenuHeaderRenderer=function(){goog.ui.ControlRenderer.call(this)};goog.inherits(goog.ui.MenuHeaderRenderer,goog.ui.ControlRenderer);goog.addSingletonGetter(goog.ui.MenuHeaderRenderer);goog.ui.MenuHeaderRenderer.CSS_CLASS="goog-menuheader";goog.ui.MenuHeaderRenderer.prototype.getCssClass=function(){return goog.ui.MenuHeaderRenderer.CSS_CLASS};goog.ui.MenuHeader=function(a,b,c){goog.ui.Control.call(this,a,c||goog.ui.MenuHeaderRenderer.getInstance(),b);this.setSupportedState(goog.ui.Component.State.DISABLED,!1);this.setSupportedState(goog.ui.Component.State.HOVER,!1);this.setSupportedState(goog.ui.Component.State.ACTIVE,!1);this.setSupportedState(goog.ui.Component.State.FOCUSED,!1);this.setStateInternal(goog.ui.Component.State.DISABLED)};goog.inherits(goog.ui.MenuHeader,goog.ui.Control);
+goog.ui.registry.setDecoratorByClassName(goog.ui.MenuHeaderRenderer.CSS_CLASS,function(){return new goog.ui.MenuHeader(null)});goog.ui.MenuItemRenderer=function(){goog.ui.ControlRenderer.call(this);this.classNameCache_=[]};goog.inherits(goog.ui.MenuItemRenderer,goog.ui.ControlRenderer);goog.addSingletonGetter(goog.ui.MenuItemRenderer);goog.ui.MenuItemRenderer.CSS_CLASS="goog-menuitem";goog.ui.MenuItemRenderer.CompositeCssClassIndex_={HOVER:0,CHECKBOX:1,CONTENT:2};
+goog.ui.MenuItemRenderer.prototype.getCompositeCssClass_=function(a){var b=this.classNameCache_[a];if(!b){switch(a){case goog.ui.MenuItemRenderer.CompositeCssClassIndex_.HOVER:b=this.getStructuralCssClass()+"-highlight";break;case goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CHECKBOX:b=this.getStructuralCssClass()+"-checkbox";break;case goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CONTENT:b=this.getStructuralCssClass()+"-content"}this.classNameCache_[a]=b}return b};
+goog.ui.MenuItemRenderer.prototype.getAriaRole=function(){return goog.a11y.aria.Role.MENU_ITEM};goog.ui.MenuItemRenderer.prototype.createDom=function(a){var b=a.getDomHelper().createDom("DIV",this.getClassNames(a).join(" "),this.createContent(a.getContent(),a.getDomHelper()));this.setEnableCheckBoxStructure(a,b,a.isSupportedState(goog.ui.Component.State.SELECTED)||a.isSupportedState(goog.ui.Component.State.CHECKED));return b};
+goog.ui.MenuItemRenderer.prototype.getContentElement=function(a){return a&&a.firstChild};goog.ui.MenuItemRenderer.prototype.decorate=function(a,b){goog.asserts.assert(b);this.hasContentStructure(b)||b.appendChild(this.createContent(b.childNodes,a.getDomHelper()));goog.dom.classlist.contains(b,"goog-option")&&(a.setCheckable(!0),this.setCheckable(a,b,!0));return goog.ui.MenuItemRenderer.superClass_.decorate.call(this,a,b)};
+goog.ui.MenuItemRenderer.prototype.setContent=function(a,b){var c=this.getContentElement(a),d=this.hasCheckBoxStructure(a)?c.firstChild:null;goog.ui.MenuItemRenderer.superClass_.setContent.call(this,a,b);d&&!this.hasCheckBoxStructure(a)&&c.insertBefore(d,c.firstChild||null)};
+goog.ui.MenuItemRenderer.prototype.hasContentStructure=function(a){a=goog.dom.getFirstElementChild(a);var b=this.getCompositeCssClass_(goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CONTENT);return!!a&&goog.dom.classlist.contains(a,b)};goog.ui.MenuItemRenderer.prototype.createContent=function(a,b){var c=this.getCompositeCssClass_(goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CONTENT);return b.createDom("DIV",c,a)};
+goog.ui.MenuItemRenderer.prototype.setSelectable=function(a,b,c){a&&b&&this.setEnableCheckBoxStructure(a,b,c)};goog.ui.MenuItemRenderer.prototype.setCheckable=function(a,b,c){a&&b&&this.setEnableCheckBoxStructure(a,b,c)};goog.ui.MenuItemRenderer.prototype.hasCheckBoxStructure=function(a){if(a=this.getContentElement(a)){a=a.firstChild;var b=this.getCompositeCssClass_(goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CHECKBOX);return!!a&&goog.dom.isElement(a)&&goog.dom.classlist.contains(a,b)}return!1};
+goog.ui.MenuItemRenderer.prototype.setEnableCheckBoxStructure=function(a,b,c){this.setAriaRole(b,a.getPreferredAriaRole());this.setAriaStates(a,b);c!=this.hasCheckBoxStructure(b)&&(goog.dom.classlist.enable(b,"goog-option",c),b=this.getContentElement(b),c?(c=this.getCompositeCssClass_(goog.ui.MenuItemRenderer.CompositeCssClassIndex_.CHECKBOX),b.insertBefore(a.getDomHelper().createDom("DIV",c),b.firstChild||null)):b.removeChild(b.firstChild))};
+goog.ui.MenuItemRenderer.prototype.getClassForState=function(a){switch(a){case goog.ui.Component.State.HOVER:return this.getCompositeCssClass_(goog.ui.MenuItemRenderer.CompositeCssClassIndex_.HOVER);case goog.ui.Component.State.CHECKED:case goog.ui.Component.State.SELECTED:return"goog-option-selected";default:return goog.ui.MenuItemRenderer.superClass_.getClassForState.call(this,a)}};
+goog.ui.MenuItemRenderer.prototype.getStateFromClass=function(a){var b=this.getCompositeCssClass_(goog.ui.MenuItemRenderer.CompositeCssClassIndex_.HOVER);switch(a){case "goog-option-selected":return goog.ui.Component.State.CHECKED;case b:return goog.ui.Component.State.HOVER;default:return goog.ui.MenuItemRenderer.superClass_.getStateFromClass.call(this,a)}};goog.ui.MenuItemRenderer.prototype.getCssClass=function(){return goog.ui.MenuItemRenderer.CSS_CLASS};goog.ui.MenuItem=function(a,b,c,d){goog.ui.Control.call(this,a,d||goog.ui.MenuItemRenderer.getInstance(),c);this.setValue(b)};goog.inherits(goog.ui.MenuItem,goog.ui.Control);goog.tagUnsealableClass(goog.ui.MenuItem);goog.ui.MenuItem.MNEMONIC_WRAPPER_CLASS_="goog-menuitem-mnemonic-separator";goog.ui.MenuItem.ACCELERATOR_CLASS="goog-menuitem-accel";goog.ui.MenuItem.prototype.getValue=function(){var a=this.getModel();return null!=a?a:this.getCaption()};goog.ui.MenuItem.prototype.setValue=function(a){this.setModel(a)};
+goog.ui.MenuItem.prototype.setSupportedState=function(a,b){goog.ui.MenuItem.superClass_.setSupportedState.call(this,a,b);switch(a){case goog.ui.Component.State.SELECTED:this.setSelectableInternal_(b);break;case goog.ui.Component.State.CHECKED:this.setCheckableInternal_(b)}};goog.ui.MenuItem.prototype.setSelectable=function(a){this.setSupportedState(goog.ui.Component.State.SELECTED,a)};
+goog.ui.MenuItem.prototype.setSelectableInternal_=function(a){this.isChecked()&&!a&&this.setChecked(!1);var b=this.getElement();b&&this.getRenderer().setSelectable(this,b,a)};goog.ui.MenuItem.prototype.setCheckable=function(a){this.setSupportedState(goog.ui.Component.State.CHECKED,a)};goog.ui.MenuItem.prototype.setCheckableInternal_=function(a){var b=this.getElement();b&&this.getRenderer().setCheckable(this,b,a)};
+goog.ui.MenuItem.prototype.getCaption=function(){var a=this.getContent();if(goog.isArray(a)){var b=goog.ui.MenuItem.ACCELERATOR_CLASS,c=goog.ui.MenuItem.MNEMONIC_WRAPPER_CLASS_,a=goog.array.map(a,function(a){return goog.dom.isElement(a)&&(goog.dom.classlist.contains(a,b)||goog.dom.classlist.contains(a,c))?"":goog.dom.getRawTextContent(a)}).join("");return goog.string.collapseBreakingSpaces(a)}return goog.ui.MenuItem.superClass_.getCaption.call(this)};
+goog.ui.MenuItem.prototype.getAccelerator=function(){var a=this.getDomHelper(),b=this.getContent();return goog.isArray(b)&&(b=goog.array.find(b,function(a){return goog.dom.classlist.contains(a,goog.ui.MenuItem.ACCELERATOR_CLASS)}))?a.getTextContent(b):null};
+goog.ui.MenuItem.prototype.handleMouseUp=function(a){var b=this.getParent();if(b){var c=b.openingCoords;b.openingCoords=null;if(c&&goog.isNumber(a.clientX)&&(b=new goog.math.Coordinate(a.clientX,a.clientY),goog.math.Coordinate.equals(c,b)))return}goog.ui.MenuItem.superClass_.handleMouseUp.call(this,a)};goog.ui.MenuItem.prototype.handleKeyEventInternal=function(a){return a.keyCode==this.getMnemonic()&&this.performActionInternal(a)?!0:goog.ui.MenuItem.superClass_.handleKeyEventInternal.call(this,a)};
+goog.ui.MenuItem.prototype.setMnemonic=function(a){this.mnemonicKey_=a};goog.ui.MenuItem.prototype.getMnemonic=function(){return this.mnemonicKey_};goog.ui.registry.setDecoratorByClassName(goog.ui.MenuItemRenderer.CSS_CLASS,function(){return new goog.ui.MenuItem(null)});
+goog.ui.MenuItem.prototype.getPreferredAriaRole=function(){return this.isSupportedState(goog.ui.Component.State.CHECKED)?goog.a11y.aria.Role.MENU_ITEM_CHECKBOX:this.isSupportedState(goog.ui.Component.State.SELECTED)?goog.a11y.aria.Role.MENU_ITEM_RADIO:goog.ui.MenuItem.superClass_.getPreferredAriaRole.call(this)};goog.ui.MenuItem.prototype.getParent=function(){return goog.ui.Control.prototype.getParent.call(this)};goog.ui.MenuItem.prototype.getParentEventTarget=function(){return goog.ui.Control.prototype.getParentEventTarget.call(this)};goog.ui.MenuSeparatorRenderer=function(){goog.ui.ControlRenderer.call(this)};goog.inherits(goog.ui.MenuSeparatorRenderer,goog.ui.ControlRenderer);goog.addSingletonGetter(goog.ui.MenuSeparatorRenderer);goog.ui.MenuSeparatorRenderer.CSS_CLASS="goog-menuseparator";goog.ui.MenuSeparatorRenderer.prototype.createDom=function(a){return a.getDomHelper().createDom("DIV",this.getCssClass())};
+goog.ui.MenuSeparatorRenderer.prototype.decorate=function(a,b){b.id&&a.setId(b.id);if("HR"==b.tagName){var c=b;b=this.createDom(a);goog.dom.insertSiblingBefore(b,c);goog.dom.removeNode(c)}else goog.dom.classlist.add(b,this.getCssClass());return b};goog.ui.MenuSeparatorRenderer.prototype.setContent=function(a,b){};goog.ui.MenuSeparatorRenderer.prototype.getCssClass=function(){return goog.ui.MenuSeparatorRenderer.CSS_CLASS};goog.ui.Separator=function(a,b){goog.ui.Control.call(this,null,a||goog.ui.MenuSeparatorRenderer.getInstance(),b);this.setSupportedState(goog.ui.Component.State.DISABLED,!1);this.setSupportedState(goog.ui.Component.State.HOVER,!1);this.setSupportedState(goog.ui.Component.State.ACTIVE,!1);this.setSupportedState(goog.ui.Component.State.FOCUSED,!1);this.setStateInternal(goog.ui.Component.State.DISABLED)};goog.inherits(goog.ui.Separator,goog.ui.Control);
+goog.ui.Separator.prototype.enterDocument=function(){goog.ui.Separator.superClass_.enterDocument.call(this);var a=this.getElement();goog.asserts.assert(a,"The DOM element for the separator cannot be null.");goog.a11y.aria.setRole(a,"separator")};goog.ui.registry.setDecoratorByClassName(goog.ui.MenuSeparatorRenderer.CSS_CLASS,function(){return new goog.ui.Separator});goog.ui.MenuRenderer=function(a){goog.ui.ContainerRenderer.call(this,a||goog.a11y.aria.Role.MENU)};goog.inherits(goog.ui.MenuRenderer,goog.ui.ContainerRenderer);goog.addSingletonGetter(goog.ui.MenuRenderer);goog.ui.MenuRenderer.CSS_CLASS="goog-menu";goog.ui.MenuRenderer.prototype.canDecorate=function(a){return"UL"==a.tagName||goog.ui.MenuRenderer.superClass_.canDecorate.call(this,a)};
+goog.ui.MenuRenderer.prototype.getDecoratorForChild=function(a){return"HR"==a.tagName?new goog.ui.Separator:goog.ui.MenuRenderer.superClass_.getDecoratorForChild.call(this,a)};goog.ui.MenuRenderer.prototype.containsElement=function(a,b){return goog.dom.contains(a.getElement(),b)};goog.ui.MenuRenderer.prototype.getCssClass=function(){return goog.ui.MenuRenderer.CSS_CLASS};
+goog.ui.MenuRenderer.prototype.initializeDom=function(a){goog.ui.MenuRenderer.superClass_.initializeDom.call(this,a);a=a.getElement();goog.asserts.assert(a,"The menu DOM element cannot be null.");goog.a11y.aria.setState(a,goog.a11y.aria.State.HASPOPUP,"true")};goog.ui.MenuSeparator=function(a){goog.ui.Separator.call(this,goog.ui.MenuSeparatorRenderer.getInstance(),a)};goog.inherits(goog.ui.MenuSeparator,goog.ui.Separator);goog.ui.registry.setDecoratorByClassName(goog.ui.MenuSeparatorRenderer.CSS_CLASS,function(){return new goog.ui.Separator});goog.ui.Menu=function(a,b){goog.ui.Container.call(this,goog.ui.Container.Orientation.VERTICAL,b||goog.ui.MenuRenderer.getInstance(),a);this.setFocusable(!1)};goog.inherits(goog.ui.Menu,goog.ui.Container);goog.tagUnsealableClass(goog.ui.Menu);goog.ui.Menu.EventType={BEFORE_SHOW:goog.ui.Component.EventType.BEFORE_SHOW,SHOW:goog.ui.Component.EventType.SHOW,BEFORE_HIDE:goog.ui.Component.EventType.HIDE,HIDE:goog.ui.Component.EventType.HIDE};goog.ui.Menu.CSS_CLASS=goog.ui.MenuRenderer.CSS_CLASS;
+goog.ui.Menu.prototype.allowAutoFocus_=!0;goog.ui.Menu.prototype.allowHighlightDisabled_=!1;goog.ui.Menu.prototype.getCssClass=function(){return this.getRenderer().getCssClass()};goog.ui.Menu.prototype.containsElement=function(a){if(this.getRenderer().containsElement(this,a))return!0;for(var b=0,c=this.getChildCount();b<c;b++){var d=this.getChildAt(b);if("function"==typeof d.containsElement&&d.containsElement(a))return!0}return!1};goog.ui.Menu.prototype.addItem=function(a){this.addChild(a,!0)};
+goog.ui.Menu.prototype.addItemAt=function(a,b){this.addChildAt(a,b,!0)};goog.ui.Menu.prototype.removeItem=function(a){(a=this.removeChild(a,!0))&&a.dispose()};goog.ui.Menu.prototype.removeItemAt=function(a){(a=this.removeChildAt(a,!0))&&a.dispose()};goog.ui.Menu.prototype.getItemAt=function(a){return this.getChildAt(a)};goog.ui.Menu.prototype.getItemCount=function(){return this.getChildCount()};goog.ui.Menu.prototype.getItems=function(){var a=[];this.forEachChild(function(b){a.push(b)});return a};
+goog.ui.Menu.prototype.setPosition=function(a,b){var c=this.isVisible();c||goog.style.setElementShown(this.getElement(),!0);goog.style.setPageOffset(this.getElement(),a,b);c||goog.style.setElementShown(this.getElement(),!1)};goog.ui.Menu.prototype.getPosition=function(){return this.isVisible()?goog.style.getPageOffset(this.getElement()):null};goog.ui.Menu.prototype.setAllowAutoFocus=function(a){(this.allowAutoFocus_=a)&&this.setFocusable(!0)};goog.ui.Menu.prototype.getAllowAutoFocus=function(){return this.allowAutoFocus_};
+goog.ui.Menu.prototype.setAllowHighlightDisabled=function(a){this.allowHighlightDisabled_=a};goog.ui.Menu.prototype.getAllowHighlightDisabled=function(){return this.allowHighlightDisabled_};goog.ui.Menu.prototype.setVisible=function(a,b,c){(b=goog.ui.Menu.superClass_.setVisible.call(this,a,b))&&a&&this.isInDocument()&&this.allowAutoFocus_&&this.getKeyEventTarget().focus();a&&c&&goog.isNumber(c.clientX)?this.openingCoords=new goog.math.Coordinate(c.clientX,c.clientY):this.openingCoords=null;return b};
+goog.ui.Menu.prototype.handleEnterItem=function(a){this.allowAutoFocus_&&this.getKeyEventTarget().focus();return goog.ui.Menu.superClass_.handleEnterItem.call(this,a)};goog.ui.Menu.prototype.highlightNextPrefix=function(a){var b=new RegExp("^"+goog.string.regExpEscape(a),"i");return this.highlightHelper(function(a,d){var c=0>a?0:a,f=!1;do{++a;a==d&&(a=0,f=!0);var g=this.getChildAt(a).getCaption();if(g&&g.match(b))return a}while(!f||a!=c);return this.getHighlightedIndex()},this.getHighlightedIndex())};
+goog.ui.Menu.prototype.canHighlightItem=function(a){return(this.allowHighlightDisabled_||a.isEnabled())&&a.isVisible()&&a.isSupportedState(goog.ui.Component.State.HOVER)};goog.ui.Menu.prototype.decorateInternal=function(a){this.decorateContent(a);goog.ui.Menu.superClass_.decorateInternal.call(this,a)};
+goog.ui.Menu.prototype.handleKeyEventInternal=function(a){var b=goog.ui.Menu.superClass_.handleKeyEventInternal.call(this,a);b||this.forEachChild(function(c){!b&&c.getMnemonic&&c.getMnemonic()==a.keyCode&&(this.isEnabled()&&this.setHighlighted(c),b=c.handleKeyEvent(a))},this);return b};goog.ui.Menu.prototype.setHighlightedIndex=function(a){goog.ui.Menu.superClass_.setHighlightedIndex.call(this,a);(a=this.getChildAt(a))&&goog.style.scrollIntoContainerView(a.getElement(),this.getElement())};
+goog.ui.Menu.prototype.decorateContent=function(a){var b=this.getRenderer();a=this.getDomHelper().getElementsByTagNameAndClass("DIV",b.getCssClass()+"-content",a);for(var c=a.length,d=0;d<c;d++)b.decorateChildren(this,a[d])};goog.color={};
+goog.color.names={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
+darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
+ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
+lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
+moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
+seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};goog.color.parse=function(a){var b={};a=String(a);var c=goog.color.prependHashIfNecessaryHelper(a);if(goog.color.isValidHexColor_(c))return b.hex=goog.color.normalizeHex(c),b.type="hex",b;c=goog.color.isValidRgbColor_(a);if(c.length)return b.hex=goog.color.rgbArrayToHex(c),b.type="rgb",b;if(goog.color.names&&(c=goog.color.names[a.toLowerCase()]))return b.hex=c,b.type="named",b;throw Error(a+" is not a valid color string");};
+goog.color.isValidColor=function(a){var b=goog.color.prependHashIfNecessaryHelper(a);return!!(goog.color.isValidHexColor_(b)||goog.color.isValidRgbColor_(a).length||goog.color.names&&goog.color.names[a.toLowerCase()])};goog.color.parseRgb=function(a){var b=goog.color.isValidRgbColor_(a);if(!b.length)throw Error(a+" is not a valid RGB color");return b};goog.color.hexToRgbStyle=function(a){return goog.color.rgbStyle_(goog.color.hexToRgb(a))};goog.color.hexTripletRe_=/#(.)(.)(.)/;
+goog.color.normalizeHex=function(a){if(!goog.color.isValidHexColor_(a))throw Error("'"+a+"' is not a valid hex color");4==a.length&&(a=a.replace(goog.color.hexTripletRe_,"#$1$1$2$2$3$3"));return a.toLowerCase()};goog.color.hexToRgb=function(a){a=goog.color.normalizeHex(a);var b=parseInt(a.substr(1,2),16),c=parseInt(a.substr(3,2),16);a=parseInt(a.substr(5,2),16);return[b,c,a]};
+goog.color.rgbToHex=function(a,b,c){a=Number(a);b=Number(b);c=Number(c);if(a!=(a&255)||b!=(b&255)||c!=(c&255))throw Error('"('+a+","+b+","+c+'") is not a valid RGB color');a=goog.color.prependZeroIfNecessaryHelper(a.toString(16));b=goog.color.prependZeroIfNecessaryHelper(b.toString(16));c=goog.color.prependZeroIfNecessaryHelper(c.toString(16));return"#"+a+b+c};goog.color.rgbArrayToHex=function(a){return goog.color.rgbToHex(a[0],a[1],a[2])};
+goog.color.rgbToHsl=function(a,b,c){a/=255;b/=255;c/=255;var d=Math.max(a,b,c),e=Math.min(a,b,c),f=0,g=0,h=.5*(d+e);d!=e&&(d==a?f=60*(b-c)/(d-e):d==b?f=60*(c-a)/(d-e)+120:d==c&&(f=60*(a-b)/(d-e)+240),g=0<h&&.5>=h?(d-e)/(2*h):(d-e)/(2-2*h));return[Math.round(f+360)%360,g,h]};goog.color.rgbArrayToHsl=function(a){return goog.color.rgbToHsl(a[0],a[1],a[2])};goog.color.hueToRgb_=function(a,b,c){0>c?c+=1:1<c&&--c;return 1>6*c?a+6*(b-a)*c:1>2*c?b:2>3*c?a+(b-a)*(2/3-c)*6:a};
+goog.color.hslToRgb=function(a,b,c){a/=360;if(0==b)c=b=a=255*c;else{var d,e;e=.5>c?c*(1+b):c+b-b*c;d=2*c-e;c=255*goog.color.hueToRgb_(d,e,a+1/3);b=255*goog.color.hueToRgb_(d,e,a);a=255*goog.color.hueToRgb_(d,e,a-1/3)}return[Math.round(c),Math.round(b),Math.round(a)]};goog.color.hslArrayToRgb=function(a){return goog.color.hslToRgb(a[0],a[1],a[2])};goog.color.validHexColorRe_=/^#(?:[0-9a-f]{3}){1,2}$/i;goog.color.isValidHexColor_=function(a){return goog.color.validHexColorRe_.test(a)};
+goog.color.normalizedHexColorRe_=/^#[0-9a-f]{6}$/;goog.color.isNormalizedHexColor_=function(a){return goog.color.normalizedHexColorRe_.test(a)};goog.color.rgbColorRe_=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;goog.color.isValidRgbColor_=function(a){var b=a.match(goog.color.rgbColorRe_);if(b){a=Number(b[1]);var c=Number(b[2]),b=Number(b[3]);if(0<=a&&255>=a&&0<=c&&255>=c&&0<=b&&255>=b)return[a,c,b]}return[]};
+goog.color.prependZeroIfNecessaryHelper=function(a){return 1==a.length?"0"+a:a};goog.color.prependHashIfNecessaryHelper=function(a){return"#"==a.charAt(0)?a:"#"+a};goog.color.rgbStyle_=function(a){return"rgb("+a.join(",")+")"};
+goog.color.hsvToRgb=function(a,b,c){var d=0,e=0,f=0;if(0==b)f=e=d=c;else{var g=Math.floor(a/60),h=a/60-g;a=c*(1-b);var k=c*(1-b*h);b=c*(1-b*(1-h));switch(g){case 1:d=k;e=c;f=a;break;case 2:d=a;e=c;f=b;break;case 3:d=a;e=k;f=c;break;case 4:d=b;e=a;f=c;break;case 5:d=c;e=a;f=k;break;case 6:case 0:d=c,e=b,f=a}}return[Math.floor(d),Math.floor(e),Math.floor(f)]};
+goog.color.rgbToHsv=function(a,b,c){var d=Math.max(Math.max(a,b),c),e=Math.min(Math.min(a,b),c);if(e==d)e=a=0;else{var f=d-e,e=f/d;a=60*(a==d?(b-c)/f:b==d?2+(c-a)/f:4+(a-b)/f);0>a&&(a+=360);360<a&&(a-=360)}return[a,e,d]};goog.color.rgbArrayToHsv=function(a){return goog.color.rgbToHsv(a[0],a[1],a[2])};goog.color.hsvArrayToRgb=function(a){return goog.color.hsvToRgb(a[0],a[1],a[2])};goog.color.hexToHsl=function(a){a=goog.color.hexToRgb(a);return goog.color.rgbToHsl(a[0],a[1],a[2])};
+goog.color.hslToHex=function(a,b,c){return goog.color.rgbArrayToHex(goog.color.hslToRgb(a,b,c))};goog.color.hslArrayToHex=function(a){return goog.color.rgbArrayToHex(goog.color.hslToRgb(a[0],a[1],a[2]))};goog.color.hexToHsv=function(a){return goog.color.rgbArrayToHsv(goog.color.hexToRgb(a))};goog.color.hsvToHex=function(a,b,c){return goog.color.rgbArrayToHex(goog.color.hsvToRgb(a,b,c))};goog.color.hsvArrayToHex=function(a){return goog.color.hsvToHex(a[0],a[1],a[2])};
+goog.color.hslDistance=function(a,b){var c,d;c=.5>=a[2]?a[1]*a[2]:a[1]*(1-a[2]);d=.5>=b[2]?b[1]*b[2]:b[1]*(1-b[2]);return(a[2]-b[2])*(a[2]-b[2])+c*c+d*d-2*c*d*Math.cos(2*(a[0]/360-b[0]/360)*Math.PI)};goog.color.blend=function(a,b,c){c=goog.math.clamp(c,0,1);return[Math.round(c*a[0]+(1-c)*b[0]),Math.round(c*a[1]+(1-c)*b[1]),Math.round(c*a[2]+(1-c)*b[2])]};goog.color.darken=function(a,b){return goog.color.blend([0,0,0],a,b)};goog.color.lighten=function(a,b){return goog.color.blend([255,255,255],a,b)};
+goog.color.highContrast=function(a,b){for(var c=[],d=0;d<b.length;d++)c.push({color:b[d],diff:goog.color.yiqBrightnessDiff_(b[d],a)+goog.color.colorDiff_(b[d],a)});c.sort(function(a,b){return b.diff-a.diff});return c[0].color};goog.color.yiqBrightness_=function(a){return Math.round((299*a[0]+587*a[1]+114*a[2])/1E3)};goog.color.yiqBrightnessDiff_=function(a,b){return Math.abs(goog.color.yiqBrightness_(a)-goog.color.yiqBrightness_(b))};
+goog.color.colorDiff_=function(a,b){return Math.abs(a[0]-b[0])+Math.abs(a[1]-b[1])+Math.abs(a[2]-b[2])};goog.iter={};goog.iter.StopIteration="StopIteration"in goog.global?goog.global.StopIteration:{message:"StopIteration",stack:""};goog.iter.Iterator=function(){};goog.iter.Iterator.prototype.next=function(){throw goog.iter.StopIteration;};goog.iter.Iterator.prototype.__iterator__=function(a){return this};
+goog.iter.toIterator=function(a){if(a instanceof goog.iter.Iterator)return a;if("function"==typeof a.__iterator__)return a.__iterator__(!1);if(goog.isArrayLike(a)){var b=0,c=new goog.iter.Iterator;c.next=function(){for(;;){if(b>=a.length)throw goog.iter.StopIteration;if(b in a)return a[b++];b++}};return c}throw Error("Not implemented");};
+goog.iter.forEach=function(a,b,c){if(goog.isArrayLike(a))try{goog.array.forEach(a,b,c)}catch(d){if(d!==goog.iter.StopIteration)throw d;}else{a=goog.iter.toIterator(a);try{for(;;)b.call(c,a.next(),void 0,a)}catch(d){if(d!==goog.iter.StopIteration)throw d;}}};goog.iter.filter=function(a,b,c){var d=goog.iter.toIterator(a);a=new goog.iter.Iterator;a.next=function(){for(;;){var a=d.next();if(b.call(c,a,void 0,d))return a}};return a};
+goog.iter.filterFalse=function(a,b,c){return goog.iter.filter(a,goog.functions.not(b),c)};goog.iter.range=function(a,b,c){var d=0,e=a,f=c||1;1<arguments.length&&(d=a,e=b);if(0==f)throw Error("Range step argument must not be zero");var g=new goog.iter.Iterator;g.next=function(){if(0<f&&d>=e||0>f&&d<=e)throw goog.iter.StopIteration;var a=d;d+=f;return a};return g};goog.iter.join=function(a,b){return goog.iter.toArray(a).join(b)};
+goog.iter.map=function(a,b,c){var d=goog.iter.toIterator(a);a=new goog.iter.Iterator;a.next=function(){var a=d.next();return b.call(c,a,void 0,d)};return a};goog.iter.reduce=function(a,b,c,d){var e=c;goog.iter.forEach(a,function(a){e=b.call(d,e,a)});return e};goog.iter.some=function(a,b,c){a=goog.iter.toIterator(a);try{for(;;)if(b.call(c,a.next(),void 0,a))return!0}catch(d){if(d!==goog.iter.StopIteration)throw d;}return!1};
+goog.iter.every=function(a,b,c){a=goog.iter.toIterator(a);try{for(;;)if(!b.call(c,a.next(),void 0,a))return!1}catch(d){if(d!==goog.iter.StopIteration)throw d;}return!0};goog.iter.chain=function(a){return goog.iter.chainFromIterable(arguments)};
+goog.iter.chainFromIterable=function(a){var b=goog.iter.toIterator(a);a=new goog.iter.Iterator;var c=null;a.next=function(){for(;;){if(null==c){var a=b.next();c=goog.iter.toIterator(a)}try{return c.next()}catch(e){if(e!==goog.iter.StopIteration)throw e;c=null}}};return a};goog.iter.dropWhile=function(a,b,c){var d=goog.iter.toIterator(a);a=new goog.iter.Iterator;var e=!0;a.next=function(){for(;;){var a=d.next();if(!e||!b.call(c,a,void 0,d))return e=!1,a}};return a};
+goog.iter.takeWhile=function(a,b,c){var d=goog.iter.toIterator(a);a=new goog.iter.Iterator;a.next=function(){var a=d.next();if(b.call(c,a,void 0,d))return a;throw goog.iter.StopIteration;};return a};goog.iter.toArray=function(a){if(goog.isArrayLike(a))return goog.array.toArray(a);a=goog.iter.toIterator(a);var b=[];goog.iter.forEach(a,function(a){b.push(a)});return b};
+goog.iter.equals=function(a,b,c){a=goog.iter.zipLongest({},a,b);var d=c||goog.array.defaultCompareEquality;return goog.iter.every(a,function(a){return d(a[0],a[1])})};goog.iter.nextOrValue=function(a,b){try{return goog.iter.toIterator(a).next()}catch(c){if(c!=goog.iter.StopIteration)throw c;return b}};
+goog.iter.product=function(a){if(goog.array.some(arguments,function(a){return!a.length})||!arguments.length)return new goog.iter.Iterator;var b=new goog.iter.Iterator,c=arguments,d=goog.array.repeat(0,c.length);b.next=function(){if(d){for(var a=goog.array.map(d,function(a,b){return c[b][a]}),b=d.length-1;0<=b;b--){goog.asserts.assert(d);if(d[b]<c[b].length-1){d[b]++;break}if(0==b){d=null;break}d[b]=0}return a}throw goog.iter.StopIteration;};return b};
+goog.iter.cycle=function(a){var b=goog.iter.toIterator(a),c=[],d=0;a=new goog.iter.Iterator;var e=!1;a.next=function(){var a=null;if(!e)try{return a=b.next(),c.push(a),a}catch(g){if(g!=goog.iter.StopIteration||goog.array.isEmpty(c))throw g;e=!0}a=c[d];d=(d+1)%c.length;return a};return a};goog.iter.count=function(a,b){var c=a||0,d=goog.isDef(b)?b:1,e=new goog.iter.Iterator;e.next=function(){var a=c;c+=d;return a};return e};
+goog.iter.repeat=function(a){var b=new goog.iter.Iterator;b.next=goog.functions.constant(a);return b};goog.iter.accumulate=function(a){var b=goog.iter.toIterator(a),c=0;a=new goog.iter.Iterator;a.next=function(){return c+=b.next()};return a};goog.iter.zip=function(a){var b=arguments,c=new goog.iter.Iterator;if(0<b.length){var d=goog.array.map(b,goog.iter.toIterator);c.next=function(){return goog.array.map(d,function(a){return a.next()})}}return c};
+goog.iter.zipLongest=function(a,b){var c=goog.array.slice(arguments,1),d=new goog.iter.Iterator;if(0<c.length){var e=goog.array.map(c,goog.iter.toIterator);d.next=function(){var b=!1,c=goog.array.map(e,function(c){var d;try{d=c.next(),b=!0}catch(l){if(l!==goog.iter.StopIteration)throw l;d=a}return d});if(!b)throw goog.iter.StopIteration;return c}}return d};goog.iter.compress=function(a,b){var c=goog.iter.toIterator(b);return goog.iter.filter(a,function(){return!!c.next()})};
+goog.iter.GroupByIterator_=function(a,b){this.iterator=goog.iter.toIterator(a);this.keyFunc=b||goog.functions.identity};goog.inherits(goog.iter.GroupByIterator_,goog.iter.Iterator);goog.iter.GroupByIterator_.prototype.next=function(){for(;this.currentKey==this.targetKey;)this.currentValue=this.iterator.next(),this.currentKey=this.keyFunc(this.currentValue);this.targetKey=this.currentKey;return[this.currentKey,this.groupItems_(this.targetKey)]};
+goog.iter.GroupByIterator_.prototype.groupItems_=function(a){for(var b=[];this.currentKey==a;){b.push(this.currentValue);try{this.currentValue=this.iterator.next()}catch(c){if(c!==goog.iter.StopIteration)throw c;break}this.currentKey=this.keyFunc(this.currentValue)}return b};goog.iter.groupBy=function(a,b){return new goog.iter.GroupByIterator_(a,b)};
+goog.iter.starMap=function(a,b,c){var d=goog.iter.toIterator(a);a=new goog.iter.Iterator;a.next=function(){var a=goog.iter.toArray(d.next());return b.apply(c,goog.array.concat(a,void 0,d))};return a};
+goog.iter.tee=function(a,b){var c=goog.iter.toIterator(a),d=goog.isNumber(b)?b:2,e=goog.array.map(goog.array.range(d),function(){return[]}),f=function(){var a=c.next();goog.array.forEach(e,function(b){b.push(a)})};return goog.array.map(e,function(a){var b=new goog.iter.Iterator;b.next=function(){goog.array.isEmpty(a)&&f();goog.asserts.assert(!goog.array.isEmpty(a));return a.shift()};return b})};goog.iter.enumerate=function(a,b){return goog.iter.zip(goog.iter.count(b),a)};
+goog.iter.limit=function(a,b){goog.asserts.assert(goog.math.isInt(b)&&0<=b);var c=goog.iter.toIterator(a),d=new goog.iter.Iterator,e=b;d.next=function(){if(0<e--)return c.next();throw goog.iter.StopIteration;};return d};goog.iter.consume=function(a,b){goog.asserts.assert(goog.math.isInt(b)&&0<=b);for(var c=goog.iter.toIterator(a);0<b--;)goog.iter.nextOrValue(c,null);return c};
+goog.iter.slice=function(a,b,c){goog.asserts.assert(goog.math.isInt(b)&&0<=b);a=goog.iter.consume(a,b);goog.isNumber(c)&&(goog.asserts.assert(goog.math.isInt(c)&&c>=b),a=goog.iter.limit(a,c-b));return a};goog.iter.hasDuplicates_=function(a){var b=[];goog.array.removeDuplicates(a,b);return a.length!=b.length};goog.iter.permutations=function(a,b){var c=goog.iter.toArray(a),d=goog.isNumber(b)?b:c.length,c=goog.array.repeat(c,d),c=goog.iter.product.apply(void 0,c);return goog.iter.filter(c,function(a){return!goog.iter.hasDuplicates_(a)})};
+goog.iter.combinations=function(a,b){function c(a){return d[a]}var d=goog.iter.toArray(a),e=goog.iter.range(d.length),e=goog.iter.permutations(e,b),f=goog.iter.filter(e,function(a){return goog.array.isSorted(a)}),e=new goog.iter.Iterator;e.next=function(){return goog.array.map(f.next(),c)};return e};
+goog.iter.combinationsWithReplacement=function(a,b){function c(a){return d[a]}var d=goog.iter.toArray(a),e=goog.array.range(d.length),e=goog.array.repeat(e,b),e=goog.iter.product.apply(void 0,e),f=goog.iter.filter(e,function(a){return goog.array.isSorted(a)}),e=new goog.iter.Iterator;e.next=function(){return goog.array.map(f.next(),c)};return e};goog.dom.TagWalkType={START_TAG:1,OTHER:0,END_TAG:-1};goog.dom.TagIterator=function(a,b,c,d,e){this.reversed=!!b;this.node=null;this.tagType=goog.dom.TagWalkType.OTHER;this.started_=!1;this.constrained=!c;a&&this.setPosition(a,d);this.depth=void 0!=e?e:this.tagType||0;this.reversed&&(this.depth*=-1)};goog.inherits(goog.dom.TagIterator,goog.iter.Iterator);
+goog.dom.TagIterator.prototype.setPosition=function(a,b,c){if(this.node=a)goog.isNumber(b)?this.tagType=b:this.tagType=this.node.nodeType!=goog.dom.NodeType.ELEMENT?goog.dom.TagWalkType.OTHER:this.reversed?goog.dom.TagWalkType.END_TAG:goog.dom.TagWalkType.START_TAG;goog.isNumber(c)&&(this.depth=c)};goog.dom.TagIterator.prototype.copyFrom=function(a){this.node=a.node;this.tagType=a.tagType;this.depth=a.depth;this.reversed=a.reversed;this.constrained=a.constrained};
+goog.dom.TagIterator.prototype.clone=function(){return new goog.dom.TagIterator(this.node,this.reversed,!this.constrained,this.tagType,this.depth)};goog.dom.TagIterator.prototype.skipTag=function(){var a=this.reversed?goog.dom.TagWalkType.END_TAG:goog.dom.TagWalkType.START_TAG;this.tagType==a&&(this.tagType=-1*a,this.depth+=this.tagType*(this.reversed?-1:1))};
+goog.dom.TagIterator.prototype.restartTag=function(){var a=this.reversed?goog.dom.TagWalkType.START_TAG:goog.dom.TagWalkType.END_TAG;this.tagType==a&&(this.tagType=-1*a,this.depth+=this.tagType*(this.reversed?-1:1))};
+goog.dom.TagIterator.prototype.next=function(){var a;if(this.started_){if(!this.node||this.constrained&&0==this.depth)throw goog.iter.StopIteration;a=this.node;var b=this.reversed?goog.dom.TagWalkType.END_TAG:goog.dom.TagWalkType.START_TAG;if(this.tagType==b){var c=this.reversed?a.lastChild:a.firstChild;c?this.setPosition(c):this.setPosition(a,-1*b)}else(c=this.reversed?a.previousSibling:a.nextSibling)?this.setPosition(c):this.setPosition(a.parentNode,-1*b);this.depth+=this.tagType*(this.reversed?
+-1:1)}else this.started_=!0;a=this.node;if(!this.node)throw goog.iter.StopIteration;return a};goog.dom.TagIterator.prototype.isStarted=function(){return this.started_};goog.dom.TagIterator.prototype.isStartTag=function(){return this.tagType==goog.dom.TagWalkType.START_TAG};goog.dom.TagIterator.prototype.isEndTag=function(){return this.tagType==goog.dom.TagWalkType.END_TAG};goog.dom.TagIterator.prototype.isNonElement=function(){return this.tagType==goog.dom.TagWalkType.OTHER};
+goog.dom.TagIterator.prototype.equals=function(a){return a.node==this.node&&(!this.node||a.tagType==this.tagType)};goog.dom.TagIterator.prototype.splice=function(a){var b=this.node;this.restartTag();this.reversed=!this.reversed;goog.dom.TagIterator.prototype.next.call(this);this.reversed=!this.reversed;for(var c=goog.isArrayLike(arguments[0])?arguments[0]:arguments,d=c.length-1;0<=d;d--)goog.dom.insertSiblingAfter(c[d],b);goog.dom.removeNode(b)};goog.dom.NodeIterator=function(a,b,c,d){goog.dom.TagIterator.call(this,a,b,c,null,d)};goog.inherits(goog.dom.NodeIterator,goog.dom.TagIterator);goog.dom.NodeIterator.prototype.next=function(){do goog.dom.NodeIterator.superClass_.next.call(this);while(this.isEndTag());return this.node};goog.ui.PaletteRenderer=function(){goog.ui.ControlRenderer.call(this)};goog.inherits(goog.ui.PaletteRenderer,goog.ui.ControlRenderer);goog.addSingletonGetter(goog.ui.PaletteRenderer);goog.ui.PaletteRenderer.cellId_=0;goog.ui.PaletteRenderer.CSS_CLASS="goog-palette";
+goog.ui.PaletteRenderer.prototype.createDom=function(a){var b=this.getClassNames(a);a=a.getDomHelper().createDom("DIV",b?b.join(" "):null,this.createGrid(a.getContent(),a.getSize(),a.getDomHelper()));goog.a11y.aria.setRole(a,goog.a11y.aria.Role.GRID);return a};goog.ui.PaletteRenderer.prototype.createGrid=function(a,b,c){for(var d=[],e=0,f=0;e<b.height;e++){for(var g=[],h=0;h<b.width;h++){var k=a&&a[f++];g.push(this.createCell(k,c))}d.push(this.createRow(g,c))}return this.createTable(d,c)};
+goog.ui.PaletteRenderer.prototype.createTable=function(a,b){var c=b.createDom("TABLE",this.getCssClass()+"-table",b.createDom("TBODY",this.getCssClass()+"-body",a));c.cellSpacing="0";c.cellPadding="0";return c};goog.ui.PaletteRenderer.prototype.createRow=function(a,b){var c=b.createDom("TR",this.getCssClass()+"-row",a);goog.a11y.aria.setRole(c,goog.a11y.aria.Role.ROW);return c};
+goog.ui.PaletteRenderer.prototype.createCell=function(a,b){var c=b.createDom("TD",{"class":this.getCssClass()+"-cell",id:this.getCssClass()+"-cell-"+goog.ui.PaletteRenderer.cellId_++},a);goog.a11y.aria.setRole(c,goog.a11y.aria.Role.GRIDCELL);goog.a11y.aria.setState(c,goog.a11y.aria.State.SELECTED,!1);if(!goog.dom.getTextContent(c)&&!goog.a11y.aria.getLabel(c)){var d=this.findAriaLabelForCell_(c);d&&goog.a11y.aria.setLabel(c,d)}return c};
+goog.ui.PaletteRenderer.prototype.findAriaLabelForCell_=function(a){a=new goog.dom.NodeIterator(a);for(var b="",c;!b&&(c=goog.iter.nextOrValue(a,null));)c.nodeType==goog.dom.NodeType.ELEMENT&&(b=goog.a11y.aria.getLabel(c)||c.title);return b};goog.ui.PaletteRenderer.prototype.canDecorate=function(a){return!1};goog.ui.PaletteRenderer.prototype.decorate=function(a,b){return null};
+goog.ui.PaletteRenderer.prototype.setContent=function(a,b){if(a){var c=goog.dom.getElementsByTagNameAndClass("TBODY",this.getCssClass()+"-body",a)[0];if(c){var d=0;goog.array.forEach(c.rows,function(a){goog.array.forEach(a.cells,function(a){goog.dom.removeChildren(a);if(b){var c=b[d++];c&&goog.dom.appendChild(a,c)}})});if(d<b.length){for(var e=[],f=goog.dom.getDomHelper(a),g=c.rows[0].cells.length;d<b.length;){var h=b[d++];e.push(this.createCell(h,f));e.length==g&&(h=this.createRow(e,f),goog.dom.appendChild(c,
+h),e.length=0)}if(0<e.length){for(;e.length<g;)e.push(this.createCell("",f));h=this.createRow(e,f);goog.dom.appendChild(c,h)}}}goog.style.setUnselectable(a,!0,goog.userAgent.GECKO)}};goog.ui.PaletteRenderer.prototype.getContainingItem=function(a,b){for(var c=a.getElement();b&&b.nodeType==goog.dom.NodeType.ELEMENT&&b!=c;){if("TD"==b.tagName&&goog.dom.classlist.contains(b,this.getCssClass()+"-cell"))return b.firstChild;b=b.parentNode}return null};
+goog.ui.PaletteRenderer.prototype.highlightCell=function(a,b,c){b&&(b=this.getCellForItem(b),goog.asserts.assert(b),goog.dom.classlist.enable(b,this.getCssClass()+"-cell-hover",c),c?goog.a11y.aria.setState(a.getElementStrict(),goog.a11y.aria.State.ACTIVEDESCENDANT,b.id):b.id==goog.a11y.aria.getState(a.getElementStrict(),goog.a11y.aria.State.ACTIVEDESCENDANT)&&goog.a11y.aria.removeState(a.getElementStrict(),goog.a11y.aria.State.ACTIVEDESCENDANT))};
+goog.ui.PaletteRenderer.prototype.getCellForItem=function(a){return a?a.parentNode:null};goog.ui.PaletteRenderer.prototype.selectCell=function(a,b,c){b&&(a=b.parentNode,goog.dom.classlist.enable(a,this.getCssClass()+"-cell-selected",c),goog.a11y.aria.setState(a,goog.a11y.aria.State.SELECTED,c))};goog.ui.PaletteRenderer.prototype.getCssClass=function(){return goog.ui.PaletteRenderer.CSS_CLASS};goog.ui.SelectionModel=function(a){goog.events.EventTarget.call(this);this.items_=[];this.addItems(a)};goog.inherits(goog.ui.SelectionModel,goog.events.EventTarget);goog.tagUnsealableClass(goog.ui.SelectionModel);goog.ui.SelectionModel.prototype.selectedItem_=null;goog.ui.SelectionModel.prototype.selectionHandler_=null;goog.ui.SelectionModel.prototype.getSelectionHandler=function(){return this.selectionHandler_};
+goog.ui.SelectionModel.prototype.setSelectionHandler=function(a){this.selectionHandler_=a};goog.ui.SelectionModel.prototype.getItemCount=function(){return this.items_.length};goog.ui.SelectionModel.prototype.indexOfItem=function(a){return a?goog.array.indexOf(this.items_,a):-1};goog.ui.SelectionModel.prototype.getFirst=function(){return this.items_[0]};goog.ui.SelectionModel.prototype.getLast=function(){return this.items_[this.items_.length-1]};
+goog.ui.SelectionModel.prototype.getItemAt=function(a){return this.items_[a]||null};goog.ui.SelectionModel.prototype.addItems=function(a){a&&(goog.array.forEach(a,function(a){this.selectItem_(a,!1)},this),goog.array.extend(this.items_,a))};goog.ui.SelectionModel.prototype.addItem=function(a){this.addItemAt(a,this.getItemCount())};goog.ui.SelectionModel.prototype.addItemAt=function(a,b){a&&(this.selectItem_(a,!1),goog.array.insertAt(this.items_,a,b))};
+goog.ui.SelectionModel.prototype.removeItem=function(a){a&&goog.array.remove(this.items_,a)&&a==this.selectedItem_&&(this.selectedItem_=null,this.dispatchEvent(goog.events.EventType.SELECT))};goog.ui.SelectionModel.prototype.removeItemAt=function(a){this.removeItem(this.getItemAt(a))};goog.ui.SelectionModel.prototype.getSelectedItem=function(){return this.selectedItem_};goog.ui.SelectionModel.prototype.getItems=function(){return goog.array.clone(this.items_)};
+goog.ui.SelectionModel.prototype.setSelectedItem=function(a){a!=this.selectedItem_&&(this.selectItem_(this.selectedItem_,!1),this.selectedItem_=a,this.selectItem_(a,!0));this.dispatchEvent(goog.events.EventType.SELECT)};goog.ui.SelectionModel.prototype.getSelectedIndex=function(){return this.indexOfItem(this.selectedItem_)};goog.ui.SelectionModel.prototype.setSelectedIndex=function(a){this.setSelectedItem(this.getItemAt(a))};
+goog.ui.SelectionModel.prototype.clear=function(){goog.array.clear(this.items_);this.selectedItem_=null};goog.ui.SelectionModel.prototype.disposeInternal=function(){goog.ui.SelectionModel.superClass_.disposeInternal.call(this);delete this.items_;this.selectedItem_=null};goog.ui.SelectionModel.prototype.selectItem_=function(a,b){a&&("function"==typeof this.selectionHandler_?this.selectionHandler_(a,b):"function"==typeof a.setSelected&&a.setSelected(b))};goog.ui.Palette=function(a,b,c){goog.ui.Control.call(this,a,b||goog.ui.PaletteRenderer.getInstance(),c);this.setAutoStates(goog.ui.Component.State.CHECKED|goog.ui.Component.State.SELECTED|goog.ui.Component.State.OPENED,!1);this.currentCellControl_=new goog.ui.Palette.CurrentCell_;this.currentCellControl_.setParentEventTarget(this);this.lastHighlightedIndex_=-1};goog.inherits(goog.ui.Palette,goog.ui.Control);goog.tagUnsealableClass(goog.ui.Palette);goog.ui.Palette.EventType={AFTER_HIGHLIGHT:goog.events.getUniqueId("afterhighlight")};
+goog.ui.Palette.prototype.size_=null;goog.ui.Palette.prototype.highlightedIndex_=-1;goog.ui.Palette.prototype.selectionModel_=null;goog.ui.Palette.prototype.disposeInternal=function(){goog.ui.Palette.superClass_.disposeInternal.call(this);this.selectionModel_&&(this.selectionModel_.dispose(),this.selectionModel_=null);this.size_=null;this.currentCellControl_.dispose()};
+goog.ui.Palette.prototype.setContentInternal=function(a){goog.ui.Palette.superClass_.setContentInternal.call(this,a);this.adjustSize_();this.selectionModel_?(this.selectionModel_.clear(),this.selectionModel_.addItems(a)):(this.selectionModel_=new goog.ui.SelectionModel(a),this.selectionModel_.setSelectionHandler(goog.bind(this.selectItem_,this)),this.getHandler().listen(this.selectionModel_,goog.events.EventType.SELECT,this.handleSelectionChange));this.highlightedIndex_=-1};
+goog.ui.Palette.prototype.getCaption=function(){return""};goog.ui.Palette.prototype.setCaption=function(a){};goog.ui.Palette.prototype.handleMouseOver=function(a){goog.ui.Palette.superClass_.handleMouseOver.call(this,a);var b=this.getRenderer().getContainingItem(this,a.target);b&&a.relatedTarget&&goog.dom.contains(b,a.relatedTarget)||b!=this.getHighlightedItem()&&this.setHighlightedItem(b)};
+goog.ui.Palette.prototype.handleMouseDown=function(a){goog.ui.Palette.superClass_.handleMouseDown.call(this,a);this.isActive()&&(a=this.getRenderer().getContainingItem(this,a.target),a!=this.getHighlightedItem()&&this.setHighlightedItem(a))};goog.ui.Palette.prototype.performActionInternal=function(a){var b=this.getHighlightedItem();return b?(a&&this.shouldSelectHighlightedItem_(a)&&this.setSelectedItem(b),goog.ui.Palette.superClass_.performActionInternal.call(this,a)):!1};
+goog.ui.Palette.prototype.shouldSelectHighlightedItem_=function(a){return this.getSelectedItem()?"mouseup"!=a.type?!0:!!this.getRenderer().getContainingItem(this,a.target):!0};
+goog.ui.Palette.prototype.handleKeyEvent=function(a){var b=this.getContent(),b=b?b.length:0,c=this.size_.width;if(0==b||!this.isEnabled())return!1;if(a.keyCode==goog.events.KeyCodes.ENTER||a.keyCode==goog.events.KeyCodes.SPACE)return this.performActionInternal(a);if(a.keyCode==goog.events.KeyCodes.HOME)return this.setHighlightedIndex(0),!0;if(a.keyCode==goog.events.KeyCodes.END)return this.setHighlightedIndex(b-1),!0;var d=0>this.highlightedIndex_?this.getSelectedIndex():this.highlightedIndex_;switch(a.keyCode){case goog.events.KeyCodes.LEFT:if(-1==
+d||0==d)d=b;this.setHighlightedIndex(d-1);a.preventDefault();return!0;case goog.events.KeyCodes.RIGHT:return d==b-1&&(d=-1),this.setHighlightedIndex(d+1),a.preventDefault(),!0;case goog.events.KeyCodes.UP:-1==d&&(d=b+c-1);if(d>=c)return this.setHighlightedIndex(d-c),a.preventDefault(),!0;break;case goog.events.KeyCodes.DOWN:if(-1==d&&(d=-c),d<b-c)return this.setHighlightedIndex(d+c),a.preventDefault(),!0}return!1};goog.ui.Palette.prototype.handleSelectionChange=function(a){};
+goog.ui.Palette.prototype.getSize=function(){return this.size_};goog.ui.Palette.prototype.setSize=function(a,b){if(this.getElement())throw Error(goog.ui.Component.Error.ALREADY_RENDERED);this.size_=goog.isNumber(a)?new goog.math.Size(a,b):a;this.adjustSize_()};goog.ui.Palette.prototype.getHighlightedIndex=function(){return this.highlightedIndex_};goog.ui.Palette.prototype.getHighlightedItem=function(){var a=this.getContent();return a&&a[this.highlightedIndex_]};
+goog.ui.Palette.prototype.getHighlightedCellElement_=function(){return this.getRenderer().getCellForItem(this.getHighlightedItem())};goog.ui.Palette.prototype.setHighlightedIndex=function(a){a!=this.highlightedIndex_&&(this.highlightIndex_(this.highlightedIndex_,!1),this.lastHighlightedIndex_=this.highlightedIndex_,this.highlightedIndex_=a,this.highlightIndex_(a,!0),this.dispatchEvent(goog.ui.Palette.EventType.AFTER_HIGHLIGHT))};
+goog.ui.Palette.prototype.setHighlightedItem=function(a){var b=this.getContent();this.setHighlightedIndex(b&&a?goog.array.indexOf(b,a):-1)};goog.ui.Palette.prototype.getSelectedIndex=function(){return this.selectionModel_?this.selectionModel_.getSelectedIndex():-1};goog.ui.Palette.prototype.getSelectedItem=function(){return this.selectionModel_?this.selectionModel_.getSelectedItem():null};goog.ui.Palette.prototype.setSelectedIndex=function(a){this.selectionModel_&&this.selectionModel_.setSelectedIndex(a)};
+goog.ui.Palette.prototype.setSelectedItem=function(a){this.selectionModel_&&this.selectionModel_.setSelectedItem(a)};goog.ui.Palette.prototype.highlightIndex_=function(a,b){if(this.getElement()){var c=this.getContent();if(c&&0<=a&&a<c.length){var d=this.getHighlightedCellElement_();this.currentCellControl_.getElement()!=d&&this.currentCellControl_.setElementInternal(d);this.currentCellControl_.tryHighlight(b)&&this.getRenderer().highlightCell(this,c[a],b)}}};
+goog.ui.Palette.prototype.setHighlighted=function(a){a&&-1==this.highlightedIndex_?this.setHighlightedIndex(-1<this.lastHighlightedIndex_?this.lastHighlightedIndex_:0):a||this.setHighlightedIndex(-1);goog.ui.Palette.superClass_.setHighlighted.call(this,a)};goog.ui.Palette.prototype.selectItem_=function(a,b){this.getElement()&&this.getRenderer().selectCell(this,a,b)};
+goog.ui.Palette.prototype.adjustSize_=function(){var a=this.getContent();if(a)if(this.size_&&this.size_.width){if(a=Math.ceil(a.length/this.size_.width),!goog.isNumber(this.size_.height)||this.size_.height<a)this.size_.height=a}else a=Math.ceil(Math.sqrt(a.length)),this.size_=new goog.math.Size(a,a);else this.size_=new goog.math.Size(0,0)};goog.ui.Palette.CurrentCell_=function(){goog.ui.Control.call(this,null);this.setDispatchTransitionEvents(goog.ui.Component.State.HOVER,!0)};
+goog.inherits(goog.ui.Palette.CurrentCell_,goog.ui.Control);goog.ui.Palette.CurrentCell_.prototype.tryHighlight=function(a){this.setHighlighted(a);return this.isHighlighted()==a};goog.ui.ColorPalette=function(a,b,c){this.colors_=a||[];goog.ui.Palette.call(this,null,b||goog.ui.PaletteRenderer.getInstance(),c);this.setColors(this.colors_)};goog.inherits(goog.ui.ColorPalette,goog.ui.Palette);goog.tagUnsealableClass(goog.ui.ColorPalette);goog.ui.ColorPalette.prototype.normalizedColors_=null;goog.ui.ColorPalette.prototype.labels_=null;goog.ui.ColorPalette.prototype.getColors=function(){return this.colors_};
+goog.ui.ColorPalette.prototype.setColors=function(a,b){this.colors_=a;this.labels_=b||null;this.normalizedColors_=null;this.setContent(this.createColorNodes())};goog.ui.ColorPalette.prototype.getSelectedColor=function(){var a=this.getSelectedItem();return a?(a=goog.style.getStyle(a,"background-color"),goog.ui.ColorPalette.parseColor_(a)):null};
+goog.ui.ColorPalette.prototype.setSelectedColor=function(a){a=goog.ui.ColorPalette.parseColor_(a);this.normalizedColors_||(this.normalizedColors_=goog.array.map(this.colors_,function(a){return goog.ui.ColorPalette.parseColor_(a)}));this.setSelectedIndex(a?goog.array.indexOf(this.normalizedColors_,a):-1)};
+goog.ui.ColorPalette.prototype.createColorNodes=function(){return goog.array.map(this.colors_,function(a,b){var c=this.getDomHelper().createDom("DIV",{"class":this.getRenderer().getCssClass()+"-colorswatch",style:"background-color:"+a});c.title=this.labels_&&this.labels_[b]?this.labels_[b]:"#"==a.charAt(0)?"RGB ("+goog.color.hexToRgb(a).join(", ")+")":a;return c},this)};goog.ui.ColorPalette.parseColor_=function(a){if(a)try{return goog.color.parse(a).hex}catch(b){}return null};goog.ui.ColorPicker=function(a,b){goog.ui.Component.call(this,a);this.colorPalette_=b||null;this.getHandler().listen(this,goog.ui.Component.EventType.ACTION,this.onColorPaletteAction_)};goog.inherits(goog.ui.ColorPicker,goog.ui.Component);goog.ui.ColorPicker.DEFAULT_NUM_COLS=5;goog.ui.ColorPicker.EventType={CHANGE:"change"};goog.ui.ColorPicker.prototype.focusable_=!0;goog.ui.ColorPicker.prototype.getColors=function(){return this.colorPalette_?this.colorPalette_.getColors():null};
+goog.ui.ColorPicker.prototype.setColors=function(a){this.colorPalette_?this.colorPalette_.setColors(a):this.createColorPalette_(a)};goog.ui.ColorPicker.prototype.addColors=function(a){this.setColors(a)};goog.ui.ColorPicker.prototype.setSize=function(a){this.colorPalette_||this.createColorPalette_([]);this.colorPalette_.setSize(a)};goog.ui.ColorPicker.prototype.getSize=function(){return this.colorPalette_?this.colorPalette_.getSize():null};goog.ui.ColorPicker.prototype.setColumnCount=function(a){this.setSize(a)};
+goog.ui.ColorPicker.prototype.getSelectedIndex=function(){return this.colorPalette_?this.colorPalette_.getSelectedIndex():-1};goog.ui.ColorPicker.prototype.setSelectedIndex=function(a){this.colorPalette_&&this.colorPalette_.setSelectedIndex(a)};goog.ui.ColorPicker.prototype.getSelectedColor=function(){return this.colorPalette_?this.colorPalette_.getSelectedColor():null};goog.ui.ColorPicker.prototype.setSelectedColor=function(a){this.colorPalette_&&this.colorPalette_.setSelectedColor(a)};
+goog.ui.ColorPicker.prototype.isFocusable=function(){return this.focusable_};goog.ui.ColorPicker.prototype.setFocusable=function(a){this.focusable_=a;this.colorPalette_&&this.colorPalette_.setSupportedState(goog.ui.Component.State.FOCUSED,a)};goog.ui.ColorPicker.prototype.canDecorate=function(a){return!1};
+goog.ui.ColorPicker.prototype.enterDocument=function(){goog.ui.ColorPicker.superClass_.enterDocument.call(this);this.colorPalette_&&this.colorPalette_.render(this.getElement());this.getElement().unselectable="on"};goog.ui.ColorPicker.prototype.disposeInternal=function(){goog.ui.ColorPicker.superClass_.disposeInternal.call(this);this.colorPalette_&&(this.colorPalette_.dispose(),this.colorPalette_=null)};goog.ui.ColorPicker.prototype.focus=function(){this.colorPalette_&&this.colorPalette_.getElement().focus()};
+goog.ui.ColorPicker.prototype.onColorPaletteAction_=function(a){a.stopPropagation();this.dispatchEvent(goog.ui.ColorPicker.EventType.CHANGE)};goog.ui.ColorPicker.prototype.createColorPalette_=function(a){a=new goog.ui.ColorPalette(a,null,this.getDomHelper());a.setSize(goog.ui.ColorPicker.DEFAULT_NUM_COLS);a.setSupportedState(goog.ui.Component.State.FOCUSED,this.focusable_);this.addChild(a);this.colorPalette_=a;this.isInDocument()&&this.colorPalette_.render(this.getElement())};
+goog.ui.ColorPicker.createSimpleColorGrid=function(a){a=new goog.ui.ColorPicker(a);a.setSize(7);a.setColors(goog.ui.ColorPicker.SIMPLE_GRID_COLORS);return a};goog.ui.ColorPicker.SIMPLE_GRID_COLORS="#ffffff #cccccc #c0c0c0 #999999 #666666 #333333 #000000 #ffcccc #ff6666 #ff0000 #cc0000 #990000 #660000 #330000 #ffcc99 #ff9966 #ff9900 #ff6600 #cc6600 #993300 #663300 #ffff99 #ffff66 #ffcc66 #ffcc33 #cc9933 #996633 #663333 #ffffcc #ffff33 #ffff00 #ffcc00 #999900 #666600 #333300 #99ff99 #66ff99 #33ff33 #33cc00 #009900 #006600 #003300 #99ffff #33ffff #66cccc #00cccc #339999 #336666 #003333 #ccffff #66ffff #33ccff #3366ff #3333ff #000099 #000066 #ccccff #9999ff #6666cc #6633ff #6600cc #333399 #330099 #ffccff #ff99ff #cc66cc #cc33cc #993399 #663366 #330033".split(" ");goog.events.FocusHandler=function(a){goog.events.EventTarget.call(this);this.element_=a;a=goog.userAgent.IE?"focusout":"blur";this.listenKeyIn_=goog.events.listen(this.element_,goog.userAgent.IE?"focusin":"focus",this,!goog.userAgent.IE);this.listenKeyOut_=goog.events.listen(this.element_,a,this,!goog.userAgent.IE)};goog.inherits(goog.events.FocusHandler,goog.events.EventTarget);goog.events.FocusHandler.EventType={FOCUSIN:"focusin",FOCUSOUT:"focusout"};
+goog.events.FocusHandler.prototype.handleEvent=function(a){var b=a.getBrowserEvent(),b=new goog.events.BrowserEvent(b);b.type="focusin"==a.type||"focus"==a.type?goog.events.FocusHandler.EventType.FOCUSIN:goog.events.FocusHandler.EventType.FOCUSOUT;this.dispatchEvent(b)};goog.events.FocusHandler.prototype.disposeInternal=function(){goog.events.FocusHandler.superClass_.disposeInternal.call(this);goog.events.unlistenByKey(this.listenKeyIn_);goog.events.unlistenByKey(this.listenKeyOut_);delete this.element_};goog.structs={};goog.structs.getCount=function(a){return a.getCount&&"function"==typeof a.getCount?a.getCount():goog.isArrayLike(a)||goog.isString(a)?a.length:goog.object.getCount(a)};goog.structs.getValues=function(a){if(a.getValues&&"function"==typeof a.getValues)return a.getValues();if(goog.isString(a))return a.split("");if(goog.isArrayLike(a)){for(var b=[],c=a.length,d=0;d<c;d++)b.push(a[d]);return b}return goog.object.getValues(a)};
+goog.structs.getKeys=function(a){if(a.getKeys&&"function"==typeof a.getKeys)return a.getKeys();if(!a.getValues||"function"!=typeof a.getValues){if(goog.isArrayLike(a)||goog.isString(a)){var b=[];a=a.length;for(var c=0;c<a;c++)b.push(c);return b}return goog.object.getKeys(a)}};
+goog.structs.contains=function(a,b){return a.contains&&"function"==typeof a.contains?a.contains(b):a.containsValue&&"function"==typeof a.containsValue?a.containsValue(b):goog.isArrayLike(a)||goog.isString(a)?goog.array.contains(a,b):goog.object.containsValue(a,b)};goog.structs.isEmpty=function(a){return a.isEmpty&&"function"==typeof a.isEmpty?a.isEmpty():goog.isArrayLike(a)||goog.isString(a)?goog.array.isEmpty(a):goog.object.isEmpty(a)};
+goog.structs.clear=function(a){a.clear&&"function"==typeof a.clear?a.clear():goog.isArrayLike(a)?goog.array.clear(a):goog.object.clear(a)};goog.structs.forEach=function(a,b,c){if(a.forEach&&"function"==typeof a.forEach)a.forEach(b,c);else if(goog.isArrayLike(a)||goog.isString(a))goog.array.forEach(a,b,c);else for(var d=goog.structs.getKeys(a),e=goog.structs.getValues(a),f=e.length,g=0;g<f;g++)b.call(c,e[g],d&&d[g],a)};
+goog.structs.filter=function(a,b,c){if("function"==typeof a.filter)return a.filter(b,c);if(goog.isArrayLike(a)||goog.isString(a))return goog.array.filter(a,b,c);var d,e=goog.structs.getKeys(a),f=goog.structs.getValues(a),g=f.length;if(e){d={};for(var h=0;h<g;h++)b.call(c,f[h],e[h],a)&&(d[e[h]]=f[h])}else for(d=[],h=0;h<g;h++)b.call(c,f[h],void 0,a)&&d.push(f[h]);return d};
+goog.structs.map=function(a,b,c){if("function"==typeof a.map)return a.map(b,c);if(goog.isArrayLike(a)||goog.isString(a))return goog.array.map(a,b,c);var d,e=goog.structs.getKeys(a),f=goog.structs.getValues(a),g=f.length;if(e){d={};for(var h=0;h<g;h++)d[e[h]]=b.call(c,f[h],e[h],a)}else for(d=[],h=0;h<g;h++)d[h]=b.call(c,f[h],void 0,a);return d};
+goog.structs.some=function(a,b,c){if("function"==typeof a.some)return a.some(b,c);if(goog.isArrayLike(a)||goog.isString(a))return goog.array.some(a,b,c);for(var d=goog.structs.getKeys(a),e=goog.structs.getValues(a),f=e.length,g=0;g<f;g++)if(b.call(c,e[g],d&&d[g],a))return!0;return!1};
+goog.structs.every=function(a,b,c){if("function"==typeof a.every)return a.every(b,c);if(goog.isArrayLike(a)||goog.isString(a))return goog.array.every(a,b,c);for(var d=goog.structs.getKeys(a),e=goog.structs.getValues(a),f=e.length,g=0;g<f;g++)if(!b.call(c,e[g],d&&d[g],a))return!1;return!0};goog.structs.Collection=function(){};goog.structs.Map=function(a,b){this.map_={};this.keys_=[];this.version_=this.count_=0;var c=arguments.length;if(1<c){if(c%2)throw Error("Uneven number of arguments");for(var d=0;d<c;d+=2)this.set(arguments[d],arguments[d+1])}else a&&this.addAll(a)};goog.structs.Map.prototype.getCount=function(){return this.count_};goog.structs.Map.prototype.getValues=function(){this.cleanupKeysArray_();for(var a=[],b=0;b<this.keys_.length;b++)a.push(this.map_[this.keys_[b]]);return a};
+goog.structs.Map.prototype.getKeys=function(){this.cleanupKeysArray_();return this.keys_.concat()};goog.structs.Map.prototype.containsKey=function(a){return goog.structs.Map.hasKey_(this.map_,a)};goog.structs.Map.prototype.containsValue=function(a){for(var b=0;b<this.keys_.length;b++){var c=this.keys_[b];if(goog.structs.Map.hasKey_(this.map_,c)&&this.map_[c]==a)return!0}return!1};
+goog.structs.Map.prototype.equals=function(a,b){if(this===a)return!0;if(this.count_!=a.getCount())return!1;var c=b||goog.structs.Map.defaultEquals;this.cleanupKeysArray_();for(var d,e=0;d=this.keys_[e];e++)if(!c(this.get(d),a.get(d)))return!1;return!0};goog.structs.Map.defaultEquals=function(a,b){return a===b};goog.structs.Map.prototype.isEmpty=function(){return 0==this.count_};goog.structs.Map.prototype.clear=function(){this.map_={};this.version_=this.count_=this.keys_.length=0};
+goog.structs.Map.prototype.remove=function(a){return goog.structs.Map.hasKey_(this.map_,a)?(delete this.map_[a],this.count_--,this.version_++,this.keys_.length>2*this.count_&&this.cleanupKeysArray_(),!0):!1};
+goog.structs.Map.prototype.cleanupKeysArray_=function(){if(this.count_!=this.keys_.length){for(var a=0,b=0;a<this.keys_.length;){var c=this.keys_[a];goog.structs.Map.hasKey_(this.map_,c)&&(this.keys_[b++]=c);a++}this.keys_.length=b}if(this.count_!=this.keys_.length){for(var d={},b=a=0;a<this.keys_.length;)c=this.keys_[a],goog.structs.Map.hasKey_(d,c)||(this.keys_[b++]=c,d[c]=1),a++;this.keys_.length=b}};
+goog.structs.Map.prototype.get=function(a,b){return goog.structs.Map.hasKey_(this.map_,a)?this.map_[a]:b};goog.structs.Map.prototype.set=function(a,b){goog.structs.Map.hasKey_(this.map_,a)||(this.count_++,this.keys_.push(a),this.version_++);this.map_[a]=b};goog.structs.Map.prototype.addAll=function(a){var b;a instanceof goog.structs.Map?(b=a.getKeys(),a=a.getValues()):(b=goog.object.getKeys(a),a=goog.object.getValues(a));for(var c=0;c<b.length;c++)this.set(b[c],a[c])};
+goog.structs.Map.prototype.forEach=function(a,b){for(var c=this.getKeys(),d=0;d<c.length;d++){var e=c[d],f=this.get(e);a.call(b,f,e,this)}};goog.structs.Map.prototype.clone=function(){return new goog.structs.Map(this)};goog.structs.Map.prototype.transpose=function(){for(var a=new goog.structs.Map,b=0;b<this.keys_.length;b++){var c=this.keys_[b];a.set(this.map_[c],c)}return a};
+goog.structs.Map.prototype.toObject=function(){this.cleanupKeysArray_();for(var a={},b=0;b<this.keys_.length;b++){var c=this.keys_[b];a[c]=this.map_[c]}return a};goog.structs.Map.prototype.getKeyIterator=function(){return this.__iterator__(!0)};goog.structs.Map.prototype.getValueIterator=function(){return this.__iterator__(!1)};
+goog.structs.Map.prototype.__iterator__=function(a){this.cleanupKeysArray_();var b=0,c=this.version_,d=this,e=new goog.iter.Iterator;e.next=function(){if(c!=d.version_)throw Error("The map has changed since the iterator was created");if(b>=d.keys_.length)throw goog.iter.StopIteration;var e=d.keys_[b++];return a?e:d.map_[e]};return e};goog.structs.Map.hasKey_=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};goog.structs.Set=function(a){this.map_=new goog.structs.Map;a&&this.addAll(a)};goog.structs.Set.getKey_=function(a){var b=typeof a;return"object"==b&&a||"function"==b?"o"+goog.getUid(a):b.substr(0,1)+a};goog.structs.Set.prototype.getCount=function(){return this.map_.getCount()};goog.structs.Set.prototype.add=function(a){this.map_.set(goog.structs.Set.getKey_(a),a)};goog.structs.Set.prototype.addAll=function(a){a=goog.structs.getValues(a);for(var b=a.length,c=0;c<b;c++)this.add(a[c])};
+goog.structs.Set.prototype.removeAll=function(a){a=goog.structs.getValues(a);for(var b=a.length,c=0;c<b;c++)this.remove(a[c])};goog.structs.Set.prototype.remove=function(a){return this.map_.remove(goog.structs.Set.getKey_(a))};goog.structs.Set.prototype.clear=function(){this.map_.clear()};goog.structs.Set.prototype.isEmpty=function(){return this.map_.isEmpty()};goog.structs.Set.prototype.contains=function(a){return this.map_.containsKey(goog.structs.Set.getKey_(a))};
+goog.structs.Set.prototype.containsAll=function(a){return goog.structs.every(a,this.contains,this)};goog.structs.Set.prototype.intersection=function(a){var b=new goog.structs.Set;a=goog.structs.getValues(a);for(var c=0;c<a.length;c++){var d=a[c];this.contains(d)&&b.add(d)}return b};goog.structs.Set.prototype.difference=function(a){var b=this.clone();b.removeAll(a);return b};goog.structs.Set.prototype.getValues=function(){return this.map_.getValues()};goog.structs.Set.prototype.clone=function(){return new goog.structs.Set(this)};
+goog.structs.Set.prototype.equals=function(a){return this.getCount()==goog.structs.getCount(a)&&this.isSubsetOf(a)};goog.structs.Set.prototype.isSubsetOf=function(a){var b=goog.structs.getCount(a);if(this.getCount()>b)return!1;!(a instanceof goog.structs.Set)&&5<b&&(a=new goog.structs.Set(a));return goog.structs.every(this,function(b){return goog.structs.contains(a,b)})};goog.structs.Set.prototype.__iterator__=function(a){return this.map_.__iterator__(!1)};goog.debug.LOGGING_ENABLED=goog.DEBUG;goog.debug.FORCE_SLOPPY_STACKS=!1;goog.debug.catchErrors=function(a,b,c){c=c||goog.global;var d=c.onerror,e=!!b;goog.userAgent.WEBKIT&&!goog.userAgent.isVersionOrHigher("535.3")&&(e=!e);c.onerror=function(b,c,h,k,l){d&&d(b,c,h,k,l);a({message:b,fileName:c,line:h,col:k,error:l});return e}};
+goog.debug.expose=function(a,b){if("undefined"==typeof a)return"undefined";if(null==a)return"NULL";var c=[],d;for(d in a)if(b||!goog.isFunction(a[d])){var e=d+" = ";try{e+=a[d]}catch(f){e+="*** "+f+" ***"}c.push(e)}return c.join("\n")};
+goog.debug.deepExpose=function(a,b){var c=[],d=function(a,f,g){var e=f+"  ";g=new goog.structs.Set(g);try{if(goog.isDef(a))if(goog.isNull(a))c.push("NULL");else if(goog.isString(a))c.push('"'+a.replace(/\n/g,"\n"+f)+'"');else if(goog.isFunction(a))c.push(String(a).replace(/\n/g,"\n"+f));else if(goog.isObject(a))if(g.contains(a))c.push("*** reference loop detected ***");else{g.add(a);c.push("{");for(var k in a)if(b||!goog.isFunction(a[k]))c.push("\n"),c.push(e),c.push(k+" = "),d(a[k],e,g);c.push("\n"+
+f+"}")}else c.push(a);else c.push("undefined")}catch(l){c.push("*** "+l+" ***")}};d(a,"",new goog.structs.Set);return c.join("")};goog.debug.exposeArray=function(a){for(var b=[],c=0;c<a.length;c++)goog.isArray(a[c])?b.push(goog.debug.exposeArray(a[c])):b.push(a[c]);return"[ "+b.join(", ")+" ]"};goog.debug.exposeException=function(a,b){var c=goog.debug.exposeExceptionAsHtml(a,b);return goog.html.SafeHtml.unwrap(c)};
+goog.debug.exposeExceptionAsHtml=function(a,b){try{var c=goog.debug.normalizeErrorObject(a),d=goog.debug.createViewSourceUrl_(c.fileName);return goog.html.SafeHtml.concat(goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces("Message: "+c.message+"\nUrl: "),goog.html.SafeHtml.create("a",{href:d,target:"_new"},c.fileName),goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces("\nLine: "+c.lineNumber+"\n\nBrowser stack:\n"+c.stack+"-> [end]\n\nJS stack traversal:\n"+goog.debug.getStacktrace(b)+
+"-> "))}catch(e){return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces("Exception trying to expose exception! You win, we lose. "+e)}};
+goog.debug.createViewSourceUrl_=function(a){goog.isDefAndNotNull(a)||(a="");if(!/^https?:\/\//i.test(a))return goog.html.SafeUrl.fromConstant(goog.string.Const.from("sanitizedviewsrc"));a=goog.html.SafeUrl.sanitize(a);return goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract(goog.string.Const.from("view-source scheme plus HTTP/HTTPS URL"),"view-source:"+goog.html.SafeUrl.unwrap(a))};
+goog.debug.normalizeErrorObject=function(a){var b=goog.getObjectByName("window.location.href");if(goog.isString(a))return{message:a,name:"Unknown error",lineNumber:"Not available",fileName:b,stack:"Not available"};var c,d,e=!1;try{c=a.lineNumber||a.line||"Not available"}catch(f){c="Not available",e=!0}try{d=a.fileName||a.filename||a.sourceURL||goog.global.$googDebugFname||b}catch(f){d="Not available",e=!0}return!e&&a.lineNumber&&a.fileName&&a.stack&&a.message&&a.name?a:{message:a.message||"Not available",
+name:a.name||"UnknownError",lineNumber:c,fileName:d,stack:a.stack||"Not available"}};goog.debug.enhanceError=function(a,b){var c;a instanceof Error?c=a:(c=Error(a),Error.captureStackTrace&&Error.captureStackTrace(c,goog.debug.enhanceError));c.stack||(c.stack=goog.debug.getStacktrace(goog.debug.enhanceError));if(b){for(var d=0;c["message"+d];)++d;c["message"+d]=String(b)}return c};
+goog.debug.getStacktraceSimple=function(a){if(!goog.debug.FORCE_SLOPPY_STACKS){var b=goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);if(b)return b}for(var b=[],c=arguments.callee.caller,d=0;c&&(!a||d<a);){b.push(goog.debug.getFunctionName(c));b.push("()\n");try{c=c.caller}catch(e){b.push("[exception trying to get caller]\n");break}d++;if(d>=goog.debug.MAX_STACK_DEPTH){b.push("[...long stack...]");break}}a&&d>=a?b.push("[...reached max depth limit...]"):b.push("[end]");return b.join("")};
+goog.debug.MAX_STACK_DEPTH=50;goog.debug.getNativeStackTrace_=function(a){var b=Error();if(Error.captureStackTrace)return Error.captureStackTrace(b,a),String(b.stack);try{throw b;}catch(c){b=c}return(a=b.stack)?String(a):null};goog.debug.getStacktrace=function(a){var b;goog.debug.FORCE_SLOPPY_STACKS||(b=goog.debug.getNativeStackTrace_(a||goog.debug.getStacktrace));b||(b=goog.debug.getStacktraceHelper_(a||arguments.callee.caller,[]));return b};
+goog.debug.getStacktraceHelper_=function(a,b){var c=[];if(goog.array.contains(b,a))c.push("[...circular reference...]");else if(a&&b.length<goog.debug.MAX_STACK_DEPTH){c.push(goog.debug.getFunctionName(a)+"(");for(var d=a.arguments,e=0;d&&e<d.length;e++){0<e&&c.push(", ");var f;f=d[e];switch(typeof f){case "object":f=f?"object":"null";break;case "string":break;case "number":f=String(f);break;case "boolean":f=f?"true":"false";break;case "function":f=(f=goog.debug.getFunctionName(f))?f:"[fn]";break;
+default:f=typeof f}40<f.length&&(f=f.substr(0,40)+"...");c.push(f)}b.push(a);c.push(")\n");try{c.push(goog.debug.getStacktraceHelper_(a.caller,b))}catch(g){c.push("[exception trying to get caller]\n")}}else a?c.push("[...long stack...]"):c.push("[end]");return c.join("")};goog.debug.setFunctionResolver=function(a){goog.debug.fnNameResolver_=a};
+goog.debug.getFunctionName=function(a){if(goog.debug.fnNameCache_[a])return goog.debug.fnNameCache_[a];if(goog.debug.fnNameResolver_){var b=goog.debug.fnNameResolver_(a);if(b)return goog.debug.fnNameCache_[a]=b}a=String(a);goog.debug.fnNameCache_[a]||(b=/function ([^\(]+)/.exec(a),goog.debug.fnNameCache_[a]=b?b[1]:"[Anonymous]");return goog.debug.fnNameCache_[a]};
+goog.debug.makeWhitespaceVisible=function(a){return a.replace(/ /g,"[_]").replace(/\f/g,"[f]").replace(/\n/g,"[n]\n").replace(/\r/g,"[r]").replace(/\t/g,"[t]")};goog.debug.runtimeType=function(a){return a instanceof Function?a.displayName||a.name||"unknown type name":a instanceof Object?a.constructor.displayName||a.constructor.name||Object.prototype.toString.call(a):null===a?"null":typeof a};goog.debug.fnNameCache_={};goog.debug.LogRecord=function(a,b,c,d,e){this.reset(a,b,c,d,e)};goog.debug.LogRecord.prototype.sequenceNumber_=0;goog.debug.LogRecord.prototype.exception_=null;goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS=!0;goog.debug.LogRecord.nextSequenceNumber_=0;
+goog.debug.LogRecord.prototype.reset=function(a,b,c,d,e){goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS&&(this.sequenceNumber_="number"==typeof e?e:goog.debug.LogRecord.nextSequenceNumber_++);this.time_=d||goog.now();this.level_=a;this.msg_=b;this.loggerName_=c;delete this.exception_};goog.debug.LogRecord.prototype.getLoggerName=function(){return this.loggerName_};goog.debug.LogRecord.prototype.getException=function(){return this.exception_};
+goog.debug.LogRecord.prototype.setException=function(a){this.exception_=a};goog.debug.LogRecord.prototype.setLoggerName=function(a){this.loggerName_=a};goog.debug.LogRecord.prototype.getLevel=function(){return this.level_};goog.debug.LogRecord.prototype.setLevel=function(a){this.level_=a};goog.debug.LogRecord.prototype.getMessage=function(){return this.msg_};goog.debug.LogRecord.prototype.setMessage=function(a){this.msg_=a};goog.debug.LogRecord.prototype.getMillis=function(){return this.time_};
+goog.debug.LogRecord.prototype.setMillis=function(a){this.time_=a};goog.debug.LogRecord.prototype.getSequenceNumber=function(){return this.sequenceNumber_};goog.debug.LogBuffer=function(){goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),"Cannot use goog.debug.LogBuffer without defining goog.debug.LogBuffer.CAPACITY.");this.clear()};goog.debug.LogBuffer.getInstance=function(){goog.debug.LogBuffer.instance_||(goog.debug.LogBuffer.instance_=new goog.debug.LogBuffer);return goog.debug.LogBuffer.instance_};goog.debug.LogBuffer.CAPACITY=0;
+goog.debug.LogBuffer.prototype.addRecord=function(a,b,c){var d=(this.curIndex_+1)%goog.debug.LogBuffer.CAPACITY;this.curIndex_=d;if(this.isFull_)return d=this.buffer_[d],d.reset(a,b,c),d;this.isFull_=d==goog.debug.LogBuffer.CAPACITY-1;return this.buffer_[d]=new goog.debug.LogRecord(a,b,c)};goog.debug.LogBuffer.isBufferingEnabled=function(){return 0<goog.debug.LogBuffer.CAPACITY};
+goog.debug.LogBuffer.prototype.clear=function(){this.buffer_=Array(goog.debug.LogBuffer.CAPACITY);this.curIndex_=-1;this.isFull_=!1};goog.debug.LogBuffer.prototype.forEachRecord=function(a){var b=this.buffer_;if(b[0]){var c=this.curIndex_,d=this.isFull_?c:-1;do d=(d+1)%goog.debug.LogBuffer.CAPACITY,a(b[d]);while(d!=c)}};goog.debug.Logger=function(a){this.name_=a;this.handlers_=this.children_=this.level_=this.parent_=null};goog.debug.Logger.ROOT_LOGGER_NAME="";goog.debug.Logger.ENABLE_HIERARCHY=!0;goog.debug.Logger.ENABLE_HIERARCHY||(goog.debug.Logger.rootHandlers_=[]);goog.debug.Logger.Level=function(a,b){this.name=a;this.value=b};goog.debug.Logger.Level.prototype.toString=function(){return this.name};goog.debug.Logger.Level.OFF=new goog.debug.Logger.Level("OFF",Infinity);
+goog.debug.Logger.Level.SHOUT=new goog.debug.Logger.Level("SHOUT",1200);goog.debug.Logger.Level.SEVERE=new goog.debug.Logger.Level("SEVERE",1E3);goog.debug.Logger.Level.WARNING=new goog.debug.Logger.Level("WARNING",900);goog.debug.Logger.Level.INFO=new goog.debug.Logger.Level("INFO",800);goog.debug.Logger.Level.CONFIG=new goog.debug.Logger.Level("CONFIG",700);goog.debug.Logger.Level.FINE=new goog.debug.Logger.Level("FINE",500);goog.debug.Logger.Level.FINER=new goog.debug.Logger.Level("FINER",400);
+goog.debug.Logger.Level.FINEST=new goog.debug.Logger.Level("FINEST",300);goog.debug.Logger.Level.ALL=new goog.debug.Logger.Level("ALL",0);goog.debug.Logger.Level.PREDEFINED_LEVELS=[goog.debug.Logger.Level.OFF,goog.debug.Logger.Level.SHOUT,goog.debug.Logger.Level.SEVERE,goog.debug.Logger.Level.WARNING,goog.debug.Logger.Level.INFO,goog.debug.Logger.Level.CONFIG,goog.debug.Logger.Level.FINE,goog.debug.Logger.Level.FINER,goog.debug.Logger.Level.FINEST,goog.debug.Logger.Level.ALL];
+goog.debug.Logger.Level.predefinedLevelsCache_=null;goog.debug.Logger.Level.createPredefinedLevelsCache_=function(){goog.debug.Logger.Level.predefinedLevelsCache_={};for(var a=0,b;b=goog.debug.Logger.Level.PREDEFINED_LEVELS[a];a++)goog.debug.Logger.Level.predefinedLevelsCache_[b.value]=b,goog.debug.Logger.Level.predefinedLevelsCache_[b.name]=b};
+goog.debug.Logger.Level.getPredefinedLevel=function(a){goog.debug.Logger.Level.predefinedLevelsCache_||goog.debug.Logger.Level.createPredefinedLevelsCache_();return goog.debug.Logger.Level.predefinedLevelsCache_[a]||null};
+goog.debug.Logger.Level.getPredefinedLevelByValue=function(a){goog.debug.Logger.Level.predefinedLevelsCache_||goog.debug.Logger.Level.createPredefinedLevelsCache_();if(a in goog.debug.Logger.Level.predefinedLevelsCache_)return goog.debug.Logger.Level.predefinedLevelsCache_[a];for(var b=0;b<goog.debug.Logger.Level.PREDEFINED_LEVELS.length;++b){var c=goog.debug.Logger.Level.PREDEFINED_LEVELS[b];if(c.value<=a)return c}return null};goog.debug.Logger.getLogger=function(a){return goog.debug.LogManager.getLogger(a)};
+goog.debug.Logger.logToProfilers=function(a){goog.global.console&&(goog.global.console.timeStamp?goog.global.console.timeStamp(a):goog.global.console.markTimeline&&goog.global.console.markTimeline(a));goog.global.msWriteProfilerMark&&goog.global.msWriteProfilerMark(a)};goog.debug.Logger.prototype.getName=function(){return this.name_};
+goog.debug.Logger.prototype.addHandler=function(a){goog.debug.LOGGING_ENABLED&&(goog.debug.Logger.ENABLE_HIERARCHY?(this.handlers_||(this.handlers_=[]),this.handlers_.push(a)):(goog.asserts.assert(!this.name_,"Cannot call addHandler on a non-root logger when goog.debug.Logger.ENABLE_HIERARCHY is false."),goog.debug.Logger.rootHandlers_.push(a)))};
+goog.debug.Logger.prototype.removeHandler=function(a){if(goog.debug.LOGGING_ENABLED){var b=goog.debug.Logger.ENABLE_HIERARCHY?this.handlers_:goog.debug.Logger.rootHandlers_;return!!b&&goog.array.remove(b,a)}return!1};goog.debug.Logger.prototype.getParent=function(){return this.parent_};goog.debug.Logger.prototype.getChildren=function(){this.children_||(this.children_={});return this.children_};
+goog.debug.Logger.prototype.setLevel=function(a){goog.debug.LOGGING_ENABLED&&(goog.debug.Logger.ENABLE_HIERARCHY?this.level_=a:(goog.asserts.assert(!this.name_,"Cannot call setLevel() on a non-root logger when goog.debug.Logger.ENABLE_HIERARCHY is false."),goog.debug.Logger.rootLevel_=a))};goog.debug.Logger.prototype.getLevel=function(){return goog.debug.LOGGING_ENABLED?this.level_:goog.debug.Logger.Level.OFF};
+goog.debug.Logger.prototype.getEffectiveLevel=function(){if(!goog.debug.LOGGING_ENABLED)return goog.debug.Logger.Level.OFF;if(!goog.debug.Logger.ENABLE_HIERARCHY)return goog.debug.Logger.rootLevel_;if(this.level_)return this.level_;if(this.parent_)return this.parent_.getEffectiveLevel();goog.asserts.fail("Root logger has no level set.");return null};goog.debug.Logger.prototype.isLoggable=function(a){return goog.debug.LOGGING_ENABLED&&a.value>=this.getEffectiveLevel().value};
+goog.debug.Logger.prototype.log=function(a,b,c){goog.debug.LOGGING_ENABLED&&this.isLoggable(a)&&(goog.isFunction(b)&&(b=b()),this.doLogRecord_(this.getLogRecord(a,b,c)))};goog.debug.Logger.prototype.getLogRecord=function(a,b,c){a=goog.debug.LogBuffer.isBufferingEnabled()?goog.debug.LogBuffer.getInstance().addRecord(a,b,this.name_):new goog.debug.LogRecord(a,String(b),this.name_);c&&a.setException(c);return a};
+goog.debug.Logger.prototype.shout=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.SHOUT,a,b)};goog.debug.Logger.prototype.severe=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.SEVERE,a,b)};goog.debug.Logger.prototype.warning=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.WARNING,a,b)};goog.debug.Logger.prototype.info=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.INFO,a,b)};
+goog.debug.Logger.prototype.config=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.CONFIG,a,b)};goog.debug.Logger.prototype.fine=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.FINE,a,b)};goog.debug.Logger.prototype.finer=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.FINER,a,b)};goog.debug.Logger.prototype.finest=function(a,b){goog.debug.LOGGING_ENABLED&&this.log(goog.debug.Logger.Level.FINEST,a,b)};
+goog.debug.Logger.prototype.logRecord=function(a){goog.debug.LOGGING_ENABLED&&this.isLoggable(a.getLevel())&&this.doLogRecord_(a)};goog.debug.Logger.prototype.doLogRecord_=function(a){goog.debug.Logger.logToProfilers("log:"+a.getMessage());if(goog.debug.Logger.ENABLE_HIERARCHY)for(var b=this;b;)b.callPublish_(a),b=b.getParent();else for(var b=0,c;c=goog.debug.Logger.rootHandlers_[b++];)c(a)};goog.debug.Logger.prototype.callPublish_=function(a){if(this.handlers_)for(var b=0,c;c=this.handlers_[b];b++)c(a)};
+goog.debug.Logger.prototype.setParent_=function(a){this.parent_=a};goog.debug.Logger.prototype.addChild_=function(a,b){this.getChildren()[a]=b};goog.debug.LogManager={};goog.debug.LogManager.loggers_={};goog.debug.LogManager.rootLogger_=null;
+goog.debug.LogManager.initialize=function(){goog.debug.LogManager.rootLogger_||(goog.debug.LogManager.rootLogger_=new goog.debug.Logger(goog.debug.Logger.ROOT_LOGGER_NAME),goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME]=goog.debug.LogManager.rootLogger_,goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG))};goog.debug.LogManager.getLoggers=function(){return goog.debug.LogManager.loggers_};
+goog.debug.LogManager.getRoot=function(){goog.debug.LogManager.initialize();return goog.debug.LogManager.rootLogger_};goog.debug.LogManager.getLogger=function(a){goog.debug.LogManager.initialize();return goog.debug.LogManager.loggers_[a]||goog.debug.LogManager.createLogger_(a)};goog.debug.LogManager.createFunctionForCatchErrors=function(a){return function(b){(a||goog.debug.LogManager.getRoot()).severe("Error: "+b.message+" ("+b.fileName+" @ Line: "+b.line+")")}};
+goog.debug.LogManager.createLogger_=function(a){var b=new goog.debug.Logger(a);if(goog.debug.Logger.ENABLE_HIERARCHY){var c=a.lastIndexOf("."),d=a.substr(0,c),c=a.substr(c+1),d=goog.debug.LogManager.getLogger(d);d.addChild_(c,b);b.setParent_(d)}return goog.debug.LogManager.loggers_[a]=b};goog.log={};goog.log.ENABLED=goog.debug.LOGGING_ENABLED;goog.log.ROOT_LOGGER_NAME=goog.debug.Logger.ROOT_LOGGER_NAME;goog.log.Logger=goog.debug.Logger;goog.log.Level=goog.debug.Logger.Level;goog.log.LogRecord=goog.debug.LogRecord;goog.log.getLogger=function(a,b){if(goog.log.ENABLED){var c=goog.debug.LogManager.getLogger(a);b&&c&&c.setLevel(b);return c}return null};goog.log.addHandler=function(a,b){goog.log.ENABLED&&a&&a.addHandler(b)};
+goog.log.removeHandler=function(a,b){return goog.log.ENABLED&&a?a.removeHandler(b):!1};goog.log.log=function(a,b,c,d){goog.log.ENABLED&&a&&a.log(b,c,d)};goog.log.error=function(a,b,c){goog.log.ENABLED&&a&&a.severe(b,c)};goog.log.warning=function(a,b,c){goog.log.ENABLED&&a&&a.warning(b,c)};goog.log.info=function(a,b,c){goog.log.ENABLED&&a&&a.info(b,c)};goog.log.fine=function(a,b,c){goog.log.ENABLED&&a&&a.fine(b,c)};goog.string.StringBuffer=function(a,b){null!=a&&this.append.apply(this,arguments)};goog.string.StringBuffer.prototype.buffer_="";goog.string.StringBuffer.prototype.set=function(a){this.buffer_=""+a};goog.string.StringBuffer.prototype.append=function(a,b,c){this.buffer_+=String(a);if(null!=b)for(var d=1;d<arguments.length;d++)this.buffer_+=arguments[d];return this};goog.string.StringBuffer.prototype.clear=function(){this.buffer_=""};goog.string.StringBuffer.prototype.getLength=function(){return this.buffer_.length};
+goog.string.StringBuffer.prototype.toString=function(){return this.buffer_};goog.ui.tree={};goog.ui.tree.BaseNode=function(a,b,c){goog.ui.Component.call(this,c);this.config_=b||goog.ui.tree.BaseNode.defaultConfig;this.html_=goog.html.SafeHtml.htmlEscapePreservingNewlines(a);this.expanded_=this.selected_=!1;this.toolTip_=null;this.afterLabelHtml_=goog.html.SafeHtml.EMPTY;this.isUserCollapsible_=!0;this.depth_=-1};goog.inherits(goog.ui.tree.BaseNode,goog.ui.Component);
+goog.ui.tree.BaseNode.EventType={BEFORE_EXPAND:"beforeexpand",EXPAND:"expand",BEFORE_COLLAPSE:"beforecollapse",COLLAPSE:"collapse"};goog.ui.tree.BaseNode.allNodes={};goog.ui.tree.BaseNode.prototype.disposeInternal=function(){goog.ui.tree.BaseNode.superClass_.disposeInternal.call(this);this.tree&&(this.tree.removeNode(this),this.tree=null);this.setElementInternal(null)};
+goog.ui.tree.BaseNode.prototype.initAccessibility=function(){var a=this.getElement();if(a){var b=this.getLabelElement();b&&!b.id&&(b.id=this.getId()+".label");goog.a11y.aria.setRole(a,"treeitem");goog.a11y.aria.setState(a,"selected",!1);goog.a11y.aria.setState(a,"expanded",!1);goog.a11y.aria.setState(a,"level",this.getDepth());b&&goog.a11y.aria.setState(a,"labelledby",b.id);(a=this.getIconElement())&&goog.a11y.aria.setRole(a,"presentation");(a=this.getExpandIconElement())&&goog.a11y.aria.setRole(a,
+"presentation");if(a=this.getChildrenElement())if(goog.a11y.aria.setRole(a,"group"),a.hasChildNodes())for(a=this.getChildCount(),b=1;b<=a;b++){var c=this.getChildAt(b-1).getElement();goog.asserts.assert(c,"The child element cannot be null");goog.a11y.aria.setState(c,"setsize",a);goog.a11y.aria.setState(c,"posinset",b)}}};goog.ui.tree.BaseNode.prototype.createDom=function(){var a=this.getDomHelper().safeHtmlToNode(this.toSafeHtml());this.setElementInternal(a)};
+goog.ui.tree.BaseNode.prototype.enterDocument=function(){goog.ui.tree.BaseNode.superClass_.enterDocument.call(this);goog.ui.tree.BaseNode.allNodes[this.getId()]=this;this.initAccessibility()};goog.ui.tree.BaseNode.prototype.exitDocument=function(){goog.ui.tree.BaseNode.superClass_.exitDocument.call(this);delete goog.ui.tree.BaseNode.allNodes[this.getId()]};
+goog.ui.tree.BaseNode.prototype.addChildAt=function(a,b,c){goog.asserts.assert(!a.getParent());goog.asserts.assertInstanceof(a,goog.ui.tree.BaseNode);c=this.getChildAt(b-1);var d=this.getChildAt(b);goog.ui.tree.BaseNode.superClass_.addChildAt.call(this,a,b);a.previousSibling_=c;a.nextSibling_=d;c?c.nextSibling_=a:this.firstChild_=a;d?d.previousSibling_=a:this.lastChild_=a;(b=this.getTree())&&a.setTreeInternal(b);a.setDepth_(this.getDepth()+1);if(this.getElement()&&(this.updateExpandIcon(),this.getExpanded())){b=
+this.getChildrenElement();a.getElement()||a.createDom();var e=a.getElement(),f=d&&d.getElement();b.insertBefore(e,f);this.isInDocument()&&a.enterDocument();d||(c?c.updateExpandIcon():(goog.style.setElementShown(b,!0),this.setExpanded(this.getExpanded())))}};goog.ui.tree.BaseNode.prototype.add=function(a,b){goog.asserts.assert(!b||b.getParent()==this,"Can only add nodes before siblings");a.getParent()&&a.getParent().removeChild(a);this.addChildAt(a,b?this.indexOfChild(b):this.getChildCount());return a};
+goog.ui.tree.BaseNode.prototype.removeChild=function(a,b){var c=this.getTree(),d=c?c.getSelectedItem():null;if(d==a||a.contains(d))c.hasFocus()?(this.select(),goog.Timer.callOnce(this.onTimeoutSelect_,10,this)):this.select();goog.ui.tree.BaseNode.superClass_.removeChild.call(this,a);this.lastChild_==a&&(this.lastChild_=a.previousSibling_);this.firstChild_==a&&(this.firstChild_=a.nextSibling_);a.previousSibling_&&(a.previousSibling_.nextSibling_=a.nextSibling_);a.nextSibling_&&(a.nextSibling_.previousSibling_=
+a.previousSibling_);d=a.isLastSibling();a.tree=null;a.depth_=-1;if(c&&(c.removeNode(a),this.isInDocument())){c=this.getChildrenElement();if(a.isInDocument()){var e=a.getElement();c.removeChild(e);a.exitDocument()}d&&(d=this.getLastChild())&&d.updateExpandIcon();this.hasChildren()||(c.style.display="none",this.updateExpandIcon(),this.updateIcon_())}return a};goog.ui.tree.BaseNode.prototype.remove=goog.ui.tree.BaseNode.prototype.removeChild;goog.ui.tree.BaseNode.prototype.onTimeoutSelect_=function(){this.select()};
+goog.ui.tree.BaseNode.prototype.getTree=goog.abstractMethod;goog.ui.tree.BaseNode.prototype.getDepth=function(){var a=this.depth_;0>a&&(a=this.computeDepth_(),this.setDepth_(a));return a};goog.ui.tree.BaseNode.prototype.computeDepth_=function(){var a=this.getParent();return a?a.getDepth()+1:0};
+goog.ui.tree.BaseNode.prototype.setDepth_=function(a){if(a!=this.depth_){this.depth_=a;var b=this.getRowElement();if(b){var c=this.getPixelIndent_()+"px";this.isRightToLeft()?b.style.paddingRight=c:b.style.paddingLeft=c}this.forEachChild(function(b){b.setDepth_(a+1)})}};goog.ui.tree.BaseNode.prototype.contains=function(a){for(;a;){if(a==this)return!0;a=a.getParent()}return!1};goog.ui.tree.BaseNode.EMPTY_CHILDREN_=[];
+goog.ui.tree.BaseNode.prototype.getChildren=function(){var a=[];this.forEachChild(function(b){a.push(b)});return a};goog.ui.tree.BaseNode.prototype.getFirstChild=function(){return this.getChildAt(0)};goog.ui.tree.BaseNode.prototype.getLastChild=function(){return this.getChildAt(this.getChildCount()-1)};goog.ui.tree.BaseNode.prototype.getPreviousSibling=function(){return this.previousSibling_};goog.ui.tree.BaseNode.prototype.getNextSibling=function(){return this.nextSibling_};
+goog.ui.tree.BaseNode.prototype.isLastSibling=function(){return!this.nextSibling_};goog.ui.tree.BaseNode.prototype.isSelected=function(){return this.selected_};goog.ui.tree.BaseNode.prototype.select=function(){var a=this.getTree();a&&a.setSelectedItem(this)};goog.ui.tree.BaseNode.prototype.deselect=goog.nullFunction;
+goog.ui.tree.BaseNode.prototype.setSelectedInternal=function(a){if(this.selected_!=a){this.selected_=a;this.updateRow();var b=this.getElement();b&&(goog.a11y.aria.setState(b,"selected",a),a&&(a=this.getTree().getElement(),goog.asserts.assert(a,"The DOM element for the tree cannot be null"),goog.a11y.aria.setState(a,"activedescendant",this.getId())))}};goog.ui.tree.BaseNode.prototype.getExpanded=function(){return this.expanded_};
+goog.ui.tree.BaseNode.prototype.setExpandedInternal=function(a){this.expanded_=a};
+goog.ui.tree.BaseNode.prototype.setExpanded=function(a){var b=a!=this.expanded_;if(!b||this.dispatchEvent(a?goog.ui.tree.BaseNode.EventType.BEFORE_EXPAND:goog.ui.tree.BaseNode.EventType.BEFORE_COLLAPSE)){var c;this.expanded_=a;c=this.getTree();var d=this.getElement();if(this.hasChildren()){if(!a&&c&&this.contains(c.getSelectedItem())&&this.select(),d){if(c=this.getChildrenElement())if(goog.style.setElementShown(c,a),a&&this.isInDocument()&&!c.hasChildNodes()){var e=[];this.forEachChild(function(a){e.push(a.toSafeHtml())});
+goog.dom.safe.setInnerHtml(c,goog.html.SafeHtml.concat(e));this.forEachChild(function(a){a.enterDocument()})}this.updateExpandIcon()}}else(c=this.getChildrenElement())&&goog.style.setElementShown(c,!1);d&&(this.updateIcon_(),goog.a11y.aria.setState(d,"expanded",a));b&&this.dispatchEvent(a?goog.ui.tree.BaseNode.EventType.EXPAND:goog.ui.tree.BaseNode.EventType.COLLAPSE)}};goog.ui.tree.BaseNode.prototype.toggle=function(){this.setExpanded(!this.getExpanded())};
+goog.ui.tree.BaseNode.prototype.expand=function(){this.setExpanded(!0)};goog.ui.tree.BaseNode.prototype.collapse=function(){this.setExpanded(!1)};goog.ui.tree.BaseNode.prototype.collapseChildren=function(){this.forEachChild(function(a){a.collapseAll()})};goog.ui.tree.BaseNode.prototype.collapseAll=function(){this.collapseChildren();this.collapse()};goog.ui.tree.BaseNode.prototype.expandChildren=function(){this.forEachChild(function(a){a.expandAll()})};
+goog.ui.tree.BaseNode.prototype.expandAll=function(){this.expandChildren();this.expand()};goog.ui.tree.BaseNode.prototype.reveal=function(){var a=this.getParent();a&&(a.setExpanded(!0),a.reveal())};goog.ui.tree.BaseNode.prototype.setIsUserCollapsible=function(a){(this.isUserCollapsible_=a)||this.expand();this.getElement()&&this.updateExpandIcon()};goog.ui.tree.BaseNode.prototype.isUserCollapsible=function(){return this.isUserCollapsible_};
+goog.ui.tree.BaseNode.prototype.toSafeHtml=function(){var a=this.getTree(),b=!a.getShowLines()||a==this.getParent()&&!a.getShowRootLines()?this.config_.cssChildrenNoLines:this.config_.cssChildren,a=this.getExpanded()&&this.hasChildren(),b={"class":b,style:this.getLineStyle()},c=[];a&&this.forEachChild(function(a){c.push(a.toSafeHtml())});a=goog.html.SafeHtml.create("div",b,c);return goog.html.SafeHtml.create("div",{"class":this.config_.cssItem,id:this.getId()},[this.getRowSafeHtml(),a])};
+goog.ui.tree.BaseNode.prototype.getPixelIndent_=function(){return Math.max(0,(this.getDepth()-1)*this.config_.indentWidth)};goog.ui.tree.BaseNode.prototype.getRowSafeHtml=function(){var a={};a["padding-"+(this.isRightToLeft()?"right":"left")]=this.getPixelIndent_()+"px";var a={"class":this.getRowClassName(),style:a},b=[this.getExpandIconSafeHtml(),this.getIconSafeHtml(),this.getLabelSafeHtml()];return goog.html.SafeHtml.create("div",a,b)};
+goog.ui.tree.BaseNode.prototype.getRowClassName=function(){var a;a=this.isSelected()?" "+this.config_.cssSelectedRow:"";return this.config_.cssTreeRow+a};goog.ui.tree.BaseNode.prototype.getLabelSafeHtml=function(){var a=goog.html.SafeHtml.create("span",{"class":this.config_.cssItemLabel,title:this.getToolTip()||null},this.getSafeHtml());return goog.html.SafeHtml.concat(a,goog.html.SafeHtml.create("span",{},this.getAfterLabelSafeHtml()))};goog.ui.tree.BaseNode.prototype.getAfterLabelHtml=function(){return goog.html.SafeHtml.unwrap(this.getAfterLabelSafeHtml())};
+goog.ui.tree.BaseNode.prototype.getAfterLabelSafeHtml=function(){return this.afterLabelHtml_};goog.ui.tree.BaseNode.prototype.setAfterLabelSafeHtml=function(a){this.afterLabelHtml_=a;var b=this.getAfterLabelElement();b&&goog.dom.safe.setInnerHtml(b,a)};goog.ui.tree.BaseNode.prototype.getIconSafeHtml=function(){return goog.html.SafeHtml.create("span",{style:{display:"inline-block"},"class":this.getCalculatedIconClass()})};goog.ui.tree.BaseNode.prototype.getCalculatedIconClass=goog.abstractMethod;
+goog.ui.tree.BaseNode.prototype.getExpandIconSafeHtml=function(){return goog.html.SafeHtml.create("span",{type:"expand",style:{display:"inline-block"},"class":this.getExpandIconClass()})};
+goog.ui.tree.BaseNode.prototype.getExpandIconClass=function(){var a=this.getTree(),b=!a.getShowLines()||a==this.getParent()&&!a.getShowRootLines(),c=this.config_,d=new goog.string.StringBuffer;d.append(c.cssTreeIcon," ",c.cssExpandTreeIcon," ");if(this.hasChildren()){var e=0;a.getShowExpandIcons()&&this.isUserCollapsible_&&(e=this.getExpanded()?2:1);b||(e=this.isLastSibling()?e+4:e+8);switch(e){case 1:d.append(c.cssExpandTreeIconPlus);break;case 2:d.append(c.cssExpandTreeIconMinus);break;case 4:d.append(c.cssExpandTreeIconL);
+break;case 5:d.append(c.cssExpandTreeIconLPlus);break;case 6:d.append(c.cssExpandTreeIconLMinus);break;case 8:d.append(c.cssExpandTreeIconT);break;case 9:d.append(c.cssExpandTreeIconTPlus);break;case 10:d.append(c.cssExpandTreeIconTMinus);break;default:d.append(c.cssExpandTreeIconBlank)}}else b?d.append(c.cssExpandTreeIconBlank):this.isLastSibling()?d.append(c.cssExpandTreeIconL):d.append(c.cssExpandTreeIconT);return d.toString()};
+goog.ui.tree.BaseNode.prototype.getLineStyle=function(){var a=this.getExpanded()&&this.hasChildren();return goog.html.SafeStyle.create({"background-position":this.getBackgroundPosition(),display:a?null:"none"})};goog.ui.tree.BaseNode.prototype.getBackgroundPosition=function(){return(this.isLastSibling()?"-100":(this.getDepth()-1)*this.config_.indentWidth)+"px 0"};
+goog.ui.tree.BaseNode.prototype.getElement=function(){var a=goog.ui.tree.BaseNode.superClass_.getElement.call(this);a||(a=this.getDomHelper().getElement(this.getId()),this.setElementInternal(a));return a};goog.ui.tree.BaseNode.prototype.getRowElement=function(){var a=this.getElement();return a?a.firstChild:null};goog.ui.tree.BaseNode.prototype.getExpandIconElement=function(){var a=this.getRowElement();return a?a.firstChild:null};
+goog.ui.tree.BaseNode.prototype.getIconElement=function(){var a=this.getRowElement();return a?a.childNodes[1]:null};goog.ui.tree.BaseNode.prototype.getLabelElement=function(){var a=this.getRowElement();return a&&a.lastChild?a.lastChild.previousSibling:null};goog.ui.tree.BaseNode.prototype.getAfterLabelElement=function(){var a=this.getRowElement();return a?a.lastChild:null};goog.ui.tree.BaseNode.prototype.getChildrenElement=function(){var a=this.getElement();return a?a.lastChild:null};
+goog.ui.tree.BaseNode.prototype.setIconClass=function(a){this.iconClass_=a;this.isInDocument()&&this.updateIcon_()};goog.ui.tree.BaseNode.prototype.getIconClass=function(){return this.iconClass_};goog.ui.tree.BaseNode.prototype.setExpandedIconClass=function(a){this.expandedIconClass_=a;this.isInDocument()&&this.updateIcon_()};goog.ui.tree.BaseNode.prototype.getExpandedIconClass=function(){return this.expandedIconClass_};goog.ui.tree.BaseNode.prototype.setText=function(a){this.setSafeHtml(goog.html.SafeHtml.htmlEscape(a))};
+goog.ui.tree.BaseNode.prototype.getText=function(){return goog.string.unescapeEntities(goog.html.SafeHtml.unwrap(this.html_))};goog.ui.tree.BaseNode.prototype.setSafeHtml=function(a){this.html_=a;var b=this.getLabelElement();b&&goog.dom.safe.setInnerHtml(b,a);(a=this.getTree())&&a.setNode(this)};goog.ui.tree.BaseNode.prototype.getHtml=function(){return goog.html.SafeHtml.unwrap(this.getSafeHtml())};goog.ui.tree.BaseNode.prototype.getSafeHtml=function(){return this.html_};
+goog.ui.tree.BaseNode.prototype.setToolTip=function(a){this.toolTip_=a;var b=this.getLabelElement();b&&(b.title=a)};goog.ui.tree.BaseNode.prototype.getToolTip=function(){return this.toolTip_};goog.ui.tree.BaseNode.prototype.updateRow=function(){var a=this.getRowElement();a&&(a.className=this.getRowClassName())};
+goog.ui.tree.BaseNode.prototype.updateExpandIcon=function(){var a=this.getExpandIconElement();a&&(a.className=this.getExpandIconClass());if(a=this.getChildrenElement())a.style.backgroundPosition=this.getBackgroundPosition()};goog.ui.tree.BaseNode.prototype.updateIcon_=function(){this.getIconElement().className=this.getCalculatedIconClass()};
+goog.ui.tree.BaseNode.prototype.onMouseDown=function(a){"expand"==a.target.getAttribute("type")&&this.hasChildren()?this.isUserCollapsible_&&this.toggle():(this.select(),this.updateRow())};goog.ui.tree.BaseNode.prototype.onClick_=goog.events.Event.preventDefault;goog.ui.tree.BaseNode.prototype.onDoubleClick_=function(a){"expand"==a.target.getAttribute("type")&&this.hasChildren()||this.isUserCollapsible_&&this.toggle()};
+goog.ui.tree.BaseNode.prototype.onKeyDown=function(a){var b=!0;switch(a.keyCode){case goog.events.KeyCodes.RIGHT:if(a.altKey)break;this.hasChildren()&&(this.getExpanded()?this.getFirstChild().select():this.setExpanded(!0));break;case goog.events.KeyCodes.LEFT:if(a.altKey)break;if(this.hasChildren()&&this.getExpanded()&&this.isUserCollapsible_)this.setExpanded(!1);else{var c=this.getParent(),d=this.getTree();c&&(d.getShowRootNode()||c!=d)&&c.select()}break;case goog.events.KeyCodes.DOWN:(c=this.getNextShownNode())&&
+c.select();break;case goog.events.KeyCodes.UP:(c=this.getPreviousShownNode())&&c.select();break;default:b=!1}b&&(a.preventDefault(),(d=this.getTree())&&d.clearTypeAhead());return b};goog.ui.tree.BaseNode.prototype.getLastShownDescendant=function(){return this.getExpanded()&&this.hasChildren()?this.getLastChild().getLastShownDescendant():this};
+goog.ui.tree.BaseNode.prototype.getNextShownNode=function(){if(this.hasChildren()&&this.getExpanded())return this.getFirstChild();for(var a=this,b;a!=this.getTree();){b=a.getNextSibling();if(null!=b)return b;a=a.getParent()}return null};goog.ui.tree.BaseNode.prototype.getPreviousShownNode=function(){var a=this.getPreviousSibling();if(null!=a)return a.getLastShownDescendant();var a=this.getParent(),b=this.getTree();return!b.getShowRootNode()&&a==b||this==b?null:a};
+goog.ui.tree.BaseNode.prototype.getClientData=goog.ui.tree.BaseNode.prototype.getModel;goog.ui.tree.BaseNode.prototype.setClientData=goog.ui.tree.BaseNode.prototype.setModel;goog.ui.tree.BaseNode.prototype.getConfig=function(){return this.config_};goog.ui.tree.BaseNode.prototype.setTreeInternal=function(a){this.tree!=a&&(this.tree=a,a.setNode(this),this.forEachChild(function(b){b.setTreeInternal(a)}))};
+goog.ui.tree.BaseNode.defaultConfig={indentWidth:19,cssRoot:"goog-tree-root goog-tree-item",cssHideRoot:"goog-tree-hide-root",cssItem:"goog-tree-item",cssChildren:"goog-tree-children",cssChildrenNoLines:"goog-tree-children-nolines",cssTreeRow:"goog-tree-row",cssItemLabel:"goog-tree-item-label",cssTreeIcon:"goog-tree-icon",cssExpandTreeIcon:"goog-tree-expand-icon",cssExpandTreeIconPlus:"goog-tree-expand-icon-plus",cssExpandTreeIconMinus:"goog-tree-expand-icon-minus",cssExpandTreeIconTPlus:"goog-tree-expand-icon-tplus",
+cssExpandTreeIconTMinus:"goog-tree-expand-icon-tminus",cssExpandTreeIconLPlus:"goog-tree-expand-icon-lplus",cssExpandTreeIconLMinus:"goog-tree-expand-icon-lminus",cssExpandTreeIconT:"goog-tree-expand-icon-t",cssExpandTreeIconL:"goog-tree-expand-icon-l",cssExpandTreeIconBlank:"goog-tree-expand-icon-blank",cssExpandedFolderIcon:"goog-tree-expanded-folder-icon",cssCollapsedFolderIcon:"goog-tree-collapsed-folder-icon",cssFileIcon:"goog-tree-file-icon",cssExpandedRootIcon:"goog-tree-expanded-folder-icon",
+cssCollapsedRootIcon:"goog-tree-collapsed-folder-icon",cssSelectedRow:"selected"};goog.ui.tree.TreeNode=function(a,b,c){goog.ui.tree.BaseNode.call(this,a,b,c)};goog.inherits(goog.ui.tree.TreeNode,goog.ui.tree.BaseNode);goog.ui.tree.TreeNode.prototype.getTree=function(){if(this.tree)return this.tree;var a=this.getParent();return a&&(a=a.getTree())?(this.setTreeInternal(a),a):null};
+goog.ui.tree.TreeNode.prototype.getCalculatedIconClass=function(){var a=this.getExpanded(),b=this.getExpandedIconClass();if(a&&b)return b;b=this.getIconClass();if(!a&&b)return b;b=this.getConfig();if(this.hasChildren()){if(a&&b.cssExpandedFolderIcon)return b.cssTreeIcon+" "+b.cssExpandedFolderIcon;if(!a&&b.cssCollapsedFolderIcon)return b.cssTreeIcon+" "+b.cssCollapsedFolderIcon}else if(b.cssFileIcon)return b.cssTreeIcon+" "+b.cssFileIcon;return""};goog.structs.Trie=function(a){this.value_=void 0;this.childNodes_={};a&&this.setAll(a)};goog.structs.Trie.prototype.set=function(a,b){this.setOrAdd_(a,b,!1)};goog.structs.Trie.prototype.add=function(a,b){this.setOrAdd_(a,b,!0)};
+goog.structs.Trie.prototype.setOrAdd_=function(a,b,c){for(var d=this,e=0;e<a.length;e++){var f=a.charAt(e);d.childNodes_[f]||(d.childNodes_[f]=new goog.structs.Trie);d=d.childNodes_[f]}if(c&&void 0!==d.value_)throw Error('The collection already contains the key "'+a+'"');d.value_=b};goog.structs.Trie.prototype.setAll=function(a){var b=goog.structs.getKeys(a);a=goog.structs.getValues(a);for(var c=0;c<b.length;c++)this.set(b[c],a[c])};
+goog.structs.Trie.prototype.getChildNode_=function(a){for(var b=this,c=0;c<a.length;c++){var d=a.charAt(c),b=b.childNodes_[d];if(!b)return}return b};goog.structs.Trie.prototype.get=function(a){return(a=this.getChildNode_(a))?a.value_:void 0};goog.structs.Trie.prototype.getKeyAndPrefixes=function(a,b){var c=this,d={},e=b||0;void 0!==c.value_&&(d[e]=c.value_);for(;e<a.length;e++){var f=a.charAt(e);if(!(f in c.childNodes_))break;c=c.childNodes_[f];void 0!==c.value_&&(d[e]=c.value_)}return d};
+goog.structs.Trie.prototype.getValues=function(){var a=[];this.getValuesInternal_(a);return a};goog.structs.Trie.prototype.getValuesInternal_=function(a){void 0!==this.value_&&a.push(this.value_);for(var b in this.childNodes_)this.childNodes_[b].getValuesInternal_(a)};goog.structs.Trie.prototype.getKeys=function(a){var b=[];if(a){for(var c=this,d=0;d<a.length;d++){var e=a.charAt(d);if(!c.childNodes_[e])return[];c=c.childNodes_[e]}c.getKeysInternal_(a,b)}else this.getKeysInternal_("",b);return b};
+goog.structs.Trie.prototype.getKeysInternal_=function(a,b){void 0!==this.value_&&b.push(a);for(var c in this.childNodes_)this.childNodes_[c].getKeysInternal_(a+c,b)};goog.structs.Trie.prototype.containsKey=function(a){return void 0!==this.get(a)};goog.structs.Trie.prototype.containsPrefix=function(a){return 0==a.length?!this.isEmpty():!!this.getChildNode_(a)};
+goog.structs.Trie.prototype.containsValue=function(a){if(this.value_===a)return!0;for(var b in this.childNodes_)if(this.childNodes_[b].containsValue(a))return!0;return!1};goog.structs.Trie.prototype.clear=function(){this.childNodes_={};this.value_=void 0};
+goog.structs.Trie.prototype.remove=function(a){for(var b=this,c=[],d=0;d<a.length;d++){var e=a.charAt(d);if(!b.childNodes_[e])throw Error('The collection does not have the key "'+a+'"');c.push([b,e]);b=b.childNodes_[e]}a=b.value_;for(delete b.value_;0<c.length;)if(e=c.pop(),b=e[0],e=e[1],b.childNodes_[e].isEmpty())delete b.childNodes_[e];else break;return a};goog.structs.Trie.prototype.clone=function(){return new goog.structs.Trie(this)};goog.structs.Trie.prototype.getCount=function(){return goog.structs.getCount(this.getValues())};
+goog.structs.Trie.prototype.isEmpty=function(){return void 0===this.value_&&goog.object.isEmpty(this.childNodes_)};goog.ui.tree.TypeAhead=function(){this.nodeMap_=new goog.structs.Trie;this.buffer_="";this.matchingNodes_=this.matchingLabels_=null;this.matchingNodeIndex_=this.matchingLabelIndex_=0};goog.ui.tree.TypeAhead.Offset={DOWN:1,UP:-1};
+goog.ui.tree.TypeAhead.prototype.handleNavigation=function(a){var b=!1;switch(a.keyCode){case goog.events.KeyCodes.DOWN:case goog.events.KeyCodes.UP:a.ctrlKey&&(this.jumpTo_(a.keyCode==goog.events.KeyCodes.DOWN?goog.ui.tree.TypeAhead.Offset.DOWN:goog.ui.tree.TypeAhead.Offset.UP),b=!0);break;case goog.events.KeyCodes.BACKSPACE:a=this.buffer_.length-1;b=!0;0<a?(this.buffer_=this.buffer_.substring(0,a),this.jumpToLabel_(this.buffer_)):0==a?this.buffer_="":b=!1;break;case goog.events.KeyCodes.ESC:this.buffer_=
+"",b=!0}return b};goog.ui.tree.TypeAhead.prototype.handleTypeAheadChar=function(a){var b=!1;a.ctrlKey||a.altKey||(a=String.fromCharCode(a.charCode||a.keyCode).toLowerCase(),goog.string.isUnicodeChar(a)&&(" "!=a||this.buffer_)&&(this.buffer_+=a,b=this.jumpToLabel_(this.buffer_)));return b};
+goog.ui.tree.TypeAhead.prototype.setNodeInMap=function(a){var b=a.getText();if(b&&!goog.string.isEmptyOrWhitespace(goog.string.makeSafe(b))){var b=b.toLowerCase(),c=this.nodeMap_.get(b);c?c.push(a):this.nodeMap_.set(b,[a])}};
+goog.ui.tree.TypeAhead.prototype.removeNodeFromMap=function(a){var b=a.getText();if(b&&!goog.string.isEmptyOrWhitespace(goog.string.makeSafe(b))){var b=b.toLowerCase(),c=this.nodeMap_.get(b);if(c){for(var d=a.getChildCount(),e=0;e<d;e++)this.removeNodeFromMap(a.getChildAt(e));goog.array.remove(c,a);c.length||this.nodeMap_.remove(b)}}};
+goog.ui.tree.TypeAhead.prototype.jumpToLabel_=function(a){var b=!1;(a=this.nodeMap_.getKeys(a))&&a.length&&(this.matchingLabelIndex_=this.matchingNodeIndex_=0,b=this.nodeMap_.get(a[0]),b=this.selectMatchingNode_(b))&&(this.matchingLabels_=a);return b};
+goog.ui.tree.TypeAhead.prototype.jumpTo_=function(a){var b=!1,c=this.matchingLabels_;if(c){var b=null,d=!1;if(this.matchingNodes_){var e=this.matchingNodeIndex_+a;0<=e&&e<this.matchingNodes_.length?(this.matchingNodeIndex_=e,b=this.matchingNodes_):d=!0}b||(e=this.matchingLabelIndex_+a,0<=e&&e<c.length&&(this.matchingLabelIndex_=e),c.length>this.matchingLabelIndex_&&(b=this.nodeMap_.get(c[this.matchingLabelIndex_])),b&&b.length&&d&&(this.matchingNodeIndex_=a==goog.ui.tree.TypeAhead.Offset.UP?b.length-
+1:0));if(b=this.selectMatchingNode_(b))this.matchingLabels_=c}return b};goog.ui.tree.TypeAhead.prototype.selectMatchingNode_=function(a){var b;a&&(this.matchingNodeIndex_<a.length&&(b=a[this.matchingNodeIndex_],this.matchingNodes_=a),b&&(b.reveal(),b.select()));return!!b};goog.ui.tree.TypeAhead.prototype.clear=function(){this.buffer_=""};goog.ui.tree.TreeControl=function(a,b,c){goog.ui.tree.BaseNode.call(this,a,b,c);this.setExpandedInternal(!0);this.setSelectedInternal(!0);this.selectedItem_=this;this.typeAhead_=new goog.ui.tree.TypeAhead;this.focusHandler_=this.keyHandler_=null;this.logger_=goog.log.getLogger("this");this.focused_=!1;this.focusedNode_=null;this.showRootLines_=this.showRootNode_=this.showExpandIcons_=this.showLines_=!0;if(goog.userAgent.IE)try{document.execCommand("BackgroundImageCache",!1,!0)}catch(d){goog.log.warning(this.logger_,
+"Failed to enable background image cache")}};goog.inherits(goog.ui.tree.TreeControl,goog.ui.tree.BaseNode);goog.ui.tree.TreeControl.prototype.getTree=function(){return this};goog.ui.tree.TreeControl.prototype.getDepth=function(){return 0};goog.ui.tree.TreeControl.prototype.reveal=function(){};goog.ui.tree.TreeControl.prototype.handleFocus_=function(a){this.focused_=!0;goog.dom.classlist.add(goog.asserts.assert(this.getElement()),"focused");this.selectedItem_&&this.selectedItem_.select()};
+goog.ui.tree.TreeControl.prototype.handleBlur_=function(a){this.focused_=!1;goog.dom.classlist.remove(goog.asserts.assert(this.getElement()),"focused")};goog.ui.tree.TreeControl.prototype.hasFocus=function(){return this.focused_};goog.ui.tree.TreeControl.prototype.getExpanded=function(){return!this.showRootNode_||goog.ui.tree.TreeControl.superClass_.getExpanded.call(this)};
+goog.ui.tree.TreeControl.prototype.setExpanded=function(a){this.showRootNode_?goog.ui.tree.TreeControl.superClass_.setExpanded.call(this,a):this.setExpandedInternal(a)};goog.ui.tree.TreeControl.prototype.getExpandIconSafeHtml=function(){return goog.html.SafeHtml.EMPTY};goog.ui.tree.TreeControl.prototype.getIconElement=function(){var a=this.getRowElement();return a?a.firstChild:null};goog.ui.tree.TreeControl.prototype.getExpandIconElement=function(){return null};
+goog.ui.tree.TreeControl.prototype.updateExpandIcon=function(){};goog.ui.tree.TreeControl.prototype.getRowClassName=function(){return goog.ui.tree.TreeControl.superClass_.getRowClassName.call(this)+(this.showRootNode_?"":" "+this.getConfig().cssHideRoot)};
+goog.ui.tree.TreeControl.prototype.getCalculatedIconClass=function(){var a=this.getExpanded(),b=this.getExpandedIconClass();if(a&&b)return b;b=this.getIconClass();if(!a&&b)return b;b=this.getConfig();return a&&b.cssExpandedRootIcon?b.cssTreeIcon+" "+b.cssExpandedRootIcon:!a&&b.cssCollapsedRootIcon?b.cssTreeIcon+" "+b.cssCollapsedRootIcon:""};
+goog.ui.tree.TreeControl.prototype.setSelectedItem=function(a){if(this.selectedItem_!=a){var b=!1;this.selectedItem_&&(b=this.selectedItem_==this.focusedNode_,this.selectedItem_.setSelectedInternal(!1));if(this.selectedItem_=a)a.setSelectedInternal(!0),b&&a.select();this.dispatchEvent(goog.events.EventType.CHANGE)}};goog.ui.tree.TreeControl.prototype.getSelectedItem=function(){return this.selectedItem_};
+goog.ui.tree.TreeControl.prototype.setShowLines=function(a){this.showLines_!=a&&(this.showLines_=a,this.isInDocument()&&this.updateLinesAndExpandIcons_())};goog.ui.tree.TreeControl.prototype.getShowLines=function(){return this.showLines_};
+goog.ui.tree.TreeControl.prototype.updateLinesAndExpandIcons_=function(){function a(e){var f=e.getChildrenElement();if(f){var g=!c||b==e.getParent()&&!d?e.getConfig().cssChildrenNoLines:e.getConfig().cssChildren;f.className=g;if(f=e.getExpandIconElement())f.className=e.getExpandIconClass()}e.forEachChild(a)}var b=this,c=b.getShowLines(),d=b.getShowRootLines();a(this)};
+goog.ui.tree.TreeControl.prototype.setShowRootLines=function(a){this.showRootLines_!=a&&(this.showRootLines_=a,this.isInDocument()&&this.updateLinesAndExpandIcons_())};goog.ui.tree.TreeControl.prototype.getShowRootLines=function(){return this.showRootLines_};goog.ui.tree.TreeControl.prototype.setShowExpandIcons=function(a){this.showExpandIcons_!=a&&(this.showExpandIcons_=a,this.isInDocument()&&this.updateLinesAndExpandIcons_())};goog.ui.tree.TreeControl.prototype.getShowExpandIcons=function(){return this.showExpandIcons_};
+goog.ui.tree.TreeControl.prototype.setShowRootNode=function(a){if(this.showRootNode_!=a){this.showRootNode_=a;if(this.isInDocument()){var b=this.getRowElement();b&&(b.className=this.getRowClassName())}!a&&this.getSelectedItem()==this&&this.getFirstChild()&&this.setSelectedItem(this.getFirstChild())}};goog.ui.tree.TreeControl.prototype.getShowRootNode=function(){return this.showRootNode_};
+goog.ui.tree.TreeControl.prototype.initAccessibility=function(){goog.ui.tree.TreeControl.superClass_.initAccessibility.call(this);var a=this.getElement();goog.asserts.assert(a,"The DOM element for the tree cannot be null.");goog.a11y.aria.setRole(a,"tree");goog.a11y.aria.setState(a,"labelledby",this.getLabelElement().id)};
+goog.ui.tree.TreeControl.prototype.enterDocument=function(){goog.ui.tree.TreeControl.superClass_.enterDocument.call(this);var a=this.getElement();a.className=this.getConfig().cssRoot;a.setAttribute("hideFocus","true");this.attachEvents_();this.initAccessibility()};goog.ui.tree.TreeControl.prototype.exitDocument=function(){goog.ui.tree.TreeControl.superClass_.exitDocument.call(this);this.detachEvents_()};
+goog.ui.tree.TreeControl.prototype.attachEvents_=function(){var a=this.getElement();a.tabIndex=0;var b=this.keyHandler_=new goog.events.KeyHandler(a),c=this.focusHandler_=new goog.events.FocusHandler(a);this.getHandler().listen(c,goog.events.FocusHandler.EventType.FOCUSOUT,this.handleBlur_).listen(c,goog.events.FocusHandler.EventType.FOCUSIN,this.handleFocus_).listen(b,goog.events.KeyHandler.EventType.KEY,this.handleKeyEvent).listen(a,goog.events.EventType.MOUSEDOWN,this.handleMouseEvent_).listen(a,
+goog.events.EventType.CLICK,this.handleMouseEvent_).listen(a,goog.events.EventType.DBLCLICK,this.handleMouseEvent_)};goog.ui.tree.TreeControl.prototype.detachEvents_=function(){this.keyHandler_.dispose();this.keyHandler_=null;this.focusHandler_.dispose();this.focusHandler_=null};
+goog.ui.tree.TreeControl.prototype.handleMouseEvent_=function(a){goog.log.fine(this.logger_,"Received event "+a.type);var b=this.getNodeFromEvent_(a);if(b)switch(a.type){case goog.events.EventType.MOUSEDOWN:b.onMouseDown(a);break;case goog.events.EventType.CLICK:b.onClick_(a);break;case goog.events.EventType.DBLCLICK:b.onDoubleClick_(a)}};
+goog.ui.tree.TreeControl.prototype.handleKeyEvent=function(a){var b;(b=this.typeAhead_.handleNavigation(a)||this.selectedItem_&&this.selectedItem_.onKeyDown(a)||this.typeAhead_.handleTypeAheadChar(a))&&a.preventDefault();return b};goog.ui.tree.TreeControl.prototype.getNodeFromEvent_=function(a){for(var b=a.target;null!=b;){if(a=goog.ui.tree.BaseNode.allNodes[b.id])return a;if(b==this.getElement())break;b=b.parentNode}return null};
+goog.ui.tree.TreeControl.prototype.createNode=function(a){return new goog.ui.tree.TreeNode(a||goog.html.SafeHtml.EMPTY,this.getConfig(),this.getDomHelper())};goog.ui.tree.TreeControl.prototype.setNode=function(a){this.typeAhead_.setNodeInMap(a)};goog.ui.tree.TreeControl.prototype.removeNode=function(a){this.typeAhead_.removeNodeFromMap(a)};goog.ui.tree.TreeControl.prototype.clearTypeAhead=function(){this.typeAhead_.clear()};goog.ui.tree.TreeControl.defaultConfig=goog.ui.tree.BaseNode.defaultConfig;
+// Copyright 2013 Google Inc.  Apache License 2.0
+var Blockly={Blocks:{}};Blockly.Blocks.ONE_BASED_INDEXING=!0;
+// Copyright 2012 Google Inc.  Apache License 2.0
+Blockly.Workspace=function(a){this.id=Blockly.genUid();Blockly.Workspace.WorkspaceDB_[this.id]=this;this.options=a||{};this.RTL=!!this.options.RTL;this.horizontalLayout=!!this.options.horizontalLayout;this.toolboxPosition=this.options.toolboxPosition;this.topBlocks_=[];this.listeners_=[];this.undoStack_=[];this.redoStack_=[];this.blockDB_=Object.create(null)};Blockly.Workspace.prototype.rendered=!1;Blockly.Workspace.prototype.MAX_UNDO=1024;
+Blockly.Workspace.prototype.dispose=function(){this.listeners_.length=0;this.clear();delete Blockly.Workspace.WorkspaceDB_[this.id]};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a)};Blockly.Workspace.prototype.removeTopBlock=function(a){for(var b=!1,c,d=0;c=this.topBlocks_[d];d++)if(c==a){this.topBlocks_.splice(d,1);b=!0;break}if(!b)throw"Block not present in workspace's list of top-most blocks.";};
+Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1<b.length){var c=Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE));this.RTL&&(c*=-1);b.sort(function(a,b){var d=a.getRelativeToSurfaceXY(),e=b.getRelativeToSurfaceXY();return d.y+c*d.x-(e.y+c*e.x)})}return b};Blockly.Workspace.prototype.getAllBlocks=function(){for(var a=this.getTopBlocks(!1),b=0;b<a.length;b++)a.push.apply(a,a[b].getChildren());return a};
+Blockly.Workspace.prototype.clear=function(){var a=Blockly.Events.getGroup();for(a||Blockly.Events.setGroup(!0);this.topBlocks_.length;)this.topBlocks_[0].dispose();a||Blockly.Events.setGroup(!1)};Blockly.Workspace.prototype.getWidth=function(){return 0};Blockly.Workspace.prototype.newBlock=function(a,b){return new Blockly.Block(this,a,b)};Blockly.Workspace.prototype.remainingCapacity=function(){return isNaN(this.options.maxBlocks)?Infinity:this.options.maxBlocks-this.getAllBlocks().length};
+Blockly.Workspace.prototype.undo=function(a){var b=a?this.redoStack_:this.undoStack_,c=a?this.undoStack_:this.redoStack_,d=b.pop();if(d){for(var e=[d];b.length&&d.group&&d.group==b[b.length-1].group;)e.push(b.pop());for(b=0;d=e[b];b++)c.push(d);e=Blockly.Events.filter(e,a);Blockly.Events.recordUndo=!1;for(b=0;d=e[b];b++)d.run(a);Blockly.Events.recordUndo=!0}};Blockly.Workspace.prototype.clearUndo=function(){this.undoStack_.length=0;this.redoStack_.length=0;Blockly.Events.clearPendingUndo()};
+Blockly.Workspace.prototype.addChangeListener=function(a){this.listeners_.push(a);return a};Blockly.Workspace.prototype.removeChangeListener=function(a){a=this.listeners_.indexOf(a);-1!=a&&this.listeners_.splice(a,1)};Blockly.Workspace.prototype.fireChangeListener=function(a){a.recordUndo&&(this.undoStack_.push(a),this.redoStack_.length=0,this.undoStack_.length>this.MAX_UNDO&&this.undoStack_.unshift());for(var b=0,c;c=this.listeners_[b];b++)c(a)};
+Blockly.Workspace.prototype.getBlockById=function(a){return this.blockDB_[a]||null};Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo;Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener;
+Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;Blockly.Bubble=function(a,b,c,d,e,f){this.workspace_=a;this.content_=b;this.shape_=c;c=Blockly.Bubble.ARROW_ANGLE;this.workspace_.RTL&&(c=-c);this.arrow_radians_=goog.math.toRadians(c);a.getBubbleCanvas().appendChild(this.createDom_(b,!(!e||!f)));this.setAnchorLocation(d);e&&f||(b=this.content_.getBBox(),e=b.width+2*Blockly.Bubble.BORDER_WIDTH,f=b.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(e,f);this.positionBubble_();this.renderArrow_();this.rendered_=!0;a.options.readOnly||(Blockly.bindEvent_(this.bubbleBack_,
+"mousedown",this,this.bubbleMouseDown_),this.resizeGroup_&&Blockly.bindEvent_(this.resizeGroup_,"mousedown",this,this.resizeMouseDown_))};Blockly.Bubble.BORDER_WIDTH=6;Blockly.Bubble.ARROW_THICKNESS=10;Blockly.Bubble.ARROW_ANGLE=20;Blockly.Bubble.ARROW_BEND=4;Blockly.Bubble.ANCHOR_RADIUS=8;Blockly.Bubble.onMouseUpWrapper_=null;Blockly.Bubble.onMouseMoveWrapper_=null;Blockly.Bubble.prototype.resizeCallback_=null;
+Blockly.Bubble.unbindDragEvents_=function(){Blockly.Bubble.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_),Blockly.Bubble.onMouseUpWrapper_=null);Blockly.Bubble.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_),Blockly.Bubble.onMouseMoveWrapper_=null)};Blockly.Bubble.prototype.rendered_=!1;Blockly.Bubble.prototype.anchorXY_=null;Blockly.Bubble.prototype.relativeLeft_=0;Blockly.Bubble.prototype.relativeTop_=0;Blockly.Bubble.prototype.width_=0;
+Blockly.Bubble.prototype.height_=0;Blockly.Bubble.prototype.autoLayout_=!0;
+Blockly.Bubble.prototype.createDom_=function(a,b){this.bubbleGroup_=Blockly.createSvgElement("g",{},null);var c={filter:"url(#"+this.workspace_.options.embossFilterId+")"};-1!=goog.userAgent.getUserAgentString().indexOf("JavaFX")&&(c={});c=Blockly.createSvgElement("g",c,this.bubbleGroup_);this.bubbleArrow_=Blockly.createSvgElement("path",{},c);this.bubbleBack_=Blockly.createSvgElement("rect",{"class":"blocklyDraggable",x:0,y:0,rx:Blockly.Bubble.BORDER_WIDTH,ry:Blockly.Bubble.BORDER_WIDTH},c);b?(this.resizeGroup_=
+Blockly.createSvgElement("g",{"class":this.workspace_.RTL?"blocklyResizeSW":"blocklyResizeSE"},this.bubbleGroup_),c=2*Blockly.Bubble.BORDER_WIDTH,Blockly.createSvgElement("polygon",{points:"0,x x,x x,0".replace(/x/g,c.toString())},this.resizeGroup_),Blockly.createSvgElement("line",{"class":"blocklyResizeLine",x1:c/3,y1:c-1,x2:c-1,y2:c/3},this.resizeGroup_),Blockly.createSvgElement("line",{"class":"blocklyResizeLine",x1:2*c/3,y1:c-1,x2:c-1,y2:2*c/3},this.resizeGroup_)):this.resizeGroup_=null;this.bubbleGroup_.appendChild(a);
+return this.bubbleGroup_};
+Blockly.Bubble.prototype.bubbleMouseDown_=function(a){this.promote_();Blockly.Bubble.unbindDragEvents_();Blockly.isRightButton(a)?a.stopPropagation():Blockly.isTargetInput_(a)||(Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED),this.workspace_.startDrag(a,new goog.math.Coordinate(this.workspace_.RTL?-this.relativeLeft_:this.relativeLeft_,this.relativeTop_)),Blockly.Bubble.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,Blockly.Bubble.unbindDragEvents_),Blockly.Bubble.onMouseMoveWrapper_=Blockly.bindEvent_(document,
+"mousemove",this,this.bubbleMouseMove_),Blockly.hideChaff(),a.stopPropagation())};Blockly.Bubble.prototype.bubbleMouseMove_=function(a){this.autoLayout_=!1;a=this.workspace_.moveDrag(a);this.relativeLeft_=this.workspace_.RTL?-a.x:a.x;this.relativeTop_=a.y;this.positionBubble_();this.renderArrow_()};
+Blockly.Bubble.prototype.resizeMouseDown_=function(a){this.promote_();Blockly.Bubble.unbindDragEvents_();Blockly.isRightButton(a)||(Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED),this.workspace_.startDrag(a,new goog.math.Coordinate(this.workspace_.RTL?-this.width_:this.width_,this.height_)),Blockly.Bubble.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,Blockly.Bubble.unbindDragEvents_),Blockly.Bubble.onMouseMoveWrapper_=Blockly.bindEvent_(document,"mousemove",this,this.resizeMouseMove_),
+Blockly.hideChaff());a.stopPropagation()};Blockly.Bubble.prototype.resizeMouseMove_=function(a){this.autoLayout_=!1;a=this.workspace_.moveDrag(a);this.setBubbleSize(this.workspace_.RTL?-a.x:a.x,a.y);this.workspace_.RTL&&this.positionBubble_()};Blockly.Bubble.prototype.registerResizeEvent=function(a){this.resizeCallback_=a};Blockly.Bubble.prototype.promote_=function(){this.bubbleGroup_.parentNode.appendChild(this.bubbleGroup_)};
+Blockly.Bubble.prototype.setAnchorLocation=function(a){this.anchorXY_=a;this.rendered_&&this.positionBubble_()};
+Blockly.Bubble.prototype.layoutBubble_=function(){var a=-this.width_/4,b=-this.height_-Blockly.BlockSvg.MIN_BLOCK_Y,c=this.workspace_.getMetrics();c.viewWidth/=this.workspace_.scale;c.viewLeft/=this.workspace_.scale;var d=this.anchorXY_.x;this.workspace_.RTL?d-c.viewLeft-a-this.width_<Blockly.Scrollbar.scrollbarThickness?a=d-c.viewLeft-this.width_-Blockly.Scrollbar.scrollbarThickness:d-c.viewLeft-a>c.viewWidth&&(a=d-c.viewLeft-c.viewWidth):d+a<c.viewLeft?a=c.viewLeft-d:c.viewLeft+c.viewWidth<d+a+
+this.width_+Blockly.BlockSvg.SEP_SPACE_X+Blockly.Scrollbar.scrollbarThickness&&(a=c.viewLeft+c.viewWidth-d-this.width_-Blockly.Scrollbar.scrollbarThickness);this.anchorXY_.y+b<c.viewTop&&(b=this.shape_.getBBox().height);this.relativeLeft_=a;this.relativeTop_=b};
+Blockly.Bubble.prototype.positionBubble_=function(){var a=this.anchorXY_.x,a=this.workspace_.RTL?a-(this.relativeLeft_+this.width_):a+this.relativeLeft_;this.bubbleGroup_.setAttribute("transform","translate("+a+","+(this.relativeTop_+this.anchorXY_.y)+")")};Blockly.Bubble.prototype.getBubbleSize=function(){return{width:this.width_,height:this.height_}};
+Blockly.Bubble.prototype.setBubbleSize=function(a,b){var c=2*Blockly.Bubble.BORDER_WIDTH;a=Math.max(a,c+45);b=Math.max(b,c+20);this.width_=a;this.height_=b;this.bubbleBack_.setAttribute("width",a);this.bubbleBack_.setAttribute("height",b);this.resizeGroup_&&(this.workspace_.RTL?this.resizeGroup_.setAttribute("transform","translate("+2*Blockly.Bubble.BORDER_WIDTH+","+(b-c)+") scale(-1 1)"):this.resizeGroup_.setAttribute("transform","translate("+(a-c)+","+(b-c)+")"));this.rendered_&&(this.autoLayout_&&
+this.layoutBubble_(),this.positionBubble_(),this.renderArrow_());this.resizeCallback_&&this.resizeCallback_()};
+Blockly.Bubble.prototype.renderArrow_=function(){var a=[],b=this.width_/2,c=this.height_/2,d=-this.relativeLeft_,e=-this.relativeTop_;if(b==d&&c==e)a.push("M "+b+","+c);else{e-=c;d-=b;this.workspace_.RTL&&(d*=-1);var f=Math.sqrt(e*e+d*d),g=Math.acos(d/f);0>e&&(g=2*Math.PI-g);var h=g+Math.PI/2;h>2*Math.PI&&(h-=2*Math.PI);var k=Math.sin(h),l=Math.cos(h),p=this.getBubbleSize(),h=(p.width+p.height)/Blockly.Bubble.ARROW_THICKNESS,h=Math.min(h,p.width,p.height)/2,p=1-Blockly.Bubble.ANCHOR_RADIUS/f,d=b+
+p*d,e=c+p*e,p=b+h*l,m=c+h*k,b=b-h*l,c=c-h*k,k=g+this.arrow_radians_;k>2*Math.PI&&(k-=2*Math.PI);g=Math.sin(k)*f/Blockly.Bubble.ARROW_BEND;f=Math.cos(k)*f/Blockly.Bubble.ARROW_BEND;a.push("M"+p+","+m);a.push("C"+(p+f)+","+(m+g)+" "+d+","+e+" "+d+","+e);a.push("C"+d+","+e+" "+(b+f)+","+(c+g)+" "+b+","+c)}a.push("z");this.bubbleArrow_.setAttribute("d",a.join(" "))};Blockly.Bubble.prototype.setColour=function(a){this.bubbleBack_.setAttribute("fill",a);this.bubbleArrow_.setAttribute("fill",a)};
+Blockly.Bubble.prototype.dispose=function(){Blockly.Bubble.unbindDragEvents_();goog.dom.removeNode(this.bubbleGroup_);this.shape_=this.content_=this.workspace_=this.resizeGroup_=this.bubbleBack_=this.bubbleArrow_=this.bubbleGroup_=null};Blockly.Icon=function(a){this.block_=a};Blockly.Icon.prototype.collapseHidden=!0;Blockly.Icon.prototype.SIZE=17;Blockly.Icon.prototype.bubble_=null;Blockly.Icon.prototype.iconXY_=null;
+Blockly.Icon.prototype.createIcon=function(){this.iconGroup_||(this.iconGroup_=Blockly.createSvgElement("g",{"class":"blocklyIconGroup"},null),this.block_.isInFlyout&&Blockly.addClass_(this.iconGroup_,"blocklyIconGroupReadonly"),this.drawIcon_(this.iconGroup_),this.block_.getSvgRoot().appendChild(this.iconGroup_),Blockly.bindEvent_(this.iconGroup_,"mouseup",this,this.iconClick_),this.updateEditable())};
+Blockly.Icon.prototype.dispose=function(){goog.dom.removeNode(this.iconGroup_);this.iconGroup_=null;this.setVisible(!1);this.block_=null};Blockly.Icon.prototype.updateEditable=function(){};Blockly.Icon.prototype.isVisible=function(){return!!this.bubble_};Blockly.Icon.prototype.iconClick_=function(a){this.block_.workspace.isDragging()||this.block_.isInFlyout||Blockly.isRightButton(a)||this.setVisible(!this.isVisible())};Blockly.Icon.prototype.updateColour=function(){this.isVisible()&&this.bubble_.setColour(this.block_.getColour())};
+Blockly.Icon.prototype.renderIcon=function(a){if(this.collapseHidden&&this.block_.isCollapsed())return this.iconGroup_.setAttribute("display","none"),a;this.iconGroup_.setAttribute("display","block");var b=this.SIZE;this.block_.RTL&&(a-=b);this.iconGroup_.setAttribute("transform","translate("+a+",5)");this.computeIconLocation();return a=this.block_.RTL?a-Blockly.BlockSvg.SEP_SPACE_X:a+(b+Blockly.BlockSvg.SEP_SPACE_X)};
+Blockly.Icon.prototype.setIconLocation=function(a){this.iconXY_=a;this.isVisible()&&this.bubble_.setAnchorLocation(a)};Blockly.Icon.prototype.computeIconLocation=function(){var a=this.block_.getRelativeToSurfaceXY(),b=Blockly.getRelativeXY_(this.iconGroup_),a=new goog.math.Coordinate(a.x+b.x+this.SIZE/2,a.y+b.y+this.SIZE/2);goog.math.Coordinate.equals(this.getIconLocation(),a)||this.setIconLocation(a)};Blockly.Icon.prototype.getIconLocation=function(){return this.iconXY_};
+Blockly.Icon.prototype.createIconOld=function(){this.iconGroup_||(this.iconGroup_=Blockly.createSvgElement("g",{},null),this.block_.getSvgRoot().appendChild(this.iconGroup_),Blockly.bindEvent_(this.iconGroup_,"mouseup",this,this.iconClick_),this.updateEditable())};
+// Copyright 2011 Google Inc.  Apache License 2.0
+Blockly.Comment=function(a){Blockly.Comment.superClass_.constructor.call(this,a);this.createIcon()};goog.inherits(Blockly.Comment,Blockly.Icon);Blockly.Comment.prototype.png_="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAANyAAADcgBffIlqAAAAAd0SU1FB98DGgAnBf0Xj5sAAAIBSURBVDjLjZO9SxxRFMXPrFkWl2UFYSOIRtF210YtAiH/gGATRNZFgo19IBaB9Ipgk3SiEoKQgI19JIVgGaOIgpWJEAV1kZk3b1ad0V+KRYIzk5ALh1ecc88978tRSgHPg0Bjvq/BbFalMNR5oaBv+bzWHMfZjOudWPOg6+pDva6elRXlt7fVcnYmPX4sDQ3pdmpKQXu7frS16aXjON8T06OIMWOwtRp3jgNSEpkMTE5y5/v4UcSLePxnroutVNKb4xgYANfFAk/vDbLG8Gtk5P8M7jE6CsZwDDwSMLm5iYmLlpbg4ABOTmBjA4aHk0ZbWxigposLvlarScH5OSwvw9oaABwdJTW1GtTrfJHnUe/uTgqKxeZaKEAUgTEQP/CeHvA8LhRFhLlc+r6zWVhfbyaZn0/yuRxEEaGCAK9USjdZWGgarK5CS0uS7+gAa3EzjYaOy2WlludJi4vSzIx0e5vky2Xp6ko/M4WCPleruk4zsVa6vJSur9OHTEzoqljUJwEdQYDf25uMe3jY3E5fX5Lr7wdr8YGSJCkIeL23h9/a+lA4Pg7T039u6h75POzv4wcBrx5Ec11Wd3bwOzv//VK7umB3F991+Zj2/R1reWstdnaWm3L5YXOlAnNz3FiLbTR4Azj6WwFPjOG953EahoT1On4YEnoep8bwDuiO9/wG1sM4kG8A4fUAAAAASUVORK5CYII=";
+Blockly.Comment.prototype.text_="";Blockly.Comment.prototype.width_=160;Blockly.Comment.prototype.height_=80;
+Blockly.Comment.prototype.drawIcon_=function(a){Blockly.createSvgElement("circle",{"class":"blocklyIconShape",r:"8",cx:"8",cy:"8"},a);Blockly.createSvgElement("path",{"class":"blocklyIconSymbol",d:"m6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405 0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25 -1.201,0.998 -1.201,1.528 -1.204,2.19z"},a);Blockly.createSvgElement("rect",{"class":"blocklyIconSymbol",x:"6.8",y:"10.78",height:"2",width:"2"},a)};
+Blockly.Comment.prototype.createEditor_=function(){this.foreignObject_=Blockly.createSvgElement("foreignObject",{x:Blockly.Bubble.BORDER_WIDTH,y:Blockly.Bubble.BORDER_WIDTH},null);var a=document.createElementNS(Blockly.HTML_NS,"body");a.setAttribute("xmlns",Blockly.HTML_NS);a.className="blocklyMinimalBody";var b=document.createElementNS(Blockly.HTML_NS,"textarea");b.className="blocklyCommentTextarea";b.setAttribute("dir",this.block_.RTL?"RTL":"LTR");a.appendChild(b);this.textarea_=b;this.foreignObject_.appendChild(a);
+Blockly.bindEvent_(b,"mouseup",this,this.textareaFocus_);Blockly.bindEvent_(b,"wheel",this,function(a){a.stopPropagation()});Blockly.bindEvent_(b,"change",this,function(a){this.text_!=b.value&&(Blockly.Events.fire(new Blockly.Events.Change(this.block_,"comment",null,this.text_,b.value)),this.text_=b.value)});setTimeout(function(){b.focus()},0);return this.foreignObject_};Blockly.Comment.prototype.updateEditable=function(){this.isVisible()&&(this.setVisible(!1),this.setVisible(!0));Blockly.Icon.prototype.updateEditable.call(this)};
+Blockly.Comment.prototype.resizeBubble_=function(){if(this.isVisible()){var a=this.bubble_.getBubbleSize(),b=2*Blockly.Bubble.BORDER_WIDTH;this.foreignObject_.setAttribute("width",a.width-b);this.foreignObject_.setAttribute("height",a.height-b);this.textarea_.style.width=a.width-b-4+"px";this.textarea_.style.height=a.height-b-4+"px"}};
+Blockly.Comment.prototype.setVisible=function(a){if(a!=this.isVisible())if(Blockly.Events.fire(new Blockly.Events.Ui(this.block_,"commentOpen",!a,a)),!this.block_.isEditable()&&!this.textarea_||goog.userAgent.IE)Blockly.Warning.prototype.setVisible.call(this,a);else{var b=this.getText(),c=this.getBubbleSize();a?(this.bubble_=new Blockly.Bubble(this.block_.workspace,this.createEditor_(),this.block_.svgPath_,this.iconXY_,this.width_,this.height_),this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this)),
+this.updateColour()):(this.bubble_.dispose(),this.foreignObject_=this.textarea_=this.bubble_=null);this.setText(b);this.setBubbleSize(c.width,c.height)}};Blockly.Comment.prototype.textareaFocus_=function(a){this.bubble_.promote_();this.textarea_.focus()};Blockly.Comment.prototype.getBubbleSize=function(){return this.isVisible()?this.bubble_.getBubbleSize():{width:this.width_,height:this.height_}};
+Blockly.Comment.prototype.setBubbleSize=function(a,b){this.textarea_?this.bubble_.setBubbleSize(a,b):(this.width_=a,this.height_=b)};Blockly.Comment.prototype.getText=function(){return this.textarea_?this.textarea_.value:this.text_};Blockly.Comment.prototype.setText=function(a){this.text_!=a&&(Blockly.Events.fire(new Blockly.Events.Change(this.block_,"comment",null,this.text_,a)),this.text_=a);this.textarea_&&(this.textarea_.value=a)};
+Blockly.Comment.prototype.dispose=function(){Blockly.Events.isEnabled()&&this.setText("");this.block_.comment=null;Blockly.Icon.prototype.dispose.call(this)};Blockly.Connection=function(a,b){this.sourceBlock_=a;this.type=b;a.workspace.connectionDBList&&(this.db_=a.workspace.connectionDBList[b],this.dbOpposite_=a.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[b]],this.hidden_=!this.db_)};Blockly.Connection.CAN_CONNECT=0;Blockly.Connection.REASON_SELF_CONNECTION=1;Blockly.Connection.REASON_WRONG_TYPE=2;Blockly.Connection.REASON_TARGET_NULL=3;Blockly.Connection.REASON_CHECKS_FAILED=4;Blockly.Connection.REASON_DIFFERENT_WORKSPACES=5;
+Blockly.Connection.REASON_SHADOW_PARENT=6;Blockly.Connection.prototype.targetConnection=null;Blockly.Connection.prototype.check_=null;Blockly.Connection.prototype.shadowDom_=null;Blockly.Connection.prototype.x_=0;Blockly.Connection.prototype.y_=0;Blockly.Connection.prototype.inDB_=!1;Blockly.Connection.prototype.db_=null;Blockly.Connection.prototype.dbOpposite_=null;Blockly.Connection.prototype.hidden_=null;
+Blockly.Connection.prototype.connect_=function(a){var b=this,c=b.getSourceBlock(),d=a.getSourceBlock();a.isConnected()&&a.disconnect();if(b.isConnected()){var e=b.targetBlock(),f=b.getShadowDom();b.setShadowDom(null);if(e.isShadow())f=Blockly.Xml.blockToDom(e),e.dispose(),e=null;else if(b.type==Blockly.INPUT_VALUE){if(!e.outputConnection)throw"Orphan block does not have an output connection.";var g=Blockly.Connection.lastConnectionInRow_(d,e);g&&(e.outputConnection.connect(g),e=null)}else if(b.type==
+Blockly.NEXT_STATEMENT){if(!e.previousConnection)throw"Orphan block does not have a previous connection.";for(g=d;g.nextConnection;){var h=g.getNextBlock();if(h&&!h.isShadow())g=h;else{e.previousConnection.checkType_(g.nextConnection)&&(g.nextConnection.connect(e.previousConnection),e=null);break}}}if(e&&(b.disconnect(),Blockly.Events.recordUndo)){var k=Blockly.Events.getGroup();setTimeout(function(){e.workspace&&!e.getParent()&&(Blockly.Events.setGroup(k),e.outputConnection?e.outputConnection.bumpAwayFrom_(b):
+e.previousConnection&&e.previousConnection.bumpAwayFrom_(b),Blockly.Events.setGroup(!1))},Blockly.BUMP_DELAY)}b.setShadowDom(f)}var l;Blockly.Events.isEnabled()&&(l=new Blockly.Events.Move(d));Blockly.Connection.connectReciprocally_(b,a);d.setParent(c);l&&(l.recordNew(),Blockly.Events.fire(l))};
+Blockly.Connection.prototype.dispose=function(){if(this.isConnected())throw"Disconnect connection before disposing of it.";this.inDB_&&this.db_.removeConnection_(this);Blockly.highlightedConnection_==this&&(Blockly.highlightedConnection_=null);Blockly.highlightedConnectionBad_==this&&(Blockly.highlightedConnectionBad_=null);Blockly.localConnection_==this&&(Blockly.localConnection_=null);this.dbOpposite_=this.db_=null};Blockly.Connection.prototype.getSourceBlock=function(){return this.sourceBlock_};
+Blockly.Connection.prototype.isSuperior=function(){return this.type==Blockly.INPUT_VALUE||this.type==Blockly.NEXT_STATEMENT};Blockly.Connection.prototype.isConnected=function(){return!!this.targetConnection};
+Blockly.Connection.prototype.canConnectWithReason_=function(a){if(!a)return Blockly.Connection.REASON_TARGET_NULL;if(this.isSuperior())var b=this.sourceBlock_,c=a.getSourceBlock();else c=this.sourceBlock_,b=a.getSourceBlock();return b&&b==c?Blockly.Connection.REASON_SELF_CONNECTION:a.type!=Blockly.OPPOSITE_TYPE[this.type]?Blockly.Connection.REASON_WRONG_TYPE:b&&c&&b.workspace!==c.workspace?Blockly.Connection.REASON_DIFFERENT_WORKSPACES:b.isShadow()&&!c.isShadow()?Blockly.Connection.REASON_SHADOW_PARENT:
+Blockly.Connection.CAN_CONNECT};
+Blockly.Connection.prototype.checkConnection_=function(a){switch(this.canConnectWithReason_(a)){case Blockly.Connection.CAN_CONNECT:break;case Blockly.Connection.REASON_SELF_CONNECTION:throw"Attempted to connect a block to itself.";case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:throw"Blocks not on same workspace.";case Blockly.Connection.REASON_WRONG_TYPE:throw"Attempt to connect incompatible types.";case Blockly.Connection.REASON_TARGET_NULL:throw"Target connection is null.";case Blockly.Connection.REASON_CHECKS_FAILED:throw"Connection checks failed.";
+case Blockly.Connection.REASON_SHADOW_PARENT:throw"Connecting non-shadow to shadow block.";default:throw"Unknown connection failure: this should never happen!";}};
+Blockly.Connection.prototype.isConnectionAllowed=function(a){if(this.canConnectWithReason_(a)!=Blockly.Connection.CAN_CONNECT)return!1;if(a.type==Blockly.OUTPUT_VALUE||a.type==Blockly.PREVIOUS_STATEMENT)if(a.isConnected()||this.isConnected())return!1;return a.type==Blockly.INPUT_VALUE&&a.isConnected()&&!a.targetBlock().isMovable()&&!a.targetBlock().isShadow()||this.type==Blockly.PREVIOUS_STATEMENT&&a.isConnected()&&!this.sourceBlock_.nextConnection&&!a.targetBlock().isShadow()&&a.targetBlock().nextConnection||
+-1!=Blockly.draggingConnections_.indexOf(a)?!1:!0};Blockly.Connection.prototype.connect=function(a){this.targetConnection!=a&&(this.checkConnection_(a),this.isSuperior()?this.connect_(a):a.connect_(this))};Blockly.Connection.connectReciprocally_=function(a,b){goog.asserts.assert(a&&b,"Cannot connect null connections.");a.targetConnection=b;b.targetConnection=a};
+Blockly.Connection.singleConnection_=function(a,b){for(var c=!1,d=0;d<a.inputList.length;d++){var e=a.inputList[d].connection;if(e&&e.type==Blockly.INPUT_VALUE&&b.outputConnection.checkType_(e)){if(c)return null;c=e}}return c};Blockly.Connection.lastConnectionInRow_=function(a,b){for(var c=a,d;d=Blockly.Connection.singleConnection_(c,b);)if(c=d.targetBlock(),!c||c.isShadow())return d;return null};
+Blockly.Connection.prototype.disconnect=function(){var a=this.targetConnection;goog.asserts.assert(a,"Source connection not connected.");goog.asserts.assert(a.targetConnection==this,"Target connection not connected to source connection.");var b,c;this.isSuperior()?(b=this.sourceBlock_,c=a.getSourceBlock(),a=this):(b=a.getSourceBlock(),c=this.sourceBlock_);this.disconnectInternal_(b,c);a.respawnShadow_()};
+Blockly.Connection.prototype.disconnectInternal_=function(a,b){var c;Blockly.Events.isEnabled()&&(c=new Blockly.Events.Move(b));this.targetConnection=this.targetConnection.targetConnection=null;b.setParent(null);c&&(c.recordNew(),Blockly.Events.fire(c))};
+Blockly.Connection.prototype.respawnShadow_=function(){var a=this.getSourceBlock(),b=this.getShadowDom();if(a.workspace&&b&&Blockly.Events.recordUndo)if(a=Blockly.Xml.domToBlock(b,a.workspace),a.outputConnection)this.connect(a.outputConnection);else if(a.previousConnection)this.connect(a.previousConnection);else throw"Child block does not have output or previous statement.";};Blockly.Connection.prototype.targetBlock=function(){return this.isConnected()?this.targetConnection.getSourceBlock():null};
+Blockly.Connection.prototype.checkType_=function(a){if(!this.check_||!a.check_)return!0;for(var b=0;b<this.check_.length;b++)if(-1!=a.check_.indexOf(this.check_[b]))return!0;return!1};Blockly.Connection.prototype.setCheck=function(a){a?(goog.isArray(a)||(a=[a]),this.check_=a,this.isConnected()&&!this.checkType_(this.targetConnection)&&((this.isSuperior()?this.targetBlock():this.sourceBlock_).unplug(),this.sourceBlock_.bumpNeighbours_())):this.check_=null;return this};
+Blockly.Connection.prototype.setShadowDom=function(a){this.shadowDom_=a};Blockly.Connection.prototype.getShadowDom=function(){return this.shadowDom_};Blockly.Field=function(a,b){this.size_=new goog.math.Size(0,25);this.setValue(a);this.setValidator(b)};Blockly.Field.cacheWidths_=null;Blockly.Field.cacheReference_=0;Blockly.Field.prototype.name=void 0;Blockly.Field.prototype.maxDisplayLength=50;Blockly.Field.prototype.text_="";Blockly.Field.prototype.sourceBlock_=null;Blockly.Field.prototype.visible_=!0;Blockly.Field.prototype.validator_=null;Blockly.Field.NBSP="\u00a0";Blockly.Field.prototype.EDITABLE=!0;
+Blockly.Field.prototype.setSourceBlock=function(a){goog.asserts.assert(!this.sourceBlock_,"Field already bound to a block.");this.sourceBlock_=a};
+Blockly.Field.prototype.init=function(){this.fieldGroup_||(this.fieldGroup_=Blockly.createSvgElement("g",{},null),this.visible_||(this.fieldGroup_.style.display="none"),this.borderRect_=Blockly.createSvgElement("rect",{rx:4,ry:4,x:-Blockly.BlockSvg.SEP_SPACE_X/2,y:0,height:16},this.fieldGroup_,this.sourceBlock_.workspace),this.textElement_=Blockly.createSvgElement("text",{"class":"blocklyText",y:this.size_.height-12.5},this.fieldGroup_),this.updateEditable(),this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_),
+this.mouseUpWrapper_=Blockly.bindEvent_(this.fieldGroup_,"mouseup",this,this.onMouseUp_),this.updateTextNode_())};Blockly.Field.prototype.dispose=function(){this.mouseUpWrapper_&&(Blockly.unbindEvent_(this.mouseUpWrapper_),this.mouseUpWrapper_=null);this.sourceBlock_=null;goog.dom.removeNode(this.fieldGroup_);this.validator_=this.borderRect_=this.textElement_=this.fieldGroup_=null};
+Blockly.Field.prototype.updateEditable=function(){this.EDITABLE&&this.fieldGroup_&&(this.sourceBlock_.isEditable()?(Blockly.addClass_(this.fieldGroup_,"blocklyEditableText"),Blockly.removeClass_(this.fieldGroup_,"blocklyNonEditableText"),this.fieldGroup_.style.cursor=this.CURSOR):(Blockly.addClass_(this.fieldGroup_,"blocklyNonEditableText"),Blockly.removeClass_(this.fieldGroup_,"blocklyEditableText"),this.fieldGroup_.style.cursor=""))};Blockly.Field.prototype.isVisible=function(){return this.visible_};
+Blockly.Field.prototype.setVisible=function(a){if(this.visible_!=a){this.visible_=a;var b=this.getSvgRoot();b&&(b.style.display=a?"block":"none",this.render_())}};Blockly.Field.prototype.setValidator=function(a){this.validator_=a};Blockly.Field.prototype.getValidator=function(){return this.validator_};Blockly.Field.prototype.classValidator=function(a){return a};
+Blockly.Field.prototype.callValidator=function(a){var b=this.classValidator(a);if(null===b)return null;void 0!==b&&(a=b);if(b=this.getValidator()){b=b.call(this,a);if(null===b)return null;void 0!==b&&(a=b)}return a};Blockly.Field.prototype.getSvgRoot=function(){return this.fieldGroup_};
+Blockly.Field.prototype.render_=function(){if(this.visible_&&this.textElement_){var a=this.textElement_.textContent+"\n"+this.textElement_.className.baseVal;if(Blockly.Field.cacheWidths_&&Blockly.Field.cacheWidths_[a])var b=Blockly.Field.cacheWidths_[a];else{try{b=this.textElement_.getComputedTextLength()}catch(c){b=8*this.textElement_.textContent.length}Blockly.Field.cacheWidths_&&(Blockly.Field.cacheWidths_[a]=b)}this.borderRect_&&this.borderRect_.setAttribute("width",b+Blockly.BlockSvg.SEP_SPACE_X)}else b=
+0;this.size_.width=b};Blockly.Field.startCache=function(){Blockly.Field.cacheReference_++;Blockly.Field.cacheWidths_||(Blockly.Field.cacheWidths_={})};Blockly.Field.stopCache=function(){Blockly.Field.cacheReference_--;Blockly.Field.cacheReference_||(Blockly.Field.cacheWidths_=null)};Blockly.Field.prototype.getSize=function(){this.size_.width||this.render_();return this.size_};
+Blockly.Field.prototype.getScaledBBox_=function(){var a=this.borderRect_.getBBox();return new goog.math.Size(a.width*this.sourceBlock_.workspace.scale,a.height*this.sourceBlock_.workspace.scale)};Blockly.Field.prototype.getText=function(){return this.text_};Blockly.Field.prototype.setText=function(a){null!==a&&(a=String(a),a!==this.text_&&(this.text_=a,this.updateTextNode_(),this.sourceBlock_&&this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_())))};
+Blockly.Field.prototype.updateTextNode_=function(){if(this.textElement_){var a=this.text_;a.length>this.maxDisplayLength&&(a=a.substring(0,this.maxDisplayLength-2)+"\u2026");goog.dom.removeChildren(this.textElement_);a=a.replace(/\s/g,Blockly.Field.NBSP);this.sourceBlock_.RTL&&a&&(a+="\u200f");a||(a=Blockly.Field.NBSP);a=document.createTextNode(a);this.textElement_.appendChild(a);this.size_.width=0}};Blockly.Field.prototype.getValue=function(){return this.getText()};
+Blockly.Field.prototype.setValue=function(a){if(null!==a){var b=this.getValue();b!=a&&(this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,b,a)),this.setText(a))}};
+Blockly.Field.prototype.onMouseUp_=function(a){if(!goog.userAgent.IPHONE&&!goog.userAgent.IPAD||goog.userAgent.isVersionOrHigher("537.51.2")||0===a.layerX||0===a.layerY)Blockly.isRightButton(a)||this.sourceBlock_.workspace.isDragging()||this.sourceBlock_.isEditable()&&this.showEditor_()};Blockly.Field.prototype.setTooltip=function(a){};Blockly.Field.prototype.getAbsoluteXY_=function(){return goog.style.getPageOffset(this.borderRect_)};Blockly.Tooltip={};Blockly.Tooltip.visible=!1;Blockly.Tooltip.LIMIT=50;Blockly.Tooltip.mouseOutPid_=0;Blockly.Tooltip.showPid_=0;Blockly.Tooltip.lastX_=0;Blockly.Tooltip.lastY_=0;Blockly.Tooltip.element_=null;Blockly.Tooltip.poisonedElement_=null;Blockly.Tooltip.OFFSET_X=0;Blockly.Tooltip.OFFSET_Y=10;Blockly.Tooltip.RADIUS_OK=10;Blockly.Tooltip.HOVER_MS=1E3;Blockly.Tooltip.MARGINS=5;Blockly.Tooltip.DIV=null;
+Blockly.Tooltip.createDom=function(){Blockly.Tooltip.DIV||(Blockly.Tooltip.DIV=goog.dom.createDom("div","blocklyTooltipDiv"),document.body.appendChild(Blockly.Tooltip.DIV))};Blockly.Tooltip.bindMouseEvents=function(a){Blockly.bindEvent_(a,"mouseover",null,Blockly.Tooltip.onMouseOver_);Blockly.bindEvent_(a,"mouseout",null,Blockly.Tooltip.onMouseOut_);Blockly.bindEvent_(a,"mousemove",null,Blockly.Tooltip.onMouseMove_)};
+Blockly.Tooltip.onMouseOver_=function(a){for(a=a.target;!goog.isString(a.tooltip)&&!goog.isFunction(a.tooltip);)a=a.tooltip;Blockly.Tooltip.element_!=a&&(Blockly.Tooltip.hide(),Blockly.Tooltip.poisonedElement_=null,Blockly.Tooltip.element_=a);clearTimeout(Blockly.Tooltip.mouseOutPid_)};Blockly.Tooltip.onMouseOut_=function(a){Blockly.Tooltip.mouseOutPid_=setTimeout(function(){Blockly.Tooltip.element_=null;Blockly.Tooltip.poisonedElement_=null;Blockly.Tooltip.hide()},1);clearTimeout(Blockly.Tooltip.showPid_)};
+Blockly.Tooltip.onMouseMove_=function(a){if(Blockly.Tooltip.element_&&Blockly.Tooltip.element_.tooltip&&Blockly.dragMode_==Blockly.DRAG_NONE&&!Blockly.WidgetDiv.isVisible())if(Blockly.Tooltip.visible){var b=Blockly.Tooltip.lastX_-a.pageX;a=Blockly.Tooltip.lastY_-a.pageY;Math.sqrt(b*b+a*a)>Blockly.Tooltip.RADIUS_OK&&Blockly.Tooltip.hide()}else Blockly.Tooltip.poisonedElement_!=Blockly.Tooltip.element_&&(clearTimeout(Blockly.Tooltip.showPid_),Blockly.Tooltip.lastX_=a.pageX,Blockly.Tooltip.lastY_=a.pageY,
+Blockly.Tooltip.showPid_=setTimeout(Blockly.Tooltip.show_,Blockly.Tooltip.HOVER_MS))};Blockly.Tooltip.hide=function(){Blockly.Tooltip.visible&&(Blockly.Tooltip.visible=!1,Blockly.Tooltip.DIV&&(Blockly.Tooltip.DIV.style.display="none"));clearTimeout(Blockly.Tooltip.showPid_)};
+Blockly.Tooltip.show_=function(){Blockly.Tooltip.poisonedElement_=Blockly.Tooltip.element_;if(Blockly.Tooltip.DIV){goog.dom.removeChildren(Blockly.Tooltip.DIV);for(var a=Blockly.Tooltip.element_.tooltip;goog.isFunction(a);)a=a();var b=0;if(a.match(/^<img /)){var c=document.createElement("div"),d=document.createElement("div");d.innerHTML=a;d=d.getElementsByTagName("img")[0];d.src&&(c.appendChild(d),b=1,Blockly.Tooltip.DIV.appendChild(c))}if(!b)for(a=Blockly.utils.wrap(a,Blockly.Tooltip.LIMIT),a=a.split("\n"),
+b=0;b<a.length;b++)c=document.createElement("div"),c.appendChild(document.createTextNode(a[b])),Blockly.Tooltip.DIV.appendChild(c);c=Blockly.Tooltip.element_.RTL;a=goog.dom.getViewportSize();Blockly.Tooltip.DIV.style.direction=c?"rtl":"ltr";Blockly.Tooltip.DIV.style.display="block";Blockly.Tooltip.visible=!0;b=Blockly.Tooltip.lastX_;b=c?b-(Blockly.Tooltip.OFFSET_X+Blockly.Tooltip.DIV.offsetWidth):b+Blockly.Tooltip.OFFSET_X;d=Blockly.Tooltip.lastY_+Blockly.Tooltip.OFFSET_Y;d+Blockly.Tooltip.DIV.offsetHeight>
+a.height+window.scrollY&&(d-=Blockly.Tooltip.DIV.offsetHeight+2*Blockly.Tooltip.OFFSET_Y);c?b=Math.max(Blockly.Tooltip.MARGINS-window.scrollX,b):b+Blockly.Tooltip.DIV.offsetWidth>a.width+window.scrollX-2*Blockly.Tooltip.MARGINS&&(b=a.width-Blockly.Tooltip.DIV.offsetWidth-2*Blockly.Tooltip.MARGINS);Blockly.Tooltip.DIV.style.top=d+"px";Blockly.Tooltip.DIV.style.left=b+"px"}};Blockly.Msg={};goog.getMsgOrig=goog.getMsg;goog.getMsg=function(a,b){var c=goog.getMsg.blocklyMsgMap[a];c&&(a=Blockly.Msg[c]);return goog.getMsgOrig(a,b)};goog.getMsg.blocklyMsgMap={Today:"TODAY"};var Blockscad=Blockscad||{};Blockly.FieldLabel=function(a,b){this.size_=new goog.math.Size(0,17.5);this.class_=b;this.setValue(a)};goog.inherits(Blockly.FieldLabel,Blockly.Field);Blockly.FieldLabel.prototype.EDITABLE=!1;
+Blockly.FieldLabel.prototype.init=function(){this.textElement_||(this.textElement_=Blockly.createSvgElement("text",{"class":"blocklyText",y:this.size_.height-5},null),this.class_&&Blockly.addClass_(this.textElement_,this.class_),this.visible_||(this.textElement_.style.display="none"),this.sourceBlock_.getSvgRoot().appendChild(this.textElement_),this.textElement_.tooltip=this.sourceBlock_,Blockly.Tooltip.bindMouseEvents(this.textElement_),this.updateTextNode_())};
+Blockly.FieldLabel.prototype.dispose=function(){goog.dom.removeNode(this.textElement_);this.textElement_=null};Blockly.FieldLabel.prototype.getSvgRoot=function(){return this.textElement_};Blockly.FieldLabel.prototype.setTooltip=function(a){this.textElement_.tooltip=a};Blockly.FieldButton=function(a,b){Blockly.FieldTextInput.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldButton,Blockly.Field);Blockly.FieldButton.prototype.CURSOR="default";
+Blockly.FieldButton.prototype.EDITABLE=!0;Blockly.FieldButton.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldButton.superClass_.dispose.call(this)};Blockly.FieldButton.prototype.setValue=function(a){if(null!==a){if(this.sourceBlock_){var b=this.callValidator(a);null!==b&&(a=b)}Blockly.Field.prototype.setValue.call(this,a)}};Blockly.FieldButton.prototype.showEditor_=function(a){Blockscad.currentInterestingBlock=this.sourceBlock_;$("#importStl").click()};
+Blockly.FieldButton.prototype.widgetDispose_=function(){var a=this;return function(){var b=Blockly.FieldButton.htmlInput_,c=b.value;if(a.sourceBlock_){var d=c;null===d?c=b.defaultValue:void 0!==d&&(c=d)}a.setValue(c);a.sourceBlock_.rendered&&a.sourceBlock_.render();Blockly.unbindEvent_(b.onKeyDownWrapper_);Blockly.unbindEvent_(b.onKeyUpWrapper_);Blockly.unbindEvent_(b.onKeyPressWrapper_);a.workspace_.removeChangeListener(b.onWorkspaceChangeWrapper_);Blockly.FieldTextInput.htmlInput_=null;b=Blockly.WidgetDiv.DIV.style;
+b.width="auto";b.height="auto";b.fontSize=""}};Blockly.Input=function(a,b,c,d){this.type=a;this.name=b;this.sourceBlock_=c;this.connection=d;this.fieldRow=[]};Blockly.Input.prototype.align=Blockly.ALIGN_LEFT;Blockly.Input.prototype.visible_=!0;
+Blockly.Input.prototype.appendField=function(a,b){if(!a&&!b)return this;goog.isString(a)&&(a=new Blockly.FieldLabel(a));a.setSourceBlock(this.sourceBlock_);this.sourceBlock_.rendered&&a.init();a.name=b;a.prefixField&&this.appendField(a.prefixField);this.fieldRow.push(a);a.suffixField&&this.appendField(a.suffixField);this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_());return this};
+Blockly.Input.prototype.appendTitle=function(a,b){console.warn("Deprecated call to appendTitle, use appendField instead.");return this.appendField(a,b)};Blockly.Input.prototype.removeField=function(a){for(var b=0,c;c=this.fieldRow[b];b++)if(c.name===a){c.dispose();this.fieldRow.splice(b,1);this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_());return}goog.asserts.fail('Field "%s" not found.',a)};Blockly.Input.prototype.isVisible=function(){return this.visible_};
+Blockly.Input.prototype.setVisible=function(a){var b=[];if(this.visible_==a)return b;for(var c=(this.visible_=a)?"block":"none",d=0,e;e=this.fieldRow[d];d++)e.setVisible(a);this.connection&&(a?b=this.connection.unhideAll():this.connection.hideAll(),d=this.connection.targetBlock())&&(d.getSvgRoot().style.display=c,a||(d.rendered=!1));return b};Blockly.Input.prototype.setCheck=function(a){if(!this.connection)throw"This input does not have a connection.";this.connection.setCheck(a);return this};
+Blockly.Input.prototype.setAlign=function(a){this.align=a;this.sourceBlock_.rendered&&this.sourceBlock_.render();return this};Blockly.Input.prototype.init=function(){if(this.sourceBlock_.workspace.rendered)for(var a=0;a<this.fieldRow.length;a++)this.fieldRow[a].init()};Blockly.Input.prototype.dispose=function(){for(var a=0,b;b=this.fieldRow[a];a++)b.dispose();this.connection&&this.connection.dispose();this.sourceBlock_=null};Blockly.ConnectionDB=function(){};Blockly.ConnectionDB.prototype=[];Blockly.ConnectionDB.constructor=Blockly.ConnectionDB;Blockly.ConnectionDB.prototype.addConnection=function(a){if(a.inDB_)throw"Connection already in database.";if(!a.getSourceBlock().isInFlyout){var b=this.findPositionForConnection_(a);this.splice(b,0,a);a.inDB_=!0}};
+Blockly.ConnectionDB.prototype.findConnection=function(a){if(!this.length)return-1;var b=this.findPositionForConnection_(a);if(b>=this.length)return-1;for(var c=a.y_,d=b;0<=d&&this[d].y_==c;){if(this[d]==a)return d;d--}for(;b<this.length&&this[b].y_==c;){if(this[b]==a)return b;b++}return-1};
+Blockly.ConnectionDB.prototype.findPositionForConnection_=function(a){if(!this.length)return 0;for(var b=0,c=this.length;b<c;){var d=Math.floor((b+c)/2);if(this[d].y_<a.y_)b=d+1;else if(this[d].y_>a.y_)c=d;else{b=d;break}}return b};Blockly.ConnectionDB.prototype.removeConnection_=function(a){if(!a.inDB_)throw"Connection not in database.";var b=this.findConnection(a);if(-1==b)throw"Unable to find connection in connectionDB.";a.inDB_=!1;this.splice(b,1)};
+Blockly.ConnectionDB.prototype.getNeighbours=function(a,b){function c(a){var c=e-d[a].x_,g=f-d[a].y_;Math.sqrt(c*c+g*g)<=b&&l.push(d[a]);return g<b}for(var d=this,e=a.x_,f=a.y_,g=0,h=d.length-2,k=h;g<k;)d[k].y_<f?g=k:h=k,k=Math.floor((g+h)/2);var l=[],h=g=k;if(d.length){for(;0<=g&&c(g);)g--;do h++;while(h<d.length&&c(h))}return l};Blockly.ConnectionDB.prototype.isInYRange_=function(a,b,c){return Math.abs(this[a].y_-b)<=c};
+Blockly.ConnectionDB.prototype.searchForClosest=function(a,b,c){if(!this.length)return{connection:null,radius:b};var d=!0,e=a.y_,f=a.x_;a.x_=f+c.x;a.y_=e+c.y;var g=this.findPositionForConnection_(a);c=null;for(var h=b,k,l=g-1;0<=l&&this.isInYRange_(l,a.y_,b);)k=this[l],a.isConnectionAllowed(k,h)&&(c=k,h=k.distanceFrom(a),d=a.checkType_(k)),l--;for(;g<this.length&&this.isInYRange_(g,a.y_,b);)k=this[g],a.isConnectionAllowed(k,h)&&(c=k,h=k.distanceFrom(a),d=a.checkType_(k)),g++;a.x_=f;a.y_=e;return{connection:c,
+radius:h,allowed:d}};Blockly.ConnectionDB.init=function(a){var b=[];b[Blockly.INPUT_VALUE]=new Blockly.ConnectionDB;b[Blockly.OUTPUT_VALUE]=new Blockly.ConnectionDB;b[Blockly.NEXT_STATEMENT]=new Blockly.ConnectionDB;b[Blockly.PREVIOUS_STATEMENT]=new Blockly.ConnectionDB;a.connectionDBList=b};
+// Copyright 2016 Google Inc.  Apache License 2.0
+Blockly.Options=function(a){var b=!!a.readOnly;if(b)var c=null,d=!1,e=!1,f=!1,g=!1,h=!1,k=!1;else c=Blockly.Options.parseToolboxTree(a.toolbox),d=!(!c||!c.getElementsByTagName("category").length),e=a.trashcan,void 0===e&&(e=d),f=a.collapse,void 0===f&&(f=d),g=a.comments,void 0===g&&(g=d),h=a.disable,void 0===h&&(h=d),k=a.sounds,void 0===k&&(k=!0);var l=!!a.rtl,p=a.horizontalLayout;void 0===p&&(p=!1);var m=a.toolboxPosition,m="end"===m?!1:!0,m=p?m?Blockly.TOOLBOX_AT_TOP:Blockly.TOOLBOX_AT_BOTTOM:m==
+l?Blockly.TOOLBOX_AT_RIGHT:Blockly.TOOLBOX_AT_LEFT,n=a.scrollbars;void 0===n&&(n=d);var q=a.css;void 0===q&&(q=!0);var t="https://blockly-demo.appspot.com/static/media/";a.media?t=a.media:a.path&&(t=a.path+"media/");this.RTL=l;this.collapse=f;this.comments=g;this.disable=h;this.readOnly=b;this.maxBlocks=a.maxBlocks||Infinity;this.pathToMedia=t;this.hasCategories=d;this.hasScrollbars=n;this.hasTrashcan=e;this.hasSounds=k;this.hasCss=q;this.horizontalLayout=p;this.languageTree=c;this.gridOptions=Blockly.Options.parseGridOptions_(a);
+this.zoomOptions=Blockly.Options.parseZoomOptions_(a);this.toolboxPosition=m};Blockly.Options.prototype.parentWorkspace=null;Blockly.Options.prototype.setMetrics=function(){};Blockly.Options.prototype.getMetrics=function(){return null};
+Blockly.Options.parseZoomOptions_=function(a){a=a.zoom||{};var b={};b.controls=void 0===a.controls?!1:!!a.controls;b.wheel=void 0===a.wheel?!1:!!a.wheel;b.startScale=void 0===a.startScale?1:parseFloat(a.startScale);b.maxScale=void 0===a.maxScale?3:parseFloat(a.maxScale);b.minScale=void 0===a.minScale?.3:parseFloat(a.minScale);b.scaleSpeed=void 0===a.scaleSpeed?1.2:parseFloat(a.scaleSpeed);return b};
+Blockly.Options.parseGridOptions_=function(a){a=a.grid||{};var b={};b.spacing=parseFloat(a.spacing)||0;b.colour=a.colour||"#888";b.length=parseFloat(a.length)||1;b.snap=0<b.spacing&&!!a.snap;return b};Blockly.Options.parseToolboxTree=function(a){a?("string"!=typeof a&&("undefined"==typeof XSLTProcessor&&a.outerHTML?a=a.outerHTML:a instanceof Element||(a=null)),"string"==typeof a&&(a=Blockly.Xml.textToDom(a))):a=null;return a};Blockly.ScrollbarPair=function(a){this.workspace_=a;this.hScroll=new Blockly.Scrollbar(a,!0,!0);this.vScroll=new Blockly.Scrollbar(a,!1,!0);this.corner_=Blockly.createSvgElement("rect",{height:Blockly.Scrollbar.scrollbarThickness,width:Blockly.Scrollbar.scrollbarThickness,"class":"blocklyScrollbarBackground"},null);Blockly.Scrollbar.insertAfter_(this.corner_,a.getBubbleCanvas())};Blockly.ScrollbarPair.prototype.oldHostMetrics_=null;
+Blockly.ScrollbarPair.prototype.dispose=function(){goog.dom.removeNode(this.corner_);this.oldHostMetrics_=this.workspace_=this.corner_=null;this.hScroll.dispose();this.hScroll=null;this.vScroll.dispose();this.vScroll=null};
+Blockly.ScrollbarPair.prototype.resize=function(){var a=this.workspace_.getMetrics();if(a){var b=!1,c=!1;this.oldHostMetrics_&&this.oldHostMetrics_.viewWidth==a.viewWidth&&this.oldHostMetrics_.viewHeight==a.viewHeight&&this.oldHostMetrics_.absoluteTop==a.absoluteTop&&this.oldHostMetrics_.absoluteLeft==a.absoluteLeft?(this.oldHostMetrics_&&this.oldHostMetrics_.contentWidth==a.contentWidth&&this.oldHostMetrics_.viewLeft==a.viewLeft&&this.oldHostMetrics_.contentLeft==a.contentLeft||(b=!0),this.oldHostMetrics_&&
+this.oldHostMetrics_.contentHeight==a.contentHeight&&this.oldHostMetrics_.viewTop==a.viewTop&&this.oldHostMetrics_.contentTop==a.contentTop||(c=!0)):c=b=!0;b&&this.hScroll.resize(a);c&&this.vScroll.resize(a);this.oldHostMetrics_&&this.oldHostMetrics_.viewWidth==a.viewWidth&&this.oldHostMetrics_.absoluteLeft==a.absoluteLeft||this.corner_.setAttribute("x",this.vScroll.position_.x);this.oldHostMetrics_&&this.oldHostMetrics_.viewHeight==a.viewHeight&&this.oldHostMetrics_.absoluteTop==a.absoluteTop||this.corner_.setAttribute("y",
+this.hScroll.position_.y);this.oldHostMetrics_=a}};Blockly.ScrollbarPair.prototype.set=function(a,b){var c={},d=a*this.hScroll.ratio_,e=b*this.vScroll.ratio_,f=this.vScroll.scrollViewSize_;c.x=this.getRatio_(d,this.hScroll.scrollViewSize_);c.y=this.getRatio_(e,f);this.workspace_.setMetrics(c);this.hScroll.setHandlePosition(d);this.vScroll.setHandlePosition(e)};Blockly.ScrollbarPair.prototype.getRatio_=function(a,b){var c=a/b;return isNaN(c)?0:c};
+Blockly.Scrollbar=function(a,b,c){this.workspace_=a;this.pair_=c||!1;this.horizontal_=b;this.oldHostMetrics_=null;this.createDom_();this.position_=new goog.math.Coordinate(0,0);b?(this.svgBackground_.setAttribute("height",Blockly.Scrollbar.scrollbarThickness),this.svgHandle_.setAttribute("height",Blockly.Scrollbar.scrollbarThickness-5),this.svgHandle_.setAttribute("y",2.5),this.lengthAttribute_="width",this.positionAttribute_="x"):(this.svgBackground_.setAttribute("width",Blockly.Scrollbar.scrollbarThickness),
+this.svgHandle_.setAttribute("width",Blockly.Scrollbar.scrollbarThickness-5),this.svgHandle_.setAttribute("x",2.5),this.lengthAttribute_="height",this.positionAttribute_="y");this.onMouseDownBarWrapper_=Blockly.bindEvent_(this.svgBackground_,"mousedown",this,this.onMouseDownBar_);this.onMouseDownHandleWrapper_=Blockly.bindEvent_(this.svgHandle_,"mousedown",this,this.onMouseDownHandle_)};Blockly.Scrollbar.prototype.scrollViewSize_=0;Blockly.Scrollbar.prototype.handleLength_=0;
+Blockly.Scrollbar.prototype.handlePosition_=0;Blockly.Scrollbar.prototype.isVisible_=!0;Blockly.Scrollbar.scrollbarThickness=15;goog.events.BrowserFeature.TOUCH_ENABLED&&(Blockly.Scrollbar.scrollbarThickness=25);
+Blockly.Scrollbar.metricsAreEquivalent_=function(a,b){return a&&b&&a.viewWidth==b.viewWidth&&a.viewHeight==b.viewHeight&&a.viewLeft==b.viewLeft&&a.viewTop==b.viewTop&&a.absoluteTop==b.absoluteTop&&a.absoluteLeft==b.absoluteLeft&&a.contentWidth==b.contentWidth&&a.contentHeight==b.contentHeight&&a.contentLeft==b.contentLeft&&a.contentTop==b.contentTop?!0:!1};
+Blockly.Scrollbar.prototype.dispose=function(){this.onMouseUpHandle_();Blockly.unbindEvent_(this.onMouseDownBarWrapper_);this.onMouseDownBarWrapper_=null;Blockly.unbindEvent_(this.onMouseDownHandleWrapper_);this.onMouseDownHandleWrapper_=null;goog.dom.removeNode(this.svgGroup_);this.workspace_=this.svgHandle_=this.svgBackground_=this.svgGroup_=null};Blockly.Scrollbar.prototype.setHandleLength_=function(a){this.handleLength_=a;this.svgHandle_.setAttribute(this.lengthAttribute_,this.handleLength_)};
+Blockly.Scrollbar.prototype.setHandlePosition=function(a){this.handlePosition_=a;this.svgHandle_.setAttribute(this.positionAttribute_,this.handlePosition_)};Blockly.Scrollbar.prototype.setScrollViewSize_=function(a){this.scrollViewSize_=a;this.svgBackground_.setAttribute(this.lengthAttribute_,this.scrollViewSize_)};Blockly.Scrollbar.prototype.setPosition=function(a,b){this.position_.x=a;this.position_.y=b;this.svgGroup_.setAttribute("transform","translate("+this.position_.x+","+this.position_.y+")")};
+Blockly.Scrollbar.prototype.resize=function(a){if(!a&&(a=this.workspace_.getMetrics(),!a))return;Blockly.Scrollbar.metricsAreEquivalent_(a,this.oldHostMetrics_)||(this.oldHostMetrics_=a,this.horizontal_?this.resizeHorizontal_(a):this.resizeVertical_(a),this.onScroll_())};Blockly.Scrollbar.prototype.resizeHorizontal_=function(a){this.resizeViewHorizontal(a)};
+Blockly.Scrollbar.prototype.resizeViewHorizontal=function(a){var b=a.viewWidth-1;this.pair_&&(b-=Blockly.Scrollbar.scrollbarThickness);this.setScrollViewSize_(Math.max(0,b));b=a.absoluteLeft+.5;this.pair_&&this.workspace_.RTL&&(b+=Blockly.Scrollbar.scrollbarThickness);this.setPosition(b,a.absoluteTop+a.viewHeight-Blockly.Scrollbar.scrollbarThickness-.5);this.resizeContentHorizontal(a)};
+Blockly.Scrollbar.prototype.resizeContentHorizontal=function(a){this.pair_||this.setVisible(this.scrollViewSize_<a.contentWidth);this.ratio_=this.scrollViewSize_/a.contentWidth;if(-Infinity==this.ratio_||Infinity==this.ratio_||isNaN(this.ratio_))this.ratio_=0;this.setHandleLength_(Math.max(0,a.viewWidth*this.ratio_));this.setHandlePosition(this.constrainHandle_((a.viewLeft-a.contentLeft)*this.ratio_))};Blockly.Scrollbar.prototype.resizeVertical_=function(a){this.resizeViewVertical(a)};
+Blockly.Scrollbar.prototype.resizeViewVertical=function(a){var b=a.viewHeight-1;this.pair_&&(b-=Blockly.Scrollbar.scrollbarThickness);this.setScrollViewSize_(Math.max(0,b));b=a.absoluteLeft+.5;this.workspace_.RTL||(b+=a.viewWidth-Blockly.Scrollbar.scrollbarThickness-1);this.setPosition(b,a.absoluteTop+.5);this.resizeContentVertical(a)};
+Blockly.Scrollbar.prototype.resizeContentVertical=function(a){this.pair_||this.setVisible(this.scrollViewSize_<a.contentHeight);this.ratio_=this.scrollViewSize_/a.contentHeight;if(-Infinity==this.ratio_||Infinity==this.ratio_||isNaN(this.ratio_))this.ratio_=0;this.setHandleLength_(Math.max(0,a.viewHeight*this.ratio_));this.setHandlePosition(this.constrainHandle_((a.viewTop-a.contentTop)*this.ratio_))};
+Blockly.Scrollbar.prototype.createDom_=function(){this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyScrollbar"+(this.horizontal_?"Horizontal":"Vertical")},null);this.svgBackground_=Blockly.createSvgElement("rect",{"class":"blocklyScrollbarBackground"},this.svgGroup_);var a=Math.floor((Blockly.Scrollbar.scrollbarThickness-5)/2);this.svgHandle_=Blockly.createSvgElement("rect",{"class":"blocklyScrollbarHandle",rx:a,ry:a},this.svgGroup_);Blockly.Scrollbar.insertAfter_(this.svgGroup_,this.workspace_.getBubbleCanvas())};
+Blockly.Scrollbar.prototype.isVisible=function(){return this.isVisible_};Blockly.Scrollbar.prototype.setVisible=function(a){if(a!=this.isVisible()){if(this.pair_)throw"Unable to toggle visibility of paired scrollbars.";(this.isVisible_=a)?this.svgGroup_.setAttribute("display","block"):(this.workspace_.setMetrics({x:0,y:0}),this.svgGroup_.setAttribute("display","none"))}};
+Blockly.Scrollbar.prototype.onMouseDownBar_=function(a){this.onMouseUpHandle_();if(Blockly.isRightButton(a))a.stopPropagation();else{var b=Blockly.mouseToSvg(a,this.workspace_.getParentSvg(),this.workspace_.getInverseScreenCTM()),b=this.horizontal_?b.x:b.y,c=Blockly.getSvgXY_(this.svgHandle_,this.workspace_),c=this.horizontal_?c.x:c.y,d=this.handlePosition_,e=.95*this.handleLength_;b<=c?d-=e:b>=c+this.handleLength_&&(d+=e);this.setHandlePosition(this.constrainHandle_(d));this.onScroll_();a.stopPropagation();
+a.preventDefault()}};Blockly.Scrollbar.prototype.onMouseDownHandle_=function(a){this.onMouseUpHandle_();Blockly.isRightButton(a)?a.stopPropagation():(this.startDragHandle=this.handlePosition_,this.startDragMouse=this.horizontal_?a.clientX:a.clientY,Blockly.Scrollbar.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,this.onMouseUpHandle_),Blockly.Scrollbar.onMouseMoveWrapper_=Blockly.bindEvent_(document,"mousemove",this,this.onMouseMoveHandle_),a.stopPropagation(),a.preventDefault())};
+Blockly.Scrollbar.prototype.onMouseMoveHandle_=function(a){this.setHandlePosition(this.constrainHandle_(this.startDragHandle+((this.horizontal_?a.clientX:a.clientY)-this.startDragMouse)));this.onScroll_()};
+Blockly.Scrollbar.prototype.onMouseUpHandle_=function(){Blockly.hideChaff(!0);Blockly.Scrollbar.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_),Blockly.Scrollbar.onMouseUpWrapper_=null);Blockly.Scrollbar.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_),Blockly.Scrollbar.onMouseMoveWrapper_=null)};
+Blockly.Scrollbar.prototype.constrainHandle_=function(a){return a=0>=a||isNaN(a)||this.scrollViewSize_<this.handleLength_?0:Math.min(a,this.scrollViewSize_-this.handleLength_)};Blockly.Scrollbar.prototype.onScroll_=function(){var a=this.handlePosition_/this.scrollViewSize_;isNaN(a)&&(a=0);var b={};this.horizontal_?b.x=a:b.y=a;this.workspace_.setMetrics(b)};Blockly.Scrollbar.prototype.set=function(a){this.setHandlePosition(this.constrainHandle_(a*this.ratio_));this.onScroll_()};
+Blockly.Scrollbar.insertAfter_=function(a,b){var c=b.nextSibling,d=b.parentNode;if(!d)throw"Reference node has no parent.";c?d.insertBefore(a,c):d.appendChild(a)};Blockly.Trashcan=function(a){this.workspace_=a};Blockly.Trashcan.prototype.WIDTH_=47;Blockly.Trashcan.prototype.BODY_HEIGHT_=44;Blockly.Trashcan.prototype.LID_HEIGHT_=16;Blockly.Trashcan.prototype.MARGIN_BOTTOM_=20;Blockly.Trashcan.prototype.MARGIN_SIDE_=20;Blockly.Trashcan.prototype.MARGIN_HOTSPOT_=10;Blockly.Trashcan.prototype.SPRITE_LEFT_=0;Blockly.Trashcan.prototype.SPRITE_TOP_=32;Blockly.Trashcan.prototype.isOpen=!1;Blockly.Trashcan.prototype.svgGroup_=null;
+Blockly.Trashcan.prototype.svgLid_=null;Blockly.Trashcan.prototype.lidTask_=0;Blockly.Trashcan.prototype.lidOpen_=0;Blockly.Trashcan.prototype.left_=0;Blockly.Trashcan.prototype.top_=0;
+Blockly.Trashcan.prototype.createDom=function(){this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyTrash"},null);var a=String(Math.random()).substring(2),b=Blockly.createSvgElement("clipPath",{id:"blocklyTrashBodyClipPath"+a},this.svgGroup_);Blockly.createSvgElement("rect",{width:this.WIDTH_,height:this.BODY_HEIGHT_,y:this.LID_HEIGHT_},b);Blockly.createSvgElement("image",{width:Blockly.SPRITE.width,x:-this.SPRITE_LEFT_,height:Blockly.SPRITE.height,y:-this.SPRITE_TOP_,"clip-path":"url(#blocklyTrashBodyClipPath"+
+a+")"},this.svgGroup_).setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",this.workspace_.options.pathToMedia+Blockly.SPRITE.url);b=Blockly.createSvgElement("clipPath",{id:"blocklyTrashLidClipPath"+a},this.svgGroup_);Blockly.createSvgElement("rect",{width:this.WIDTH_,height:this.LID_HEIGHT_},b);this.svgLid_=Blockly.createSvgElement("image",{width:Blockly.SPRITE.width,x:-this.SPRITE_LEFT_,height:Blockly.SPRITE.height,y:-this.SPRITE_TOP_,"clip-path":"url(#blocklyTrashLidClipPath"+a+")"},this.svgGroup_);
+this.svgLid_.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",this.workspace_.options.pathToMedia+Blockly.SPRITE.url);Blockly.bindEvent_(this.svgGroup_,"mouseup",this,this.click);this.animateLid_();return this.svgGroup_};Blockly.Trashcan.prototype.init=function(a){this.bottom_=this.MARGIN_BOTTOM_+a;this.setOpen_(!1);return this.bottom_+this.BODY_HEIGHT_+this.LID_HEIGHT_};
+Blockly.Trashcan.prototype.dispose=function(){this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.workspace_=this.svgLid_=null;goog.Timer.clear(this.lidTask_)};
+Blockly.Trashcan.prototype.position=function(){var a=this.workspace_.getMetrics();a&&(this.workspace_.RTL?(this.left_=this.MARGIN_SIDE_+Blockly.Scrollbar.scrollbarThickness,a.toolboxPosition==Blockly.TOOLBOX_AT_LEFT&&(this.left_+=a.flyoutWidth,this.workspace_.toolbox_&&(this.left_+=a.absoluteLeft))):(this.left_=a.viewWidth+a.absoluteLeft-this.WIDTH_-this.MARGIN_SIDE_-Blockly.Scrollbar.scrollbarThickness,a.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT&&(this.left_-=a.flyoutWidth)),this.top_=a.viewHeight+
+a.absoluteTop-(this.BODY_HEIGHT_+this.LID_HEIGHT_)-this.bottom_,a.toolboxPosition==Blockly.TOOLBOX_AT_BOTTOM&&(this.top_-=a.flyoutHeight),this.svgGroup_.setAttribute("transform","translate("+this.left_+","+this.top_+")"))};
+Blockly.Trashcan.prototype.getClientRect=function(){if(!this.svgGroup_)return null;var a=this.svgGroup_.getBoundingClientRect();return new goog.math.Rect(a.left+this.SPRITE_LEFT_-this.MARGIN_HOTSPOT_,a.top+this.SPRITE_TOP_-this.MARGIN_HOTSPOT_,this.WIDTH_+2*this.MARGIN_HOTSPOT_,this.LID_HEIGHT_+this.BODY_HEIGHT_+2*this.MARGIN_HOTSPOT_)};Blockly.Trashcan.prototype.setOpen_=function(a){this.isOpen!=a&&(goog.Timer.clear(this.lidTask_),this.isOpen=a,this.animateLid_())};
+Blockly.Trashcan.prototype.animateLid_=function(){this.lidOpen_+=this.isOpen?.2:-.2;this.lidOpen_=goog.math.clamp(this.lidOpen_,0,1);var a=45*this.lidOpen_;this.svgLid_.setAttribute("transform","rotate("+(this.workspace_.RTL?-a:a)+","+(this.workspace_.RTL?4:this.WIDTH_-4)+","+(this.LID_HEIGHT_-2)+")");a=goog.math.lerp(.4,.8,this.lidOpen_);this.svgGroup_.style.opacity=a;0<this.lidOpen_&&1>this.lidOpen_&&(this.lidTask_=goog.Timer.callOnce(this.animateLid_,20,this))};
+Blockly.Trashcan.prototype.close=function(){this.setOpen_(!1)};Blockly.Trashcan.prototype.click=function(){var a=this.workspace_.startScrollX-this.workspace_.scrollX,b=this.workspace_.startScrollY-this.workspace_.scrollY;Math.sqrt(a*a+b*b)>Blockly.DRAG_RADIUS||console.log("TODO: Inspect trash.")};Blockly.Xml={};Blockscad=Blockscad||{};Blockly.Xml.workspaceToDom=function(a){var b=goog.dom.createDom("xml");a=a.getTopBlocks(!0);var c=goog.dom.createDom("version");c.setAttribute("num",Blockscad.version);b.appendChild(c);c=goog.dom.createDom("color");c.setAttribute("rgba",Blockscad.defaultColor);b.appendChild(c);for(var c=0,d;d=a[c];c++)b.appendChild(Blockly.Xml.blockToDomWithXY(d));return b};
+Blockly.Xml.blockToDomWithXY=function(a){var b;a.workspace.RTL&&(b=a.workspace.getWidth());var c=Blockly.Xml.blockToDom(a),d=a.getRelativeToSurfaceXY();c.setAttribute("x",Math.round(a.workspace.RTL?b-d.x:d.x));c.setAttribute("y",Math.round(d.y));return c};
+Blockly.Xml.blockToDom=function(a){var b=goog.dom.createDom(a.isShadow()?"shadow":"block");b.setAttribute("type",a.type);b.setAttribute("id",a.id);if(a.mutationToDom){var c=a.mutationToDom();c&&(c.hasChildNodes()||c.hasAttributes())&&b.appendChild(c)}for(var c=0,d;d=a.inputList[c];c++)for(var e=0,f;f=d.fieldRow[e];e++)if(f.name&&(f.EDITABLE||"STL_FILENAME"==f.name||"STL_CONTENTS"==f.name)){var g=goog.dom.createDom("field",null,f.getValue());g.setAttribute("name",f.name);b.appendChild(g)}if(c=a.getCommentText())c=
+goog.dom.createDom("comment",null,c),"object"==typeof a.comment&&(c.setAttribute("pinned",a.comment.isVisible()),d=a.comment.getBubbleSize(),c.setAttribute("h",d.height),c.setAttribute("w",d.width)),b.appendChild(c);a.data&&(c=goog.dom.createDom("data",null,a.data),b.appendChild(c));for(c=0;d=a.inputList[c];c++){var h;f=!0;d.type!=Blockly.DUMMY_INPUT&&(g=d.connection.targetBlock(),d.type==Blockly.INPUT_VALUE?h=goog.dom.createDom("value"):d.type==Blockly.NEXT_STATEMENT&&(h=goog.dom.createDom("statement")),
+e=d.connection.getShadowDom(),!e||g&&g.isShadow()||h.appendChild(Blockly.Xml.cloneShadow_(e)),g&&(h.appendChild(Blockly.Xml.blockToDom(g)),f=!1),h.setAttribute("name",d.name),f||b.appendChild(h))}a.inputsInlineDefault!=a.inputsInline&&b.setAttribute("inline",a.inputsInline);a.isCollapsed()&&b.setAttribute("collapsed",!0);a.disabled&&b.setAttribute("disabled",!0);a.isDeletable()||a.isShadow()||b.setAttribute("deletable",!1);a.isMovable()||a.isShadow()||b.setAttribute("movable",!1);a.isEditable()||
+b.setAttribute("editable",!1);if(c=a.getNextBlock())h=goog.dom.createDom("next",null,Blockly.Xml.blockToDom(c)),b.appendChild(h);e=a.nextConnection&&a.nextConnection.getShadowDom();!e||c&&c.isShadow()||h.appendChild(Blockly.Xml.cloneShadow_(e));return b};
+Blockly.Xml.cloneShadow_=function(a){for(var b=a=a.cloneNode(!0),c;b;)if(b.firstChild)b=b.firstChild;else{for(;b&&!b.nextSibling;)c=b,b=b.parentNode,3==c.nodeType&&""==c.data.trim()&&b.firstChild!=c&&goog.dom.removeNode(c);b&&(c=b,b=b.nextSibling,3==c.nodeType&&""==c.data.trim()&&goog.dom.removeNode(c))}return a};Blockly.Xml.domToText=function(a){a=(new XMLSerializer).serializeToString(a);return a=a.replace('xmlns="http://www.w3.org/1999/xhtml"','xmlns="http://blockscad.einsteinsworkshop.com"')};
+Blockly.Xml.domToPrettyText=function(a){a=Blockly.Xml.domToText(a).split("<");for(var b="",c=1;c<a.length;c++){var d=a[c];"/"==d[0]&&(b=b.substring(2));a[c]=b+"<"+d;"/"!=d[0]&&"/>"!=d.slice(-2)&&(b+="  ")}a=a.join("\n");a=a.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g,"$1</$2>");return a.replace(/^\n/,"")};
+Blockly.Xml.textToDom=function(a){(a=(new DOMParser).parseFromString(a,"text/xml"))&&a.firstChild&&"xml"==a.firstChild.nodeName.toLowerCase()&&a.firstChild===a.lastChild||goog.asserts.fail("Blockly.Xml.textToDom did not obtain a valid XML tree.");return a.firstChild};
+Blockly.Xml.domToWorkspace=function(a,b){if(a instanceof Blockly.Workspace){var c=a;a=b;b=c;console.warn("Deprecated call to Blockly.Xml.domToWorkspace, swap the arguments.")}var d;b.RTL&&(d=b.getWidth());Blockly.Field.startCache();Blockscad.inputVersion=null;var e=!1,c=a.childNodes.length,f=Blockly.Events.getGroup();f||Blockly.Events.setGroup(!0);for(var g=0;g<c;g++){var h=a.childNodes[g],k=h.nodeName.toLowerCase();if("version"==k)Blockscad.inputVersion=h.getAttribute("num");else if("color"==k)e=
+h.getAttribute("rgba"),"undefined"==e?(e=!0,Blockscad.setColor(255,128,255)):(e=e.split(","),Blockscad.setColor(e[0],e[1],e[2]),e=!0);else if("block"==k||"shadow"==k&&!Blockly.Events.recordUndo){var k=Blockly.Xml.domToBlock(h,b),l=parseInt(h.getAttribute("x"),10),h=parseInt(h.getAttribute("y"),10);isNaN(l)||isNaN(h)||k.moveBy(b.RTL?d-l:l,h)}else"shadow"==k&&goog.asserts.fail("Shadow block cannot be a top-level block.")}f||Blockly.Events.setGroup(!1);Blockly.Field.stopCache();Blockscad.inputVersion=
+Blockscad.version;e||Blockscad.setColor(255,128,255)};
+Blockly.Xml.domToBlock=function(a,b){if(a instanceof Blockly.Workspace){var c=a;a=b;b=c;console.warn("Deprecated call to Blockly.Xml.domToBlock, swap the arguments.")}Blockly.Events.disable();try{var d=Blockly.Xml.domToBlockHeadless_(a,b);if(b.rendered){d.setConnectionsHidden(!0);for(var e=d.getDescendants(),f=e.length-1;0<=f;f--)e[f].initSvg();for(f=e.length-1;0<=f;f--)e[f].render(!1);setTimeout(function(){d.workspace&&d.setConnectionsHidden(!1)},1);d.updateDisabled();Blockly.resizeSvgContents(b)}}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&
+Blockly.Events.fire(new Blockly.Events.Create(d));return d};
+Blockly.Xml.domToBlockHeadless_=function(a,b){var c=null,d=a.getAttribute("type");goog.asserts.assert(d,"Block type unspecified: %s",a.outerHTML);for(var e=a.getAttribute("id"),c=b.newBlock(d,e),f=null,e=0,g;g=a.childNodes[e];e++)if(3!=g.nodeType){for(var h=f=null,k=0,l;l=g.childNodes[k];k++)1==l.nodeType&&("block"==l.nodeName.toLowerCase()?f=l:"shadow"==l.nodeName.toLowerCase()&&(h=l));!f&&h&&(f=h);k=g.getAttribute("name");switch(g.nodeName.toLowerCase()){case "mutation":c.domToMutation&&(c.domToMutation(g),
+c.initSvg&&c.initSvg());break;case "comment":c.setCommentText(g.textContent);var p=g.getAttribute("pinned");p&&!c.isInFlyout&&setTimeout(function(){c.comment&&c.comment.setVisible&&c.comment.setVisible("true"==p)},1);f=parseInt(g.getAttribute("w"),10);g=parseInt(g.getAttribute("h"),10);!isNaN(f)&&!isNaN(g)&&c.comment&&c.comment.setVisible&&c.comment.setBubbleSize(f,g);break;case "data":c.data=g.textContent;break;case "title":case "field":f=c.getField(k);if(!f){console.warn("Ignoring non-existent field "+
+k+" in block "+d);break}f.setValue(g.textContent);break;case "value":case "statement":g=c.getInput(k);if(!g){console.warn("Ignoring non-existent input "+k+" in block "+d);break}h&&g.connection.setShadowDom(h);f&&(f=Blockly.Xml.domToBlockHeadless_(f,b),f.outputConnection?g.connection.connect(f.outputConnection):f.previousConnection?g.connection.connect(f.previousConnection):goog.asserts.fail("Child block does not have output or previous statement."));break;case "next":h&&c.nextConnection&&c.nextConnection.setShadowDom(h);
+f&&(goog.asserts.assert(c.nextConnection,"Next statement does not exist."),goog.asserts.assert(!c.nextConnection.isConnected(),"Next statement is already connected."),f=Blockly.Xml.domToBlockHeadless_(f,b),goog.asserts.assert(f.previousConnection,"Next block does not have previous statement."),c.nextConnection.connect(f.previousConnection));break;default:console.warn("Ignoring unknown tag: "+g.nodeName)}}(e=a.getAttribute("inline"))&&c.setInputsInline("true"==e);(e=a.getAttribute("disabled"))&&c.setDisabled("true"==
+e);(e=a.getAttribute("deletable"))&&c.setDeletable("true"==e);(e=a.getAttribute("movable"))&&c.setMovable("true"==e);(e=a.getAttribute("editable"))&&c.setEditable("true"==e);(e=a.getAttribute("collapsed"))&&c.setCollapsed("true"==e);if("shadow"==a.nodeName.toLowerCase()){d=c.getChildren();for(e=0;g=d[e];e++)goog.asserts.assert(g.isShadow(),"Shadow block not allowed non-shadow child.");c.setShadow(!0)}return c};
+Blockly.Xml.deleteNext=function(a){for(var b=0,c;c=a.childNodes[b];b++)if("next"==c.nodeName.toLowerCase()){a.removeChild(c);break}};goog.global.Blockly||(goog.global.Blockly={});goog.global.Blockly.Xml||(goog.global.Blockly.Xml={});goog.global.Blockly.Xml.domToText=Blockly.Xml.domToText;goog.global.Blockly.Xml.domToWorkspace=Blockly.Xml.domToWorkspace;goog.global.Blockly.Xml.textToDom=Blockly.Xml.textToDom;goog.global.Blockly.Xml.workspaceToDom=Blockly.Xml.workspaceToDom;
+// Copyright 2015 Google Inc.  Apache License 2.0
+Blockly.ZoomControls=function(a){this.workspace_=a};Blockly.ZoomControls.prototype.WIDTH_=32;Blockly.ZoomControls.prototype.HEIGHT_=30;Blockly.ZoomControls.prototype.MARGIN_BOTTOM_=100;Blockly.ZoomControls.prototype.MARGIN_SIDE_=35;Blockly.ZoomControls.prototype.svgGroup_=null;Blockly.ZoomControls.prototype.left_=0;Blockly.ZoomControls.prototype.top_=0;
+Blockly.ZoomControls.prototype.createDom=function(){var a=this.workspace_;this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyZoom"},null);var b=String(Math.random()).substring(2),c=Blockly.createSvgElement("clipPath",{id:"blocklyZoomoutClipPath"+b},this.svgGroup_);Blockly.createSvgElement("rect",{width:32,height:32,y:77},c);var d=Blockly.createSvgElement("image",{width:Blockly.SPRITE.width,height:Blockly.SPRITE.height,x:-64,y:-15,"clip-path":"url(#blocklyZoomoutClipPath"+b+")"},this.svgGroup_);
+d.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",a.options.pathToMedia+Blockly.SPRITE.url);c=Blockly.createSvgElement("clipPath",{id:"blocklyZoominClipPath"+b},this.svgGroup_);Blockly.createSvgElement("rect",{width:32,height:32,y:43},c);var e=Blockly.createSvgElement("image",{width:Blockly.SPRITE.width,height:Blockly.SPRITE.height,x:-32,y:-49,"clip-path":"url(#blocklyZoominClipPath"+b+")"},this.svgGroup_);e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",a.options.pathToMedia+
+Blockly.SPRITE.url);c=Blockly.createSvgElement("clipPath",{id:"blocklyZoomresetClipPath"+b},this.svgGroup_);Blockly.createSvgElement("rect",{width:32,height:32},c);b=Blockly.createSvgElement("image",{width:Blockly.SPRITE.width,height:Blockly.SPRITE.height,y:-92,"clip-path":"url(#blocklyZoomresetClipPath"+b+")"},this.svgGroup_);b.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",a.options.pathToMedia+Blockly.SPRITE.url);Blockly.bindEvent_(b,"mousedown",null,function(b){a.setScale(1);a.scrollCenter();
+b.stopPropagation();b.preventDefault()});Blockly.bindEvent_(e,"mousedown",null,function(b){a.zoomCenter(1);b.stopPropagation();b.preventDefault()});Blockly.bindEvent_(d,"mousedown",null,function(b){a.zoomCenter(-1);b.stopPropagation();b.preventDefault()});return this.svgGroup_};Blockly.ZoomControls.prototype.init=function(a){this.bottom_=this.MARGIN_BOTTOM_+a;return this.bottom_+this.HEIGHT_};
+Blockly.ZoomControls.prototype.dispose=function(){this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.workspace_=null};
+Blockly.ZoomControls.prototype.position=function(){var a=this.workspace_.getMetrics();a&&(this.workspace_.RTL?(this.left_=this.MARGIN_SIDE_+Blockly.Scrollbar.scrollbarThickness,a.toolboxPosition==Blockly.TOOLBOX_AT_LEFT&&(this.left_+=a.flyoutWidth,this.workspace_.toolbox_&&(this.left_+=a.absoluteLeft))):(this.left_=a.viewWidth+a.absoluteLeft-this.WIDTH_-this.MARGIN_SIDE_-Blockly.Scrollbar.scrollbarThickness,a.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT&&(this.left_-=a.flyoutWidth)),this.top_=a.viewHeight+
+a.absoluteTop-this.HEIGHT_-this.bottom_,a.toolboxPosition==Blockly.TOOLBOX_AT_BOTTOM&&(this.top_-=a.flyoutHeight),this.svgGroup_.setAttribute("transform","translate("+this.left_+","+this.top_+")"))};
+// Copyright 2014 Google Inc.  Apache License 2.0
+Blockscad=Blockscad||{};Blockly.WorkspaceSvg=function(a){Blockly.WorkspaceSvg.superClass_.constructor.call(this,a);this.getMetrics=a.getMetrics;this.setMetrics=a.setMetrics;Blockly.ConnectionDB.init(this);this.SOUNDS_=Object.create(null)};goog.inherits(Blockly.WorkspaceSvg,Blockly.Workspace);Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_=null;Blockly.WorkspaceSvg.prototype.rendered=!0;Blockly.WorkspaceSvg.prototype.isFlyout=!1;Blockly.WorkspaceSvg.prototype.dragMode_=Blockly.DRAG_NONE;
+Blockly.WorkspaceSvg.prototype.scrollX=0;Blockly.WorkspaceSvg.prototype.scrollY=0;Blockly.WorkspaceSvg.prototype.startScrollX=0;Blockly.WorkspaceSvg.prototype.startScrollY=0;Blockly.WorkspaceSvg.prototype.dragDeltaXY_=null;Blockly.WorkspaceSvg.prototype.scale=1;Blockly.WorkspaceSvg.prototype.trashcan=null;Blockly.WorkspaceSvg.prototype.scrollbar=null;Blockly.WorkspaceSvg.prototype.lastSound_=null;Blockly.WorkspaceSvg.prototype.inverseScreenCTM_=null;
+Blockly.WorkspaceSvg.prototype.getInverseScreenCTM=function(){return this.inverseScreenCTM_};Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM=function(){this.inverseScreenCTM_=this.getParentSvg().getScreenCTM().inverse()};Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper=function(a){this.resizeHandlerWrapper_=a};
+Blockly.WorkspaceSvg.prototype.createDom=function(a){this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyWorkspace"},null);a&&(this.svgBackground_=Blockly.createSvgElement("rect",{height:"100%",width:"100%","class":a},this.svgGroup_),"blocklyMainBackground"==a&&(this.svgBackground_.style.fill="url(#"+this.options.gridPattern.id+")"));this.svgBlockCanvas_=Blockly.createSvgElement("g",{"class":"blocklyBlockCanvas"},this.svgGroup_,this);this.svgBubbleCanvas_=Blockly.createSvgElement("g",{"class":"blocklyBubbleCanvas"},
+this.svgGroup_,this);a=Blockly.Scrollbar.scrollbarThickness;this.options.hasTrashcan&&(a=this.addTrashcan_(a));this.options.zoomOptions&&this.options.zoomOptions.controls&&(a=this.addZoomControls_(a));Blockly.bindEvent_(this.svgGroup_,"mousedown",this,this.onMouseDown_);var b=this;Blockly.bindEvent_(this.svgGroup_,"touchstart",null,function(a){Blockly.longStart_(a,b)});this.options.zoomOptions&&this.options.zoomOptions.wheel&&Blockly.bindEvent_(this.svgGroup_,"wheel",this,this.onMouseWheel_);this.options.hasCategories?
+this.toolbox_=new Blockly.Toolbox(this):this.options.languageTree&&this.addFlyout_();this.updateGridPattern_();this.recordDeleteAreas();return this.svgGroup_};
+Blockly.WorkspaceSvg.prototype.dispose=function(){this.rendered=!1;Blockly.WorkspaceSvg.superClass_.dispose.call(this);this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.svgBubbleCanvas_=this.svgBlockCanvas_=null;this.toolbox_&&(this.toolbox_.dispose(),this.toolbox_=null);this.flyout_&&(this.flyout_.dispose(),this.flyout_=null);this.trashcan&&(this.trashcan.dispose(),this.trashcan=null);this.scrollbar&&(this.scrollbar.dispose(),this.scrollbar=null);this.zoomControls_&&
+(this.zoomControls_.dispose(),this.zoomControls_=null);this.options.parentWorkspace||goog.dom.removeNode(this.getParentSvg());this.resizeHandlerWrapper_&&(Blockly.unbindEvent_(this.resizeHandlerWrapper_),this.resizeHandlerWrapper_=null)};Blockly.WorkspaceSvg.prototype.newBlock=function(a,b){return new Blockly.BlockSvg(this,a,b)};
+Blockly.WorkspaceSvg.prototype.addTrashcan_=function(a){this.trashcan=new Blockly.Trashcan(this);var b=this.trashcan.createDom();this.svgGroup_.insertBefore(b,this.svgBlockCanvas_);return this.trashcan.init(a)};Blockly.WorkspaceSvg.prototype.addZoomControls_=function(a){this.zoomControls_=new Blockly.ZoomControls(this);var b=this.zoomControls_.createDom();this.svgGroup_.appendChild(b);return this.zoomControls_.init(a)};
+Blockly.WorkspaceSvg.prototype.addFlyout_=function(){this.flyout_=new Blockly.Flyout({disabledPatternId:this.options.disabledPatternId,parentWorkspace:this,RTL:this.RTL,horizontalLayout:this.horizontalLayout,toolboxPosition:this.options.toolboxPosition});this.flyout_.autoClose=!1;var a=this.flyout_.createDom();this.svgGroup_.insertBefore(a,this.svgBlockCanvas_)};Blockly.WorkspaceSvg.prototype.resizeContents=function(){this.scrollbar&&this.scrollbar.resize();this.updateInverseScreenCTM()};
+Blockly.WorkspaceSvg.prototype.resize=function(){this.toolbox_&&this.toolbox_.position();this.flyout_&&this.flyout_.position();this.trashcan&&this.trashcan.position();this.zoomControls_&&this.zoomControls_.position();this.scrollbar&&this.scrollbar.resize();this.updateInverseScreenCTM();this.recordDeleteAreas()};Blockly.WorkspaceSvg.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.WorkspaceSvg.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_};
+Blockly.WorkspaceSvg.prototype.getParentSvg=function(){if(this.cachedParentSvg_)return this.cachedParentSvg_;for(var a=this.svgGroup_;a;){if("svg"==a.tagName)return this.cachedParentSvg_=a;a=a.parentNode}return null};Blockly.WorkspaceSvg.prototype.translate=function(a,b){var c="translate("+a+","+b+") scale("+this.scale+")";this.svgBlockCanvas_.setAttribute("transform",c);this.svgBubbleCanvas_.setAttribute("transform",c)};
+Blockly.WorkspaceSvg.prototype.getWidth=function(){var a=this.getMetrics();return a?a.viewWidth/this.scale:0};Blockly.WorkspaceSvg.prototype.setVisible=function(a){this.getParentSvg().style.display=a?"block":"none";this.toolbox_&&(this.toolbox_.HtmlDiv.style.display=a?"block":"none");a?(this.render(),this.toolbox_&&this.toolbox_.position()):Blockly.hideChaff(!0)};Blockly.WorkspaceSvg.prototype.render=function(){for(var a=this.getAllBlocks(),b=a.length-1;0<=b;b--)a[b].render(!1)};
+Blockly.WorkspaceSvg.prototype.traceOn=function(a){this.traceOn_=a;this.traceWrapper_&&(Blockly.unbindEvent_(this.traceWrapper_),this.traceWrapper_=null);a&&(this.traceWrapper_=Blockly.bindEvent_(this.svgBlockCanvas_,"blocklySelectChange",this,function(){this.traceOn_=!1}))};
+Blockly.WorkspaceSvg.prototype.highlightBlock=function(a){this.traceOn_&&Blockly.dragMode_!=Blockly.DRAG_NONE&&this.traceOn(!1);if(this.traceOn_){var b=null;if(a&&(b=this.getBlockById(a),!b))return;this.traceOn(!1);b?b.select():Blockly.selected&&Blockly.selected.unselect();var c=this;setTimeout(function(){c.traceOn(!0)},1)}};
+Blockly.WorkspaceSvg.prototype.paste=function(a){if(this.rendered&&!(a.getElementsByTagName("block").length>=this.remainingCapacity())){Blockly.terminateDrag_();Blockly.Events.disable();try{var b=Blockly.Xml.domToBlock(a,this),c=parseInt(a.getAttribute("x"),10),d=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(d)){this.RTL&&(c=-c);do{a=!1;for(var e=this.getAllBlocks(),f=0,g;g=e[f];f++){var h=g.getRelativeToSurfaceXY();if(1>=Math.abs(c-h.x)&&1>=Math.abs(d-h.y)){a=!0;break}}if(!a)for(var k=b.getConnections_(!1),
+f=0,l;l=k[f];f++)if(l.closest(Blockly.SNAP_RADIUS,new goog.math.Coordinate(c,d)).connection){a=!0;break}a&&(c=this.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,d+=2*Blockly.SNAP_RADIUS)}while(a);b.moveBy(c,d)}}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!b.isShadow()&&Blockly.Events.fire(new Blockly.Events.Create(b));b.select()}};
+Blockly.WorkspaceSvg.prototype.recordDeleteAreas=function(){this.deleteAreaTrash_=this.trashcan?this.trashcan.getClientRect():null;this.deleteAreaToolbox_=this.flyout_?this.flyout_.getClientRect():this.toolbox_?this.toolbox_.getClientRect():null};
+Blockly.WorkspaceSvg.prototype.isDeleteArea=function(a){a=new goog.math.Coordinate(a.clientX,a.clientY);if(this.deleteAreaTrash_){if(this.deleteAreaTrash_.contains(a))return this.trashcan.setOpen_(!0),Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE),!0;this.trashcan.setOpen_(!1)}if(this.deleteAreaToolbox_&&this.deleteAreaToolbox_.contains(a))return Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE),!0;Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);return!1};
+Blockly.WorkspaceSvg.prototype.onMouseDown_=function(a){this.markFocused();Blockly.isTargetInput_(a)||(Blockly.terminateDrag_(),Blockly.hideChaff(),a.target&&a.target.nodeName&&("svg"==a.target.nodeName.toLowerCase()||a.target==this.svgBackground_)&&Blockly.selected&&!this.options.readOnly&&Blockly.selected.unselect(),Blockly.isRightButton(a)?this.showContextMenu_(a):this.scrollbar&&(this.dragMode_=Blockly.DRAG_BEGIN,this.startDragMouseX=a.clientX,this.startDragMouseY=a.clientY,this.startDragMetrics=
+this.getMetrics(),this.startScrollX=this.scrollX,this.startScrollY=this.scrollY,"mouseup"in Blockly.bindEvent_.TOUCH_MAP&&(Blockly.onTouchUpWrapper_=Blockly.onTouchUpWrapper_||[],Blockly.onTouchUpWrapper_=Blockly.onTouchUpWrapper_.concat(Blockly.bindEvent_(document,"mouseup",null,Blockly.onMouseUp_))),Blockly.onMouseMoveWrapper_=Blockly.onMouseMoveWrapper_||[],Blockly.onMouseMoveWrapper_=Blockly.onMouseMoveWrapper_.concat(Blockly.bindEvent_(document,"mousemove",null,Blockly.onMouseMove_))),a.stopPropagation(),
+a.preventDefault())};Blockly.WorkspaceSvg.prototype.startDrag=function(a,b){var c=Blockly.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());c.x/=this.scale;c.y/=this.scale;this.dragDeltaXY_=goog.math.Coordinate.difference(b,c)};Blockly.WorkspaceSvg.prototype.moveDrag=function(a){a=Blockly.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());a.x/=this.scale;a.y/=this.scale;return goog.math.Coordinate.sum(this.dragDeltaXY_,a)};
+Blockly.WorkspaceSvg.prototype.isDragging=function(){return Blockly.dragMode_==Blockly.DRAG_FREE||Blockly.Flyout.startFlyout_&&Blockly.Flyout.startFlyout_.dragMode_==Blockly.DRAG_FREE||this.dragMode_==Blockly.DRAG_FREE};Blockly.WorkspaceSvg.prototype.onMouseWheel_=function(a){Blockly.terminateDrag_();var b=0<a.deltaY?-1:1,c=Blockly.mouseToSvg(a,this.getParentSvg(),this.getInverseScreenCTM());this.zoom(c.x,c.y,b);a.preventDefault()};
+Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox=function(){var a=this.getTopBlocks(!1);if(!a.length)return{x:0,y:0,width:0,height:0};for(var b=a[0].getBoundingRectangle(),c=1;c<a.length;c++){var d=a[c].getBoundingRectangle();d.topLeft.x<b.topLeft.x&&(b.topLeft.x=d.topLeft.x);d.bottomRight.x>b.bottomRight.x&&(b.bottomRight.x=d.bottomRight.x);d.topLeft.y<b.topLeft.y&&(b.topLeft.y=d.topLeft.y);d.bottomRight.y>b.bottomRight.y&&(b.bottomRight.y=d.bottomRight.y)}return{x:b.topLeft.x,y:b.topLeft.y,width:b.bottomRight.x-
+b.topLeft.x,height:b.bottomRight.y-b.topLeft.y}};Blockly.WorkspaceSvg.prototype.cleanUp_=function(){Blockly.Events.setGroup(!0);for(var a=this.getTopBlocks(!0),b=0,c=0,d;d=a[c];c++){var e=d.getRelativeToSurfaceXY();d.moveBy(-e.x,b-e.y);d.snapToGrid();b=d.getRelativeToSurfaceXY().y+d.getHeightWidth().height+Blockly.BlockSvg.MIN_BLOCK_Y}Blockly.Events.setGroup(!1);Blockly.resizeSvgContents(this)};
+Blockly.WorkspaceSvg.prototype.showContextMenu_=function(a){function b(a){if(a.isDeletable())l=l.concat(a.getDescendants());else{a=a.getChildren();for(var c=0;c<a.length;c++)b(a[c])}}if(!this.options.readOnly&&!this.isFlyout){var c=[],d=this.getTopBlocks(!0);Blockly.genUid();var e={};e.text=Blockly.Msg.UNDO;e.enabled=0<this.undoStack_.length;e.callback=this.undo.bind(this,!1);c.push(e);e={};e.text=Blockly.Msg.REDO;e.enabled=0<this.redoStack_.length;e.callback=this.undo.bind(this,!0);c.push(e);this.scrollbar&&
+(e={},e.text=Blockly.Msg.CLEAN_UP,e.enabled=1<d.length,e.callback=this.cleanUp_.bind(this),c.push(e));if(this.options.collapse){for(var f=!1,g=!1,e=0;e<d.length;e++)for(var h=d[e];h;)h.isCollapsed()?f=!0:g=!0,h=h.getNextBlock();var k=function(a){for(var b=0,c=0;c<d.length;c++)for(var e=d[c];e;)setTimeout(e.setCollapsed.bind(e,a),b),e=e.getNextBlock(),b+=10},e={enabled:g};e.text=Blockly.Msg.COLLAPSE_ALL;e.callback=function(){k(!0)};c.push(e);e={enabled:f};e.text=Blockly.Msg.EXPAND_ALL;e.callback=function(){k(!1)};
+c.push(e)}Blockly.backlight.length&&(e={enabled:!0},e.text=Blockscad.Msg.REMOVE_BLOCK_HIGHLIGHTING,e.callback=function(){Blockscad.workspace.clearBacklight()},c.push(e));if(this.options.disable){g=f=!1;d=this.getTopBlocks(!1);for(e=0;e<d.length;e++)for(h=d[e];h;)h.disabled?f=!0:g=!0,h=h.getNextBlock();e={enabled:g};e.text=Blockscad.Msg.DISABLE_ALL;e.callback=function(){for(var a=0;a<d.length;a++)for(var b=d[a];b;)"PROCEDURE"!=b.category&&b.setDisabled(!0),b=b.getNextBlock()};c.push(e);e={enabled:f};
+e.text=Blockscad.Msg.ENABLE_ALL;e.callback=function(){for(var a=0;a<d.length;a++)for(var b=d[a];b;)b.setDisabled(!1),b=b.getNextBlock()};c.push(e)}for(var l=[],e=0;e<d.length;e++)b(d[e]);e={text:1==l.length?Blockly.Msg.DELETE_BLOCK:Blockly.Msg.DELETE_X_BLOCKS.replace("%1",String(l.length)),enabled:0<l.length,callback:function(){Blockscad.discard()}};c.push(e);Blockly.ContextMenu.show(a,c,this.RTL)}};
+Blockly.WorkspaceSvg.prototype.loadAudio_=function(a,b){if(a.length){try{var c=new window.Audio}catch(h){return}for(var d,e=0;e<a.length;e++){var f=a[e],g=f.match(/\.(\w+)$/);if(g&&c.canPlayType("audio/"+g[1])){d=new window.Audio(f);break}}d&&d.play&&(this.SOUNDS_[b]=d)}};Blockly.WorkspaceSvg.prototype.preloadAudio_=function(){for(var a in this.SOUNDS_){var b=this.SOUNDS_[a];b.volume=.01;b.play();b.pause();if(goog.userAgent.IPAD||goog.userAgent.IPHONE)break}};
+Blockly.WorkspaceSvg.prototype.playAudio=function(a,b){var c=this.SOUNDS_[a];if(c){var d=new Date;d-this.lastSound_<Blockly.SOUND_LIMIT||(this.lastSound_=d,goog.userAgent.DOCUMENT_MODE&&9===goog.userAgent.DOCUMENT_MODE||goog.userAgent.IPAD||goog.userAgent.ANDROID||c.cloneNode())}else document.getElementById("audio_"+a).play()};
+Blockly.WorkspaceSvg.prototype.updateToolbox=function(a){if(a=Blockly.Options.parseToolboxTree(a)){if(!this.options.languageTree)throw"Existing toolbox is null.  Can't create new toolbox.";if(a.getElementsByTagName("category").length){if(!this.toolbox_)throw"Existing toolbox has no categories.  Can't change mode.";this.options.languageTree=a;this.toolbox_.populate_(a);this.toolbox_.addColour_()}else{if(!this.flyout_)throw"Existing toolbox has categories.  Can't change mode.";this.options.languageTree=
+a;this.flyout_.show(a.childNodes)}}else if(this.options.languageTree)throw"Can't nullify an existing toolbox.";};Blockly.WorkspaceSvg.prototype.markFocused=function(){this.options.parentWorkspace?this.options.parentWorkspace.markFocused():Blockly.mainWorkspace=this};
+Blockly.WorkspaceSvg.prototype.zoom=function(a,b,c){var d=this.options.zoomOptions.scaleSpeed,e=this.getMetrics(),f=this.getParentSvg().createSVGPoint();f.x=a;f.y=b;f=f.matrixTransform(this.getCanvas().getCTM().inverse());a=f.x;b=f.y;f=this.getCanvas();d=1==c?d:1/d;c=this.scale*d;c>this.options.zoomOptions.maxScale?d=this.options.zoomOptions.maxScale/this.scale:c<this.options.zoomOptions.minScale&&(d=this.options.zoomOptions.minScale/this.scale);this.scale!=c&&(this.scrollbar&&(a=f.getCTM().translate(a*
+(1-d),b*(1-d)).scale(d),this.scrollX=a.e-e.absoluteLeft,this.scrollY=a.f-e.absoluteTop),this.setScale(c))};Blockly.WorkspaceSvg.prototype.zoomCenter=function(a){var b=this.getMetrics();this.zoom(b.viewWidth/2,b.viewHeight/2,a)};
+Blockly.WorkspaceSvg.prototype.zoomToFit=function(){var a=this.getMetrics(),b=this.getBlocksBoundingBox(),c=b.width,b=b.height;if(c){var d=a.viewWidth,e=a.viewHeight;this.flyout_&&(d-=this.flyout_.width_);this.scrollbar||(c+=a.contentLeft,b+=a.contentTop);this.setScale(Math.min(d/c,e/b));this.scrollCenter()}};
+Blockly.WorkspaceSvg.prototype.scrollCenter=function(){if(this.scrollbar){var a=this.getMetrics(),b=(a.contentWidth-a.viewWidth)/2;this.flyout_&&(b-=this.flyout_.width_/2);this.scrollbar.set(b,(a.contentHeight-a.viewHeight)/2)}};
+Blockly.WorkspaceSvg.prototype.setScale=function(a){this.options.zoomOptions.maxScale&&a>this.options.zoomOptions.maxScale?a=this.options.zoomOptions.maxScale:this.options.zoomOptions.minScale&&a<this.options.zoomOptions.minScale&&(a=this.options.zoomOptions.minScale);this.scale=a;this.updateGridPattern_();this.scrollbar?this.scrollbar.resize():this.translate(this.scrollX,this.scrollY);Blockly.hideChaff(!1);this.flyout_&&this.flyout_.reflow()};
+Blockly.WorkspaceSvg.prototype.updateGridPattern_=function(){if(this.options.gridPattern){var a=this.options.gridOptions.spacing*this.scale||100;this.options.gridPattern.setAttribute("width",a);this.options.gridPattern.setAttribute("height",a);var a=Math.floor(this.options.gridOptions.spacing/2)+.5,b=a-this.options.gridOptions.length/2,c=a+this.options.gridOptions.length/2,d=this.options.gridPattern.firstChild,e=d&&d.nextSibling,a=a*this.scale,b=b*this.scale,c=c*this.scale;d&&(d.setAttribute("stroke-width",
+this.scale),d.setAttribute("x1",b),d.setAttribute("y1",a),d.setAttribute("x2",c),d.setAttribute("y2",a));e&&(e.setAttribute("stroke-width",this.scale),e.setAttribute("x1",a),e.setAttribute("y1",b),e.setAttribute("x2",a),e.setAttribute("y2",c))}};Blockly.WorkspaceSvg.prototype.clearBacklight=function(){for(;Blockly.backlight.length;){var a=this.getBlockById(Blockly.backlight[0]);a&&a.unbacklight()}};Blockly.WorkspaceSvg.prototype.setVisible=Blockly.WorkspaceSvg.prototype.setVisible;Blockly.Mutator=function(a){Blockly.Mutator.superClass_.constructor.call(this,null);this.quarkNames_=a};goog.inherits(Blockly.Mutator,Blockly.Icon);Blockly.Mutator.prototype.workspaceWidth_=0;Blockly.Mutator.prototype.workspaceHeight_=0;
+Blockly.Mutator.prototype.drawIcon_=function(a){Blockly.createSvgElement("rect",{"class":"blocklyIconShape",rx:"4",ry:"4",height:"16",width:"16"},a);Blockly.createSvgElement("path",{"class":"blocklyIconSymbol",d:"m4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 -0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z"},a);
+Blockly.createSvgElement("circle",{"class":"blocklyIconShape",r:"2.7",cx:"8",cy:"8"},a)};Blockly.Mutator.prototype.iconClick_=function(a){this.block_.isEditable()&&Blockly.Icon.prototype.iconClick_.call(this,a)};
+Blockly.Mutator.prototype.createEditor_=function(){this.svgDialog_=Blockly.createSvgElement("svg",{x:Blockly.Bubble.BORDER_WIDTH,y:Blockly.Bubble.BORDER_WIDTH},null);if(this.quarkNames_.length)for(var a=goog.dom.createDom("xml"),b=0,c;c=this.quarkNames_[b];b++)a.appendChild(goog.dom.createDom("block",{type:c}));else a=null;a={languageTree:a,parentWorkspace:this.block_.workspace,pathToMedia:this.block_.workspace.options.pathToMedia,RTL:this.block_.RTL,toolboxPosition:this.block_.RTL?Blockly.TOOLBOX_AT_RIGHT:
+Blockly.TOOLBOX_AT_LEFT,horizontalLayout:!1,getMetrics:this.getFlyoutMetrics_.bind(this),setMetrics:null};this.workspace_=new Blockly.WorkspaceSvg(a);this.svgDialog_.appendChild(this.workspace_.createDom("blocklyMutatorBackground"));return this.svgDialog_};
+Blockly.Mutator.prototype.updateEditable=function(){this.block_.isInFlyout||(this.block_.isEditable()?this.iconGroup_&&Blockly.removeClass_(this.iconGroup_,"blocklyIconGroupReadonly"):(this.setVisible(!1),this.iconGroup_&&Blockly.addClass_(this.iconGroup_,"blocklyIconGroupReadonly")));Blockly.Icon.prototype.updateEditable.call(this)};
+Blockly.Mutator.prototype.resizeBubble_=function(){var a=2*Blockly.Bubble.BORDER_WIDTH,b=this.workspace_.getCanvas().getBBox(),c;c=this.block_.RTL?-b.x:b.width+b.x;b=b.height+3*a;if(this.workspace_.flyout_)var d=this.workspace_.flyout_.getMetrics_(),b=Math.max(b,d.contentHeight+20);c+=3*a;if(Math.abs(this.workspaceWidth_-c)>a||Math.abs(this.workspaceHeight_-b)>a)this.workspaceWidth_=c,this.workspaceHeight_=b,this.bubble_.setBubbleSize(c+a,b+a),this.svgDialog_.setAttribute("width",this.workspaceWidth_),
+this.svgDialog_.setAttribute("height",this.workspaceHeight_);this.block_.RTL&&(a="translate("+this.workspaceWidth_+",0)",this.workspace_.getCanvas().setAttribute("transform",a));this.workspace_.resize()};
+Blockly.Mutator.prototype.setVisible=function(a){if(a!=this.isVisible())if(Blockly.Events.fire(new Blockly.Events.Ui(this.block_,"mutatorOpen",!a,a)),a){this.bubble_=new Blockly.Bubble(this.block_.workspace,this.createEditor_(),this.block_.svgPath_,this.iconXY_,null,null);if(a=this.workspace_.options.languageTree)this.workspace_.flyout_.init(this.workspace_),this.workspace_.flyout_.show(a.childNodes);this.rootBlock_=this.block_.decompose(this.workspace_);a=this.rootBlock_.getDescendants();for(var b=
+0,c;c=a[b];b++)c.render();this.rootBlock_.setMovable(!1);this.rootBlock_.setDeletable(!1);this.workspace_.flyout_?(a=2*this.workspace_.flyout_.CORNER_RADIUS,b=this.workspace_.flyout_.width_+a):b=a=16;this.block_.RTL&&(b=-b);this.rootBlock_.moveBy(b,a);if(this.block_.saveConnections){var d=this;this.block_.saveConnections(this.rootBlock_);this.sourceListener_=function(){d.block_.saveConnections(d.rootBlock_)};this.block_.workspace.addChangeListener(this.sourceListener_)}this.resizeBubble_();this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));
+this.updateColour()}else this.svgDialog_=null,this.workspace_.dispose(),this.rootBlock_=this.workspace_=null,this.bubble_.dispose(),this.bubble_=null,this.workspaceHeight_=this.workspaceWidth_=0,this.sourceListener_&&(this.block_.workspace.removeChangeListener(this.sourceListener_),this.sourceListener_=null)};
+Blockly.Mutator.prototype.workspaceChanged_=function(){if(Blockly.dragMode_==Blockly.DRAG_NONE)for(var a=this.workspace_.getTopBlocks(!1),b=0,c;c=a[b];b++){var d=c.getRelativeToSurfaceXY(),e=c.getHeightWidth();20>d.y+e.height&&c.moveBy(0,20-e.height-d.y)}if(this.rootBlock_.workspace==this.workspace_){Blockly.Events.setGroup(!0);c=this.block_;a=(a=c.mutationToDom())&&Blockly.Xml.domToText(a);b=c.rendered;c.rendered=!1;c.compose(this.rootBlock_);c.rendered=b;c.initSvg();b=(b=c.mutationToDom())&&Blockly.Xml.domToText(b);
+if(a!=b){Blockly.Events.fire(new Blockly.Events.Change(c,"mutation",null,a,b));var f=Blockly.Events.getGroup();setTimeout(function(){Blockly.Events.setGroup(f);c.bumpNeighbours_();Blockly.Events.setGroup(!1)},Blockly.BUMP_DELAY)}c.rendered&&c.render();this.resizeBubble_();Blockly.Events.setGroup(!1)}};Blockly.Mutator.prototype.getFlyoutMetrics_=function(){return{viewHeight:this.workspaceHeight_,viewWidth:this.workspaceWidth_,absoluteTop:0,absoluteLeft:0}};
+Blockly.Mutator.prototype.dispose=function(){this.block_.mutator=null;Blockly.Icon.prototype.dispose.call(this)};Blockly.Mutator.reconnect=function(a,b,c){if(!a||!a.getSourceBlock().workspace)return!1;c=b.getInput(c).connection;var d=a.targetBlock();return d&&d!=b||c.targetConnection==a?!1:(c.isConnected()&&c.disconnect(),c.connect(a),!0)};goog.global.Blockly||(goog.global.Blockly={});goog.global.Blockly.Mutator||(goog.global.Blockly.Mutator={});goog.global.Blockly.Mutator.reconnect=Blockly.Mutator.reconnect;Blockly.MutatorPlus=function(a){Blockly.MutatorPlus.superClass_.constructor.call(this,this,null)};goog.inherits(Blockly.MutatorPlus,Blockly.Mutator,Blockly.Icon);Blockly.MutatorPlus.prototype.clicked_=!1;
+Blockly.MutatorPlus.prototype.createIcon=function(){if(!this.iconMark_){Blockly.Icon.prototype.createIconOld.call(this);Blockly.Icon.radius=8;var a=Blockly.Icon.radius/2;Blockly.createSvgElement("rect",{"class":"blocklyIconShield",width:4*a,height:4*a,rx:a,ry:a},this.iconGroup_);this.iconMark_=Blockly.createSvgElement("text",{"class":"blocklyIconMark",x:Blockly.Icon.radius,y:2*Blockly.Icon.radius-4},this.iconGroup_);this.iconMark_.appendChild(document.createTextNode("+"))}};
+Blockly.MutatorPlus.prototype.iconClick_=function(a){2!=Blockly.dragMode_&&(this.block_.isEditable()&&this.block_.updateShape_(1),goog.Timer.callOnce(this.block_.bumpNeighbours_,Blockly.BUMP_DELAY,this.block_))};Blockly.MutatorMinus=function(a){Blockly.MutatorMinus.superClass_.constructor.call(this,this,null)};goog.inherits(Blockly.MutatorMinus,Blockly.Mutator,Blockly.Icon);Blockly.MutatorMinus.prototype.clicked_=!1;
+Blockly.MutatorMinus.prototype.createIcon=function(){if(!this.iconMark_){Blockly.Icon.prototype.createIconOld.call(this);Blockly.Icon.radius=8;var a=Blockly.Icon.radius/2;Blockly.createSvgElement("rect",{"class":"blocklyIconShield",width:4*a,height:4*a,rx:a,ry:a},this.iconGroup_);this.iconMark_=Blockly.createSvgElement("text",{"class":"blocklyIconMark",x:Blockly.Icon.radius,y:2*Blockly.Icon.radius-4},this.iconGroup_);this.iconMark_.appendChild(document.createTextNode("\u2212"))}};
+Blockly.MutatorMinus.prototype.iconClick_=function(a){2!=Blockly.dragMode_&&this.block_.isEditable()&&this.block_.updateShape_(-1)};Blockly.Warning=function(a){Blockly.Warning.superClass_.constructor.call(this,a);this.createIcon();this.text_={}};goog.inherits(Blockly.Warning,Blockly.Icon);Blockly.Warning.prototype.collapseHidden=!1;
+Blockly.Warning.prototype.drawIcon_=function(a){Blockly.createSvgElement("path",{"class":"blocklyIconShape",d:"M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z"},a);Blockly.createSvgElement("path",{"class":"blocklyIconSymbol",d:"m7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z"},a);Blockly.createSvgElement("rect",{"class":"blocklyIconSymbol",x:"7",y:"11",height:"2",width:"2"},a)};
+Blockly.Warning.textToDom_=function(a){var b=Blockly.createSvgElement("text",{"class":"blocklyText blocklyBubbleText",y:Blockly.Bubble.BORDER_WIDTH},null);a=a.split("\n");for(var c=0;c<a.length;c++){var d=Blockly.createSvgElement("tspan",{dy:"1em",x:Blockly.Bubble.BORDER_WIDTH},b),e=document.createTextNode(a[c]);d.appendChild(e)}return b};
+Blockly.Warning.prototype.setVisible=function(a){if(a!=this.isVisible())if(Blockly.Events.fire(new Blockly.Events.Ui(this.block_,"warningOpen",!a,a)),a){a=Blockly.Warning.textToDom_(this.getText());this.bubble_=new Blockly.Bubble(this.block_.workspace,a,this.block_.svgPath_,this.iconXY_,null,null);if(this.block_.RTL)for(var b=a.getBBox().width,c=0,d;d=a.childNodes[c];c++)d.setAttribute("text-anchor","end"),d.setAttribute("x",b+Blockly.Bubble.BORDER_WIDTH);this.updateColour();a=this.bubble_.getBubbleSize();
+this.bubble_.setBubbleSize(a.width,a.height)}else this.bubble_.dispose(),this.body_=this.bubble_=null};Blockly.Warning.prototype.bodyFocus_=function(a){this.bubble_.promote_()};Blockly.Warning.prototype.setText=function(a,b){this.text_[b]!=a&&(a?this.text_[b]=a:delete this.text_[b],this.isVisible()&&(this.setVisible(!1),this.setVisible(!0)))};Blockly.Warning.prototype.getText=function(){var a=[],b;for(b in this.text_)a.push(this.text_[b]);return a.join("\n")};
+Blockly.Warning.prototype.dispose=function(){this.block_.warning=null;Blockly.Icon.prototype.dispose.call(this)};Blockly.Block=function(a,b,c){this.id=c&&!a.getBlockById(c)?c:Blockly.genUid();a.blockDB_[this.id]=this;this.previousConnection=this.nextConnection=this.outputConnection=null;this.inputList=[];this.inputsInline=void 0;this.disabled=!1;this.tooltip="";this.contextMenu=!0;this.parentBlock_=null;this.childBlocks_=[];this.editable_=this.movable_=this.deletable_=!0;this.collapsed_=this.isShadow_=!1;this.comment=null;this.xy_=new goog.math.Coordinate(0,0);this.workspace=a;this.isInFlyout=a.isFlyout;this.RTL=
+a.RTL;b&&(this.type=b,c=Blockly.Blocks[b],goog.asserts.assertObject(c,'Error: "%s" is an unknown language block.',b),goog.mixin(this,c));a.addTopBlock(this);goog.isFunction(this.init)&&this.init();this.inputsInlineDefault=this.inputsInline;Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Create(this));goog.isFunction(this.onchange)&&(this.onchangeWrapper_=this.onchange.bind(this),this.workspace.addChangeListener(this.onchangeWrapper_))};
+Blockly.Block.obtain=function(a,b){console.warn("Deprecated call to Blockly.Block.obtain, use workspace.newBlock instead.");return a.newBlock(b)};Blockly.Block.prototype.data=null;Blockly.Block.prototype.colour_="#000000";
+Blockly.Block.prototype.dispose=function(a){this.onchangeWrapper_&&this.workspace.removeChangeListener(this.onchangeWrapper_);this.unplug(a);Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Delete(this));Blockly.Events.disable();try{this.workspace&&(this.workspace.removeTopBlock(this),delete this.workspace.blockDB_[this.id],this.workspace=null);this.unbacklight();for(var b=this.childBlocks_.length-1;0<=b;b--)this.childBlocks_[b].dispose(!1);for(var b=0,c;c=this.inputList[b];b++)c.dispose();
+this.inputList.length=0;for(var d=this.getConnections_(!0),b=0;b<d.length;b++){var e=d[b];e.isConnected()&&e.disconnect();d[b].dispose()}}finally{Blockly.Events.enable()}};
+Blockly.Block.prototype.unplug=function(a){if(this.outputConnection)this.outputConnection.isConnected()&&this.outputConnection.disconnect();else if(this.previousConnection){var b=null;this.previousConnection.isConnected()&&(b=this.previousConnection.targetConnection,this.previousConnection.disconnect());var c=this.getNextBlock();a&&c&&(a=this.nextConnection.targetConnection,a.disconnect(),b&&b.checkType_(a)&&b.connect(a))}};
+Blockly.Block.prototype.getConnections_=function(){var a=[];this.outputConnection&&a.push(this.outputConnection);this.previousConnection&&a.push(this.previousConnection);this.nextConnection&&a.push(this.nextConnection);for(var b=0,c;c=this.inputList[b];b++)c.connection&&a.push(c.connection);return a};Blockly.Block.prototype.lastConnectionInStack_=function(){for(var a=this.nextConnection;a;){var b=a.targetBlock();if(!b)return a;a=b.nextConnection}return null};
+Blockly.Block.prototype.bumpNeighbours_=function(){if(this.workspace&&Blockly.dragMode_==Blockly.DRAG_NONE){var a=this.getRootBlock();if(!a.isInFlyout)for(var b=this.getConnections_(!1),c=0,d;d=b[c];c++){d.isConnected()&&d.isSuperior()&&d.targetBlock().bumpNeighbours_();for(var e=d.neighbours_(Blockly.SNAP_RADIUS),f=0,g;g=e[f];f++)d.isConnected()&&g.isConnected()||g.getSourceBlock().getRootBlock()!=a&&(d.isSuperior()?g.bumpAwayFrom_(d):d.bumpAwayFrom_(g))}}};Blockly.Block.prototype.getParent=function(){return this.parentBlock_};
+Blockly.Block.prototype.getInputWithBlock=function(a){for(var b=0,c;c=this.inputList[b];b++)if(c.connection&&c.connection.targetBlock()==a)return c;return null};Blockly.Block.prototype.getSurroundParent=function(){var a=this;do{var b=a,a=a.getParent();if(!a)return null}while(a.getNextBlock()==b);return a};Blockly.Block.prototype.getNextBlock=function(){return this.nextConnection&&this.nextConnection.targetBlock()};
+Blockly.Block.prototype.getRootBlock=function(){var a,b=this;do a=b,b=a.parentBlock_;while(b);return a};Blockly.Block.prototype.getChildren=function(){return this.childBlocks_};
+Blockly.Block.prototype.setParent=function(a){if(a!=this.parentBlock_){if(this.parentBlock_){for(var b=this.parentBlock_.childBlocks_,c,d=0;c=b[d];d++)if(c==this){b.splice(d,1);break}if(this.previousConnection&&this.previousConnection.isConnected())throw"Still connected to previous block.";if(this.outputConnection&&this.outputConnection.isConnected())throw"Still connected to parent block.";this.parentBlock_=null}else this.workspace.removeTopBlock(this);(this.parentBlock_=a)?a.childBlocks_.push(this):
+this.workspace.addTopBlock(this)}};Blockly.Block.prototype.getDescendants=function(){for(var a=[this],b,c=0;b=this.childBlocks_[c];c++)a.push.apply(a,b.getDescendants());return a};Blockly.Block.prototype.isDeletable=function(){return this.deletable_&&!this.isShadow_&&!(this.workspace&&this.workspace.options.readOnly)};Blockly.Block.prototype.setDeletable=function(a){this.deletable_=a};Blockly.Block.prototype.isMovable=function(){return this.movable_&&!this.isShadow_&&!(this.workspace&&this.workspace.options.readOnly)};
+Blockly.Block.prototype.setMovable=function(a){this.movable_=a};Blockly.Block.prototype.isShadow=function(){return this.isShadow_};Blockly.Block.prototype.setShadow=function(a){this.isShadow_=a};Blockly.Block.prototype.isEditable=function(){return this.editable_&&!(this.workspace&&this.workspace.options.readOnly)};Blockly.Block.prototype.setEditable=function(a){this.editable_=a;a=0;for(var b;b=this.inputList[a];a++)for(var c=0,d;d=b.fieldRow[c];c++)d.updateEditable()};
+Blockly.Block.prototype.setConnectionsHidden=function(a){if(!a&&this.isCollapsed()){if(this.outputConnection&&this.outputConnection.setHidden(a),this.previousConnection&&this.previousConnection.setHidden(a),this.nextConnection){this.nextConnection.setHidden(a);var b=this.nextConnection.targetBlock();b&&b.setConnectionsHidden(a)}}else for(var c=this.getConnections_(!0),d=0;b=c[d];d++)b.setHidden(a),b.isSuperior()&&(b=b.targetBlock())&&b.setConnectionsHidden(a)};
+Blockly.Block.prototype.setHelpUrl=function(a){this.helpUrl=a};Blockly.Block.prototype.setTooltip=function(a){this.tooltip=a};Blockly.Block.prototype.getColour=function(){return this.colour_};Blockly.Block.prototype.setColour=function(a){var b=parseFloat(a);if(isNaN(b))if(goog.isString(a)&&a.match(/^#[0-9a-fA-F]{6}$/))this.colour_=a;else throw"Invalid colour: "+a;else this.colour_=Blockly.hueToRgb(b)};
+Blockly.Block.prototype.getField=function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(e.name===a)return e;return null};Blockly.Block.prototype.getAllFieldValues=function(){for(var a="",b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)void 0!=e.name&&(a+=e.getValue(),a+=":");return a};
+Blockly.Block.prototype.getVars=function(){for(var a=[],b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)e instanceof Blockly.FieldVariable&&a.push(e.getValue());return a};Blockly.Block.prototype.renameVar=function(a,b){for(var c=0,d;d=this.inputList[c];c++)for(var e=0,f;f=d.fieldRow[e];e++)f instanceof Blockly.FieldVariable&&Blockly.Names.equals(a,f.getValue())&&f.setValue(b)};Blockly.Block.prototype.getFieldValue=function(a){return(a=this.getField(a))?a.getValue():null};
+Blockly.Block.prototype.getTitleValue=function(a){console.warn("Deprecated call to getTitleValue, use getFieldValue instead.");return this.getFieldValue(a)};Blockly.Block.prototype.setFieldValue=function(a,b){var c=this.getField(b);goog.asserts.assertObject(c,'Field "%s" not found.',b);c.setValue(a)};Blockly.Block.prototype.setTitleValue=function(a,b){console.warn("Deprecated call to setTitleValue, use setFieldValue instead.");this.setFieldValue(a,b)};
+Blockly.Block.prototype.setPreviousStatement=function(a,b){a?(void 0===b&&(b=null),this.previousConnection||(goog.asserts.assert(!this.outputConnection,"Remove output connection prior to adding previous connection."),this.previousConnection=this.makeConnection_(Blockly.PREVIOUS_STATEMENT)),this.previousConnection.setCheck(b)):this.previousConnection&&(goog.asserts.assert(!this.previousConnection.isConnected(),"Must disconnect previous statement before removing connection."),this.previousConnection.dispose(),
+this.previousConnection=null)};Blockly.Block.prototype.setNextStatement=function(a,b){a?(void 0===b&&(b=null),this.nextConnection||(this.nextConnection=this.makeConnection_(Blockly.NEXT_STATEMENT)),this.nextConnection.setCheck(b)):this.nextConnection&&(goog.asserts.assert(!this.nextConnection.isConnected(),"Must disconnect next statement before removing connection."),this.nextConnection.dispose(),this.nextConnection=null)};
+Blockly.Block.prototype.setOutput=function(a,b){a?(void 0===b&&(b=null),this.outputConnection||(goog.asserts.assert(!this.previousConnection,"Remove previous connection prior to adding output connection."),this.outputConnection=this.makeConnection_(Blockly.OUTPUT_VALUE)),this.outputConnection.setCheck(b)):this.outputConnection&&(goog.asserts.assert(!this.outputConnection.isConnected(),"Must disconnect output value before removing connection."),this.outputConnection.dispose(),this.outputConnection=
+null)};Blockly.Block.prototype.setInputsInline=function(a){this.inputsInline!=a&&(Blockly.Events.fire(new Blockly.Events.Change(this,"inline",null,this.inputsInline,a)),this.inputsInline=a)};
+Blockly.Block.prototype.getInputsInline=function(){if(void 0!=this.inputsInline)return this.inputsInline;for(var a=1;a<this.inputList.length;a++)if(this.inputList[a-1].type==Blockly.DUMMY_INPUT&&this.inputList[a].type==Blockly.DUMMY_INPUT)return!1;for(a=1;a<this.inputList.length;a++)if(this.inputList[a-1].type==Blockly.INPUT_VALUE&&this.inputList[a].type==Blockly.DUMMY_INPUT)return!0;return!1};
+Blockly.Block.prototype.setDisabled=function(a){this.disabled!=a&&(Blockly.Events.fire(new Blockly.Events.Change(this,"disabled",null,this.disabled,a)),this.disabled=a)};Blockly.Block.prototype.getInheritedDisabled=function(){for(var a=this;;){a=a.getSurroundParent();if(!a)return!1;if(a.disabled)return!0}};Blockly.Block.prototype.isCollapsed=function(){return this.collapsed_};
+Blockly.Block.prototype.setCollapsed=function(a){this.collapsed_!=a&&(Blockly.Events.fire(new Blockly.Events.Change(this,"collapsed",null,this.collapsed_,a)),this.collapsed_=a)};
+Blockly.Block.prototype.toString=function(a){var b=[];if(this.collapsed_)b.push(this.getInput("_TEMP_COLLAPSED_INPUT").fieldRow[0].text_);else for(var c=0,d;d=this.inputList[c];c++){for(var e=0,f;f=d.fieldRow[e];e++)b.push(f.getText());d.connection&&((d=d.connection.targetBlock())?b.push(d.toString()):b.push("?"))}b=goog.string.trim(b.join(" "))||"???";a&&(b=goog.string.truncate(b,a));return b};Blockly.Block.prototype.appendValueInput=function(a){return this.appendInput_(Blockly.INPUT_VALUE,a)};
+Blockly.Block.prototype.appendStatementInput=function(a){return this.appendInput_(Blockly.NEXT_STATEMENT,a)};Blockly.Block.prototype.appendDummyInput=function(a){return this.appendInput_(Blockly.DUMMY_INPUT,a||"")};
+Blockly.Block.prototype.jsonInit=function(a){goog.asserts.assert(void 0==a.output||void 0==a.previousStatement,"Must not have both an output and a previousStatement.");void 0!==a.colour&&this.setColour(a.colour);for(var b=0;void 0!==a["message"+b];)this.interpolate_(a["message"+b],a["args"+b]||[],a["lastDummyAlign"+b]),b++;void 0!==a.inputsInline&&this.setInputsInline(a.inputsInline);void 0!==a.output&&this.setOutput(!0,a.output);void 0!==a.previousStatement&&this.setPreviousStatement(!0,a.previousStatement);
+void 0!==a.nextStatement&&this.setNextStatement(!0,a.nextStatement);void 0!==a.tooltip&&this.setTooltip(a.tooltip);void 0!==a.helpUrl&&this.setHelpUrl(a.helpUrl)};
+Blockly.Block.prototype.interpolate_=function(a,b,c){var d=Blockly.utils.tokenizeInterpolation(a),e=[],f=0;a=[];for(var g=0;g<d.length;g++){var h=d[g];"number"==typeof h?(goog.asserts.assert(0<h&&h<=b.length,'Message index "%s" out of range.',h),goog.asserts.assert(!e[h],'Message index "%s" duplicated.',h),e[h]=!0,f++,a.push(b[h-1])):(h=h.trim())&&a.push(h)}goog.asserts.assert(f==b.length,"Message does not reference all %s arg(s).",b.length);!a.length||"string"!=typeof a[a.length-1]&&0!=a[a.length-
+1].type.indexOf("field_")||(g={type:"input_dummy"},c&&(g.align=c),a.push(g));c={LEFT:Blockly.ALIGN_LEFT,RIGHT:Blockly.ALIGN_RIGHT,CENTRE:Blockly.ALIGN_CENTRE};b=[];for(g=0;g<a.length;g++)if(e=a[g],"string"==typeof e)b.push([e,void 0]);else{d=f=null;do if(h=!1,"string"==typeof e)f=new Blockly.FieldLabel(e);else switch(e.type){case "input_value":d=this.appendValueInput(e.name);break;case "input_statement":d=this.appendStatementInput(e.name);break;case "input_dummy":d=this.appendDummyInput(e.name);break;
+case "field_label":f=new Blockly.FieldLabel(e.text,e["class"]);break;case "field_input":f=new Blockly.FieldTextInput(e.text);"boolean"==typeof e.spellcheck&&f.setSpellcheck(e.spellcheck);break;case "field_angle":f=new Blockly.FieldAngle(e.angle);break;case "field_checkbox":f=new Blockly.FieldCheckbox(e.checked?"TRUE":"FALSE");break;case "field_colour":f=new Blockly.FieldColour(e.colour);break;case "field_variable":f=new Blockly.FieldVariable(e.variable);break;case "field_dropdown":f=new Blockly.FieldDropdown(e.options);
+break;case "field_image":f=new Blockly.FieldImage(e.src,e.width,e.height,e.alt);break;case "field_number":f=new Blockly.FieldNumber(e.value,e.min,e.max,e.precision);break;case "field_date":if(Blockly.FieldDate){f=new Blockly.FieldDate(e.date);break}default:e.alt&&(e=e.alt,h=!0)}while(h);if(f)b.push([f,e.name]);else if(d){e.check&&d.setCheck(e.check);e.align&&d.setAlign(c[e.align]);for(e=0;e<b.length;e++)d.appendField(b[e][0],b[e][1]);b.length=0}}};
+Blockly.Block.prototype.appendInput_=function(a,b){var c=null;if(a==Blockly.INPUT_VALUE||a==Blockly.NEXT_STATEMENT)c=this.makeConnection_(a);c=new Blockly.Input(a,b,this,c);this.inputList.push(c);return c};
+Blockly.Block.prototype.moveInputBefore=function(a,b){if(a!=b){for(var c=-1,d=b?-1:this.inputList.length,e=0,f;f=this.inputList[e];e++)if(f.name==a){if(c=e,-1!=d)break}else if(b&&f.name==b&&(d=e,-1!=c))break;goog.asserts.assert(-1!=c,'Named input "%s" not found.',a);goog.asserts.assert(-1!=d,'Reference input "%s" not found.',b);this.moveNumberedInputBefore(c,d)}};
+Blockly.Block.prototype.moveNumberedInputBefore=function(a,b){goog.asserts.assert(a!=b,"Can't move input to itself.");goog.asserts.assert(a<this.inputList.length,"Input index "+a+" out of bounds.");goog.asserts.assert(b<=this.inputList.length,"Reference input "+b+" out of bounds.");var c=this.inputList[a];this.inputList.splice(a,1);a<b&&b--;this.inputList.splice(b,0,c)};
+Blockly.Block.prototype.removeInput=function(a,b){for(var c=0,d;d=this.inputList[c];c++)if(d.name==a){if(d.connection&&d.connection.isConnected()){d.connection.setShadowDom(null);var e=d.connection.targetBlock();e.isShadow()?e.dispose():e.unplug()}d.dispose();this.inputList.splice(c,1);return}b||goog.asserts.fail('Input "%s" not found.',a)};Blockly.Block.prototype.getInput=function(a){for(var b=0,c;c=this.inputList[b];b++)if(c.name==a)return c;return null};
+Blockly.Block.prototype.getInputTargetBlock=function(a){return(a=this.getInput(a))&&a.connection&&a.connection.targetBlock()};Blockly.Block.prototype.getCommentText=function(){return this.comment||""};Blockly.Block.prototype.setCommentText=function(a){this.comment!=a&&(Blockly.Events.fire(new Blockly.Events.Change(this,"comment",null,this.comment,a||"")),this.comment=a)};Blockly.Block.prototype.setWarningText=function(a){};Blockly.Block.prototype.setMutator=function(a){};
+Blockly.Block.prototype.setMutatorPlus=function(a){};Blockly.Block.prototype.setMutatorMinus=function(a){};Blockly.Block.prototype.getRelativeToSurfaceXY=function(){return this.xy_};Blockly.Block.prototype.moveBy=function(a,b){goog.asserts.assert(!this.parentBlock_,"Block has parent.");var c=new Blockly.Events.Move(this);this.xy_.translate(a,b);c.recordNew();Blockly.Events.fire(c)};Blockly.Block.prototype.makeConnection_=function(a){return new Blockly.Connection(this,a)};
+Blockly.Block.prototype.collapsedParents=function(){var a=this,b=[];do a.isCollapsed()&&b.push(a),a=a.parentBlock_;while(a);return 0==b.length?!1:b};Blockly.Block.prototype.hasDisabledParent=function(){var a=this;do{if(a.disabled)return!0;a=a.parentBlock_}while(a);return!1};
+Blockly.Block.prototype.interpolateMsg=function(a,b){function c(a){a instanceof Blockly.Field?this.appendField(a):(goog.asserts.assert(goog.isArray(a)),this.appendField(a[1],a[0]))}goog.asserts.assertString(a);var d=arguments[arguments.length-1];goog.asserts.assert(d===Blockly.ALIGN_LEFT||d===Blockly.ALIGN_CENTRE||d===Blockly.ALIGN_RIGHT,'Illegal final argument "%d" is not an alignment.',d);--arguments.length;for(var e=a.split(this.interpolateMsg.SPLIT_REGEX_),f=[],g=0;g<e.length;g+=2){var h=goog.string.trim(e[g]),
+k=void 0;h&&f.push(new Blockly.FieldLabel(h));if((h=e[g+1])&&"%"==h.charAt(0)){var l=parseInt(h.substring(1),10),p=arguments[l];goog.asserts.assertArray(p,'Message symbol "%s" is out of range.',h);goog.asserts.assertArray(p,'Argument "%s" is not a tuple.',h);p[1]instanceof Blockly.Field?f.push([p[0],p[1]]):k=this.appendValueInput(p[0]).setCheck(p[1]).setAlign(p[2]);arguments[l]=null}else"\n"==h&&f.length&&(k=this.appendDummyInput());k&&f.length&&(f.forEach(c,k),f=[])}f.length&&(k=this.appendDummyInput().setAlign(d),
+f.forEach(c,k));for(g=1;g<arguments.length-1;g++)goog.asserts.assert(null===arguments[g],'Input "%%s" not used in message: "%s"',g,a);this.setInputsInline(!a.match(this.interpolateMsg.INLINE_REGEX_))};Blockly.Block.prototype.interpolateMsg.SPLIT_REGEX_=/(%\d+|\n)/;Blockly.Block.prototype.interpolateMsg.INLINE_REGEX_=/%1\s*$/;Blockly.ContextMenu={};Blockly.ContextMenu.currentBlock=null;
+Blockly.ContextMenu.show=function(a,b,c){Blockly.WidgetDiv.show(Blockly.ContextMenu,c,null);if(b.length){var d=new goog.ui.Menu;d.setRightToLeft(c);for(var e=0,f;f=b[e];e++){var g=new goog.ui.MenuItem(f.text);g.setRightToLeft(c);d.addChild(g,!0);g.setEnabled(f.enabled);f.enabled&&goog.events.listen(g,goog.ui.Component.EventType.ACTION,f.callback)}goog.events.listen(d,goog.ui.Component.EventType.ACTION,Blockly.ContextMenu.hide);b=goog.dom.getViewportSize();e=goog.style.getViewportPageOffset(document);
+d.render(Blockly.WidgetDiv.DIV);var h=d.getElement();Blockly.addClass_(h,"blocklyContextMenu");Blockly.bindEvent_(h,"contextmenu",null,Blockly.noEvent);f=goog.style.getSize(h);var g=a.clientX+e.x,k=a.clientY+e.y;a.clientY+f.height>=b.height&&(k-=f.height);c?f.width>=a.clientX&&(g+=f.width):a.clientX+f.width>=b.width&&(g-=f.width);Blockly.WidgetDiv.position(g,k,b,e,c);d.setAllowAutoFocus(!0);setTimeout(function(){h.focus()},1);Blockly.ContextMenu.currentBlock=null}else Blockly.ContextMenu.hide()};
+Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null};
+Blockly.ContextMenu.callbackFactory=function(a,b){return function(){Blockly.Events.disable();try{var c=Blockly.Xml.domToBlock(b,a.workspace),d=a.getRelativeToSurfaceXY();d.x=a.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y)}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&!c.isShadow()&&Blockly.Events.fire(new Blockly.Events.Create(c));c.select()}};Blockly.RenderedConnection=function(a,b){Blockly.RenderedConnection.superClass_.constructor.call(this,a,b);this.offsetInBlock_=new goog.math.Coordinate(0,0)};goog.inherits(Blockly.RenderedConnection,Blockly.Connection);Blockly.RenderedConnection.prototype.distanceFrom=function(a){var b=this.x_-a.x_;a=this.y_-a.y_;return Math.sqrt(b*b+a*a)};
+Blockly.RenderedConnection.prototype.bumpAwayFrom_=function(a){if(Blockly.dragMode_==Blockly.DRAG_NONE){var b=this.sourceBlock_.getRootBlock();if(!b.isInFlyout){var c=!1;if(!b.isMovable()){b=a.getSourceBlock().getRootBlock();if(!b.isMovable())return;a=this;c=!0}var d=Blockly.selected==b;d||b.select();var e=a.x_+Blockly.SNAP_RADIUS-this.x_;a=a.y_+Blockly.SNAP_RADIUS-this.y_;c&&(a=-a);b.RTL&&(e=-e);b.moveBy(e,a);d||b.unselect()}}};
+Blockly.RenderedConnection.prototype.moveTo=function(a,b){this.inDB_&&this.db_.removeConnection_(this);this.x_=a;this.y_=b;this.hidden_||this.db_.addConnection(this)};Blockly.RenderedConnection.prototype.moveBy=function(a,b){this.moveTo(this.x_+a,this.y_+b)};Blockly.RenderedConnection.prototype.moveToOffset=function(a){this.moveTo(a.x+this.offsetInBlock_.x,a.y+this.offsetInBlock_.y)};
+Blockly.RenderedConnection.prototype.setOffsetInBlock=function(a,b){this.offsetInBlock_.x=a;this.offsetInBlock_.y=b};Blockly.RenderedConnection.prototype.tighten_=function(){var a=this.targetConnection.x_-this.x_,b=this.targetConnection.y_-this.y_;if(0!=a||0!=b){var c=this.targetBlock(),d=c.getSvgRoot();if(!d)throw"block is not rendered.";d=Blockly.getRelativeXY_(d);c.getSvgRoot().setAttribute("transform","translate("+(d.x-a)+","+(d.y-b)+")");c.moveConnections_(-a,-b)}};
+Blockly.RenderedConnection.prototype.closest=function(a,b,c){return this.dbOpposite_.searchForClosest(this,a,b,c)};
+Blockly.RenderedConnection.prototype.highlight=function(){var a;a=this.type==Blockly.INPUT_VALUE||this.type==Blockly.OUTPUT_VALUE?"m 0,0 "+Blockly.BlockSvg.TAB_PATH_DOWN+" v 5":"m -20,0 h 5 "+Blockly.BlockSvg.NOTCH_PATH_LEFT+" h 5";var b=this.sourceBlock_.getRelativeToSurfaceXY();Blockly.Connection.highlightedPath_=Blockly.createSvgElement("path",{"class":"blocklyHighlightedConnectionPath",d:a,transform:"translate("+(this.x_-b.x)+","+(this.y_-b.y)+")"+(this.sourceBlock_.RTL?" scale(-1 1)":"")},this.sourceBlock_.getSvgRoot())};
+Blockly.Connection.prototype.highlightBad=function(){var a;a=this.type==Blockly.INPUT_VALUE||this.type==Blockly.OUTPUT_VALUE?"m -10,5 l 15,15 m -15,0 l 15,-15":"m -15,-5 l 15,15 m -15,0 l 15,-15 ";var b=this.sourceBlock_.getRelativeToSurfaceXY();Blockly.Connection.highlightedPathBad_=Blockly.createSvgElement("path",{"class":"blocklyHighlightedConnectionPathBad",d:a,transform:"translate("+(this.x_-b.x)+","+(this.y_-b.y)+")"+(this.sourceBlock_.RTL?" scale(-1 1)":"")},this.sourceBlock_.getSvgRoot())};
+Blockly.RenderedConnection.prototype.unhideAll=function(){this.setHidden(!1);var a=[];if(this.type!=Blockly.INPUT_VALUE&&this.type!=Blockly.NEXT_STATEMENT)return a;var b=this.targetBlock();if(b){var c;b.isCollapsed()?(c=[],b.outputConnection&&c.push(b.outputConnection),b.nextConnection&&c.push(b.nextConnection),b.previousConnection&&c.push(b.previousConnection)):c=b.getConnections_(!0);for(var d=0;d<c.length;d++)a.push.apply(a,c[d].unhideAll());a.length||(a[0]=b)}return a};
+Blockly.RenderedConnection.prototype.unhighlight=function(){Blockly.Connection.highlightedPath_&&(goog.dom.removeNode(Blockly.Connection.highlightedPath_),delete Blockly.Connection.highlightedPath_);Blockly.Connection.highlightedPathBad_&&(goog.dom.removeNode(Blockly.Connection.highlightedPathBad_),delete Blockly.Connection.highlightedPathBad_)};Blockly.RenderedConnection.prototype.setHidden=function(a){(this.hidden_=a)&&this.inDB_?this.db_.removeConnection_(this):a||this.inDB_||this.db_.addConnection(this)};
+Blockly.RenderedConnection.prototype.hideAll=function(){this.setHidden(!0);if(this.targetConnection)for(var a=this.targetBlock().getDescendants(),b=0;b<a.length;b++){for(var c=a[b],d=c.getConnections_(!0),e=0;e<d.length;e++)d[e].setHidden(!0);c=c.getIcons();for(e=0;e<c.length;e++)c[e].setVisible(!1)}};Blockly.RenderedConnection.prototype.isConnectionAllowed=function(a,b){return this.distanceFrom(a)>b?!1:Blockly.RenderedConnection.superClass_.isConnectionAllowed.call(this,a)};
+Blockly.RenderedConnection.prototype.disconnectInternal_=function(a,b){Blockly.RenderedConnection.superClass_.disconnectInternal_.call(this,a,b);a.rendered&&a.render();b.rendered&&(b.updateDisabled(),b.render())};
+Blockly.RenderedConnection.prototype.respawnShadow_=function(){var a=this.getSourceBlock(),b=this.getShadowDom();if(a.workspace&&b&&Blockly.Events.recordUndo){Blockly.RenderedConnection.superClass_.respawnShadow_.call(this);b=this.targetBlock();if(!b)throw"Couldn't respawn the shadow block that should exist here.";b.initSvg();b.render(!1);a.rendered&&a.render()}};Blockly.RenderedConnection.prototype.neighbours_=function(a){return this.dbOpposite_.getNeighbours(this,a)};
+Blockly.RenderedConnection.prototype.connect_=function(a){Blockly.RenderedConnection.superClass_.connect_.call(this,a);var b=this.getSourceBlock();a=a.getSourceBlock();b.rendered&&b.updateDisabled();a.rendered&&a.updateDisabled();b.rendered&&a.rendered&&(this.type==Blockly.NEXT_STATEMENT||this.type==Blockly.PREVIOUS_STATEMENT?a.render():b.render())};Blockly.BlockSvg=function(a,b,c){this.svgGroup_=Blockly.createSvgElement("g",{},null);this.svgPathDark_=Blockly.createSvgElement("path",{"class":"blocklyPathDark",transform:"translate(1,1)"},this.svgGroup_);this.svgPath_=Blockly.createSvgElement("path",{"class":"blocklyPath"},this.svgGroup_);this.svgPathLight_=Blockly.createSvgElement("path",{"class":"blocklyPathLight"},this.svgGroup_);this.svgPath_.tooltip=this;this.rendered=!1;Blockly.Tooltip.bindMouseEvents(this.svgPath_);Blockly.BlockSvg.superClass_.constructor.call(this,
+a,b,c)};goog.inherits(Blockly.BlockSvg,Blockly.Block);Blockly.BlockSvg.prototype.height=0;Blockly.BlockSvg.prototype.width=0;Blockly.BlockSvg.prototype.dragStartXY_=null;Blockly.BlockSvg.INLINE=-1;
+Blockly.BlockSvg.prototype.initSvg=function(){goog.asserts.assert(this.workspace.rendered,"Workspace is headless.");for(var a=0,b;b=this.inputList[a];a++)b.init();b=this.getIcons();for(a=0;a<b.length;a++)b[a].createIcon();this.mutatorPlus&&this.mutatorPlus.createIcon();this.mutatorMinus&&this.mutatorMinus.createIcon();this.updateColour();this.updateMovable();if(!this.workspace.options.readOnly&&!this.eventsInit_){Blockly.bindEvent_(this.getSvgRoot(),"mousedown",this,this.onMouseDown_);var c=this;
+Blockly.bindEvent_(this.getSvgRoot(),"touchstart",null,function(a){Blockly.longStart_(a,c)})}this.eventsInit_=!0;this.getSvgRoot().parentNode||this.workspace.getCanvas().appendChild(this.getSvgRoot())};
+Blockly.BlockSvg.prototype.select=function(){if(this.isShadow()&&this.getParent())this.getParent().select();else if(Blockly.selected!=this){var a=null;if(Blockly.selected){a=Blockly.selected.id;Blockly.Events.disable();try{Blockly.selected.unselect()}finally{Blockly.Events.enable()}}a=new Blockly.Events.Ui(null,"selected",a,this.id);a.workspaceId=this.workspace.id;Blockly.Events.fire(a);Blockly.selected=this;this.addSelect();this.unbacklight()}};
+Blockly.BlockSvg.prototype.unselect=function(){if(Blockly.selected==this){var a=new Blockly.Events.Ui(null,"selected",this.id,null);a.workspaceId=this.workspace.id;Blockly.Events.fire(a);Blockly.selected=null;this.removeSelect()}};Blockly.BlockSvg.prototype.backlight=function(){for(var a=0,b=Blockly.backlight.length;b--;)if(Blockly.backlight[b]==this.id){a=1;break}!a&&this&&Blockly.backlight.push(this.id);this.addBacklight()};
+Blockly.BlockSvg.prototype.unbacklight=function(){for(var a=0,b=Blockly.backlight.length;b--;)Blockly.backlight[b]==this.id&&(Blockly.backlight.splice(b,1),a=1);if(a&&this)if(this.removeBacklight(),"varibles_get"==this.type){for(var c=Blockly.Variables.getInstances(this.getFieldValue("VAR"),this.workspace),a=[],b=0;b<c.length;b++)"variables_set"==c[b].type&&a.push(c[b]);for(b=0;b<a.length;b++)for(c=0;c<a[b].backlightBlocks.length;c++)a[b].backlightBlocks[c]==this.id&&(a[b].backlightBlocks.splice(c,
+1),0==a[b].backlightBlocks.length&&a[b].setWarningText(null))}else if("procedures_callnoreturn"==this.type||"procedures_callreturn"==this.type)for(a=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace),b=0;b<a.backlightBlocks.length;b++)a.backlightBlocks[b]==this.id&&(a.backlightBlocks.splice(b,1),0==a.backlightBlocks.length&&a.setWarningText(null))};Blockly.BlockSvg.prototype.mutator=null;Blockly.BlockSvg.prototype.comment=null;Blockly.BlockSvg.prototype.warning=null;
+Blockly.BlockSvg.prototype.getIcons=function(){var a=[];this.mutator&&a.push(this.mutator);this.mutatorPlus&&a.push(this.mutatorPlus);this.mutatorMinus&&a.push(this.mutatorMinus);this.comment&&a.push(this.comment);this.warning&&a.push(this.warning);return a};Blockly.BlockSvg.onMouseUpWrapper_=null;Blockly.BlockSvg.onMouseMoveWrapper_=null;
+Blockly.BlockSvg.terminateDrag=function(){Blockly.BlockSvg.disconnectUiStop_();Blockly.BlockSvg.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_),Blockly.BlockSvg.onMouseUpWrapper_=null);Blockly.BlockSvg.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_),Blockly.BlockSvg.onMouseMoveWrapper_=null);var a=Blockly.selected;if(Blockly.dragMode_==Blockly.DRAG_FREE&&a){var b=a.getRelativeToSurfaceXY(),b=goog.math.Coordinate.difference(b,a.dragStartXY_),
+c=new Blockly.Events.Move(a);c.oldCoordinate=a.dragStartXY_;c.recordNew();Blockly.Events.fire(c);a.moveConnections_(b.x,b.y);delete a.draggedBubbles_;a.setDragging_(!1);a.render();var d=Blockly.Events.getGroup();setTimeout(function(){Blockly.Events.setGroup(d);a.snapToGrid();Blockly.Events.setGroup(!1)},Blockly.BUMP_DELAY/2);setTimeout(function(){Blockly.Events.setGroup(d);a.bumpNeighbours_();Blockly.Events.setGroup(!1)},Blockly.BUMP_DELAY);Blockly.resizeSvgContents(a.workspace)}Blockly.dragMode_=
+Blockly.DRAG_NONE;Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN)};
+Blockly.BlockSvg.prototype.setParent=function(a){if(a!=this.parentBlock_){var b=this.getSvgRoot();if(this.parentBlock_&&b){var c=this.getRelativeToSurfaceXY();this.workspace.getCanvas().appendChild(b);b.setAttribute("transform","translate("+c.x+","+c.y+")")}Blockly.Field.startCache();Blockly.BlockSvg.superClass_.setParent.call(this,a);Blockly.Field.stopCache();a&&(c=this.getRelativeToSurfaceXY(),a.getSvgRoot().appendChild(b),a=this.getRelativeToSurfaceXY(),this.moveConnections_(a.x-c.x,a.y-c.y))}};
+Blockly.BlockSvg.prototype.getRelativeToSurfaceXY=function(){var a=0,b=0,c=this.getSvgRoot();if(c){do var d=Blockly.getRelativeXY_(c),a=a+d.x,b=b+d.y,c=c.parentNode;while(c&&c!=this.workspace.getCanvas())}return new goog.math.Coordinate(a,b)};
+Blockly.BlockSvg.prototype.moveBy=function(a,b){goog.asserts.assert(!this.parentBlock_,"Block has parent.");var c=new Blockly.Events.Move(this),d=this.getRelativeToSurfaceXY();this.getSvgRoot().setAttribute("transform","translate("+(d.x+a)+","+(d.y+b)+")");this.moveConnections_(a,b);c.recordNew();Blockly.resizeSvgContents(this.workspace);Blockly.Events.fire(c)};
+Blockly.BlockSvg.prototype.snapToGrid=function(){if(this.workspace&&Blockly.dragMode_==Blockly.DRAG_NONE&&!this.getParent()&&!this.isInFlyout&&this.workspace.options.gridOptions&&this.workspace.options.gridOptions.snap){var a=this.workspace.options.gridOptions.spacing,b=a/2,c=this.getRelativeToSurfaceXY(),d=Math.round((c.x-b)/a)*a+b-c.x,a=Math.round((c.y-b)/a)*a+b-c.y,d=Math.round(d),a=Math.round(a);0==d&&0==a||this.moveBy(d,a)}};
+Blockly.BlockSvg.prototype.getHeightWidth=function(){var a=this.height,b=this.width,c=this.getNextBlock();c?(c=c.getHeightWidth(),a+=c.height-4,b=Math.max(b,c.width)):this.nextConnection||this.outputConnection||(a+=2);return{height:a,width:b}};
+Blockly.BlockSvg.prototype.getBoundingRectangle=function(){var a=this.getRelativeToSurfaceXY(this),b=this.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,c=this.getHeightWidth(),d;this.RTL?(d=new goog.math.Coordinate(a.x-(c.width-b),a.y),a=new goog.math.Coordinate(a.x+b,a.y+c.height)):(d=new goog.math.Coordinate(a.x-b,a.y),a=new goog.math.Coordinate(a.x+c.width-b,a.y+c.height));return{topLeft:d,bottomRight:a}};
+Blockly.BlockSvg.prototype.setCollapsed=function(a,b){if(this.collapsed_!=a||b){for(var c=[],d=0,e;e=this.inputList[d];d++)c.push.apply(c,e.setVisible(!a));if(a){d=this.getIcons();for(e=0;e<d.length;e++)d[e].setVisible(!1);d="";e=getTopComment(this);e.length>Blockly.COLLAPSE_CHARS-3?d=e=e.substring(0,Blockly.COLLAPSE_CHARS-3)+"...":(b||(d=e),8<Blockly.COLLAPSE_CHARS-e.length&&(0<e.length&&(b||(d+=" - ")),d+=this.toString(Blockly.COLLAPSE_CHARS-e.length)));b?(e=this.getInput("_TEMP_COLLAPSED_INPUT"),
+e.setVisible(!0),e.removeField("COLLAPSE_TEXT"),e.appendField(d,"COLLAPSE_TEXT").init()):this.appendDummyInput("_TEMP_COLLAPSED_INPUT").appendField(d,"COLLAPSE_TEXT").init()}else this.removeInput("_TEMP_COLLAPSED_INPUT"),this.setWarningText(null);Blockly.BlockSvg.superClass_.setCollapsed.call(this,a);c.length||(c[0]=this);if(this.rendered)for(d=0;e=c[d];d++)e.render();a||Blockscad.assignBlockTypes([this])}};
+function getTopComment(a){if(a.category&&"PROCEDURE"!=a.category){for(var b=a.getDescendants(),c=0;c<b.length;c++)if(a=b[c].getCommentText(),0<a.length)return a;for(c=1;c<b.length;c++)if(-1!=b[c].type.lastIndexOf("procedures_call"))return a=b[c].inputList[0].fieldRow[0].getText()}return""}
+Blockly.BlockSvg.prototype.tab=function(a,b){for(var c=[],d=0,e;e=this.inputList[d];d++){for(var f=0,g;g=e.fieldRow[f];f++)g instanceof Blockly.FieldTextInput&&c.push(g);e.connection&&(e=e.connection.targetBlock())&&c.push(e)}d=c.indexOf(a);-1==d&&(d=b?-1:c.length);(c=c[b?d+1:d-1])?c instanceof Blockly.Field?c.showEditor_():c.tab(null,b):(c=this.getParent())&&c.tab(this,b)};
+Blockly.BlockSvg.prototype.onMouseDown_=function(a){if(!this.workspace.options.readOnly&&!this.isInFlyout){this.workspace.markFocused();Blockly.terminateDrag_();this.select();Blockly.hideChaff();if(Blockly.isRightButton(a))this.showContextMenu_(a);else if(this.isMovable()){Blockly.Events.getGroup()||Blockly.Events.setGroup(!0);Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);this.dragStartXY_=this.getRelativeToSurfaceXY();this.workspace.startDrag(a,this.dragStartXY_);Blockly.dragMode_=Blockly.DRAG_STICKY;
+Blockly.BlockSvg.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,this.onMouseUp_);Blockly.BlockSvg.onMouseMoveWrapper_=Blockly.bindEvent_(document,"mousemove",this,this.onMouseMove_);this.draggedBubbles_=[];for(var b=this.getDescendants(),c=0,d;d=b[c];c++){d=d.getIcons();for(var e=0;e<d.length;e++){var f=d[e].getIconLocation();f.bubble=d[e];this.draggedBubbles_.push(f)}}}else return;a.stopPropagation();a.preventDefault()}};
+Blockly.BlockSvg.prototype.onMouseUp_=function(a){Blockly.dragMode_==Blockly.DRAG_FREE||Blockly.WidgetDiv.isVisible()||Blockly.Events.fire(new Blockly.Events.Ui(this,"click",void 0,void 0));Blockly.terminateDrag_();Blockly.selected&&Blockly.highlightedConnection_?(Blockly.localConnection_.connect(Blockly.highlightedConnection_),this.rendered&&(Blockly.localConnection_.isSuperior()?Blockly.highlightedConnection_:Blockly.localConnection_).getSourceBlock().connectionUiEffect(),this.workspace.trashcan&&
+this.workspace.trashcan.close()):!this.getParent()&&Blockly.selected.isDeletable()&&this.workspace.isDeleteArea(a)&&((a=this.workspace.trashcan)&&goog.Timer.callOnce(a.close,100,a),Blockly.selected.dispose(!1,!0));Blockly.highlightedConnection_&&(Blockly.highlightedConnection_.unhighlight(),Blockly.highlightedConnection_=null);Blockly.highlightedConnectionBad_&&(Blockly.highlightedConnectionBad_.unhighlight(),Blockly.highlightedConnectionBad_=null);Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);Blockly.WidgetDiv.isVisible()||
+Blockly.Events.setGroup(!1)};Blockly.BlockSvg.prototype.showHelp_=function(){var a=goog.isFunction(this.helpUrl)?this.helpUrl():this.helpUrl;a&&window.open(a)};
+Blockly.BlockSvg.prototype.showContextMenu_=function(a){if(!this.workspace.options.readOnly&&this.contextMenu){var b=this,c=[];if(this.isDeletable()&&this.isMovable()&&!b.isInFlyout){var d={text:Blockly.Msg.DUPLICATE_BLOCK,enabled:!0,callback:function(){Blockly.duplicate_(b)}};this.getDescendants().length>this.workspace.remainingCapacity()&&(d.enabled=!1);c.push(d);this.isEditable()&&!this.collapsed_&&this.workspace.options.comments&&(d={enabled:!goog.userAgent.IE},this.comment?(d.text=Blockly.Msg.REMOVE_COMMENT,
+d.callback=function(){b.setCommentText(null)}):(d.text=Blockly.Msg.ADD_COMMENT,d.callback=function(){b.setCommentText("")}),c.push(d));if(!this.collapsed_)for(d=1;d<this.inputList.length;d++)if(this.inputList[d-1].type!=Blockly.NEXT_STATEMENT&&this.inputList[d].type!=Blockly.NEXT_STATEMENT){var d={enabled:!0},e=this.getInputsInline();d.text=e?Blockly.Msg.EXTERNAL_INPUTS:Blockly.Msg.INLINE_INPUTS;d.callback=function(){b.setInputsInline(!e)};c.push(d);break}this.workspace.options.collapse&&(this.collapsed_?
+(d={enabled:!0},d.text=Blockly.Msg.EXPAND_BLOCK,d.callback=function(){b.setCollapsed(!1)}):(d={enabled:!0},d.text=Blockly.Msg.COLLAPSE_BLOCK,d.callback=function(){b.setCollapsed(!0)}),c.push(d));this.workspace.options.disable&&(d={text:this.disabled?Blockly.Msg.ENABLE_BLOCK:Blockly.Msg.DISABLE_BLOCK,enabled:!this.getInheritedDisabled(),callback:function(){b.setDisabled(!b.disabled)}},c.push(d));var d=this.getDescendants().length,f=this.getNextBlock();f&&(d-=f.getDescendants().length);d={text:1==d?
+Blockly.Msg.DELETE_BLOCK:Blockly.Msg.DELETE_X_BLOCKS.replace("%1",String(d)),enabled:!0,callback:function(){Blockly.Events.setGroup(!0);b.dispose(!0,!0);Blockly.Events.setGroup(!1)}};c.push(d)}d={enabled:!(goog.isFunction(this.helpUrl)?!this.helpUrl():!this.helpUrl)};d.text=Blockly.Msg.HELP;d.callback=function(){b.showHelp_()};c.push(d);this.customContextMenu&&!b.isInFlyout&&this.customContextMenu(c);Blockly.ContextMenu.show(a,c,this.RTL);Blockly.ContextMenu.currentBlock=this}};
+Blockly.BlockSvg.prototype.moveConnections_=function(a,b){if(this.rendered){for(var c=this.getConnections_(!1),d=0;d<c.length;d++)c[d].moveBy(a,b);c=this.getIcons();for(d=0;d<c.length;d++)c[d].computeIconLocation();for(d=0;d<this.childBlocks_.length;d++)this.childBlocks_[d].moveConnections_(a,b)}};
+Blockly.BlockSvg.prototype.setDragging_=function(a){if(a){var b=this.getSvgRoot();b.translate_="";b.skew_="";this.addDragging();Blockly.draggingConnections_=Blockly.draggingConnections_.concat(this.getConnections_(!0))}else this.removeDragging(),Blockly.draggingConnections_=[];for(b=0;b<this.childBlocks_.length;b++)this.childBlocks_[b].setDragging_(a)};
+Blockly.BlockSvg.prototype.onMouseMove_=function(a){if("mousemove"==a.type&&1>=a.clientX&&0==a.clientY&&0==a.button)a.stopPropagation();else{var b=this.getRelativeToSurfaceXY(),c=this.workspace.moveDrag(a);if(Blockly.dragMode_==Blockly.DRAG_STICKY&&goog.math.Coordinate.distance(b,c)*this.workspace.scale>Blockly.DRAG_RADIUS){Blockly.dragMode_=Blockly.DRAG_FREE;Blockly.longStop_();if(this.parentBlock_){this.unplug();var d=this.getSvgRoot();d.translate_="translate("+c.x+","+c.y+")";this.disconnectUiEffect()}this.setDragging_(!0)}if(Blockly.dragMode_==
+Blockly.DRAG_FREE){b=goog.math.Coordinate.difference(b,this.dragStartXY_);d=this.getSvgRoot();d.translate_="translate("+c.x+","+c.y+")";d.setAttribute("transform",d.translate_+d.skew_);for(c=0;c<this.draggedBubbles_.length;c++)d=this.draggedBubbles_[c],d.bubble.setIconLocation(goog.math.Coordinate.sum(d,b));d=this.getConnections_(!1);(c=this.lastConnectionInStack_())&&c!=this.nextConnection&&d.push(c);for(var e=null,f=null,g=null,h=Blockly.SNAP_RADIUS,c=0;c<d.length;c++){var k=d[c],l=k.closest(h,
+b);l.connection&&l.allowed?(e=l.connection,g=k,h=l.radius):l.connection&&!l.allowed&&(f=l.connection,g=k,h=l.radius)}Blockly.highlightedConnection_&&Blockly.highlightedConnection_!=e&&(Blockly.highlightedConnection_.unhighlight(),Blockly.highlightedConnection_=null,Blockly.localConnection_=null);Blockly.highlightedConnectionBad_&&Blockly.highlightedConnectionBad_!=f&&(Blockly.highlightedConnectionBad_.unhighlight(),Blockly.highlightedConnectionBad_=null,Blockly.localConnection_=null);e&&e!=Blockly.highlightedConnection_&&
+(e.highlight(),Blockly.highlightedConnection_=e,Blockly.localConnection_=g);f&&f!=Blockly.highlightedConnectionBad_&&(f.highlightBad(),Blockly.highlightedConnectionBad_=f,Blockly.localConnection_=g);this.isDeletable()&&this.workspace.isDeleteArea(a)}a.stopPropagation();a.preventDefault()}};Blockly.BlockSvg.prototype.updateMovable=function(){this.isMovable()?Blockly.addClass_(this.svgGroup_,"blocklyDraggable"):Blockly.removeClass_(this.svgGroup_,"blocklyDraggable")};
+Blockly.BlockSvg.prototype.setMovable=function(a){Blockly.BlockSvg.superClass_.setMovable.call(this,a);this.updateMovable()};Blockly.BlockSvg.prototype.setEditable=function(a){Blockly.BlockSvg.superClass_.setEditable.call(this,a);a=this.getIcons();for(var b=0;b<a.length;b++)a[b].updateEditable()};Blockly.BlockSvg.prototype.setShadow=function(a){Blockly.BlockSvg.superClass_.setShadow.call(this,a);this.updateColour()};Blockly.BlockSvg.prototype.getSvgRoot=function(){return this.svgGroup_};
+Blockly.BlockSvg.prototype.dispose=function(a,b){Blockly.Tooltip.hide();Blockly.Field.startCache();var c=this.workspace;Blockly.selected==this&&(this.unselect(),Blockly.terminateDrag_());Blockly.ContextMenu.currentBlock==this&&Blockly.ContextMenu.hide();b&&this.rendered&&(this.unplug(a),this.disposeUiEffect());this.rendered=!1;Blockly.Events.disable();try{for(var d=this.getIcons(),e=0;e<d.length;e++)d[e].dispose()}finally{Blockly.Events.enable()}Blockly.BlockSvg.superClass_.dispose.call(this,a);goog.dom.removeNode(this.svgGroup_);
+Blockly.resizeSvgContents(c);this.svgPathDark_=this.svgPathLight_=this.svgPath_=this.svgGroup_=null;Blockly.Field.stopCache()};
+Blockly.BlockSvg.prototype.disposeUiEffect=function(){this.workspace.playAudio("delete");var a=Blockly.getSvgXY_(this.svgGroup_,this.workspace),b=this.svgGroup_.cloneNode(!0);b.translateX_=a.x;b.translateY_=a.y;b.setAttribute("transform","translate("+b.translateX_+","+b.translateY_+")");this.workspace.getParentSvg().appendChild(b);b.bBox_=b.getBBox();Blockly.BlockSvg.disposeUiStep_(b,this.RTL,new Date,this.workspace.scale)};
+Blockly.BlockSvg.disposeUiStep_=function(a,b,c,d){var e=(new Date-c)/150;1<e?goog.dom.removeNode(a):(a.setAttribute("transform","translate("+(a.translateX_+(b?-1:1)*a.bBox_.width*d/2*e)+","+(a.translateY_+a.bBox_.height*d*e)+") scale("+(1-e)*d+")"),setTimeout(function(){Blockly.BlockSvg.disposeUiStep_(a,b,c,d)},10))};
+Blockly.BlockSvg.prototype.connectionUiEffect=function(){this.workspace.playAudio("click");if(!(1>this.workspace.scale)){var a=Blockly.getSvgXY_(this.svgGroup_,this.workspace);this.outputConnection?(a.x+=(this.RTL?3:-3)*this.workspace.scale,a.y+=13*this.workspace.scale):this.previousConnection&&(a.x+=(this.RTL?-23:23)*this.workspace.scale,a.y+=3*this.workspace.scale);a=Blockly.createSvgElement("circle",{cx:a.x,cy:a.y,r:0,fill:"none",stroke:"#888","stroke-width":10},this.workspace.getParentSvg());
+Blockly.BlockSvg.connectionUiStep_(a,new Date,this.workspace.scale)}};Blockly.BlockSvg.connectionUiStep_=function(a,b,c){var d=(new Date-b)/150;1<d?goog.dom.removeNode(a):(a.setAttribute("r",25*d*c),a.style.opacity=1-d,Blockly.BlockSvg.disconnectUiStop_.pid_=setTimeout(function(){Blockly.BlockSvg.connectionUiStep_(a,b,c)},10))};
+Blockly.BlockSvg.prototype.disconnectUiEffect=function(){this.workspace.playAudio("disconnect");if(!(1>this.workspace.scale)){var a=this.getHeightWidth().height,a=Math.atan(10/a)/Math.PI*180;this.RTL||(a*=-1);Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_,a,new Date)}};
+Blockly.BlockSvg.disconnectUiStep_=function(a,b,c){var d=(new Date-c)/200;1<d?a.skew_="":(a.skew_="skewX("+Math.round(Math.sin(d*Math.PI*3)*(1-d)*b)+")",Blockly.BlockSvg.disconnectUiStop_.group=a,Blockly.BlockSvg.disconnectUiStop_.pid=setTimeout(function(){Blockly.BlockSvg.disconnectUiStep_(a,b,c)},10));a.setAttribute("transform",a.translate_+a.skew_)};
+Blockly.BlockSvg.disconnectUiStop_=function(){if(Blockly.BlockSvg.disconnectUiStop_.group){clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid);var a=Blockly.BlockSvg.disconnectUiStop_.group;a.skew_="";a.setAttribute("transform",a.translate_);Blockly.BlockSvg.disconnectUiStop_.group=null}};Blockly.BlockSvg.disconnectUiStop_.pid=0;Blockly.BlockSvg.disconnectUiStop_.group=null;
+Blockly.BlockSvg.prototype.updateColour=function(){if(!this.disabled){var a=this.getColour(),b=goog.color.hexToRgb(a);if(this.isShadow())b=goog.color.lighten(b,.6),a=goog.color.rgbArrayToHex(b),this.svgPathLight_.style.display="none",this.svgPathDark_.setAttribute("fill",a);else{this.svgPathLight_.style.display="";var c=goog.color.rgbArrayToHex(goog.color.lighten(b,.3)),b=goog.color.rgbArrayToHex(goog.color.darken(b,.2));this.svgPathLight_.setAttribute("stroke",c);this.svgPathDark_.setAttribute("fill",
+b)}this.svgPath_.setAttribute("fill",a);a=this.getIcons();for(c=0;c<a.length;c++)a[c].updateColour();for(a=0;c=this.inputList[a];a++)for(var b=0,d;d=c.fieldRow[b];b++)d.setText(null)}};
+Blockly.BlockSvg.prototype.updateDisabled=function(){var a=Blockly.hasClass_(this.svgGroup_,"blocklyDisabled");this.disabled||this.getInheritedDisabled()?a||(Blockly.addClass_(this.svgGroup_,"blocklyDisabled"),this.svgPath_.setAttribute("fill","url(#"+this.workspace.options.disabledPatternId+")")):a&&(Blockly.removeClass_(this.svgGroup_,"blocklyDisabled"),this.updateColour());for(var a=this.getChildren(),b=0,c;c=a[b];b++)c.updateDisabled()};
+Blockly.BlockSvg.prototype.getCommentText=function(){return this.comment?this.comment.getText().replace(/\s+$/,"").replace(/ +\n/g,"\n"):""};Blockly.BlockSvg.prototype.setCommentText=function(a){var b=!1;goog.isString(a)?(this.comment||(this.comment=new Blockly.Comment(this),b=!0),this.comment.setText(a)):this.comment&&(this.comment.dispose(),b=!0);b&&this.rendered&&(this.render(),this.bumpNeighbours_())};
+Blockly.BlockSvg.prototype.setWarningText=function(a,b){this.setWarningText.pid_||(this.setWarningText.pid_=Object.create(null));var c=b||"";if(c)this.setWarningText.pid_[c]&&(clearTimeout(this.setWarningText.pid_[c]),delete this.setWarningText.pid_[c]);else for(var d in this.setWarningText.pid_)clearTimeout(this.setWarningText.pid_[d]),delete this.setWarningText.pid_[d];if(Blockly.dragMode_==Blockly.DRAG_FREE){var e=this;this.setWarningText.pid_[c]=setTimeout(function(){e.workspace&&(delete e.setWarningText.pid_[c],
+e.setWarningText(a,c))},100)}else{this.isInFlyout&&(a=null);d=this.getSurroundParent();for(var f=null;d;)d.isCollapsed()&&(f=d),d=d.getSurroundParent();f&&f.setWarningText(a,"collapsed "+this.id+" "+c);d=!1;goog.isString(a)?(this.warning||(this.warning=new Blockly.Warning(this),d=!0),this.warning.setText(a,c)):this.warning&&!c?(this.warning.dispose(),d=!0):this.warning&&(d=this.warning.getText(),this.warning.setText("",c),(f=this.warning.getText())||this.warning.dispose(),d=d==f);d&&this.rendered&&
+(this.render(),this.bumpNeighbours_())}};Blockly.BlockSvg.prototype.setMutator=function(a){this.mutator&&this.mutator!==a&&this.mutator.dispose();a&&(a.block_=this,this.mutator=a,a.createIcon())};Blockly.BlockSvg.prototype.setMutatorPlus=function(a){this.mutatorPlus&&this.mutatorPlus!==a&&this.mutatorPlus.dispose();a&&(a.block_=this,this.mutatorPlus=a,this.rendered&&a.createIcon())};
+Blockly.BlockSvg.prototype.setMutatorMinus=function(a){this.mutatorMinus&&this.mutatorMinus!==a&&this.mutatorMinus.dispose();a&&(a.block_=this,this.mutatorMinus=a,this.rendered&&a.createIcon())};Blockly.BlockSvg.prototype.setDisabled=function(a){this.disabled!=a&&(Blockly.BlockSvg.superClass_.setDisabled.call(this,a),this.rendered&&this.updateDisabled())};
+Blockly.BlockSvg.prototype.addSelect=function(){Blockly.addClass_(this.svgGroup_,"blocklySelected");var a=this;do{var b=a.getSvgRoot();b.parentNode.appendChild(b);a=a.getParent()}while(a)};Blockly.BlockSvg.prototype.removeSelect=function(){Blockly.removeClass_(this.svgGroup_,"blocklySelected")};Blockly.BlockSvg.prototype.addBacklight=function(a){Blockly.addClass_(this.svgGroup_,"blocklyBacklight");this.svgGroup_.parentNode.appendChild(this.svgGroup_)};
+Blockly.BlockSvg.prototype.removeBacklight=function(a){Blockly.removeClass_(this.svgGroup_,"blocklyBacklight")};Blockly.BlockSvg.prototype.addDragging=function(){Blockly.addClass_(this.svgGroup_,"blocklyDragging")};Blockly.BlockSvg.prototype.removeDragging=function(){Blockly.removeClass_(this.svgGroup_,"blocklyDragging")};Blockly.BlockSvg.prototype.setColour=function(a){Blockly.BlockSvg.superClass_.setColour.call(this,a);this.rendered&&this.updateColour()};
+Blockly.BlockSvg.prototype.setPreviousStatement=function(a,b){Blockly.BlockSvg.superClass_.setPreviousStatement.call(this,a,b);this.rendered&&(this.render(),this.bumpNeighbours_())};Blockly.BlockSvg.prototype.setNextStatement=function(a,b){Blockly.BlockSvg.superClass_.setNextStatement.call(this,a,b);this.rendered&&(this.render(),this.bumpNeighbours_())};Blockly.BlockSvg.prototype.setOutput=function(a,b){Blockly.BlockSvg.superClass_.setOutput.call(this,a,b);this.rendered&&(this.render(),this.bumpNeighbours_())};
+Blockly.BlockSvg.prototype.setInputsInline=function(a){Blockly.BlockSvg.superClass_.setInputsInline.call(this,a);this.rendered&&(this.render(),this.bumpNeighbours_())};Blockly.BlockSvg.prototype.removeInput=function(a,b){Blockly.BlockSvg.superClass_.removeInput.call(this,a,b);this.rendered&&(this.render(),this.bumpNeighbours_())};Blockly.BlockSvg.prototype.moveNumberedInputBefore=function(a,b){Blockly.BlockSvg.superClass_.moveNumberedInputBefore.call(this,a,b);this.rendered&&(this.render(),this.bumpNeighbours_())};
+Blockly.BlockSvg.prototype.appendInput_=function(a,b){var c=Blockly.BlockSvg.superClass_.appendInput_.call(this,a,b);this.rendered&&(this.render(),this.bumpNeighbours_());return c};Blockly.BlockSvg.prototype.getConnections_=function(a){var b=[];if(a||this.rendered)if(this.outputConnection&&b.push(this.outputConnection),this.previousConnection&&b.push(this.previousConnection),this.nextConnection&&b.push(this.nextConnection),a||!this.collapsed_){a=0;for(var c;c=this.inputList[a];a++)c.connection&&b.push(c.connection)}return b};
+Blockly.BlockSvg.prototype.makeConnection_=function(a){return new Blockly.RenderedConnection(this,a)};Blockly.BlockSvg.render={};Blockly.BlockSvg.SEP_SPACE_X=10;Blockly.BlockSvg.SEP_SPACE_Y=10;Blockly.BlockSvg.INLINE_PADDING_Y=5;Blockly.BlockSvg.MIN_BLOCK_Y=25;Blockly.BlockSvg.TAB_HEIGHT=20;Blockly.BlockSvg.TAB_WIDTH=8;Blockly.BlockSvg.NOTCH_WIDTH=30;Blockly.BlockSvg.CORNER_RADIUS=8;Blockly.BlockSvg.START_HAT=!1;Blockly.BlockSvg.START_HAT_HEIGHT=15;Blockly.BlockSvg.START_HAT_PATH="c 30,-"+Blockly.BlockSvg.START_HAT_HEIGHT+" 70,-"+Blockly.BlockSvg.START_HAT_HEIGHT+" 100,0";
+Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR="c 17.8,-9.2 45.3,-14.9 75,-8.7 M 100.5,0.5";Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL="m 25,-8.7 c 29.7,-6.2 57.2,-0.5 75,8.7";Blockly.BlockSvg.DISTANCE_45_INSIDE=(1-Math.SQRT1_2)*(Blockly.BlockSvg.CORNER_RADIUS-.5)+.5;Blockly.BlockSvg.DISTANCE_45_OUTSIDE=(1-Math.SQRT1_2)*(Blockly.BlockSvg.CORNER_RADIUS+.5)-.5;Blockly.BlockSvg.NOTCH_PATH_LEFT="l 6,4 3,0 6,-4";Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT="l 6,4 3,0 6,-4";Blockly.BlockSvg.NOTCH_PATH_RIGHT="l -6,4 -3,0 -6,-4";
+Blockly.BlockSvg.JAGGED_TEETH="l 8,0 0,4 8,4 -16,8 8,4";Blockly.BlockSvg.JAGGED_TEETH_HEIGHT=20;Blockly.BlockSvg.JAGGED_TEETH_WIDTH=15;Blockly.BlockSvg.TAB_PATH_DOWN="v 5 c 0,10 -"+Blockly.BlockSvg.TAB_WIDTH+",-8 -"+Blockly.BlockSvg.TAB_WIDTH+",7.5 s "+Blockly.BlockSvg.TAB_WIDTH+",-2.5 "+Blockly.BlockSvg.TAB_WIDTH+",7.5";
+Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL="v 6.5 m -"+.97*Blockly.BlockSvg.TAB_WIDTH+",3 q -"+.05*Blockly.BlockSvg.TAB_WIDTH+",10 "+.3*Blockly.BlockSvg.TAB_WIDTH+",9.5 m "+.67*Blockly.BlockSvg.TAB_WIDTH+",-1.9 v 1.4";Blockly.BlockSvg.TOP_LEFT_CORNER_START="m 0,"+Blockly.BlockSvg.CORNER_RADIUS;Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL="m "+Blockly.BlockSvg.DISTANCE_45_INSIDE+","+Blockly.BlockSvg.DISTANCE_45_INSIDE;
+Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR="m 0.5,"+(Blockly.BlockSvg.CORNER_RADIUS-.5);Blockly.BlockSvg.TOP_LEFT_CORNER="A "+Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS+" 0 0,1 "+Blockly.BlockSvg.CORNER_RADIUS+",0";Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT="A "+(Blockly.BlockSvg.CORNER_RADIUS-.5)+","+(Blockly.BlockSvg.CORNER_RADIUS-.5)+" 0 0,1 "+Blockly.BlockSvg.CORNER_RADIUS+",0.5";
+Blockly.BlockSvg.INNER_TOP_LEFT_CORNER=Blockly.BlockSvg.NOTCH_PATH_RIGHT+" h -"+(Blockly.BlockSvg.NOTCH_WIDTH-15-Blockly.BlockSvg.CORNER_RADIUS)+" a "+Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS+" 0 0,0 -"+Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS;Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER="a "+Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS+" 0 0,0 "+Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS;
+Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL="a "+Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS+" 0 0,0 "+(-Blockly.BlockSvg.DISTANCE_45_OUTSIDE-.5)+","+(Blockly.BlockSvg.CORNER_RADIUS-Blockly.BlockSvg.DISTANCE_45_OUTSIDE);Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL="a "+(Blockly.BlockSvg.CORNER_RADIUS+.5)+","+(Blockly.BlockSvg.CORNER_RADIUS+.5)+" 0 0,0 "+(Blockly.BlockSvg.CORNER_RADIUS+.5)+","+(Blockly.BlockSvg.CORNER_RADIUS+.5);
+Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR="a "+(Blockly.BlockSvg.CORNER_RADIUS+.5)+","+(Blockly.BlockSvg.CORNER_RADIUS+.5)+" 0 0,0 "+(Blockly.BlockSvg.CORNER_RADIUS-Blockly.BlockSvg.DISTANCE_45_OUTSIDE)+","+(Blockly.BlockSvg.DISTANCE_45_OUTSIDE+.5);
+Blockly.BlockSvg.prototype.render=function(a){Blockly.Field.startCache();this.rendered=!0;var b=Blockly.BlockSvg.SEP_SPACE_X;this.RTL&&(b=-b);for(var c=this.getIcons(),d=0;d<c.length;d++)b=c[d].renderIcon(b);b+=this.RTL?Blockly.BlockSvg.SEP_SPACE_X:-Blockly.BlockSvg.SEP_SPACE_X;c=this.renderCompute_(b);this.renderDraw_(b,c);this.renderMoveConnections_();!1!==a&&((a=this.getParent())?a.render(!0):Blockly.resizeSvgContents(this.workspace));Blockly.Field.stopCache()};
+Blockly.BlockSvg.prototype.renderFields_=function(a,b,c){c+=Blockly.BlockSvg.INLINE_PADDING_Y;this.RTL&&(b=-b);for(var d=0,e;e=a[d];d++){var f=e.getSvgRoot();f&&(this.RTL?(b-=e.renderSep+e.renderWidth,f.setAttribute("transform","translate("+b+","+c+")"),e.renderWidth&&(b-=Blockly.BlockSvg.SEP_SPACE_X)):(f.setAttribute("transform","translate("+(b+e.renderSep)+","+c+")"),e.renderWidth&&(b+=e.renderSep+e.renderWidth+Blockly.BlockSvg.SEP_SPACE_X)))}return this.RTL?-b:b};
+Blockly.BlockSvg.prototype.renderCompute_=function(a){var b=this.inputList,c=[];c.rightEdge=a+2*Blockly.BlockSvg.SEP_SPACE_X;if(this.previousConnection||this.nextConnection)c.rightEdge=Math.max(c.rightEdge,Blockly.BlockSvg.NOTCH_WIDTH+Blockly.BlockSvg.SEP_SPACE_X);for(var d=0,e=0,f=!1,g=!1,h=!1,k=void 0,l=this.getInputsInline()&&!this.isCollapsed(),p=0,m;m=b[p];p++)if(m.isVisible()){var n;l&&k&&k!=Blockly.NEXT_STATEMENT&&m.type!=Blockly.NEXT_STATEMENT?n=c[c.length-1]:(k=m.type,n=[],n.type=l&&m.type!=
+Blockly.NEXT_STATEMENT?Blockly.BlockSvg.INLINE:m.type,n.height=0,c.push(n));n.push(m);m.renderHeight=Blockly.BlockSvg.MIN_BLOCK_Y;m.renderWidth=l&&m.type==Blockly.INPUT_VALUE?Blockly.BlockSvg.TAB_WIDTH+1.25*Blockly.BlockSvg.SEP_SPACE_X:0;if(m.connection&&m.connection.isConnected()){var q=m.connection.targetBlock().getHeightWidth();m.renderHeight=Math.max(m.renderHeight,q.height);m.renderWidth=Math.max(m.renderWidth,q.width)}l||p!=b.length-1?!l&&m.type==Blockly.INPUT_VALUE&&b[p+1]&&b[p+1].type==Blockly.NEXT_STATEMENT&&
+m.renderHeight--:m.renderHeight--;n.height=Math.max(n.height,m.renderHeight);m.fieldWidth=0;1==c.length&&(m.fieldWidth+=this.RTL?-a:a);for(var q=!1,t=0,r;r=m.fieldRow[t];t++){0!=t&&(m.fieldWidth+=Blockly.BlockSvg.SEP_SPACE_X);var u=r.getSize();r.renderWidth=u.width;r.renderSep=q&&r.EDITABLE?Blockly.BlockSvg.SEP_SPACE_X:0;m.fieldWidth+=r.renderWidth+r.renderSep;n.height=Math.max(n.height,u.height);q=r.EDITABLE}n.type!=Blockly.BlockSvg.INLINE&&(n.type==Blockly.NEXT_STATEMENT?(g=!0,e=Math.max(e,m.fieldWidth)):
+(n.type==Blockly.INPUT_VALUE?f=!0:n.type==Blockly.DUMMY_INPUT&&(h=!0),d=Math.max(d,m.fieldWidth)))}for(a=0;n=c[a];a++)if(n.thicker=!1,n.type==Blockly.BlockSvg.INLINE)for(b=0;m=n[b];b++)if(m.type==Blockly.INPUT_VALUE){n.height+=2*Blockly.BlockSvg.INLINE_PADDING_Y;n.thicker=!0;break}c.statementEdge=2*Blockly.BlockSvg.SEP_SPACE_X+e;g&&(c.rightEdge=Math.max(c.rightEdge,c.statementEdge+Blockly.BlockSvg.NOTCH_WIDTH));f?c.rightEdge=Math.max(c.rightEdge,d+2*Blockly.BlockSvg.SEP_SPACE_X+Blockly.BlockSvg.TAB_WIDTH):
+h&&(c.rightEdge=Math.max(c.rightEdge,d+2*Blockly.BlockSvg.SEP_SPACE_X));c.hasValue=f;c.hasStatement=g;c.hasDummy=h;return c};
+Blockly.BlockSvg.prototype.renderDraw_=function(a,b){this.startHat_=!1;this.height=0;if(this.outputConnection)this.squareBottomLeftCorner_=this.squareTopLeftCorner_=!0;else{this.squareBottomLeftCorner_=this.squareTopLeftCorner_=!1;if(this.previousConnection){var c=this.previousConnection.targetBlock();c&&c.getNextBlock()==this&&(this.squareTopLeftCorner_=!0)}else Blockly.BlockSvg.START_HAT&&(this.startHat_=this.squareTopLeftCorner_=!0,this.height+=Blockly.BlockSvg.START_HAT_HEIGHT,b.rightEdge=Math.max(b.rightEdge,
+100));this.getNextBlock()&&(this.squareBottomLeftCorner_=!0)}var d=[],e=[],c=[],f=[];this.renderDrawTop_(d,c,b.rightEdge);var g=this.renderDrawRight_(d,c,e,f,b,a);this.renderDrawBottom_(d,c,g);this.renderDrawLeft_(d,c);d=d.join(" ")+"\n"+e.join(" ");this.svgPath_.setAttribute("d",d);this.svgPathDark_.setAttribute("d",d);d=c.join(" ")+"\n"+f.join(" ");this.svgPathLight_.setAttribute("d",d);this.RTL&&(this.svgPath_.setAttribute("transform","scale(-1 1)"),this.svgPathLight_.setAttribute("transform",
+"scale(-1 1)"),this.svgPathDark_.setAttribute("transform","translate(1,1) scale(-1 1)"))};
+Blockly.BlockSvg.prototype.renderMoveConnections_=function(){var a=this.getRelativeToSurfaceXY();this.previousConnection&&this.previousConnection.moveToOffset(a);this.outputConnection&&this.outputConnection.moveToOffset(a);for(var b=0;b<this.inputList.length;b++){var c=this.inputList[b].connection;c&&(c.moveToOffset(a),c.isConnected()&&c.tighten_())}this.nextConnection&&(this.nextConnection.moveToOffset(a),this.nextConnection.isConnected()&&this.nextConnection.tighten_())};
+Blockly.BlockSvg.prototype.renderDrawTop_=function(a,b,c){this.squareTopLeftCorner_?(a.push("m 0,0"),b.push("m 0.5,0.5"),this.startHat_&&(a.push(Blockly.BlockSvg.START_HAT_PATH),b.push(this.RTL?Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL:Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR))):(a.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START),b.push(this.RTL?Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL:Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR),a.push(Blockly.BlockSvg.TOP_LEFT_CORNER),b.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT));
+this.previousConnection&&(a.push("H",Blockly.BlockSvg.NOTCH_WIDTH-15),b.push("H",Blockly.BlockSvg.NOTCH_WIDTH-15),a.push(Blockly.BlockSvg.NOTCH_PATH_LEFT),b.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT),this.previousConnection.setOffsetInBlock(this.RTL?-Blockly.BlockSvg.NOTCH_WIDTH:Blockly.BlockSvg.NOTCH_WIDTH,0));a.push("H",c);b.push("H",c-.5);this.width=c};
+Blockly.BlockSvg.prototype.renderDrawRight_=function(a,b,c,d,e,f){for(var g,h=0,k,l,p=0,m;m=e[p];p++){g=Blockly.BlockSvg.SEP_SPACE_X;0==p&&(g+=this.RTL?-f:f);b.push("M",e.rightEdge-.5+","+(h+.5));if(this.isCollapsed()){var n=m[0];k=h;this.renderFields_(n.fieldRow,g,k);a.push(Blockly.BlockSvg.JAGGED_TEETH);b.push("h 8");n=m.height-Blockly.BlockSvg.JAGGED_TEETH_HEIGHT;a.push("v",n);this.RTL&&(b.push("v 3.9 l 7.2,3.4 m -14.5,8.9 l 7.3,3.5"),b.push("v",n-.7));this.width+=Blockly.BlockSvg.JAGGED_TEETH_WIDTH}else if(m.type==
+Blockly.BlockSvg.INLINE){for(var q=0;n=m[q];q++)k=h,m.thicker&&(k+=Blockly.BlockSvg.INLINE_PADDING_Y),g=this.renderFields_(n.fieldRow,g,k),n.type!=Blockly.DUMMY_INPUT&&(g+=n.renderWidth+Blockly.BlockSvg.SEP_SPACE_X),n.type==Blockly.INPUT_VALUE&&(c.push("M",g-Blockly.BlockSvg.SEP_SPACE_X+","+(h+Blockly.BlockSvg.INLINE_PADDING_Y)),c.push("h",Blockly.BlockSvg.TAB_WIDTH-2-n.renderWidth),c.push(Blockly.BlockSvg.TAB_PATH_DOWN),c.push("v",n.renderHeight+1-Blockly.BlockSvg.TAB_HEIGHT),c.push("h",n.renderWidth+
+2-Blockly.BlockSvg.TAB_WIDTH),c.push("z"),this.RTL?(d.push("M",g-Blockly.BlockSvg.SEP_SPACE_X-2.5+Blockly.BlockSvg.TAB_WIDTH-n.renderWidth+","+(h+Blockly.BlockSvg.INLINE_PADDING_Y+.5)),d.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL),d.push("v",n.renderHeight-Blockly.BlockSvg.TAB_HEIGHT+2.5),d.push("h",n.renderWidth-Blockly.BlockSvg.TAB_WIDTH+2)):(d.push("M",g-Blockly.BlockSvg.SEP_SPACE_X+.5+","+(h+Blockly.BlockSvg.INLINE_PADDING_Y+.5)),d.push("v",n.renderHeight+1),d.push("h",Blockly.BlockSvg.TAB_WIDTH-
+2-n.renderWidth),d.push("M",g-n.renderWidth-Blockly.BlockSvg.SEP_SPACE_X+.9+","+(h+Blockly.BlockSvg.INLINE_PADDING_Y+Blockly.BlockSvg.TAB_HEIGHT-.7)),d.push("l",.46*Blockly.BlockSvg.TAB_WIDTH+",-2.1")),k=this.RTL?-g-Blockly.BlockSvg.TAB_WIDTH+Blockly.BlockSvg.SEP_SPACE_X+n.renderWidth+1:g+Blockly.BlockSvg.TAB_WIDTH-Blockly.BlockSvg.SEP_SPACE_X-n.renderWidth-1,l=h+Blockly.BlockSvg.INLINE_PADDING_Y+1,n.connection.setOffsetInBlock(k,l));g=Math.max(g,e.rightEdge);this.width=Math.max(this.width,g);a.push("H",
+g);b.push("H",g-.5);a.push("v",m.height);this.RTL&&b.push("v",m.height-1)}else m.type==Blockly.INPUT_VALUE?(n=m[0],k=h,n.align!=Blockly.ALIGN_LEFT&&(q=e.rightEdge-n.fieldWidth-Blockly.BlockSvg.TAB_WIDTH-2*Blockly.BlockSvg.SEP_SPACE_X,n.align==Blockly.ALIGN_RIGHT?g+=q:n.align==Blockly.ALIGN_CENTRE&&(g+=q/2)),this.renderFields_(n.fieldRow,g,k),a.push(Blockly.BlockSvg.TAB_PATH_DOWN),q=m.height-Blockly.BlockSvg.TAB_HEIGHT,a.push("v",q),this.RTL?(b.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL),b.push("v",
+q+.5)):(b.push("M",e.rightEdge-5+","+(h+Blockly.BlockSvg.TAB_HEIGHT-.7)),b.push("l",.46*Blockly.BlockSvg.TAB_WIDTH+",-2.1")),k=this.RTL?-e.rightEdge-1:e.rightEdge+1,n.connection.setOffsetInBlock(k,h),n.connection.isConnected()&&(this.width=Math.max(this.width,e.rightEdge+n.connection.targetBlock().getHeightWidth().width-Blockly.BlockSvg.TAB_WIDTH+1))):m.type==Blockly.DUMMY_INPUT?(n=m[0],k=h,n.align!=Blockly.ALIGN_LEFT&&(q=e.rightEdge-n.fieldWidth-2*Blockly.BlockSvg.SEP_SPACE_X,e.hasValue&&(q-=Blockly.BlockSvg.TAB_WIDTH),
+n.align==Blockly.ALIGN_RIGHT?g+=q:n.align==Blockly.ALIGN_CENTRE&&(g+=q/2)),this.renderFields_(n.fieldRow,g,k),a.push("v",m.height),this.RTL&&b.push("v",m.height-1)):m.type==Blockly.NEXT_STATEMENT&&(n=m[0],0==p&&(a.push("v",Blockly.BlockSvg.SEP_SPACE_Y),this.RTL&&b.push("v",Blockly.BlockSvg.SEP_SPACE_Y-1),h+=Blockly.BlockSvg.SEP_SPACE_Y),k=h,n.align!=Blockly.ALIGN_LEFT&&(q=e.statementEdge-n.fieldWidth-2*Blockly.BlockSvg.SEP_SPACE_X,n.align==Blockly.ALIGN_RIGHT?g+=q:n.align==Blockly.ALIGN_CENTRE&&(g+=
+q/2)),this.renderFields_(n.fieldRow,g,k),g=e.statementEdge+Blockly.BlockSvg.NOTCH_WIDTH,a.push("H",g),a.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER),a.push("v",m.height-2*Blockly.BlockSvg.CORNER_RADIUS),a.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER),a.push("H",e.rightEdge),this.RTL?(b.push("M",g-Blockly.BlockSvg.NOTCH_WIDTH+Blockly.BlockSvg.DISTANCE_45_OUTSIDE+","+(h+Blockly.BlockSvg.DISTANCE_45_OUTSIDE)),b.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL),b.push("v",m.height-2*Blockly.BlockSvg.CORNER_RADIUS),
+b.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL)):(b.push("M",g-Blockly.BlockSvg.NOTCH_WIDTH+Blockly.BlockSvg.DISTANCE_45_OUTSIDE+","+(h+m.height-Blockly.BlockSvg.DISTANCE_45_OUTSIDE)),b.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR)),b.push("H",e.rightEdge-.5),k=this.RTL?-g:g+1,n.connection.setOffsetInBlock(k,h+1),n.connection.isConnected()&&(this.width=Math.max(this.width,e.statementEdge+n.connection.targetBlock().getHeightWidth().width)),p==e.length-1||e[p+1].type==
+Blockly.NEXT_STATEMENT)&&(a.push("v",Blockly.BlockSvg.SEP_SPACE_Y),this.RTL&&b.push("v",Blockly.BlockSvg.SEP_SPACE_Y-1),h+=Blockly.BlockSvg.SEP_SPACE_Y);h+=m.height}e.length||(h=Blockly.BlockSvg.MIN_BLOCK_Y,a.push("V",h),this.RTL&&b.push("V",h-1));return h};
+Blockly.BlockSvg.prototype.renderDrawBottom_=function(a,b,c){this.height+=c+1;this.nextConnection&&(a.push("H",Blockly.BlockSvg.NOTCH_WIDTH+(this.RTL?.5:-.5)+" "+Blockly.BlockSvg.NOTCH_PATH_RIGHT),this.nextConnection.setOffsetInBlock(this.RTL?-Blockly.BlockSvg.NOTCH_WIDTH:Blockly.BlockSvg.NOTCH_WIDTH,c+1),this.height+=4);this.squareBottomLeftCorner_?(a.push("H 0"),this.RTL||b.push("M","0.5,"+(c-.5))):(a.push("H",Blockly.BlockSvg.CORNER_RADIUS),a.push("a",Blockly.BlockSvg.CORNER_RADIUS+","+Blockly.BlockSvg.CORNER_RADIUS+
+" 0 0,1 -"+Blockly.BlockSvg.CORNER_RADIUS+",-"+Blockly.BlockSvg.CORNER_RADIUS),this.RTL||(b.push("M",Blockly.BlockSvg.DISTANCE_45_INSIDE+","+(c-Blockly.BlockSvg.DISTANCE_45_INSIDE)),b.push("A",Blockly.BlockSvg.CORNER_RADIUS-.5+","+(Blockly.BlockSvg.CORNER_RADIUS-.5)+" 0 0,1 0.5,"+(c-Blockly.BlockSvg.CORNER_RADIUS))))};
+Blockly.BlockSvg.prototype.renderDrawLeft_=function(a,b){this.outputConnection?(this.outputConnection.setOffsetInBlock(0,0),a.push("V",Blockly.BlockSvg.TAB_HEIGHT),a.push("c 0,-10 -"+Blockly.BlockSvg.TAB_WIDTH+",8 -"+Blockly.BlockSvg.TAB_WIDTH+",-7.5 s "+Blockly.BlockSvg.TAB_WIDTH+",2.5 "+Blockly.BlockSvg.TAB_WIDTH+",-7.5"),this.RTL?(b.push("M",-.25*Blockly.BlockSvg.TAB_WIDTH+",8.4"),b.push("l",-.45*Blockly.BlockSvg.TAB_WIDTH+",-2.1")):(b.push("V",Blockly.BlockSvg.TAB_HEIGHT-1.5),b.push("m",-.92*
+Blockly.BlockSvg.TAB_WIDTH+",-0.5 q "+-.19*Blockly.BlockSvg.TAB_WIDTH+",-5.5 0,-11"),b.push("m",.92*Blockly.BlockSvg.TAB_WIDTH+",1 V 0.5 H 1")),this.width+=Blockly.BlockSvg.TAB_WIDTH):this.RTL||(this.squareTopLeftCorner_?b.push("V",.5):b.push("V",Blockly.BlockSvg.CORNER_RADIUS));a.push("z")};Blockly.Events={};Blockly.Events.group_="";Blockly.Events.recordUndo=!0;Blockly.Events.disabled_=0;Blockly.Events.CREATE="create";Blockly.Events.DELETE="delete";Blockly.Events.CHANGE="change";Blockly.Events.MOVE="move";Blockly.Events.UI="ui";Blockly.Events.TYPING="typing";Blockly.Events.FIRE_QUEUE_=[];Blockly.Events.fire=function(a){Blockly.Events.isEnabled()&&(Blockly.Events.FIRE_QUEUE_.length||setTimeout(Blockly.Events.fireNow_,0),Blockly.Events.FIRE_QUEUE_.push(a))};
+Blockly.Events.fireNow_=function(){for(var a=Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_,!0),b=Blockly.Events.FIRE_QUEUE_.length=0,c;c=a[b];b++){var d=Blockly.Workspace.getById(c.workspaceId);d&&d.fireChangeListener(c)}};
+Blockly.Events.filter=function(a,b){var c=goog.array.clone(a);b||c.reverse();for(var d=0,e;e=c[d];d++)for(var f=d+1,g;g=c[f];f++)e.type==g.type&&e.blockId==g.blockId&&e.workspaceId==g.workspaceId&&(e.type==Blockly.Events.MOVE?(e.newParentId=g.newParentId,e.newInputName=g.newInputName,e.newCoordinate=g.newCoordinate,c.splice(f,1),f--):e.type==Blockly.Events.CHANGE&&e.element==g.element&&e.name==g.name?(e.newValue=g.newValue,c.splice(f,1),f--):e.type!=Blockly.Events.UI||"click"!=g.element||"commentOpen"!=
+e.element&&"mutatorOpen"!=e.element&&"warningOpen"!=e.element||(e.newValue=g.newValue,c.splice(f,1),f--));for(d=c.length-1;0<=d;d--)c[d].isNull()&&c.splice(d,1);b||c.reverse();for(d=1;e=c[d];d++)e.type==Blockly.Events.CHANGE&&"mutation"==e.element&&c.unshift(c.splice(d,1)[0]);return c};Blockly.Events.clearPendingUndo=function(){for(var a=0,b;b=Blockly.Events.FIRE_QUEUE_[a];a++)b.recordUndo=!1};Blockly.Events.disable=function(){Blockly.Events.disabled_++};Blockly.Events.enable=function(){Blockly.Events.disabled_--};
+Blockly.Events.isEnabled=function(){return 0==Blockly.Events.disabled_};Blockly.Events.getGroup=function(){return Blockly.Events.group_};Blockly.Events.setGroup=function(a){Blockly.Events.group_="boolean"==typeof a?a?Blockly.genUid():"":a};Blockly.Events.getDescendantIds_=function(a){var b=[];a=a.getDescendants();for(var c=0,d;d=a[c];c++)b[c]=d.id;return b};
+Blockly.Events.fromJson=function(a,b){var c;switch(a.type){case Blockly.Events.CREATE:c=new Blockly.Events.Create(null);break;case Blockly.Events.DELETE:c=new Blockly.Events.Delete(null);break;case Blockly.Events.CHANGE:c=new Blockly.Events.Change(null);break;case Blockly.Events.MOVE:c=new Blockly.Events.Move(null);break;case Blockly.Events.UI:c=new Blockly.Events.Ui(null);break;default:throw"Unknown event type.";}c.fromJson(a);c.workspaceId=b.id;return c};
+Blockly.Events.Abstract=function(a){a&&(this.blockId=a.id,this.workspaceId=a.workspace.id);this.group=Blockly.Events.group_;this.recordUndo=Blockly.Events.recordUndo};Blockly.Events.Abstract.prototype.toJson=function(){var a={type:this.type};this.blockId&&(a.blockId=this.blockId);this.group&&(a.group=this.group);return a};Blockly.Events.Abstract.prototype.fromJson=function(a){this.blockId=a.blockId;this.group=a.group};Blockly.Events.Abstract.prototype.isNull=function(){return!1};
+Blockly.Events.Abstract.prototype.run=function(a){};Blockly.Events.Create=function(a){a&&(Blockly.Events.Create.superClass_.constructor.call(this,a),this.xml=Blockly.Xml.blockToDomWithXY(a),this.ids=Blockly.Events.getDescendantIds_(a))};goog.inherits(Blockly.Events.Create,Blockly.Events.Abstract);Blockly.Events.Create.prototype.type=Blockly.Events.CREATE;
+Blockly.Events.Create.prototype.toJson=function(){var a=Blockly.Events.Create.superClass_.toJson.call(this);a.xml=Blockly.Xml.domToText(this.xml);a.ids=this.ids;return a};Blockly.Events.Create.prototype.fromJson=function(a){Blockly.Events.Create.superClass_.fromJson.call(this,a);this.xml=Blockly.Xml.textToDom("<xml>"+a.xml+"</xml>").firstChild;this.ids=a.ids};
+Blockly.Events.Create.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId);if(a)a=goog.dom.createDom("xml"),a.appendChild(this.xml),Blockly.Xml.domToWorkspace(a,b);else{a=0;for(var c;c=this.ids[a];a++){var d=b.getBlockById(c);d?d.dispose(!1,!1):c==this.blockId&&console.warn("Can't uncreate non-existant block: "+c)}}};
+Blockly.Events.Delete=function(a){if(a){if(a.getParent())throw"Connected blocks cannot be deleted.";Blockly.Events.Delete.superClass_.constructor.call(this,a);this.oldXml=Blockly.Xml.blockToDomWithXY(a);this.ids=Blockly.Events.getDescendantIds_(a)}};goog.inherits(Blockly.Events.Delete,Blockly.Events.Abstract);Blockly.Events.Delete.prototype.type=Blockly.Events.DELETE;Blockly.Events.Delete.prototype.toJson=function(){var a=Blockly.Events.Delete.superClass_.toJson.call(this);a.ids=this.ids;return a};
+Blockly.Events.Delete.prototype.fromJson=function(a){Blockly.Events.Delete.superClass_.fromJson.call(this,a);this.ids=a.ids};Blockly.Events.Delete.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId);if(a){a=0;for(var c;c=this.ids[a];a++){var d=b.getBlockById(c);d?d.dispose(!1,!1):c==this.blockId&&console.warn("Can't delete non-existant block: "+c)}}else a=goog.dom.createDom("xml"),a.appendChild(this.oldXml),Blockly.Xml.domToWorkspace(a,b)};
+Blockly.Events.Change=function(a,b,c,d,e){a&&(Blockly.Events.Change.superClass_.constructor.call(this,a),this.element=b,this.name=c,this.oldValue=d,this.newValue=e)};goog.inherits(Blockly.Events.Change,Blockly.Events.Abstract);Blockly.Events.Change.prototype.type=Blockly.Events.CHANGE;Blockly.Events.Change.prototype.toJson=function(){var a=Blockly.Events.Change.superClass_.toJson.call(this);a.element=this.element;this.name&&(a.name=this.name);a.newValue=this.newValue;return a};
+Blockly.Events.Change.prototype.fromJson=function(a){Blockly.Events.Change.superClass_.fromJson.call(this,a);this.element=a.element;this.name=a.name;this.newValue=a.newValue};Blockly.Events.Change.prototype.isNull=function(){return this.oldValue==this.newValue};
+Blockly.Events.Change.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId);if(b)switch(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,this.element){case "field":(b=b.getField(this.name))?(b.callValidator(a),b.setValue(a)):console.warn("Can't set non-existant field: "+this.name);break;case "comment":b.setCommentText(a||null);break;case "collapsed":b.setCollapsed(a);break;case "disabled":b.setDisabled(a);break;case "inline":b.setInputsInline(a);
+break;case "mutation":var c="";b.mutationToDom&&(c=(c=b.mutationToDom())&&Blockly.Xml.domToText(c));if(b.domToMutation){a=a||"<mutation></mutation>";var d=Blockly.Xml.textToDom("<xml>"+a+"</xml>");b.domToMutation(d.firstChild)}Blockly.Events.fire(new Blockly.Events.Change(b,"mutation",null,c,a));break;default:console.warn("Unknown change type: "+this.element)}else console.warn("Can't change non-existant block: "+this.blockId)};
+Blockly.Events.Typing=function(a,b,c){a&&(Blockly.Events.Change.superClass_.constructor.call(this,a),this.oldValue=b,this.newValue=c)};goog.inherits(Blockly.Events.Typing,Blockly.Events.Abstract);Blockly.Events.Typing.prototype.type=Blockly.Events.TYPING;Blockly.Events.Typing.prototype.toJson=function(){var a=Blockly.Events.Typing.superClass_.toJson.call(this);a.newValue=this.newValue;return a};
+Blockly.Events.Typing.prototype.fromJson=function(a){Blockly.Events.Typing.superClass_.fromJson.call(this,a);this.newValue=a.newValue};Blockly.Events.Typing.prototype.isNull=function(){return!1};
+Blockly.Events.Typing.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId);b&&(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,b.previousConnection?(b.previousConnection.setCheck(a),b.category="CSG"==a?"PRIMITIVE_CSG":"CAG"==a?"PRIMITIVE_CAG":"UNKNOWN"):b.outputConnection&&b.outputConnection.setCheck(a),b.unbacklight())};
+Blockly.Events.Move=function(a){a&&(Blockly.Events.Move.superClass_.constructor.call(this,a),a=this.currentLocation_(),this.oldParentId=a.parentId,this.oldInputName=a.inputName,this.oldCoordinate=a.coordinate)};goog.inherits(Blockly.Events.Move,Blockly.Events.Abstract);Blockly.Events.Move.prototype.type=Blockly.Events.MOVE;
+Blockly.Events.Move.prototype.toJson=function(){var a=Blockly.Events.Move.superClass_.toJson.call(this);this.newParentId&&(a.newParentId=this.newParentId);this.newInputName&&(a.newInputName=this.newInputName);this.newCoordinate&&(a.newCoordinate=Math.round(this.newCoordinate.x)+","+Math.round(this.newCoordinate.y));return a};
+Blockly.Events.Move.prototype.fromJson=function(a){Blockly.Events.Move.superClass_.fromJson.call(this,a);this.newParentId=a.newParentId;this.newInputName=a.newInputName;a.newCoordinate&&(a=a.newCoordinate.split(","),this.newCoordinate=new goog.math.Coordinate(parseFloat(a[0]),parseFloat(a[1])))};Blockly.Events.Move.prototype.recordNew=function(){var a=this.currentLocation_();this.newParentId=a.parentId;this.newInputName=a.inputName;this.newCoordinate=a.coordinate};
+Blockly.Events.Move.prototype.currentLocation_=function(){var a=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId),b={},c=a.getParent();if(c){if(b.parentId=c.id,a=c.getInputWithBlock(a))b.inputName=a.name}else b.coordinate=a.getRelativeToSurfaceXY();return b};Blockly.Events.Move.prototype.isNull=function(){return this.oldParentId==this.newParentId&&this.oldInputName==this.newInputName&&goog.math.Coordinate.equals(this.oldCoordinate,this.newCoordinate)};
+Blockly.Events.Move.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId),c=b.getBlockById(this.blockId);if(c){var d=a?this.newParentId:this.oldParentId,e=a?this.newInputName:this.oldInputName;a=a?this.newCoordinate:this.oldCoordinate;var f=null;if(d&&(f=b.getBlockById(d),!f)){console.warn("Can't connect to non-existant block: "+d);return}c.getParent()&&c.unplug();if(a)e=c.getRelativeToSurfaceXY(),c.moveBy(a.x-e.x,a.y-e.y);else{var c=c.outputConnection||c.previousConnection,
+g;if(e){if(b=f.getInput(e))g=b.connection}else c.type==Blockly.PREVIOUS_STATEMENT&&(g=f.nextConnection);g?c.connect(g):console.warn("Can't connect to non-existant input: "+e)}}else console.warn("Can't move non-existant block: "+this.blockId)};Blockly.Events.Ui=function(a,b,c,d){Blockly.Events.Ui.superClass_.constructor.call(this,a);this.element=b;this.oldValue=c;this.newValue=d;this.recordUndo=!1};goog.inherits(Blockly.Events.Ui,Blockly.Events.Abstract);Blockly.Events.Ui.prototype.type=Blockly.Events.UI;
+Blockly.Events.Ui.prototype.toJson=function(){var a=Blockly.Events.Ui.superClass_.toJson.call(this);a.element=this.element;void 0!==this.newValue&&(a.newValue=this.newValue);return a};Blockly.Events.Ui.prototype.fromJson=function(a){Blockly.Events.Ui.superClass_.fromJson.call(this,a);this.element=a.element;this.newValue=a.newValue};
+Blockly.Events.disableOrphans=function(a){if(a.type==Blockly.Events.MOVE||a.type==Blockly.Events.CREATE){Blockly.Events.disable();if(a=Blockly.Workspace.getById(a.workspaceId).getBlockById(a.blockId))if(a.getParent()&&!a.getParent().disabled){a=a.getDescendants();for(var b=0,c;c=a[b];b++)c.setDisabled(!1)}else if((a.outputConnection||a.previousConnection)&&Blockly.dragMode_==Blockly.DRAG_NONE){do a.setDisabled(!0),a=a.getNextBlock();while(a)}Blockly.Events.enable()}};Blockly.FieldTextInput=function(a,b){Blockly.FieldTextInput.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldTextInput,Blockly.Field);Blockly.FieldTextInput.FONTSIZE=11;Blockly.FieldTextInput.prototype.CURSOR="text";Blockly.FieldTextInput.prototype.spellcheck_=!0;Blockly.FieldTextInput.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldTextInput.superClass_.dispose.call(this)};
+Blockly.FieldTextInput.prototype.setValue=function(a){if(null!==a){if(this.sourceBlock_){var b=this.callValidator(a);null!==b&&(a=b)}Blockly.Field.prototype.setValue.call(this,a)}};Blockly.FieldTextInput.prototype.setSpellcheck=function(a){this.spellcheck_=a};
+Blockly.FieldTextInput.prototype.showEditor_=function(a){this.workspace_=this.sourceBlock_.workspace;a=a||!1;if(!a&&(goog.userAgent.MOBILE||goog.userAgent.ANDROID||goog.userAgent.IPAD))a=window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE,this.text_),this.sourceBlock_&&(a=this.callValidator(a)),this.setValue(a);else{Blockly.WidgetDiv.show(this,this.sourceBlock_.RTL,this.widgetDispose_());var b=Blockly.WidgetDiv.DIV,c=goog.dom.createDom("input","blocklyHtmlInput");c.setAttribute("spellcheck",this.spellcheck_);
+var d=Blockly.FieldTextInput.FONTSIZE*this.workspace_.scale+"pt";b.style.fontSize=d;c.style.fontSize=d;Blockly.FieldTextInput.htmlInput_=c;b.appendChild(c);c.value=c.defaultValue=this.text_;c.oldValue_=null;this.validate_();this.resizeEditor_();a||(c.focus(),c.select());c.onKeyDownWrapper_=Blockly.bindEvent_(c,"keydown",this,this.onHtmlInputKeyDown_);c.onKeyUpWrapper_=Blockly.bindEvent_(c,"keyup",this,this.onHtmlInputChange_);c.onKeyPressWrapper_=Blockly.bindEvent_(c,"keypress",this,this.onHtmlInputChange_);
+c.onWorkspaceChangeWrapper_=this.resizeEditor_.bind(this);this.workspace_.addChangeListener(c.onWorkspaceChangeWrapper_)}};Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_=function(a){var b=Blockly.FieldTextInput.htmlInput_;13==a.keyCode?Blockly.WidgetDiv.hide():27==a.keyCode?(b.value=b.defaultValue,Blockly.WidgetDiv.hide()):9==a.keyCode&&(Blockly.WidgetDiv.hide(),this.sourceBlock_.tab(this,!a.shiftKey),a.preventDefault())};
+Blockly.FieldTextInput.prototype.onHtmlInputChange_=function(a){a=Blockly.FieldTextInput.htmlInput_;var b=a.value;b!==a.oldValue_?(a.oldValue_=b,this.setValue(b),this.validate_()):goog.userAgent.WEBKIT&&this.sourceBlock_.render();this.resizeEditor_();Blockly.svgResize(this.sourceBlock_.workspace)};
+Blockly.FieldTextInput.prototype.validate_=function(){var a=!0;goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_);var b=Blockly.FieldTextInput.htmlInput_;this.sourceBlock_&&(a=this.callValidator(b.value));null===a?Blockly.addClass_(b,"blocklyInvalidInput"):Blockly.removeClass_(b,"blocklyInvalidInput")};
+Blockly.FieldTextInput.prototype.resizeEditor_=function(){var a=Blockly.WidgetDiv.DIV,b=this.fieldGroup_.getBBox();a.style.width=b.width*this.workspace_.scale+"px";a.style.height=b.height*this.workspace_.scale+"px";b=this.getAbsoluteXY_();if(this.sourceBlock_.RTL){var c=this.getScaledBBox_();b.x+=c.width;b.x-=a.offsetWidth}b.y+=1;goog.userAgent.GECKO&&Blockly.WidgetDiv.DIV.style.top&&(--b.x,--b.y);goog.userAgent.WEBKIT&&(b.y-=3);a.style.left=b.x+"px";a.style.top=b.y+"px"};
+Blockly.FieldTextInput.prototype.widgetDispose_=function(){var a=this;return function(){var b=Blockly.FieldTextInput.htmlInput_,c=b.value;a.sourceBlock_&&(c=a.callValidator(c),c=null===c?b.defaultValue:c);a.setValue(c);a.sourceBlock_.rendered&&a.sourceBlock_.render();Blockly.unbindEvent_(b.onKeyDownWrapper_);Blockly.unbindEvent_(b.onKeyUpWrapper_);Blockly.unbindEvent_(b.onKeyPressWrapper_);a.workspace_.removeChangeListener(b.onWorkspaceChangeWrapper_);Blockly.FieldTextInput.htmlInput_=null;b=Blockly.WidgetDiv.DIV.style;
+b.width="auto";b.height="auto";b.fontSize=""}};Blockly.FieldTextInput.numberValidator=function(a){console.warn("Blockly.FieldTextInput.numberValidator is deprecated. Use Blockly.FieldNumber instead.");if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=parseFloat(a||0);return isNaN(a)?null:String(a)};Blockly.FieldTextInput.nonnegativeIntegerValidator=function(a){(a=Blockly.FieldTextInput.numberValidator(a))&&(a=String(Math.max(0,Math.floor(a))));return a};Blockly.FieldAngle=function(a,b){this.symbol_=Blockly.createSvgElement("tspan",{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));Blockly.FieldAngle.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldAngle,Blockly.FieldTextInput);Blockly.FieldAngle.ROUND=15;Blockly.FieldAngle.HALF=50;Blockly.FieldAngle.CLOCKWISE=!1;Blockly.FieldAngle.OFFSET=0;Blockly.FieldAngle.WRAP=360;Blockly.FieldAngle.RADIUS=Blockly.FieldAngle.HALF-1;
+Blockly.FieldAngle.prototype.dispose_=function(){var a=this;return function(){Blockly.FieldAngle.superClass_.dispose_.call(a)();a.gauge_=null;a.clickWrapper_&&Blockly.unbindEvent_(a.clickWrapper_);a.moveWrapper1_&&Blockly.unbindEvent_(a.moveWrapper1_);a.moveWrapper2_&&Blockly.unbindEvent_(a.moveWrapper2_)}};
+Blockly.FieldAngle.prototype.showEditor_=function(){Blockly.FieldAngle.superClass_.showEditor_.call(this,goog.userAgent.MOBILE||goog.userAgent.ANDROID||goog.userAgent.IPAD);var a=Blockly.WidgetDiv.DIV;if(a.firstChild){var a=Blockly.createSvgElement("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:html":"http://www.w3.org/1999/xhtml","xmlns:xlink":"http://www.w3.org/1999/xlink",version:"1.1",height:2*Blockly.FieldAngle.HALF+"px",width:2*Blockly.FieldAngle.HALF+"px"},a),b=Blockly.createSvgElement("circle",
+{cx:Blockly.FieldAngle.HALF,cy:Blockly.FieldAngle.HALF,r:Blockly.FieldAngle.RADIUS,"class":"blocklyAngleCircle"},a);this.gauge_=Blockly.createSvgElement("path",{"class":"blocklyAngleGauge"},a);this.line_=Blockly.createSvgElement("line",{x1:Blockly.FieldAngle.HALF,y1:Blockly.FieldAngle.HALF,"class":"blocklyAngleLine"},a);for(var c=0;360>c;c+=15)Blockly.createSvgElement("line",{x1:Blockly.FieldAngle.HALF+Blockly.FieldAngle.RADIUS,y1:Blockly.FieldAngle.HALF,x2:Blockly.FieldAngle.HALF+Blockly.FieldAngle.RADIUS-
+(0==c%45?10:5),y2:Blockly.FieldAngle.HALF,"class":"blocklyAngleMarks",transform:"rotate("+c+","+Blockly.FieldAngle.HALF+","+Blockly.FieldAngle.HALF+")"},a);a.style.marginLeft=15-Blockly.FieldAngle.RADIUS+"px";this.clickWrapper_=Blockly.bindEvent_(a,"click",this,Blockly.WidgetDiv.hide);this.moveWrapper1_=Blockly.bindEvent_(b,"mousemove",this,this.onMouseMove);this.moveWrapper2_=Blockly.bindEvent_(this.gauge_,"mousemove",this,this.onMouseMove);this.updateGraph_()}};
+Blockly.FieldAngle.prototype.onMouseMove=function(a){var b=this.gauge_.ownerSVGElement.getBoundingClientRect(),c=a.clientX-b.left-Blockly.FieldAngle.HALF;a=a.clientY-b.top-Blockly.FieldAngle.HALF;b=Math.atan(-a/c);isNaN(b)||(b=goog.math.toDegrees(b),0>c?b+=180:0<a&&(b+=360),b=Blockly.FieldAngle.CLOCKWISE?Blockly.FieldAngle.OFFSET+360-b:b-Blockly.FieldAngle.OFFSET,Blockly.FieldAngle.ROUND&&(b=Math.round(b/Blockly.FieldAngle.ROUND)*Blockly.FieldAngle.ROUND),b=this.callValidator(b),Blockly.FieldTextInput.htmlInput_.value=
+b,this.setValue(b),this.validate_(),this.resizeEditor_())};Blockly.FieldAngle.prototype.setText=function(a){Blockly.FieldAngle.superClass_.setText.call(this,a);this.textElement_&&(this.updateGraph_(),this.sourceBlock_.RTL?this.textElement_.insertBefore(this.symbol_,this.textElement_.firstChild):this.textElement_.appendChild(this.symbol_),this.size_.width=0)};
+Blockly.FieldAngle.prototype.updateGraph_=function(){if(this.gauge_){var a=Number(this.getText())+Blockly.FieldAngle.OFFSET,b=goog.math.toRadians(a),a=["M ",Blockly.FieldAngle.HALF,",",Blockly.FieldAngle.HALF],c=Blockly.FieldAngle.HALF,d=Blockly.FieldAngle.HALF;if(!isNaN(b)){var e=goog.math.toRadians(Blockly.FieldAngle.OFFSET),f=Math.cos(e)*Blockly.FieldAngle.RADIUS,g=Math.sin(e)*-Blockly.FieldAngle.RADIUS;Blockly.FieldAngle.CLOCKWISE&&(b=2*e-b);c+=Math.cos(b)*Blockly.FieldAngle.RADIUS;d-=Math.sin(b)*
+Blockly.FieldAngle.RADIUS;b=Math.abs(Math.floor((b-e)/Math.PI)%2);Blockly.FieldAngle.CLOCKWISE&&(b=1-b);a.push(" l ",f,",",g," A ",Blockly.FieldAngle.RADIUS,",",Blockly.FieldAngle.RADIUS," 0 ",b," ",Number(Blockly.FieldAngle.CLOCKWISE)," ",c,",",d," z")}this.gauge_.setAttribute("d",a.join(""));this.line_.setAttribute("x2",c);this.line_.setAttribute("y2",d)}};
+Blockly.FieldAngle.prototype.classValidator=function(a){if(null===a)return null;a=parseFloat(a||0);if(isNaN(a))return null;a%=360;0>a&&(a+=360);a>Blockly.FieldAngle.WRAP&&(a-=360);return String(a)};var sz=25;Blockly.FieldCheckbox=function(a,b,c,d){Blockly.FieldCheckbox.superClass_.constructor.call(this,"",b);c&&d&&(this.img1=c,this.img2=d,this.size_=new goog.math.Size(sz,1.1*sz));this.setValue(a)};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.prototype.CURSOR="default";Blockly.FieldCheckbox.prototype.EDITABLE=!0;
+Blockly.FieldCheckbox.prototype.init=function(){if(!this.fieldGroup_)if(this.img1&&this.img2)this.fieldGroup_=Blockly.createSvgElement("g",{},null),this.visible_||(this.fieldGroup_.style.display="none"),this.imageElement_=Blockly.createSvgElement("image",{height:sz+"px",width:1.1*sz+"px",x:5,y:-5},this.fieldGroup_),this.imageElement_.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",this.state_?this.img1:this.img2),this.updateEditable(),this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_),
+this.mouseUpWrapper_=Blockly.bindEvent_(this.fieldGroup_,"mouseup",this,this.onMouseUp_);else{Blockly.FieldCheckbox.superClass_.init.call(this);this.checkElement_=Blockly.createSvgElement("text",{"class":"blocklyText blocklyCheckbox",x:-3,y:14},this.fieldGroup_);var a=document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);this.checkElement_.appendChild(a);this.checkElement_.style.display=this.state_?"block":"none"}};Blockly.FieldCheckbox.prototype.getValue=function(){return String(this.state_).toUpperCase()};
+Blockly.FieldCheckbox.prototype.setValue=function(a){a="TRUE"==a;this.state_!==a&&(this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.state_,a)),this.state_=a,this.checkElement_?this.checkElement_.style.display=a?"block":"none":this.imageElement_&&this.imageElement_.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",a?this.img1:this.img2))};
+Blockly.FieldCheckbox.prototype.showEditor_=function(){var a=!this.state_;this.sourceBlock_&&(a=this.callValidator(a));null!==a&&this.setValue(String(a).toUpperCase())};Blockly.SIMPLE_GRID_COLORS="#ffffff #cccccc #c0c0c0 #999999 #666666 #333333 #000000 #ffcccc #ff6666 #ee0000 #cc0000 #990000 #660000 #330000 #ffcc99 #ff9966 #ff9900 #ff6600 #cc6600 #993300 #663300 #ffff99 #ffff66 #ffcc66 #ffcc33 #cc9933 #996633 #663333 #e2c48c #ffff33 #ffff00 #ffcc00 #999900 #666600 #333300 #99ff99 #66ff99 #33ff33 #33cc00 #009900 #006600 #003300 #99ffff #33ffff #66cccc #00cccc #339999 #336666 #003333 #ccffff #66ffff #33ccff #3366ff #3333ff #000099 #000066 #ccccff #9999ff #6666cc #6633ff #6600cc #333399 #330099 #ffccff #ff80ff #cc66cc #cc33cc #993399 #663366 #330033".split(" ");
+Blockly.FieldColour=function(a,b){Blockly.FieldColour.superClass_.constructor.call(this,a,b);this.setText(Blockly.Field.NBSP+Blockly.Field.NBSP+Blockly.Field.NBSP)};goog.inherits(Blockly.FieldColour,Blockly.Field);Blockly.FieldColour.prototype.colours_=null;Blockly.FieldColour.prototype.columns_=0;Blockly.FieldColour.prototype.init=function(){Blockly.FieldColour.superClass_.init.call(this);this.borderRect_.style.fillOpacity=1;this.setValue(this.getValue())};Blockly.FieldColour.prototype.CURSOR="default";
+Blockly.FieldColour.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldColour.superClass_.dispose.call(this)};Blockly.FieldColour.prototype.getValue=function(){return this.colour_};Blockly.FieldColour.prototype.setValue=function(a){this.sourceBlock_&&Blockly.Events.isEnabled()&&this.colour_!=a&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.colour_,a));this.colour_=a;this.borderRect_&&(this.borderRect_.style.fill=a)};
+Blockly.FieldColour.prototype.getText=function(){var a=this.colour_,b=a.match(/^#(.)\1(.)\2(.)\3$/);b&&(a="#"+b[1]+b[2]+b[3]);return a};Blockly.FieldColour.COLOURS=Blockly.SIMPLE_GRID_COLORS;Blockly.FieldColour.COLUMNS=7;Blockly.FieldColour.prototype.setColours=function(a){this.colours_=a;return this};Blockly.FieldColour.prototype.setColumns=function(a){this.columns_=a;return this};
+Blockly.FieldColour.prototype.showEditor_=function(){Blockly.WidgetDiv.show(this,this.sourceBlock_.RTL,Blockly.FieldColour.widgetDispose_);var a=new goog.ui.ColorPicker;a.setSize(this.columns_||Blockly.FieldColour.COLUMNS);a.setColors(this.colours_||Blockly.FieldColour.COLOURS);var b=goog.dom.getViewportSize(),c=goog.style.getViewportPageOffset(document),d=this.getAbsoluteXY_(),e=this.getScaledBBox_();a.render(Blockly.WidgetDiv.DIV);a.setSelectedColor(this.getValue());var f=goog.style.getSize(a.getElement());
+d.y=d.y+f.height+e.height>=b.height+c.y?d.y-(f.height-1):d.y+(e.height-1);this.sourceBlock_.RTL?(d.x+=e.width,d.x-=f.width,d.x<c.x&&(d.x=c.x)):d.x>b.width+c.x-f.width&&(d.x=b.width+c.x-f.width);Blockly.WidgetDiv.position(d.x,d.y,b,c,this.sourceBlock_.RTL);var g=this;Blockly.FieldColour.changeEventKey_=goog.events.listen(a,goog.ui.ColorPicker.EventType.CHANGE,function(a){a=a.target.getSelectedColor()||"#000000";Blockly.WidgetDiv.hide();g.sourceBlock_&&(a=g.callValidator(a));null!==a&&g.setValue(a)})};
+Blockly.FieldColour.widgetDispose_=function(){Blockly.FieldColour.changeEventKey_&&goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_)};Blockly.FieldDropdown=function(a,b){this.menuGenerator_=a;this.trimOptions_();var c=this.getOptions_()[0];Blockly.FieldDropdown.superClass_.constructor.call(this,c[1],b)};goog.inherits(Blockly.FieldDropdown,Blockly.Field);Blockly.FieldDropdown.CHECKMARK_OVERHANG=25;Blockly.FieldDropdown.ARROW_CHAR=goog.userAgent.ANDROID?"\u25bc":"\u25be";Blockly.FieldDropdown.prototype.CURSOR="default";
+Blockly.FieldDropdown.prototype.init=function(){if(!this.fieldGroup_){this.arrow_=Blockly.createSvgElement("tspan",{},null);this.arrow_.appendChild(document.createTextNode(this.sourceBlock_.RTL?Blockly.FieldDropdown.ARROW_CHAR+" ":" "+Blockly.FieldDropdown.ARROW_CHAR));Blockly.FieldDropdown.superClass_.init.call(this);var a=this.text_;this.text_=null;this.setText(a)}};
+Blockly.FieldDropdown.prototype.showEditor_=function(){Blockly.WidgetDiv.show(this,this.sourceBlock_.RTL,null);var a=this,b=new goog.ui.Menu;b.setRightToLeft(this.sourceBlock_.RTL);for(var c=this.getOptions_(),d=0;d<c.length;d++){var e=c[d][1],f=new goog.ui.MenuItem(c[d][0]);f.setRightToLeft(this.sourceBlock_.RTL);f.setValue(e);f.setCheckable(!0);b.addChild(f,!0);f.setChecked(e==this.value_)}goog.events.listen(b,goog.ui.Component.EventType.ACTION,function(b){if(b=b.target)b=b.getValue(),a.sourceBlock_&&
+(b=a.callValidator(b)),null!==b&&a.setValue(b);Blockly.WidgetDiv.hideIfOwner(a)});b.getHandler().listen(b.getElement(),goog.events.EventType.TOUCHSTART,function(a){this.getOwnerControl(a.target).handleMouseDown(a)});b.getHandler().listen(b.getElement(),goog.events.EventType.TOUCHEND,function(a){this.getOwnerControl(a.target).performActionInternal(a)});c=goog.dom.getViewportSize();d=goog.style.getViewportPageOffset(document);e=this.getAbsoluteXY_();f=this.getScaledBBox_();b.render(Blockly.WidgetDiv.DIV);
+var g=b.getElement();Blockly.addClass_(g,"blocklyDropdownMenu");var h=goog.style.getSize(g);h.height=g.scrollHeight;e.y=e.y+h.height+f.height>=c.height+d.y?e.y-(h.height+2):e.y+f.height;this.sourceBlock_.RTL?(e.x+=f.width,e.x+=Blockly.FieldDropdown.CHECKMARK_OVERHANG,e.x<d.x+h.width&&(e.x=d.x+h.width)):(e.x-=Blockly.FieldDropdown.CHECKMARK_OVERHANG,e.x>c.width+d.x-h.width&&(e.x=c.width+d.x-h.width));Blockly.WidgetDiv.position(e.x,e.y,c,d,this.sourceBlock_.RTL);b.setAllowAutoFocus(!0);g.focus()};
+Blockly.FieldDropdown.prototype.trimOptions_=function(){this.suffixField=this.prefixField=null;var a=this.menuGenerator_;if(goog.isArray(a)&&!(2>a.length)){var b=a.map(function(a){return a[0]}),c=Blockly.shortestStringLength(b),d=Blockly.commonWordPrefix(b,c),e=Blockly.commonWordSuffix(b,c);if((d||e)&&!(c<=d+e)){d&&(this.prefixField=b[0].substring(0,d-1));e&&(this.suffixField=b[0].substr(1-e));b=[];for(c=0;c<a.length;c++){var f=a[c][0],g=a[c][1],f=f.substring(d,f.length-e);b[c]=[f,g]}this.menuGenerator_=
+b}}};Blockly.FieldDropdown.prototype.getOptions_=function(){return goog.isFunction(this.menuGenerator_)?this.menuGenerator_.call(this):this.menuGenerator_};Blockly.FieldDropdown.prototype.getValue=function(){return this.value_};
+Blockly.FieldDropdown.prototype.setValue=function(a){if(null!==a&&a!==this.value_){this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.value_,a));this.value_=a;for(var b=this.getOptions_(),c=0;c<b.length;c++)if(b[c][1]==a){this.setText(b[c][0]);return}this.setText(a)}};
+Blockly.FieldDropdown.prototype.setText=function(a){this.sourceBlock_&&this.arrow_&&(this.arrow_.style.fill=this.sourceBlock_.getColour());null!==a&&a!==this.text_&&(this.text_=a,this.updateTextNode_(),this.textElement_&&(this.sourceBlock_.RTL?this.textElement_.insertBefore(this.arrow_,this.textElement_.firstChild):this.textElement_.appendChild(this.arrow_)),this.sourceBlock_&&this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_()))};
+Blockly.FieldDropdown.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldDropdown.superClass_.dispose.call(this)};Blockly.FieldImage=function(a,b,c,d){this.sourceBlock_=null;this.height_=Number(c);this.width_=Number(b);this.size_=new goog.math.Size(this.width_,this.height_+2*Blockly.BlockSvg.INLINE_PADDING_Y);this.text_=d||"";this.setValue(a)};goog.inherits(Blockly.FieldImage,Blockly.Field);Blockly.FieldImage.prototype.rectElement_=null;Blockly.FieldImage.prototype.EDITABLE=!1;
+Blockly.FieldImage.prototype.init=function(){if(!this.fieldGroup_){this.fieldGroup_=Blockly.createSvgElement("g",{},null);this.visible_||(this.fieldGroup_.style.display="none");this.imageElement_=Blockly.createSvgElement("image",{height:this.height_+"px",width:this.width_+"px"},this.fieldGroup_);this.setValue(this.src_);goog.userAgent.GECKO&&(this.rectElement_=Blockly.createSvgElement("rect",{height:this.height_+"px",width:this.width_+"px","fill-opacity":0},this.fieldGroup_));this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
+var a=this.rectElement_||this.imageElement_;a.tooltip=this.sourceBlock_;Blockly.Tooltip.bindMouseEvents(a)}};Blockly.FieldImage.prototype.dispose=function(){goog.dom.removeNode(this.fieldGroup_);this.rectElement_=this.imageElement_=this.fieldGroup_=null};Blockly.FieldImage.prototype.setTooltip=function(a){(this.rectElement_||this.imageElement_).tooltip=a};Blockly.FieldImage.prototype.getValue=function(){return this.src_};
+Blockly.FieldImage.prototype.setValue=function(a){null!==a&&(this.src_=a,this.imageElement_&&this.imageElement_.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",goog.isString(a)?a:""))};Blockly.FieldImage.prototype.setText=function(a){null!==a&&(this.text_=a)};Blockly.FieldImage.prototype.render_=function(){};Blockly.FieldNumber=function(a,b,c,d,e){a=String(a);Blockly.FieldNumber.superClass_.constructor.call(this,a,e);this.setConstraints(b,c,d)};goog.inherits(Blockly.FieldNumber,Blockly.FieldTextInput);Blockly.FieldNumber.prototype.setConstraints=function(a,b,c){c=parseFloat(c);this.precision_=isNaN(c)?0:c;a=parseFloat(a);this.min_=isNaN(a)?-Infinity:a;b=parseFloat(b);this.max_=isNaN(b)?Infinity:b;this.setValue(this.callValidator(this.getValue()))};
+Blockly.FieldNumber.prototype.classValidator=function(a){if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=parseFloat(a||0);if(isNaN(a))return null;this.precision_&&Number.isFinite(a)&&(a=Math.round(a/this.precision_)*this.precision_);a=goog.math.clamp(a,this.min_,this.max_);return String(a)};Blockly.Variables={};Blockly.Variables.NAME_TYPE="VARIABLE";Blockly.Variables.allVariables=function(a){var b;if(a.getDescendants)b=a.getDescendants();else if(a.getAllBlocks)b=a.getAllBlocks();else throw"Not Block or Workspace: "+a;a=Object.create(null);for(var c=0;c<b.length;c++)for(var d=b[c].getVars(),e=0;e<d.length;e++){var f=d[e];f&&(a[f.toLowerCase()]=f)}b=[];for(var g in a)b.push(a[g]);return b};
+Blockly.Variables.renameVariable=function(a,b,c){Blockly.Events.setGroup(!0);c=c.getAllBlocks();for(var d=0;d<c.length;d++)c[d].renameVar(a,b);Blockly.Events.setGroup(!1)};Blockly.Variables.getInstances=function(a,b){if(b){for(var c=b.getAllBlocks(),d=[],e=0;e<c.length;e++)c[e].getFieldValue("VAR")&&c[e].getFieldValue("VAR")==a&&d.push(c[e]);return d.length?d:null}};
+Blockly.Variables.flyoutCategory=function(a){a=Blockly.Variables.allVariables(a);a.sort(goog.string.caseInsensitiveCompare);goog.array.remove(a,Blockly.Msg.VARIABLES_DEFAULT_NAME);a.unshift(Blockly.Msg.VARIABLES_DEFAULT_NAME);for(var b=[],c=0;c<a.length;c++){if(Blockly.Blocks.variables_set){var d=goog.dom.createDom("block");d.setAttribute("type","variables_set");Blockly.Blocks.variables_get&&d.setAttribute("gap",8);var e=goog.dom.createDom("field",null,a[c]);e.setAttribute("name","VAR");d.appendChild(e);
+b.push(d)}Blockly.Blocks.variables_get&&(d=goog.dom.createDom("block"),d.setAttribute("type","variables_get"),Blockly.Blocks.variables_set&&d.setAttribute("gap",24),e=goog.dom.createDom("field",null,a[c]),e.setAttribute("name","VAR"),d.appendChild(e),b.push(d))}return b};
+Blockly.Variables.generateUniqueName=function(a){a=Blockly.Variables.allVariables(a);var b="";if(a.length)for(var c=1,d=0,e="ijkmnopqrstuvwxyzabcdefgh".charAt(d);!b;){for(var f=!1,g=0;g<a.length;g++)if(a[g].toLowerCase()==e){f=!0;break}f?(d++,25==d&&(d=0,c++),e="ijkmnopqrstuvwxyzabcdefgh".charAt(d),1<c&&(e+=c)):b=e}else b="i";return b};Blockly.FieldVariable=function(a,b){Blockly.FieldVariable.superClass_.constructor.call(this,Blockly.FieldVariable.dropdownCreate,b);this.setValue(a||"")};goog.inherits(Blockly.FieldVariable,Blockly.FieldDropdown);Blockly.FieldVariable.prototype.init=function(){this.fieldGroup_||(Blockly.FieldVariable.superClass_.init.call(this),this.getValue()||this.setValue(Blockly.Variables.generateUniqueName(this.sourceBlock_.isInFlyout?this.sourceBlock_.workspace.targetWorkspace:this.sourceBlock_.workspace)))};
+Blockly.FieldVariable.prototype.getValue=function(){return this.getText()};Blockly.FieldVariable.prototype.setValue=function(a){this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.value_,a));this.value_=a;this.setText(a)};
+Blockly.FieldVariable.dropdownCreate=function(){var a=this.sourceBlock_&&this.sourceBlock_.workspace?Blockly.Variables.allVariables(this.sourceBlock_.workspace):[],b=this.getText();b&&-1==a.indexOf(b)&&a.push(b);a.sort(goog.string.caseInsensitiveCompare);a.push(Blockly.Msg.RENAME_VARIABLE);a.push(Blockly.Msg.NEW_VARIABLE);for(var b=[],c=0;c<a.length;c++)b[c]=[a[c],a[c]];return b};
+Blockly.FieldVariable.prototype.classValidator=function(a){function b(a,b){Blockly.hideChaff();var c=window.prompt(a,b);c&&(c=c.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,""),c==Blockly.Msg.RENAME_VARIABLE||c==Blockly.Msg.NEW_VARIABLE)&&(c=null);return c}var c=this.sourceBlock_.workspace;if(a==Blockly.Msg.RENAME_VARIABLE){var d=this.getText();(a=b(Blockly.Msg.RENAME_VARIABLE_TITLE.replace("%1",d),d))&&Blockly.Variables.renameVariable(d,a,c);return null}if(a==Blockly.Msg.NEW_VARIABLE)return(a=b(Blockly.Msg.NEW_VARIABLE_TITLE,
+""))?(Blockly.Variables.renameVariable(a,a,c),a):null};Blockly.Generator=function(a){this.name_=a;this.FUNCTION_NAME_PLACEHOLDER_REGEXP_=new RegExp(this.FUNCTION_NAME_PLACEHOLDER_,"g")};Blockly.Generator.NAME_TYPE="generated_function";Blockly.Generator.prototype.INFINITE_LOOP_TRAP=null;Blockly.Generator.prototype.STATEMENT_PREFIX=null;Blockly.Generator.prototype.INDENT="  ";Blockly.Generator.prototype.COMMENT_WRAP=60;Blockly.Generator.prototype.ORDER_OVERRIDES=[];
+Blockly.Generator.prototype.workspaceToCode=function(a){a||(console.warn("No workspace specified in workspaceToCode call.  Guessing."),a=Blockly.getMainWorkspace());var b=[];this.init(a);a=a.getTopBlocks(!0);for(var c=0,d;d=a[c];c++){var e=this.blockToCode(d);goog.isArray(e)&&(e=e[0]);e&&(d.outputConnection&&this.scrubNakedValue&&(e=this.scrubNakedValue(e)),b.push(e))}b=b.join("\n");b=this.finish(b);b=b.replace(/^\s+\n/,"");b=b.replace(/\n\s+$/,"\n");return b=b.replace(/[ \t]+\n/g,"\n")};
+Blockly.Generator.prototype.prefixLines=function(a,b){return b+a.replace(/(?!\n$)\n/g,"\n"+b)};Blockly.Generator.prototype.allNestedComments=function(a){var b=[];a=a.getDescendants();for(var c=0;c<a.length;c++){var d=a[c].getCommentText();d&&b.push(d)}b.length&&b.push("");return b.join("\n")};
+Blockly.Generator.prototype.blockToCode=function(a){if(!a)return"";if(a.disabled)return this.blockToCode(a.getNextBlock());var b=this[a.type];goog.asserts.assertFunction(b,'Language "%s" does not know how to generate code for block type "%s".',this.name_,a.type);b=b.call(a,a);if(goog.isArray(b))return goog.asserts.assert(a.outputConnection,'Expecting string from statement block "%s".',a.type),[this.scrub_(a,b[0]),b[1]];if(goog.isString(b))return this.STATEMENT_PREFIX&&(b=this.STATEMENT_PREFIX.replace(/%1/g,
+"'"+a.id+"'")+b),this.scrub_(a,b);if(null===b)return"";goog.asserts.fail("Invalid code generated: %s",b)};
+Blockly.Generator.prototype.valueToCode=function(a,b,c){isNaN(c)&&goog.asserts.fail('Expecting valid order from block "%s".',a.type);var d=a.getInputTargetBlock(b);if(!d)return"";b=this.blockToCode(d);if(""===b)return"";goog.asserts.assertArray(b,'Expecting tuple from value block "%s".',d.type);a=b[0];b=b[1];isNaN(b)&&goog.asserts.fail('Expecting valid order from value block "%s".',d.type);if(!a)return"";var d=!1,e=Math.floor(c),f=Math.floor(b);if(e<=f&&(e!=f||0!=e&&99!=e))for(d=!0,e=0;e<this.ORDER_OVERRIDES.length;e++)if(this.ORDER_OVERRIDES[e][0]==
+c&&this.ORDER_OVERRIDES[e][1]==b){d=!1;break}d&&(a="("+a+")");return a};Blockly.Generator.prototype.statementToCode=function(a,b){var c=a.getInputTargetBlock(b),d=this.blockToCode(c);goog.asserts.assertString(d,'Expecting code from statement block "%s".',c&&c.type);d&&(d=this.prefixLines(d,this.INDENT));return d};
+Blockly.Generator.prototype.addLoopTrap=function(a,b){this.INFINITE_LOOP_TRAP&&(a=this.INFINITE_LOOP_TRAP.replace(/%1/g,"'"+b+"'")+a);this.STATEMENT_PREFIX&&(a+=this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g,"'"+b+"'"),this.INDENT));return a};Blockly.Generator.prototype.RESERVED_WORDS_="";Blockly.Generator.prototype.addReservedWords=function(a){this.RESERVED_WORDS_+=a+","};Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_="{leCUI8hutHZI4480Dc}";
+Blockly.Generator.prototype.provideFunction_=function(a,b){if(!this.definitions_[a]){var c=this.variableDB_.getDistinctName(a,Blockly.Procedures.NAME_TYPE);this.functionNames_[a]=c;for(var c=b.join("\n").replace(this.FUNCTION_NAME_PLACEHOLDER_REGEXP_,c),d;d!=c;)d=c,c=c.replace(/^((  )*)  /gm,"$1"+this.INDENT);this.definitions_[a]=c}return this.functionNames_[a]};Blockly.Names=function(a,b){this.variablePrefix_=b||"";this.reservedDict_=Object.create(null);if(a)for(var c=a.split(","),d=0;d<c.length;d++)this.reservedDict_[c[d]]=!0;this.reset()};Blockly.Names.prototype.reset=function(){this.db_=Object.create(null);this.dbReverse_=Object.create(null)};
+Blockly.Names.prototype.getName=function(a,b){var c=a.toLowerCase()+"_"+b,d=b==Blockly.Variables.NAME_TYPE?this.variablePrefix_:"";if(c in this.db_)return d+this.db_[c];var e=this.getDistinctName(a,b);this.db_[c]=e.substr(d.length);return e};Blockly.Names.prototype.getDistinctName=function(a,b){for(var c=this.safeName_(a),d="";this.dbReverse_[c+d]||c+d in this.reservedDict_;)d=d?d+1:2;c+=d;this.dbReverse_[c]=!0;return(b==Blockly.Variables.NAME_TYPE?this.variablePrefix_:"")+c};
+Blockly.Names.prototype.safeName_=function(a){a?(a=encodeURI(a.replace(/ /g,"_")).replace(/[^\w]/g,"_"),-1!="0123456789".indexOf(a[0])&&(a="my_"+a)):a="unnamed";return a};Blockly.Names.equals=function(a,b){return a.toLowerCase()==b.toLowerCase()};Blockly.Procedures={};Blockly.Procedures.NAME_TYPE="PROCEDURE";Blockly.Procedures.allProcedures=function(a){a=a.getAllBlocks();for(var b=[],c=[],d=0;d<a.length;d++)if(a[d].getProcedureDef){var e=a[d].getProcedureDef();e&&(e[2]?b.push(e):c.push(e))}c.sort(Blockly.Procedures.procTupleComparator_);b.sort(Blockly.Procedures.procTupleComparator_);return[c,b]};Blockly.Procedures.procTupleComparator_=function(a,b){return a[0].toLowerCase().localeCompare(b[0].toLowerCase())};
+Blockly.Procedures.findLegalName=function(a,b){if(b.isInFlyout)return a;for(;!Blockly.Procedures.isLegalName_(a,b.workspace,b);){var c=a.match(/^(.*?)(\d+)$/);a=c?c[1]+(parseInt(c[2],10)+1):a+"2"}return a};Blockly.Procedures.isLegalName_=function(a,b,c){b=b.getAllBlocks();for(var d=0;d<b.length;d++)if(b[d]!=c&&b[d].getProcedureDef){var e=b[d].getProcedureDef();if(Blockly.Names.equals(e[0],a))return!1}return!0};
+Blockly.Procedures.rename=function(a){a=a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"");var b=Blockly.Procedures.findLegalName(a,this.sourceBlock_),c=this.text_;if(c!=a&&c!=b){a=this.sourceBlock_.workspace.getAllBlocks();for(var d=0;d<a.length;d++)a[d].renameProcedure&&a[d].renameProcedure(c,b)}return b};
+Blockly.Procedures.flyoutCategory=function(a){function b(a,b){for(var d=0;d<a.length;d++){var e=a[d][0],f=a[d][1],l=goog.dom.createDom("block");l.setAttribute("type",b);l.setAttribute("gap",16);var p=goog.dom.createDom("mutation");p.setAttribute("name",e);l.appendChild(p);for(e=0;e<f.length;e++){var m=goog.dom.createDom("arg");m.setAttribute("name",f[e]);p.appendChild(m)}c.push(l)}}var c=[];if(Blockly.Blocks.procedures_defnoreturn){var d=goog.dom.createDom("block");d.setAttribute("type","procedures_defnoreturn");
+d.setAttribute("gap",16);c.push(d)}Blockly.Blocks.procedures_defreturn&&(d=goog.dom.createDom("block"),d.setAttribute("type","procedures_defreturn"),d.setAttribute("gap",16),c.push(d));Blockly.Blocks.procedures_ifreturn&&(d=goog.dom.createDom("block"),d.setAttribute("type","procedures_ifreturn"),d.setAttribute("gap",16),c.push(d));c.length&&c[c.length-1].setAttribute("gap",24);a=Blockly.Procedures.allProcedures(a);b(a[0],"procedures_callnoreturn");b(a[1],"procedures_callreturn");return c};
+Blockly.Procedures.getCallers=function(a,b){for(var c=[],d=b.getAllBlocks(),e=0;e<d.length;e++)if(d[e].getProcedureCall){var f=d[e].getProcedureCall();f&&Blockly.Names.equals(f,a)&&c.push(d[e])}return c};
+Blockly.Procedures.mutateCallers=function(a){var b=Blockly.Events.recordUndo,c=a.getProcedureDef()[0],d=a.mutationToDom(!0);a=Blockly.Procedures.getCallers(c,a.workspace);for(var c=0,e;e=a[c];c++){var f=e.mutationToDom(),f=f&&Blockly.Xml.domToText(f);e.domToMutation(d);var g=e.mutationToDom(),g=g&&Blockly.Xml.domToText(g);f!=g&&(Blockly.Events.recordUndo=!1,Blockly.Events.fire(new Blockly.Events.Change(e,"mutation",null,f,g)),Blockly.Events.recordUndo=b)}};
+Blockly.Procedures.getDefinition=function(a,b){for(var c=b.getTopBlocks(!1),d=0;d<c.length;d++)if(c[d].getProcedureDef){var e=c[d].getProcedureDef();if(e&&Blockly.Names.equals(e[0],a))return c[d]}return null};Blockly.Flyout=function(a){a.getMetrics=this.getMetrics_.bind(this);a.setMetrics=this.setMetrics_.bind(this);this.workspace_=new Blockly.WorkspaceSvg(a);this.workspace_.isFlyout=!0;this.RTL=!!a.RTL;this.horizontalLayout_=a.horizontalLayout;this.toolboxPosition_=a.toolboxPosition;this.eventWrappers_=[];this.buttons_=[];this.listeners_=[];this.permanentlyDisabled_=[]};Blockly.Flyout.prototype.autoClose=!0;Blockly.Flyout.prototype.CORNER_RADIUS=8;Blockly.Flyout.prototype.DRAG_RADIUS=10;
+Blockly.Flyout.prototype.MARGIN=Blockly.Flyout.prototype.CORNER_RADIUS;Blockly.Flyout.prototype.SCROLLBAR_PADDING=2;Blockly.Flyout.prototype.width_=0;Blockly.Flyout.prototype.height_=0;Blockly.Flyout.prototype.dragMode_=Blockly.DRAG_NONE;Blockly.Flyout.prototype.dragAngleRange_=80;
+Blockly.Flyout.prototype.createDom=function(){this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyFlyout"},null);this.svgBackground_=Blockly.createSvgElement("path",{"class":"blocklyFlyoutBackground"},this.svgGroup_);this.svgGroup_.appendChild(this.workspace_.createDom());return this.svgGroup_};
+Blockly.Flyout.prototype.init=function(a){this.targetWorkspace_=a;this.workspace_.targetWorkspace=a;this.scrollbar_=new Blockly.Scrollbar(this.workspace_,this.horizontalLayout_,!1);this.hide();Array.prototype.push.apply(this.eventWrappers_,Blockly.bindEvent_(this.svgGroup_,"wheel",this,this.wheel_));this.autoClose||(this.filterWrapper_=this.filterForCapacity_.bind(this),this.targetWorkspace_.addChangeListener(this.filterWrapper_));Array.prototype.push.apply(this.eventWrappers_,Blockly.bindEvent_(this.svgGroup_,
+"mousedown",this,this.onMouseDown_))};
+Blockly.Flyout.prototype.dispose=function(){this.hide();Blockly.unbindEvent_(this.eventWrappers_);this.filterWrapper_&&(this.targetWorkspace_.removeChangeListener(this.filterWrapper_),this.filterWrapper_=null);this.scrollbar_&&(this.scrollbar_.dispose(),this.scrollbar_=null);this.workspace_&&(this.workspace_.targetWorkspace=null,this.workspace_.dispose(),this.workspace_=null);this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.targetWorkspace_=this.svgBackground_=null};
+Blockly.Flyout.prototype.getWidth=function(){return this.width_};Blockly.Flyout.prototype.getHeight=function(){return this.height_};
+Blockly.Flyout.prototype.getMetrics_=function(){if(!this.isVisible())return null;try{var a=this.workspace_.getCanvas().getBBox()}catch(f){a={height:0,y:0,width:0,x:0}}var b=this.SCROLLBAR_PADDING,c=this.SCROLLBAR_PADDING;if(this.horizontalLayout_){this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM&&(b=0);var d=this.height_;this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP&&(d+=this.MARGIN-this.SCROLLBAR_PADDING);var e=this.width_-2*this.SCROLLBAR_PADDING}else c=0,d=this.height_-2*this.SCROLLBAR_PADDING,
+e=this.width_,this.RTL||(e-=this.SCROLLBAR_PADDING);return{viewHeight:d,viewWidth:e,contentHeight:(a.height+2*this.MARGIN)*this.workspace_.scale,contentWidth:(a.width+2*this.MARGIN)*this.workspace_.scale,viewTop:-this.workspace_.scrollY,viewLeft:-this.workspace_.scrollX,contentTop:a.y,contentLeft:a.x,absoluteTop:b,absoluteLeft:c}};
+Blockly.Flyout.prototype.setMetrics_=function(a){var b=this.getMetrics_();b&&(!this.horizontalLayout_&&goog.isNumber(a.y)?this.workspace_.scrollY=-b.contentHeight*a.y:this.horizontalLayout_&&goog.isNumber(a.x)&&(this.workspace_.scrollX=-b.contentWidth*a.x),this.workspace_.translate(this.workspace_.scrollX+b.absoluteLeft,this.workspace_.scrollY+b.absoluteTop))};
+Blockly.Flyout.prototype.position=function(){if(this.isVisible()){var a=this.targetWorkspace_.getMetrics();if(a){var b=this.horizontalLayout_?a.viewWidth:this.width_,b=b-this.CORNER_RADIUS;this.toolboxPosition_==Blockly.TOOLBOX_AT_RIGHT&&(b*=-1);this.setBackgroundPath_(b,this.horizontalLayout_?this.height_:a.viewHeight);b=a.absoluteLeft;this.toolboxPosition_==Blockly.TOOLBOX_AT_RIGHT&&(b+=a.viewWidth,b-=this.width_);var c=a.absoluteTop;this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM&&(c+=a.viewHeight,
+c-=this.height_);this.svgGroup_.setAttribute("transform","translate("+b+","+c+")");this.horizontalLayout_?this.width_=a.viewWidth:this.height_=a.viewHeight;this.scrollbar_&&this.scrollbar_.resize()}}};Blockly.Flyout.prototype.setBackgroundPath_=function(a,b){this.horizontalLayout_?this.setBackgroundPathHorizontal_(a,b):this.setBackgroundPathVertical_(a,b)};
+Blockly.Flyout.prototype.setBackgroundPathVertical_=function(a,b){var c=this.toolboxPosition_==Blockly.TOOLBOX_AT_RIGHT,d=["M "+(c?this.width_:0)+",0"];d.push("h",a);d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,c?0:1,c?-this.CORNER_RADIUS:this.CORNER_RADIUS,this.CORNER_RADIUS);d.push("v",Math.max(0,b-2*this.CORNER_RADIUS));d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,c?0:1,c?this.CORNER_RADIUS:-this.CORNER_RADIUS,this.CORNER_RADIUS);d.push("h",-a);d.push("z");this.svgBackground_.setAttribute("d",
+d.join(" "))};
+Blockly.Flyout.prototype.setBackgroundPathHorizontal_=function(a,b){var c=this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP,d=["M 0,"+(c?0:this.CORNER_RADIUS)];c?(d.push("h",a+this.CORNER_RADIUS),d.push("v",b),d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,-this.CORNER_RADIUS,this.CORNER_RADIUS),d.push("h",-1*(a-this.CORNER_RADIUS)),d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,-this.CORNER_RADIUS,-this.CORNER_RADIUS)):(d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,this.CORNER_RADIUS,
+-this.CORNER_RADIUS),d.push("h",a-this.CORNER_RADIUS),d.push("a",this.CORNER_RADIUS,this.CORNER_RADIUS,0,0,1,this.CORNER_RADIUS,this.CORNER_RADIUS),d.push("v",b-this.CORNER_RADIUS),d.push("h",-a-this.CORNER_RADIUS));d.push("z");this.svgBackground_.setAttribute("d",d.join(" "))};Blockly.Flyout.prototype.scrollToStart=function(){this.scrollbar_.set(this.horizontalLayout_&&this.RTL?Infinity:0)};
+Blockly.Flyout.prototype.wheel_=function(a){var b=this.horizontalLayout_?a.deltaX:a.deltaY;if(b){goog.userAgent.GECKO&&(b*=10);var c=this.getMetrics_(),b=this.horizontalLayout_?c.viewLeft+b:c.viewTop+b,b=Math.min(b,this.horizontalLayout_?c.contentWidth-c.viewWidth:c.contentHeight-c.viewHeight),b=Math.max(b,0);this.scrollbar_.set(b)}a.preventDefault();a.stopPropagation()};Blockly.Flyout.prototype.isVisible=function(){return this.svgGroup_&&"block"==this.svgGroup_.style.display};
+Blockly.Flyout.prototype.hide=function(){if(this.isVisible()){this.svgGroup_.style.display="none";for(var a=0,b;b=this.listeners_[a];a++)Blockly.unbindEvent_(b);this.listeners_.length=0;this.reflowWrapper_&&(this.workspace_.removeChangeListener(this.reflowWrapper_),this.reflowWrapper_=null)}};
+Blockly.Flyout.prototype.show=function(a){this.hide();this.clearOldBlocks_();a==Blockly.Variables.NAME_TYPE?a=Blockly.Variables.flyoutCategory(this.workspace_.targetWorkspace):a==Blockly.Procedures.NAME_TYPE&&(a=Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace));this.svgGroup_.style.display="block";for(var b=[],c=[],d=this.permanentlyDisabled_.length=0,e;e=a[d];d++)if(e.tagName&&"BLOCK"==e.tagName.toUpperCase()){var f=Blockly.Xml.domToBlock(e,this.workspace_);f.disabled&&this.permanentlyDisabled_.push(f);
+b.push(f);e=parseInt(e.getAttribute("gap"),10);c.push(isNaN(e)?3*this.MARGIN:e)}this.layoutBlocks_(b,c);this.listeners_.push(Blockly.bindEvent_(this.svgBackground_,"mouseover",this,function(){for(var a=this.workspace_.getTopBlocks(!1),b=0,c;c=a[b];b++)c.removeSelect()}));this.horizontalLayout_?this.height_=0:this.width_=0;this.reflow();this.filterForCapacity_();this.position();this.reflowWrapper_=this.reflow.bind(this);this.workspace_.addChangeListener(this.reflowWrapper_)};
+Blockly.Flyout.prototype.layoutBlocks_=function(a,b){var c=this.MARGIN,d=this.RTL?c:c+Blockly.BlockSvg.TAB_WIDTH;this.horizontalLayout_&&this.RTL&&(a=a.reverse());for(var e=0,f;f=a[e];e++){for(var g=f.getDescendants(),h=0,k;k=g[h];h++)k.isInFlyout=!0;f.render();g=f.getSvgRoot();h=f.getHeightWidth();k=f.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0;this.horizontalLayout_&&(d+=k);this.horizontalLayout_&&this.RTL?f.moveBy(d+h.width-k,c):f.moveBy(d,c);this.horizontalLayout_?d+=h.width+b[e]-k:c+=h.height+
+b[e];h=Blockly.createSvgElement("rect",{"fill-opacity":0},null);h.tooltip=f;Blockly.Tooltip.bindMouseEvents(h);this.workspace_.getCanvas().insertBefore(h,f.getSvgRoot());f.flyoutRect_=h;this.buttons_[e]=h;this.addBlockListeners_(g,f,h)}};Blockly.Flyout.prototype.clearOldBlocks_=function(){for(var a=this.workspace_.getTopBlocks(!1),b=0,c;c=a[b];b++)c.workspace==this.workspace_&&c.dispose(!1,!1);for(a=0;b=this.buttons_[a];a++)goog.dom.removeNode(b);this.buttons_.length=0};
+Blockly.Flyout.prototype.addBlockListeners_=function(a,b,c){this.listeners_.push(Blockly.bindEvent_(a,"mousedown",null,this.blockMouseDown_(b)));this.listeners_.push(Blockly.bindEvent_(c,"mousedown",null,this.blockMouseDown_(b)));this.listeners_.push(Blockly.bindEvent_(a,"mouseover",b,b.addSelect));this.listeners_.push(Blockly.bindEvent_(a,"mouseout",b,b.removeSelect));this.listeners_.push(Blockly.bindEvent_(c,"mouseover",b,b.addSelect));this.listeners_.push(Blockly.bindEvent_(c,"mouseout",b,b.removeSelect))};
+Blockly.Flyout.prototype.blockMouseDown_=function(a){var b=this;return function(c){Blockly.terminateDrag_();Blockly.hideChaff(!0);Blockly.isRightButton(c)?a.showContextMenu_(c):(Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED),b.startDragMouseY_=c.clientY,b.startDragMouseX_=c.clientX,Blockly.Flyout.startDownEvent_=c,Blockly.Flyout.startBlock_=a,Blockly.Flyout.startFlyout_=b,Blockly.Flyout.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",b,b.onMouseUp_),Blockly.Flyout.onMouseMoveBlockWrapper_=
+Blockly.bindEvent_(document,"mousemove",b,b.onMouseMoveBlock_));c.stopPropagation();c.preventDefault()}};
+Blockly.Flyout.prototype.onMouseDown_=function(a){Blockly.isRightButton(a)||(Blockly.hideChaff(!0),this.dragMode_=Blockly.DRAG_FREE,this.startDragMouseY_=a.clientY,this.startDragMouseX_=a.clientX,Blockly.Flyout.startFlyout_=this,Blockly.Flyout.onMouseMoveWrapper_=Blockly.bindEvent_(document,"mousemove",this,this.onMouseMove_),Blockly.Flyout.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,Blockly.Flyout.terminateDrag_),a.preventDefault(),a.stopPropagation())};
+Blockly.Flyout.prototype.onMouseUp_=function(a){this.workspace_.isDragging()||(this.autoClose?this.createBlockFunc_(Blockly.Flyout.startBlock_)(Blockly.Flyout.startDownEvent_):Blockly.WidgetDiv.isVisible()||Blockly.Events.fire(new Blockly.Events.Ui(Blockly.Flyout.startBlock_,"click",void 0,void 0)));Blockly.terminateDrag_()};
+Blockly.Flyout.prototype.onMouseMove_=function(a){var b=this.getMetrics_();if(this.horizontalLayout_){if(!(0>b.contentWidth-b.viewWidth)){var c=a.clientX-this.startDragMouseX_;this.startDragMouseX_=a.clientX;a=b.viewLeft-c;a=goog.math.clamp(a,0,b.contentWidth-b.viewWidth);this.scrollbar_.set(a)}}else 0>b.contentHeight-b.viewHeight||(c=a.clientY-this.startDragMouseY_,this.startDragMouseY_=a.clientY,a=b.viewTop-c,a=goog.math.clamp(a,0,b.contentHeight-b.viewHeight),this.scrollbar_.set(a))};
+Blockly.Flyout.prototype.onMouseMoveBlock_=function(a){if(!("mousemove"==a.type&&1>=a.clientX&&0==a.clientY&&0==a.button))if(this.determineDragIntention_(a.clientX-Blockly.Flyout.startDownEvent_.clientX,a.clientY-Blockly.Flyout.startDownEvent_.clientY))this.createBlockFunc_(Blockly.Flyout.startBlock_)(Blockly.Flyout.startDownEvent_);else if(this.dragMode_==Blockly.DRAG_FREE)this.onMouseMove_(a);a.stopPropagation()};
+Blockly.Flyout.prototype.determineDragIntention_=function(a,b){if(this.dragMode_==Blockly.DRAG_FREE)return!1;if(Math.sqrt(a*a+b*b)<this.DRAG_RADIUS)return this.dragMode_=Blockly.DRAG_STICKY,!1;if(this.isDragTowardWorkspace_(a,b)||!this.scrollbar_.isVisible())return!0;this.dragMode_=Blockly.DRAG_FREE;return!1};
+Blockly.Flyout.prototype.isDragTowardWorkspace_=function(a,b){var c=Math.atan2(b,a)/Math.PI*180,d=!1,e=this.dragAngleRange_;if(this.horizontalLayout_)this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP?c<90+e&&c>90-e&&(d=!0):c>-90-e&&c<-90+e&&(d=!0);else if(this.toolboxPosition_==Blockly.TOOLBOX_AT_LEFT)c<e&&c>-e&&(d=!0);else if(c<-180+e||c>180-e)d=!0;return d};
+Blockly.Flyout.prototype.createBlockFunc_=function(a){var b=this;return function(c){if(!Blockly.isRightButton(c)&&!a.disabled){Blockly.Events.disable();try{var d=b.placeNewBlock_(a)}finally{Blockly.Events.enable()}Blockly.Events.isEnabled()&&(Blockly.Events.setGroup(!0),Blockly.Events.fire(new Blockly.Events.Create(d)));b.autoClose?b.hide():b.filterForCapacity_();d.onMouseDown_(c);Blockly.dragMode_=Blockly.DRAG_FREE;d.setDragging_(!0)}}};
+Blockly.Flyout.prototype.placeNewBlock_=function(a){var b=this.targetWorkspace_,c=a.getSvgRoot();if(!c)throw"originBlock is not rendered.";var c=Blockly.getSvgXY_(c,b),d=this.workspace_.scrollX,e=this.workspace_.scale;c.x+=d/e-d;this.toolboxPosition_==Blockly.TOOLBOX_AT_RIGHT&&(d=b.getMetrics().viewWidth-this.width_,e=b.scale,c.x+=d/e-d);d=this.workspace_.scrollY;e=this.workspace_.scale;c.y+=d/e-d;this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM&&(d=b.getMetrics().viewHeight-this.height_,e=b.scale,
+c.y+=d/e-d);a=Blockly.Xml.blockToDom(a);a=Blockly.Xml.domToBlock(a,b);e=a.getSvgRoot();if(!e)throw"block is not rendered.";e=Blockly.getSvgXY_(e,b);e.x+=b.scrollX/b.scale-b.scrollX;e.y+=b.scrollY/b.scale-b.scrollY;b.toolbox_&&!b.scrollbar&&(e.x+=b.toolbox_.getWidth()/b.scale,e.y+=b.toolbox_.getHeight()/b.scale);a.moveBy(c.x-e.x,c.y-e.y);return a};
+Blockly.Flyout.prototype.filterForCapacity_=function(){for(var a=this.targetWorkspace_.remainingCapacity(),b=this.workspace_.getTopBlocks(!1),c=0,d;d=b[c];c++)if(-1==this.permanentlyDisabled_.indexOf(d)){var e=d.getDescendants();d.setDisabled(e.length>a)}};
+Blockly.Flyout.prototype.getClientRect=function(){if(!this.svgGroup_)return null;var a=this.svgGroup_.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E9,c-1E9,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM?new goog.math.Rect(-1E9,c,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(b-1E9,-1E9,1E9+d,2E9):new goog.math.Rect(b,-1E9,1E9+d,2E9)};
+Blockly.Flyout.terminateDrag_=function(){Blockly.Flyout.startFlyout_&&(Blockly.Flyout.startFlyout_.dragMode_=Blockly.DRAG_NONE);Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_),Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.onMouseMoveBlockWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_),Blockly.Flyout.onMouseMoveBlockWrapper_=null);Blockly.Flyout.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_),
+Blockly.Flyout.onMouseMoveWrapper_=null);Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_),Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.startDownEvent_=null;Blockly.Flyout.startBlock_=null;Blockly.Flyout.startFlyout_=null};
+Blockly.Flyout.prototype.reflowHorizontal=function(a){this.workspace_.scale=this.targetWorkspace_.scale;for(var b=0,c=0,d;d=a[c];c++)b=Math.max(b,d.getHeightWidth().height);b+=1.5*this.MARGIN;b*=this.workspace_.scale;b+=Blockly.Scrollbar.scrollbarThickness;if(this.height_!=b){for(c=0;d=a[c];c++){var e=d.getHeightWidth();if(d.flyoutRect_){d.flyoutRect_.setAttribute("width",e.width);d.flyoutRect_.setAttribute("height",e.height);var f=d.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,g=d.getRelativeToSurfaceXY();
+d.flyoutRect_.setAttribute("y",g.y);d.flyoutRect_.setAttribute("x",this.RTL?g.x-e.width+f:g.x-f);(e=d.startHat_?Blockly.BlockSvg.START_HAT_HEIGHT:0)&&d.moveBy(0,e);d.flyoutRect_.setAttribute("y",g.y)}}this.height_=b;this.targetWorkspace_.resize()}};
+Blockly.Flyout.prototype.reflowVertical=function(a){this.workspace_.scale=this.targetWorkspace_.scale;for(var b=0,c=0,d;d=a[c];c++){var e=d.getHeightWidth().width;d.outputConnection&&(e-=Blockly.BlockSvg.TAB_WIDTH);b=Math.max(b,e)}b+=1.5*this.MARGIN+Blockly.BlockSvg.TAB_WIDTH;b*=this.workspace_.scale;b+=Blockly.Scrollbar.scrollbarThickness;if(this.width_!=b){for(c=0;d=a[c];c++){e=d.getHeightWidth();if(this.RTL){var f=d.getRelativeToSurfaceXY().x,g=b/this.workspace_.scale-this.MARGIN,g=g-Blockly.BlockSvg.TAB_WIDTH;
+d.moveBy(g-f,0)}d.flyoutRect_&&(d.flyoutRect_.setAttribute("width",e.width),d.flyoutRect_.setAttribute("height",e.height),g=d.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,f=d.getRelativeToSurfaceXY(),d.flyoutRect_.setAttribute("x",this.RTL?f.x-e.width+g:f.x-g),(e=d.startHat_?Blockly.BlockSvg.START_HAT_HEIGHT:0)&&d.moveBy(0,e),d.flyoutRect_.setAttribute("y",f.y))}this.width_=b;this.targetWorkspace_.resize()}};
+Blockly.Flyout.prototype.reflow=function(){this.reflowWrapper_&&this.workspace_.removeChangeListener(this.reflowWrapper_);var a=this.workspace_.getTopBlocks(!1);this.horizontalLayout_?this.reflowHorizontal(a):this.reflowVertical(a);this.reflowWrapper_&&this.workspace_.addChangeListener(this.reflowWrapper_)};Blockly.Toolbox=function(a){this.workspace_=a;this.RTL=a.options.RTL;this.horizontalLayout_=a.options.horizontalLayout;this.toolboxPosition=a.options.toolboxPosition;this.config_={indentWidth:19,cssRoot:"blocklyTreeRoot",cssHideRoot:"blocklyHidden",cssItem:"",cssTreeRow:"blocklyTreeRow",cssItemLabel:"blocklyTreeLabel",cssTreeIcon:"blocklyTreeIcon",cssExpandedFolderIcon:"blocklyTreeIconOpen",cssFileIcon:"blocklyTreeIconNone",cssSelectedRow:"blocklyTreeSelected"};this.treeSeparatorConfig_={cssTreeRow:"blocklyTreeSeparator"};
+this.horizontalLayout_&&(this.config_.cssTreeRow+=a.RTL?" blocklyHorizontalTreeRtl":" blocklyHorizontalTree",this.treeSeparatorConfig_.cssTreeRow="blocklyTreeSeparatorHorizontal "+(a.RTL?"blocklyHorizontalTreeRtl":"blocklyHorizontalTree"),this.config_.cssTreeIcon="")};Blockly.Toolbox.prototype.width=0;Blockly.Toolbox.prototype.height=0;Blockly.Toolbox.prototype.selectedOption_=null;Blockly.Toolbox.prototype.lastCategory_=null;
+Blockly.Toolbox.prototype.init=function(){var a=this.workspace_,b=this.workspace_.getParentSvg();this.HtmlDiv=goog.dom.createDom("div","blocklyToolboxDiv");this.HtmlDiv.setAttribute("dir",a.RTL?"RTL":"LTR");b.parentNode.insertBefore(this.HtmlDiv,b);Blockly.bindEvent_(this.HtmlDiv,"mousedown",this,function(a){Blockly.isRightButton(a)||a.target==this.HtmlDiv?Blockly.hideChaff(!1):Blockly.hideChaff(!0)});this.flyout_=new Blockly.Flyout({disabledPatternId:a.options.disabledPatternId,parentWorkspace:a,
+RTL:a.RTL,horizontalLayout:a.horizontalLayout,toolboxPosition:a.options.toolboxPosition});goog.dom.insertSiblingAfter(this.flyout_.createDom(),a.svgGroup_);this.flyout_.init(a);this.config_.cleardotPath=a.options.pathToMedia+"1x1.gif";this.config_.cssCollapsedFolderIcon="blocklyTreeIconClosed"+(a.RTL?"Rtl":"Ltr");this.tree_=b=new Blockly.Toolbox.TreeControl(this,this.config_);b.setShowRootNode(!1);b.setShowLines(!1);b.setShowExpandIcons(!1);b.setSelectedItem(null);this.populate_(a.options.languageTree);
+b.render(this.HtmlDiv);this.addColour_();this.position()};Blockly.Toolbox.prototype.dispose=function(){this.flyout_.dispose();this.tree_.dispose();goog.dom.removeNode(this.HtmlDiv);this.lastCategory_=this.workspace_=null};Blockly.Toolbox.prototype.getWidth=function(){return this.width};Blockly.Toolbox.prototype.getHeight=function(){return this.height};
+Blockly.Toolbox.prototype.position=function(){var a=this.HtmlDiv;if(a){var b=this.workspace_.getParentSvg(),c=goog.style.getPageOffset(b),b=Blockly.svgSize(b);this.horizontalLayout_?(a.style.left=c.x+"px",a.style.height="auto",a.style.width=b.width+"px",this.height=a.offsetHeight,a.style.top=this.toolboxPosition==Blockly.TOOLBOX_AT_TOP?"0px":0+b.height-a.offsetHeight+"px"):(a.style.left=this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT?c.x+b.width-a.offsetWidth+"px":c.x+"px",a.style.height=b.height+
+"px",a.style.top="0px",this.width=a.offsetWidth,this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT&&--this.width);this.flyout_.position()}};Blockly.Toolbox.prototype.populate_=function(a){this.tree_.removeChildren();this.tree_.blocks=[];this.hasColours_=!1;this.syncTrees_(a,this.tree_,this.workspace_.options.pathToMedia);if(this.tree_.blocks.length)throw"Toolbox cannot have both blocks and categories in the root level.";Blockly.resizeSvgContents(this.workspace_)};
+Blockly.Toolbox.prototype.syncTrees_=function(a,b,c){for(var d=null,e=0,f;f=a.childNodes[e];e++)if(f.tagName)switch(f.tagName.toUpperCase()){case "CATEGORY":var g=this.tree_.createNode(f.getAttribute("name"));g.blocks=[];b.add(g);(d=f.getAttribute("custom"))?g.blocks=d:this.syncTrees_(f,g,c);d=f.getAttribute("colour");goog.isString(d)?(d.match(/^#[0-9a-fA-F]{6}$/)?g.hexColour=d:g.hexColour=Blockly.hueToRgb(d),this.hasColours_=!0):g.hexColour="";"true"==f.getAttribute("expanded")?(g.blocks.length&&
+this.tree_.setSelectedItem(g),g.setExpanded(!0)):g.setExpanded(!1);d=f;Blockscad.Toolbox.catIDs.push(g.id_);break;case "SEP":d&&("CATEGORY"==d.tagName.toUpperCase()?b.add(new Blockly.Toolbox.TreeSeparator(this.treeSeparatorConfig_)):(f=parseFloat(f.getAttribute("gap")),isNaN(f)||(g=parseFloat(d.getAttribute("gap")),f=isNaN(g)?f:g+f,d.setAttribute("gap",f))));break;case "BLOCK":case "SHADOW":b.blocks.push(f),d=f}};
+Blockly.Toolbox.prototype.addColour_=function(a){a=(a||this.tree_).getChildren();for(var b=0,c;c=a[b];b++){var d=c.getRowElement();if(d){var e=this.hasColours_?"8px solid "+(c.hexColour||"#ddd"):"none";this.workspace_.RTL?d.style.borderRight=e:d.style.borderLeft=e}this.addColour_(c)}};Blockly.Toolbox.prototype.clearSelection=function(){this.tree_.setSelectedItem(null)};
+Blockly.Toolbox.prototype.getClientRect=function(){if(!this.HtmlDiv)return null;var a=this.HtmlDiv.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(-1E7,-1E7,1E7+b+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT?new goog.math.Rect(b,-1E7,1E7+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E7,-1E7,2E7,1E7+c+a):new goog.math.Rect(0,c,2E7,1E7+d)};
+Blockly.Toolbox.TreeControl=function(a,b){this.toolbox_=a;goog.ui.tree.TreeControl.call(this,goog.html.SafeHtml.EMPTY,b)};goog.inherits(Blockly.Toolbox.TreeControl,goog.ui.tree.TreeControl);Blockly.Toolbox.TreeControl.prototype.enterDocument=function(){Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this);if(goog.events.BrowserFeature.TOUCH_ENABLED){var a=this.getElement();Blockly.bindEvent_(a,goog.events.EventType.TOUCHSTART,this,this.handleTouchEvent_)}};
+Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_=function(a){a.preventDefault();var b=this.getNodeFromEvent_(a);b&&a.type===goog.events.EventType.TOUCHSTART&&setTimeout(function(){b.onMouseDown(a)},1)};Blockly.Toolbox.TreeControl.prototype.createNode=function(a){return new Blockly.Toolbox.TreeNode(this.toolbox_,a?goog.html.SafeHtml.htmlEscape(a):goog.html.SafeHtml.EMPTY,this.getConfig(),this.getDomHelper())};
+Blockly.Toolbox.TreeControl.prototype.setSelectedItem=function(a){var b=this.toolbox_;if(a!=this.selectedItem_&&a!=b.tree_){b.lastCategory_&&(b.lastCategory_.getRowElement().style.backgroundColor="");var c=this.getSelectedItem();goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this,a);a&&a.blocks&&a.blocks.length?(b.flyout_.show(a.blocks),b.lastCategory_!=a&&b.flyout_.scrollToStart()):b.flyout_.hide();c!=a&&c!=this&&(c=new Blockly.Events.Ui(null,"category",c&&c.getHtml(),a&&a.getHtml()),c.workspaceId=
+b.workspace_.id,Blockly.Events.fire(c));a&&(b.lastCategory_=a)}};Blockly.Toolbox.TreeNode=function(a,b,c,d){goog.ui.tree.TreeNode.call(this,b,c,d);a&&(b=function(){Blockly.svgResize(a.workspace_)},goog.events.listen(a.tree_,goog.ui.tree.BaseNode.EventType.EXPAND,b),goog.events.listen(a.tree_,goog.ui.tree.BaseNode.EventType.COLLAPSE,b))};goog.inherits(Blockly.Toolbox.TreeNode,goog.ui.tree.TreeNode);Blockly.Toolbox.TreeNode.prototype.getExpandIconSafeHtml=function(){return goog.html.SafeHtml.create("span")};
+Blockly.Toolbox.TreeNode.prototype.onMouseDown=function(a){this.hasChildren()&&this.isUserCollapsible_?(this.toggle(),this.select()):this.isSelected()?this.getTree().setSelectedItem(null):this.select();this.updateRow()};Blockly.Toolbox.TreeNode.prototype.onDoubleClick_=function(a){};Blockly.Toolbox.TreeSeparator=function(a){Blockly.Toolbox.TreeNode.call(this,null,"",a)};goog.inherits(Blockly.Toolbox.TreeSeparator,Blockly.Toolbox.TreeNode);Blockly.Css={};Blockly.Css.Cursor={OPEN:"handopen",CLOSED:"handclosed",DELETE:"handdelete"};Blockly.Css.currentCursor_="";Blockly.Css.styleSheet_=null;Blockly.Css.mediaPath_="";
+Blockly.Css.inject=function(a,b){if(!Blockly.Css.styleSheet_){var c=".blocklyDraggable {}\n";a&&(c+=Blockly.Css.CONTENT.join("\n"),Blockly.FieldDate&&(c+=Blockly.FieldDate.CSS.join("\n")));Blockly.Css.mediaPath_=b.replace(/[\\\/]$/,"");var c=c.replace(/<<<PATH>>>/g,Blockly.Css.mediaPath_),d=document.createElement("style");document.head.appendChild(d);c=document.createTextNode(c);d.appendChild(c);Blockly.Css.styleSheet_=d.sheet;Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN)}};
+Blockly.Css.setCursor=function(a){if(Blockly.Css.currentCursor_!=a){Blockly.Css.currentCursor_=a;var b="url("+Blockly.Css.mediaPath_+"/"+a+".cur), auto",c=".blocklyDraggable {\n  cursor: "+b+";\n}\n";Blockly.Css.styleSheet_.deleteRule(0);Blockly.Css.styleSheet_.insertRule(c,0);for(var c=document.getElementsByClassName("blocklyToolboxDiv"),d=0,e;e=c[d];d++)e.style.cursor=a==Blockly.Css.Cursor.DELETE?b:"";document.body.parentNode.style.cursor=a==Blockly.Css.Cursor.OPEN?"":b}};
+Blockly.Css.CONTENT=[".blocklySvg {","background-color: #fff;","outline: none;","overflow: hidden;","}",".blocklyWidgetDiv {","display: none;","position: absolute;","z-index: 999;","}",".blocklyTooltipDiv {","background-color: #ffffc7;","border: 1px solid #ddc;","box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);","color: #000;","display: none;","font-family: sans-serif;","font-size: 9pt;","opacity: 0.94;","padding: 2px;","position: absolute;","z-index: 1000;","}",".blocklyResizeSE {","cursor: se-resize;",
+"fill: #aaa;","}",".blocklyResizeSW {","cursor: sw-resize;","fill: #aaa;","}",".blocklyResizeLine {","stroke: #888;","stroke-width: 1;","}",".blocklyHighlightedConnectionPath {","  fill: none;","  stroke: #0f0;","  stroke-width: 8px;","}",".blocklyHighlightedConnectionPathBad {","  fill: none;","  stroke: #f00;","  stroke-width: 4px;","}",".blocklyPathLight {","  fill: none;","  stroke-linecap: round;","  stroke-width: 2;","}",".blocklyBacklight>.blocklyPath {","  stroke: #f00;","  stroke-width: 4px;",
+"}",".blocklyBacklight>.blocklyPathLight {","  display: none;","}",".blocklySelected>.blocklyPath {","stroke: #fc3;","stroke-width: 3px;","}",".blocklySelected>.blocklyPathLight {","display: none;","}",".blocklyDragging>.blocklyPath,",".blocklyDragging>.blocklyPathLight {","fill-opacity: .8;","stroke-opacity: .8;","}",".blocklyDragging>.blocklyPathDark {","display: none;","}",".blocklyDisabled>.blocklyPath {","fill-opacity: .5;","stroke-opacity: .5;","}",".blocklyDisabled>.blocklyPathLight,",".blocklyDisabled>.blocklyPathDark {",
+"display: none;","}",".blocklyText {","  cursor: default;","  fill: #fff;","  font-family: sans-serif;","  font-size: 11pt;","}",".blocklyButton {","  cursor: default;","  fill: #fff;","  border: 1px solid #000;","  font-size:12pt;","}",".blocklyNonEditableText>text {","pointer-events: none;","}",".blocklyNonEditableText>rect,",".blocklyEditableText>rect {","fill: #fff;","fill-opacity: .6;","}",".blocklyNonEditableText>text,",".blocklyEditableText>text {","fill: #000;","}",".blocklyEditableText:hover>rect {",
+"stroke: #fff;","stroke-width: 2;","}",".blocklyBubbleText {","fill: #000;","}",".blocklySvg text {","user-select: none;","-moz-user-select: none;","-webkit-user-select: none;","cursor: inherit;","}",".blocklyHidden {","display: none;","}",".blocklyFieldDropdown:not(.blocklyHidden) {","display: block;","}",".blocklyIconGroup {","cursor: default;","}",".blocklyIconGroup:not(:hover),",".blocklyIconGroupReadonly {","  opacity: .6;","}",".blocklyIconShield {","  cursor: default;","  fill: #00c;","  stroke: #ccc;",
+"  stroke-width: 1px;","}",".blocklyIconGroup:hover>.blocklyIconShield {","  fill: #00f;","  stroke: #fff;","}",".blocklyIconGroup:hover>.blocklyIconMark {","  fill: #fff;","}",".blocklyIconMark {","  cursor: default !important;","  fill: #ccc;","  font-family: sans-serif;","  font-size: 9pt;","  font-weight: bold;","  text-anchor: middle;","}",".blocklyIconShape {","fill: #00f;","stroke: #fff;","stroke-width: 1px;","}",".blocklyIconSymbol {","fill: #fff;","}",".blocklyMinimalBody {","margin: 0;",
+"padding: 0;","}",".blocklyCommentTextarea {","background-color: #ffc;","border: 0;","margin: 0;","padding: 2px;","resize: none;","}",".blocklyHtmlInput {","border: none;","border-radius: 4px;","font-family: sans-serif;","height: 100%;","margin: 0;","outline: none;","padding: 0 1px;","width: 100%","}",".blocklyMainBackground {","stroke-width: 1;","stroke: #c6c6c6;","}",".blocklyMutatorBackground {","fill: #fff;","stroke: #ddd;","stroke-width: 1;","}",".blocklyFlyoutBackground {","  fill: #ddd;","  fill-opacity: .8;",
+"}",".blocklyColourBackground {","  fill: #666;","}",".blocklyScrollbarBackground {","opacity: 0;","}",".blocklyScrollbarHandle {","fill: #ccc;","}",".blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",".blocklyScrollbarHandle:hover {","fill: #bbb;","}",".blocklyZoom>image {","opacity: .4;","}",".blocklyZoom>image:hover {","opacity: .6;","}",".blocklyZoom>image:active {","opacity: .8;","}",".blocklyFlyout .blocklyScrollbarHandle {","fill: #bbb;","}",".blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,",
+".blocklyFlyout .blocklyScrollbarHandle:hover {","fill: #aaa;","}",".blocklyInvalidInput {","background: #faa;","}",".blocklyAngleCircle {","stroke: #444;","stroke-width: 1;","fill: #ddd;","fill-opacity: .8;","}",".blocklyAngleMarks {","stroke: #444;","stroke-width: 1;","}",".blocklyAngleGauge {","fill: #f88;","fill-opacity: .8;","}",".blocklyAngleLine {","stroke: #f00;","stroke-width: 2;","stroke-linecap: round;","}",".blocklyContextMenu {","border-radius: 4px;","}",".blocklyDropdownMenu {","padding: 0 !important;",
+"}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(<<<PATH>>>/sprites.png) no-repeat -48px -16px !important;","}",".blocklyToolboxDiv {","background-color: #ddd;","overflow-x: visible;","overflow-y: auto;","position: absolute;","}",".blocklyTreeRoot {","  padding: 2px 0;","}",".blocklyTreeRoot:focus {","outline: none;","}",".blocklyTreeRow {","  line-height: 40px;","  height: 40px;","  padding-right: 1em;",
+"  white-space: nowrap;","  margin-top: 3px;","-webkit-touch-callout: none;","-webkit-user-select: none;","-khtml-user-select: none;","-moz-user-select: none;","-ms-user-select: none;","user-select: none;","}",".blocklyHorizontalTree {","float: left;","margin: 1px 5px 8px 0;","}",".blocklyHorizontalTreeRtl {","float: right;","margin: 1px 0 8px 5px;","}",'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',"  padding-right: 0;","  padding-left: 1em !important;","}",".blocklyTreeRow:hover {","-webkit-box-shadow:inset 0px 0px 0px 1px #fff;",
+"-moz-box-shadow:inset 0px 0px 0px 1px #fff;","box-shadow:inset 0px 0px 0px 1px #fff;","}",".blocklyTreeSeparator {","border-bottom: solid #e5e5e5 1px;","height: 0;","margin: 5px 0;","}",".blocklyTreeSeparatorHorizontal {","border-right: solid #e5e5e5 1px;","width: 0;","padding: 5px 0;","margin: 0 5px;","}",".blocklyTreeIcon {","background-image: url(<<<PATH>>>/sprites.png);","height: 16px;","vertical-align: middle;","width: 16px;","}",".blocklyTreeIconClosedLtr {","background-position: -32px -1px;",
+"}",".blocklyTreeIconClosedRtl {","background-position: 0px -1px;","}",".blocklyTreeIconOpen {","background-position: -16px -1px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedLtr {","background-position: -32px -17px;","}",".blocklyTreeSelected>.blocklyTreeIconClosedRtl {","background-position: 0px -17px;","}",".blocklyTreeSelected>.blocklyTreeIconOpen {","background-position: -16px -17px;","}",".blocklyTreeIconNone,",".blocklyTreeSelected>.blocklyTreeIconNone {","background-position: -48px -1px;",
+"}",".blocklyTreeLabel {","cursor: default;","font-family: sans-serif;","font-size: 16px;","padding: 0 3px;","vertical-align: middle;","color: #fff","}",".blocklyTreeSelected .blocklyTreeLabel {","color: #fff;","}",".blocklyWidgetDiv .goog-palette {","outline: none;","cursor: default;","}",".blocklyWidgetDiv .goog-palette-table {","border: 1px solid #666;","border-collapse: collapse;","}",".blocklyWidgetDiv .goog-palette-cell {","height: 13px;","width: 15px;","margin: 0;","border: 0;","text-align: center;",
+"vertical-align: middle;","border-right: 1px solid #666;","font-size: 1px;","}",".blocklyWidgetDiv .goog-palette-colorswatch {","position: relative;","height: 13px;","width: 15px;","border: 1px solid #666;","}",".blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {","border: 1px solid #FFF;","}",".blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {","border: 1px solid #000;","color: #fff;","}",".blocklyWidgetDiv .goog-menu {","background: #fff;","border-color: #ccc #666 #666 #ccc;",
+"border-style: solid;","border-width: 1px;","cursor: default;","font: normal 13px Arial, sans-serif;","margin: 0;","outline: none;","padding: 4px 0;","position: absolute;","overflow-y: auto;","overflow-x: hidden;","max-height: 100%;","z-index: 20000;","}",".blocklyWidgetDiv .goog-menuitem {","color: #000;","font: normal 13px Arial, sans-serif;","list-style: none;","margin: 0;","padding: 4px 7em 4px 28px;","white-space: nowrap;","}",".blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {","padding-left: 7em;",
+"padding-right: 28px;","}",".blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,",".blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {","padding-left: 12px;","}",".blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {","padding-right: 20px;","}",".blocklyWidgetDiv .goog-menuitem-content {","color: #000;","font: normal 13px Arial, sans-serif;","}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {","color: #ccc !important;",
+"}",".blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {","opacity: 0.3;","-moz-opacity: 0.3;","filter: alpha(opacity=30);","}",".blocklyWidgetDiv .goog-menuitem-highlight,",".blocklyWidgetDiv .goog-menuitem-hover {","background-color: #d6e9f8;","border-color: #d6e9f8;","border-style: dotted;","border-width: 1px 0;","padding-bottom: 3px;","padding-top: 3px;","}",".blocklyWidgetDiv .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-icon {","background-repeat: no-repeat;","height: 16px;",
+"left: 6px;","position: absolute;","right: auto;","vertical-align: middle;","width: 16px;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {","left: auto;","right: 6px;","}",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,",".blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {","background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;","}",".blocklyWidgetDiv .goog-menuitem-accel {",
+"color: #999;","direction: ltr;","left: auto;","padding: 0 6px;","position: absolute;","right: 0;","text-align: right;","}",".blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {","left: 0;","right: auto;","text-align: left;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-hint {","text-decoration: underline;","}",".blocklyWidgetDiv .goog-menuitem-mnemonic-separator {","color: #999;","font-size: 12px;","padding-left: 4px;","}",".blocklyWidgetDiv .goog-menuseparator {","border-top: 1px solid #ccc;",
+"margin: 4px 0;","padding: 0;","}",""];Blockly.WidgetDiv={};Blockly.WidgetDiv.DIV=null;Blockly.WidgetDiv.owner_=null;Blockly.WidgetDiv.dispose_=null;Blockly.WidgetDiv.createDom=function(){Blockly.WidgetDiv.DIV||(Blockly.WidgetDiv.DIV=goog.dom.createDom("div","blocklyWidgetDiv"),document.body.appendChild(Blockly.WidgetDiv.DIV))};
+Blockly.WidgetDiv.show=function(a,b,c){Blockly.WidgetDiv.hide();Blockly.WidgetDiv.owner_=a;Blockly.WidgetDiv.dispose_=c;a=goog.style.getViewportPageOffset(document);Blockly.WidgetDiv.DIV.style.top=a.y+"px";Blockly.WidgetDiv.DIV.style.direction=b?"rtl":"ltr";Blockly.WidgetDiv.DIV.style.display="block"};
+Blockly.WidgetDiv.hide=function(){Blockly.WidgetDiv.owner_&&(Blockly.WidgetDiv.owner_=null,Blockly.WidgetDiv.DIV.style.display="none",Blockly.WidgetDiv.DIV.style.left="",Blockly.WidgetDiv.DIV.style.top="",Blockly.WidgetDiv.DIV.style.height="",Blockly.WidgetDiv.dispose_&&Blockly.WidgetDiv.dispose_(),Blockly.WidgetDiv.dispose_=null,goog.dom.removeChildren(Blockly.WidgetDiv.DIV))};Blockly.WidgetDiv.isVisible=function(){return!!Blockly.WidgetDiv.owner_};
+Blockly.WidgetDiv.hideIfOwner=function(a){Blockly.WidgetDiv.owner_==a&&Blockly.WidgetDiv.hide()};Blockly.WidgetDiv.position=function(a,b,c,d,e){b<d.y&&(b=d.y);e?a>c.width+d.x&&(a=c.width+d.x):a<d.x&&(a=d.x);Blockly.WidgetDiv.DIV.style.left=a+"px";Blockly.WidgetDiv.DIV.style.top=b+"px";Blockly.WidgetDiv.DIV.style.height=c.height-b+d.y+"px"};Blockly.constants={};Blockly.DRAG_RADIUS=5;Blockly.SNAP_RADIUS=20;Blockly.BUMP_DELAY=0;Blockly.COLLAPSE_CHARS=30;Blockly.LONGPRESS=750;Blockly.SOUND_LIMIT=100;Blockly.HSV_SATURATION=.45;Blockly.HSV_VALUE=.65;Blockly.SPRITE={width:96,height:124,url:"sprites.png"};Blockly.SVG_NS="http://www.w3.org/2000/svg";Blockly.HTML_NS="http://www.w3.org/1999/xhtml";Blockly.INPUT_VALUE=1;Blockly.OUTPUT_VALUE=2;Blockly.NEXT_STATEMENT=3;Blockly.PREVIOUS_STATEMENT=4;Blockly.DUMMY_INPUT=5;Blockly.ALIGN_LEFT=-1;
+Blockly.ALIGN_CENTRE=0;Blockly.ALIGN_RIGHT=1;Blockly.DRAG_NONE=0;Blockly.DRAG_STICKY=1;Blockly.DRAG_BEGIN=1;Blockly.DRAG_FREE=2;Blockly.OPPOSITE_TYPE=[];Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE]=Blockly.OUTPUT_VALUE;Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE]=Blockly.INPUT_VALUE;Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT]=Blockly.PREVIOUS_STATEMENT;Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT]=Blockly.NEXT_STATEMENT;Blockly.TOOLBOX_AT_TOP=0;Blockly.TOOLBOX_AT_BOTTOM=1;
+Blockly.TOOLBOX_AT_LEFT=2;Blockly.TOOLBOX_AT_RIGHT=3;Blockly.inject=function(a,b){goog.isString(a)&&(a=document.getElementById(a)||document.querySelector(a));if(!goog.dom.contains(document,a))throw"Error: container is not in current document.";var c=new Blockly.Options(b||{}),d=Blockly.createDom_(a,c),c=Blockly.createMainWorkspace_(d,c);Blockly.init_(c);c.markFocused();Blockly.bindEvent_(d,"focus",c,c.markFocused);Blockly.svgResize(c);return c};
+Blockly.createDom_=function(a,b){a.setAttribute("dir","LTR");goog.ui.Component.setDefaultRightToLeft(b.RTL);Blockly.Css.inject(b.hasCss,b.pathToMedia);var c=Blockly.createSvgElement("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:html":"http://www.w3.org/1999/xhtml","xmlns:xlink":"http://www.w3.org/1999/xlink",version:"1.1","class":"blocklySvg"},a),d=Blockly.createSvgElement("defs",{},c),e=String(Math.random()).substring(2),f=Blockly.createSvgElement("filter",{id:"blocklyEmbossFilter"+e},d);Blockly.createSvgElement("feGaussianBlur",
+{"in":"SourceAlpha",stdDeviation:1,result:"blur"},f);var g=Blockly.createSvgElement("feSpecularLighting",{"in":"blur",surfaceScale:1,specularConstant:.5,specularExponent:10,"lighting-color":"white",result:"specOut"},f);Blockly.createSvgElement("fePointLight",{x:-5E3,y:-1E4,z:2E4},g);Blockly.createSvgElement("feComposite",{"in":"specOut",in2:"SourceAlpha",operator:"in",result:"specOut"},f);Blockly.createSvgElement("feComposite",{"in":"SourceGraphic",in2:"specOut",operator:"arithmetic",k1:0,k2:1,k3:1,
+k4:0},f);b.embossFilterId=f.id;f=Blockly.createSvgElement("pattern",{id:"blocklyDisabledPattern"+e,patternUnits:"userSpaceOnUse",width:10,height:10},d);Blockly.createSvgElement("rect",{width:10,height:10,fill:"#aaa"},f);Blockly.createSvgElement("path",{d:"M 0 0 L 10 10 M 10 0 L 0 10",stroke:"#cc0"},f);b.disabledPatternId=f.id;d=Blockly.createSvgElement("pattern",{id:"blocklyGridPattern"+e,patternUnits:"userSpaceOnUse"},d);0<b.gridOptions.length&&0<b.gridOptions.spacing&&(Blockly.createSvgElement("line",
+{stroke:b.gridOptions.colour},d),1<b.gridOptions.length&&Blockly.createSvgElement("line",{stroke:b.gridOptions.colour},d));b.gridPattern=d;return c};
+Blockly.createMainWorkspace_=function(a,b){b.parentWorkspace=null;b.getMetrics=Blockly.getMainWorkspaceMetrics_;b.setMetrics=Blockly.setMainWorkspaceMetrics_;var c=new Blockly.WorkspaceSvg(b);c.scale=b.zoomOptions.startScale;a.appendChild(c.createDom("blocklyMainBackground"));c.translate(0,0);c.markFocused();b.readOnly||b.hasScrollbars||c.addChangeListener(function(){if(Blockly.dragMode_==Blockly.DRAG_NONE){var a=c.getMetrics(),e=a.viewLeft+a.absoluteLeft,f=a.viewTop+a.absoluteTop;if(a.contentTop<
+f||a.contentTop+a.contentHeight>a.viewHeight+f||a.contentLeft<(b.RTL?a.viewLeft:e)||a.contentLeft+a.contentWidth>(b.RTL?a.viewWidth:a.viewWidth+e))for(var g=c.getTopBlocks(!1),h=0,k;k=g[h];h++){var l=k.getRelativeToSurfaceXY(),p=k.getHeightWidth(),m=f+25-p.height-l.y;0<m&&k.moveBy(0,m);m=f+a.viewHeight-25-l.y;0>m&&k.moveBy(0,m);m=25+e-l.x-(b.RTL?0:p.width);0<m&&k.moveBy(m,0);l=e+a.viewWidth-25-l.x+(b.RTL?p.width:0);0>l&&k.moveBy(l,0)}}});Blockly.svgResize(c);Blockly.WidgetDiv.createDom();Blockly.Tooltip.createDom();
+return c};
+Blockly.init_=function(a){var b=a.options;a.getParentSvg();Blockly.bindEvent_(document,"contextmenu",null,function(a){Blockly.isTargetInput_(a)||a.preventDefault()});var c=Blockly.bindEvent_(window,"resize",null,function(){Blockly.hideChaff(!0);Blockly.svgResize(a)});a.setResizeHandlerWrapper(c);Blockly.inject.bindDocumentEvents_();b.languageTree&&(a.toolbox_?a.toolbox_.init(a):a.flyout_&&(a.flyout_.init(a),a.flyout_.show(b.languageTree.childNodes),a.flyout_.scrollToStart(),a.scrollX=a.flyout_.width_,b.toolboxPosition==
+Blockly.TOOLBOX_AT_RIGHT&&(a.scrollX*=-1),a.translate(a.scrollX,0)));b.hasScrollbars&&(a.scrollbar=new Blockly.ScrollbarPair(a),a.scrollbar.resize());b.hasSounds&&Blockly.inject.loadSounds_(b.pathToMedia,a)};
+Blockly.inject.bindDocumentEvents_=function(){Blockly.documentEventsBound_||(Blockly.bindEvent_(document,"keydown",null,Blockly.onKeyDown_),Blockly.bindEvent_(document,"touchend",null,Blockly.longStop_),Blockly.bindEvent_(document,"touchcancel",null,Blockly.longStop_),document.addEventListener("mouseup",Blockly.onMouseUp_,!1),goog.userAgent.IPAD&&Blockly.bindEvent_(window,"orientationchange",document,function(){Blockly.svgResize(Blockly.getMainWorkspace())}));Blockly.documentEventsBound_=!0};
+Blockly.inject.loadSounds_=function(a,b){b.loadAudio_([a+"click.mp3",a+"click.wav",a+"click.ogg"],"click");b.loadAudio_([a+"disconnect.wav",a+"disconnect.mp3",a+"disconnect.ogg"],"disconnect");b.loadAudio_([a+"delete.mp3",a+"delete.ogg",a+"delete.wav"],"delete");var c=[],d=function(){for(;c.length;)Blockly.unbindEvent_(c.pop());b.preloadAudio_()};c.push(Blockly.bindEvent_(document,"mousemove",null,d));c.push(Blockly.bindEvent_(document,"touchstart",null,d))};
+Blockly.updateToolbox=function(a){console.warn("Deprecated call to Blockly.updateToolbox, use workspace.updateToolbox instead.");Blockly.getMainWorkspace().updateToolbox(a)};Blockly.utils={};Blockly.addClass_=function(a,b){var c=a.getAttribute("class")||"";-1==(" "+c+" ").indexOf(" "+b+" ")&&(c&&(c+=" "),a.setAttribute("class",c+b))};Blockly.removeClass_=function(a,b){var c=a.getAttribute("class");if(-1!=(" "+c+" ").indexOf(" "+b+" ")){for(var c=c.split(/\s+/),d=0;d<c.length;d++)c[d]&&c[d]!=b||(c.splice(d,1),d--);c.length?a.setAttribute("class",c.join(" ")):a.removeAttribute("class")}};
+Blockly.hasClass_=function(a,b){return-1!=(" "+a.getAttribute("class")+" ").indexOf(" "+b+" ")};Blockly.bindEvent_=function(a,b,c,d){var e=c?function(a){d.call(c,a)}:d;a.addEventListener(b,e,!1);var f=[[a,b,e]];if(b in Blockly.bindEvent_.TOUCH_MAP)for(var e=function(a){if(1==a.changedTouches.length){var b=a.changedTouches[0];a.clientX=b.clientX;a.clientY=b.clientY}d.call(c,a);a.preventDefault()},g=0,h;h=Blockly.bindEvent_.TOUCH_MAP[b][g];g++)a.addEventListener(h,e,!1),f.push([a,h,e]);return f};
+Blockly.bindEvent_.TOUCH_MAP={};goog.events.BrowserFeature.TOUCH_ENABLED&&(Blockly.bindEvent_.TOUCH_MAP={mousedown:["touchstart"],mousemove:["touchmove"],mouseup:["touchend","touchcancel"]});Blockly.unbindEvent_=function(a){for(;a.length;){var b=a.pop(),c=b[2];b[0].removeEventListener(b[1],c,!1)}return c};Blockly.noEvent=function(a){a.preventDefault();a.stopPropagation()};
+Blockly.isTargetInput_=function(a){return"textarea"==a.target.type||"text"==a.target.type||"number"==a.target.type||"email"==a.target.type||"password"==a.target.type||"search"==a.target.type||"tel"==a.target.type||"url"==a.target.type||a.target.isContentEditable};
+Blockly.getRelativeXY_=function(a){var b=new goog.math.Coordinate(0,0),c=a.getAttribute("x");c&&(b.x=parseInt(c,10));if(c=a.getAttribute("y"))b.y=parseInt(c,10);if(a=(a=a.getAttribute("transform"))&&a.match(Blockly.getRelativeXY_.XY_REGEXP_))b.x+=parseFloat(a[1]),a[3]&&(b.y+=parseFloat(a[3]));return b};Blockly.getRelativeXY_.XY_REGEXP_=/translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/;
+Blockly.getSvgXY_=function(a,b){var c=0,d=0,e=1;if(goog.dom.contains(b.getCanvas(),a)||goog.dom.contains(b.getBubbleCanvas(),a))e=b.scale;do{var f=Blockly.getRelativeXY_(a);if(a==b.getCanvas()||a==b.getBubbleCanvas())e=1;c+=f.x*e;d+=f.y*e;a=a.parentNode}while(a&&a!=b.getParentSvg());return new goog.math.Coordinate(c,d)};
+Blockly.createSvgElement=function(a,b,c,d){a=document.createElementNS(Blockly.SVG_NS,a);for(var e in b)a.setAttribute(e,b[e]);document.body.runtimeStyle&&(a.runtimeStyle=a.currentStyle=a.style);c&&c.appendChild(a);return a};Blockly.isRightButton=function(a){return a.ctrlKey&&goog.userAgent.MAC?!0:2==a.button};Blockly.mouseToSvg=function(a,b,c){var d=b.createSVGPoint();d.x=a.clientX;d.y=a.clientY;c||(c=b.getScreenCTM().inverse());return d.matrixTransform(c)};
+Blockly.shortestStringLength=function(a){if(!a.length)return 0;for(var b=a[0].length,c=1;c<a.length;c++)b=Math.min(b,a[c].length);return b};Blockly.commonWordPrefix=function(a,b){if(!a.length)return 0;if(1==a.length)return a[0].length;for(var c=0,d=b||Blockly.shortestStringLength(a),e=0;e<d;e++){for(var f=a[0][e],g=1;g<a.length;g++)if(f!=a[g][e])return c;" "==f&&(c=e+1)}for(g=1;g<a.length;g++)if((f=a[g][e])&&" "!=f)return c;return d};
+Blockly.commonWordSuffix=function(a,b){if(!a.length)return 0;if(1==a.length)return a[0].length;for(var c=0,d=b||Blockly.shortestStringLength(a),e=0;e<d;e++){for(var f=a[0].substr(-e-1,1),g=1;g<a.length;g++)if(f!=a[g].substr(-e-1,1))return c;" "==f&&(c=e+1)}for(g=1;g<a.length;g++)if((f=a[g].charAt(a[g].length-e-1))&&" "!=f)return c;return d};Blockly.isNumber=function(a){return!!a.match(/^\s*-?\d+(\.\d+)?\s*$/)};
+Blockly.utils.tokenizeInterpolation=function(a){var b=[];a=a.split("");a.push("");for(var c=0,d=[],e=null,f=0;f<a.length;f++){var g=a[f];0==c?"%"==g?c=1:d.push(g):1==c?"%"==g?(d.push(g),c=0):"0"<=g&&"9">=g?(c=2,e=g,(g=d.join(""))&&b.push(g),d.length=0):(d.push("%",g),c=0):2==c&&("0"<=g&&"9">=g?e+=g:(b.push(parseInt(e,10)),f--,c=0))}(g=d.join(""))&&b.push(g);return b};
+Blockly.genUid=function(){for(var a=Blockly.genUid.soup_.length,b=[],c=0;20>c;c++)b[c]=Blockly.genUid.soup_.charAt(Math.random()*a);return b.join("")};Blockly.genUid.soup_="!#%()*+,-./:;=?@[]^_`{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";Blockly.utils.wrap=function(a,b){for(var c=a.split("\n"),d=0;d<c.length;d++)c[d]=Blockly.utils.wrap_line_(c[d],b);return c.join("\n")};
+Blockly.utils.wrap_line_=function(a,b){if(a.length<=b)return a;for(var c=a.trim().split(/\s+/),d=0;d<c.length;d++)c[d].length>b&&(b=c[d].length);var e,d=-Infinity,f,g=1;do{e=d;f=a;for(var h=[],k=c.length/g,l=1,d=0;d<c.length-1;d++)l<(d+1.5)/k?(l++,h[d]=!0):h[d]=!1;h=Blockly.utils.wrapMutate_(c,h,b);d=Blockly.utils.wrapScore_(c,h,b);a=Blockly.utils.wrapToText_(c,h);g++}while(d>e);return f};
+Blockly.utils.wrapScore_=function(a,b,c){for(var d=[0],e=[],f=0;f<a.length;f++)d[d.length-1]+=a[f].length,!0===b[f]?(d.push(0),e.push(a[f].charAt(a[f].length-1))):!1===b[f]&&d[d.length-1]++;a=Math.max.apply(Math,d);for(f=b=0;f<d.length;f++)b-=2*Math.pow(Math.abs(c-d[f]),1.5),b-=Math.pow(a-d[f],1.5),-1!=".?!".indexOf(e[f])?b+=c/3:-1!=",;)]}".indexOf(e[f])&&(b+=c/4);1<d.length&&d[d.length-1]<=d[d.length-2]&&(b+=.5);return b};
+Blockly.utils.wrapMutate_=function(a,b,c){for(var d=Blockly.utils.wrapScore_(a,b,c),e,f=0;f<b.length-1;f++)if(b[f]!=b[f+1]){var g=[].concat(b);g[f]=!g[f];g[f+1]=!g[f+1];var h=Blockly.utils.wrapScore_(a,g,c);h>d&&(d=h,e=g)}return e?Blockly.utils.wrapMutate_(a,e,c):b};Blockly.utils.wrapToText_=function(a,b){for(var c=[],d=0;d<a.length;d++)c.push(a[d]),void 0!==b[d]&&c.push(b[d]?"\n":" ");return c.join("")};var CLOSURE_DEFINES={"goog.DEBUG":!1};Blockly.mainWorkspace=null;Blockly.selected=null;Blockly.highlightedConnection_=null;Blockly.backlight=[];Blockly.highlightedConnectionBad_=null;Blockly.localConnection_=null;Blockly.draggingConnections_=[];Blockly.clipboardXml_=null;Blockly.clipboardSource_=null;Blockly.dragMode_=Blockly.DRAG_NONE;Blockly.onTouchUpWrapper_=null;Blockly.hueToRgb=function(a){return goog.color.hsvToHex(a,Blockly.HSV_SATURATION,255*Blockly.HSV_VALUE)};
+Blockly.svgSize=function(a){return{width:a.cachedWidth_,height:a.cachedHeight_}};Blockly.resizeSvgContents=function(a){a.resizeContents()};Blockly.svgResize=function(a){for(;a.options.parentWorkspace;)a=a.options.parentWorkspace;var b=a.getParentSvg(),c=b.parentNode;if(c){var d=c.offsetWidth,c=c.offsetHeight;b.cachedWidth_!=d&&(b.setAttribute("width",d+"px"),b.cachedWidth_=d);b.cachedHeight_!=c&&(b.setAttribute("height",c+"px"),b.cachedHeight_=c);a.resize()}};
+Blockly.onMouseUp_=function(a){a=Blockly.getMainWorkspace();Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);a.dragMode_=Blockly.DRAG_NONE;Blockly.onTouchUpWrapper_&&(Blockly.unbindEvent_(Blockly.onTouchUpWrapper_),Blockly.onTouchUpWrapper_=null);Blockly.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_),Blockly.onMouseMoveWrapper_=null)};
+Blockly.onMouseMove_=function(a){if(!(a.touches&&2<=a.touches.length)){var b=Blockly.getMainWorkspace();if(b.dragMode_!=Blockly.DRAG_NONE){var c=a.clientX-b.startDragMouseX,d=a.clientY-b.startDragMouseY,e=b.startDragMetrics,f=b.startScrollX+c,g=b.startScrollY+d,f=Math.min(f,-e.contentLeft),g=Math.min(g,-e.contentTop),f=Math.max(f,e.viewWidth-e.contentLeft-e.contentWidth),g=Math.max(g,e.viewHeight-e.contentTop-e.contentHeight);b.scrollbar.set(-f-e.contentLeft,-g-e.contentTop);Math.sqrt(c*c+d*d)>Blockly.DRAG_RADIUS&&
+(Blockly.longStop_(),b.dragMode_=Blockly.DRAG_FREE);a.stopPropagation();a.preventDefault()}}};
+Blockly.onKeyDown_=function(a){if(!Blockly.mainWorkspace.options.readOnly&&!Blockly.isTargetInput_(a)){var b=!1;if(27==a.keyCode)Blockly.hideChaff();else if(8==a.keyCode||46==a.keyCode)a.preventDefault(),Blockly.selected&&Blockly.selected.isDeletable()&&(b=!0);else if(a.altKey||a.ctrlKey||a.metaKey)Blockly.selected&&Blockly.selected.isDeletable()&&Blockly.selected.isMovable()&&(67==a.keyCode?(Blockly.hideChaff(),Blockly.copy_(Blockly.selected)):88==a.keyCode&&(Blockly.copy_(Blockly.selected),b=!0)),
+86==a.keyCode?Blockly.clipboardXml_&&(Blockly.Events.setGroup(!0),Blockly.clipboardSource_.paste(Blockly.clipboardXml_),Blockly.Events.setGroup(!1)):90==a.keyCode&&(Blockly.hideChaff(),Blockly.mainWorkspace.undo(a.shiftKey));b&&(Blockly.Events.setGroup(!0),Blockly.hideChaff(),Blockly.selected.dispose(Blockly.dragMode_!=Blockly.DRAG_FREE,!0),Blockly.highlightedConnection_&&(Blockly.highlightedConnection_.unhighlight(),Blockly.highlightedConnection_=null),Blockly.Events.setGroup(!1))}};
+Blockly.terminateDrag_=function(){Blockly.BlockSvg.terminateDrag();Blockly.Flyout.terminateDrag_()};Blockly.longPid_=0;Blockly.longStart_=function(a,b){Blockly.longStop_();Blockly.longPid_=setTimeout(function(){a.button=2;b.onMouseDown_(a)},Blockly.LONGPRESS)};Blockly.longStop_=function(){Blockly.longPid_&&(clearTimeout(Blockly.longPid_),Blockly.longPid_=0)};
+Blockly.copy_=function(a){var b=Blockly.Xml.blockToDom(a);Blockly.dragMode_!=Blockly.DRAG_FREE&&Blockly.Xml.deleteNext(b);var c=a.getRelativeToSurfaceXY();b.setAttribute("x",a.RTL?-c.x:c.x);b.setAttribute("y",c.y);Blockly.clipboardXml_=b;Blockly.clipboardSource_=a.workspace};Blockly.duplicate_=function(a){var b=Blockly.clipboardXml_,c=Blockly.clipboardSource_;Blockly.copy_(a);a.workspace.paste(Blockly.clipboardXml_);Blockly.clipboardXml_=b;Blockly.clipboardSource_=c};
+Blockly.onContextMenu_=function(a){Blockly.isTargetInput_(a)||a.preventDefault()};Blockly.hideChaff=function(a){Blockly.Tooltip.hide();Blockly.WidgetDiv.hide();a||(a=Blockly.getMainWorkspace(),a.toolbox_&&a.toolbox_.flyout_&&a.toolbox_.flyout_.autoClose&&a.toolbox_.clearSelection())};
+Blockly.getMainWorkspaceMetrics_=function(){var a=Blockly.svgSize(this.getParentSvg());if(this.toolbox_)if(this.toolboxPosition==Blockly.TOOLBOX_AT_TOP||this.toolboxPosition==Blockly.TOOLBOX_AT_BOTTOM)a.height-=this.toolbox_.getHeight();else if(this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT||this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT)a.width-=this.toolbox_.getWidth();var b=Blockly.Flyout.prototype.CORNER_RADIUS-1,c=a.width-b,d=a.height-b,e=this.getBlocksBoundingBox(),f=e.width*this.scale,g=e.height*
+this.scale,h=e.x*this.scale,k=e.y*this.scale;this.scrollbar?(b=Math.min(h-c/2,h+f-c),c=Math.max(h+f+c/2,h+c),f=Math.min(k-d/2,k+g-d),d=Math.max(k+g+d/2,k+d)):(b=e.x,c=b+e.width,f=e.y,d=f+e.height);e=0;this.toolbox_&&this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT&&(e=this.toolbox_.getWidth());g=0;this.toolbox_&&this.toolboxPosition==Blockly.TOOLBOX_AT_TOP&&(g=this.toolbox_.getHeight());return{viewHeight:a.height,viewWidth:a.width,contentHeight:d-f,contentWidth:c-b,viewTop:-this.scrollY,viewLeft:-this.scrollX,
+contentTop:f,contentLeft:b,absoluteTop:g,absoluteLeft:e,toolboxWidth:this.toolbox_?this.toolbox_.getWidth():0,toolboxHeight:this.toolbox_?this.toolbox_.getHeight():0,flyoutWidth:this.flyout_?this.flyout_.getWidth():0,flyoutHeight:this.flyout_?this.flyout_.getHeight():0,toolboxPosition:this.toolboxPosition}};
+Blockly.setMainWorkspaceMetrics_=function(a){if(!this.scrollbar)throw"Attempt to set main workspace scroll without scrollbars.";var b=this.getMetrics();goog.isNumber(a.x)&&(this.scrollX=-b.contentWidth*a.x-b.contentLeft);goog.isNumber(a.y)&&(this.scrollY=-b.contentHeight*a.y-b.contentTop);a=this.scrollX+b.absoluteLeft;b=this.scrollY+b.absoluteTop;this.translate(a,b);this.options.gridPattern&&(this.options.gridPattern.setAttribute("x",a),this.options.gridPattern.setAttribute("y",b),goog.userAgent.IE&&
+this.updateGridPattern_())};Blockly.addChangeListener=function(a){console.warn("Deprecated call to Blockly.addChangeListener, use workspace.addChangeListener instead.");return Blockly.getMainWorkspace().addChangeListener(a)};Blockly.getMainWorkspace=function(){return Blockly.mainWorkspace};goog.global.console||(goog.global.console={log:function(){},warn:function(){}});goog.global.Blockly||(goog.global.Blockly={});goog.global.Blockly.getMainWorkspace=Blockly.getMainWorkspace;
+goog.global.Blockly.addChangeListener=Blockly.addChangeListener;

File diff suppressed because it is too large
+ 586 - 0
blockly/blockly_uncompressed.js


+ 15 - 0
blockly/blocks/blockscad_changes

@@ -0,0 +1,15 @@
+// blockscad_changes - blockly/blocks
+// lists all changes from Blockly standard used for Blockscad
+// when updating Blockly, make sure to reapply these changes to new files
+// or else!
+// jayod
+
+// added files: geom_set_ops.js     contains union, diff, intersection, hull blocks (mutators)
+// 				primitives.js		contains primitives, transformations, other blocks?
+
+// files with changes:
+//		colour.js:   in colour_picker block, change FieldColour to '#ffcc00'  // do we really want to keep doing this?
+
+//		procedures.js: the whole thing has changed.  Assume all changes are for us.
+// 		loops.js:		added typing code.  Do not remove.
+

+ 113 - 0
blockly/blocks/colour.js

@@ -0,0 +1,113 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Colour blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.colour');
+
+goog.require('Blockly.Blocks');
+
+
+
+Blockly.Blocks['colour_picker'] = {
+  /**
+   * Block for colour picker.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.COLOUR_PICKER_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR');
+    this.setOutput(true, 'Colour');
+    this.setTooltip(Blockly.Msg.COLOUR_PICKER_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['colour_random'] = {
+  /**
+   * Block for random colour.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.COLOUR_RANDOM_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.COLOUR_RANDOM_TITLE);
+    this.setOutput(true, 'Colour');
+    this.setTooltip(Blockly.Msg.COLOUR_RANDOM_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['colour_rgb'] = {
+  /**
+   * Block for composing a colour from RGB components.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.COLOUR_RGB_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendValueInput('RED')
+        .setCheck('Number')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.COLOUR_RGB_TITLE)
+        .appendField(Blockly.Msg.COLOUR_RGB_RED);
+    this.appendValueInput('GREEN')
+        .setCheck('Number')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.COLOUR_RGB_GREEN);
+    this.appendValueInput('BLUE')
+        .setCheck('Number')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.COLOUR_RGB_BLUE);
+    this.setOutput(true, 'Colour');
+    this.setTooltip(Blockly.Msg.COLOUR_RGB_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['colour_blend'] = {
+  /**
+   * Block for blending two colours together.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.COLOUR_BLEND_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendValueInput('COLOUR1')
+        .setCheck('Colour')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.COLOUR_BLEND_TITLE)
+        .appendField(Blockly.Msg.COLOUR_BLEND_COLOUR1);
+    this.appendValueInput('COLOUR2')
+        .setCheck('Colour')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.COLOUR_BLEND_COLOUR2);
+    this.appendValueInput('RATIO')
+        .setCheck('Number')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.COLOUR_BLEND_RATIO);
+    this.setOutput(true, 'Colour');
+    this.setTooltip(Blockly.Msg.COLOUR_BLEND_TOOLTIP);
+  }
+};

+ 320 - 0
blockly/blocks/geom_set_ops.js

@@ -0,0 +1,320 @@
+// geom_set_ops.js
+// contains the definitions of the Geometric Set operations 
+// union, difference, intersection, and hull
+// These blocks are made with mutators that allow them to 
+// be expanded by the user to take more than the default two
+// object arguments.
+// 
+goog.require('Blockly.Blocks');
+//goog.require('Blockly.MutatorPlus');
+
+Blockly.Blocks['union'] = {
+  init: function() {
+    this.category = 'SET_OP';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_SETOP);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.UNION);
+    this.appendStatementInput("A")
+        .setCheck(["CSG","CAG"]);
+    this.appendStatementInput("PLUS0")
+        .appendField(Blockscad.Msg.PLUS)
+        .setCheck(["CSG","CAG"]);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ["CSG","CAG"]);
+    this.setTooltip(Blockscad.Msg.UNION_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .appendField(Blockscad.Msg.PLUS)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  },
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .appendField(Blockscad.Msg.PLUS)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },
+  setType: function(type) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    //console.log("setting union type to",type);
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    this.getInput('PLUS0').connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }
+  }   
+}; 
+Blockly.Blocks['difference'] = {
+  init: function() {
+    this.category = 'SET_OP';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_SETOP);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.DIFFERENCE);
+    this.appendStatementInput("A")
+        .setCheck(["CSG",'CAG']);
+    this.appendStatementInput("MINUS0")
+        .appendField(Blockscad.Msg.MINUS)
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.DIFFERENCE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.minusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.minusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.minusCount_) {
+        container.setAttribute('minus',this.minusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.minusCount_ = parseInt(xmlElement.getAttribute('minus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.minusCount_; x++) {
+        this.appendStatementInput('MINUS' + x)
+            .appendField(Blockscad.Msg.MINUS)
+            .setCheck(mytype);
+    }
+    if (this.minusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  },
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.minusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var minusInput = this.appendStatementInput('MINUS' + this.minusCount_)
+          .appendField(Blockscad.Msg.MINUS)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('MINUS' + this.minusCount_); 
+      this.minusCount_--;
+    }
+    if (this.minusCount_ >= 1) {
+      if (this.minusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }, 
+  setType: function(type) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    //console.log("setting diff type to",type);
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    this.getInput('MINUS0').connection.setCheck(type);
+    for (var i = 1; i
+        <= this.minusCount_; i++) {
+      this.getInput('MINUS' + i).connection.setCheck(type);
+    }
+  }   
+};
+Blockly.Blocks['intersection'] = {
+  init: function() {
+    this.category = 'SET_OP';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_SETOP);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.INTERSECTION);
+    this.appendStatementInput("A")
+        .setCheck(["CSG","CAG"]);
+    this.appendStatementInput("WITH0")
+        .appendField(Blockscad.Msg.WITH)
+        .setCheck(["CSG","CAG"]);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ["CSG",'CAG']);
+    this.setTooltip(Blockscad.Msg.INTERSECTION_TOOLTIP);
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.withCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.withCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.withCount_) {
+        container.setAttribute('with',this.withCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.withCount_ = parseInt(xmlElement.getAttribute('with'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.withCount_; x++) {
+        this.appendStatementInput('WITH' + x)
+            .appendField(Blockscad.Msg.WITH)
+            .setCheck(mytype);
+    }
+    if (this.withCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  },
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.withCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var withInput = this.appendStatementInput('WITH' + this.withCount_)
+          .appendField(Blockscad.Msg.WITH)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('WITH' + this.withCount_); 
+      this.withCount_--;
+    }
+    if (this.withCount_ >= 1) {
+      if (this.withCount_ == 1) {
+          this.setMutatorMinus(new Blockly.MutatorMinus(this));
+          this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },
+  setType: function(type) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    //console.log("setting intersect type to",type);
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    this.getInput('WITH0').connection.setCheck(type);
+    for (var i = 1; i
+            <= this.withCount_; i++) {
+      this.getInput('WITH' + i).connection.setCheck(type);
+    }
+  }      
+};
+
+Blockly.Blocks['hull'] = {
+  init: function() {
+    this.category = 'SET_OP';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_SETOP);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.CONVEX_HULL);
+    this.appendStatementInput("A")
+        .setCheck(["CSG","CAG"]);
+    this.appendStatementInput("WITH0")
+        .appendField(Blockscad.Msg.WITH)
+        .setCheck(["CSG","CAG"]);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ["CSG","CAG"]);
+    this.setTooltip(Blockscad.Msg.HULL_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.withCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.withCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.withCount_) {
+        container.setAttribute('with',this.withCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.withCount_ = parseInt(xmlElement.getAttribute('with'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.withCount_; x++) {
+        this.appendStatementInput('WITH' + x)
+            .appendField(Blockscad.Msg.WITH)
+            .setCheck(mytype);
+    }
+    if (this.withCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  },
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.withCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var withInput = this.appendStatementInput('WITH' + this.withCount_)
+          .appendField(Blockscad.Msg.WITH)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('WITH' + this.withCount_); 
+      this.withCount_--;
+    }
+    if (this.withCount_ >= 1) {
+      if (this.withCount_ == 1) {
+          this.setMutatorMinus(new Blockly.MutatorMinus(this));
+          this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }, 
+  setType: function(type) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    //console.log("setting union type to",type);
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    this.getInput('WITH0').connection.setCheck(type);
+    for (var i = 1; i <= this.withCount_; i++) {
+      this.getInput('WITH' + i).connection.setCheck(type);
+    }
+  } 
+};

+ 706 - 0
blockly/blocks/lists.js

@@ -0,0 +1,706 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 List blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.lists');
+
+goog.require('Blockly.Blocks');
+Blockscad.Toolbox = Blockscad.Toolbox || {};
+
+
+Blockly.Blocks.lists.HUE = Blockscad.Toolbox.HEX_LOGIC;
+
+Blockly.Blocks['lists_create_empty'] = {
+  /**
+   * Block for creating an empty list.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.setOutput(true, 'Array');
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);
+    this.setTooltip(Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['lists_create_with'] = {
+  /**
+   * Block for creating a list with any number of elements of any type.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.LISTS_CREATE_WITH_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.itemCount_ = 3;
+    this.updateShape_();
+    this.setOutput(true, 'Array');
+    this.setMutator(new Blockly.Mutator(['lists_create_with_item']));
+    this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP);
+  },
+  /**
+   * Create XML to represent list inputs.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('items', this.itemCount_);
+    return container;
+  },
+  /**
+   * Parse XML to restore the list inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
+    this.updateShape_();
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock =
+        Blockly.Block.obtain(workspace, 'lists_create_with_container');
+    containerBlock.initSvg();
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 0; i < this.itemCount_; i++) {
+      var itemBlock = Blockly.Block.obtain(workspace, 'lists_create_with_item');
+      itemBlock.initSvg();
+      connection.connect(itemBlock.previousConnection);
+      connection = itemBlock.nextConnection;
+    }
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    // Count number of inputs.
+    var connections = [];
+    var i = 0;
+    while (itemBlock) {
+      connections[i] = itemBlock.valueConnection_;
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+      i++;
+    }
+    this.itemCount_ = i;
+    this.updateShape_();
+    // Reconnect any child blocks.
+    for (var i = 0; i < this.itemCount_; i++) {
+      if (connections[i]) {
+        this.getInput('ADD' + i).connection.connect(connections[i]);
+      }
+    }
+  },
+  /**
+   * Store pointers to any connected child blocks.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  saveConnections: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    var i = 0;
+    while (itemBlock) {
+      var input = this.getInput('ADD' + i);
+      itemBlock.valueConnection_ = input && input.connection.targetConnection;
+      i++;
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+    }
+  },
+  /**
+   * Modify this block to have the correct number of inputs.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function() {
+    // Delete everything.
+    if (this.getInput('EMPTY')) {
+      this.removeInput('EMPTY');
+    } else {
+      var i = 0;
+      while (this.getInput('ADD' + i)) {
+        this.removeInput('ADD' + i);
+        i++;
+      }
+    }
+    // Rebuild block.
+    if (this.itemCount_ == 0) {
+      this.appendDummyInput('EMPTY')
+          .appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);
+    } else {
+      for (var i = 0; i < this.itemCount_; i++) {
+        var input = this.appendValueInput('ADD' + i);
+        if (i == 0) {
+          input.appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH);
+        }
+      }
+    }
+  }
+};
+
+Blockly.Blocks['lists_create_with_container'] = {
+  /**
+   * Mutator block for list container.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);
+    this.appendStatementInput('STACK');
+    this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['lists_create_with_item'] = {
+  /**
+   * Mutator bolck for adding items.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['lists_repeat'] = {
+  /**
+   * Block for creating a list with one element repeated.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.jsonInit({
+      "message0": Blockly.Msg.LISTS_REPEAT_TITLE,
+      "args0": [
+        {
+          "type": "input_value",
+          "name": "ITEM"
+        },
+        {
+          "type": "input_value",
+          "name": "NUM",
+          "check": "Number"
+        }
+      ],
+      "output": "Array",
+      "colour": Blockly.Blocks.lists.HUE,
+      "tooltip": Blockly.Msg.LISTS_REPEAT_TOOLTIP,
+      "helpUrl": Blockly.Msg.LISTS_REPEAT_HELPURL
+    });
+  }
+};
+
+Blockly.Blocks['lists_length'] = {
+  /**
+   * Block for list length.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.jsonInit({
+      "message0": Blockly.Msg.LISTS_LENGTH_TITLE,
+      "args0": [
+        {
+          "type": "input_value",
+          "name": "VALUE",
+          "check": ['String', 'Array']
+        }
+      ],
+      "output": 'Number',
+      "colour": Blockly.Blocks.lists.HUE,
+      "tooltip": Blockly.Msg.LISTS_LENGTH_TOOLTIP,
+      "helpUrl": Blockly.Msg.LISTS_LENGTH_HELPURL
+    });
+  }
+};
+
+Blockly.Blocks['lists_isEmpty'] = {
+  /**
+   * Block for is the list empty?
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.jsonInit({
+      "message0": Blockly.Msg.LISTS_ISEMPTY_TITLE,
+      "args0": [
+        {
+          "type": "input_value",
+          "name": "VALUE",
+          "check": ['String', 'Array']
+        }
+      ],
+      "output": 'Boolean',
+      "colour": Blockly.Blocks.lists.HUE,
+      "tooltip": Blockly.Msg.LISTS_ISEMPTY_TOOLTIP,
+      "helpUrl": Blockly.Msg.LISTS_ISEMPTY_HELPURL
+    });
+  }
+};
+
+Blockly.Blocks['lists_indexOf'] = {
+  /**
+   * Block for finding an item in the list.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.LISTS_INDEX_OF_FIRST, 'FIRST'],
+         [Blockly.Msg.LISTS_INDEX_OF_LAST, 'LAST']];
+    this.setHelpUrl(Blockly.Msg.LISTS_INDEX_OF_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.setOutput(true, 'Number');
+    this.appendValueInput('VALUE')
+        .setCheck('Array')
+        .appendField(Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST);
+    this.appendValueInput('FIND')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'END');
+    this.setInputsInline(true);
+    this.setTooltip(Blockly.Msg.LISTS_INDEX_OF_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['lists_getIndex'] = {
+  /**
+   * Block for getting element at index.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var MODE =
+        [[Blockly.Msg.LISTS_GET_INDEX_GET, 'GET'],
+         [Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE, 'GET_REMOVE'],
+         [Blockly.Msg.LISTS_GET_INDEX_REMOVE, 'REMOVE']];
+    this.WHERE_OPTIONS =
+        [[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'],
+         [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'],
+         [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'],
+         [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'],
+         [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM']];
+    this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    var modeMenu = new Blockly.FieldDropdown(MODE, function(value) {
+      var isStatement = (value == 'REMOVE');
+      this.sourceBlock_.updateStatement_(isStatement);
+    });
+    this.appendValueInput('VALUE')
+        .setCheck('Array')
+        .appendField(Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST);
+    this.appendDummyInput()
+        .appendField(modeMenu, 'MODE')
+        .appendField('', 'SPACE');
+    this.appendDummyInput('AT');
+    if (Blockly.Msg.LISTS_GET_INDEX_TAIL) {
+      this.appendDummyInput('TAIL')
+          .appendField(Blockly.Msg.LISTS_GET_INDEX_TAIL);
+    }
+    this.setInputsInline(true);
+    this.setOutput(true);
+    this.updateAt_(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var combo = thisBlock.getFieldValue('MODE') + '_' +
+          thisBlock.getFieldValue('WHERE');
+      return Blockly.Msg['LISTS_GET_INDEX_TOOLTIP_' + combo];
+    });
+  },
+  /**
+   * Create XML to represent whether the block is a statement or a value.
+   * Also represent whether there is an 'AT' input.
+   * @return {Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    var isStatement = !this.outputConnection;
+    container.setAttribute('statement', isStatement);
+    var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at', isAt);
+    return container;
+  },
+  /**
+   * Parse XML to restore the 'AT' input.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    // Note: Until January 2013 this block did not have mutations,
+    // so 'statement' defaults to false and 'at' defaults to true.
+    var isStatement = (xmlElement.getAttribute('statement') == 'true');
+    this.updateStatement_(isStatement);
+    var isAt = (xmlElement.getAttribute('at') != 'false');
+    this.updateAt_(isAt);
+  },
+  /**
+   * Switch between a value block and a statement block.
+   * @param {boolean} newStatement True if the block should be a statement.
+   *     False if the block should be a value.
+   * @private
+   * @this Blockly.Block
+   */
+  updateStatement_: function(newStatement) {
+    var oldStatement = !this.outputConnection;
+    if (newStatement != oldStatement) {
+      this.unplug(true, true);
+      if (newStatement) {
+        this.setOutput(false);
+        this.setPreviousStatement(true);
+        this.setNextStatement(true);
+      } else {
+        this.setPreviousStatement(false);
+        this.setNextStatement(false);
+        this.setOutput(true);
+      }
+    }
+  },
+  /**
+   * Create or delete an input for the numeric index.
+   * @param {boolean} isAt True if the input should exist.
+   * @private
+   * @this Blockly.Block
+   */
+  updateAt_: function(isAt) {
+    // Destroy old 'AT' and 'ORDINAL' inputs.
+    this.removeInput('AT');
+    this.removeInput('ORDINAL', true);
+    // Create either a value 'AT' input or a dummy input.
+    if (isAt) {
+      this.appendValueInput('AT').setCheck('Number');
+      if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
+        this.appendDummyInput('ORDINAL')
+            .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
+      }
+    } else {
+      this.appendDummyInput('AT');
+    }
+    var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
+      var newAt = (value == 'FROM_START') || (value == 'FROM_END');
+      // The 'isAt' variable is available due to this function being a closure.
+      if (newAt != isAt) {
+        var block = this.sourceBlock_;
+        block.updateAt_(newAt);
+        // This menu has been destroyed and replaced.  Update the replacement.
+        block.setFieldValue(value, 'WHERE');
+        return null;
+      }
+      return undefined;
+    });
+    this.getInput('AT').appendField(menu, 'WHERE');
+    if (Blockly.Msg.LISTS_GET_INDEX_TAIL) {
+      this.moveInputBefore('TAIL', null);
+    }
+  }
+};
+
+Blockly.Blocks['lists_setIndex'] = {
+  /**
+   * Block for setting the element at index.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var MODE =
+        [[Blockly.Msg.LISTS_SET_INDEX_SET, 'SET'],
+         [Blockly.Msg.LISTS_SET_INDEX_INSERT, 'INSERT']];
+    this.WHERE_OPTIONS =
+        [[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'],
+         [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'],
+         [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'],
+         [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'],
+         [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM']];
+    this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.appendValueInput('LIST')
+        .setCheck('Array')
+        .appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown(MODE), 'MODE')
+        .appendField('', 'SPACE');
+    this.appendDummyInput('AT');
+    this.appendValueInput('TO')
+        .appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_TO);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.LISTS_SET_INDEX_TOOLTIP);
+    this.updateAt_(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var combo = thisBlock.getFieldValue('MODE') + '_' +
+          thisBlock.getFieldValue('WHERE');
+      return Blockly.Msg['LISTS_SET_INDEX_TOOLTIP_' + combo];
+    });
+  },
+  /**
+   * Create XML to represent whether there is an 'AT' input.
+   * @return {Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at', isAt);
+    return container;
+  },
+  /**
+   * Parse XML to restore the 'AT' input.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    // Note: Until January 2013 this block did not have mutations,
+    // so 'at' defaults to true.
+    var isAt = (xmlElement.getAttribute('at') != 'false');
+    this.updateAt_(isAt);
+  },
+  /**
+   * Create or delete an input for the numeric index.
+   * @param {boolean} isAt True if the input should exist.
+   * @private
+   * @this Blockly.Block
+   */
+  updateAt_: function(isAt) {
+    // Destroy old 'AT' and 'ORDINAL' input.
+    this.removeInput('AT');
+    this.removeInput('ORDINAL', true);
+    // Create either a value 'AT' input or a dummy input.
+    if (isAt) {
+      this.appendValueInput('AT').setCheck('Number');
+      if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
+        this.appendDummyInput('ORDINAL')
+            .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
+      }
+    } else {
+      this.appendDummyInput('AT');
+    }
+    var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
+      var newAt = (value == 'FROM_START') || (value == 'FROM_END');
+      // The 'isAt' variable is available due to this function being a closure.
+      if (newAt != isAt) {
+        var block = this.sourceBlock_;
+        block.updateAt_(newAt);
+        // This menu has been destroyed and replaced.  Update the replacement.
+        block.setFieldValue(value, 'WHERE');
+        return null;
+      }
+      return undefined;
+    });
+    this.moveInputBefore('AT', 'TO');
+    if (this.getInput('ORDINAL')) {
+      this.moveInputBefore('ORDINAL', 'TO');
+    }
+
+    this.getInput('AT').appendField(menu, 'WHERE');
+  }
+};
+
+Blockly.Blocks['lists_getSublist'] = {
+  /**
+   * Block for getting sublist.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this['WHERE_OPTIONS_1'] =
+        [[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START, 'FROM_START'],
+         [Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END, 'FROM_END'],
+         [Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST, 'FIRST']];
+    this['WHERE_OPTIONS_2'] =
+        [[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START, 'FROM_START'],
+         [Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END, 'FROM_END'],
+         [Blockly.Msg.LISTS_GET_SUBLIST_END_LAST, 'LAST']];
+    this.setHelpUrl(Blockly.Msg.LISTS_GET_SUBLIST_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.appendValueInput('LIST')
+        .setCheck('Array')
+        .appendField(Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);
+    this.appendDummyInput('AT1');
+    this.appendDummyInput('AT2');
+    if (Blockly.Msg.LISTS_GET_SUBLIST_TAIL) {
+      this.appendDummyInput('TAIL')
+          .appendField(Blockly.Msg.LISTS_GET_SUBLIST_TAIL);
+    }
+    this.setInputsInline(true);
+    this.setOutput(true, 'Array');
+    this.updateAt_(1, true);
+    this.updateAt_(2, true);
+    this.setTooltip(Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP);
+  },
+  /**
+   * Create XML to represent whether there are 'AT' inputs.
+   * @return {Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    var isAt1 = this.getInput('AT1').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at1', isAt1);
+    var isAt2 = this.getInput('AT2').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at2', isAt2);
+    return container;
+  },
+  /**
+   * Parse XML to restore the 'AT' inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    var isAt1 = (xmlElement.getAttribute('at1') == 'true');
+    var isAt2 = (xmlElement.getAttribute('at2') == 'true');
+    this.updateAt_(1, isAt1);
+    this.updateAt_(2, isAt2);
+  },
+  /**
+   * Create or delete an input for a numeric index.
+   * This block has two such inputs, independant of each other.
+   * @param {number} n Specify first or second input (1 or 2).
+   * @param {boolean} isAt True if the input should exist.
+   * @private
+   * @this Blockly.Block
+   */
+  updateAt_: function(n, isAt) {
+    // Create or delete an input for the numeric index.
+    // Destroy old 'AT' and 'ORDINAL' inputs.
+    this.removeInput('AT' + n);
+    this.removeInput('ORDINAL' + n, true);
+    // Create either a value 'AT' input or a dummy input.
+    if (isAt) {
+      this.appendValueInput('AT' + n).setCheck('Number');
+      if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
+        this.appendDummyInput('ORDINAL' + n)
+            .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
+      }
+    } else {
+      this.appendDummyInput('AT' + n);
+    }
+    var menu = new Blockly.FieldDropdown(this['WHERE_OPTIONS_' + n],
+        function(value) {
+      var newAt = (value == 'FROM_START') || (value == 'FROM_END');
+      // The 'isAt' variable is available due to this function being a closure.
+      if (newAt != isAt) {
+        var block = this.sourceBlock_;
+        block.updateAt_(n, newAt);
+        // This menu has been destroyed and replaced.  Update the replacement.
+        block.setFieldValue(value, 'WHERE' + n);
+        return null;
+      }
+      return undefined;
+    });
+    this.getInput('AT' + n)
+        .appendField(menu, 'WHERE' + n);
+    if (n == 1) {
+      this.moveInputBefore('AT1', 'AT2');
+      if (this.getInput('ORDINAL1')) {
+        this.moveInputBefore('ORDINAL1', 'AT2');
+      }
+    }
+    if (Blockly.Msg.LISTS_GET_SUBLIST_TAIL) {
+      this.moveInputBefore('TAIL', null);
+    }
+  }
+};
+
+Blockly.Blocks['lists_split'] = {
+  /**
+   * Block for splitting text into a list, or joining a list into text.
+   * @this Blockly.Block
+   */
+  init: function() {
+    // Assign 'this' to a variable for use in the closures below.
+    var thisBlock = this;
+    var dropdown = new Blockly.FieldDropdown(
+        [[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT, 'SPLIT'],
+         [Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST, 'JOIN']],
+        function(newMode) {
+          thisBlock.updateType_(newMode);
+        });
+    this.setHelpUrl(Blockly.Msg.LISTS_SPLIT_HELPURL);
+    this.setColour(Blockly.Blocks.lists.HUE);
+    this.appendValueInput('INPUT')
+        .setCheck('String')
+        .appendField(dropdown, 'MODE');
+    this.appendValueInput('DELIM')
+        .setCheck('String')
+        .appendField(Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER);
+    this.setInputsInline(true);
+    this.setOutput(true, 'Array');
+    this.setTooltip(function() {
+      var mode = thisBlock.getFieldValue('MODE');
+      if (mode == 'SPLIT') {
+        return Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT;
+      } else if (mode == 'JOIN') {
+        return Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN;
+      }
+      throw 'Unknown mode: ' + mode;
+    });
+  },
+  /**
+   * Modify this block to have the correct input and output types.
+   * @param {string} newMode Either 'SPLIT' or 'JOIN'.
+   * @private
+   * @this Blockly.Block
+   */
+  updateType_: function(newMode) {
+    if (newMode == 'SPLIT') {
+      this.outputConnection.setCheck('Array');
+      this.getInput('INPUT').setCheck('String');
+    } else {
+      this.outputConnection.setCheck('String');
+      this.getInput('INPUT').setCheck('Array');
+    }
+  },
+  /**
+   * Create XML to represent the input and output types.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('mode', this.getFieldValue('MODE'));
+    return container;
+  },
+  /**
+   * Parse XML to restore the input and output types.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.updateType_(xmlElement.getAttribute('mode'));
+  }
+};

+ 433 - 0
blockly/blocks/logic.js

@@ -0,0 +1,433 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Logic blocks for Blockly.
+ * @author q.neutron@gmail.com (Quynh Neutron)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.logic');
+
+goog.require('Blockly.Blocks');
+
+
+Blockly.Blocks['controls_if'] = {
+  /**
+   * Block for if/elseif/else condition.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.appendValueInput('IF0')
+        .setCheck(['Boolean','Number'])
+        .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
+    this.appendStatementInput('DO0')
+        .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN)
+        .setCheck(['CSG','CAG']);
+    this.setPreviousStatement(true);
+    //this.setNextStatement(true);
+    this.setMutator(new Blockly.Mutator(['controls_if_elseif',
+                                         'controls_if_else']));
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) {
+        return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;
+      } else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) {
+        return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;
+      } else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) {
+        return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;
+      } else if (thisBlock.elseifCount_ && thisBlock.elseCount_) {
+        return Blockly.Msg.CONTROLS_IF_TOOLTIP_4;
+      }
+      return '';
+    });
+    this.elseifCount_ = 0;
+    this.elseCount_ = 0;
+  },
+  /**
+   * Create XML to represent the number of else-if and else inputs.
+   * @return {Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    if (!this.elseifCount_ && !this.elseCount_) {
+      return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.elseifCount_) {
+      container.setAttribute('elseif', this.elseifCount_);
+    }
+    if (this.elseCount_) {
+      container.setAttribute('else', 1);
+    }
+    return container;
+  },
+  /**
+   * Parse XML to restore the else-if and else inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0;
+    this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0;
+    for (var i = 1; i <= this.elseifCount_; i++) {
+      this.appendValueInput('IF' + i)
+          .setCheck('Boolean')
+          .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
+      this.appendStatementInput('DO' + i)
+          .setCheck(['CSG','CAG'])
+          .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
+    }
+    if (this.elseCount_) {
+      this.appendStatementInput('ELSE')
+          .setCheck(['CSG','CAG'])
+          .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
+    }
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock = Blockly.Block.obtain(workspace, 'controls_if_if');
+    containerBlock.initSvg();
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 1; i <= this.elseifCount_; i++) {
+      var elseifBlock = Blockly.Block.obtain(workspace, 'controls_if_elseif');
+      elseifBlock.initSvg();
+      connection.connect(elseifBlock.previousConnection);
+      connection = elseifBlock.nextConnection;
+    }
+    if (this.elseCount_) {
+      var elseBlock = Blockly.Block.obtain(workspace, 'controls_if_else');
+      elseBlock.initSvg();
+      connection.connect(elseBlock.previousConnection);
+    }
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    // Disconnect the else input blocks and remove the inputs.
+    if (this.elseCount_) {
+      this.removeInput('ELSE');
+    }
+    this.elseCount_ = 0;
+    // Disconnect all the elseif input blocks and remove the inputs.
+    for (var i = this.elseifCount_; i > 0; i--) {
+      this.removeInput('IF' + i);
+      this.removeInput('DO' + i);
+    }
+    this.elseifCount_ = 0;
+    // Rebuild the block's optional inputs.
+    var clauseBlock = containerBlock.getInputTargetBlock('STACK');
+    while (clauseBlock) {
+      switch (clauseBlock.type) {
+        case 'controls_if_elseif':
+          this.elseifCount_++;
+          var ifInput = this.appendValueInput('IF' + this.elseifCount_)
+              .setCheck('Boolean')
+              .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
+          var doInput = this.appendStatementInput('DO' + this.elseifCount_)
+              .setCheck(['CSG','CAG']);
+          doInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
+          // Reconnect any child blocks.
+          if (clauseBlock.valueConnection_) {
+            ifInput.connection.connect(clauseBlock.valueConnection_);
+          }
+          if (clauseBlock.statementConnection_) {
+            doInput.connection.connect(clauseBlock.statementConnection_);
+          }
+          break;
+        case 'controls_if_else':
+          this.elseCount_++;
+          var elseInput = this.appendStatementInput('ELSE')
+                .setCheck(['CSG','CAG']);
+          elseInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
+          // Reconnect any child blocks.
+          if (clauseBlock.statementConnection_) {
+            elseInput.connection.connect(clauseBlock.statementConnection_);
+          }
+          break;
+        default:
+          throw 'Unknown block type.';
+      }
+      clauseBlock = clauseBlock.nextConnection &&
+          clauseBlock.nextConnection.targetBlock();
+    }
+  },
+  /**
+   * Store pointers to any connected child blocks.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  saveConnections: function(containerBlock) {
+    var clauseBlock = containerBlock.getInputTargetBlock('STACK');
+    var i = 1;
+    while (clauseBlock) {
+      switch (clauseBlock.type) {
+        case 'controls_if_elseif':
+          var inputIf = this.getInput('IF' + i);
+          var inputDo = this.getInput('DO' + i);
+          clauseBlock.valueConnection_ =
+              inputIf && inputIf.connection.targetConnection;
+          clauseBlock.statementConnection_ =
+              inputDo && inputDo.connection.targetConnection;
+          i++;
+          break;
+        case 'controls_if_else':
+          var inputDo = this.getInput('ELSE');
+          clauseBlock.statementConnection_ =
+              inputDo && inputDo.connection.targetConnection;
+          break;
+        default:
+          throw 'Unknown block type.';
+      }
+      clauseBlock = clauseBlock.nextConnection &&
+          clauseBlock.nextConnection.targetBlock();
+    }
+  }
+};
+
+Blockly.Blocks['controls_if_if'] = {
+  /**
+   * Mutator block for if container.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF);
+    this.appendStatementInput('STACK');
+    this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['controls_if_elseif'] = {
+  /**
+   * Mutator bolck for else-if condition.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['controls_if_else'] = {
+  /**
+   * Mutator block for else condition.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE);
+    this.setPreviousStatement(true);
+    this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['logic_compare'] = {
+  /**
+   * Block for comparison operator.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS = this.RTL ? [
+          ['=', 'EQ'],
+          ['\u2260', 'NEQ'],
+          ['>', 'LT'],
+          ['\u2265', 'LTE'],
+          ['<', 'GT'],
+          ['\u2264', 'GTE']
+        ] : [
+          ['=', 'EQ'],
+          ['\u2260', 'NEQ'],
+          ['<', 'LT'],
+          ['\u2264', 'LTE'],
+          ['>', 'GT'],
+          ['\u2265', 'GTE']
+        ];
+    this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.setOutput(true, 'Boolean');
+    this.appendValueInput('A');
+     this.appendValueInput('B')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
+    this.setInputsInline(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var op = thisBlock.getFieldValue('OP');
+      var TOOLTIPS = {
+        'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,
+        'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,
+        'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,
+        'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,
+        'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,
+        'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE
+      };
+      return TOOLTIPS[op];
+    });
+    this.prevBlocks_ = [null, null];
+  },
+  /**
+   * Called whenever anything on the workspace changes.
+   * Prevent mismatched types from being compared.
+   * @this Blockly.Block
+   */
+  onchange: function() {
+    var blockA = this.getInputTargetBlock('A');
+    var blockB = this.getInputTargetBlock('B');
+    // Disconnect blocks that existed prior to this change if they don't match.
+    if (blockA && blockB &&
+        !blockA.outputConnection.checkType_(blockB.outputConnection)) {
+      // Mismatch between two inputs.  Disconnect previous and bump it away.
+      for (var i = 0; i < this.prevBlocks_.length; i++) {
+        var block = this.prevBlocks_[i];
+        if (block === blockA || block === blockB) {
+          block.setParent(null);
+          block.bumpNeighbours_();
+        }
+      }
+    }
+    this.prevBlocks_[0] = blockA;
+    this.prevBlocks_[1] = blockB;
+  }
+  
+};
+
+Blockly.Blocks['logic_operation'] = {
+  /**
+   * Block for logical operations: 'and', 'or'.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'],
+         [Blockly.Msg.LOGIC_OPERATION_OR, 'OR']];
+    this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.setOutput(true, 'Boolean');
+    this.appendValueInput('A')
+        .setCheck('Boolean');
+    this.appendValueInput('B')
+        .setCheck('Boolean')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
+    this.setInputsInline(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var op = thisBlock.getFieldValue('OP');
+      var TOOLTIPS = {
+        'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND,
+        'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR
+      };
+      return TOOLTIPS[op];
+    });
+  }
+};
+
+Blockly.Blocks['logic_negate'] = {
+  /**
+   * Block for negation.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.LOGIC_NEGATE_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.setOutput(true, 'Boolean');
+    this.interpolateMsg(Blockly.Msg.LOGIC_NEGATE_TITLE,
+                        ['BOOL', 'Boolean', Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setTooltip(Blockly.Msg.LOGIC_NEGATE_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['logic_boolean'] = {
+  /**
+   * Block for boolean data type: true and false.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var BOOLEANS =
+        [[Blockly.Msg.LOGIC_BOOLEAN_TRUE, 'TRUE'],
+         [Blockly.Msg.LOGIC_BOOLEAN_FALSE, 'FALSE']];
+    this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.setOutput(true, 'Boolean');
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown(BOOLEANS), 'BOOL');
+    this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['logic_null'] = {
+  /**
+   * Block for null data type.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.LOGIC_NULL_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.setOutput(true,'Boolean');
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.LOGIC_NULL);
+    this.setTooltip(Blockly.Msg.LOGIC_NULL_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['logic_ternary'] = {
+  /**
+   * Block for ternary operator.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOGIC);
+    this.appendValueInput('IF')
+        .setCheck('Boolean')
+        .appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);
+    this.appendValueInput('THEN')
+        // .setCheck('Number')
+        .appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);
+    this.appendValueInput('ELSE')
+        // .setCheck('Number')
+        .appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);
+    this.setOutput(true,'Number');
+    this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP);
+  }
+};

+ 412 - 0
blockly/blocks/loops.js

@@ -0,0 +1,412 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Loop blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.loops');
+
+goog.require('Blockly.Blocks');
+
+
+
+
+Blockly.Blocks['controls_repeat'] = {
+  /**
+   * Block for repeat n times (internal number).
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.CONTROLS_REPEAT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.CONTROLS_REPEAT_TITLE_REPEAT)
+        .appendField(new Blockly.FieldTextInput('10',
+            Blockly.FieldTextInput.nonnegativeIntegerValidator), 'TIMES')
+        .appendField(Blockly.Msg.CONTROLS_REPEAT_TITLE_TIMES);
+    this.appendStatementInput('DO')
+        .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.CONTROLS_REPEAT_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['controls_repeat_ext'] = {
+  /**
+   * Block for repeat n times (external number).
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.CONTROLS_REPEAT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.interpolateMsg(Blockly.Msg.CONTROLS_REPEAT_TITLE,
+                        ['TIMES', 'Number', Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('DO')
+        .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setInputsInline(true);
+    this.setTooltip(Blockly.Msg.CONTROLS_REPEAT_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['controls_whileUntil'] = {
+  /**
+   * Block for 'do while/until' loop.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE, 'WHILE'],
+         [Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL, 'UNTIL']];
+    this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.appendValueInput('BOOL')
+        .setCheck('Boolean')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE');
+    this.appendStatementInput('DO')
+        .appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var op = thisBlock.getFieldValue('MODE');
+      var TOOLTIPS = {
+        'WHILE': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE,
+        'UNTIL': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL
+      };
+      return TOOLTIPS[op];
+    });
+  }
+};
+
+Blockly.Blocks['controls_for'] = {
+  /**
+   * Block for 'for' loop.
+   * @this Blockly.Block
+   * this blockscad version has typing information 
+   */
+  init: function() {
+    this.category = 'LOOP';     // for Blockscad typing - jayod
+    this.setHelpUrl(Blockly.Msg.CONTROLS_FOR_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.jsonInit({
+      "message0": Blockly.Msg.CONTROLS_FOR_TITLE,
+      "args0": [
+        {
+          "type": "field_variable",
+          "name": "VAR",
+          "variable": null
+        },
+        {
+          "type": "input_value",
+          "name": "FROM",
+          "check": "Number",
+          "align": "RIGHT"
+        },
+        {
+          "type": "input_value",
+          "name": "TO",
+          "check": "Number",
+          "align": "RIGHT"
+        },
+        {
+          "type": "input_value",
+          "name": "BY",
+          "check": "Number",
+          "align": "RIGHT"
+        }
+      ],
+      "inputsInline": true,
+      "previousStatement": null,
+     });
+    this.appendDummyInput()
+        .appendField("(" + Blockscad.Msg.CONVEX_HULL)
+        .appendField(new Blockly.FieldCheckbox('FALSE'), 'HULL')
+        .appendField(")");
+    this.appendStatementInput('DO')
+        .appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO)
+        .setCheck(['CSG','CAG']);
+    this.setPreviousStatement(true,['CSG','CAG']);    // jayod - blockscad typing
+    //this.setNextStatement(true);                    // jayod - blockscad
+    this.setInputsInline(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace('%1',
+          thisBlock.getFieldValue('VAR')) + "\n" + Blockscad.Msg.CONTROLS_FOR_TOOLTIP_CHAINHULL;
+    });
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.<string>} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return [this.getFieldValue('VAR')];
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+      this.setFieldValue(newName, 'VAR');
+    }
+  },
+  /**
+   * Add menu option to create getter block for loop variable.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    if (!this.isCollapsed()) {
+      var option = {enabled: true};
+      var name = this.getFieldValue('VAR');
+      option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
+      var xmlField = goog.dom.createDom('field', null, name);
+      xmlField.setAttribute('name', 'VAR');
+      var xmlBlock = goog.dom.createDom('block', null, xmlField);
+      xmlBlock.setAttribute('type', 'variables_get');
+      option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+      options.push(option);
+    }
+  },
+  setType: function(type) {       // for blockscad typing - jayod
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    this.previousConnection.setCheck(type);
+    this.getInput('DO').connection.setCheck(type);
+  }   
+};
+
+Blockly.Blocks['controls_for_chainhull'] = {
+  /**
+   * Block for 'for' loop.
+   * @this Blockly.Block
+   * this blockscad version has typing information 
+   */
+  init: function() {
+    this.category = 'LOOP';     // for Blockscad typing - jayod
+    this.setHelpUrl(Blockly.Msg.CONTROLS_FOR_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.jsonInit({
+      "message0": Blockly.Msg.CONTROLS_FOR_TITLE,
+      "args0": [
+        {
+          "type": "field_variable",
+          "name": "VAR",
+          "variable": null
+        },
+        {
+          "type": "input_value",
+          "name": "FROM",
+          "check": "Number",
+          "align": "RIGHT"
+        },
+        {
+          "type": "input_value",
+          "name": "TO",
+          "check": "Number",
+          "align": "RIGHT"
+        },
+        {
+          "type": "input_value",
+          "name": "BY",
+          "check": "Number",
+          "align": "RIGHT"
+        }
+      ],
+      "inputsInline": true,
+      "previousStatement": null,
+     });
+
+    this.appendStatementInput('DO')
+        .appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO)
+        .setCheck(['CSG','CAG']);
+    this.setPreviousStatement(true,['CSG','CAG']);    // jayod - blockscad typing
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      return Blockscad.Msg.CONTROLS_FOR_TOOLTIP_CHAINHULL;
+    });
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.<string>} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return [this.getFieldValue('VAR')];
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+      this.setFieldValue(newName, 'VAR');
+    }
+  },
+  /**
+   * Add menu option to create getter block for loop variable.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    if (!this.isCollapsed()) {
+      var option = {enabled: true};
+      var name = this.getFieldValue('VAR');
+      option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
+      var xmlField = goog.dom.createDom('field', null, name);
+      xmlField.setAttribute('name', 'VAR');
+      var xmlBlock = goog.dom.createDom('block', null, xmlField);
+      xmlBlock.setAttribute('type', 'variables_get');
+      option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+      options.push(option);
+    }
+  },
+  setType: function(type) {       // for blockscad typing - jayod
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    this.previousConnection.setCheck(type);
+    this.getInput('DO').connection.setCheck(type);
+  }   
+};
+Blockly.Blocks['controls_forEach'] = {
+  /**
+   * Block for 'for each' loop.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.CONTROLS_FOREACH_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.appendValueInput('LIST')
+        .setCheck('Array')
+        .appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_ITEM)
+        .appendField(new Blockly.FieldVariable(null), 'VAR')
+        .appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_INLIST);
+    if (Blockly.Msg.CONTROLS_FOREACH_INPUT_INLIST_TAIL) {
+      this.appendDummyInput()
+          .appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_INLIST_TAIL);
+      this.setInputsInline(true);
+    }
+    this.appendStatementInput('DO')
+        .appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace('%1',
+          thisBlock.getFieldValue('VAR'));
+    });
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.<string>} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return [this.getFieldValue('VAR')];
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+      this.setFieldValue(newName, 'VAR');
+    }
+  },
+  customContextMenu: Blockly.Blocks['controls_for'].customContextMenu
+};
+
+Blockly.Blocks['controls_flow_statements'] = {
+  /**
+   * Block for flow statements: continue, break.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK, 'BREAK'],
+         [Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE, 'CONTINUE']];
+    this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_LOOP);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'FLOW');
+    this.setPreviousStatement(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var op = thisBlock.getFieldValue('FLOW');
+      var TOOLTIPS = {
+        'BREAK': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK,
+        'CONTINUE': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE
+      };
+      return TOOLTIPS[op];
+    });
+  },
+  /**
+   * Called whenever anything on the workspace changes.
+   * Add warning if this flow block is not nested inside a loop.
+   * @this Blockly.Block
+   */
+  onchange: function() {
+    var legal = false;
+    // Is the block nested in a loop?
+    var block = this;
+    do {
+      if (block.type == 'controls_repeat' ||
+          block.type == 'controls_repeat_ext' ||
+          block.type == 'controls_forEach' ||
+          block.type == 'controls_for' ||
+          block.type == 'controls_whileUntil') {
+        legal = true;
+        break;
+      }
+      block = block.getSurroundParent();
+    } while (block);
+    if (legal) {
+      this.setWarningText(null);
+    } else {
+      this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING);
+    }
+  }
+};

+ 465 - 0
blockly/blocks/math.js

@@ -0,0 +1,465 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Math blocks for Blockly.
+ * @author q.neutron@gmail.com (Quynh Neutron)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.math');
+
+goog.require('Blockly.Blocks');
+
+
+Blockly.Blocks['math_number'] = {
+  /**
+   * Block for numeric value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldNumber('0'), 'NUM');
+    this.setOutput(true, 'Number');
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    // Number block is trivial.  Use tooltip of parent block if it exists.
+    this.setTooltip(function() {
+      var parent = thisBlock.getParent();
+      return (parent && parent.getInputsInline() && parent.tooltip) ||
+          Blockly.Msg.MATH_NUMBER_TOOLTIP;
+    });
+  }
+};
+
+Blockly.Blocks['math_arithmetic'] = {
+  /**
+   * Block for basic arithmetic operator.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.MATH_ADDITION_SYMBOL, 'ADD'],
+         [Blockly.Msg.MATH_SUBTRACTION_SYMBOL, 'MINUS'],
+         [Blockly.Msg.MATH_MULTIPLICATION_SYMBOL, 'MULTIPLY'],
+         [Blockly.Msg.MATH_DIVISION_SYMBOL, 'DIVIDE'],
+         [Blockly.Msg.MATH_POWER_SYMBOL, 'POWER']];
+    this.setHelpUrl(Blockly.Msg.MATH_ARITHMETIC_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendValueInput('A')
+        .setCheck('Number');
+    this.appendValueInput('B')
+        .setCheck('Number')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
+    this.setInputsInline(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var mode = thisBlock.getFieldValue('OP');
+      var TOOLTIPS = {
+        'ADD': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD,
+        'MINUS': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS,
+        'MULTIPLY': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY,
+        'DIVIDE': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE,
+        'POWER': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER
+      };
+      return TOOLTIPS[mode];
+    });
+  }
+};
+
+Blockly.Blocks['math_single'] = {
+  /**
+   * Block for advanced math operators with single operand.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.MATH_SINGLE_OP_ROOT, 'ROOT'],
+         [Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE, 'ABS'],
+         ['-', 'NEG'],
+         ['ln', 'LN'],
+         ['log10', 'LOG10'],
+         ['e^', 'EXP'],
+         ['10^', 'POW10']];
+    this.setHelpUrl(Blockly.Msg.MATH_SINGLE_HELPURL);
+
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendValueInput('NUM')
+        .setCheck('Number')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var mode = thisBlock.getFieldValue('OP');
+      var TOOLTIPS = {
+        'ROOT': Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT,
+        'ABS': Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS,
+        'NEG': Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG,
+        'LN': Blockly.Msg.MATH_SINGLE_TOOLTIP_LN,
+        'LOG10': Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10,
+        'EXP': Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP,
+        'POW10': Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10
+      };
+      return TOOLTIPS[mode];
+    });
+  }
+};
+
+Blockly.Blocks['math_trig'] = {
+  /**
+   * Block for trigonometry operators.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.MATH_TRIG_SIN, 'SIN'],
+         [Blockly.Msg.MATH_TRIG_COS, 'COS'],
+         [Blockly.Msg.MATH_TRIG_TAN, 'TAN'],
+         [Blockly.Msg.MATH_TRIG_ASIN, 'ASIN'],
+         [Blockly.Msg.MATH_TRIG_ACOS, 'ACOS'],
+         [Blockly.Msg.MATH_TRIG_ATAN, 'ATAN']];
+    this.setHelpUrl(Blockly.Msg.MATH_TRIG_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendValueInput('NUM')
+        .setCheck('Number')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var mode = thisBlock.getFieldValue('OP');
+      var TOOLTIPS = {
+        'SIN': Blockly.Msg.MATH_TRIG_TOOLTIP_SIN,
+        'COS': Blockly.Msg.MATH_TRIG_TOOLTIP_COS,
+        'TAN': Blockly.Msg.MATH_TRIG_TOOLTIP_TAN,
+        'ASIN': Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN,
+        'ACOS': Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS,
+        'ATAN': Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN
+      };
+      return TOOLTIPS[mode];
+    });
+  }
+};
+
+Blockly.Blocks['math_constant'] = {
+  /**
+   * Block for constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var CONSTANTS =
+        [['\u03c0', 'PI'],
+         ['e', 'E'],
+         ['\u03c6', 'GOLDEN_RATIO'],
+         ['sqrt(2)', 'SQRT2'],
+         ['sqrt(\u00bd)', 'SQRT1_2'],
+         ['\u221e', 'INFINITY']];
+    this.setHelpUrl(Blockly.Msg.MATH_CONSTANT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown(CONSTANTS), 'CONSTANT');
+    this.setTooltip(Blockly.Msg.MATH_CONSTANT_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['math_number_property'] = {
+  /**
+   * Block for checking if a number is even, odd, prime, whole, positive,
+   * negative or if it is divisible by certain number.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var PROPERTIES =
+        [[Blockly.Msg.MATH_IS_EVEN, 'EVEN'],
+         [Blockly.Msg.MATH_IS_ODD, 'ODD'],
+         //[Blockly.Msg.MATH_IS_PRIME, 'PRIME'],
+         [Blockly.Msg.MATH_IS_WHOLE, 'WHOLE'],
+         [Blockly.Msg.MATH_IS_POSITIVE, 'POSITIVE'],
+         [Blockly.Msg.MATH_IS_NEGATIVE, 'NEGATIVE'],
+         [Blockly.Msg.MATH_IS_DIVISIBLE_BY, 'DIVISIBLE_BY']];
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.appendValueInput('NUMBER_TO_CHECK')
+        .setCheck('Number');
+    var dropdown = new Blockly.FieldDropdown(PROPERTIES, function(option) {
+      var divisorInput = (option == 'DIVISIBLE_BY');
+      this.sourceBlock_.updateShape_(divisorInput);
+    });
+    this.appendDummyInput()
+        .appendField(dropdown, 'PROPERTY');
+    this.setInputsInline(true);
+    this.setOutput(true, 'Boolean');
+    this.setTooltip(Blockly.Msg.MATH_IS_TOOLTIP);
+  },
+  /**
+   * Create XML to represent whether the 'divisorInput' should be present.
+   * @return {Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    var divisorInput = (this.getFieldValue('PROPERTY') == 'DIVISIBLE_BY');
+    container.setAttribute('divisor_input', divisorInput);
+    return container;
+  },
+  /**
+   * Parse XML to restore the 'divisorInput'.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    var divisorInput = (xmlElement.getAttribute('divisor_input') == 'true');
+    this.updateShape_(divisorInput);
+  },
+  /**
+   * Modify this block to have (or not have) an input for 'is divisible by'.
+   * @param {boolean} divisorInput True if this block has a divisor input.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function(divisorInput) {
+    // Add or remove a Value Input.
+    var inputExists = this.getInput('DIVISOR');
+    if (divisorInput) {
+      if (!inputExists) {
+        this.appendValueInput('DIVISOR')
+            .setCheck('Number');
+      }
+    } else if (inputExists) {
+      this.removeInput('DIVISOR');
+    }
+  }
+};
+
+Blockly.Blocks['math_change'] = {
+  /**
+   * Block for adding to a variable in place.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.MATH_CHANGE_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.interpolateMsg(
+        // TODO: Combine these messages instead of using concatenation.
+        Blockly.Msg.MATH_CHANGE_TITLE_CHANGE + ' %1 ' +
+        Blockly.Msg.MATH_CHANGE_INPUT_BY + ' %2',
+        ['VAR', new Blockly.FieldVariable(Blockly.Msg.MATH_CHANGE_TITLE_ITEM)],
+        ['DELTA', 'Number', Blockly.ALIGN_RIGHT],
+        Blockly.ALIGN_RIGHT);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      return Blockly.Msg.MATH_CHANGE_TOOLTIP.replace('%1',
+          thisBlock.getFieldValue('VAR'));
+    });
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.<string>} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return [this.getFieldValue('VAR')];
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+      this.setFieldValue(newName, 'VAR');
+    }
+  }
+};
+
+Blockly.Blocks['math_round'] = {
+  /**
+   * Block for rounding functions.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.MATH_ROUND_OPERATOR_ROUND, 'ROUND'],
+         [Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP, 'ROUNDUP'],
+         [Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN, 'ROUNDDOWN']];
+    this.setHelpUrl(Blockly.Msg.MATH_ROUND_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendValueInput('NUM')
+        .setCheck('Number')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
+    this.setTooltip(Blockly.Msg.MATH_ROUND_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['math_on_list'] = {
+  /**
+   * Block for evaluating a list of numbers to return sum, average, min, max,
+   * etc.  Some functions also work on text (min, max, mode, median).
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.MATH_ONLIST_OPERATOR_SUM, 'SUM'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_MIN, 'MIN'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_MAX, 'MAX'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE, 'AVERAGE'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN, 'MEDIAN'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_MODE, 'MODE'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV, 'STD_DEV'],
+         [Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM, 'RANDOM']];
+    // Assign 'this' to a variable for use in the closures below.
+    var thisBlock = this;
+    this.setHelpUrl(Blockly.Msg.MATH_ONLIST_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    var dropdown = new Blockly.FieldDropdown(OPERATORS, function(newOp) {
+      thisBlock.updateType_(newOp);
+    });
+    this.appendValueInput('LIST')
+        .setCheck('Array')
+        .appendField(dropdown, 'OP');
+    this.setTooltip(function() {
+      var mode = thisBlock.getFieldValue('OP');
+      var TOOLTIPS = {
+        'SUM': Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM,
+        'MIN': Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN,
+        'MAX': Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX,
+        'AVERAGE': Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE,
+        'MEDIAN': Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN,
+        'MODE': Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE,
+        'STD_DEV': Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV,
+        'RANDOM': Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM
+      };
+      return TOOLTIPS[mode];
+    });
+  },
+  /**
+   * Modify this block to have the correct output type.
+   * @param {string} newOp Either 'MODE' or some op than returns a number.
+   * @private
+   * @this Blockly.Block
+   */
+  updateType_: function(newOp) {
+    if (newOp == 'MODE') {
+      this.outputConnection.setCheck('Array');
+    } else {
+      this.outputConnection.setCheck('Number');
+    }
+  },
+  /**
+   * Create XML to represent the output type.
+   * @return {Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('op', this.getFieldValue('OP'));
+    return container;
+  },
+  /**
+   * Parse XML to restore the output type.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.updateType_(xmlElement.getAttribute('op'));
+  }
+};
+
+Blockly.Blocks['math_modulo'] = {
+  /**
+   * Block for remainder of a division.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.MATH_MODULO_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.interpolateMsg(Blockly.Msg.MATH_MODULO_TITLE,
+                        ['DIVIDEND', 'Number', Blockly.ALIGN_RIGHT],
+                        ['DIVISOR', 'Number', Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setTooltip(Blockly.Msg.MATH_MODULO_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['math_constrain'] = {
+  /**
+   * Block for constraining a number between two limits.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.MATH_CONSTRAIN_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.interpolateMsg(Blockly.Msg.MATH_CONSTRAIN_TITLE,
+                        ['VALUE', 'Number', Blockly.ALIGN_RIGHT],
+                        ['LOW', 'Number', Blockly.ALIGN_RIGHT],
+                        ['HIGH', 'Number', Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setTooltip(Blockly.Msg.MATH_CONSTRAIN_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['math_random_int'] = {
+  /**
+   * Block for random integer between [X] and [Y].
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.MATH_RANDOM_INT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.interpolateMsg(Blockly.Msg.MATH_RANDOM_INT_TITLE,
+                        ['FROM', 'Number', Blockly.ALIGN_RIGHT],
+                        ['TO', 'Number', Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setTooltip(Blockly.Msg.MATH_RANDOM_INT_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['math_random_float'] = {
+  /**
+   * Block for random fraction between 0 and 1.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM);
+    this.setTooltip(Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP);
+  }
+};

+ 1893 - 0
blockly/blocks/primitives.js

@@ -0,0 +1,1893 @@
+goog.require('Blockly.Blocks');
+//goog.require('Blockly.MutatorPlus');
+
+Blockly.Blocks['sphere'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.SPHERE + "  ");
+    this.appendValueInput("RAD")
+        .setCheck("Number")
+        .appendField(Blockscad.Msg.RADIUS)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.SPHERE_TOOLTIP);
+  }//,
+  // onchange: function() {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }    
+  //   // if one of the value fields is missing, I want to pop up a warning.
+  //   var val = this.getInput("RAD").connection;
+  //   if (!val.targetConnection)
+  //     this.setWarningText("Sphere requires a radius to be set");
+  //   else this.setWarningText(null);
+  // }
+};
+// Blockly.Blocks['sphere'] = {
+//   init: function() {
+//     this.category = 'PRIMITIVE_CSG'
+//     this.setHelpUrl('http://www.example.com/');
+//     this.setColourHex(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+//     this.appendDummyInput()
+//         .appendField(Blockscad.Msg.SPHERE + "  ");
+//     this.appendValueInput("RAD")
+//         .setCheck("Number")
+//         .appendField(Blockscad.Msg.RADIUS)
+//         .setAlign(Blockly.ALIGN_RIGHT);
+//     this.setInputsInline(true);
+//     this.setPreviousStatement(true, 'CSG');
+//     this.setTooltip(Blockscad.Msg.SPHERE_TOOLTIP);
+//   }//,
+// };
+
+
+Blockly.Blocks['cylinder'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG';
+    this.prevR1 = null;
+    this.prevR2 = null;
+    this.pR1id = null;
+    this.pR2id = null;
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.CYLINDER + '  ');
+    this.appendValueInput('RAD1')
+        .setCheck('Number')    
+        .appendField(Blockscad.Msg.RADIUS + '1')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    // handle backwards compatibility for cylinders created before locking.
+    if (Blockscad.inputVersion == null || Blockscad.inputVersion == "1.0.0"
+        || Blockscad.inputVersion == "1.0.1" || Blockscad.inputVersion == "1.1.0") {
+      this.appendDummyInput()
+          .setAlign(Blockly.ALIGN_RIGHT)
+          .appendField(new Blockly.FieldCheckbox("FALSE", null,
+            "imgs/lock_icon.png","imgs/unlock_icon.png"), "LOCKED");
+    }
+    else {
+      this.appendDummyInput()
+          .setAlign(Blockly.ALIGN_RIGHT)
+          .appendField(new Blockly.FieldCheckbox("TRUE", null,
+            "imgs/lock_icon.png","imgs/unlock_icon.png"), "LOCKED") ;    
+    }
+    // this.appendDummyInput()
+    //     .setAlign(Blockly.ALIGN_RIGHT)
+    //     .appendField(new Blockly.FieldCheckbox("TRUE", null), "CHECK") ; 
+    this.appendValueInput('RAD2')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.RADIUS + '2')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('HEIGHT')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.HEIGHT)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED, 'false'], [Blockscad.Msg.CENTERED, 'true']]), 'CENTERDROPDOWN');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.CYLINDER_TOOLTIP);
+  },
+  updateRadii: function() {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    // console.log('in updateRadii');
+    var locked = this.getField("LOCKED").getValue();
+
+    if (locked == 'FALSE') {
+      return;
+    }
+
+    var R1 = null;
+    var R2 = null;
+    // get the values (if any) attached to the two radius inputs.
+    if (this.getInput('RAD1').connection.targetConnection &&
+        this.getInput('RAD1').connection.targetConnection.sourceBlock_.type == "math_number") { 
+      R1 = this.getInput('RAD1').connection.targetConnection.sourceBlock_.getField('NUM').getValue();
+    }
+    if (this.getInput('RAD2').connection.targetConnection &&
+        this.getInput('RAD2').connection.targetConnection.sourceBlock_.type == "math_number") {
+      R2 = this.getInput('RAD2').connection.targetConnection.sourceBlock_.getField('NUM').getValue();
+    }
+    if (locked == 'TRUE' && R1 && R2 && R1 != R2) {
+      if (R1 != this.prevR1) { 
+        this.getInput('RAD2').connection.targetConnection.sourceBlock_.getField('NUM').setValue(R1,true);
+      }
+      else if (R2 != this.prevR2) { 
+        this.getInput('RAD1').connection.targetConnection.sourceBlock_.getField('NUM').setValue(R2,true);
+      }
+      // if you set locking on two different radii, do you want them to both take the value of R1?
+      // else if (R1 != R2) this.getInput('RAD2').connection.targetConnection.sourceBlock_.getField('NUM').setValue(R1);
+    }
+
+    this.prevR1 = R1;
+    this.prevR2 = R2;
+    // console.log("in cylinder onchange.  R1  R2  pR1  pR2", R1, R2, this.prevR1, this.prevR2);
+
+  }
+};
+
+// planning not to use this.
+Blockly.Blocks['simple_cylinder'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.CYLINDER + '  ');
+    this.appendValueInput('RAD1')
+        .setCheck('Number')    
+        .appendField(Blockscad.Msg.RADIUS)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('HEIGHT')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.HEIGHT)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED, 'false'], [Blockscad.Msg.CENTERED, 'true']]), 'CENTERDROPDOWN');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip('Creates a cylinder with a specified radius and height.  It may optionally be centered at the origin.');
+  }//,
+};
+
+Blockly.Blocks['cube'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.CUBE + '   ');
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField('X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('ZVAL')
+        .setCheck('Number')
+        .appendField('Z')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED, 'false'], [Blockscad.Msg.CENTERED, 'true']]), 'CENTERDROPDOWN');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    //this.setNextStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.CUBE_TOOLTIP);
+  }//,
+  // onchange: function() {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   // if one of the value fields is missing, I want to pop up a warning.
+  //   var val1 = this.getInput("XVAL").connection;
+  //   var val2 = this.getInput("YVAL").connection;
+  //   var val3 = this.getInput("ZVAL").connection;
+  //   if (val1.targetConnection && val2.targetConnection && val3.targetConnection)
+  //     this.setWarningText(null);
+  //   else this.setWarningText("Cube needs all paramaters to have number values");
+  // }  
+};
+
+Blockly.Blocks['torus'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.TORUS + '  ');
+    this.appendValueInput('RAD1')
+        .setCheck('Number')    
+        .appendField( Blockscad.Msg.RADIUS + '1')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('RAD2')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.RADIUS + '2')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('SIDES')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.SIDES)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('FACES')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.FACES)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.TORUS_TOOLTIP);
+  }//,
+  // onchange: function() {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   // if one of the value fields is missing, I want to pop up a warning.
+  //   var val1 = this.getInput("RAD1").connection;
+  //   var val2 = this.getInput("RAD2").connection;
+  //   var val3 = this.getInput("SIDES").connection;
+  //   var val4 = this.getInput("FACES").connection;
+  //   if (val1.targetConnection && val2.targetConnection && val3.targetConnection
+  //       && val4.targetConnection)
+  //     this.setWarningText(null);
+  //   else this.setWarningText("Torus needs all paramaters to have number values");
+  // } 
+};
+
+Blockly.Blocks['twistytorus'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField('Twisty Torus  ');
+    this.appendValueInput('RAD1')
+        .setCheck('Number')    
+        .appendField('ring radius')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('RAD2')
+        .setCheck('Number')
+        .appendField('cross-section radius')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('SIDES')
+        .setCheck('Number')
+        .appendField('ring sides')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('FACES')
+        .setCheck('Number')
+        .appendField('cross section faces')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('TWIST')
+        .setCheck('Number')
+        .appendField('twist (degrees)')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip('Creates a torus with a ring of specified distance on-center from the origin (radius1), with a specified radius (radius2), a specified number of sides and faces. The "twist" is in degrees, and should be used with caution');
+  }//,
+  // onchange: function() {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   // if one of the value fields is missing, I want to pop up a warning.
+  //   var val1 = this.getInput("RAD1").connection;
+  //   var val2 = this.getInput("RAD2").connection;
+  //   var val3 = this.getInput("SIDES").connection;
+  //   var val4 = this.getInput("FACES").connection;
+  //   if (val1.targetConnection && val2.targetConnection && val3.targetConnection
+  //       && val4.targetConnection)
+  //     this.setWarningText(null);
+  //   else this.setWarningText("Torus needs all paramaters to have number values");
+  // } 
+};
+
+Blockly.Blocks['circle'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CAG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_2D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.CIRCLE + '   ');
+    this.appendValueInput('RAD')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.RADIUS)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CAG');
+    this.setTooltip(Blockscad.Msg.CIRCLE_TOOLTIP);
+  }//,
+  // onchange: function() {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }    
+  //   // if one of the value fields is missing, I want to pop up a warning.
+  //   var val = this.getInput("RAD").connection;
+  //   if (!val.targetConnection)
+  //     this.setWarningText("Circle requires a radius to be set");
+  //   else this.setWarningText(null);
+  // }  
+};
+
+Blockly.Blocks['square'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CAG'
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_2D_PRIMITIVE);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.SQUARE + '   ');
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField('X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED, 'false'], [Blockscad.Msg.CENTERED, 'true']]), 'CENTERDROPDOWN');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CAG');
+    this.setTooltip(Blockscad.Msg.SQUARE_TOOLTIP);
+  }//,
+  // onchange: function() {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   // if one of the value fields is missing, I want to pop up a warning.
+  //   var val1 = this.getInput("XVAL").connection;
+  //   var val2 = this.getInput("YVAL").connection;
+  //   if (val1.targetConnection && val2.targetConnection)
+  //     this.setWarningText(null);
+  //   else this.setWarningText("Square needs all paramaters to have number values");
+  // }
+};
+Blockly.Blocks['translate'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.TRANSLATE);
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField('X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('ZVAL')
+        .setCheck('Number')
+        .appendField('Z')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.TRANSLATE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },  
+   /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * and whether ZVAL field exists.
+   * only call the drawing routines if the type is actually changing.
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    if (!goog.isArray(type))
+      type = [type];
+
+    var zval = this.getInput('ZVAL');
+    var next = this.getInput('A');
+    var myType = this.previousConnection.check_;
+
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    } 
+
+    if (type[0] == 'CAG' && myType[0] == 'CSG') {      
+        hideMyInput(zval,drawMe);
+    }
+    else if (type[0] == 'CSG' && myType[0] == 'CAG') {                    
+        showMyInput(zval,drawMe);
+    } 
+
+    // console.log("translate type has become",this.previousConnection.check_);
+    //console.log(this.getInput('A').connection.check_);
+  }
+};
+
+Blockly.Blocks['scale'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.SCALE);
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField('X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('ZVAL')
+        .setCheck('Number')
+        .appendField('Z')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.SCALE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+   mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+    /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * and whether ZVAL field exists.
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    if (!goog.isArray(type))
+      type = [type];
+
+    var zval = this.getInput('ZVAL');
+    var next = this.getInput('A');
+    var myType = this.previousConnection.check_;
+    
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    } 
+
+    if (type[0] == 'CAG' && myType[0] == 'CSG') {      
+        hideMyInput(zval,drawMe);
+    }
+    else if (type[0] == 'CSG' && myType[0] == 'CAG') {                    
+        showMyInput(zval,drawMe);
+    } 
+  } 
+};
+
+// Blockly.Blocks['resize'] = {
+//   init: function() {
+//     this.category = 'TRANSFORM';
+//     this.setHelpUrl('http://www.example.com/');
+//     this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+//     this.appendDummyInput()
+//         .appendField('Resize');
+//     this.appendDummyInput()
+//         .appendField('X');
+//     this.appendValueInput('XVAL')
+//         .setCheck('Number');
+//     this.appendDummyInput()
+//         .appendField('Y');
+//     this.appendValueInput('YVAL')
+//         .setCheck('Number');
+//     this.appendDummyInput()
+//         .appendField('Z');
+//     this.appendValueInput('ZVAL')
+//         .setCheck('Number');
+//     this.appendStatementInput('A')
+//         .setCheck('CSG');
+//     this.setInputsInline(true);
+//     this.setPreviousStatement(true, 'CSG');
+//     this.setTooltip('');
+//   }
+// };
+
+Blockly.Blocks['fancymirror'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.MIRROR_ADVANCED);
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField('X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('ZVAL')
+        .setCheck('Number')
+        .appendField('Z')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.FANCYMIRROR_TOOLTIP);
+       // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0; 
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    var zval = this.getInput('ZVAL');
+
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }  
+  }   
+};
+
+Blockly.Blocks['simplemirror'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField('Simple Mirror');
+    this.appendDummyInput()
+        .appendField('across')
+        .appendField(new Blockly.FieldDropdown([['+', 'pos'], ['-', 'neg']]), 'sign')
+        .appendField(new Blockly.FieldDropdown([['XY', 'XY'], ['YZ', 'YZ'], ['XZ', 'XZ']]), 'mirrorplane');
+    this.appendStatementInput('A')
+        .setCheck('CSG');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.SIMPLEMIRROR_TOOLTIP);
+  },
+    /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    var next = this.getInput('A');
+
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+  }  
+};
+Blockly.Blocks['simplemirror_new'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.MIRROR);
+    this.appendDummyInput('3D')
+        .appendField(Blockscad.Msg.ACROSS)
+        .appendField(new Blockly.FieldDropdown([['XY', 'XY'], ['YZ', 'YZ'], ['XZ', 'XZ']]), 'mirrorplane');
+    this.appendDummyInput('2D')
+        .appendField(Blockscad.Msg.ACROSS)
+        .appendField(new Blockly.FieldDropdown([['YZ', 'YZ'], ['XZ', 'XZ']]), 'mirrorplane_cag')
+        .setVisible(false);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.SIMPLEMIRROR_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+   /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * and whether ZVAL field exists.
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    if (!goog.isArray(type))
+      type = [type];
+    // var csg = this.getField_('mirrorplane');
+    // var cag = this.getField_('mirrorplane_cag');
+
+    var csg = this.getInput('3D');
+    var cag = this.getInput('2D');
+    var next = this.getInput('A');
+    var myType = this.previousConnection.check_;
+
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }   
+
+    if (type[0] == 'CAG' && myType[0] == 'CSG') {
+      hideMyInput(csg,drawMe);
+      showMyInput(cag,drawMe);
+    }
+    else if (type[0] == 'CSG' && myType[0] == 'CAG') { 
+      hideMyInput(cag,drawMe);
+      showMyInput(csg,drawMe);
+    } 
+  } 
+};
+
+Blockly.Blocks['taper'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.TAPER);
+    this.appendDummyInput('3D')
+        .appendField(Blockscad.Msg.ALONG + ' ')
+        .appendField(new Blockly.FieldDropdown([['X', 'X'], ['Y', 'Y'], ['Z', 'Z']]), 'taperaxis')
+        .appendField(Blockscad.Msg.AXIS);
+    this.appendDummyInput('2D')
+        .appendField(Blockscad.Msg.ALONG + ' ')
+        .appendField(new Blockly.FieldDropdown([['X', 'X'], ['Y', 'Y']]), 'taperaxis_cag')
+        .appendField(Blockscad.Msg.AXIS)
+        .setVisible(false);
+    this.appendValueInput('FACTOR')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.SCALE)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.TAPER_TOOLTIP);
+    this.setWarningText(Blockscad.Msg.NOT_COMPATIBLE_WITH_OPENSCAD);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+   /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * and whether ZVAL field exists.
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    if (!goog.isArray(type))
+      type = [type];
+
+    var csg = this.getInput('3D');
+    var cag = this.getInput('2D');
+    var next = this.getInput('A');
+    var myType = this.previousConnection.check_;
+
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }  
+
+    if (type[0] == 'CAG' && myType[0] == 'CSG') {  
+      hideMyInput(csg,drawMe);
+      showMyInput(cag,drawMe);
+    }
+    else if (type[0] == 'CSG' && myType[0] == 'CAG') { 
+      hideMyInput(cag,drawMe);
+      showMyInput(csg,drawMe);
+      if (drawMe) this.render();
+    } 
+  } 
+};
+
+Blockly.Blocks['simplerotate'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.ROTATE);
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField('X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('ZVAL')
+        .setCheck('Number')
+        .appendField('Z')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.SIMPLEROTATE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+   /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * and whether ZVAL field exists.
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    // don't hide x and y axes for rotation - openscad creates a projection.
+    // var xval = this.getInput('XVAL');
+    // var yval = this.getInput('YVAL');
+    var next = this.getInput('A');
+
+    // if (type == 'CAG') {      // parent wants a 2D shape
+    //   hideMyInput(xval,drawMe);
+    //   hideMyInput(yval,drawMe);
+    //   if (drawMe) this.render();
+    // }
+    // else {                    // parent wants 3D or doesn't care
+    //   showMyInput(xval,drawMe);
+    //   showMyInput(yval,drawMe);
+    //   if (drawMe) this.render();
+    // } 
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }   
+  }   
+};
+
+Blockly.Blocks['fancyrotate'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.ROTATE_ADVANCED);
+    this.appendValueInput('AVAL')
+        .setCheck('Number'); 
+    this.appendValueInput('XVAL')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.AROUND + ' X')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YVAL')
+        .setCheck('Number')
+        .appendField('Y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('ZVAL')
+        .setCheck('Number')
+        .appendField('Z')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.FANCYROTATE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },  
+     /**
+   * If our parent or child is CSG or CAG, that sets our output type
+   * and whether ZVAL field exists.
+   * @this Blockly.Block
+   */
+  setType: function(type,drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    if (!goog.isArray(type))
+      type = [type];
+
+    var zval = this.getInput('ZVAL');
+    var xval = this.getInput('XVAL');
+    var yval = this.getInput('YVAL');
+    var next = this.getInput('A');
+    var myType = this.previousConnection.check_;
+
+    this.previousConnection.setCheck(type);
+    next.connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    } 
+
+    if (type[0] == 'CAG' && myType[0] == 'CSG') {  
+      hideMyInput(xval,drawMe);
+      hideMyInput(yval,drawMe);
+      hideMyInput(zval,drawMe);
+    }
+    else if (type[0] == 'CSG' && myType[0] == 'CAG') {  
+      showMyInput(xval,drawMe);
+      showMyInput(yval,drawMe);
+      showMyInput(zval,drawMe);
+    }   
+  } 
+};
+
+Blockly.Blocks['color'] = {
+  init: function() {
+    this.category = 'COLOR';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.COLOR);
+    this.appendValueInput('COLOR')
+        .setCheck('Colour');
+    this.appendStatementInput('A')
+        .setCheck('CSG');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.COLOR_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+   mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }   
+};
+
+Blockly.Blocks['color_rgb'] = {
+  init: function() {
+    this.category = 'COLOR';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.COLOR + '  ');
+    var dropdown = new Blockly.FieldDropdown([[Blockscad.Msg.HSV_COLOR_MODEL, 'HSV'],[Blockscad.Msg.RGB_COLOR_MODEL, 'RGB']], function(option) {
+      var isRGB = (option == 'RGB');
+      this.sourceBlock_.optUpdateShape_(isRGB);
+    });
+    this.appendDummyInput()
+        .appendField(dropdown, 'SCHEME')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('RED')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.COLOR_HUE, '1')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('GREEN')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.COLOR_SATURATION, '2')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('BLUE')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.COLOR_VALUE,'3')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck('CSG');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    var thisBlock = this;
+    this.setTooltip(function() {
+      var mode = thisBlock.getFieldValue('SCHEME');
+      var TOOLTIPS = {
+        'RGB': Blockscad.Msg.COLOR_RGB_TOOLTIP,
+        'HSV': Blockscad.Msg.COLOR_HSV_TOOLTIP
+      }; 
+      return TOOLTIPS[mode];
+    });  
+
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+   mutationToDom: function() {
+    // if (!this.plusCount_) {
+    //     return null;
+    // }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    else container.setAttribute('plus', 0);
+    var isRGB = (this.getFieldValue('SCHEME') == 'RGB');
+    container.setAttribute('isrgb', isRGB);
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+    var isRGB = (xmlElement.getAttribute('isrgb') == 'true');
+    this.optUpdateShape_(isRGB);
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      if (this.mutatorMinus) this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }  ,
+
+  // if change the labels on the value inputs based on if this is RGB or HSV
+  optUpdateShape_: function(isRGB) {
+    // make labels match the color schema (RGB or HSV)
+    var one = this.getField('1');
+    var two = this.getField('2');
+    var three = this.getField('3');
+    if (isRGB) {
+      one.setText(Blockly.Msg.COLOUR_RGB_RED);
+      two.setText(Blockly.Msg.COLOUR_RGB_GREEN);
+      three.setText(Blockly.Msg.COLOUR_RGB_BLUE);
+    }
+    else {
+      one.setText(Blockscad.Msg.COLOR_HUE);
+      two.setText(Blockscad.Msg.COLOR_SATURATION);
+      three.setText(Blockscad.Msg.COLOR_VALUE);
+    }
+
+
+  } 
+};
+Blockly.Blocks['$fn'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendValueInput('SIDES')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.SIDES);
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.FN_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+  setType: function(type) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    //console.log("setting union type to",type);
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }   
+  }   
+};
+Blockly.Blocks['assign'] = {
+  init: function() {
+    this.category = 'TRANSFORM';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendValueInput('NAME')
+        .appendField("set ")
+        // .setCheck('String')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('VALUE')
+        .setCheck('Number')
+        .appendField(' = ');
+    this.appendStatementInput('A')
+        .setCheck(['CSG','CAG']);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, ['CSG','CAG']);
+    this.setTooltip(Blockscad.Msg.FN_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+  mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  },   
+  setType: function(type) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    //console.log("setting union type to",type);
+    this.previousConnection.setCheck(type);
+    this.getInput('A').connection.setCheck(type);
+    for (var i = 1; i <= this.plusCount_; i++) {
+      this.getInput('PLUS' + i).connection.setCheck(type);
+    }   
+  }   
+};
+
+Blockly.Blocks['linearextrude'] = {
+  init: function() {
+    this.category = 'EXTRUDE';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.LINEAR_EXTRUDE + '  ');
+    this.appendValueInput('HEIGHT')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.HEIGHT)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('TWIST')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.TWIST)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    // this.appendDummyInput()
+    //     .appendField('scale: ')
+    //     .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('XSCALE')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.SCALE + ': x')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('YSCALE')
+        .setCheck('Number')
+        .appendField('y')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED, 'false'], [Blockscad.Msg.CENTERED, 'true']]), 'CENTERDROPDOWN')
+        .setAlign(Blockly.ALIGN_RIGHT);
+
+    this.appendStatementInput('A')
+        .setCheck('CAG');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.LINEAREXTRUDE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+   mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }   
+};
+
+Blockly.Blocks['rotateextrude'] = {
+  init: function() {
+    this.category = 'EXTRUDE';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.ROTATE_EXTRUDE + '  ');
+    this.appendValueInput('FACES')
+        .setCheck('Number')
+        .appendField(Blockscad.Msg.SIDES)
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck('CAG');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip(Blockscad.Msg.ROTATEEXTRUDE_TOOLTIP);
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+   mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }   
+};
+
+Blockly.Blocks['rotateextrudetwist'] = {
+  init: function() {
+    this.category = 'EXTRUDE';
+    this.setHelpUrl('http://www.example.com/');
+    this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);
+    this.appendDummyInput()
+        .appendField('Rotate Extrude Twist ');
+    this.appendValueInput('RAD')
+        .setCheck('Number')
+        .appendField('R')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('FACES')
+        .setCheck('Number')
+        .appendField('Sides')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('TWIST')
+        .setCheck('Number')
+        .appendField('Twist')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendValueInput('TSTEPS')
+        .setCheck('Number')
+        .appendField('Twist-steps')
+        .setAlign(Blockly.ALIGN_RIGHT);
+    this.appendStatementInput('A')
+        .setCheck('CAG');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setTooltip('Rotate extrudes shape translated by radius around the Z axis with a specified number of sides. ');
+    // try to set up a mutator - Jennie
+    this.setMutatorPlus(new Blockly.MutatorPlus(this));    
+    this.plusCount_ = 0;
+  },
+
+   mutationToDom: function() {
+    if (!this.plusCount_) {
+        return null;
+    }
+    var container = document.createElement('mutation');
+    if (this.plusCount_) {
+        container.setAttribute('plus',this.plusCount_);
+    }
+    return container;
+  },
+  domToMutation: function(xmlElement) {
+    this.plusCount_ = parseInt(xmlElement.getAttribute('plus'), 10);
+    var mytype = this.getInput('A').connection.check_;
+    for (var x = 1; x <= this.plusCount_; x++) {
+        this.appendStatementInput('PLUS' + x)
+            .setCheck(mytype);
+    }
+    if (this.plusCount_ >= 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+    }
+  }, 
+  updateShape_ : function(num) {
+    if (num == 1) {
+      this.plusCount_++;
+      var mytype = this.getInput('A').connection.check_;
+      var plusInput = this.appendStatementInput('PLUS' + this.plusCount_)
+          .setCheck(mytype); 
+    } else if (num == -1) {
+      this.removeInput('PLUS' + this.plusCount_); 
+      this.plusCount_--;
+    }
+    if (this.plusCount_ >= 1) {
+      if (this.plusCount_ == 1) {
+        this.setMutatorMinus(new Blockly.MutatorMinus(this));
+        this.render();
+      }
+    } else {
+      this.mutatorMinus.dispose();
+      this.mutatorMinus = null;
+      this.render();
+    }
+  }   
+};
+// math_angle block added for BlocksCAD - Jennie jayod
+Blockly.Blocks['math_angle'] = {
+  // Numeric value, but in an angle field.
+  init: function() {
+    //this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldAngle('0'), 'NUM');
+    this.setOutput(true, 'Number');
+    //this.setTooltip(Blockly.Msg.MATH_NUMBER_TOOLTIP);
+  }
+};
+
+// this is just like the blockly math constant block, but with no infinity constant.
+// I moved it here and renamed it so we don't have to change it with every Blockly sync.
+Blockly.Blocks['math_constant_bs'] = {
+  /**
+   * Block for constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2)
+   * @this Blockly.Block
+   */
+  init: function() {
+    var CONSTANTS =
+        [['\u03c0', 'PI'],
+         ['e', 'E'],
+         ['\u03c6', 'GOLDEN_RATIO'],
+         ['sqrt(2)', 'SQRT2'],
+         ['sqrt(\u00bd)', 'SQRT1_2']];
+    this.setHelpUrl(Blockly.Msg.MATH_CONSTANT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_MATH);
+    this.setOutput(true, 'Number');
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldDropdown(CONSTANTS), 'CONSTANT');
+    this.setTooltip(Blockly.Msg.MATH_CONSTANT_TOOLTIP);
+  }
+};
+
+// I want a block for stl import (file). 
+Blockly.Blocks['stl_import'] = {
+  init: function() {
+    this.category = 'PRIMITIVE_CSG'
+    this.appendDummyInput()
+        .appendField(Blockscad.Msg.IMPORT_STL);
+    this.appendDummyInput('')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(new Blockly.FieldLabel(""),'STL_FILENAME');
+    this.appendDummyInput('')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(new Blockly.FieldButton(Blockscad.Msg.BROWSE),'STL_BUTTON');
+    this.appendDummyInput('C')
+        .appendField(new Blockly.FieldLabel(""),'STL_CONTENTS')
+        .setVisible(false);
+    this.setInputsInline(true);
+    this.setPreviousStatement(true);
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.setTooltip('');
+    this.setWarningText(Blockscad.Msg.STL_IMPORT_WARNING);
+    this.setHelpUrl('http://www.example.com/');
+  },
+  onchange: function() {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }    
+    // if one of the value fields is missing, I want to pop up a warning.
+    var fn = this.getField('STL_FILENAME').getText();
+    var contents = this.getField('STL_CONTENTS').getText();
+    if (fn.length > 0) {
+      this.getField('STL_BUTTON').setVisible(false);
+      this.setCommentText(fn + '\ncenter: (' + Blockscad.csg_center[contents] + ')');
+    }
+    this.getField('STL_CONTENTS').setVisible(false);
+    // this.render();
+  }
+};
+
+// the original text block, in amazing 2D (CAG)
+
+Blockly.Blocks['bs_text'] = {
+  /**
+   * Block for text value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    // load up the font names and positions
+    var CONSTANTS = [];
+    for (var i=0; i<Blockscad.fontName.length; i++) {
+        CONSTANTS.push([Blockscad.fontName[i],i.toString()]);
+    }
+    this.category = 'PRIMITIVE_CAG'
+    this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
+    this.appendValueInput('TEXT')
+        .appendField(Blockscad.Msg.BLOCK_TEXT_2D)
+        // .setCheck('String')
+        .setAlign(Blockly.ALIGN_RIGHT);
+
+    this.appendValueInput("SIZE")
+        .setCheck("Number")
+        .appendField(" " + Blockscad.Msg.FONT_SIZE)
+        .setAlign(Blockly.ALIGN_RIGHT);
+
+    this.appendDummyInput()
+        .appendField(" " + Blockscad.Msg.FONT_NAME)
+        .appendField(new Blockly.FieldDropdown(CONSTANTS), 'FONT');
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CAG');
+    this.setColour(Blockscad.Toolbox.HEX_2D_PRIMITIVE);
+    this.setTooltip(Blockscad.Msg.BS_TEXT_TOOLTIP);
+  },
+  /**
+   * Create an image of an open or closed quote.
+   * @param {boolean} open True if open quote, false if closed.
+   * @return {!Blockly.FieldImage} The field image of the quote.
+   * @this Blockly.Block
+   * @private
+   */
+  newQuote_: function(open) {
+    if (open == this.RTL) {
+      var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
+    } else {
+      var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
+    }
+    return new Blockly.FieldImage(file, 12, 12, '"');
+  }
+};
+
+// a 3D text block.
+
+Blockly.Blocks['bs_3dtext'] = {
+  /**
+   * Block for text value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    // load up the font names and positions
+    var CONSTANTS = [];
+    for (var i=0; i<Blockscad.fontName.length; i++) {
+        CONSTANTS.push([Blockscad.fontName[i],i.toString()]);
+    }
+    this.category = 'PRIMITIVE_CSG'
+    this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
+    this.appendValueInput('TEXT')
+        .appendField(Blockscad.Msg.BLOCK_TEXT_3D)
+        // .setCheck('String')
+        .setAlign(Blockly.ALIGN_RIGHT);
+
+    this.appendValueInput("SIZE")
+        .setCheck("Number")
+        .appendField(" " + Blockscad.Msg.FONT_SIZE)
+        .setAlign(Blockly.ALIGN_RIGHT);
+
+    this.appendDummyInput()
+        .appendField(" " + Blockscad.Msg.FONT_NAME)
+        .appendField(new Blockly.FieldDropdown(CONSTANTS), 'FONT');
+
+    this.appendValueInput('THICKNESS')
+        .appendField(" " + Blockscad.Msg.TEXT_THICKNESS)
+        .setCheck('Number')
+        .setAlign(Blockly.ALIGN_RIGHT);
+
+    this.setInputsInline(true);
+    this.setPreviousStatement(true, 'CSG');
+    this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);
+    this.setTooltip(Blockscad.Msg.BS_3DTEXT_TOOLTIP);
+  },
+  /**
+   * Create an image of an open or closed quote.
+   * @param {boolean} open True if open quote, false if closed.
+   * @return {!Blockly.FieldImage} The field image of the quote.
+   * @this Blockly.Block
+   * @private
+   */
+  newQuote_: function(open) {
+    if (open == this.RTL) {
+      var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
+    } else {
+      var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
+    }
+    return new Blockly.FieldImage(file, 12, 12, '"');
+  }
+};
+
+function hideMyInput(value,drawMe) {
+  if (drawMe) {
+    Blockscad.executeAfterDrag_(function() {
+      if (value.isVisible()) {
+        //console.log("trying to hide input",value);
+        value.setVisible(false);
+      }
+       value.sourceBlock_.render();
+    }, value);
+  }
+}
+function showMyInput(value,drawMe) {
+  if (!value.isVisible() && drawMe) {
+    Blockscad.executeAfterDrag_(function() {
+      // console.log(value);
+      var blocks_to_render = value.setVisible(true);
+      if (blocks_to_render.length > 0)
+        blocks_to_render[0].render();
+    }, value);
+  }
+}
+
+Blockly.Blocks['bs_text_length'] = {
+  /**
+   * Block for string length.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_LENGTH_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_TEXT);
+    this.interpolateMsg(Blockly.Msg.TEXT_LENGTH_TITLE,
+                        ['VALUE', ['String', 'Array'], Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setOutput(true, 'Number');
+    this.setTooltip(Blockly.Msg.TEXT_LENGTH_TOOLTIP);
+  }
+};

+ 1474 - 0
blockly/blocks/procedures.js

@@ -0,0 +1,1474 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Procedure blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.procedures');
+
+goog.require('Blockly.Blocks');
+
+
+Blockly.Blocks['procedures_defnoreturn'] = {
+  /**
+   * Block for defining a procedure with no return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.category = 'PROCEDURE';        // for blocksCAD
+    this.myType_ = ['CSG','CAG'];       // for blocksCAD
+    this.backlightBlocks = [];            // for blocksCAD
+
+    var nameField = new Blockly.FieldTextInput(
+        Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE,
+        Blockly.Procedures.rename);
+    nameField.setSpellcheck(false);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE)
+        .appendField(nameField, 'NAME')
+        .appendField('', 'PARAMS');
+    this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
+    this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+    this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
+    this.arguments_ = [];
+    this.setStatements_(true, 'VariableSet');
+    this.statementConnection_ = null;
+  },
+   /**
+   * if this procedure has statements, use them to determine the 
+   * type of this procedure, then update types of any callers..
+   * @this Blockly.Block
+   */
+  // setType: function(type,drawMe) {      // for blocksCAD
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   console.log("starting proc ST with oldtype:" + this.myType_ + " and newtype:" + type);
+  //   if (this.myType_ == type)
+  //     return;
+
+  //   console.log("in modules's setType");
+
+  //   var oldtype = this.myType_;
+  //   var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
+  //   var numBumped = [];
+  //   var notBumped = [];
+
+  //   // I need to find out what my caller stacks think their types are.
+  //   if (callers.length) {
+  //     for (var i = 0; i < callers.length; i++) {
+  //       var areaType = Blockscad.findBlockType(callers[i],callers);
+  //       //console.log("caller area type is",areaType);
+  //       //console.log("caller category is", callers[i].category);
+  //      // console.log("parent type is changing to",type);
+  //       if (!goog.isArray(type) && areaType != 'EITHER' && areaType != type) {
+  //         // call blocks are going to be kicked out.  
+  //         // console.log("warning message!  call block id", callers[i].id, "will be kicked out and backlit");
+  //         numBumped.push(callers[i]);
+  //         // If the call block is in a collapsed stack, find the collapsed parent and expand them.
+  //         var topBlock = callers[i].collapsedParents();
+  //         if (topBlock)
+  //           for (var j=0; j < topBlock.length; j++) 
+  //             topBlock[j].setCollapsed(false); 
+  //       }
+  //       else notBumped.push(callers[i]);  
+  //     }
+  //   }
+
+  //   if (numBumped.length) {
+  //     var text = '';
+  //     // text += numBumped.length + " ";
+  //     // took out the name so I wouldn't have to deal with renaming the proc.
+  //     //text += this.getFieldValue('NAME') + " ";
+  //     text += Blockscad.Msg.BLOCKS_BUMPED_OUT_DIMENSIONS.replace("%1", numBumped.length);
+  //     this.setWarningText(text);
+  //   }
+
+  //   this.myType_ = type;
+
+
+
+  //   // some of my callers don't need to be bumped.  I'll set their category to "BLAH"
+  //   // temporarily (note this is NOT a valid category),
+  //   // reset the types of the blocks around them, then set them to their new type.  
+  //   // this should prevent them getting bumped out incorrectly.
+
+  //   // if (notBumped.length) {
+  //   //   for (var j = 0; j < notBumped.length; j++) {
+  //   //     notBumped[j].category = 'BLAH';
+  //   //     notBumped[j].previousConnection.setCheck(['CSG','CAG']);
+  //   //   }
+  //   //   for (j = 0; j < notBumped.length; j++) {
+  //   //     console.log("in proc set_type, calling assignBT with BLAH");
+  //   //     this.myType_ = type;
+  //   //     Blockscad.assignBlockTypes([notBumped[j]]);
+  //   //   }
+  //   // }
+
+  //   // this section actually re-sets the type that triggers the bumping.  This needs to be done before backlighting.
+  //   // to make undo work, I need to set the group of the next events.
+
+
+  //   if (numBumped.length) {
+
+  //     // console.log("firing events now - something should have been bumped");
+  //     // lets get the group event
+  //     var eventGroup = true;
+  //     if (Blockscad.workspace.undoStack_.length) 
+  //       eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
+  //     // console.log("event group is: ", eventGroup);
+  //     Blockly.Events.setGroup(eventGroup);
+  //   }
+
+
+  //   if (callers.length > 0) {
+  //     for (var i = 0; i < callers.length; i++) {
+  //       callers[i].previousConnection.setCheck(type);
+  //       // if I'm bumping something, I need to put the typing event into the undoStack_
+  //       // so that on undo the caller doesn't get immediately bumped out again!
+  //       // So first I set the check (which will prompt the bump) THEN fire the type event
+  //       // so that when it is run backwards I untype first then move back into place.
+  //       if (Blockly.Events.isEnabled() && numBumped.length) {
+  //         Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
+  //       }
+  //       if (type == 'CSG')
+  //         callers[i].category = 'PRIMITIVE_CSG'
+  //       else if (type == 'CAG')
+  //         callers[i].category = 'PRIMITIVE_CAG';
+  //       else callers[i].category = 'UNKNOWN';
+  //       // if the top block isn't the procedure definition (recursion!), then assign their types
+  //       var topBlock = callers[i].getRootBlock();
+  //       if (!(topBlock.category && topBlock.category == 'PROCEDURE')) {
+  //         console.log("calling assignBlockTypes from proc ST (this is prob the bad one");
+  //         this.myType_ = type;
+  //         Blockscad.assignBlockTypes([callers[i]]);
+  //       }
+  //     }
+  //   }
+  //   // // the system will be done now with unplugging all the blocks that need it.  
+  //   // set the backlighting and warning message here (with a delay) so that the events that occur during the 
+  //   // bumping don't overwrite the backlighting of the caller blocks.
+
+  //   for (var k = 0; k < numBumped.length; k++) {
+  //     numBumped[k].backlight();
+  //     this.backlightBlocks.push(numBumped[k].id);
+  //   }
+
+  //   if (numBumped.length) {
+  //     // Blockly.Events.Filter
+  //     Blockly.Events.setGroup(false);
+  //   }
+
+  //   // events aren't all getting the group setting.  Walk back through the undoStack_ and make sure the group is set.
+
+  //   // for (var i = Blockscad.workspace.undoStack_.length - 1; i > 0; i--) {
+  //   //   if 
+  //   // }
+  //   this.myType_ = type;
+
+  // },        // end for blocksCAD
+  // // for BlocksCAD - check to see if my callers are still backlight?
+  // onchange: function() {
+  //   var found_it;
+  //   // go through my backlight id list, see if I have any blocks on it that are not on 
+  //   // the general backlight list (they must have been unhighlighted!)
+  //   for (var i=0; i < this.backlightBlocks.length; i++) {
+  //     found_it = 0;
+  //     for (var j=0; j<Blockly.backlight.length; j++) {
+  //       if (this.backlightBlocks[i] === Blockly.backlight[j]) {
+  //         found_it = 1;
+  //         break;
+  //       }
+  //     }
+  //     if (!found_it) {  // this block needs to come off our list
+  //       this.backlightBlocks.splice(i,1);
+  //     }
+  //   }
+  //   if (!this.backlightBlocks.length) {
+  //     // console.log("turning off warning text");
+  //     this.setWarningText(null);
+  //   } 
+  // },
+
+  // second stab at setType.
+  setType: function(type, drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    // console.log("starting proc ST with oldtype:" + this.myType_ + " and newtype:" + type);
+    // console.log("arrays: " + goog.isArray(this.myType_) + ', ' + goog.isArray(type));
+    if (!goog.isArray(type))
+      type = [type];
+    // compare to see if type matches this.myType_
+
+    if (Blockscad.arraysEqual(type, this.myType_))
+      return;
+
+    // console.log("in modules's setType");
+
+    var oldtype = this.myType_;
+    var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
+    var numBumped = [];
+
+    // first, set my type (the module definition's type)
+    this.myType_ = type;
+
+    // start grouping events in case some blocks are bumped out, so that undo will work easily.
+    var eventGroup = true;
+    if (Blockscad.workspace.undoStack_.length) 
+      eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
+    // console.log("event group is: ", eventGroup);
+    Blockly.Events.setGroup(eventGroup);
+
+    // now, set my caller block's types
+    if (callers.length) {
+      for (var i = 0; i < callers.length; i++) {
+        // find what the type is of the stack the caller is in.
+        var areaType = Blockscad.findBlockType(callers[i],callers);
+        // if the stack's type doesn't match the caller's new type, bumpage!
+        // mark that block that will be bumped
+        if (areaType != 'EITHER' && areaType != type[0]) {
+          // console.log("warning message!  call block id", callers[i].id, "will be kicked out and backlit");
+            // console.log("parent accepts: " + parentAccepts + ", type is:", type[0]);          
+          numBumped.push(callers[i]);
+          // If the call block is in a collapsed stack, find the collapsed parent and expand them.
+          var topBlock = callers[i].collapsedParents();
+          if (topBlock)
+            for (var j=0; j < topBlock.length; j++) 
+              topBlock[j].setCollapsed(false); 
+        }
+
+        // change caller's type - this is the command that actually prompts Blockly to bump blocks out
+        callers[i].previousConnection.setCheck(type);
+        // if it was a bumping change, fire a typing event
+        if (Blockly.Events.isEnabled() && numBumped.length) {
+          Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
+        }
+        // procedure callers also have a "category" because once typed they are a shape
+        // CSG, CAG, or UNKNOWN.
+        if (type[0] == 'CSG' && type.length == 1)
+          callers[i].category = 'PRIMITIVE_CSG'
+        else if (type[0] == 'CAG')
+          callers[i].category = 'PRIMITIVE_CAG';
+        else callers[i].category = 'UNKNOWN';
+
+        // if caller is inside of another setter block, that setter's type needs to be changed.  Do so.
+        // note that this can lead to an infinite loop if procedures are circularly defined - that is why
+        // setType MUST exit immediately if it is called with the type not changing.
+
+        // what if a parent is a variables_set of a different variable?
+        // then I want to call Blockscad.assignVarTypes for that parent.
+        var setterParent = Blockscad.hasParentOfType(callers[i], "procedures_defnoreturn");
+        if (setterParent) {
+          setTimeout(function() {
+            // console.log("this caller is inside a setter: ", setterParent.id);
+            if (setterParent) setterParent.setType(type);
+          }, 0);
+        }
+        // if the caller was inside a non setter, I still want to type that parent.
+        var parent = callers[i].getParent();
+        if (parent) {
+          Blockscad.assignBlockTypes(parent)
+        }
+      }
+    } // end of going through all callers to set their types.
+
+    // turn off event grouping
+    Blockly.Events.setGroup(false);
+
+    // handle backlighting and warning text - do this later so that 
+    // the bumping process itself (which now selects and deselects the blocks) doesn't
+    // just immediately turn the backlighting off.
+
+    for (var k = 0; k < numBumped.length; k++) {
+      // console.log("backlighting a block:", numBumped[k].id);
+      numBumped[k].backlight();
+      this.backlightBlocks.push(numBumped[k].id);
+      // finally, set a warning message on the procedure definition that counts how many callers were bumped.
+      var text = '';
+      text += Blockscad.Msg.BLOCKS_BUMPED_OUT_DIMENSIONS.replace("%1", numBumped.length);
+      this.setWarningText(text);
+    }
+  },
+  /**
+   * Add or remove the statement block from this function definition.
+   * @param {boolean} hasStatements True if a statement block is needed.
+   * @this Blockly.Block
+   */
+  setStatements_: function(hasStatements) {
+    if (this.hasStatements_ === hasStatements) {
+      return;
+    }
+    if (hasStatements) {
+      this.appendStatementInput('STACK')
+          .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO);
+      if (this.getInput('RETURN')) {
+        this.moveInputBefore('STACK', 'RETURN');
+      }
+    } else {
+      this.removeInput('STACK', true);
+    }
+    this.hasStatements_ = hasStatements;
+  },
+  /**
+   * Update the display of parameters for this procedure definition block.
+   * Display a warning if there are duplicately named parameters.
+   * @private
+   * @this Blockly.Block
+   */
+  updateParams_: function() {
+    // Check for duplicated arguments.
+    var badArg = false;
+    var hash = {};
+    for (var i = 0; i < this.arguments_.length; i++) {
+      if (hash['arg_' + this.arguments_[i].toLowerCase()]) {
+        badArg = true;
+        break;
+      }
+      hash['arg_' + this.arguments_[i].toLowerCase()] = true;
+    }
+    if (badArg) {
+      this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING);
+    } else {
+      this.setWarningText(null);
+    }
+    // Merge the arguments into a human-readable list.
+    var paramString = '';
+    if (this.arguments_.length) {
+      paramString = Blockly.Msg.PROCEDURES_BEFORE_PARAMS +
+          ' ' + this.arguments_.join(', ');
+    }
+    // The params field is deterministic based on the mutation,
+    // no need to fire a change event.
+    Blockly.Events.disable();
+    try {
+      this.setFieldValue(paramString, 'PARAMS');
+    } finally {
+      Blockly.Events.enable();
+    }
+  },
+  /**
+   * Create XML to represent the argument inputs.
+   * @param {=boolean} opt_paramIds If true include the IDs of the parameter
+   *     quarks.  Used by Blockly.Procedures.mutateCallers for reconnection.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function(opt_paramIds) {
+    var container = document.createElement('mutation');
+    if (opt_paramIds) {
+      container.setAttribute('name', this.getFieldValue('NAME'));
+    }
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var parameter = document.createElement('arg');
+      parameter.setAttribute('name', this.arguments_[i]);
+      if (opt_paramIds && this.paramIds_) {
+        parameter.setAttribute('paramId', this.paramIds_[i]);
+      }
+      container.appendChild(parameter);
+    }
+
+    // Save whether the statement input is visible.
+    if (!this.hasStatements_) {
+      container.setAttribute('statements', 'false');
+    }
+    return container;
+  },
+  /**
+   * Parse XML to restore the argument inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.arguments_ = [];
+    for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
+      if (childNode.nodeName.toLowerCase() == 'arg') {
+        this.arguments_.push(childNode.getAttribute('name'));
+      }
+    }
+    this.updateParams_();
+    Blockly.Procedures.mutateCallers(this);
+
+    // Show or hide the statement input.
+    this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock = workspace.newBlock('procedures_mutatorcontainer');
+    containerBlock.initSvg();
+
+    // Check/uncheck the allow statement box.
+    // for blocksCAD - take out the if statements here so that we always don't show the 
+    // statement checkbox in the mutator.  both statements required.  
+    containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE',
+                                 'STATEMENTS');
+    containerBlock.getInput('STATEMENT_INPUT').setVisible(false);
+
+
+    // Parameter list.
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var paramBlock = workspace.newBlock('procedures_mutatorarg');
+      paramBlock.initSvg();
+      paramBlock.setFieldValue(this.arguments_[i], 'NAME');
+      // Store the old location.
+      paramBlock.oldLocation = i;
+      connection.connect(paramBlock.previousConnection);
+      connection = paramBlock.nextConnection;
+    }
+    // Initialize procedure's callers with blank IDs.
+    Blockly.Procedures.mutateCallers(this);
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    // Parameter list.
+    this.arguments_ = [];
+    this.paramIds_ = [];
+    var paramBlock = containerBlock.getInputTargetBlock('STACK');
+    while (paramBlock) {
+      this.arguments_.push(paramBlock.getFieldValue('NAME'));
+      this.paramIds_.push(paramBlock.id);
+      paramBlock = paramBlock.nextConnection &&
+          paramBlock.nextConnection.targetBlock();
+    }
+    this.updateParams_();
+    Blockly.Procedures.mutateCallers(this);
+
+    // Show/hide the statement input.
+    var hasStatements = containerBlock.getFieldValue('STATEMENTS');
+    if (hasStatements !== null) {
+      hasStatements = hasStatements == 'TRUE';
+      if (this.hasStatements_ != hasStatements) {
+        if (hasStatements) {
+          this.setStatements_(true);
+          // Restore the stack, if one was saved.
+          Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
+          this.statementConnection_ = null;
+        } else {
+          // Save the stack, then disconnect it.
+          var stackConnection = this.getInput('STACK').connection;
+          this.statementConnection_ = stackConnection.targetConnection;
+          if (this.statementConnection_) {
+            var stackBlock = stackConnection.targetBlock();
+            stackBlock.unplug();
+            stackBlock.bumpNeighbours_();
+          }
+          this.setStatements_(false);
+        }
+      }
+    }
+  },
+  /**
+   * Return the signature of this procedure definition.
+   * @return {!Array} Tuple containing three elements:
+   *     - the name of the defined procedure,
+   *     - a list of all its arguments,
+   *     - that it DOES NOT have a return value.
+   * @this Blockly.Block
+   */
+  getProcedureDef: function() {
+    // console.log("in getProcedureDef for noreturn for:",this.getFieldValue('NAME'));
+    return [this.getFieldValue('NAME'), this.arguments_, false];
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.<string>} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return this.arguments_;
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    var change = false;
+    for (var i = 0; i < this.arguments_.length; i++) {
+      if (Blockly.Names.equals(oldName, this.arguments_[i])) {
+        this.arguments_[i] = newName;
+        change = true;
+      }
+    }
+    if (change) {
+      this.updateParams_();
+      // Update the mutator's variables if the mutator is open.
+      if (this.mutator.isVisible()) {
+        var blocks = this.mutator.workspace_.getAllBlocks();
+        for (var i = 0, block; block = blocks[i]; i++) {
+          if (block.type == 'procedures_mutatorarg' &&
+              Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) {
+            block.setFieldValue(newName, 'NAME');
+          }
+        }
+      }
+    }
+  },
+  /**
+   * Add custom menu options to this block's context menu.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    // Add option to create caller.
+    var option = {enabled: true};
+    var name = this.getFieldValue('NAME');
+    option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name);
+    var xmlMutation = goog.dom.createDom('mutation');
+    xmlMutation.setAttribute('name', name);
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var xmlArg = goog.dom.createDom('arg');
+      xmlArg.setAttribute('name', this.arguments_[i]);
+      xmlMutation.appendChild(xmlArg);
+    }
+    var xmlBlock = goog.dom.createDom('block', null, xmlMutation);
+    xmlBlock.setAttribute('type', this.callType_);
+    option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+    options.push(option);
+
+    // Add options to create getters for each parameter.
+    if (!this.isCollapsed()) {
+      for (var i = 0; i < this.arguments_.length; i++) {
+        var option = {enabled: true};
+        var name = this.arguments_[i];
+        option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
+        var xmlField = goog.dom.createDom('field', null, name);
+        xmlField.setAttribute('name', 'VAR');
+        var xmlBlock = goog.dom.createDom('block', null, xmlField);
+        xmlBlock.setAttribute('type', 'variables_get');
+        option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+        options.push(option);
+      }
+    }
+    // for BlocksCAD, 
+    var option = {enabled: true};
+    var name = this.getFieldValue('NAME');
+    option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name);
+    var workspace = this.workspace;
+    option.callback = function() {
+      var def = Blockly.Procedures.getDefinition(name, workspace);
+      if (def) {
+        var callers = Blockly.Procedures.getCallers(name, workspace);
+        workspace.clearBacklight();
+        Blockly.selected.unselect();
+        for (var i = 0; callers && i < callers.length; i++) {
+          callers[i] && callers[i].backlight();
+          // if caller block is in a collapsed parent, highlight collapsed parent too
+          var others = callers[i].collapsedParents();
+          if (others)
+            for (var j=0; j < others.length; j++) 
+              others[j].backlight(); 
+        }
+      }
+    };
+    options.push(option); 
+  },
+  callType_: 'procedures_callnoreturn'
+};
+
+Blockly.Blocks['procedures_defreturn'] = {
+  /**
+   * Block for a blockSCAD function: no statements, a return value.
+   * 
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.category = 'PROCEDURE';    // for blockscad
+    this.myType_ = null;            // for blocksCAD
+    this.backlightBlocks = [];      // for blocksCAD
+
+    var nameField = new Blockly.FieldTextInput(
+        Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE,
+        Blockly.Procedures.rename);
+    nameField.setSpellcheck(false);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE)
+        .appendField(nameField, 'NAME')
+        .appendField('', 'PARAMS');
+    this.appendValueInput('RETURN')
+        .setAlign(Blockly.ALIGN_RIGHT)
+        .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+    this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
+    this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+    this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);
+    this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);
+    this.arguments_ = [];
+    this.setStatements_(false);         // set false for blockscad - jayod
+    this.statementConnection_ = null;
+  },
+    /**
+   * if this procedure has statements, use them to determine the 
+   * type of this procedure, then update types of any callers..
+   * @this Blockly.Block
+   */
+  // setType: function(type,drawMe) {      // for blocksCAD
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   var ret = this.getInput('RETURN');
+  //   console.log("in setType for function. here is the input:",ret);
+  //   if (ret.connection.targetConnection) {
+  //     if (ret.connection.targetConnection.check_ == 'Number')
+  //       this.myType_ = ret.connection.check_ = 'Number';
+  //     else if (ret.connection.targetConnection.check_ == 'Boolean')
+  //       this.myType_ = ret.connection.check_ = 'Boolean';
+  //   }
+  //   else this.myType_ = ret.connection.check_ = null;
+
+  //   var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
+  //   var numBumped = [];
+  //   var conType = null;
+
+  //   // I need to find out what my caller stacks think their types are.
+  //   if (callers.length) {
+  //     for (var i = 0; i < callers.length; i++) {
+  //       // console.log("callers.length is:",callers.length);
+  //       // get caller's connection type here
+  //       if (callers[i].outputConnection.targetConnection)
+  //         conType = callers[i].outputConnection.targetConnection.check_;
+
+  //       if (!goog.isArray(conType)) conType = [conType];
+  //       // conType is an array.  
+  //       // console.log("caller type is",conType);
+  //       // console.log(this.myType_);
+  //     if (this.myType_ && conType && conType.indexOf(this.myType_) == -1) {
+
+  //         // call blocks are going to be kicked out.  
+  //         console.log("warning message!  call block id", callers[i].id, "will be kicked out");
+  //         // there is a bug here - if we add to the numBumped stack, then we get an infinite loop. ???
+  //         // if (numBumped[numBumped.length] != callers[i]) 
+  //         // numBumped.push(callers[i]);
+  //         // If the call block is in a collapsed stack, find the collapsed parent and expand them.
+  //         var topBlock = callers[i].collapsedParents();
+  //         if (topBlock)
+  //           for (var j=0; j < topBlock.length; j++)
+  //             topBlock[j].setCollapsed(false);
+  //       }
+  //     }
+  //   }
+  //   if (numBumped.length) {
+  //     // console.log("blah");
+  //     var text = '';
+  //     // text += numBumped.length + " ";
+  //     // text += this.getFieldValue('NAME') + " ";
+  //     text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length + " " + this.getFieldValue('NAME'));
+
+  //     this.setWarningText(text);
+  //   }
+  //   if (callers.length > 0) {
+  //     for (var i = 0; i < callers.length; i++) {
+  //       callers[i].outputConnection.setCheck(this.myType_); 
+  //       if (this.myType_ == 'Number')
+  //         callers[i].category = 'NUMBER'
+  //       else if (this.myType_ == 'Boolean')
+  //         callers[i].category = 'BOOLEAN';
+  //       else callers[i].category = 'UNKNOWN';
+  //       // console.log("tried to set caller type to ",this.myType_, callers[i]);
+  //     }
+  //   }
+  //   // the system will be done now with unplugging all the blocks that need it.  
+  //   // Time to fire a workspaceChanged() so our list of parentIDs will be current.
+  //   if (numBumped.length)
+  //     Blockscad.workspaceChanged();
+  // },    // end for blocksCAD 
+  // second stab at setType.
+  setType: function(type, drawMe) {
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+
+    // // compare to see if type matches this.myType_
+
+    var oldtype = this.myType_;
+
+    var ret = this.getInput('RETURN');
+    // console.log("in setType for function. here is the input:" + ret.connection);
+    if (ret.connection.targetConnection) {
+      if (ret.connection.targetConnection.check_ == 'Number')
+        type = 'Number';
+      else if (ret.connection.targetConnection.check_ == 'Boolean')
+        type = 'Boolean';
+      else if (ret.connection.targetConnection.check_ == 'String')
+        type = 'String';
+      else 
+        type = null;
+    }
+    else type = null;
+
+    // console.log("starting func ST with oldtype:" + this.myType_ + " and newtype:" + type);
+
+    if (this.myType_ == type) {
+      // console.log("in func ST.  returning because types didn't change.");
+      return;
+    }
+    // set the function def's type to what is now connected to its output
+    this.myType_ = type;
+
+    var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
+    var numBumped = [];
+    var conType = null;     // type of a caller's output connection
+    var parentAccepts;
+
+    // start grouping events in case some blocks are bumped out, so that undo will work easily.
+    var eventGroup = true;
+    if (Blockscad.workspace.undoStack_.length) 
+      eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
+    // console.log("event group is: ", eventGroup);
+    Blockly.Events.setGroup(eventGroup);
+
+    // now, set my caller block's types
+    if (callers.length) {
+      for (var i = 0; i < callers.length; i++) {
+        if (!callers[i])
+          continue;
+        // the caller block only gets bumped if it has a parent.
+        var parent = callers[i].getParent();
+        // get caller's connection type here
+        if (parent) {
+          // console.log("found instance with parent: ", parent.type);
+          parentAccepts = callers[i].outputConnection.targetConnection.check_;
+          // console.log(parentAccepts);
+
+          // make sure that parentAccepts is an array so that we can check for a match
+
+          if (parentAccepts && !(goog.isArray(parentAccepts)))
+            parentAccepts = [parentAccepts];
+
+          if (parentAccepts) {
+            var found_match = 0;
+            // check to see if my type matches any type accepted by the parent - if so, it will bump.
+            for (var j = 0; j < parentAccepts.length; j++) {
+              if (parentAccepts[j] == this.myType_) {
+                found_match = 1;
+              }
+            }
+            if (!found_match) {
+              // I have a type mismatch with this variable.  it is going to be bumped.
+              // console.log("warning message!  call block id", callers[i].id, "will be kicked out and backlit");
+              // console.log("parent accepts: " + parentAccepts + ", type is:", this.myType_);
+              numBumped.push(callers[i]);
+              // instances[i].backlight();
+              // this.backlightBlocks.push(instances[i].id);
+              // if the instance is in a collapsed stack, find collapsed parent and expand
+              var topBlock = callers[i].collapsedParents();
+              if (topBlock)
+                for (var j = 0; j < topBlock.length; j++)
+                  topBlock[j].setCollapsed(false);
+            }
+
+          }
+
+        }  // end if (parent)
+
+        // change caller's type - this is the command that actually prompts Blockly to bump blocks out
+
+        // console.log("Set the caller's output check to ", this.myType_);
+
+        if (callers[i]) {
+          callers[i].outputConnection.setCheck(this.myType_);
+          if (this.myType_ == 'Number')
+            callers[i].category = 'NUMBER'
+          else if (this.myType_ == 'Boolean')
+            callers[i].category = 'BOOLEAN';
+          else if (this.myType_ == 'String')
+            callers[i].category = 'STRING';
+          else {
+            callers[i].category = 'UNKNOWN';
+            // console.log("function caller with type UNKNOWN");
+          }
+        }
+        // console.log("tried to set caller type to ",this.myType_, callers[i]);
+        // if it was a bumping change, fire a typing event
+        // if (Blockly.Events.isEnabled() && numBumped.length) {
+        //   Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
+        // }
+
+        // if caller is inside of another setter block, that setter's type needs to be changed.  Do so.
+        // note that this can lead to an infinite loop if procedures are circularly defined - that is why
+        // setType MUST exit immediately if it is called with the type not changing.
+
+        // what if a parent is a variables_set of a different variable?
+        // then I want to call Blockscad.assignVarTypes for that parent.
+
+
+        var setterParent = Blockscad.hasParentOfType(callers[i], "procedures_defreturn");
+        if (!setterParent)
+          setterParent = Blockscad.hasParentOfType(callers[i],"variables_set");
+        if (setterParent) {
+          setTimeout(function() {
+            // console.log("this caller function is inside a setter.  Set its type to: ",type);
+            setterParent.setType(type);
+          }, 0);
+        }
+        else {
+          // if the caller was inside a non setter, I still want to type that parent.
+          var parent = false;
+          if (callers[i])
+            parent = callers[i].getParent();
+          if (parent) {
+            Blockscad.assignBlockTypes(parent)
+          }
+        }
+      }
+    } // end of going through all callers to set their types.
+
+    // turn off event grouping
+    Blockly.Events.setGroup(false);
+
+    // handle backlighting and warning text - do this later so that 
+    // the bumping process itself (which now selects and deselects the blocks) doesn't
+    // just immediately turn the backlighting off.
+
+    for (var k = 0; k < numBumped.length; k++) {
+      numBumped[k].backlight();
+      this.backlightBlocks.push(numBumped[k].id);
+      // finally, set a warning message on the procedure definition that counts how many callers were bumped.
+      var text = '';
+      text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length).replace("%2", parentAccepts).replace("%3",type);
+      this.setWarningText(text);
+    }
+  },
+  setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_,
+  updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_,
+  mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom,
+  domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation,
+  decompose: Blockly.Blocks['procedures_defnoreturn'].decompose,
+  compose: Blockly.Blocks['procedures_defnoreturn'].compose,
+  /**
+   * Return the signature of this procedure definition.
+   * @return {!Array} Tuple containing three elements:
+   *     - the name of the defined procedure,
+   *     - a list of all its arguments,
+   *     - that it DOES have a return value.
+   * @this Blockly.Block
+   */
+  getProcedureDef: function() {
+    return [this.getFieldValue('NAME'), this.arguments_, true];
+  },
+  getVars: Blockly.Blocks['procedures_defnoreturn'].getVars,
+  renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar,
+  customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu,
+  callType_: 'procedures_callreturn'
+};
+
+Blockly.Blocks['procedures_mutatorcontainer'] = {
+  /**
+   * Mutator block for procedure container.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);
+    this.appendStatementInput('STACK');
+    this.appendDummyInput('STATEMENT_INPUT')
+        .appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS)
+        .appendField(new Blockly.FieldCheckbox('TRUE'), 'STATEMENTS');
+    this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['procedures_mutatorarg'] = {
+  /**
+   * Mutator block for procedure argument.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE)
+        .appendField(new Blockly.FieldTextInput('x', this.validator_), 'NAME');
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);
+    this.contextMenu = false;
+  },
+  /**
+   * Obtain a valid name for the procedure.
+   * Merge runs of whitespace.  Strip leading and trailing whitespace.
+   * Beyond this, all names are legal.
+   * @param {string} newVar User-supplied name.
+   * @return {?string} Valid name, or null if a name was not specified.
+   * @private
+   * @this Blockly.Block
+   */
+  validator_: function(newVar) {
+    newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
+    return newVar || null;
+  }
+};
+
+Blockly.Blocks['procedures_callnoreturn'] = {
+  /**
+   * Block for calling a procedure with no return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.category = 'UNKNOWN';     // for blocksCAD
+    this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+    this.appendDummyInput('TOPROW')
+        .appendField(this.id, 'NAME');
+    this.setPreviousStatement(true);
+    //this.setNextStatement(true);  // for Blockscad, we don't want this to have a next.  Breaks difference.
+    // Tooltip is set in domToMutation.
+    this.arguments_ = [];
+    this.quarkConnections_ = {};
+    this.quarkIds_ = null;
+    this.setType();
+  },
+  /**
+   * on being added, this will be called to set the parent procedure type for BlocksCAD
+   */
+  setType: function() {     // for blockscad - jayod
+    var parent = Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace);
+    // console.log("in caller's setType func.  defined by: ",parent);
+    if (parent) {
+      var myType = parent.myType_;
+      // console.log("found a parent procedure with type: ",parent.myType_);
+      if (myType) {
+        this.previousConnection.setCheck(myType);
+        //this.nextConnection.setCheck(myType);   // no more next connection
+        if (myType == 'CSG' || myType == ['CSG'])
+          this.category = 'PRIMITIVE_CSG'
+        else if (myType == 'CAG' || myType == ['CAG'])
+          this.category = 'PRIMITIVE_CAG';
+        else this.category = 'UNKNOWN'; 
+      }
+    }
+  },
+  /**
+   * Returns the name of the procedure this block calls.
+   * @return {string} Procedure name.
+   * @this Blockly.Block
+   */
+  getProcedureCall: function() {
+    // The NAME field is guaranteed to exist, null will never be returned.
+    return /** @type {string} */ (this.getFieldValue('NAME'));
+  },
+  /**
+   * Notification that a procedure is renaming.
+   * If the name matches this block's procedure, rename it.
+   * @param {string} oldName Previous name of procedure.
+   * @param {string} newName Renamed procedure.
+   * @this Blockly.Block
+   */
+  renameProcedure: function(oldName, newName) {
+    if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
+      this.setFieldValue(newName, 'NAME');
+      this.setTooltip(
+          (this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP :
+           Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
+          .replace('%1', newName));
+    }
+  },
+  /**
+   * Notification that the procedure's parameters have changed.
+   * @param {!Array.<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
+   * @param {!Array.<string>} paramIds IDs of params (consistent for each
+   *     parameter through the life of a mutator, regardless of param renaming),
+   *     e.g. ['piua', 'f8b_', 'oi.o'].
+   * @private
+   * @this Blockly.Block
+   */
+  setProcedureParameters_: function(paramNames, paramIds) {
+    // Data structures:
+    // this.arguments = ['x', 'y']
+    //     Existing param names.
+    // this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
+    //     Look-up of paramIds to connections plugged into the call block.
+    // this.quarkIds_ = ['piua', 'f8b_']
+    //     Existing param IDs.
+    // Note that quarkConnections_ may include IDs that no longer exist, but
+    // which might reappear if a param is reattached in the mutator.
+    var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
+        this.workspace);
+    var mutatorOpen = defBlock && defBlock.mutator &&
+        defBlock.mutator.isVisible();
+    if (!mutatorOpen) {
+      this.quarkConnections_ = {};
+      this.quarkIds_ = null;
+    }
+    if (!paramIds) {
+      // Reset the quarks (a mutator is about to open).
+      return;
+    }
+    if (goog.array.equals(this.arguments_, paramNames)) {
+      // No change.
+      this.quarkIds_ = paramIds;
+      return;
+    }
+    if (paramIds.length != paramNames.length) {
+      throw 'Error: paramNames and paramIds must be the same length.';
+    }
+    this.setCollapsed(false);
+    if (!this.quarkIds_) {
+      // Initialize tracking for this block.
+      this.quarkConnections_ = {};
+      if (paramNames.join('\n') == this.arguments_.join('\n')) {
+        // No change to the parameters, allow quarkConnections_ to be
+        // populated with the existing connections.
+        this.quarkIds_ = paramIds;
+      } else {
+        this.quarkIds_ = [];
+      }
+    }
+    // Switch off rendering while the block is rebuilt.
+    var savedRendered = this.rendered;
+    this.rendered = false;
+    // Update the quarkConnections_ with existing connections.
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var input = this.getInput('ARG' + i);
+      if (input) {
+        var connection = input.connection.targetConnection;
+        this.quarkConnections_[this.quarkIds_[i]] = connection;
+        if (mutatorOpen && connection &&
+            paramIds.indexOf(this.quarkIds_[i]) == -1) {
+          // This connection should no longer be attached to this block.
+          connection.disconnect();
+          connection.getSourceBlock().bumpNeighbours_();
+        }
+      }
+    }
+    // Rebuild the block's arguments.
+    this.arguments_ = [].concat(paramNames);
+    this.updateShape_();
+    this.quarkIds_ = paramIds;
+    // Reconnect any child blocks.
+    if (this.quarkIds_) {
+      for (var i = 0; i < this.arguments_.length; i++) {
+        var quarkId = this.quarkIds_[i];
+        if (quarkId in this.quarkConnections_) {
+          var connection = this.quarkConnections_[quarkId];
+          if (!Blockly.Mutator.reconnect(connection, this, 'ARG' + i)) {
+            // Block no longer exists or has been attached elsewhere.
+            delete this.quarkConnections_[quarkId];
+          }
+        }
+      }
+    }
+    // Restore rendering and show the changes.
+    this.rendered = savedRendered;
+    if (this.rendered) {
+      this.render();
+    }
+  },
+  /**
+   * Modify this block to have the correct number of arguments.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function() {
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var field = this.getField('ARGNAME' + i);
+      if (field) {
+        // Ensure argument name is up to date.
+        // The argument name field is deterministic based on the mutation,
+        // no need to fire a change event.
+        Blockly.Events.disable();
+        try {
+          field.setValue(this.arguments_[i]);
+        } finally {
+          Blockly.Events.enable();
+        }
+      } else {
+        // Add new input.
+        field = new Blockly.FieldLabel(this.arguments_[i]);
+        var input = this.appendValueInput('ARG' + i)
+            .setAlign(Blockly.ALIGN_RIGHT)
+            .appendField(field, 'ARGNAME' + i);
+        input.init();
+      }
+    }
+    // Remove deleted inputs.
+    while (this.getInput('ARG' + i)) {
+      this.removeInput('ARG' + i);
+      i++;
+    }
+    // Add 'with:' if there are parameters, remove otherwise.
+    var topRow = this.getInput('TOPROW');
+    if (topRow) {
+      if (this.arguments_.length) {
+        if (!this.getField('WITH')) {
+          topRow.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS, 'WITH');
+          topRow.init();
+        }
+      } else {
+        if (this.getField('WITH')) {
+          topRow.removeField('WITH');
+        }
+      }
+    }
+  },
+  /**
+   * Create XML to represent the (non-editable) name and arguments.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('name', this.getProcedureCall());
+    for (var i = 0; i < this.arguments_.length; i++) {
+      var parameter = document.createElement('arg');
+      parameter.setAttribute('name', this.arguments_[i]);
+      container.appendChild(parameter);
+    }
+    return container;
+  },
+  /**
+   * Parse XML to restore the (non-editable) name and parameters.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    var name = xmlElement.getAttribute('name');
+    this.renameProcedure(this.getProcedureCall(), name);
+    var args = [];
+    var paramIds = [];
+    for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
+      if (childNode.nodeName.toLowerCase() == 'arg') {
+        args.push(childNode.getAttribute('name'));
+        paramIds.push(childNode.getAttribute('paramId'));
+      }
+    }
+    this.setProcedureParameters_(args, paramIds);
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    for (var i = 0; i < this.arguments_.length; i++) {
+      if (Blockly.Names.equals(oldName, this.arguments_[i])) {
+        this.arguments_[i] = newName;
+        this.getField('ARGNAME' + i).setValue(newName);
+      }
+    }
+  },
+  /**
+   * Procedure calls cannot exist without the corresponding procedure
+   * definition.  Enforce this link whenever an event is fired.
+   * @this Blockly.Block
+   */
+  onchange: function(event) {
+    if (!this.workspace || this.workspace.isFlyout) {
+      // Block is deleted or is in a flyout.
+      return;
+    }
+    if (event.type == Blockly.Events.CREATE &&
+        event.ids.indexOf(this.id) != -1) {
+      // Look for the case where a procedure call was created (usually through
+      // paste) and there is no matching definition.  In this case, create
+      // an empty definition block with the correct signature.
+      var name = this.getProcedureCall();
+      var def = Blockly.Procedures.getDefinition(name, this.workspace);
+      if (def && (def.type != this.defType_ ||
+          JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
+        // The signatures don't match.
+        def = null;
+      }
+      if (!def) {
+        Blockly.Events.setGroup(event.group);
+        /**
+         * Create matching definition block.
+         * <xml>
+         *   <block type="procedures_defreturn" x="10" y="20">
+         *     <mutation name="test">
+         *       <arg name="x"></arg>
+         *     </mutation>
+         *     <field name="NAME">test</field>
+         *   </block>
+         * </xml>
+         */
+        var xml = goog.dom.createDom('xml');
+        var block = goog.dom.createDom('block');
+        block.setAttribute('type', this.defType_);
+        var xy = this.getRelativeToSurfaceXY();
+        var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
+        var y = xy.y + Blockly.SNAP_RADIUS * 2;
+        block.setAttribute('x', x);
+        block.setAttribute('y', y);
+        var mutation = this.mutationToDom();
+        block.appendChild(mutation);
+        var field = goog.dom.createDom('field');
+        field.setAttribute('name', 'NAME');
+        field.appendChild(document.createTextNode(this.getProcedureCall()));
+        block.appendChild(field);
+        xml.appendChild(block);
+        Blockly.Xml.domToWorkspace(xml, this.workspace);
+        Blockly.Events.setGroup(false);
+      }
+    } else if (event.type == Blockly.Events.DELETE) {
+      // Look for the case where a procedure definition has been deleted,
+      // leaving this block (a procedure call) orphaned.  In this case, delete
+      // the orphan.
+      var name = this.getProcedureCall();
+      var def = Blockly.Procedures.getDefinition(name, this.workspace);
+      if (!def) {
+        Blockly.Events.setGroup(event.group);
+        this.dispose(true, false);
+        Blockly.Events.setGroup(false);
+      }
+    }
+  },
+  /**
+   * Add menu option to find the definition block for this call.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    var option = {enabled: true};
+    option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
+    var name = this.getProcedureCall();
+    var workspace = this.workspace;
+    option.callback = function() {
+      var def = Blockly.Procedures.getDefinition(name, workspace);
+      workspace.clearBacklight();
+      // def && def.backlight();
+      def && def.select();
+    };
+    options.push(option);
+
+    // for BlocksCAD, 
+    var option = {enabled: true};
+    var name = this.getProcedureCall();
+    option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name);
+
+    var workspace = this.workspace;
+    option.callback = function() {
+      var def = Blockly.Procedures.getDefinition(name, workspace);
+      if (def) {
+        var callers = Blockly.Procedures.getCallers(name, workspace);
+        workspace.clearBacklight();
+        Blockly.selected.unselect();
+        for (var i = 0; callers && i < callers.length; i++) {
+          callers[i] && callers[i].backlight();
+          // if caller block is in a collapsed parent, highlight collapsed parent too
+          var others = callers[i].collapsedParents();
+          if (others)
+            for (var j=0; j < others.length; j++) 
+              others[j].backlight(); 
+        }
+      }
+    };
+    options.push(option); 
+  },
+  defType_: 'procedures_defnoreturn'
+};
+
+Blockly.Blocks['procedures_callreturn'] = {
+  /**
+   * Block for calling a procedure with a return value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.category = 'UNKNOWN';  // for blockscad - jayod
+    this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+    this.appendDummyInput('TOPROW')
+        .appendField('', 'NAME');
+    this.setOutput(true);
+    // Tooltip is set in domToMutation.
+    this.arguments_ = [];
+    this.quarkConnections_ = {};
+    this.quarkIds_ = null;
+    // set the initial type.
+    this.setType();
+  },
+   /**
+   * on being added, this will be called to get the parent procedure type for BlocksCAD
+   */
+  setType: function() {  // for blockscad - jayod
+    var parent = Blockly.Procedures.getDefinition(this.getFieldValue('NAME'),this.workspace);
+    if (parent) {
+      var myType = parent.myType_;
+      if (myType) {
+        // console.log("setting type for new function caller to:",myType);
+       this.outputConnection.setCheck(myType); 
+        if (myType == 'Number')
+          this.category = 'NUMBER'
+        else if (myType == 'Boolean')
+          this.category = 'BOOLEAN';
+        else this.category = 'UNKNOWN'; 
+      }
+    }
+  }, 
+  getProcedureCall: Blockly.Blocks['procedures_callnoreturn'].getProcedureCall,
+  renameProcedure: Blockly.Blocks['procedures_callnoreturn'].renameProcedure,
+  setProcedureParameters_:
+      Blockly.Blocks['procedures_callnoreturn'].setProcedureParameters_,
+  updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
+  mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
+  domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
+  renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar,
+  onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
+   /**
+   * Add menu option to find the definition block for this call.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    var option = {enabled: true};
+    option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
+    var name = this.getProcedureCall();
+    var workspace = this.workspace;
+    option.callback = function() {
+      var def = Blockly.Procedures.getDefinition(name, workspace);
+      workspace.clearBacklight();
+      def && def.backlight();
+    };
+    options.push(option);
+
+    // for BlocksCAD, 
+    var name = this.getProcedureCall();
+    var option = {enabled: true};
+    option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",name);
+
+    var workspace = this.workspace;
+    option.callback = function() {
+      var def = Blockly.Procedures.getDefinition(name, workspace);
+      if (def) {
+        var callers = Blockly.Procedures.getCallers(name, workspace);
+        workspace.clearBacklight();
+        Blockly.selected.unselect();
+        for (var i = 0; callers && i < callers.length; i++) {
+          callers[i] && callers[i].backlight(); 
+          // if caller block is in a collapsed parent, highlight collapsed parent too
+          var others = callers[i].collapsedParents();
+          if (others)
+            for (var j=0; j < others.length; j++) 
+              others[j].backlight(); 
+        }
+      }
+    };
+    options.push(option); 
+  },
+  defType_: 'procedures_defreturn'  
+};
+
+// Blockly.Blocks['procedures_ifreturn'] = {  // commented out for Blockscad
+//   /**
+//    * Block for conditionally returning a value from a procedure.
+//    * @this Blockly.Block
+//    */
+//   init: function() {
+//     this.setHelpUrl('http://c2.com/cgi/wiki?GuardClause');
+//     this.setColour(290);
+//     this.appendValueInput('CONDITION')
+//         .setCheck('Boolean')
+//         .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
+//     this.appendValueInput('VALUE')
+//         .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+//     this.setInputsInline(true);
+//     this.setPreviousStatement(true);
+//     this.setNextStatement(true);
+//     this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);
+//     this.hasReturnValue_ = true;
+//   },
+//   /**
+//    * Create XML to represent whether this block has a return value.
+//    * @return {Element} XML storage element.
+//    * @this Blockly.Block
+//    */
+//   mutationToDom: function() {
+//     var container = document.createElement('mutation');
+//     container.setAttribute('value', Number(this.hasReturnValue_));
+//     return container;
+//   },
+//   /**
+//    * Parse XML to restore whether this block has a return value.
+//    * @param {!Element} xmlElement XML storage element.
+//    * @this Blockly.Block
+//    */
+//   domToMutation: function(xmlElement) {
+//     var value = xmlElement.getAttribute('value');
+//     this.hasReturnValue_ = (value == 1);
+//     if (!this.hasReturnValue_) {
+//       this.removeInput('VALUE');
+//       this.appendDummyInput('VALUE')
+//         .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+//     }
+//   },
+//   /**
+//    * Called whenever anything on the workspace changes.
+//    * Add warning if this flow block is not nested inside a loop.
+//    * @this Blockly.Block
+//    */
+//   onchange: function() {
+//     if (!this.workspace) {
+//       // Block has been deleted.
+//       return;
+//     }
+//     var legal = false;
+//     // Is the block nested in a procedure?
+//     var block = this;
+//     do {
+//       if (block.type == 'procedures_defnoreturn' ||
+//           block.type == 'procedures_defreturn') {
+//         legal = true;
+//         break;
+//       }
+//       block = block.getSurroundParent();
+//     } while (block);
+//     if (legal) {
+//       // If needed, toggle whether this block has a return value.
+//       if (block.type == 'procedures_defnoreturn' && this.hasReturnValue_) {
+//         this.removeInput('VALUE');
+//         this.appendDummyInput('VALUE')
+//           .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+//         this.hasReturnValue_ = false;
+//       } else if (block.type == 'procedures_defreturn' &&
+//                  !this.hasReturnValue_) {
+//         this.removeInput('VALUE');
+//         this.appendValueInput('VALUE')
+//           .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+//         this.hasReturnValue_ = true;
+//       }
+//       this.setWarningText(null);
+//     } else {
+//       this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING);
+//     }
+//   }
+// };

+ 666 - 0
blockly/blocks/text.js

@@ -0,0 +1,666 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Text blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.texts');
+
+goog.require('Blockly.Blocks');
+
+
+Blockscad.Toolbox = Blockscad.Toolbox || {};
+
+Blockly.Blocks.texts.HUE = Blockscad.Toolbox.HEX_TEXT;
+
+Blockly.Blocks['text'] = {
+  /**
+   * Block for text value.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_TEXT);
+    this.appendDummyInput()
+        .appendField(this.newQuote_(true))
+        .appendField(new Blockly.FieldTextInput(Blockscad.Msg.TEXT_DEFAULT_VALUE), 'TEXT')
+        .appendField(this.newQuote_(false));
+    this.setOutput(true, 'String');
+    this.setTooltip(Blockly.Msg.TEXT_TEXT_TOOLTIP);
+  },
+  /**
+   * Create an image of an open or closed quote.
+   * @param {boolean} open True if open quote, false if closed.
+   * @return {!Blockly.FieldImage} The field image of the quote.
+   * @this Blockly.Block
+   * @private
+   */
+  newQuote_: function(open) {
+    if (open == this.RTL) {
+      var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
+    } else {
+      var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
+    }
+    return new Blockly.FieldImage(file, 10, 10, '');
+  }
+};
+
+Blockly.Blocks['text_join'] = {
+  /**
+   * Block for creating a string made up of any number of elements of any type.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_JOIN_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.itemCount_ = 2;
+    this.updateShape_();
+    this.setOutput(true, 'String');
+    this.setMutator(new Blockly.Mutator(['text_create_join_item']));
+    this.setTooltip(Blockly.Msg.TEXT_JOIN_TOOLTIP);
+  },
+  /**
+   * Create XML to represent number of text inputs.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('items', this.itemCount_);
+    return container;
+  },
+  /**
+   * Parse XML to restore the text inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
+    this.updateShape_();
+  },
+  /**
+   * Populate the mutator's dialog with this block's components.
+   * @param {!Blockly.Workspace} workspace Mutator's workspace.
+   * @return {!Blockly.Block} Root block in mutator.
+   * @this Blockly.Block
+   */
+  decompose: function(workspace) {
+    var containerBlock = Blockly.Block.obtain(workspace,
+                                           'text_create_join_container');
+    containerBlock.initSvg();
+    var connection = containerBlock.getInput('STACK').connection;
+    for (var i = 0; i < this.itemCount_; i++) {
+      var itemBlock = Blockly.Block.obtain(workspace, 'text_create_join_item');
+      itemBlock.initSvg();
+      connection.connect(itemBlock.previousConnection);
+      connection = itemBlock.nextConnection;
+    }
+    return containerBlock;
+  },
+  /**
+   * Reconfigure this block based on the mutator dialog's components.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  compose: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    // Count number of inputs.
+    var connections = [];
+    var i = 0;
+    while (itemBlock) {
+      connections[i] = itemBlock.valueConnection_;
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+      i++;
+    }
+    this.itemCount_ = i;
+    this.updateShape_();
+    // Reconnect any child blocks.
+    for (var i = 0; i < this.itemCount_; i++) {
+      if (connections[i]) {
+        this.getInput('ADD' + i).connection.connect(connections[i]);
+      }
+    }
+  },
+  /**
+   * Store pointers to any connected child blocks.
+   * @param {!Blockly.Block} containerBlock Root block in mutator.
+   * @this Blockly.Block
+   */
+  saveConnections: function(containerBlock) {
+    var itemBlock = containerBlock.getInputTargetBlock('STACK');
+    var i = 0;
+    while (itemBlock) {
+      var input = this.getInput('ADD' + i);
+      itemBlock.valueConnection_ = input && input.connection.targetConnection;
+      i++;
+      itemBlock = itemBlock.nextConnection &&
+          itemBlock.nextConnection.targetBlock();
+    }
+  },
+  /**
+   * Modify this block to have the correct number of inputs.
+   * @private
+   * @this Blockly.Block
+   */
+  updateShape_: function() {
+    // Delete everything.
+    if (this.getInput('EMPTY')) {
+      this.removeInput('EMPTY');
+    } else {
+      var i = 0;
+      while (this.getInput('ADD' + i)) {
+        this.removeInput('ADD' + i);
+        i++;
+      }
+    }
+    // Rebuild block.
+    if (this.itemCount_ == 0) {
+      this.appendDummyInput('EMPTY')
+          .appendField(this.newQuote_(true))
+          .appendField(this.newQuote_(false));
+    } else {
+      for (var i = 0; i < this.itemCount_; i++) {
+        var input = this.appendValueInput('ADD' + i);
+        if (i == 0) {
+          input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH);
+        }
+      }
+    }
+  },
+  newQuote_: Blockly.Blocks['text'].newQuote_
+};
+
+Blockly.Blocks['text_create_join_container'] = {
+  /**
+   * Mutator block for container.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN);
+    this.appendStatementInput('STACK');
+    this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['text_create_join_item'] = {
+  /**
+   * Mutator block for add items.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.appendDummyInput()
+        .appendField(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP);
+    this.contextMenu = false;
+  }
+};
+
+Blockly.Blocks['text_append'] = {
+  /**
+   * Block for appending to a variable in place.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_APPEND_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.appendValueInput('TEXT')
+        .appendField(Blockly.Msg.TEXT_APPEND_TO)
+        .appendField(new Blockly.FieldVariable(
+        Blockly.Msg.TEXT_APPEND_VARIABLE), 'VAR')
+        .appendField(Blockly.Msg.TEXT_APPEND_APPENDTEXT);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace('%1',
+          thisBlock.getFieldValue('VAR'));
+    });
+  },
+  /**
+   * Return all variables referenced by this block.
+   * @return {!Array.<string>} List of variable names.
+   * @this Blockly.Block
+   */
+  getVars: function() {
+    return [this.getFieldValue('VAR')];
+  },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+  renameVar: function(oldName, newName) {
+    if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+      this.setFieldValue(newName, 'VAR');
+    }
+  }
+};
+
+Blockly.Blocks['text_length'] = {
+  /**
+   * Block for string length.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_LENGTH_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.interpolateMsg(Blockly.Msg.TEXT_LENGTH_TITLE,
+                        ['VALUE', ['String', 'Array'], Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setOutput(true, 'Number');
+    this.setTooltip(Blockly.Msg.TEXT_LENGTH_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['text_isEmpty'] = {
+  /**
+   * Block for is the string null?
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_ISEMPTY_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.interpolateMsg(Blockly.Msg.TEXT_ISEMPTY_TITLE,
+                        ['VALUE', ['String', 'Array'], Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setOutput(true, 'Boolean');
+    this.setTooltip(Blockly.Msg.TEXT_ISEMPTY_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['text_indexOf'] = {
+  /**
+   * Block for finding a substring in the text.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST, 'FIRST'],
+         [Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST, 'LAST']];
+    this.setHelpUrl(Blockly.Msg.TEXT_INDEXOF_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.setOutput(true, 'Number');
+    this.appendValueInput('VALUE')
+        .setCheck('String')
+        .appendField(Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT);
+    this.appendValueInput('FIND')
+        .setCheck('String')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'END');
+    if (Blockly.Msg.TEXT_INDEXOF_TAIL) {
+      this.appendDummyInput().appendField(Blockly.Msg.TEXT_INDEXOF_TAIL);
+    }
+    this.setInputsInline(true);
+    this.setTooltip(Blockly.Msg.TEXT_INDEXOF_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['text_charAt'] = {
+  /**
+   * Block for getting a character from the string.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.WHERE_OPTIONS =
+        [[Blockly.Msg.TEXT_CHARAT_FROM_START, 'FROM_START'],
+         [Blockly.Msg.TEXT_CHARAT_FROM_END, 'FROM_END'],
+         [Blockly.Msg.TEXT_CHARAT_FIRST, 'FIRST'],
+         [Blockly.Msg.TEXT_CHARAT_LAST, 'LAST'],
+         [Blockly.Msg.TEXT_CHARAT_RANDOM, 'RANDOM']];
+    this.setHelpUrl(Blockly.Msg.TEXT_CHARAT_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.setOutput(true, 'String');
+    this.appendValueInput('VALUE')
+        .setCheck('String')
+        .appendField(Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT);
+    this.appendDummyInput('AT');
+    this.setInputsInline(true);
+    this.updateAt_(true);
+    this.setTooltip(Blockly.Msg.TEXT_CHARAT_TOOLTIP);
+  },
+  /**
+   * Create XML to represent whether there is an 'AT' input.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at', isAt);
+    return container;
+  },
+  /**
+   * Parse XML to restore the 'AT' input.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    // Note: Until January 2013 this block did not have mutations,
+    // so 'at' defaults to true.
+    var isAt = (xmlElement.getAttribute('at') != 'false');
+    this.updateAt_(isAt);
+  },
+  /**
+   * Create or delete an input for the numeric index.
+   * @param {boolean} isAt True if the input should exist.
+   * @private
+   * @this Blockly.Block
+   */
+  updateAt_: function(isAt) {
+    // Destroy old 'AT' and 'ORDINAL' inputs.
+    this.removeInput('AT');
+    this.removeInput('ORDINAL', true);
+    // Create either a value 'AT' input or a dummy input.
+    if (isAt) {
+      this.appendValueInput('AT').setCheck('Number');
+      if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
+        this.appendDummyInput('ORDINAL')
+            .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
+      }
+    } else {
+      this.appendDummyInput('AT');
+    }
+    if (Blockly.Msg.TEXT_CHARAT_TAIL) {
+      this.removeInput('TAIL', true);
+      this.appendDummyInput('TAIL')
+          .appendField(Blockly.Msg.TEXT_CHARAT_TAIL);
+    }
+    var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
+      var newAt = (value == 'FROM_START') || (value == 'FROM_END');
+      // The 'isAt' variable is available due to this function being a closure.
+      if (newAt != isAt) {
+        var block = this.sourceBlock_;
+        block.updateAt_(newAt);
+        // This menu has been destroyed and replaced.  Update the replacement.
+        block.setFieldValue(value, 'WHERE');
+        return null;
+      }
+      return undefined;
+    });
+    this.getInput('AT').appendField(menu, 'WHERE');
+  }
+};
+
+Blockly.Blocks['text_getSubstring'] = {
+  /**
+   * Block for getting substring.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this['WHERE_OPTIONS_1'] =
+        [[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START, 'FROM_START'],
+         [Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END, 'FROM_END'],
+         [Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST, 'FIRST']];
+    this['WHERE_OPTIONS_2'] =
+        [[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START, 'FROM_START'],
+         [Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END, 'FROM_END'],
+         [Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST, 'LAST']];
+    this.setHelpUrl(Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.appendValueInput('STRING')
+        .setCheck('String')
+        .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);
+    this.appendDummyInput('AT1');
+    this.appendDummyInput('AT2');
+    if (Blockly.Msg.TEXT_GET_SUBSTRING_TAIL) {
+      this.appendDummyInput('TAIL')
+          .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL);
+    }
+    this.setInputsInline(true);
+    this.setOutput(true, 'String');
+    this.updateAt_(1, true);
+    this.updateAt_(2, true);
+    this.setTooltip(Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP);
+  },
+  /**
+   * Create XML to represent whether there are 'AT' inputs.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    var isAt1 = this.getInput('AT1').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at1', isAt1);
+    var isAt2 = this.getInput('AT2').type == Blockly.INPUT_VALUE;
+    container.setAttribute('at2', isAt2);
+    return container;
+  },
+  /**
+   * Parse XML to restore the 'AT' inputs.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    var isAt1 = (xmlElement.getAttribute('at1') == 'true');
+    var isAt2 = (xmlElement.getAttribute('at2') == 'true');
+    this.updateAt_(1, isAt1);
+    this.updateAt_(2, isAt2);
+  },
+  /**
+   * Create or delete an input for a numeric index.
+   * This block has two such inputs, independant of each other.
+   * @param {number} n Specify first or second input (1 or 2).
+   * @param {boolean} isAt True if the input should exist.
+   * @private
+   * @this Blockly.Block
+   */
+  updateAt_: function(n, isAt) {
+    // Create or delete an input for the numeric index.
+    // Destroy old 'AT' and 'ORDINAL' inputs.
+    this.removeInput('AT' + n);
+    this.removeInput('ORDINAL' + n, true);
+    // Create either a value 'AT' input or a dummy input.
+    if (isAt) {
+      this.appendValueInput('AT' + n).setCheck('Number');
+      if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
+        this.appendDummyInput('ORDINAL' + n)
+            .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
+      }
+    } else {
+      this.appendDummyInput('AT' + n);
+    }
+    // Move tail, if present, to end of block.
+    if (n == 2 && Blockly.Msg.TEXT_GET_SUBSTRING_TAIL) {
+      this.removeInput('TAIL', true);
+      this.appendDummyInput('TAIL')
+          .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL);
+    }
+    var menu = new Blockly.FieldDropdown(this['WHERE_OPTIONS_' + n],
+        function(value) {
+      var newAt = (value == 'FROM_START') || (value == 'FROM_END');
+      // The 'isAt' variable is available due to this function being a closure.
+      if (newAt != isAt) {
+        var block = this.sourceBlock_;
+        block.updateAt_(n, newAt);
+        // This menu has been destroyed and replaced.  Update the replacement.
+        block.setFieldValue(value, 'WHERE' + n);
+        return null;
+      }
+      return undefined;
+    });
+    this.getInput('AT' + n)
+        .appendField(menu, 'WHERE' + n);
+    if (n == 1) {
+      this.moveInputBefore('AT1', 'AT2');
+    }
+  }
+};
+
+Blockly.Blocks['text_changeCase'] = {
+  /**
+   * Block for changing capitalization.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE, 'UPPERCASE'],
+         [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE, 'LOWERCASE'],
+         [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE, 'TITLECASE']];
+    this.setHelpUrl(Blockly.Msg.TEXT_CHANGECASE_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.appendValueInput('TEXT')
+        .setCheck('String')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'CASE');
+    this.setOutput(true, 'String');
+    this.setTooltip(Blockly.Msg.TEXT_CHANGECASE_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['text_trim'] = {
+  /**
+   * Block for trimming spaces.
+   * @this Blockly.Block
+   */
+  init: function() {
+    var OPERATORS =
+        [[Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH, 'BOTH'],
+         [Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT, 'LEFT'],
+         [Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT, 'RIGHT']];
+    this.setHelpUrl(Blockly.Msg.TEXT_TRIM_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.appendValueInput('TEXT')
+        .setCheck('String')
+        .appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE');
+    this.setOutput(true, 'String');
+    this.setTooltip(Blockly.Msg.TEXT_TRIM_TOOLTIP);
+  }
+};
+
+Blockly.Blocks['text_print'] = {
+  /**
+   * Block for print statement.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.setHelpUrl(Blockly.Msg.TEXT_PRINT_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    this.interpolateMsg(Blockly.Msg.TEXT_PRINT_TITLE,
+                        ['TEXT', null, Blockly.ALIGN_RIGHT],
+                        Blockly.ALIGN_RIGHT);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.TEXT_PRINT_TOOLTIP);
+  }
+};
+Blockly.Blocks['text_prompt_ext'] = {
+  /**
+   * Block for prompt function (external message).
+   * @this Blockly.Block
+   */
+  init: function() {
+    var TYPES =
+        [[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT, 'TEXT'],
+         [Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER, 'NUMBER']];
+    this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    // Assign 'this' to a variable for use in the closures below.
+    var thisBlock = this;
+    var dropdown = new Blockly.FieldDropdown(TYPES, function(newOp) {
+      thisBlock.updateType_(newOp);
+    });
+    this.appendValueInput('TEXT')
+        .appendField(dropdown, 'TYPE');
+    this.setOutput(true, 'String');
+    this.setTooltip(function() {
+      return (thisBlock.getFieldValue('TYPE') == 'TEXT') ?
+          Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT :
+          Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER;
+    });
+  },
+  /**
+   * Modify this block to have the correct output type.
+   * @param {string} newOp Either 'TEXT' or 'NUMBER'.
+   * @private
+   * @this Blockly.Block
+   */
+  updateType_: function(newOp) {
+    if (newOp == 'NUMBER') {
+      this.outputConnection.setCheck('Number');
+    } else {
+      this.outputConnection.setCheck('String');
+    }
+  },
+  /**
+   * Create XML to represent the output type.
+   * @return {!Element} XML storage element.
+   * @this Blockly.Block
+   */
+  mutationToDom: function() {
+    var container = document.createElement('mutation');
+    container.setAttribute('type', this.getFieldValue('TYPE'));
+    return container;
+  },
+  /**
+   * Parse XML to restore the output type.
+   * @param {!Element} xmlElement XML storage element.
+   * @this Blockly.Block
+   */
+  domToMutation: function(xmlElement) {
+    this.updateType_(xmlElement.getAttribute('type'));
+  }
+};
+
+Blockly.Blocks['text_prompt'] = {
+  /**
+   * Block for prompt function (internal message).
+   * @this Blockly.Block
+   */
+  init: function() {
+    var TYPES =
+        [[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT, 'TEXT'],
+         [Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER, 'NUMBER']];
+    // Assign 'this' to a variable for use in the closure below.
+    var thisBlock = this;
+    this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL);
+    this.setColour(Blockly.Blocks.texts.HUE);
+    var dropdown = new Blockly.FieldDropdown(TYPES, function(newOp) {
+      thisBlock.updateType_(newOp);
+    });
+    this.appendDummyInput()
+        .appendField(dropdown, 'TYPE')
+        .appendField(this.newQuote_(true))
+        .appendField(new Blockly.FieldTextInput(''), 'TEXT')
+        .appendField(this.newQuote_(false));
+    this.setOutput(true, 'String');
+    // Assign 'this' to a variable for use in the tooltip closure below.
+    var thisBlock = this;
+    this.setTooltip(function() {
+      return (thisBlock.getFieldValue('TYPE') == 'TEXT') ?
+          Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT :
+          Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER;
+    });
+  },
+  newQuote_: Blockly.Blocks['text'].newQuote_,
+  updateType_: Blockly.Blocks['text_prompt_ext'].updateType_,
+  mutationToDom: Blockly.Blocks['text_prompt_ext'].mutationToDom,
+  domToMutation: Blockly.Blocks['text_prompt_ext'].domToMutation
+};

+ 574 - 0
blockly/blocks/variables.js

@@ -0,0 +1,574 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Variable blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.variables');
+
+goog.require('Blockly.Blocks');
+
+
+
+Blockly.Blocks['variables_get'] = {
+  /**
+   * Block for variable getter.
+   * @this Blockly.Block
+   */
+  init: function() {
+    // this.initialized_type = 0;  // for blockscad - to keep onchange from churning
+    this.setHelpUrl(Blockly.Msg.VARIABLES_GET_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_VARIABLE);
+    this.appendDummyInput()
+        .appendField(new Blockly.FieldVariable(
+        Blockly.Msg.VARIABLES_DEFAULT_NAME), 'VAR');
+    this.setOutput(true);
+    this.setTooltip(Blockly.Msg.VARIABLES_GET_TOOLTIP);
+    this.contextMenuMsg_ = Blockly.Msg.VARIABLES_GET_CREATE_SET;
+
+    // get my "type" from my corresponding variables_set block
+    var all_of_them = Blockly.Variables.getInstances(this.getFieldValue('VAR'), this.workspace);
+    // console.log(all_of_them);
+    var found_it = 0;
+    for (var i = 0; i < all_of_them.length; i++) {
+      if (all_of_them[i].type == 'variables_set') {
+        this.outputConnection.setCheck(all_of_them[i].myType_);
+        // console.log("vars_get " + this.id + " was initialized to " + all_of_them[i].myType_);
+        found_it = 1;
+        break;
+      }
+      if (all_of_them[i].type == 'controls_for' || all_of_them[i].type == 'controls_for_chainhull') {
+        this.outputConnection.setCheck(null);
+        found_it = 1;
+      }
+    }
+    if (!found_it) {
+      // this has no variables_set block... Could be from a procedure.
+      // since I don't know it's type, set it to null.
+      // console.log("is there a variable instance from a procedure here?");
+      this.outputConnection.setCheck(null);
+    }  
+
+  },
+  contextMenuType_: 'variables_set',
+  // /**
+  //  * Return all variables referenced by this block.
+  //  * @return {!Array.<string>} List of variable names.
+  //  * @this Blockly.Block
+  //  */
+  // getVars: function() {
+  //   return [this.getFieldValue('VAR')];
+  // },
+  // /**
+  //  * Notification that a variable is renaming.
+  //  * If the name matches one of this block's variables, rename it.
+  //  * @param {string} oldName Previous name of variable.
+  //  * @param {string} newName Renamed variable.
+  //  * @this Blockly.Block
+  //  */
+  // renameVar: function(oldName, newName) {
+  //   if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+  //     this.setFieldValue(newName, 'VAR');
+  //   }
+  // },
+  /**
+   * onchange: happens on EVERY WORKSPACE CHANGE
+   * because I need to type variables_get blocks before the user 
+   * has a chance to try plugging them in
+   * - could I do this code during init?  Would that make sense?
+   */
+  // onchange: function() {
+  //   if (this.initialized_type == 0) {
+  //     // console.log("initializing a new variables_get: id ", this.id);
+  //     this.initialized_type = 1;
+
+  //     // get my "type" from my corresponding variables_set block
+  //     var all_of_them = Blockly.Variables.getInstances(this.getFieldValue('VAR'), this.workspace);
+  //     var found_it = 0;
+  //     for (var i = 0; i < all_of_them.length; i++) {
+  //       if (all_of_them[i].type == 'variables_set') {
+  //         this.outputConnection.setCheck(all_of_them[i].myType_);
+  //         console.log("vars_get " + this.id + " was initialized to " + all_of_them[i].myType_);
+  //         found_it = 1;
+  //         break;
+  //       }
+  //       if (all_of_them[i].type == 'controls_for' || all_of_them[i].type == 'controls_for_chainhull') {
+  //         this.outputConnection.setCheck(null);
+  //         found_it = 1;
+  //       }
+  //     }
+  //     if (!found_it) {
+  //       // this has no variables_set block... Could be from a procedure.
+  //       // since I don't know it's type, set it to null.
+  //       // console.log("is there a variable instance from a procedure here?");
+  //       this.outputConnection.setCheck(null);
+  //     }      
+  //   }
+  // },
+  /**
+   * Add menu option to create getter/setter block for this setter/getter.
+   * @param {!Array} options List of menu options to add to.
+   * @this Blockly.Block
+   */
+  customContextMenu: function(options) {
+    var option = {enabled: true};
+    var name = this.getFieldValue('VAR');
+    option.text = this.contextMenuMsg_.replace('%1', name);
+    var xmlField = goog.dom.createDom('field', null, name);
+    xmlField.setAttribute('name', 'VAR');
+    var xmlBlock = goog.dom.createDom('block', null, xmlField);
+    xmlBlock.setAttribute('type', this.contextMenuType_);
+    option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
+    options.push(option);
+  
+    // for BlocksCAD, 
+    var option = {enabled: true};
+    option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name);
+    var workspace = this.workspace;
+    var thisVar = this;
+    option.callback = function() {
+      var instances = Blockly.Variables.getInstances(name,workspace); 
+      workspace.clearBacklight();
+      thisVar.unselect();
+      for (var i = 0; instances && i < instances.length; i++) {
+        instances[i] && instances[i].backlight();
+        // if caller block is in a collapsed parent, highlight collapsed parent too
+        var others = instances[i].collapsedParents();
+        if (others)
+          for (var j=0; j < others.length; j++) 
+            others[j].backlight(); 
+      }
+    };
+    options.push(option);  
+  }
+};
+
+Blockly.Blocks['variables_set'] = {
+  /**
+   * Block for variable setter.
+   * @this Blockly.Block
+   */
+  init: function() {
+    this.myType_ = null;       // for blocksCAD
+    this.backlightBlocks = []; // for blocksCAD
+    this.jsonInit({
+      "message0": Blockly.Msg.VARIABLES_SET,
+      "args0": [
+        {
+          "type": "field_variable",
+          "name": "VAR",
+          "variable": Blockly.Msg.VARIABLES_DEFAULT_NAME
+        },
+        {
+          "type": "input_value",
+          "name": "VALUE"
+        }
+      ],
+      "inputsInline": true
+    });
+    this.setHelpUrl(Blockly.Msg.VARIABLES_SET_HELPURL);
+    this.setColour(Blockscad.Toolbox.HEX_VARIABLE);
+    // this.setPreviousStatement(true,['VariableSet']);
+    // this.setNextStatement(true, ['VariableSet','CAG','CSG']);
+    this.setPreviousStatement(true);
+    this.setNextStatement(true);
+    this.setTooltip(Blockly.Msg.VARIABLES_SET_TOOLTIP);
+    this.contextMenuMsg_ = Blockly.Msg.VARIABLES_SET_CREATE_GET;
+  },
+  contextMenuType_: 'variables_get',
+  // g
+  //  * Return all variables referenced by this block.
+  //  * @return {!Array.<string>} List of variable names.
+  //  * @this Blockly.Block
+   
+  // getVars: function() {
+  //   return [this.getFieldValue('VAR')];
+  // },
+   /**
+   * if this variable is set to a value, set the associated variable blocks to 
+   * the type of the value block.
+   * @this Blockly.Block
+   */
+  // setType: function(type) {      // for blocksCAD
+  //   console.log("in variable_set setType:  old:" + this.myType_ + "  and new:" + type);
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+  //   if (this.myType_ == type) {
+  //     console.log("type didn't actually change.  Returning without doing work.");
+  //     return;
+  //   }
+  //   var oldtype = this.myType_;
+  //   // var instances = this.getVars();
+  //   var instances = Blockly.Variables.getInstances(this.getFieldValue('VAR'), this.workspace);
+  //   var numBumped = [];
+  //   var parentAccepts;
+
+  //   // go through instances, pulling out the variables_get blocks.
+  //   // if the variables have a parent block, they might need to get bumped 
+
+  //   // console.log("firing events now - something should have been bumped");
+  //   // lets get the group event
+  //   var eventGroup = true;
+  //   if (Blockscad.workspace.undoStack_.length) 
+  //     eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
+  //   // console.log("event group is: ", eventGroup);
+  //   Blockly.Events.setGroup(eventGroup);
+
+  //   if (instances.length > 0) {
+  //     for (var i = 0; i < instances.length; i++) {
+  //       // console.log("found an instance: ", instances[i].id , " ", instances[i].type);
+  //       if (instances[i].type != "variables_get")
+  //         continue;
+  //       var parent = instances[i].getParent();
+  //       if (type != null) {
+  //         // this is a variables_get block, so the parent is the block connected
+  //         // to the output connection. let's handle any bumpage that occurs.
+  //         if (parent) {
+  //           // console.log("found instance with parent: ", parent.type);
+  //           parentAccepts = instances[i].outputConnection.targetConnection.check_;
+  //           if (parentAccepts != null)
+  //             parentAccepts = parentAccepts[0];
+  //           // console.log("types parent accepts: ",parentAccepts);
+  //           // take care of bumps
+  //           if (parentAccepts != null && parentAccepts != type[0]) {
+  //             // I have a type mismatch with this variable.  it is going to be bumped.
+  //             // console.log("block " + instances[i].id + " will be kicked out.");
+  //             numBumped.push(instances[i]);
+  //             // instances[i].backlight();
+  //             // this.backlightBlocks.push(instances[i].id);
+  //             // if the instance is in a collapsed stack, find collapsed parent and expand
+  //             var topBlock = instances[i].collapsedParents();
+  //             if (topBlock)
+  //               for (var j = 0; j < topBlock.length; j++)
+  //                 topBlock[j].setCollapsed(false);
+  //           }
+
+  //         }  // end if (parent)
+  //       }  // end if type == null
+  //       // actually set the type here
+  //       // console.log("setting block :" + instances[i].id + " to type " + type);
+  //       instances[i].outputConnection.setCheck(type);
+  //       if (Blockly.Events.isEnabled() && numBumped.length) {
+  //         Blockly.Events.fire(new Blockly.Events.Typing(instances[i], oldtype,type));
+  //       }
+  //       // what if a parent is a variables_set of a different variable?
+  //       // then I want to call Blockscad.assignVarTypes for that parent.
+  //       if (parent && parent.type == "variables_set") {
+  //         // console.log("found a variables_set parent from inside variables code");
+  //         console.log("setting var_set type in loopable area");
+  //         this.myType_ = type;
+  //         Blockscad.assignVarTypes(parent);
+  //       }
+  //     }  // end looping through instances
+  //   }  // end if instances.length > 0
+
+
+  //   Blockly.Events.setGroup(false);
+  
+
+  //   if (numBumped.length) {
+  //     // I've already changed the types, so bumping should have happened.  Now do the 
+  //     // backlighting and warning text.
+  //     for (var i = 0; i < numBumped.length; i++) {
+  //       numBumped[i].backlight();
+  //       this.backlightBlocks.push(numBumped[i].id);
+  //     }
+  //     var text = '';
+  //     // text += numBumped.length + " ";
+  //     // took out the name so I wouldn't have to deal with renaming the proc.
+  //     text += Blockscad.Msg.VARIABLES_BUMPED_ONE.replace("%1", numBumped.length) + '\n';
+  //     text += Blockscad.Msg.VARIABLES_BUMPED_TWO.replace("%1",this.getFieldValue('VAR')).replace("%2", parentAccepts).replace("%3", type[0]);
+
+  //     this.setWarningText(text);
+  //   }
+  //   else
+  //     this.setWarningText(null);
+
+  //   // set the variables_set type - important for stopping infinite typing loops
+  //   console.log("setting variable set type");
+  //   this.myType_ = type;
+
+  // },        // end for blocksCAD
+  setType: function(type) {      // for blocksCAD
+
+    if (!this.workspace) {
+      // Block has been deleted.
+      return;
+    }
+    if (type != null && !goog.isArray(type)) {
+      type = [type];
+    }
+    // console.log("in variable_set setType:  old:" + this.myType_ + "  and new:" + type);
+    if (Blockscad.arraysEqual(type, this.myType_)) {
+      // console.log("type didn't actually change.  Returning without doing work.");
+      return;
+    }
+    var oldtype = this.myType_;
+
+    // set the type of the setter.
+
+    this.myType_ = type;
+
+    var instances = Blockly.Variables.getInstances(this.getFieldValue('VAR'), this.workspace);
+    var numBumped = [];
+    var parentAccepts;
+
+    // go through instances, pulling out the variables_get blocks.
+    // if the variables have a parent block, they might need to get bumped 
+
+    // Group the events during bumping so undo will work as a unit.
+    var eventGroup = true;
+    if (Blockscad.workspace.undoStack_.length) 
+      eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
+    // console.log("event group is: ", eventGroup);
+    Blockly.Events.setGroup(eventGroup);
+
+    if (instances.length > 0) {
+      for (var i = 0; i < instances.length; i++) {
+        // console.log("found an instance: ", instances[i].id , " ", instances[i].type);
+        if (instances[i].type != "variables_get")
+          continue;
+        var parent = instances[i].getParent();
+        if (this.myType_ != null) {
+          // this is a variables_get block, so the parent is the block connected
+          // to the output connection. let's handle any bumpage that occurs.
+          if (parent) {
+            // console.log("found instance with parent: ", parent.type);
+            parentAccepts = instances[i].outputConnection.targetConnection.check_;
+            if (parentAccepts != null)
+              parentAccepts = parentAccepts[0];
+            // console.log("Test for bumping. types were: " + parentAccepts + " and " + type[0]);
+            // take care of bumps
+            if (parentAccepts != null && type != null && parentAccepts != type[0]) {
+              // console.log(" variable block " + instances[i].id + " will be kicked out.");
+            // console.log("parent accepts: " + parentAccepts + ", type is:", type);
+              numBumped.push(instances[i]);
+              // if the instance is in a collapsed stack, find collapsed parent and expand
+              var topBlock = instances[i].collapsedParents();
+              if (topBlock)
+                for (var j = 0; j < topBlock.length; j++)
+                  topBlock[j].setCollapsed(false);
+            }
+
+          }  // end if (parent)
+        }  // end if type == null
+
+        // actually set the caller's type here
+        // console.log("setting block :" + instances[i].id + " to type " + type);
+        instances[i].outputConnection.setCheck(type);
+        if (Blockly.Events.isEnabled() && numBumped.length) {
+          Blockly.Events.fire(new Blockly.Events.Typing(instances[i], oldtype,type));
+        }
+        // console.log("trying to call hasParentOfType",callers[i]);
+        // caller has a parent that is a setter - either a variable or a function definition.
+        var setterParent = Blockscad.hasParentOfType(instances[i], "procedures_defreturn");
+        if (!setterParent)
+          setterParent = Blockscad.hasParentOfType(instances[i],"variables_set");
+
+        // Be careful here.  Even if you have a setter parent, if there is a ternary parent,
+        // it will _change_ your type from boolean to number.  Don't set your setter parent's 
+        // type to boolean!
+
+        // of course, right now ternary typing is borked anyway, because it ALWAYS returns a number.
+        // though you really should be able to have shapes, text, etc coming out of that.
+        // but for now, I'm just going to ignore setting a type if a ternary parent is involved.
+        var ternaryParent = Blockscad.hasParentOfType(instances[i], "logic_ternary");
+
+        setTimeout(function() {
+          // console.log("this caller function is inside a setter.  Set its type to: ",type);
+          if (setterParent && !ternaryParent) setterParent.setType(type);
+        }, 0);
+
+
+      }  // end looping through instances
+    }  // end if instances.length > 0
+
+
+    Blockly.Events.setGroup(false);
+  
+
+    if (numBumped.length) {
+      // I've already changed the types, so bumping should have happened.  Now do the 
+      // backlighting and warning text.
+      for (var i = 0; i < numBumped.length; i++) {
+        numBumped[i].backlight();
+        this.backlightBlocks.push(numBumped[i].id);
+      }
+      var text = '';
+      // text += numBumped.length + " ";
+      // took out the name so I wouldn't have to deal with renaming the proc.
+      text += Blockscad.Msg.VARIABLES_BUMPED_ONE.replace("%1", numBumped.length) + '\n';
+      text += Blockscad.Msg.VARIABLES_BUMPED_TWO.replace("%1",this.getFieldValue('VAR')).replace("%2", parentAccepts).replace("%3", type);
+
+      this.setWarningText(text);
+    }
+    else
+      this.setWarningText(null);
+  },        // end for blocksCAD
+  // setType: function(type, drawMe) {
+  //   if (!this.workspace) {
+  //     // Block has been deleted.
+  //     return;
+  //   }
+
+  //   // // compare to see if type matches this.myType_
+
+
+  //   var oldtype = this.myType_;
+
+  //   var ret = this.getInput('RETURN');
+  //   // console.log("in setType for function. here is the input:",ret);
+  //   if (ret.connection.targetConnection) {
+  //     if (ret.connection.targetConnection.check_ == 'Number')
+  //       type = 'Number';
+  //     else if (ret.connection.targetConnection.check_ == 'Boolean')
+  //       type = 'Boolean';
+  //     else if (ret.connection.targetConnection.check_ == 'String')
+  //       type = 'String';
+  //     else 
+  //       type = null;
+  //   }
+  //   else type = null;
+
+  //   // console.log("starting func ST with oldtype:" + this.myType_ + " and newtype:" + type);
+
+  //   if (this.myType_ == type) {
+  //     console.log("in func ST.  returning because types didn't change.");
+  //     return;
+  //   }
+  //   // set the function def's type to what is now connected to its output
+  //   this.myType_ = type;
+
+  //   var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
+  //   var numBumped = [];
+  //   var conType = null;     // type of a caller's output connection
+  //   var parentAccepts;
+
+  //   // start grouping events in case some blocks are bumped out, so that undo will work easily.
+  //   var eventGroup = true;
+  //   if (Blockscad.workspace.undoStack_.length) 
+  //     eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
+  //   // console.log("event group is: ", eventGroup);
+  //   Blockly.Events.setGroup(eventGroup);
+
+  //   // now, set my caller block's types
+  //   if (callers.length) {
+  //     for (var i = 0; i < callers.length; i++) {
+  //       // the caller block only gets bumped if it has a parent.
+  //       var parent = callers[i].getParent();
+  //       // get caller's connection type here
+  //       if (parent) {
+  //         // console.log("found instance with parent: ", parent.type);
+  //         parentAccepts = callers[i].outputConnection.targetConnection.check_;
+
+  //         if (parentAccepts != null && goog.isArray(parentAccepts))
+  //           parentAccepts = parentAccepts[0];
+
+  //         var callerAccepts = callers[i].outputConnection.check_;
+
+  //         // take care of bumps
+  //         if (parentAccepts != null && this.myType_ != null && parentAccepts != this.myType_) {
+  //           // I have a type mismatch with this variable.  it is going to be bumped.
+  //           // console.log("warning message!  call block id", callers[i].id, "will be kicked out and backlit");
+  //           numBumped.push(callers[i]);
+  //           // instances[i].backlight();
+  //           // this.backlightBlocks.push(instances[i].id);
+  //           // if the instance is in a collapsed stack, find collapsed parent and expand
+  //           var topBlock = callers[i].collapsedParents();
+  //           if (topBlock)
+  //             for (var j = 0; j < topBlock.length; j++)
+  //               topBlock[j].setCollapsed(false);
+  //         }
+
+  //       }  // end if (parent)
+
+  //       // change caller's type - this is the command that actually prompts Blockly to bump blocks out
+
+  //       // console.log("Set the caller's output check to ", this.myType_);
+
+  //       callers[i].outputConnection.setCheck(this.myType_);
+  //       if (this.myType_ == 'Number')
+  //         callers[i].category = 'NUMBER'
+  //       else if (this.myType_ == 'Boolean')
+  //         callers[i].category = 'BOOLEAN';
+  //       else if (this.myType_ == 'String')
+  //         callers[i].category == 'STRING';
+  //       else callers[i].category = 'UNKNOWN';
+  //       // console.log("tried to set caller type to ",this.myType_, callers[i]);
+  //       // if it was a bumping change, fire a typing event
+  //       if (Blockly.Events.isEnabled() && numBumped.length) {
+  //         Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
+  //       }
+
+  //       // if caller is inside of another setter block, that setter's type needs to be changed.  Do so.
+  //       // note that this can lead to an infinite loop if procedures are circularly defined - that is why
+  //       // setType MUST exit immediately if it is called with the type not changing.
+
+  //       // what if a parent is a variables_set of a different variable?
+  //       // then I want to call Blockscad.assignVarTypes for that parent.
+
+  //       // console.log("trying to call hasParentOfType",callers[i]);
+  //       var setterParent = Blockscad.hasParentOfType(callers[i], "procedures_defreturn");
+  //       if (!setterParent)
+  //         setterParent = Blockscad.hasParentOfType(callers[i],"variables_set");
+  //       if (setterParent) {
+  //         setTimeout(function() {
+  //           console.log("this caller function is inside a setter.  Set its type to: ",type);
+  //           setterParent.setType(type);
+  //         }, 0);
+  //       }
+  //     }
+  //   } // end of going through all callers to set their types.
+
+  //   // turn off event grouping
+  //   Blockly.Events.setGroup(false);
+
+  //   // handle backlighting and warning text - do this later so that 
+  //   // the bumping process itself (which now selects and deselects the blocks) doesn't
+  //   // just immediately turn the backlighting off.
+
+  //   for (var k = 0; k < numBumped.length; k++) {
+  //     numBumped[k].backlight();
+  //     this.backlightBlocks.push(numBumped[k].id);
+  //     // finally, set a warning message on the procedure definition that counts how many callers were bumped.
+  //     var text = '';
+  //     text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length).replace("%2", parentAccepts).replace("%3",type);
+  //     this.setWarningText(text);
+  //   }
+  // },
+  /**
+   * Notification that a variable is renaming.
+   * If the name matches one of this block's variables, rename it.
+   * @param {string} oldName Previous name of variable.
+   * @param {string} newName Renamed variable.
+   * @this Blockly.Block
+   */
+//   renameVar: function(oldName, newName) {
+//     if (Blockly.Names.equals(oldName, this.getFieldValue('VAR'))) {
+//       this.setFieldValue(newName, 'VAR');
+//     }
+//   },
+  customContextMenu: Blockly.Blocks['variables_get'].customContextMenu
+};

+ 255 - 0
blockly/blocks_compressed.js

@@ -0,0 +1,255 @@
+// Do not edit this file; automatically generated by build.py.
+'use strict';
+
+
+// Copyright 2012 Google Inc.  Apache License 2.0
+Blockly.Blocks.colour={};Blockly.Blocks.colour_picker={init:function(){this.setHelpUrl(Blockly.Msg.COLOUR_PICKER_HELPURL);this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(new Blockly.FieldColour("#ff0000"),"COLOUR");this.setOutput(!0,"Colour");this.setTooltip(Blockly.Msg.COLOUR_PICKER_TOOLTIP)}};
+Blockly.Blocks.colour_random={init:function(){this.setHelpUrl(Blockly.Msg.COLOUR_RANDOM_HELPURL);this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockly.Msg.COLOUR_RANDOM_TITLE);this.setOutput(!0,"Colour");this.setTooltip(Blockly.Msg.COLOUR_RANDOM_TOOLTIP)}};
+Blockly.Blocks.colour_rgb={init:function(){this.setHelpUrl(Blockly.Msg.COLOUR_RGB_HELPURL);this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendValueInput("RED").setCheck("Number").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_RGB_TITLE).appendField(Blockly.Msg.COLOUR_RGB_RED);this.appendValueInput("GREEN").setCheck("Number").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_RGB_GREEN);this.appendValueInput("BLUE").setCheck("Number").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_RGB_BLUE);
+this.setOutput(!0,"Colour");this.setTooltip(Blockly.Msg.COLOUR_RGB_TOOLTIP)}};
+Blockly.Blocks.colour_blend={init:function(){this.setHelpUrl(Blockly.Msg.COLOUR_BLEND_HELPURL);this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendValueInput("COLOUR1").setCheck("Colour").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_BLEND_TITLE).appendField(Blockly.Msg.COLOUR_BLEND_COLOUR1);this.appendValueInput("COLOUR2").setCheck("Colour").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_BLEND_COLOUR2);this.appendValueInput("RATIO").setCheck("Number").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.COLOUR_BLEND_RATIO);
+this.setOutput(!0,"Colour");this.setTooltip(Blockly.Msg.COLOUR_BLEND_TOOLTIP)}};Blockly.Blocks.union={init:function(){this.category="SET_OP";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_SETOP);this.appendDummyInput().appendField(Blockscad.Msg.UNION);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.appendStatementInput("PLUS0").appendField(Blockscad.Msg.PLUS).setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.UNION_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));
+this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).appendField(Blockscad.Msg.PLUS).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==
+a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).appendField(Blockscad.Msg.PLUS).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a){if(this.workspace){this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);
+this.getInput("PLUS0").connection.setCheck(a);for(var b=1;b<=this.plusCount_;b++)this.getInput("PLUS"+b).connection.setCheck(a)}}};
+Blockly.Blocks.difference={init:function(){this.category="SET_OP";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_SETOP);this.appendDummyInput().appendField(Blockscad.Msg.DIFFERENCE);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.appendStatementInput("MINUS0").appendField(Blockscad.Msg.MINUS).setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.DIFFERENCE_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));
+this.minusCount_=0},mutationToDom:function(){if(!this.minusCount_)return null;var a=document.createElement("mutation");this.minusCount_&&a.setAttribute("minus",this.minusCount_);return a},domToMutation:function(a){this.minusCount_=parseInt(a.getAttribute("minus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.minusCount_;b++)this.appendStatementInput("MINUS"+b).appendField(Blockscad.Msg.MINUS).setCheck(a);1<=this.minusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==
+a?(this.minusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("MINUS"+this.minusCount_).appendField(Blockscad.Msg.MINUS).setCheck(a)):-1==a&&(this.removeInput("MINUS"+this.minusCount_),this.minusCount_--);1<=this.minusCount_?1==this.minusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a){if(this.workspace){this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);
+this.getInput("MINUS0").connection.setCheck(a);for(var b=1;b<=this.minusCount_;b++)this.getInput("MINUS"+b).connection.setCheck(a)}}};
+Blockly.Blocks.intersection={init:function(){this.category="SET_OP";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_SETOP);this.appendDummyInput().appendField(Blockscad.Msg.INTERSECTION);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.appendStatementInput("WITH0").appendField(Blockscad.Msg.WITH).setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.INTERSECTION_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));
+this.withCount_=0},mutationToDom:function(){if(!this.withCount_)return null;var a=document.createElement("mutation");this.withCount_&&a.setAttribute("with",this.withCount_);return a},domToMutation:function(a){this.withCount_=parseInt(a.getAttribute("with"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.withCount_;b++)this.appendStatementInput("WITH"+b).appendField(Blockscad.Msg.WITH).setCheck(a);1<=this.withCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==
+a?(this.withCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("WITH"+this.withCount_).appendField(Blockscad.Msg.WITH).setCheck(a)):-1==a&&(this.removeInput("WITH"+this.withCount_),this.withCount_--);1<=this.withCount_?1==this.withCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a){if(this.workspace){this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);
+this.getInput("WITH0").connection.setCheck(a);for(var b=1;b<=this.withCount_;b++)this.getInput("WITH"+b).connection.setCheck(a)}}};
+Blockly.Blocks.hull={init:function(){this.category="SET_OP";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_SETOP);this.appendDummyInput().appendField(Blockscad.Msg.CONVEX_HULL);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.appendStatementInput("WITH0").appendField(Blockscad.Msg.WITH).setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.HULL_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));
+this.withCount_=0},mutationToDom:function(){if(!this.withCount_)return null;var a=document.createElement("mutation");this.withCount_&&a.setAttribute("with",this.withCount_);return a},domToMutation:function(a){this.withCount_=parseInt(a.getAttribute("with"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.withCount_;b++)this.appendStatementInput("WITH"+b).appendField(Blockscad.Msg.WITH).setCheck(a);1<=this.withCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==
+a?(this.withCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("WITH"+this.withCount_).appendField(Blockscad.Msg.WITH).setCheck(a)):-1==a&&(this.removeInput("WITH"+this.withCount_),this.withCount_--);1<=this.withCount_?1==this.withCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a){if(this.workspace){this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);
+this.getInput("WITH0").connection.setCheck(a);for(var b=1;b<=this.withCount_;b++)this.getInput("WITH"+b).connection.setCheck(a)}}};Blockly.Blocks.lists={};Blockscad.Toolbox=Blockscad.Toolbox||{};Blockly.Blocks.lists.HUE=Blockscad.Toolbox.HEX_LOGIC;Blockly.Blocks.lists_create_empty={init:function(){this.setHelpUrl(Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.setOutput(!0,"Array");this.appendDummyInput().appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);this.setTooltip(Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP)}};
+Blockly.Blocks.lists_create_with={init:function(){this.setHelpUrl(Blockly.Msg.LISTS_CREATE_WITH_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new Blockly.Mutator(["lists_create_with_item"]));this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),
+10);this.updateShape_()},decompose:function(a){var b=Blockly.Block.obtain(a,"lists_create_with_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=Blockly.Block.obtain(a,"lists_create_with_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){a=a.getInputTargetBlock("STACK");for(var b=[],c=0;a;)b[c]=a.valueConnection_,a=a.nextConnection&&a.nextConnection.targetBlock(),c++;this.itemCount_=c;this.updateShape_();
+for(c=0;c<this.itemCount_;c++)b[c]&&this.getInput("ADD"+c).connection.connect(b[c])},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;b++;a=a.nextConnection&&a.nextConnection.targetBlock()}},updateShape_:function(){if(this.getInput("EMPTY"))this.removeInput("EMPTY");else for(var a=0;this.getInput("ADD"+a);)this.removeInput("ADD"+a),a++;if(0==this.itemCount_)this.appendDummyInput("EMPTY").appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);
+else for(a=0;a<this.itemCount_;a++){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH)}}};Blockly.Blocks.lists_create_with_container={init:function(){this.setColour(Blockly.Blocks.lists.HUE);this.appendDummyInput().appendField(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);this.appendStatementInput("STACK");this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.lists_create_with_item={init:function(){this.setColour(Blockly.Blocks.lists.HUE);this.appendDummyInput().appendField(Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.lists_repeat={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_REPEAT_TITLE,args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",colour:Blockly.Blocks.lists.HUE,tooltip:Blockly.Msg.LISTS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.LISTS_REPEAT_HELPURL})}};
+Blockly.Blocks.lists_length={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_LENGTH_TITLE,args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",colour:Blockly.Blocks.lists.HUE,tooltip:Blockly.Msg.LISTS_LENGTH_TOOLTIP,helpUrl:Blockly.Msg.LISTS_LENGTH_HELPURL})}};
+Blockly.Blocks.lists_isEmpty={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_ISEMPTY_TITLE,args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",colour:Blockly.Blocks.lists.HUE,tooltip:Blockly.Msg.LISTS_ISEMPTY_TOOLTIP,helpUrl:Blockly.Msg.LISTS_ISEMPTY_HELPURL})}};
+Blockly.Blocks.lists_indexOf={init:function(){var a=[[Blockly.Msg.LISTS_INDEX_OF_FIRST,"FIRST"],[Blockly.Msg.LISTS_INDEX_OF_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.LISTS_INDEX_OF_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("Array").appendField(Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST);this.appendValueInput("FIND").appendField(new Blockly.FieldDropdown(a),"END");this.setInputsInline(!0);this.setTooltip(Blockly.Msg.LISTS_INDEX_OF_TOOLTIP)}};
+Blockly.Blocks.lists_getIndex={init:function(){var a=[[Blockly.Msg.LISTS_GET_INDEX_GET,"GET"],[Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE,"GET_REMOVE"],[Blockly.Msg.LISTS_GET_INDEX_REMOVE,"REMOVE"]];this.WHERE_OPTIONS=[[Blockly.Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_INDEX_FIRST,"FIRST"],[Blockly.Msg.LISTS_GET_INDEX_LAST,"LAST"],[Blockly.Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL);
+this.setColour(Blockly.Blocks.lists.HUE);a=new Blockly.FieldDropdown(a,function(a){this.sourceBlock_.updateStatement_("REMOVE"==a)});this.appendValueInput("VALUE").setCheck("Array").appendField(Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST);this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");this.appendDummyInput("AT");Blockly.Msg.LISTS_GET_INDEX_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.LISTS_GET_INDEX_TAIL);this.setInputsInline(!0);this.setOutput(!0);this.updateAt_(!0);
+var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE")+"_"+b.getFieldValue("WHERE");return Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_"+a]})},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("statement",!this.outputConnection);var b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("statement");this.updateStatement_(b);a="false"!=a.getAttribute("at");this.updateAt_(a)},updateStatement_:function(a){a!=
+!this.outputConnection&&(this.unplug(!0,!0),a?(this.setOutput(!1),this.setPreviousStatement(!0),this.setNextStatement(!0)):(this.setPreviousStatement(!1),this.setNextStatement(!1),this.setOutput(!0)))},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,
+function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.sourceBlock_;e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE");Blockly.Msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};
+Blockly.Blocks.lists_setIndex={init:function(){var a=[[Blockly.Msg.LISTS_SET_INDEX_SET,"SET"],[Blockly.Msg.LISTS_SET_INDEX_INSERT,"INSERT"]];this.WHERE_OPTIONS=[[Blockly.Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_INDEX_FIRST,"FIRST"],[Blockly.Msg.LISTS_GET_INDEX_LAST,"LAST"],[Blockly.Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.appendValueInput("LIST").setCheck("Array").appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST);
+this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"MODE").appendField("","SPACE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_TO);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE")+"_"+b.getFieldValue("WHERE");return Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_"+a]})},
+mutationToDom:function(){var a=document.createElement("mutation"),b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a="false"!=a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,
+function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.sourceBlock_;e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&this.moveInputBefore("ORDINAL","TO");this.getInput("AT").appendField(b,"WHERE")}};
+Blockly.Blocks.lists_getSublist={init:function(){this.WHERE_OPTIONS_1=[[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.LISTS_GET_SUBLIST_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);
+this.appendValueInput("LIST").setCheck("Array").appendField(Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);this.appendDummyInput("AT1");this.appendDummyInput("AT2");Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.LISTS_GET_SUBLIST_TAIL);this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation"),b=this.getInput("AT1").type==
+Blockly.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type==Blockly.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("at1");a="true"==a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
+this.appendDummyInput("AT"+a);var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var f=this.sourceBlock_;f.updateAt_(a,d);f.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"));Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
+Blockly.Blocks.lists_split={init:function(){var a=this,b=new Blockly.FieldDropdown([[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]],function(b){a.updateType_(b)});this.setHelpUrl(Blockly.Msg.LISTS_SPLIT_HELPURL);this.setColour(Blockly.Blocks.lists.HUE);this.appendValueInput("INPUT").setCheck("String").appendField(b,"MODE");this.appendValueInput("DELIM").setCheck("String").appendField(Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER);this.setInputsInline(!0);
+this.setOutput(!0,"Array");this.setTooltip(function(){var b=a.getFieldValue("MODE");if("SPLIT"==b)return Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT;if("JOIN"==b)return Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN;throw"Unknown mode: "+b;})},updateType_:function(a){"SPLIT"==a?(this.outputConnection.setCheck("Array"),this.getInput("INPUT").setCheck("String")):(this.outputConnection.setCheck("String"),this.getInput("INPUT").setCheck("Array"))},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("mode",
+this.getFieldValue("MODE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("mode"))}};Blockly.Blocks.logic={};
+Blockly.Blocks.controls_if={init:function(){this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.appendValueInput("IF0").setCheck(["Boolean","Number"]).appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);this.appendStatementInput("DO0").appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN).setCheck(["CSG","CAG"]);this.setPreviousStatement(!0);this.setMutator(new Blockly.Mutator(["controls_if_elseif","controls_if_else"]));var a=this;this.setTooltip(function(){if(a.elseifCount_||a.elseCount_){if(!a.elseifCount_&&
+a.elseCount_)return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;if(a.elseifCount_&&!a.elseCount_)return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;if(a.elseifCount_&&a.elseCount_)return Blockly.Msg.CONTROLS_IF_TOOLTIP_4}else return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;return""});this.elseCount_=this.elseifCount_=0},mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=document.createElement("mutation");this.elseifCount_&&a.setAttribute("elseif",this.elseifCount_);this.elseCount_&&a.setAttribute("else",
+1);return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||0;for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).setCheck(["CSG","CAG"]).appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").setCheck(["CSG","CAG"]).appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE)},
+decompose:function(a){var b=Blockly.Block.obtain(a,"controls_if_if");b.initSvg();for(var c=b.getInput("STACK").connection,d=1;d<=this.elseifCount_;d++){var e=Blockly.Block.obtain(a,"controls_if_elseif");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}this.elseCount_&&(a=Blockly.Block.obtain(a,"controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){this.elseCount_&&this.removeInput("ELSE");this.elseCount_=0;for(var b=this.elseifCount_;0<b;b--)this.removeInput("IF"+
+b),this.removeInput("DO"+b);this.elseifCount_=0;for(a=a.getInputTargetBlock("STACK");a;){switch(a.type){case "controls_if_elseif":this.elseifCount_++;var b=this.appendValueInput("IF"+this.elseifCount_).setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF),c=this.appendStatementInput("DO"+this.elseifCount_).setCheck(["CSG","CAG"]);c.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);a.valueConnection_&&b.connection.connect(a.valueConnection_);a.statementConnection_&&c.connection.connect(a.statementConnection_);
+break;case "controls_if_else":this.elseCount_++;b=this.appendStatementInput("ELSE").setCheck(["CSG","CAG"]);b.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);a.statementConnection_&&b.connection.connect(a.statementConnection_);break;default:throw"Unknown block type.";}a=a.nextConnection&&a.nextConnection.targetBlock()}},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=1;a;){switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b),d=this.getInput("DO"+b);a.valueConnection_=
+c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":d=this.getInput("ELSE");a.statementConnection_=d&&d.connection.targetConnection;break;default:throw"Unknown block type.";}a=a.nextConnection&&a.nextConnection.targetBlock()}}};
+Blockly.Blocks.controls_if_if={init:function(){this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF);this.appendStatementInput("STACK");this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.controls_if_elseif={init:function(){this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.controls_if_else={init:function(){this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE);this.setPreviousStatement(!0);this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.logic_compare={init:function(){var a=this.RTL?[["=","EQ"],["\u2260","NEQ"],[">","LT"],["\u2265","LTE"],["<","GT"],["\u2264","GTE"]]:[["=","EQ"],["\u2260","NEQ"],["<","LT"],["\u2264","LTE"],[">","GT"],["\u2265","GTE"]];this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.setOutput(!0,"Boolean");this.appendValueInput("A");this.appendValueInput("B").appendField(new Blockly.FieldDropdown(a),"OP");this.setInputsInline(!0);var b=this;this.setTooltip(function(){var a=
+b.getFieldValue("OP");return{EQ:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,NEQ:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,LT:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,LTE:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,GT:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,GTE:Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE}[a]});this.prevBlocks_=[null,null]},onchange:function(){var a=this.getInputTargetBlock("A"),b=this.getInputTargetBlock("B");if(a&&b&&!a.outputConnection.checkType_(b.outputConnection))for(var c=0;c<this.prevBlocks_.length;c++){var d=
+this.prevBlocks_[c];if(d===a||d===b)d.setParent(null),d.bumpNeighbours_()}this.prevBlocks_[0]=a;this.prevBlocks_[1]=b}};
+Blockly.Blocks.logic_operation={init:function(){var a=[[Blockly.Msg.LOGIC_OPERATION_AND,"AND"],[Blockly.Msg.LOGIC_OPERATION_OR,"OR"]];this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.setOutput(!0,"Boolean");this.appendValueInput("A").setCheck("Boolean");this.appendValueInput("B").setCheck("Boolean").appendField(new Blockly.FieldDropdown(a),"OP");this.setInputsInline(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("OP");return{AND:Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND,
+OR:Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR}[a]})}};Blockly.Blocks.logic_negate={init:function(){this.setHelpUrl(Blockly.Msg.LOGIC_NEGATE_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.setOutput(!0,"Boolean");this.interpolateMsg(Blockly.Msg.LOGIC_NEGATE_TITLE,["BOOL","Boolean",Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setTooltip(Blockly.Msg.LOGIC_NEGATE_TOOLTIP)}};
+Blockly.Blocks.logic_boolean={init:function(){var a=[[Blockly.Msg.LOGIC_BOOLEAN_TRUE,"TRUE"],[Blockly.Msg.LOGIC_BOOLEAN_FALSE,"FALSE"]];this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.setOutput(!0,"Boolean");this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"BOOL");this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP)}};
+Blockly.Blocks.logic_null={init:function(){this.setHelpUrl(Blockly.Msg.LOGIC_NULL_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.setOutput(!0,"Boolean");this.appendDummyInput().appendField(Blockly.Msg.LOGIC_NULL);this.setTooltip(Blockly.Msg.LOGIC_NULL_TOOLTIP)}};
+Blockly.Blocks.logic_ternary={init:function(){this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOGIC);this.appendValueInput("IF").setCheck("Boolean").appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);this.appendValueInput("THEN").appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);this.appendValueInput("ELSE").appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);this.setOutput(!0,"Number");this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP)}};Blockly.Blocks.loops={};
+Blockly.Blocks.controls_repeat={init:function(){this.setHelpUrl(Blockly.Msg.CONTROLS_REPEAT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_REPEAT_TITLE_REPEAT).appendField(new Blockly.FieldTextInput("10",Blockly.FieldTextInput.nonnegativeIntegerValidator),"TIMES").appendField(Blockly.Msg.CONTROLS_REPEAT_TITLE_TIMES);this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);this.setPreviousStatement(!0);this.setNextStatement(!0);
+this.setTooltip(Blockly.Msg.CONTROLS_REPEAT_TOOLTIP)}};Blockly.Blocks.controls_repeat_ext={init:function(){this.setHelpUrl(Blockly.Msg.CONTROLS_REPEAT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.interpolateMsg(Blockly.Msg.CONTROLS_REPEAT_TITLE,["TIMES","Number",Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setInputsInline(!0);this.setTooltip(Blockly.Msg.CONTROLS_REPEAT_TOOLTIP)}};
+Blockly.Blocks.controls_whileUntil={init:function(){var a=[[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE,"WHILE"],[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL,"UNTIL"]];this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.appendValueInput("BOOL").setCheck("Boolean").appendField(new Blockly.FieldDropdown(a),"MODE");this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO);this.setPreviousStatement(!0);this.setNextStatement(!0);
+var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE");return{WHILE:Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE,UNTIL:Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}[a]})}};
+Blockly.Blocks.controls_for={init:function(){this.category="LOOP";this.setHelpUrl(Blockly.Msg.CONTROLS_FOR_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.jsonInit({message0:Blockly.Msg.CONTROLS_FOR_TITLE,args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],inputsInline:!0,previousStatement:null});this.appendDummyInput().appendField("("+
+Blockscad.Msg.CONVEX_HULL).appendField(new Blockly.FieldCheckbox("FALSE"),"HULL").appendField(")");this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO).setCheck(["CSG","CAG"]);this.setPreviousStatement(!0,["CSG","CAG"]);this.setInputsInline(!0);var a=this;this.setTooltip(function(){return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace("%1",a.getFieldValue("VAR"))+"\n"+Blockscad.Msg.CONTROLS_FOR_TOOLTIP_CHAINHULL})},getVars:function(){return[this.getFieldValue("VAR")]},renameVar:function(a,
+b){Blockly.Names.equals(a,this.getFieldValue("VAR"))&&this.setFieldValue(b,"VAR")},customContextMenu:function(a){if(!this.isCollapsed()){var b={enabled:!0},c=this.getFieldValue("VAR");b.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c);c=goog.dom.createDom("field",null,c);c.setAttribute("name","VAR");c=goog.dom.createDom("block",null,c);c.setAttribute("type","variables_get");b.callback=Blockly.ContextMenu.callbackFactory(this,c);a.push(b)}},setType:function(a){this.workspace&&(this.previousConnection.setCheck(a),
+this.getInput("DO").connection.setCheck(a))}};
+Blockly.Blocks.controls_for_chainhull={init:function(){this.category="LOOP";this.setHelpUrl(Blockly.Msg.CONTROLS_FOR_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.jsonInit({message0:Blockly.Msg.CONTROLS_FOR_TITLE,args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],inputsInline:!0,previousStatement:null});
+this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO).setCheck(["CSG","CAG"]);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(function(){return Blockscad.Msg.CONTROLS_FOR_TOOLTIP_CHAINHULL})},getVars:function(){return[this.getFieldValue("VAR")]},renameVar:function(a,b){Blockly.Names.equals(a,this.getFieldValue("VAR"))&&this.setFieldValue(b,"VAR")},customContextMenu:function(a){if(!this.isCollapsed()){var b={enabled:!0},c=this.getFieldValue("VAR");b.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",
+c);c=goog.dom.createDom("field",null,c);c.setAttribute("name","VAR");c=goog.dom.createDom("block",null,c);c.setAttribute("type","variables_get");b.callback=Blockly.ContextMenu.callbackFactory(this,c);a.push(b)}},setType:function(a){this.workspace&&(this.previousConnection.setCheck(a),this.getInput("DO").connection.setCheck(a))}};
+Blockly.Blocks.controls_forEach={init:function(){this.setHelpUrl(Blockly.Msg.CONTROLS_FOREACH_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.appendValueInput("LIST").setCheck("Array").appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_ITEM).appendField(new Blockly.FieldVariable(null),"VAR").appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_INLIST);Blockly.Msg.CONTROLS_FOREACH_INPUT_INLIST_TAIL&&(this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_INLIST_TAIL),this.setInputsInline(!0));
+this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);this.setPreviousStatement(!0);this.setNextStatement(!0);var a=this;this.setTooltip(function(){return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace("%1",a.getFieldValue("VAR"))})},getVars:function(){return[this.getFieldValue("VAR")]},renameVar:function(a,b){Blockly.Names.equals(a,this.getFieldValue("VAR"))&&this.setFieldValue(b,"VAR")},customContextMenu:Blockly.Blocks.controls_for.customContextMenu};
+Blockly.Blocks.controls_flow_statements={init:function(){var a=[[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK,"BREAK"],[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE,"CONTINUE"]];this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);this.setColour(Blockscad.Toolbox.HEX_LOOP);this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"FLOW");this.setPreviousStatement(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("FLOW");return{BREAK:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK,
+CONTINUE:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}[a]})},onchange:function(){var a=!1,b=this;do{if("controls_repeat"==b.type||"controls_repeat_ext"==b.type||"controls_forEach"==b.type||"controls_for"==b.type||"controls_whileUntil"==b.type){a=!0;break}b=b.getSurroundParent()}while(b);a?this.setWarningText(null):this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING)}};Blockly.Blocks.math={};Blockly.Blocks.math_number={init:function(){this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.appendDummyInput().appendField(new Blockly.FieldNumber("0"),"NUM");this.setOutput(!0,"Number");var a=this;this.setTooltip(function(){var b=a.getParent();return b&&b.getInputsInline()&&b.tooltip||Blockly.Msg.MATH_NUMBER_TOOLTIP})}};
+Blockly.Blocks.math_arithmetic={init:function(){var a=[[Blockly.Msg.MATH_ADDITION_SYMBOL,"ADD"],[Blockly.Msg.MATH_SUBTRACTION_SYMBOL,"MINUS"],[Blockly.Msg.MATH_MULTIPLICATION_SYMBOL,"MULTIPLY"],[Blockly.Msg.MATH_DIVISION_SYMBOL,"DIVIDE"],[Blockly.Msg.MATH_POWER_SYMBOL,"POWER"]];this.setHelpUrl(Blockly.Msg.MATH_ARITHMETIC_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendValueInput("A").setCheck("Number");this.appendValueInput("B").setCheck("Number").appendField(new Blockly.FieldDropdown(a),
+"OP");this.setInputsInline(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("OP");return{ADD:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD,MINUS:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS,MULTIPLY:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY,DIVIDE:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE,POWER:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER}[a]})}};
+Blockly.Blocks.math_single={init:function(){var a=[[Blockly.Msg.MATH_SINGLE_OP_ROOT,"ROOT"],[Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE,"ABS"],["-","NEG"],["ln","LN"],["log10","LOG10"],["e^","EXP"],["10^","POW10"]];this.setHelpUrl(Blockly.Msg.MATH_SINGLE_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendValueInput("NUM").setCheck("Number").appendField(new Blockly.FieldDropdown(a),"OP");var b=this;this.setTooltip(function(){var a=b.getFieldValue("OP");return{ROOT:Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT,
+ABS:Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS,NEG:Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG,LN:Blockly.Msg.MATH_SINGLE_TOOLTIP_LN,LOG10:Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10,EXP:Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP,POW10:Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10}[a]})}};
+Blockly.Blocks.math_trig={init:function(){var a=[[Blockly.Msg.MATH_TRIG_SIN,"SIN"],[Blockly.Msg.MATH_TRIG_COS,"COS"],[Blockly.Msg.MATH_TRIG_TAN,"TAN"],[Blockly.Msg.MATH_TRIG_ASIN,"ASIN"],[Blockly.Msg.MATH_TRIG_ACOS,"ACOS"],[Blockly.Msg.MATH_TRIG_ATAN,"ATAN"]];this.setHelpUrl(Blockly.Msg.MATH_TRIG_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendValueInput("NUM").setCheck("Number").appendField(new Blockly.FieldDropdown(a),"OP");var b=this;this.setTooltip(function(){var a=
+b.getFieldValue("OP");return{SIN:Blockly.Msg.MATH_TRIG_TOOLTIP_SIN,COS:Blockly.Msg.MATH_TRIG_TOOLTIP_COS,TAN:Blockly.Msg.MATH_TRIG_TOOLTIP_TAN,ASIN:Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN,ACOS:Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS,ATAN:Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN}[a]})}};
+Blockly.Blocks.math_constant={init:function(){this.setHelpUrl(Blockly.Msg.MATH_CONSTANT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendDummyInput().appendField(new Blockly.FieldDropdown([["\u03c0","PI"],["e","E"],["\u03c6","GOLDEN_RATIO"],["sqrt(2)","SQRT2"],["sqrt(\u00bd)","SQRT1_2"],["\u221e","INFINITY"]]),"CONSTANT");this.setTooltip(Blockly.Msg.MATH_CONSTANT_TOOLTIP)}};
+Blockly.Blocks.math_number_property={init:function(){var a=[[Blockly.Msg.MATH_IS_EVEN,"EVEN"],[Blockly.Msg.MATH_IS_ODD,"ODD"],[Blockly.Msg.MATH_IS_WHOLE,"WHOLE"],[Blockly.Msg.MATH_IS_POSITIVE,"POSITIVE"],[Blockly.Msg.MATH_IS_NEGATIVE,"NEGATIVE"],[Blockly.Msg.MATH_IS_DIVISIBLE_BY,"DIVISIBLE_BY"]];this.setColour(Blockscad.Toolbox.HEX_MATH);this.appendValueInput("NUMBER_TO_CHECK").setCheck("Number");a=new Blockly.FieldDropdown(a,function(a){this.sourceBlock_.updateShape_("DIVISIBLE_BY"==a)});this.appendDummyInput().appendField(a,
+"PROPERTY");this.setInputsInline(!0);this.setOutput(!0,"Boolean");this.setTooltip(Blockly.Msg.MATH_IS_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation"),b="DIVISIBLE_BY"==this.getFieldValue("PROPERTY");a.setAttribute("divisor_input",b);return a},domToMutation:function(a){a="true"==a.getAttribute("divisor_input");this.updateShape_(a)},updateShape_:function(a){var b=this.getInput("DIVISOR");a?b||this.appendValueInput("DIVISOR").setCheck("Number"):b&&this.removeInput("DIVISOR")}};
+Blockly.Blocks.math_change={init:function(){this.setHelpUrl(Blockly.Msg.MATH_CHANGE_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.interpolateMsg(Blockly.Msg.MATH_CHANGE_TITLE_CHANGE+" %1 "+Blockly.Msg.MATH_CHANGE_INPUT_BY+" %2",["VAR",new Blockly.FieldVariable(Blockly.Msg.MATH_CHANGE_TITLE_ITEM)],["DELTA","Number",Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setPreviousStatement(!0);this.setNextStatement(!0);var a=this;this.setTooltip(function(){return Blockly.Msg.MATH_CHANGE_TOOLTIP.replace("%1",
+a.getFieldValue("VAR"))})},getVars:function(){return[this.getFieldValue("VAR")]},renameVar:function(a,b){Blockly.Names.equals(a,this.getFieldValue("VAR"))&&this.setFieldValue(b,"VAR")}};
+Blockly.Blocks.math_round={init:function(){var a=[[Blockly.Msg.MATH_ROUND_OPERATOR_ROUND,"ROUND"],[Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP,"ROUNDUP"],[Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN,"ROUNDDOWN"]];this.setHelpUrl(Blockly.Msg.MATH_ROUND_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendValueInput("NUM").setCheck("Number").appendField(new Blockly.FieldDropdown(a),"OP");this.setTooltip(Blockly.Msg.MATH_ROUND_TOOLTIP)}};
+Blockly.Blocks.math_on_list={init:function(){var a=[[Blockly.Msg.MATH_ONLIST_OPERATOR_SUM,"SUM"],[Blockly.Msg.MATH_ONLIST_OPERATOR_MIN,"MIN"],[Blockly.Msg.MATH_ONLIST_OPERATOR_MAX,"MAX"],[Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE,"AVERAGE"],[Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN,"MEDIAN"],[Blockly.Msg.MATH_ONLIST_OPERATOR_MODE,"MODE"],[Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV,"STD_DEV"],[Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM,"RANDOM"]],b=this;this.setHelpUrl(Blockly.Msg.MATH_ONLIST_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);
+this.setOutput(!0,"Number");a=new Blockly.FieldDropdown(a,function(a){b.updateType_(a)});this.appendValueInput("LIST").setCheck("Array").appendField(a,"OP");this.setTooltip(function(){var a=b.getFieldValue("OP");return{SUM:Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM,MIN:Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN,MAX:Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX,AVERAGE:Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE,MEDIAN:Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN,MODE:Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE,STD_DEV:Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV,
+RANDOM:Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM}[a]})},updateType_:function(a){"MODE"==a?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("op"))}};
+Blockly.Blocks.math_modulo={init:function(){this.setHelpUrl(Blockly.Msg.MATH_MODULO_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.interpolateMsg(Blockly.Msg.MATH_MODULO_TITLE,["DIVIDEND","Number",Blockly.ALIGN_RIGHT],["DIVISOR","Number",Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setTooltip(Blockly.Msg.MATH_MODULO_TOOLTIP)}};
+Blockly.Blocks.math_constrain={init:function(){this.setHelpUrl(Blockly.Msg.MATH_CONSTRAIN_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.interpolateMsg(Blockly.Msg.MATH_CONSTRAIN_TITLE,["VALUE","Number",Blockly.ALIGN_RIGHT],["LOW","Number",Blockly.ALIGN_RIGHT],["HIGH","Number",Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setTooltip(Blockly.Msg.MATH_CONSTRAIN_TOOLTIP)}};
+Blockly.Blocks.math_random_int={init:function(){this.setHelpUrl(Blockly.Msg.MATH_RANDOM_INT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.interpolateMsg(Blockly.Msg.MATH_RANDOM_INT_TITLE,["FROM","Number",Blockly.ALIGN_RIGHT],["TO","Number",Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setTooltip(Blockly.Msg.MATH_RANDOM_INT_TOOLTIP)}};
+Blockly.Blocks.math_random_float={init:function(){this.setHelpUrl(Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendDummyInput().appendField(Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM);this.setTooltip(Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP)}};Blockly.Blocks.sphere={init:function(){this.category="PRIMITIVE_CSG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.SPHERE+"  ");this.appendValueInput("RAD").setCheck("Number").appendField(Blockscad.Msg.RADIUS).setAlign(Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.SPHERE_TOOLTIP)}};
+Blockly.Blocks.cylinder={init:function(){this.category="PRIMITIVE_CSG";this.pR2id=this.pR1id=this.prevR2=this.prevR1=null;this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.CYLINDER+"  ");this.appendValueInput("RAD1").setCheck("Number").appendField(Blockscad.Msg.RADIUS+"1").setAlign(Blockly.ALIGN_RIGHT);null==Blockscad.inputVersion||"1.0.0"==Blockscad.inputVersion||"1.0.1"==Blockscad.inputVersion||"1.1.0"==
+Blockscad.inputVersion?this.appendDummyInput().setAlign(Blockly.ALIGN_RIGHT).appendField(new Blockly.FieldCheckbox("FALSE",null,"imgs/lock_icon.png","imgs/unlock_icon.png"),"LOCKED"):this.appendDummyInput().setAlign(Blockly.ALIGN_RIGHT).appendField(new Blockly.FieldCheckbox("TRUE",null,"imgs/lock_icon.png","imgs/unlock_icon.png"),"LOCKED");this.appendValueInput("RAD2").setCheck("Number").appendField(Blockscad.Msg.RADIUS+"2").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("HEIGHT").setCheck("Number").appendField(Blockscad.Msg.HEIGHT).setAlign(Blockly.ALIGN_RIGHT);
+this.appendDummyInput().appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED,"false"],[Blockscad.Msg.CENTERED,"true"]]),"CENTERDROPDOWN");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.CYLINDER_TOOLTIP)},updateRadii:function(){if(this.workspace){var a=this.getField("LOCKED").getValue();if("FALSE"!=a){var b=null,c=null;this.getInput("RAD1").connection.targetConnection&&"math_number"==this.getInput("RAD1").connection.targetConnection.sourceBlock_.type&&
+(b=this.getInput("RAD1").connection.targetConnection.sourceBlock_.getField("NUM").getValue());this.getInput("RAD2").connection.targetConnection&&"math_number"==this.getInput("RAD2").connection.targetConnection.sourceBlock_.type&&(c=this.getInput("RAD2").connection.targetConnection.sourceBlock_.getField("NUM").getValue());"TRUE"==a&&b&&c&&b!=c&&(b!=this.prevR1?this.getInput("RAD2").connection.targetConnection.sourceBlock_.getField("NUM").setValue(b,!0):c!=this.prevR2&&this.getInput("RAD1").connection.targetConnection.sourceBlock_.getField("NUM").setValue(c,
+!0));this.prevR1=b;this.prevR2=c}}}};
+Blockly.Blocks.simple_cylinder={init:function(){this.category="PRIMITIVE_CSG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.CYLINDER+"  ");this.appendValueInput("RAD1").setCheck("Number").appendField(Blockscad.Msg.RADIUS).setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("HEIGHT").setCheck("Number").appendField(Blockscad.Msg.HEIGHT).setAlign(Blockly.ALIGN_RIGHT);this.appendDummyInput().appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED,
+"false"],[Blockscad.Msg.CENTERED,"true"]]),"CENTERDROPDOWN");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip("Creates a cylinder with a specified radius and height.  It may optionally be centered at the origin.")}};
+Blockly.Blocks.cube={init:function(){this.category="PRIMITIVE_CSG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.CUBE+"   ");this.appendValueInput("XVAL").setCheck("Number").appendField("X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("ZVAL").setCheck("Number").appendField("Z").setAlign(Blockly.ALIGN_RIGHT);
+this.appendDummyInput().appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED,"false"],[Blockscad.Msg.CENTERED,"true"]]),"CENTERDROPDOWN");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.CUBE_TOOLTIP)}};
+Blockly.Blocks.torus={init:function(){this.category="PRIMITIVE_CSG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.TORUS+"  ");this.appendValueInput("RAD1").setCheck("Number").appendField(Blockscad.Msg.RADIUS+"1").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("RAD2").setCheck("Number").appendField(Blockscad.Msg.RADIUS+"2").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("SIDES").setCheck("Number").appendField(Blockscad.Msg.SIDES).setAlign(Blockly.ALIGN_RIGHT);
+this.appendValueInput("FACES").setCheck("Number").appendField(Blockscad.Msg.FACES).setAlign(Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.TORUS_TOOLTIP)}};
+Blockly.Blocks.twistytorus={init:function(){this.category="PRIMITIVE_CSG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.appendDummyInput().appendField("Twisty Torus  ");this.appendValueInput("RAD1").setCheck("Number").appendField("ring radius").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("RAD2").setCheck("Number").appendField("cross-section radius").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("SIDES").setCheck("Number").appendField("ring sides").setAlign(Blockly.ALIGN_RIGHT);
+this.appendValueInput("FACES").setCheck("Number").appendField("cross section faces").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("TWIST").setCheck("Number").appendField("twist (degrees)").setAlign(Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip('Creates a torus with a ring of specified distance on-center from the origin (radius1), with a specified radius (radius2), a specified number of sides and faces. The "twist" is in degrees, and should be used with caution')}};
+Blockly.Blocks.circle={init:function(){this.category="PRIMITIVE_CAG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_2D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.CIRCLE+"   ");this.appendValueInput("RAD").setCheck("Number").appendField(Blockscad.Msg.RADIUS).setAlign(Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setPreviousStatement(!0,"CAG");this.setTooltip(Blockscad.Msg.CIRCLE_TOOLTIP)}};
+Blockly.Blocks.square={init:function(){this.category="PRIMITIVE_CAG";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_2D_PRIMITIVE);this.appendDummyInput().appendField(Blockscad.Msg.SQUARE+"   ");this.appendValueInput("XVAL").setCheck("Number").appendField("X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendDummyInput().appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED,
+"false"],[Blockscad.Msg.CENTERED,"true"]]),"CENTERDROPDOWN");this.setInputsInline(!0);this.setPreviousStatement(!0,"CAG");this.setTooltip(Blockscad.Msg.SQUARE_TOOLTIP)}};
+Blockly.Blocks.translate={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.TRANSLATE);this.appendValueInput("XVAL").setCheck("Number").appendField("X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("ZVAL").setCheck("Number").appendField("Z").setAlign(Blockly.ALIGN_RIGHT);
+this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.TRANSLATE_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;
+for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),
+this.mutatorMinus=null,this.render())},setType:function(a,b){if(this.workspace){goog.isArray(a)||(a=[a]);var c=this.getInput("ZVAL"),d=this.getInput("A"),e=this.previousConnection.check_;this.previousConnection.setCheck(a);d.connection.setCheck(a);for(d=1;d<=this.plusCount_;d++)this.getInput("PLUS"+d).connection.setCheck(a);"CAG"==a[0]&&"CSG"==e[0]?hideMyInput(c,b):"CSG"==a[0]&&"CAG"==e[0]&&showMyInput(c,b)}}};
+Blockly.Blocks.scale={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.SCALE);this.appendValueInput("XVAL").setCheck("Number").appendField("X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("ZVAL").setCheck("Number").appendField("Z").setAlign(Blockly.ALIGN_RIGHT);this.appendStatementInput("A").setCheck(["CSG",
+"CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.SCALE_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+
+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a,
+b){if(this.workspace){goog.isArray(a)||(a=[a]);var c=this.getInput("ZVAL"),d=this.getInput("A"),e=this.previousConnection.check_;this.previousConnection.setCheck(a);d.connection.setCheck(a);for(d=1;d<=this.plusCount_;d++)this.getInput("PLUS"+d).connection.setCheck(a);"CAG"==a[0]&&"CSG"==e[0]?hideMyInput(c,b):"CSG"==a[0]&&"CAG"==e[0]&&showMyInput(c,b)}}};
+Blockly.Blocks.fancymirror={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.MIRROR_ADVANCED);this.appendValueInput("XVAL").setCheck("Number").appendField("X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("ZVAL").setCheck("Number").appendField("Z").setAlign(Blockly.ALIGN_RIGHT);
+this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.FANCYMIRROR_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;
+for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),
+this.mutatorMinus=null,this.render())},setType:function(a,b){if(this.workspace){this.getInput("ZVAL");this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);for(var c=1;c<=this.plusCount_;c++)this.getInput("PLUS"+c).connection.setCheck(a)}}};
+Blockly.Blocks.simplemirror={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField("Simple Mirror");this.appendDummyInput().appendField("across").appendField(new Blockly.FieldDropdown([["+","pos"],["-","neg"]]),"sign").appendField(new Blockly.FieldDropdown([["XY","XY"],["YZ","YZ"],["XZ","XZ"]]),"mirrorplane");this.appendStatementInput("A").setCheck("CSG");this.setInputsInline(!0);this.setPreviousStatement(!0,
+"CSG");this.setTooltip(Blockscad.Msg.SIMPLEMIRROR_TOOLTIP)},setType:function(a,b){if(this.workspace){var c=this.getInput("A");this.previousConnection.setCheck(a);c.connection.setCheck(a)}}};
+Blockly.Blocks.simplemirror_new={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.MIRROR);this.appendDummyInput("3D").appendField(Blockscad.Msg.ACROSS).appendField(new Blockly.FieldDropdown([["XY","XY"],["YZ","YZ"],["XZ","XZ"]]),"mirrorplane");this.appendDummyInput("2D").appendField(Blockscad.Msg.ACROSS).appendField(new Blockly.FieldDropdown([["YZ","YZ"],["XZ","XZ"]]),
+"mirrorplane_cag").setVisible(!1);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.SIMPLEMIRROR_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),
+10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),
+this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a,b){if(this.workspace){goog.isArray(a)||(a=[a]);var c=this.getInput("3D"),d=this.getInput("2D"),e=this.getInput("A"),f=this.previousConnection.check_;this.previousConnection.setCheck(a);e.connection.setCheck(a);for(e=1;e<=this.plusCount_;e++)this.getInput("PLUS"+e).connection.setCheck(a);"CAG"==a[0]&&"CSG"==f[0]?(hideMyInput(c,b),showMyInput(d,b)):"CSG"==a[0]&&"CAG"==f[0]&&(hideMyInput(d,b),showMyInput(c,
+b))}}};
+Blockly.Blocks.taper={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.TAPER);this.appendDummyInput("3D").appendField(Blockscad.Msg.ALONG+" ").appendField(new Blockly.FieldDropdown([["X","X"],["Y","Y"],["Z","Z"]]),"taperaxis").appendField(Blockscad.Msg.AXIS);this.appendDummyInput("2D").appendField(Blockscad.Msg.ALONG+" ").appendField(new Blockly.FieldDropdown([["X","X"],
+["Y","Y"]]),"taperaxis_cag").appendField(Blockscad.Msg.AXIS).setVisible(!1);this.appendValueInput("FACTOR").setCheck("Number").appendField(Blockscad.Msg.SCALE).setAlign(Blockly.ALIGN_RIGHT);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.TAPER_TOOLTIP);this.setWarningText(Blockscad.Msg.NOT_COMPATIBLE_WITH_OPENSCAD);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;
+var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+
+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a,b){if(this.workspace){goog.isArray(a)||(a=[a]);var c=this.getInput("3D"),d=this.getInput("2D"),e=this.getInput("A"),f=this.previousConnection.check_;this.previousConnection.setCheck(a);e.connection.setCheck(a);
+for(e=1;e<=this.plusCount_;e++)this.getInput("PLUS"+e).connection.setCheck(a);"CAG"==a[0]&&"CSG"==f[0]?(hideMyInput(c,b),showMyInput(d,b)):"CSG"==a[0]&&"CAG"==f[0]&&(hideMyInput(d,b),showMyInput(c,b),b&&this.render())}}};
+Blockly.Blocks.simplerotate={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.ROTATE);this.appendValueInput("XVAL").setCheck("Number").appendField("X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("ZVAL").setCheck("Number").appendField("Z").setAlign(Blockly.ALIGN_RIGHT);
+this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.SIMPLEROTATE_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;
+for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),
+this.mutatorMinus=null,this.render())},setType:function(a,b){if(this.workspace){var c=this.getInput("A");this.previousConnection.setCheck(a);c.connection.setCheck(a);for(c=1;c<=this.plusCount_;c++)this.getInput("PLUS"+c).connection.setCheck(a)}}};
+Blockly.Blocks.fancyrotate={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.ROTATE_ADVANCED);this.appendValueInput("AVAL").setCheck("Number");this.appendValueInput("XVAL").setCheck("Number").appendField(Blockscad.Msg.AROUND+" X").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("YVAL").setCheck("Number").appendField("Y").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("ZVAL").setCheck("Number").appendField("Z").setAlign(Blockly.ALIGN_RIGHT);
+this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.FANCYROTATE_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;
+for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),
+this.mutatorMinus=null,this.render())},setType:function(a,b){if(this.workspace){goog.isArray(a)||(a=[a]);var c=this.getInput("ZVAL"),d=this.getInput("XVAL"),e=this.getInput("YVAL"),f=this.getInput("A"),g=this.previousConnection.check_;this.previousConnection.setCheck(a);f.connection.setCheck(a);for(f=1;f<=this.plusCount_;f++)this.getInput("PLUS"+f).connection.setCheck(a);"CAG"==a[0]&&"CSG"==g[0]?(hideMyInput(d,b),hideMyInput(e,b),hideMyInput(c,b)):"CSG"==a[0]&&"CAG"==g[0]&&(showMyInput(d,b),showMyInput(e,
+b),showMyInput(c,b))}}};
+Blockly.Blocks.color={init:function(){this.category="COLOR";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.COLOR);this.appendValueInput("COLOR").setCheck("Colour");this.appendStatementInput("A").setCheck("CSG");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.COLOR_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;
+var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+
+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())}};
+Blockly.Blocks.color_rgb={init:function(){this.category="COLOR";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.COLOR+"  ");var a=new Blockly.FieldDropdown([[Blockscad.Msg.HSV_COLOR_MODEL,"HSV"],[Blockscad.Msg.RGB_COLOR_MODEL,"RGB"]],function(a){this.sourceBlock_.optUpdateShape_("RGB"==a)});this.appendDummyInput().appendField(a,"SCHEME").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("RED").setCheck("Number").appendField(Blockscad.Msg.COLOR_HUE,
+"1").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("GREEN").setCheck("Number").appendField(Blockscad.Msg.COLOR_SATURATION,"2").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("BLUE").setCheck("Number").appendField(Blockscad.Msg.COLOR_VALUE,"3").setAlign(Blockly.ALIGN_RIGHT);this.appendStatementInput("A").setCheck("CSG");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");var b=this;this.setTooltip(function(){var a=b.getFieldValue("SCHEME");return{RGB:Blockscad.Msg.COLOR_RGB_TOOLTIP,
+HSV:Blockscad.Msg.COLOR_HSV_TOOLTIP}[a]});this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){var a=document.createElement("mutation");this.plusCount_?a.setAttribute("plus",this.plusCount_):a.setAttribute("plus",0);var b="RGB"==this.getFieldValue("SCHEME");a.setAttribute("isrgb",b);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);for(var b=this.getInput("A").connection.check_,c=1;c<=this.plusCount_;c++)this.appendStatementInput("PLUS"+
+c).setCheck(b);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this));a="true"==a.getAttribute("isrgb");this.optUpdateShape_(a)},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus&&this.mutatorMinus.dispose(),
+this.mutatorMinus=null,this.render())},optUpdateShape_:function(a){var b=this.getField("1"),c=this.getField("2"),d=this.getField("3");a?(b.setText(Blockly.Msg.COLOUR_RGB_RED),c.setText(Blockly.Msg.COLOUR_RGB_GREEN),d.setText(Blockly.Msg.COLOUR_RGB_BLUE)):(b.setText(Blockscad.Msg.COLOR_HUE),c.setText(Blockscad.Msg.COLOR_SATURATION),d.setText(Blockscad.Msg.COLOR_VALUE))}};
+Blockly.Blocks.$fn={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendValueInput("SIDES").setCheck("Number").appendField(Blockscad.Msg.SIDES);this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.FN_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;
+var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+
+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a){if(this.workspace){this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);for(var b=1;b<=this.plusCount_;b++)this.getInput("PLUS"+b).connection.setCheck(a)}}};
+Blockly.Blocks.assign={init:function(){this.category="TRANSFORM";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendValueInput("NAME").appendField("set ").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("VALUE").setCheck("Number").appendField(" = ");this.appendStatementInput("A").setCheck(["CSG","CAG"]);this.setInputsInline(!0);this.setPreviousStatement(!0,["CSG","CAG"]);this.setTooltip(Blockscad.Msg.FN_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));
+this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,
+a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())},setType:function(a){if(this.workspace){this.previousConnection.setCheck(a);this.getInput("A").connection.setCheck(a);for(var b=1;b<=this.plusCount_;b++)this.getInput("PLUS"+
+b).connection.setCheck(a)}}};
+Blockly.Blocks.linearextrude={init:function(){this.category="EXTRUDE";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.LINEAR_EXTRUDE+"  ");this.appendValueInput("HEIGHT").setCheck("Number").appendField(Blockscad.Msg.HEIGHT).setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("TWIST").setCheck("Number").appendField(Blockscad.Msg.TWIST).setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("XSCALE").setCheck("Number").appendField(Blockscad.Msg.SCALE+": x").setAlign(Blockly.ALIGN_RIGHT);
+this.appendValueInput("YSCALE").setCheck("Number").appendField("y").setAlign(Blockly.ALIGN_RIGHT);this.appendDummyInput().appendField(new Blockly.FieldDropdown([[Blockscad.Msg.NOT_CENTERED,"false"],[Blockscad.Msg.CENTERED,"true"]]),"CENTERDROPDOWN").setAlign(Blockly.ALIGN_RIGHT);this.appendStatementInput("A").setCheck("CAG");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.LINEAREXTRUDE_TOOLTIP);this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=
+0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,
+this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())}};
+Blockly.Blocks.rotateextrude={init:function(){this.category="EXTRUDE";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField(Blockscad.Msg.ROTATE_EXTRUDE+"  ");this.appendValueInput("FACES").setCheck("Number").appendField(Blockscad.Msg.SIDES).setAlign(Blockly.ALIGN_RIGHT);this.appendStatementInput("A").setCheck("CAG");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip(Blockscad.Msg.ROTATEEXTRUDE_TOOLTIP);
+this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},
+updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())}};
+Blockly.Blocks.rotateextrudetwist={init:function(){this.category="EXTRUDE";this.setHelpUrl("http://www.example.com/");this.setColour(Blockscad.Toolbox.HEX_TRANSFORM);this.appendDummyInput().appendField("Rotate Extrude Twist ");this.appendValueInput("RAD").setCheck("Number").appendField("R").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("FACES").setCheck("Number").appendField("Sides").setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("TWIST").setCheck("Number").appendField("Twist").setAlign(Blockly.ALIGN_RIGHT);
+this.appendValueInput("TSTEPS").setCheck("Number").appendField("Twist-steps").setAlign(Blockly.ALIGN_RIGHT);this.appendStatementInput("A").setCheck("CAG");this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setTooltip("Rotate extrudes shape translated by radius around the Z axis with a specified number of sides. ");this.setMutatorPlus(new Blockly.MutatorPlus(this));this.plusCount_=0},mutationToDom:function(){if(!this.plusCount_)return null;var a=document.createElement("mutation");this.plusCount_&&
+a.setAttribute("plus",this.plusCount_);return a},domToMutation:function(a){this.plusCount_=parseInt(a.getAttribute("plus"),10);a=this.getInput("A").connection.check_;for(var b=1;b<=this.plusCount_;b++)this.appendStatementInput("PLUS"+b).setCheck(a);1<=this.plusCount_&&this.setMutatorMinus(new Blockly.MutatorMinus(this))},updateShape_:function(a){1==a?(this.plusCount_++,a=this.getInput("A").connection.check_,this.appendStatementInput("PLUS"+this.plusCount_).setCheck(a)):-1==a&&(this.removeInput("PLUS"+
+this.plusCount_),this.plusCount_--);1<=this.plusCount_?1==this.plusCount_&&(this.setMutatorMinus(new Blockly.MutatorMinus(this)),this.render()):(this.mutatorMinus.dispose(),this.mutatorMinus=null,this.render())}};Blockly.Blocks.math_angle={init:function(){this.setColour(Blockscad.Toolbox.HEX_MATH);this.appendDummyInput().appendField(new Blockly.FieldAngle("0"),"NUM");this.setOutput(!0,"Number")}};
+Blockly.Blocks.math_constant_bs={init:function(){this.setHelpUrl(Blockly.Msg.MATH_CONSTANT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_MATH);this.setOutput(!0,"Number");this.appendDummyInput().appendField(new Blockly.FieldDropdown([["\u03c0","PI"],["e","E"],["\u03c6","GOLDEN_RATIO"],["sqrt(2)","SQRT2"],["sqrt(\u00bd)","SQRT1_2"]]),"CONSTANT");this.setTooltip(Blockly.Msg.MATH_CONSTANT_TOOLTIP)}};
+Blockly.Blocks.stl_import={init:function(){this.category="PRIMITIVE_CSG";this.appendDummyInput().appendField(Blockscad.Msg.IMPORT_STL);this.appendDummyInput("").setAlign(Blockly.ALIGN_RIGHT).appendField(new Blockly.FieldLabel(""),"STL_FILENAME");this.appendDummyInput("").setAlign(Blockly.ALIGN_RIGHT).appendField(new Blockly.FieldButton(Blockscad.Msg.BROWSE),"STL_BUTTON");this.appendDummyInput("C").appendField(new Blockly.FieldLabel(""),"STL_CONTENTS").setVisible(!1);this.setInputsInline(!0);this.setPreviousStatement(!0);
+this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.setTooltip("");this.setWarningText(Blockscad.Msg.STL_IMPORT_WARNING);this.setHelpUrl("http://www.example.com/")},onchange:function(){if(this.workspace){var a=this.getField("STL_FILENAME").getText(),b=this.getField("STL_CONTENTS").getText();0<a.length&&(this.getField("STL_BUTTON").setVisible(!1),this.setCommentText(a+"\ncenter: ("+Blockscad.csg_center[b]+")"));this.getField("STL_CONTENTS").setVisible(!1)}}};
+Blockly.Blocks.bs_text={init:function(){for(var a=[],b=0;b<Blockscad.fontName.length;b++)a.push([Blockscad.fontName[b],b.toString()]);this.category="PRIMITIVE_CAG";this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);this.appendValueInput("TEXT").appendField(Blockscad.Msg.BLOCK_TEXT_2D).setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("SIZE").setCheck("Number").appendField(" "+Blockscad.Msg.FONT_SIZE).setAlign(Blockly.ALIGN_RIGHT);this.appendDummyInput().appendField(" "+Blockscad.Msg.FONT_NAME).appendField(new Blockly.FieldDropdown(a),
+"FONT");this.setInputsInline(!0);this.setPreviousStatement(!0,"CAG");this.setColour(Blockscad.Toolbox.HEX_2D_PRIMITIVE);this.setTooltip(Blockscad.Msg.BS_TEXT_TOOLTIP)},newQuote_:function(a){return new Blockly.FieldImage(a==this.RTL?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==":
+"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",12,12,'"')}};
+Blockly.Blocks.bs_3dtext={init:function(){for(var a=[],b=0;b<Blockscad.fontName.length;b++)a.push([Blockscad.fontName[b],b.toString()]);this.category="PRIMITIVE_CSG";this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);this.appendValueInput("TEXT").appendField(Blockscad.Msg.BLOCK_TEXT_3D).setAlign(Blockly.ALIGN_RIGHT);this.appendValueInput("SIZE").setCheck("Number").appendField(" "+Blockscad.Msg.FONT_SIZE).setAlign(Blockly.ALIGN_RIGHT);this.appendDummyInput().appendField(" "+Blockscad.Msg.FONT_NAME).appendField(new Blockly.FieldDropdown(a),
+"FONT");this.appendValueInput("THICKNESS").appendField(" "+Blockscad.Msg.TEXT_THICKNESS).setCheck("Number").setAlign(Blockly.ALIGN_RIGHT);this.setInputsInline(!0);this.setPreviousStatement(!0,"CSG");this.setColour(Blockscad.Toolbox.HEX_3D_PRIMITIVE);this.setTooltip(Blockscad.Msg.BS_3DTEXT_TOOLTIP)},newQuote_:function(a){return new Blockly.FieldImage(a==this.RTL?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==":
+"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",12,12,'"')}};function hideMyInput(a,b){b&&Blockscad.executeAfterDrag_(function(){a.isVisible()&&a.setVisible(!1);a.sourceBlock_.render()},a)}
+function showMyInput(a,b){!a.isVisible()&&b&&Blockscad.executeAfterDrag_(function(){var b=a.setVisible(!0);0<b.length&&b[0].render()},a)}Blockly.Blocks.bs_text_length={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_LENGTH_HELPURL);this.setColour(Blockscad.Toolbox.HEX_TEXT);this.interpolateMsg(Blockly.Msg.TEXT_LENGTH_TITLE,["VALUE",["String","Array"],Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setOutput(!0,"Number");this.setTooltip(Blockly.Msg.TEXT_LENGTH_TOOLTIP)}};Blockly.Blocks.procedures={};
+Blockly.Blocks.procedures_defnoreturn={init:function(){this.category="PROCEDURE";this.myType_=["CSG","CAG"];this.backlightBlocks=[];var a=new Blockly.FieldTextInput(Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE,Blockly.Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,"NAME").appendField("","PARAMS");this.setMutator(new Blockly.Mutator(["procedures_mutatorarg"]));this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
+this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);this.arguments_=[];this.setStatements_(!0,"VariableSet");this.statementConnection_=null},setType:function(a,b){if(this.workspace&&(goog.isArray(a)||(a=[a]),!Blockscad.arraysEqual(a,this.myType_))){var c=this.myType_,d=Blockly.Procedures.getCallers(this.getFieldValue("NAME"),this.workspace),e=[];this.myType_=a;var f=!0;Blockscad.workspace.undoStack_.length&&(f=Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length-1].group);
+Blockly.Events.setGroup(f);if(d.length)for(f=0;f<d.length;f++){var g=Blockscad.findBlockType(d[f],d);if("EITHER"!=g&&g!=a[0]&&(e.push(d[f]),g=d[f].collapsedParents()))for(var h=0;h<g.length;h++)g[h].setCollapsed(!1);d[f].previousConnection.setCheck(a);Blockly.Events.isEnabled()&&e.length&&Blockly.Events.fire(new Blockly.Events.Typing(d[f],c,a));d[f].category="CSG"==a[0]&&1==a.length?"PRIMITIVE_CSG":"CAG"==a[0]?"PRIMITIVE_CAG":"UNKNOWN";var k=Blockscad.hasParentOfType(d[f],"procedures_defnoreturn");
+k&&setTimeout(function(){k&&k.setType(a)},0);(g=d[f].getParent())&&Blockscad.assignBlockTypes(g)}Blockly.Events.setGroup(!1);for(c=0;c<e.length;c++)e[c].backlight(),this.backlightBlocks.push(e[c].id),d="",d+=Blockscad.Msg.BLOCKS_BUMPED_OUT_DIMENSIONS.replace("%1",e.length),this.setWarningText(d)}},setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK","RETURN")):
+this.removeInput("STACK",!0),this.hasStatements_=a)},updateParams_:function(){for(var a=!1,b={},c=0;c<this.arguments_.length;c++){if(b["arg_"+this.arguments_[c].toLowerCase()]){a=!0;break}b["arg_"+this.arguments_[c].toLowerCase()]=!0}a?this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING):this.setWarningText(null);a="";this.arguments_.length&&(a=Blockly.Msg.PROCEDURES_BEFORE_PARAMS+" "+this.arguments_.join(", "));Blockly.Events.disable();try{this.setFieldValue(a,"PARAMS")}finally{Blockly.Events.enable()}},
+mutationToDom:function(a){var b=document.createElement("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(var c=0;c<this.arguments_.length;c++){var d=document.createElement("arg");d.setAttribute("name",this.arguments_[c]);a&&this.paramIds_&&d.setAttribute("paramId",this.paramIds_[c]);b.appendChild(d)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];for(var b=0,c;c=a.childNodes[b];b++)"arg"==c.nodeName.toLowerCase()&&
+this.arguments_.push(c.getAttribute("name"));this.updateParams_();Blockly.Procedures.mutateCallers(this);this.setStatements_("false"!==a.getAttribute("statements"))},decompose:function(a){var b=a.newBlock("procedures_mutatorcontainer");b.initSvg();b.setFieldValue(this.hasStatements_?"TRUE":"FALSE","STATEMENTS");b.getInput("STATEMENT_INPUT").setVisible(!1);for(var c=b.getInput("STACK").connection,d=0;d<this.arguments_.length;d++){var e=a.newBlock("procedures_mutatorarg");e.initSvg();e.setFieldValue(this.arguments_[d],
+"NAME");e.oldLocation=d;c.connect(e.previousConnection);c=e.nextConnection}Blockly.Procedures.mutateCallers(this);return b},compose:function(a){this.arguments_=[];this.paramIds_=[];for(var b=a.getInputTargetBlock("STACK");b;)this.arguments_.push(b.getFieldValue("NAME")),this.paramIds_.push(b.id),b=b.nextConnection&&b.nextConnection.targetBlock();this.updateParams_();Blockly.Procedures.mutateCallers(this);a=a.getFieldValue("STATEMENTS");if(null!==a&&(a="TRUE"==a,this.hasStatements_!=a))if(a)this.setStatements_(!0),
+Blockly.Mutator.reconnect(this.statementConnection_,this,"STACK"),this.statementConnection_=null;else{a=this.getInput("STACK").connection;if(this.statementConnection_=a.targetConnection)a=a.targetBlock(),a.unplug(),a.bumpNeighbours_();this.setStatements_(!1)}},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},getVars:function(){return this.arguments_},renameVar:function(a,b){for(var c=!1,d=0;d<this.arguments_.length;d++)Blockly.Names.equals(a,this.arguments_[d])&&(this.arguments_[d]=
+b,c=!0);if(c&&(this.updateParams_(),this.mutator.isVisible()))for(var c=this.mutator.workspace_.getAllBlocks(),d=0,e;e=c[d];d++)"procedures_mutatorarg"==e.type&&Blockly.Names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")},customContextMenu:function(a){var b={enabled:!0},c=this.getFieldValue("NAME");b.text=Blockly.Msg.PROCEDURES_CREATE_DO.replace("%1",c);var d=goog.dom.createDom("mutation");d.setAttribute("name",c);for(var e=0;e<this.arguments_.length;e++){var f=goog.dom.createDom("arg");
+f.setAttribute("name",this.arguments_[e]);d.appendChild(f)}d=goog.dom.createDom("block",null,d);d.setAttribute("type",this.callType_);b.callback=Blockly.ContextMenu.callbackFactory(this,d);a.push(b);if(!this.isCollapsed())for(e=0;e<this.arguments_.length;e++)b={enabled:!0},c=this.arguments_[e],b.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c),d=goog.dom.createDom("field",null,c),d.setAttribute("name","VAR"),d=goog.dom.createDom("block",null,d),d.setAttribute("type","variables_get"),b.callback=
+Blockly.ContextMenu.callbackFactory(this,d),a.push(b);b={enabled:!0};c=this.getFieldValue("NAME");b.text=Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",c);var g=this.workspace;b.callback=function(){if(Blockly.Procedures.getDefinition(c,g)){var a=Blockly.Procedures.getCallers(c,g);g.clearBacklight();Blockly.selected.unselect();for(var b=0;a&&b<a.length;b++){a[b]&&a[b].backlight();var d=a[b].collapsedParents();if(d)for(var e=0;e<d.length;e++)d[e].backlight()}}};a.push(b)},callType_:"procedures_callnoreturn"};
+Blockly.Blocks.procedures_defreturn={init:function(){this.category="PROCEDURE";this.myType_=null;this.backlightBlocks=[];var a=new Blockly.FieldTextInput(Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE,Blockly.Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,"NAME").appendField("","PARAMS");this.appendValueInput("RETURN").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new Blockly.Mutator(["procedures_mutatorarg"]));
+this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);this.arguments_=[];this.setStatements_(!1);this.statementConnection_=null},setType:function(a,b){if(this.workspace){var c=this.getInput("RETURN");a=c.connection.targetConnection?"Number"==c.connection.targetConnection.check_?"Number":"Boolean"==c.connection.targetConnection.check_?"Boolean":"String"==c.connection.targetConnection.check_?
+"String":null:null;if(this.myType_!=a){this.myType_=a;var d=Blockly.Procedures.getCallers(this.getFieldValue("NAME"),this.workspace),c=[],e,f=!0;Blockscad.workspace.undoStack_.length&&(f=Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length-1].group);Blockly.Events.setGroup(f);if(d.length)for(f=0;f<d.length;f++)if(d[f]){var g=d[f].getParent();if(g&&((e=d[f].outputConnection.targetConnection.check_)&&!goog.isArray(e)&&(e=[e]),e)){for(var h=0,g=0;g<e.length;g++)e[g]==this.myType_&&(h=
+1);if(!h&&(c.push(d[f]),h=d[f].collapsedParents()))for(g=0;g<h.length;g++)h[g].setCollapsed(!1)}d[f]&&(d[f].outputConnection.setCheck(this.myType_),d[f].category="Number"==this.myType_?"NUMBER":"Boolean"==this.myType_?"BOOLEAN":"String"==this.myType_?"STRING":"UNKNOWN");var k=Blockscad.hasParentOfType(d[f],"procedures_defreturn");k||(k=Blockscad.hasParentOfType(d[f],"variables_set"));k?setTimeout(function(){k.setType(a)},0):(g=!1,d[f]&&(g=d[f].getParent()),g&&Blockscad.assignBlockTypes(g))}Blockly.Events.setGroup(!1);
+for(d=0;d<c.length;d++)c[d].backlight(),this.backlightBlocks.push(c[d].id),f="",f+=Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1",c.length).replace("%2",e).replace("%3",a),this.setWarningText(f)}}},setStatements_:Blockly.Blocks.procedures_defnoreturn.setStatements_,updateParams_:Blockly.Blocks.procedures_defnoreturn.updateParams_,mutationToDom:Blockly.Blocks.procedures_defnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_defnoreturn.domToMutation,decompose:Blockly.Blocks.procedures_defnoreturn.decompose,
+compose:Blockly.Blocks.procedures_defnoreturn.compose,getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!0]},getVars:Blockly.Blocks.procedures_defnoreturn.getVars,renameVar:Blockly.Blocks.procedures_defnoreturn.renameVar,customContextMenu:Blockly.Blocks.procedures_defnoreturn.customContextMenu,callType_:"procedures_callreturn"};
+Blockly.Blocks.procedures_mutatorcontainer={init:function(){this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS).appendField(new Blockly.FieldCheckbox("TRUE"),"STATEMENTS");this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.procedures_mutatorarg={init:function(){this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE).appendField(new Blockly.FieldTextInput("x",this.validator_),"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1},validator_:function(a){return(a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,""))||null}};
+Blockly.Blocks.procedures_callnoreturn={init:function(){this.category="UNKNOWN";this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);this.appendDummyInput("TOPROW").appendField(this.id,"NAME");this.setPreviousStatement(!0);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null;this.setType()},setType:function(){var a=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace);a&&(a=a.myType_)&&(this.previousConnection.setCheck(a),
+this.category="CSG"==a||a==["CSG"]?"PRIMITIVE_CSG":"CAG"==a||a==["CAG"]?"PRIMITIVE_CAG":"UNKNOWN")},getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){Blockly.Names.equals(a,this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP:Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c=Blockly.Procedures.getDefinition(this.getProcedureCall(),
+this.workspace),d=c&&c.mutator&&c.mutator.isVisible();d||(this.quarkConnections_={},this.quarkIds_=null);if(b)if(goog.array.equals(this.arguments_,a))this.quarkIds_=b;else{if(b.length!=a.length)throw"Error: paramNames and paramIds must be the same length.";this.setCollapsed(!1);this.quarkIds_||(this.quarkConnections_={},a.join("\n")==this.arguments_.join("\n")?this.quarkIds_=b:this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(var e=0;e<this.arguments_.length;e++){var f=this.getInput("ARG"+e);
+f&&(f=f.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=f,d&&f&&-1==b.indexOf(this.quarkIds_[e])&&(f.disconnect(),f.getSourceBlock().bumpNeighbours_()))}this.arguments_=[].concat(a);this.updateShape_();if(this.quarkIds_=b)for(e=0;e<this.arguments_.length;e++)d=this.quarkIds_[e],d in this.quarkConnections_&&(f=this.quarkConnections_[d],Blockly.Mutator.reconnect(f,this,"ARG"+e)||delete this.quarkConnections_[d]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=
+0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){Blockly.Events.disable();try{b.setValue(this.arguments_[a])}finally{Blockly.Events.enable()}}else b=new Blockly.FieldLabel(this.arguments_[a]),this.appendValueInput("ARG"+a).setAlign(Blockly.ALIGN_RIGHT).appendField(b,"ARGNAME"+a).init()}for(;this.getInput("ARG"+a);)this.removeInput("ARG"+a),a++;if(a=this.getInput("TOPROW"))this.arguments_.length?this.getField("WITH")||(a.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"),
+a.init()):this.getField("WITH")&&a.removeField("WITH")},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("name",this.getProcedureCall());for(var b=0;b<this.arguments_.length;b++){var c=document.createElement("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);for(var b=[],c=[],d=0,e;e=a.childNodes[d];d++)"arg"==e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),
+c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,c)},renameVar:function(a,b){for(var c=0;c<this.arguments_.length;c++)Blockly.Names.equals(a,this.arguments_[c])&&(this.arguments_[c]=b,this.getField("ARGNAME"+c).setValue(b))},onchange:function(a){if(this.workspace&&!this.workspace.isFlyout)if(a.type==Blockly.Events.CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.arguments_)==
+JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=goog.dom.createDom("xml");b=goog.dom.createDom("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),d=c.y+2*Blockly.SNAP_RADIUS;b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=goog.dom.createDom("field");c.setAttribute("name","NAME");c.appendChild(document.createTextNode(this.getProcedureCall()));b.appendChild(c);
+a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.DELETE&&(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace),b||(Blockly.Events.setGroup(a.group),this.dispose(!0,!1),Blockly.Events.setGroup(!1)))},customContextMenu:function(a){var b={enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,d);
+d.clearBacklight();a&&a.select()};a.push(b);b={enabled:!0};c=this.getProcedureCall();b.text=Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",c);d=this.workspace;b.callback=function(){if(Blockly.Procedures.getDefinition(c,d)){var a=Blockly.Procedures.getCallers(c,d);d.clearBacklight();Blockly.selected.unselect();for(var b=0;a&&b<a.length;b++){a[b]&&a[b].backlight();var g=a[b].collapsedParents();if(g)for(var h=0;h<g.length;h++)g[h].backlight()}}};a.push(b)},defType_:"procedures_defnoreturn"};
+Blockly.Blocks.procedures_callreturn={init:function(){this.category="UNKNOWN";this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null;this.setType()},setType:function(){var a=Blockly.Procedures.getDefinition(this.getFieldValue("NAME"),this.workspace);a&&(a=a.myType_)&&(this.outputConnection.setCheck(a),this.category=
+"Number"==a?"NUMBER":"Boolean"==a?"BOOLEAN":"UNKNOWN")},getProcedureCall:Blockly.Blocks.procedures_callnoreturn.getProcedureCall,renameProcedure:Blockly.Blocks.procedures_callnoreturn.renameProcedure,setProcedureParameters_:Blockly.Blocks.procedures_callnoreturn.setProcedureParameters_,updateShape_:Blockly.Blocks.procedures_callnoreturn.updateShape_,mutationToDom:Blockly.Blocks.procedures_callnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_callnoreturn.domToMutation,renameVar:Blockly.Blocks.procedures_callnoreturn.renameVar,
+onchange:Blockly.Blocks.procedures_callnoreturn.onchange,customContextMenu:function(a){var b={enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,d);d.clearBacklight();a&&a.backlight()};a.push(b);c=this.getProcedureCall();b={enabled:!0};b.text=Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",c);d=this.workspace;b.callback=function(){if(Blockly.Procedures.getDefinition(c,d)){var a=Blockly.Procedures.getCallers(c,
+d);d.clearBacklight();Blockly.selected.unselect();for(var b=0;a&&b<a.length;b++){a[b]&&a[b].backlight();var g=a[b].collapsedParents();if(g)for(var h=0;h<g.length;h++)g[h].backlight()}}};a.push(b)},defType_:"procedures_defreturn"};Blockly.Blocks.texts={};Blockscad.Toolbox=Blockscad.Toolbox||{};Blockly.Blocks.texts.HUE=Blockscad.Toolbox.HEX_TEXT;
+Blockly.Blocks.text={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);this.setColour(Blockscad.Toolbox.HEX_TEXT);this.appendDummyInput().appendField(this.newQuote_(!0)).appendField(new Blockly.FieldTextInput(Blockscad.Msg.TEXT_DEFAULT_VALUE),"TEXT").appendField(this.newQuote_(!1));this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_TEXT_TOOLTIP)},newQuote_:function(a){return new Blockly.FieldImage(a==this.RTL?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==":
+"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",10,10,"")}};
+Blockly.Blocks.text_join={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_JOIN_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.itemCount_=2;this.updateShape_();this.setOutput(!0,"String");this.setMutator(new Blockly.Mutator(["text_create_join_item"]));this.setTooltip(Blockly.Msg.TEXT_JOIN_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);
+this.updateShape_()},decompose:function(a){var b=Blockly.Block.obtain(a,"text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=Blockly.Block.obtain(a,"text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){a=a.getInputTargetBlock("STACK");for(var b=[],c=0;a;)b[c]=a.valueConnection_,a=a.nextConnection&&a.nextConnection.targetBlock(),c++;this.itemCount_=c;this.updateShape_();for(c=
+0;c<this.itemCount_;c++)b[c]&&this.getInput("ADD"+c).connection.connect(b[c])},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;b++;a=a.nextConnection&&a.nextConnection.targetBlock()}},updateShape_:function(){if(this.getInput("EMPTY"))this.removeInput("EMPTY");else for(var a=0;this.getInput("ADD"+a);)this.removeInput("ADD"+a),a++;if(0==this.itemCount_)this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));
+else for(a=0;a<this.itemCount_;a++){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH)}},newQuote_:Blockly.Blocks.text.newQuote_};Blockly.Blocks.text_create_join_container={init:function(){this.setColour(Blockly.Blocks.texts.HUE);this.appendDummyInput().appendField(Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN);this.appendStatementInput("STACK");this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.text_create_join_item={init:function(){this.setColour(Blockly.Blocks.texts.HUE);this.appendDummyInput().appendField(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP);this.contextMenu=!1}};
+Blockly.Blocks.text_append={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_APPEND_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.appendValueInput("TEXT").appendField(Blockly.Msg.TEXT_APPEND_TO).appendField(new Blockly.FieldVariable(Blockly.Msg.TEXT_APPEND_VARIABLE),"VAR").appendField(Blockly.Msg.TEXT_APPEND_APPENDTEXT);this.setPreviousStatement(!0);this.setNextStatement(!0);var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace("%1",a.getFieldValue("VAR"))})},
+getVars:function(){return[this.getFieldValue("VAR")]},renameVar:function(a,b){Blockly.Names.equals(a,this.getFieldValue("VAR"))&&this.setFieldValue(b,"VAR")}};Blockly.Blocks.text_length={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_LENGTH_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.interpolateMsg(Blockly.Msg.TEXT_LENGTH_TITLE,["VALUE",["String","Array"],Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setOutput(!0,"Number");this.setTooltip(Blockly.Msg.TEXT_LENGTH_TOOLTIP)}};
+Blockly.Blocks.text_isEmpty={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_ISEMPTY_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.interpolateMsg(Blockly.Msg.TEXT_ISEMPTY_TITLE,["VALUE",["String","Array"],Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setOutput(!0,"Boolean");this.setTooltip(Blockly.Msg.TEXT_ISEMPTY_TOOLTIP)}};
+Blockly.Blocks.text_indexOf={init:function(){var a=[[Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST,"FIRST"],[Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.TEXT_INDEXOF_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("String").appendField(Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT);this.appendValueInput("FIND").setCheck("String").appendField(new Blockly.FieldDropdown(a),"END");Blockly.Msg.TEXT_INDEXOF_TAIL&&this.appendDummyInput().appendField(Blockly.Msg.TEXT_INDEXOF_TAIL);
+this.setInputsInline(!0);this.setTooltip(Blockly.Msg.TEXT_INDEXOF_TOOLTIP)}};
+Blockly.Blocks.text_charAt={init:function(){this.WHERE_OPTIONS=[[Blockly.Msg.TEXT_CHARAT_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_CHARAT_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_CHARAT_FIRST,"FIRST"],[Blockly.Msg.TEXT_CHARAT_LAST,"LAST"],[Blockly.Msg.TEXT_CHARAT_RANDOM,"RANDOM"]];this.setHelpUrl(Blockly.Msg.TEXT_CHARAT_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.setOutput(!0,"String");this.appendValueInput("VALUE").setCheck("String").appendField(Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT);this.appendDummyInput("AT");
+this.setInputsInline(!0);this.updateAt_(!0);this.setTooltip(Blockly.Msg.TEXT_CHARAT_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation"),b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a="false"!=a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
+this.appendDummyInput("AT");Blockly.Msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_CHARAT_TAIL));var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.sourceBlock_;e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE")}};
+Blockly.Blocks.text_getSubstring={init:function(){this.WHERE_OPTIONS_1=[[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);
+this.appendValueInput("STRING").setCheck("String").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);this.appendDummyInput("AT1");this.appendDummyInput("AT2");Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL);this.setInputsInline(!0);this.setOutput(!0,"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){var a=document.createElement("mutation"),
+b=this.getInput("AT1").type==Blockly.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type==Blockly.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("at1");a="true"==a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
+this.appendDummyInput("AT"+a);2==a&&Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL));var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var f=this.sourceBlock_;f.updateAt_(a,d);f.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&this.moveInputBefore("AT1","AT2")}};
+Blockly.Blocks.text_changeCase={init:function(){var a=[[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl(Blockly.Msg.TEXT_CHANGECASE_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.appendValueInput("TEXT").setCheck("String").appendField(new Blockly.FieldDropdown(a),"CASE");this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_CHANGECASE_TOOLTIP)}};
+Blockly.Blocks.text_trim={init:function(){var a=[[Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl(Blockly.Msg.TEXT_TRIM_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.appendValueInput("TEXT").setCheck("String").appendField(new Blockly.FieldDropdown(a),"MODE");this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_TRIM_TOOLTIP)}};
+Blockly.Blocks.text_print={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_PRINT_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.interpolateMsg(Blockly.Msg.TEXT_PRINT_TITLE,["TEXT",null,Blockly.ALIGN_RIGHT],Blockly.ALIGN_RIGHT);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.TEXT_PRINT_TOOLTIP)}};
+Blockly.Blocks.text_prompt_ext={init:function(){var a=[[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);var b=this,a=new Blockly.FieldDropdown(a,function(a){b.updateType_(a)});this.appendValueInput("TEXT").appendField(a,"TYPE");this.setOutput(!0,"String");this.setTooltip(function(){return"TEXT"==b.getFieldValue("TYPE")?Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT:Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER})},
+updateType_:function(a){"NUMBER"==a?this.outputConnection.setCheck("Number"):this.outputConnection.setCheck("String")},mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("type",this.getFieldValue("TYPE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("type"))}};
+Blockly.Blocks.text_prompt={init:function(){var a=[[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]],b=this;this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);a=new Blockly.FieldDropdown(a,function(a){b.updateType_(a)});this.appendDummyInput().appendField(a,"TYPE").appendField(this.newQuote_(!0)).appendField(new Blockly.FieldTextInput(""),"TEXT").appendField(this.newQuote_(!1));this.setOutput(!0,"String");b=this;this.setTooltip(function(){return"TEXT"==
+b.getFieldValue("TYPE")?Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT:Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER})},newQuote_:Blockly.Blocks.text.newQuote_,updateType_:Blockly.Blocks.text_prompt_ext.updateType_,mutationToDom:Blockly.Blocks.text_prompt_ext.mutationToDom,domToMutation:Blockly.Blocks.text_prompt_ext.domToMutation};Blockly.Blocks.variables={};
+Blockly.Blocks.variables_get={init:function(){this.setHelpUrl(Blockly.Msg.VARIABLES_GET_HELPURL);this.setColour(Blockscad.Toolbox.HEX_VARIABLE);this.appendDummyInput().appendField(new Blockly.FieldVariable(Blockly.Msg.VARIABLES_DEFAULT_NAME),"VAR");this.setOutput(!0);this.setTooltip(Blockly.Msg.VARIABLES_GET_TOOLTIP);this.contextMenuMsg_=Blockly.Msg.VARIABLES_GET_CREATE_SET;for(var a=Blockly.Variables.getInstances(this.getFieldValue("VAR"),this.workspace),b=0,c=0;c<a.length;c++){if("variables_set"==a[c].type){this.outputConnection.setCheck(a[c].myType_);
+b=1;break}if("controls_for"==a[c].type||"controls_for_chainhull"==a[c].type)this.outputConnection.setCheck(null),b=1}b||this.outputConnection.setCheck(null)},contextMenuType_:"variables_set",customContextMenu:function(a){var b={enabled:!0},c=this.getFieldValue("VAR");b.text=this.contextMenuMsg_.replace("%1",c);var d=goog.dom.createDom("field",null,c);d.setAttribute("name","VAR");d=goog.dom.createDom("block",null,d);d.setAttribute("type",this.contextMenuType_);b.callback=Blockly.ContextMenu.callbackFactory(this,
+d);a.push(b);b={enabled:!0};b.text=Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",c);var e=this.workspace,f=this;b.callback=function(){var a=Blockly.Variables.getInstances(c,e);e.clearBacklight();f.unselect();for(var b=0;a&&b<a.length;b++){a[b]&&a[b].backlight();var d=a[b].collapsedParents();if(d)for(var l=0;l<d.length;l++)d[l].backlight()}};a.push(b)}};
+Blockly.Blocks.variables_set={init:function(){this.myType_=null;this.backlightBlocks=[];this.jsonInit({message0:Blockly.Msg.VARIABLES_SET,args0:[{type:"field_variable",name:"VAR",variable:Blockly.Msg.VARIABLES_DEFAULT_NAME},{type:"input_value",name:"VALUE"}],inputsInline:!0});this.setHelpUrl(Blockly.Msg.VARIABLES_SET_HELPURL);this.setColour(Blockscad.Toolbox.HEX_VARIABLE);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.VARIABLES_SET_TOOLTIP);this.contextMenuMsg_=
+Blockly.Msg.VARIABLES_SET_CREATE_GET},contextMenuType_:"variables_get",setType:function(a){if(this.workspace&&(null==a||goog.isArray(a)||(a=[a]),!Blockscad.arraysEqual(a,this.myType_))){var b=this.myType_;this.myType_=a;var c=Blockly.Variables.getInstances(this.getFieldValue("VAR"),this.workspace),d=[],e,f=!0;Blockscad.workspace.undoStack_.length&&(f=Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length-1].group);Blockly.Events.setGroup(f);if(0<c.length)for(f=0;f<c.length;f++)if("variables_get"==
+c[f].type){var g=c[f].getParent();if(null!=this.myType_&&g&&(e=c[f].outputConnection.targetConnection.check_,null!=e&&(e=e[0]),null!=e&&null!=a&&e!=a[0]&&(d.push(c[f]),g=c[f].collapsedParents())))for(var h=0;h<g.length;h++)g[h].setCollapsed(!1);c[f].outputConnection.setCheck(a);Blockly.Events.isEnabled()&&d.length&&Blockly.Events.fire(new Blockly.Events.Typing(c[f],b,a));var k=Blockscad.hasParentOfType(c[f],"procedures_defreturn");k||(k=Blockscad.hasParentOfType(c[f],"variables_set"));var l=Blockscad.hasParentOfType(c[f],
+"logic_ternary");setTimeout(function(){k&&!l&&k.setType(a)},0)}Blockly.Events.setGroup(!1);if(d.length){for(f=0;f<d.length;f++)d[f].backlight(),this.backlightBlocks.push(d[f].id);b=""+(Blockscad.Msg.VARIABLES_BUMPED_ONE.replace("%1",d.length)+"\n");b+=Blockscad.Msg.VARIABLES_BUMPED_TWO.replace("%1",this.getFieldValue("VAR")).replace("%2",e).replace("%3",a);this.setWarningText(b)}else this.setWarningText(null)}},customContextMenu:Blockly.Blocks.variables_get.customContextMenu};

+ 465 - 0
blockly/build.py

@@ -0,0 +1,465 @@
+#!/usr/bin/python2.7
+# Compresses the core Blockly files into a single JavaScript file.
+#
+# Copyright 2012 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.
+
+# This script generates two versions of Blockly's core files:
+#   blockly_compressed.js
+#   blockly_uncompressed.js
+# The compressed file is a concatenation of all of Blockly's core files which
+# have been run through Google's Closure Compiler.  This is done using the
+# online API (which takes a few seconds and requires an Internet connection).
+# The uncompressed file is a script that loads in each of Blockly's core files
+# one by one.  This takes much longer for a browser to load, but is useful
+# when debugging code since line numbers are meaningful and variables haven't
+# been renamed.  The uncompressed file also allows for a faster developement
+# cycle since there is no need to rebuild or recompile, just reload.
+#
+# This script also generates:
+#   blocks_compressed.js: The compressed Blockly language blocks.
+#   javascript_compressed.js: The compressed Javascript generator.
+#   python_compressed.js: The compressed Python generator.
+#   dart_compressed.js: The compressed Dart generator.
+#   lua_compressed.js: The compressed Lua generator.
+#   msg/js/<LANG>.js for every language <LANG> defined in msg/js/<LANG>.json.
+
+import sys
+if sys.version_info[0] != 2:
+  raise Exception("Blockly build only compatible with Python 2.x.\n"
+                  "You are using: " + sys.version)
+
+import errno, glob, httplib, json, os, re, subprocess, threading, urllib
+
+
+def import_path(fullpath):
+  """Import a file with full path specification.
+  Allows one to import from any directory, something __import__ does not do.
+
+  Args:
+      fullpath:  Path and filename of import.
+
+  Returns:
+      An imported module.
+  """
+  path, filename = os.path.split(fullpath)
+  filename, ext = os.path.splitext(filename)
+  sys.path.append(path)
+  module = __import__(filename)
+  reload(module)  # Might be out of date.
+  del sys.path[-1]
+  return module
+
+
+HEADER = ("// Do not edit this file; automatically generated by build.py.\n"
+          "'use strict';\n")
+
+
+class Gen_uncompressed(threading.Thread):
+  """Generate a JavaScript file that loads Blockly's raw files.
+  Runs in a separate thread.
+  """
+  def __init__(self, search_paths):
+    threading.Thread.__init__(self)
+    self.search_paths = search_paths
+
+  def run(self):
+    target_filename = 'blockly_uncompressed.js'
+    f = open(target_filename, 'w')
+    f.write(HEADER)
+    f.write("""
+var isNodeJS = !!(typeof module !== 'undefined' && module.exports &&
+                  typeof window === 'undefined');
+
+if (isNodeJS) {
+  var window = {};
+  require('../closure-library/closure/goog/bootstrap/nodejs');
+}
+
+window.BLOCKLY_DIR = (function() {
+  if (!isNodeJS) {
+    // Find name of current directory.
+    var scripts = document.getElementsByTagName('script');
+    var re = new RegExp('(.+)[\/]blockly_uncompressed\.js$');
+    for (var i = 0, script; script = scripts[i]; i++) {
+      var match = re.exec(script.src);
+      if (match) {
+        return match[1];
+      }
+    }
+    alert('Could not detect Blockly\\'s directory name.');
+  }
+  return '';
+})();
+
+window.BLOCKLY_BOOT = function() {
+  var dir = '';
+  if (isNodeJS) {
+    require('../closure-library/closure/goog/bootstrap/nodejs');
+    dir = 'blockly';
+  } else {
+    // Execute after Closure has loaded.
+    if (!window.goog) {
+      alert('Error: Closure not found.  Read this:\\n' +
+            'developers.google.com/blockly/guides/modify/web/closure');
+    }
+    dir = window.BLOCKLY_DIR.match(/[^\\/]+$/)[0];
+  }
+""")
+    add_dependency = []
+    base_path = calcdeps.FindClosureBasePath(self.search_paths)
+    for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths):
+      add_dependency.append(calcdeps.GetDepsLine(dep, base_path))
+    add_dependency = '\n'.join(add_dependency)
+    # Find the Blockly directory name and replace it with a JS variable.
+    # This allows blockly_uncompressed.js to be compiled on one computer and be
+    # used on another, even if the directory name differs.
+    m = re.search('[\\/]([^\\/]+)[\\/]core[\\/]blockly.js', add_dependency)
+    add_dependency = re.sub('([\\/])' + re.escape(m.group(1)) +
+        '([\\/]core[\\/])', '\\1" + dir + "\\2', add_dependency)
+    f.write(add_dependency + '\n')
+
+    provides = []
+    for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths):
+      if not dep.filename.startswith(os.pardir + os.sep):  # '../'
+        provides.extend(dep.provides)
+    provides.sort()
+    f.write('\n')
+    f.write('// Load Blockly.\n')
+    for provide in provides:
+      f.write("goog.require('%s');\n" % provide)
+
+    f.write("""
+delete this.BLOCKLY_DIR;
+delete this.BLOCKLY_BOOT;
+};
+
+if (isNodeJS) {
+  window.BLOCKLY_BOOT()
+  module.exports = Blockly;
+} else {
+  // Delete any existing Closure (e.g. Soy's nogoog_shim).
+  document.write('<script>var goog = undefined;</script>');
+  // Load fresh Closure Library.
+  document.write('<script src="' + window.BLOCKLY_DIR +
+      '/../closure-library/closure/goog/base.js"></script>');
+  document.write('<script>window.BLOCKLY_BOOT();</script>');
+}
+""")
+    f.close()
+    print("SUCCESS: " + target_filename)
+
+
+class Gen_compressed(threading.Thread):
+  """Generate a JavaScript file that contains all of Blockly's core and all
+  required parts of Closure, compiled together.
+  Uses the Closure Compiler's online API.
+  Runs in a separate thread.
+  """
+  def __init__(self, search_paths):
+    threading.Thread.__init__(self)
+    self.search_paths = search_paths
+
+  def run(self):
+    self.gen_core()
+    self.gen_blocks()
+    self.gen_generator("openscad")
+
+
+  def gen_core(self):
+    target_filename = "blockly_compressed.js"
+    # Define the parameters for the POST request.
+    params = [
+        ("compilation_level", "SIMPLE_OPTIMIZATIONS"),
+        ("use_closure_library", "true"),
+        ("output_format", "json"),
+        ("output_info", "compiled_code"),
+        ("output_info", "warnings"),
+        ("output_info", "errors"),
+        ("output_info", "statistics"),
+      ]
+
+    # Read in all the source files.
+    filenames = calcdeps.CalculateDependencies(self.search_paths,
+        [os.path.join("core", "blockly.js")])
+    for filename in filenames:
+      # Filter out the Closure files (the compiler will add them).
+      if filename.startswith(os.pardir + os.sep):  # '../'
+        continue
+      f = open(filename)
+      params.append(("js_code", "".join(f.readlines())))
+      f.close()
+
+    self.do_compile(params, target_filename, filenames, "")
+
+  def gen_blocks(self):
+    target_filename = "blocks_compressed.js"
+    # Define the parameters for the POST request.
+    params = [
+        ("compilation_level", "SIMPLE_OPTIMIZATIONS"),
+        ("output_format", "json"),
+        ("output_info", "compiled_code"),
+        ("output_info", "warnings"),
+        ("output_info", "errors"),
+        ("output_info", "statistics"),
+      ]
+
+    # Read in all the source files.
+    # Add Blockly.Blocks to be compatible with the compiler.
+    params.append(("js_code", "goog.provide('Blockly.Blocks');"))
+    filenames = glob.glob(os.path.join("blocks", "*.js"))
+    for filename in filenames:
+      f = open(filename)
+      params.append(("js_code", "".join(f.readlines())))
+      f.close()
+
+    # Remove Blockly.Blocks to be compatible with Blockly.
+    remove = "var Blockly={Blocks:{}};"
+    self.do_compile(params, target_filename, filenames, remove)
+
+  def gen_generator(self, language):
+    target_filename = language + "_compressed.js"
+    # Define the parameters for the POST request.
+    params = [
+        ("compilation_level", "SIMPLE_OPTIMIZATIONS"),
+        ("output_format", "json"),
+        ("output_info", "compiled_code"),
+        ("output_info", "warnings"),
+        ("output_info", "errors"),
+        ("output_info", "statistics"),
+      ]
+
+    # Read in all the source files.
+    # Add Blockly.Generator to be compatible with the compiler.
+    params.append(("js_code", "goog.provide('Blockly.Generator');"))
+    filenames = glob.glob(
+        os.path.join("generators", language, "*.js"))
+    filenames.insert(0, os.path.join("generators", language + ".js"))
+    for filename in filenames:
+      f = open(filename)
+      params.append(("js_code", "".join(f.readlines())))
+      f.close()
+    filenames.insert(0, "[goog.provide]")
+
+    # Remove Blockly.Generator to be compatible with Blockly.
+    remove = "var Blockly={Generator:{}};"
+    self.do_compile(params, target_filename, filenames, remove)
+
+  def do_compile(self, params, target_filename, filenames, remove):
+    # Send the request to Google.
+    headers = {"Content-type": "application/x-www-form-urlencoded"}
+    conn = httplib.HTTPConnection("closure-compiler.appspot.com")
+    conn.request("POST", "/compile", urllib.urlencode(params), headers)
+    response = conn.getresponse()
+    json_str = response.read()
+    conn.close()
+
+    # Parse the JSON response.
+    json_data = json.loads(json_str)
+
+    def file_lookup(name):
+      if not name.startswith("Input_"):
+        return "???"
+      n = int(name[6:]) - 1
+      return filenames[n]
+
+    if json_data.has_key("serverErrors"):
+      errors = json_data["serverErrors"]
+      for error in errors:
+        print("SERVER ERROR: %s" % target_filename)
+        print(error["error"])
+    elif json_data.has_key("errors"):
+      errors = json_data["errors"]
+      for error in errors:
+        print("FATAL ERROR")
+        print(error["error"])
+        if error["file"]:
+          print("%s at line %d:" % (
+              file_lookup(error["file"]), error["lineno"]))
+          print(error["line"])
+          print((" " * error["charno"]) + "^")
+        sys.exit(1)
+    else:
+      if json_data.has_key("warnings"):
+        warnings = json_data["warnings"]
+        for warning in warnings:
+          print("WARNING")
+          print(warning["warning"])
+          if warning["file"]:
+            print("%s at line %d:" % (
+                file_lookup(warning["file"]), warning["lineno"]))
+            print(warning["line"])
+            print((" " * warning["charno"]) + "^")
+        print()
+
+      if not json_data.has_key("compiledCode"):
+        print("FATAL ERROR: Compiler did not return compiledCode.")
+        sys.exit(1)
+
+      code = HEADER + "\n" + json_data["compiledCode"]
+      code = code.replace(remove, "")
+
+      # Trim down Google's Apache licences.
+      # The Closure Compiler used to preserve these until August 2015.
+      # Delete this in a few months if the licences don't return.
+      LICENSE = re.compile("""/\\*
+
+ [\w ]+
+
+ (Copyright \\d+ 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.
+\\*/""")
+      code = re.sub(LICENSE, r"\n// \1  Apache License 2.0", code)
+
+      stats = json_data["statistics"]
+      original_b = stats["originalSize"]
+      compressed_b = stats["compressedSize"]
+      if original_b > 0 and compressed_b > 0:
+        f = open(target_filename, "w")
+        f.write(code)
+        f.close()
+
+        original_kb = int(original_b / 1024 + 0.5)
+        compressed_kb = int(compressed_b / 1024 + 0.5)
+        ratio = int(float(compressed_b) / float(original_b) * 100 + 0.5)
+        print("SUCCESS: " + target_filename)
+        print("Size changed from %d KB to %d KB (%d%%)." % (
+            original_kb, compressed_kb, ratio))
+      else:
+        print("UNKNOWN ERROR")
+
+
+class Gen_langfiles(threading.Thread):
+  """Generate JavaScript file for each natural language supported.
+
+  Runs in a separate thread.
+  """
+
+  def __init__(self):
+    threading.Thread.__init__(self)
+
+  def _rebuild(self, srcs, dests):
+    # Determine whether any of the files in srcs is newer than any in dests.
+    try:
+      return (max(os.path.getmtime(src) for src in srcs) >
+              min(os.path.getmtime(dest) for dest in dests))
+    except OSError as e:
+      # Was a file not found?
+      if e.errno == errno.ENOENT:
+        # If it was a source file, we can't proceed.
+        if e.filename in srcs:
+          print("Source file missing: " + e.filename)
+          sys.exit(1)
+        else:
+          # If a destination file was missing, rebuild.
+          return True
+      else:
+        print("Error checking file creation times: " + e)
+
+  def run(self):
+    # The files msg/json/{en,qqq,synonyms}.json depend on msg/messages.js.
+    if self._rebuild([os.path.join("msg", "messages.js")],
+                     [os.path.join("msg", "json", f) for f in
+                      ["en.json", "qqq.json", "synonyms.json"]]):
+      try:
+        subprocess.check_call([
+            "python",
+            os.path.join("i18n", "js_to_json.py"),
+            "--input_file", "msg/messages.js",
+            "--output_dir", "msg/json/",
+            "--quiet"])
+      except (subprocess.CalledProcessError, OSError) as e:
+        # Documentation for subprocess.check_call says that CalledProcessError
+        # will be raised on failure, but I found that OSError is also possible.
+        print("Error running i18n/js_to_json.py: ", e)
+        sys.exit(1)
+
+    # Checking whether it is necessary to rebuild the js files would be a lot of
+    # work since we would have to compare each <lang>.json file with each
+    # <lang>.js file.  Rebuilding is easy and cheap, so just go ahead and do it.
+    try:
+      # Use create_messages.py to create .js files from .json files.
+      cmd = [
+          "python",
+          os.path.join("i18n", "create_messages.py"),
+          "--source_lang_file", os.path.join("msg", "json", "en.json"),
+          "--source_synonym_file", os.path.join("msg", "json", "synonyms.json"),
+          "--key_file", os.path.join("msg", "json", "keys.json"),
+          "--output_dir", os.path.join("msg", "js"),
+          "--quiet"]
+      json_files = glob.glob(os.path.join("msg", "json", "*.json"))
+      json_files = [file for file in json_files if not
+                    (file.endswith(("keys.json", "synonyms.json", "qqq.json")))]
+      cmd.extend(json_files)
+      subprocess.check_call(cmd)
+    except (subprocess.CalledProcessError, OSError) as e:
+      print("Error running i18n/create_messages.py: ", e)
+      sys.exit(1)
+
+    # Output list of .js files created.
+    for f in json_files:
+      # This assumes the path to the current directory does not contain "json".
+      f = f.replace("json", "js")
+      if os.path.isfile(f):
+        print("SUCCESS: " + f)
+      else:
+        print("FAILED to create " + f)
+
+
+if __name__ == "__main__":
+  try:
+    calcdeps = import_path(os.path.join(
+        os.path.pardir, "closure-library", "closure", "bin", "calcdeps.py"))
+  except ImportError:
+    if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")):
+      # Dir got renamed when Closure moved from Google Code to GitHub in 2014.
+      print("Error: Closure directory needs to be renamed from"
+            "'closure-library-read-only' to 'closure-library'.\n"
+            "Please rename this directory.")
+    elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")):
+      # When Closure is installed by npm, it is named "google-closure-library".
+      #calcdeps = import_path(os.path.join(
+      # os.path.pardir, "google-closure-library", "closure", "bin", "calcdeps.py"))
+      print("Error: Closure directory needs to be renamed from"
+           "'google-closure-library' to 'closure-library'.\n"
+           "Please rename this directory.")
+    else:
+      print("""Error: Closure not found.  Read this:
+developers.google.com/blockly/guides/modify/web/closure""")
+    sys.exit(1)
+
+  search_paths = calcdeps.ExpandDirectories(
+      ["core", os.path.join(os.path.pardir, "closure-library")])
+
+  # Run both tasks in parallel threads.
+  # Uncompressed is limited by processor speed.
+  # Compressed is limited by network and server speed.
+  Gen_uncompressed(search_paths).start()
+  Gen_compressed(search_paths).start()
+
+  # This is run locally in a separate thread.
+  Gen_langfiles().start()

+ 1554 - 0
blockly/core/block.js

@@ -0,0 +1,1554 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 The class representing one block.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Block');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly.Comment');
+goog.require('Blockly.Connection');
+goog.require('Blockly.Input');
+goog.require('Blockly.Mutator');
+goog.require('Blockly.MutatorPlus');
+goog.require('Blockly.MutatorMinus');
+goog.require('Blockly.Warning');
+goog.require('Blockly.Workspace');
+goog.require('Blockly.Xml');
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.math.Coordinate');
+goog.require('goog.string');
+
+
+/**
+ * Class for one block.
+ * Not normally called directly, workspace.newBlock() is preferred.
+ * @param {!Blockly.Workspace} workspace The block's workspace.
+ * @param {?string} prototypeName Name of the language object containing
+ *     type-specific functions for this block.
+ * @param {=string} opt_id Optional ID.  Use this ID if provided, otherwise
+ *     create a new id.
+ * @constructor
+ */
+Blockly.Block = function(workspace, prototypeName, opt_id) {
+  /** @type {string} */
+  this.id = (opt_id && !workspace.getBlockById(opt_id)) ?
+      opt_id : Blockly.genUid();
+  workspace.blockDB_[this.id] = this;
+  /** @type {Blockly.Connection} */
+  this.outputConnection = null;
+  /** @type {Blockly.Connection} */
+  this.nextConnection = null;
+  /** @type {Blockly.Connection} */
+  this.previousConnection = null;
+  /** @type {!Array.<!Blockly.Input>} */
+  this.inputList = [];
+  /** @type {boolean|undefined} */
+  this.inputsInline = undefined;
+  /** @type {boolean} */
+  this.disabled = false;
+  /** @type {string|!Function} */
+  this.tooltip = '';
+  /** @type {boolean} */
+  this.contextMenu = true;
+
+  /**
+   * @type {Blockly.Block}
+   * @private
+   */
+  this.parentBlock_ = null;
+
+  /**
+   * @type {!Array.<!Blockly.Block>}
+   * @private
+   */
+  this.childBlocks_ = [];
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.deletable_ = true;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.movable_ = true;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.editable_ = true;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.isShadow_ = false;
+
+  /**
+   * @type {boolean}
+   * @private
+   */
+  this.collapsed_ = false;
+
+  /** @type {string|Blockly.Comment} */
+  this.comment = null;
+
+  /**
+   * @type {!goog.math.Coordinate}
+   * @private
+   */
+  this.xy_ = new goog.math.Coordinate(0, 0);
+
+  /** @type {!Blockly.Workspace} */
+  this.workspace = workspace;
+  /** @type {boolean} */
+  this.isInFlyout = workspace.isFlyout;
+  /** @type {boolean} */
+  this.RTL = workspace.RTL;
+
+  // Copy the type-specific functions and data from the prototype.
+  if (prototypeName) {
+    /** @type {string} */
+    this.type = prototypeName;
+    var prototype = Blockly.Blocks[prototypeName];
+    goog.asserts.assertObject(prototype,
+        'Error: "%s" is an unknown language block.', prototypeName);
+    goog.mixin(this, prototype);
+  }
+
+  workspace.addTopBlock(this);
+
+  // Call an initialization function, if it exists.
+  if (goog.isFunction(this.init)) {
+    this.init();
+  }
+  // Record initial inline state.
+  /** @type {boolean|undefined} */
+  this.inputsInlineDefault = this.inputsInline;
+  if (Blockly.Events.isEnabled()) {
+    Blockly.Events.fire(new Blockly.Events.Create(this));
+  }
+  // Bind an onchange function, if it exists.
+  if (goog.isFunction(this.onchange)) {
+    this.onchangeWrapper_ = this.onchange.bind(this);
+    this.workspace.addChangeListener(this.onchangeWrapper_);
+  }
+};
+
+/**
+ * Obtain a newly created block.
+ * @param {!Blockly.Workspace} workspace The block's workspace.
+ * @param {?string} prototypeName Name of the language object containing
+ *     type-specific functions for this block.
+ * @return {!Blockly.Block} The created block.
+ * @deprecated December 2015
+ */
+Blockly.Block.obtain = function(workspace, prototypeName) {
+  console.warn('Deprecated call to Blockly.Block.obtain, ' +
+               'use workspace.newBlock instead.');
+  return workspace.newBlock(prototypeName);
+};
+
+/**
+ * Optional text data that round-trips beween blocks and XML.
+ * Has no effect. May be used by 3rd parties for meta information.
+ * @type {?string}
+ */
+Blockly.Block.prototype.data = null;
+
+/**
+ * Colour of the block in '#RRGGBB' format.
+ * @type {string}
+ * @private
+ */
+Blockly.Block.prototype.colour_ = '#000000';
+
+/**
+ * Dispose of this block.
+ * @param {boolean} healStack If true, then try to heal any gap by connecting
+ *     the next statement with the previous statement.  Otherwise, dispose of
+ *     all children of this block.
+ */
+Blockly.Block.prototype.dispose = function(healStack) {
+  // Terminate onchange event calls.
+  if (this.onchangeWrapper_) {
+    this.workspace.removeChangeListener(this.onchangeWrapper_);
+  }
+  this.unplug(healStack);
+  if (Blockly.Events.isEnabled()) {
+    Blockly.Events.fire(new Blockly.Events.Delete(this));
+  }
+  Blockly.Events.disable();
+
+  try {
+    // This block is now at the top of the workspace.
+    // Remove this block from the workspace's list of top-most blocks.
+    if (this.workspace) {
+      this.workspace.removeTopBlock(this);
+      // Remove from block database.
+      delete this.workspace.blockDB_[this.id];
+      this.workspace = null;
+    }
+
+    // Just deleting this block from the DOM would result in a memory leak as
+    // well as corruption of the connection database.  Therefore we must
+    // methodically step through the blocks and carefully disassemble them.
+
+    this.unbacklight();  // BlocksCAD
+
+    // First, dispose of all my children.
+    for (var i = this.childBlocks_.length - 1; i >= 0; i--) {
+      this.childBlocks_[i].dispose(false);
+    }
+    // Then dispose of myself.
+    // Dispose of all inputs and their fields.
+    for (var i = 0, input; input = this.inputList[i]; i++) {
+      input.dispose();
+    }
+    this.inputList.length = 0;
+    // Dispose of any remaining connections (next/previous/output).
+    var connections = this.getConnections_(true);
+    for (var i = 0; i < connections.length; i++) {
+      var connection = connections[i];
+      if (connection.isConnected()) {
+        connection.disconnect();
+      }
+      connections[i].dispose();
+    }
+  } finally {
+    Blockly.Events.enable();
+  }
+};
+
+/**
+ * Unplug this block from its superior block.  If this block is a statement,
+ * optionally reconnect the block underneath with the block on top.
+ * @param {boolean} opt_healStack Disconnect child statement and reconnect
+ *   stack.  Defaults to false.
+ */
+Blockly.Block.prototype.unplug = function(opt_healStack) {
+  if (this.outputConnection) {
+    if (this.outputConnection.isConnected()) {
+      // Disconnect from any superior block.
+      this.outputConnection.disconnect();
+    }
+  } else if (this.previousConnection) {
+    var previousTarget = null;
+    if (this.previousConnection.isConnected()) {
+      // Remember the connection that any next statements need to connect to.
+      previousTarget = this.previousConnection.targetConnection;
+      // Detach this block from the parent's tree.
+      this.previousConnection.disconnect();
+    }
+    var nextBlock = this.getNextBlock();
+    if (opt_healStack && nextBlock) {
+      // Disconnect the next statement.
+      var nextTarget = this.nextConnection.targetConnection;
+      nextTarget.disconnect();
+      if (previousTarget && previousTarget.checkType_(nextTarget)) {
+        // Attach the next statement to the previous statement.
+        previousTarget.connect(nextTarget);
+      }
+    }
+  }
+};
+
+/**
+ * Returns all connections originating from this block.
+ * @return {!Array.<!Blockly.Connection>} Array of connections.
+ * @private
+ */
+Blockly.Block.prototype.getConnections_ = function() {
+  var myConnections = [];
+  if (this.outputConnection) {
+    myConnections.push(this.outputConnection);
+  }
+  if (this.previousConnection) {
+    myConnections.push(this.previousConnection);
+  }
+  if (this.nextConnection) {
+    myConnections.push(this.nextConnection);
+  }
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    if (input.connection) {
+      myConnections.push(input.connection);
+    }
+  }
+  return myConnections;
+};
+
+/**
+ * Walks down a stack of blocks and finds the last next connection on the stack.
+ * @return {Blockly.Connection} The last next connection on the stack, or null.
+ * @private
+ */
+Blockly.Block.prototype.lastConnectionInStack_ = function() {
+  var nextConnection = this.nextConnection;
+  while (nextConnection) {
+    var nextBlock = nextConnection.targetBlock();
+    if (!nextBlock) {
+      // Found a next connection with nothing on the other side.
+      return nextConnection;
+    }
+    nextConnection = nextBlock.nextConnection;
+  }
+  // Ran out of next connections.
+  return null;
+};
+
+/**
+ * Bump unconnected blocks out of alignment.  Two blocks which aren't actually
+ * connected should not coincidentally line up on screen.
+ * @private
+ */
+Blockly.Block.prototype.bumpNeighbours_ = function() {
+  if (!this.workspace) {
+    return;  // Deleted block.
+  }
+  if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
+    return;  // Don't bump blocks during a drag.
+  }
+  var rootBlock = this.getRootBlock();
+  if (rootBlock.isInFlyout) {
+    return;  // Don't move blocks around in a flyout.
+  }
+  // Loop though every connection on this block.
+  var myConnections = this.getConnections_(false);
+  for (var i = 0, connection; connection = myConnections[i]; i++) {
+    // Spider down from this block bumping all sub-blocks.
+    if (connection.isConnected() && connection.isSuperior()) {
+      connection.targetBlock().bumpNeighbours_();
+    }
+
+    var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS);
+    for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
+      // If both connections are connected, that's probably fine.  But if
+      // either one of them is unconnected, then there could be confusion.
+      if (!connection.isConnected() || !otherConnection.isConnected()) {
+        // Only bump blocks if they are from different tree structures.
+        if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) {
+          // Always bump the inferior block.
+          if (connection.isSuperior()) {
+            otherConnection.bumpAwayFrom_(connection);
+          } else {
+            connection.bumpAwayFrom_(otherConnection);
+          }
+        }
+      }
+    }
+  }
+};
+
+/**
+ * Return the parent block or null if this block is at the top level.
+ * @return {Blockly.Block} The block that holds the current block.
+ */
+Blockly.Block.prototype.getParent = function() {
+  // Look at the DOM to see if we are nested in another block.
+  return this.parentBlock_;
+};
+
+/**
+ * Return the input that connects to the specified block.
+ * @param {!Blockly.Block} block A block connected to an input on this block.
+ * @return {Blockly.Input} The input that connects to the specified block.
+ */
+Blockly.Block.prototype.getInputWithBlock = function(block) {
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    if (input.connection && input.connection.targetBlock() == block) {
+      return input;
+    }
+  }
+  return null;
+};
+
+/**
+ * Return the parent block that surrounds the current block, or null if this
+ * block has no surrounding block.  A parent block might just be the previous
+ * statement, whereas the surrounding block is an if statement, while loop, etc.
+ * @return {Blockly.Block} The block that surrounds the current block.
+ */
+Blockly.Block.prototype.getSurroundParent = function() {
+  var block = this;
+  do {
+    var prevBlock = block;
+    block = block.getParent();
+    if (!block) {
+      // Ran off the top.
+      return null;
+    }
+  } while (block.getNextBlock() == prevBlock);
+  // This block is an enclosing parent, not just a statement in a stack.
+  return block;
+};
+
+/**
+ * Return the next statement block directly connected to this block.
+ * @return {Blockly.Block} The next statement block or null.
+ */
+Blockly.Block.prototype.getNextBlock = function() {
+  return this.nextConnection && this.nextConnection.targetBlock();
+};
+
+/**
+ * Return the top-most block in this block's tree.
+ * This will return itself if this block is at the top level.
+ * @return {!Blockly.Block} The root block.
+ */
+Blockly.Block.prototype.getRootBlock = function() {
+  var rootBlock;
+  var block = this;
+  do {
+    rootBlock = block;
+    block = rootBlock.parentBlock_;
+  } while (block);
+  return rootBlock;
+};
+
+/**
+ * Find all the blocks that are directly nested inside this one.
+ * Includes value and block inputs, as well as any following statement.
+ * Excludes any connection on an output tab or any preceding statement.
+ * @return {!Array.<!Blockly.Block>} Array of blocks.
+ */
+Blockly.Block.prototype.getChildren = function() {
+  return this.childBlocks_;
+};
+
+/**
+ * Set parent of this block to be a new block or null.
+ * @param {Blockly.Block} newParent New parent block.
+ */
+Blockly.Block.prototype.setParent = function(newParent) {
+  if (newParent == this.parentBlock_) {
+    return;
+  }
+  if (this.parentBlock_) {
+    // Remove this block from the old parent's child list.
+    var children = this.parentBlock_.childBlocks_;
+    for (var child, x = 0; child = children[x]; x++) {
+      if (child == this) {
+        children.splice(x, 1);
+        break;
+      }
+    }
+
+    // Disconnect from superior blocks.
+    if (this.previousConnection && this.previousConnection.isConnected()) {
+      throw 'Still connected to previous block.';
+    }
+    if (this.outputConnection && this.outputConnection.isConnected()) {
+      throw 'Still connected to parent block.';
+    }
+    this.parentBlock_ = null;
+    // This block hasn't actually moved on-screen, so there's no need to update
+    // its connection locations.
+  } else {
+    // Remove this block from the workspace's list of top-most blocks.
+    this.workspace.removeTopBlock(this);
+  }
+
+  this.parentBlock_ = newParent;
+  if (newParent) {
+    // Add this block to the new parent's child list.
+    newParent.childBlocks_.push(this);
+  } else {
+    this.workspace.addTopBlock(this);
+  }
+};
+
+/**
+ * Find all the blocks that are directly or indirectly nested inside this one.
+ * Includes this block in the list.
+ * Includes value and block inputs, as well as any following statements.
+ * Excludes any connection on an output tab or any preceding statements.
+ * @return {!Array.<!Blockly.Block>} Flattened array of blocks.
+ */
+Blockly.Block.prototype.getDescendants = function() {
+  var blocks = [this];
+  for (var child, x = 0; child = this.childBlocks_[x]; x++) {
+    blocks.push.apply(blocks, child.getDescendants());
+  }
+  return blocks;
+};
+
+/**
+ * Get whether this block is deletable or not.
+ * @return {boolean} True if deletable.
+ */
+Blockly.Block.prototype.isDeletable = function() {
+  return this.deletable_ && !this.isShadow_ &&
+      !(this.workspace && this.workspace.options.readOnly);
+};
+
+/**
+ * Set whether this block is deletable or not.
+ * @param {boolean} deletable True if deletable.
+ */
+Blockly.Block.prototype.setDeletable = function(deletable) {
+  this.deletable_ = deletable;
+};
+
+/**
+ * Get whether this block is movable or not.
+ * @return {boolean} True if movable.
+ */
+Blockly.Block.prototype.isMovable = function() {
+  return this.movable_ && !this.isShadow_ &&
+      !(this.workspace && this.workspace.options.readOnly);
+};
+
+/**
+ * Set whether this block is movable or not.
+ * @param {boolean} movable True if movable.
+ */
+Blockly.Block.prototype.setMovable = function(movable) {
+  this.movable_ = movable;
+};
+
+/**
+ * Get whether this block is a shadow block or not.
+ * @return {boolean} True if a shadow.
+ */
+Blockly.Block.prototype.isShadow = function() {
+  return this.isShadow_;
+};
+
+/**
+ * Set whether this block is a shadow block or not.
+ * @param {boolean} shadow True if a shadow.
+ */
+Blockly.Block.prototype.setShadow = function(shadow) {
+  this.isShadow_ = shadow;
+};
+
+/**
+ * Get whether this block is editable or not.
+ * @return {boolean} True if editable.
+ */
+Blockly.Block.prototype.isEditable = function() {
+  return this.editable_ && !(this.workspace && this.workspace.options.readOnly);
+};
+
+/**
+ * Set whether this block is editable or not.
+ * @param {boolean} editable True if editable.
+ */
+Blockly.Block.prototype.setEditable = function(editable) {
+  this.editable_ = editable;
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      field.updateEditable();
+    }
+  }
+};
+
+/**
+ * Set whether the connections are hidden (not tracked in a database) or not.
+ * Recursively walk down all child blocks (except collapsed blocks).
+ * @param {boolean} hidden True if connections are hidden.
+ */
+Blockly.Block.prototype.setConnectionsHidden = function(hidden) {
+  if (!hidden && this.isCollapsed()) {
+    if (this.outputConnection) {
+      this.outputConnection.setHidden(hidden);
+    }
+    if (this.previousConnection) {
+      this.previousConnection.setHidden(hidden);
+    }
+    if (this.nextConnection) {
+      this.nextConnection.setHidden(hidden);
+      var child = this.nextConnection.targetBlock();
+      if (child) {
+        child.setConnectionsHidden(hidden);
+      }
+    }
+  } else {
+    var myConnections = this.getConnections_(true);
+    for (var i = 0, connection; connection = myConnections[i]; i++) {
+      connection.setHidden(hidden);
+      if (connection.isSuperior()) {
+        var child = connection.targetBlock();
+        if (child) {
+          child.setConnectionsHidden(hidden);
+        }
+      }
+    }
+  }
+};
+
+/**
+ * Set the URL of this block's help page.
+ * @param {string|Function} url URL string for block help, or function that
+ *     returns a URL.  Null for no help.
+ */
+Blockly.Block.prototype.setHelpUrl = function(url) {
+  this.helpUrl = url;
+};
+
+/**
+ * Change the tooltip text for a block.
+ * @param {string|!Function} newTip Text for tooltip or a parent element to
+ *     link to for its tooltip.  May be a function that returns a string.
+ */
+Blockly.Block.prototype.setTooltip = function(newTip) {
+  this.tooltip = newTip;
+};
+
+/**
+ * Get the colour of a block.
+ * @return {string} #RRGGBB string.
+ */
+Blockly.Block.prototype.getColour = function() {
+  return this.colour_;
+};
+
+/**
+ * Change the colour of a block.
+ * @param {number|string} colour HSV hue value, or #RRGGBB string.
+ */
+Blockly.Block.prototype.setColour = function(colour) {
+  var hue = parseFloat(colour);
+  if (!isNaN(hue)) {
+    this.colour_ = Blockly.hueToRgb(hue);
+  } else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) {
+    this.colour_ = colour;
+  } else {
+    throw 'Invalid colour: ' + colour;
+  }
+};
+
+// /**
+//  * Get the colour of a block.
+//  * @return {number} colourRGB: an Array [r,g,b]
+//  * Added for Blockscad.
+//  */
+// Blockly.Block.prototype.getColourHex = function() {
+//   return this.colourHex;
+// };
+
+// *
+//  * Change the colour of a block.
+//  * @param {number} colourRGB: an array containing [R,G,B] values
+//  * Added for Blockscad
+ 
+// Blockly.Block.prototype.setColourHex = function(colourHex) {
+//   this.colourHex = colourHex;
+//   // if (this.rendered) {
+//   //   this.updateColour();
+//   // }
+// };
+/**
+ * Returns the named field from a block.
+ * @param {string} name The name of the field.
+ * @return {Blockly.Field} Named field, or null if field does not exist.
+ */
+Blockly.Block.prototype.getField = function(name) {
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      if (field.name === name) {
+        return field;
+      }
+    }
+  }
+  return null;
+};
+
+// BlocksCAD
+Blockly.Block.prototype.getAllFieldValues = function() {
+  var fields = '';
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      if (field.name != undefined) {
+        fields += (field.getValue());
+        fields += ':';
+      }
+    }
+  }
+  return fields;
+}
+/**
+ * Return all variables referenced by this block.
+ * @return {!Array.<string>} List of variable names.
+ */
+Blockly.Block.prototype.getVars = function() {
+  var vars = [];
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      if (field instanceof Blockly.FieldVariable) {
+        vars.push(field.getValue());
+      }
+    }
+  }
+  return vars;
+};
+
+/**
+ * Notification that a variable is renaming.
+ * If the name matches one of this block's variables, rename it.
+ * @param {string} oldName Previous name of variable.
+ * @param {string} newName Renamed variable.
+ */
+Blockly.Block.prototype.renameVar = function(oldName, newName) {
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      if (field instanceof Blockly.FieldVariable &&
+          Blockly.Names.equals(oldName, field.getValue())) {
+        field.setValue(newName);
+      }
+    }
+  }
+};
+
+/**
+ * Returns the language-neutral value from the field of a block.
+ * @param {string} name The name of the field.
+ * @return {?string} Value from the field or null if field does not exist.
+ */
+Blockly.Block.prototype.getFieldValue = function(name) {
+  var field = this.getField(name);
+  if (field) {
+    return field.getValue();
+  }
+  return null;
+};
+
+/**
+ * Returns the language-neutral value from the field of a block.
+ * @param {string} name The name of the field.
+ * @return {?string} Value from the field or null if field does not exist.
+ * @deprecated December 2013
+ */
+Blockly.Block.prototype.getTitleValue = function(name) {
+  console.warn('Deprecated call to getTitleValue, use getFieldValue instead.');
+  return this.getFieldValue(name);
+};
+
+/**
+ * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE').
+ * @param {string} newValue Value to be the new field.
+ * @param {string} name The name of the field.
+ */
+Blockly.Block.prototype.setFieldValue = function(newValue, name) {
+  var field = this.getField(name);
+  goog.asserts.assertObject(field, 'Field "%s" not found.', name);
+  field.setValue(newValue);
+};
+
+/**
+ * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE').
+ * @param {string} newValue Value to be the new field.
+ * @param {string} name The name of the field.
+ * @deprecated December 2013
+ */
+Blockly.Block.prototype.setTitleValue = function(newValue, name) {
+  console.warn('Deprecated call to setTitleValue, use setFieldValue instead.');
+  this.setFieldValue(newValue, name);
+};
+
+/**
+ * Set whether this block can chain onto the bottom of another block.
+ * @param {boolean} newBoolean True if there can be a previous statement.
+ * @param {string|Array.<string>|null|undefined} opt_check Statement type or
+ *     list of statement types.  Null/undefined if any type could be connected.
+ */
+Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) {
+  if (newBoolean) {
+    if (opt_check === undefined) {
+      opt_check = null;
+    }
+    if (!this.previousConnection) {
+      goog.asserts.assert(!this.outputConnection,
+          'Remove output connection prior to adding previous connection.');
+      this.previousConnection =
+          this.makeConnection_(Blockly.PREVIOUS_STATEMENT);
+    }
+    this.previousConnection.setCheck(opt_check);
+  } else {
+    if (this.previousConnection) {
+      goog.asserts.assert(!this.previousConnection.isConnected(),
+          'Must disconnect previous statement before removing connection.');
+      this.previousConnection.dispose();
+      this.previousConnection = null;
+    }
+  }
+};
+
+/**
+ * Set whether another block can chain onto the bottom of this block.
+ * @param {boolean} newBoolean True if there can be a next statement.
+ * @param {string|Array.<string>|null|undefined} opt_check Statement type or
+ *     list of statement types.  Null/undefined if any type could be connected.
+ */
+Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) {
+  if (newBoolean) {
+    if (opt_check === undefined) {
+      opt_check = null;
+    }
+    if (!this.nextConnection) {
+      this.nextConnection = this.makeConnection_(Blockly.NEXT_STATEMENT);
+    }
+    this.nextConnection.setCheck(opt_check);
+  } else {
+    if (this.nextConnection) {
+      goog.asserts.assert(!this.nextConnection.isConnected(),
+          'Must disconnect next statement before removing connection.');
+      this.nextConnection.dispose();
+      this.nextConnection = null;
+    }
+  }
+};
+
+/**
+ * Set whether this block returns a value.
+ * @param {boolean} newBoolean True if there is an output.
+ * @param {string|Array.<string>|null|undefined} opt_check Returned type or list
+ *     of returned types.  Null or undefined if any type could be returned
+ *     (e.g. variable get).
+ */
+Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) {
+  if (newBoolean) {
+    if (opt_check === undefined) {
+      opt_check = null;
+    }
+    if (!this.outputConnection) {
+      goog.asserts.assert(!this.previousConnection,
+          'Remove previous connection prior to adding output connection.');
+      this.outputConnection = this.makeConnection_(Blockly.OUTPUT_VALUE);
+    }
+    this.outputConnection.setCheck(opt_check);
+  } else {
+    if (this.outputConnection) {
+      goog.asserts.assert(!this.outputConnection.isConnected(),
+          'Must disconnect output value before removing connection.');
+      this.outputConnection.dispose();
+      this.outputConnection = null;
+    }
+  }
+};
+
+/**
+ * Set whether value inputs are arranged horizontally or vertically.
+ * @param {boolean} newBoolean True if inputs are horizontal.
+ */
+Blockly.Block.prototype.setInputsInline = function(newBoolean) {
+  if (this.inputsInline != newBoolean) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this, 'inline', null, this.inputsInline, newBoolean));
+    this.inputsInline = newBoolean;
+  }
+};
+
+/**
+ * Get whether value inputs are arranged horizontally or vertically.
+ * @return {boolean} True if inputs are horizontal.
+ */
+Blockly.Block.prototype.getInputsInline = function() {
+  if (this.inputsInline != undefined) {
+    // Set explicitly.
+    return this.inputsInline;
+  }
+  // Not defined explicitly.  Figure out what would look best.
+  for (var i = 1; i < this.inputList.length; i++) {
+    if (this.inputList[i - 1].type == Blockly.DUMMY_INPUT &&
+        this.inputList[i].type == Blockly.DUMMY_INPUT) {
+      // Two dummy inputs in a row.  Don't inline them.
+      return false;
+    }
+  }
+  for (var i = 1; i < this.inputList.length; i++) {
+    if (this.inputList[i - 1].type == Blockly.INPUT_VALUE &&
+        this.inputList[i].type == Blockly.DUMMY_INPUT) {
+      // Dummy input after a value input.  Inline them.
+      return true;
+    }
+  }
+  return false;
+};
+
+/**
+ * Set whether the block is disabled or not.
+ * @param {boolean} disabled True if disabled.
+ */
+Blockly.Block.prototype.setDisabled = function(disabled) {
+  if (this.disabled != disabled) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this, 'disabled', null, this.disabled, disabled));
+    this.disabled = disabled;
+  }
+};
+
+/**
+ * Get whether the block is disabled or not due to parents.
+ * The block's own disabled property is not considered.
+ * @return {boolean} True if disabled.
+ */
+Blockly.Block.prototype.getInheritedDisabled = function() {
+  var block = this;
+  while (true) {
+    block = block.getSurroundParent();
+    if (!block) {
+      // Ran off the top.
+      return false;
+    } else if (block.disabled) {
+      return true;
+    }
+  }
+};
+
+/**
+ * Get whether the block is collapsed or not.
+ * @return {boolean} True if collapsed.
+ */
+Blockly.Block.prototype.isCollapsed = function() {
+  return this.collapsed_;
+};
+
+/**
+ * Set whether the block is collapsed or not.
+ * @param {boolean} collapsed True if collapsed.
+ */
+Blockly.Block.prototype.setCollapsed = function(collapsed) {
+  if (this.collapsed_ != collapsed) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this, 'collapsed', null, this.collapsed_, collapsed));
+    this.collapsed_ = collapsed;
+  }
+};
+
+/**
+ * Create a human-readable text representation of this block and any children.
+ * @param {number=} opt_maxLength Truncate the string to this length.
+ * @return {string} Text of block.
+ */
+Blockly.Block.prototype.toString = function(opt_maxLength) {
+  var text = [];
+  if (this.collapsed_) {
+    text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_);
+  } else {
+    for (var i = 0, input; input = this.inputList[i]; i++) {
+      for (var j = 0, field; field = input.fieldRow[j]; j++) {
+        text.push(field.getText());
+      }
+      if (input.connection) {
+        var child = input.connection.targetBlock();
+        if (child) {
+          text.push(child.toString());
+        } else {
+          text.push('?');
+        }
+      }
+    }
+  }
+  text = goog.string.trim(text.join(' ')) || '???';
+  if (opt_maxLength) {
+    // TODO: Improve truncation so that text from this block is given priority.
+    // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...".
+    // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
+    text = goog.string.truncate(text, opt_maxLength);
+  }
+  return text;
+};
+
+/**
+ * Shortcut for appending a value input row.
+ * @param {string} name Language-neutral identifier which may used to find this
+ *     input again.  Should be unique to this block.
+ * @return {!Blockly.Input} The input object created.
+ */
+Blockly.Block.prototype.appendValueInput = function(name) {
+  return this.appendInput_(Blockly.INPUT_VALUE, name);
+};
+
+/**
+ * Shortcut for appending a statement input row.
+ * @param {string} name Language-neutral identifier which may used to find this
+ *     input again.  Should be unique to this block.
+ * @return {!Blockly.Input} The input object created.
+ */
+Blockly.Block.prototype.appendStatementInput = function(name) {
+  return this.appendInput_(Blockly.NEXT_STATEMENT, name);
+};
+
+/**
+ * Shortcut for appending a dummy input row.
+ * @param {string=} opt_name Language-neutral identifier which may used to find
+ *     this input again.  Should be unique to this block.
+ * @return {!Blockly.Input} The input object created.
+ */
+Blockly.Block.prototype.appendDummyInput = function(opt_name) {
+  return this.appendInput_(Blockly.DUMMY_INPUT, opt_name || '');
+};
+
+/**
+ * Initialize this block using a cross-platform, internationalization-friendly
+ * JSON description.
+ * @param {!Object} json Structured data describing the block.
+ */
+Blockly.Block.prototype.jsonInit = function(json) {
+  // Validate inputs.
+  goog.asserts.assert(json['output'] == undefined ||
+      json['previousStatement'] == undefined,
+      'Must not have both an output and a previousStatement.');
+
+  // Set basic properties of block.
+  if (json['colour'] !== undefined) {
+    this.setColour(json['colour']);
+  }
+
+  // Interpolate the message blocks.
+  var i = 0;
+  while (json['message' + i] !== undefined) {
+    this.interpolate_(json['message' + i], json['args' + i] || [],
+        json['lastDummyAlign' + i]);
+    i++;
+  }
+
+  if (json['inputsInline'] !== undefined) {
+    this.setInputsInline(json['inputsInline']);
+  }
+  // Set output and previous/next connections.
+  if (json['output'] !== undefined) {
+    this.setOutput(true, json['output']);
+  }
+  if (json['previousStatement'] !== undefined) {
+    this.setPreviousStatement(true, json['previousStatement']);
+  }
+  if (json['nextStatement'] !== undefined) {
+    this.setNextStatement(true, json['nextStatement']);
+  }
+  if (json['tooltip'] !== undefined) {
+    this.setTooltip(json['tooltip']);
+  }
+  if (json['helpUrl'] !== undefined) {
+    this.setHelpUrl(json['helpUrl']);
+  }
+};
+
+/**
+ * Interpolate a message description onto the block.
+ * @param {string} message Text contains interpolation tokens (%1, %2, ...)
+ *     that match with fields or inputs defined in the args array.
+ * @param {!Array} args Array of arguments to be interpolated.
+ * @param {=string} lastDummyAlign If a dummy input is added at the end,
+ *     how should it be aligned?
+ * @private
+ */
+Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
+  var tokens = Blockly.utils.tokenizeInterpolation(message);
+  // Interpolate the arguments.  Build a list of elements.
+  var indexDup = [];
+  var indexCount = 0;
+  var elements = [];
+  for (var i = 0; i < tokens.length; i++) {
+    var token = tokens[i];
+    if (typeof token == 'number') {
+      goog.asserts.assert(token > 0 && token <= args.length,
+          'Message index "%s" out of range.', token);
+      goog.asserts.assert(!indexDup[token],
+          'Message index "%s" duplicated.', token);
+      indexDup[token] = true;
+      indexCount++;
+      elements.push(args[token - 1]);
+    } else {
+      token = token.trim();
+      if (token) {
+        elements.push(token);
+      }
+    }
+  }
+  goog.asserts.assert(indexCount == args.length,
+      'Message does not reference all %s arg(s).', args.length);
+  // Add last dummy input if needed.
+  if (elements.length && (typeof elements[elements.length - 1] == 'string' ||
+      elements[elements.length - 1]['type'].indexOf('field_') == 0)) {
+    var dummyInput = {type: 'input_dummy'};
+    if (lastDummyAlign) {
+      dummyInput['align'] = lastDummyAlign;
+    }
+    elements.push(dummyInput);
+  }
+  // Lookup of alignment constants.
+  var alignmentLookup = {
+    'LEFT': Blockly.ALIGN_LEFT,
+    'RIGHT': Blockly.ALIGN_RIGHT,
+    'CENTRE': Blockly.ALIGN_CENTRE
+  };
+  // Populate block with inputs and fields.
+  var fieldStack = [];
+  for (var i = 0; i < elements.length; i++) {
+    var element = elements[i];
+    if (typeof element == 'string') {
+      fieldStack.push([element, undefined]);
+    } else {
+      var field = null;
+      var input = null;
+      do {
+        var altRepeat = false;
+        if (typeof element == 'string') {
+          field = new Blockly.FieldLabel(element);
+        } else {
+          switch (element['type']) {
+            case 'input_value':
+              input = this.appendValueInput(element['name']);
+              break;
+            case 'input_statement':
+              input = this.appendStatementInput(element['name']);
+              break;
+            case 'input_dummy':
+              input = this.appendDummyInput(element['name']);
+              break;
+            case 'field_label':
+              field = new Blockly.FieldLabel(element['text'], element['class']);
+              break;
+            case 'field_input':
+              field = new Blockly.FieldTextInput(element['text']);
+              if (typeof element['spellcheck'] == 'boolean') {
+                field.setSpellcheck(element['spellcheck']);
+              }
+              break;
+            case 'field_angle':
+              field = new Blockly.FieldAngle(element['angle']);
+              break;
+            case 'field_checkbox':
+              field = new Blockly.FieldCheckbox(
+                  element['checked'] ? 'TRUE' : 'FALSE');
+              break;
+            case 'field_colour':
+              field = new Blockly.FieldColour(element['colour']);
+              break;
+            case 'field_variable':
+              field = new Blockly.FieldVariable(element['variable']);
+              break;
+            case 'field_dropdown':
+              field = new Blockly.FieldDropdown(element['options']);
+              break;
+            case 'field_image':
+              field = new Blockly.FieldImage(element['src'],
+                  element['width'], element['height'], element['alt']);
+              break;
+            case 'field_number':
+              field = new Blockly.FieldNumber(element['value'],
+                  element['min'], element['max'], element['precision']);
+              break;
+            case 'field_date':
+              if (Blockly.FieldDate) {
+                field = new Blockly.FieldDate(element['date']);
+                break;
+              }
+              // Fall through if FieldDate is not compiled in.
+            default:
+              // Unknown field.
+              if (element['alt']) {
+                element = element['alt'];
+                altRepeat = true;
+              }
+          }
+        }
+      } while (altRepeat);
+      if (field) {
+        fieldStack.push([field, element['name']]);
+      } else if (input) {
+        if (element['check']) {
+          input.setCheck(element['check']);
+        }
+        if (element['align']) {
+          input.setAlign(alignmentLookup[element['align']]);
+        }
+        for (var j = 0; j < fieldStack.length; j++) {
+          input.appendField(fieldStack[j][0], fieldStack[j][1]);
+        }
+        fieldStack.length = 0;
+      }
+    }
+  }
+};
+
+/**
+ * Add a value input, statement input or local variable to this block.
+ * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or
+ *     Blockly.DUMMY_INPUT.
+ * @param {string} name Language-neutral identifier which may used to find this
+ *     input again.  Should be unique to this block.
+ * @return {!Blockly.Input} The input object created.
+ * @private
+ */
+Blockly.Block.prototype.appendInput_ = function(type, name) {
+  var connection = null;
+  if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) {
+    connection = this.makeConnection_(type);
+  }
+  var input = new Blockly.Input(type, name, this, connection);
+  // Append input to list.
+  this.inputList.push(input);
+  return input;
+};
+
+/**
+ * Move a named input to a different location on this block.
+ * @param {string} name The name of the input to move.
+ * @param {?string} refName Name of input that should be after the moved input,
+ *   or null to be the input at the end.
+ */
+Blockly.Block.prototype.moveInputBefore = function(name, refName) {
+  if (name == refName) {
+    return;
+  }
+  // Find both inputs.
+  var inputIndex = -1;
+  var refIndex = refName ? -1 : this.inputList.length;
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    if (input.name == name) {
+      inputIndex = i;
+      if (refIndex != -1) {
+        break;
+      }
+    } else if (refName && input.name == refName) {
+      refIndex = i;
+      if (inputIndex != -1) {
+        break;
+      }
+    }
+  }
+  goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name);
+  goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.',
+                      refName);
+  this.moveNumberedInputBefore(inputIndex, refIndex);
+};
+
+/**
+ * Move a numbered input to a different location on this block.
+ * @param {number} inputIndex Index of the input to move.
+ * @param {number} refIndex Index of input that should be after the moved input.
+ */
+Blockly.Block.prototype.moveNumberedInputBefore = function(
+    inputIndex, refIndex) {
+  // Validate arguments.
+  goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.');
+  goog.asserts.assert(inputIndex < this.inputList.length,
+                      'Input index ' + inputIndex + ' out of bounds.');
+  goog.asserts.assert(refIndex <= this.inputList.length,
+                      'Reference input ' + refIndex + ' out of bounds.');
+  // Remove input.
+  var input = this.inputList[inputIndex];
+  this.inputList.splice(inputIndex, 1);
+  if (inputIndex < refIndex) {
+    refIndex--;
+  }
+  // Reinsert input.
+  this.inputList.splice(refIndex, 0, input);
+};
+
+/**
+ * Remove an input from this block.
+ * @param {string} name The name of the input.
+ * @param {boolean=} opt_quiet True to prevent error if input is not present.
+ * @throws {goog.asserts.AssertionError} if the input is not present and
+ *     opt_quiet is not true.
+ */
+Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    if (input.name == name) {
+      if (input.connection && input.connection.isConnected()) {
+        input.connection.setShadowDom(null);
+        var block = input.connection.targetBlock();
+        if (block.isShadow()) {
+          // Destroy any attached shadow block.
+          block.dispose();
+        } else {
+          // Disconnect any attached normal block.
+          block.unplug();
+        }
+      }
+      input.dispose();
+      this.inputList.splice(i, 1);
+      return;
+    }
+  }
+  if (!opt_quiet) {
+    goog.asserts.fail('Input "%s" not found.', name);
+  }
+};
+
+/**
+ * Fetches the named input object.
+ * @param {string} name The name of the input.
+ * @return {Blockly.Input} The input object, or null if input does not exist.
+ */
+Blockly.Block.prototype.getInput = function(name) {
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    if (input.name == name) {
+      return input;
+    }
+  }
+  // This input does not exist.
+  return null;
+};
+
+/**
+ * Fetches the block attached to the named input.
+ * @param {string} name The name of the input.
+ * @return {Blockly.Block} The attached value block, or null if the input is
+ *     either disconnected or if the input does not exist.
+ */
+Blockly.Block.prototype.getInputTargetBlock = function(name) {
+  var input = this.getInput(name);
+  return input && input.connection && input.connection.targetBlock();
+};
+
+/**
+ * Returns the comment on this block (or '' if none).
+ * @return {string} Block's comment.
+ */
+Blockly.Block.prototype.getCommentText = function() {
+  return this.comment || '';
+};
+
+/**
+ * Set this block's comment text.
+ * @param {?string} text The text, or null to delete.
+ */
+Blockly.Block.prototype.setCommentText = function(text) {
+  if (this.comment != text) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this, 'comment', null, this.comment, text || ''));
+    this.comment = text;
+  }
+};
+
+/**
+ * Set this block's warning text.
+ * @param {?string} text The text, or null to delete.
+ */
+Blockly.Block.prototype.setWarningText = function(text) {
+  // NOP.
+};
+
+/**
+ * Give this block a mutator dialog.
+ * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
+ */
+Blockly.Block.prototype.setMutator = function(mutator) {
+  // NOP.
+};
+
+/**
+ * Give this block a mutatorPlus button for BlocksCAD.
+ * @param {Blockly.MutatorPlus}
+ *            mutatorPlus A mutatorPlus instance or null to remove.
+ */
+Blockly.Block.prototype.setMutatorPlus = function(mutatorPlus) {
+  // NOP
+};
+
+/**
+ * Give this block a mutatorMinus button for BlocksCAD.
+ * @param {Blockly.MutatorMinus}
+ *            mutatorMinus A mutatorMinus instance or null to remove.
+ */
+Blockly.Block.prototype.setMutatorMinus = function(mutatorMinus) {
+  // NOP.
+};
+/**
+ * Return the coordinates of the top-left corner of this block relative to the
+ * drawing surface's origin (0,0).
+ * @return {!goog.math.Coordinate} Object with .x and .y properties.
+ */
+Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
+  return this.xy_;
+};
+
+/**
+ * Move a block by a relative offset.
+ * @param {number} dx Horizontal offset.
+ * @param {number} dy Vertical offset.
+ */
+Blockly.Block.prototype.moveBy = function(dx, dy) {
+  goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
+  var event = new Blockly.Events.Move(this);
+  this.xy_.translate(dx, dy);
+  event.recordNew();
+  Blockly.Events.fire(event);
+};
+
+/**
+ * Create a connection of the specified type.
+ * @param {number} type The type of the connection to create.
+ * @return {!Blockly.Connection} A new connection of the specified type.
+ * @private
+ */
+Blockly.Block.prototype.makeConnection_ = function(type) {
+  return new Blockly.Connection(this, type);
+};
+
+
+/**
+ * For BlocksCAD
+ * return a list of all collapsed parents of a block
+ */
+Blockly.Block.prototype.collapsedParents = function() {
+  var block = this;
+  var collapsedParents = [];
+  do {
+    if (block.isCollapsed())
+      collapsedParents.push(block);
+    block = block.parentBlock_;
+  } while (block);
+  if (collapsedParents.length == 0) return false;
+  return collapsedParents; 
+}
+
+/**
+ * For BlocksCAD
+ * true if any parent of this block is disabled
+ */
+Blockly.Block.prototype.hasDisabledParent = function() {
+  var block = this;
+  do {
+    if (block.disabled) return true;
+    block = block.parentBlock_;
+  } while (block);
+  return false;
+}
+
+
+/* BlocksCAD still uses interpolate message, so KEEP IT!!!
+/**
+ * Interpolate a message string, creating fields and inputs.
+ * @param {string} msg The message string to parse.  %1, %2, etc. are symbols
+ *     for value inputs or for Fields, such as an instance of
+ *     Blockly.FieldDropdown, which would be placed as a field in either the
+ *     following value input or a dummy input.  The newline character forces
+ *     the creation of an unnamed dummy input if any fields need placement.
+ *     Note that '%10' would be interpreted as a reference to the tenth
+ *     argument.  To show the first argument followed by a zero, use '%1 0'.
+ *     (Spaces around tokens are stripped.)  To display a percentage sign
+ *     followed by a number (e.g., "%123"), put that text in a
+ *     Blockly.FieldLabel (as described below).
+ * @param {!Array.<?string|number|Array.<string>|Blockly.Field>|number} var_args
+ *     A series of tuples that each specify the value inputs to create.  Each
+ *     tuple has at least two elements.  The first is its name; the second is
+ *     its type, which can be any of:
+ *     - A string (such as 'Number'), denoting the one type allowed in the
+ *       corresponding socket.
+ *     - An array of strings (such as ['Number', 'List']), denoting the
+ *       different types allowed in the corresponding socket.
+ *     - null, denoting that any type is allowed in the corresponding socket.
+ *     - Blockly.Field, in which case that field instance, such as an
+ *       instance of Blockly.FieldDropdown, appears (instead of a socket).
+ *     If the type is any of the first three options (which are legal arguments
+ *     to setCheck()), there should be a third element in the tuple, giving its
+ *     alignment.
+ *     The final parameter is not a tuple, but just an alignment for any
+ *     trailing dummy inputs.  This last parameter is mandatory; there may be
+ *     any number of tuples (though the number of tuples must match the symbols
+ *     in msg).
+ */
+Blockly.Block.prototype.interpolateMsg = function(msg, var_args) {
+  /**
+   * Add a field to this input.
+   * @this !Blockly.Input
+   * @param {Blockly.Field|Array.<string|Blockly.Field>} field
+   *     This is either a Field or a tuple of a name and a Field.
+   */
+  function addFieldToInput(field) {
+    if (field instanceof Blockly.Field) {
+      this.appendField(field);
+    } else {
+      goog.asserts.assert(goog.isArray(field));
+      this.appendField(field[1], field[0]);
+    }
+  }
+
+  // Validate the msg at the start and the dummy alignment at the end,
+  // and remove the latter.
+  // console.log("in interpolate: here is msg:",msg);
+  goog.asserts.assertString(msg);
+  var dummyAlign = arguments[arguments.length - 1];
+  goog.asserts.assert(
+      dummyAlign === Blockly.ALIGN_LEFT ||
+      dummyAlign === Blockly.ALIGN_CENTRE ||
+      dummyAlign === Blockly.ALIGN_RIGHT,
+      'Illegal final argument "%d" is not an alignment.', dummyAlign);
+  arguments.length = arguments.length - 1;
+
+  var tokens = msg.split(this.interpolateMsg.SPLIT_REGEX_);
+  var fields = [];
+  for (var i = 0; i < tokens.length; i += 2) {
+    var text = goog.string.trim(tokens[i]);
+    var input = undefined;
+    if (text) {
+      fields.push(new Blockly.FieldLabel(text));
+    }
+    var symbol = tokens[i + 1];
+    if (symbol && symbol.charAt(0) == '%') {
+      // Numeric field.
+      var number = parseInt(symbol.substring(1), 10);
+      var tuple = arguments[number];
+      goog.asserts.assertArray(tuple,
+          'Message symbol "%s" is out of range.', symbol);
+      goog.asserts.assertArray(tuple,
+          'Argument "%s" is not a tuple.', symbol);
+      if (tuple[1] instanceof Blockly.Field) {
+        fields.push([tuple[0], tuple[1]]);
+      } else {
+        input = this.appendValueInput(tuple[0])
+            .setCheck(tuple[1])
+            .setAlign(tuple[2]);
+      }
+      arguments[number] = null;  // Inputs may not be reused.
+    } else if (symbol == '\n' && fields.length) {
+      // Create a dummy input.
+      input = this.appendDummyInput();
+    }
+    // If we just added an input, hang any pending fields on it.
+    if (input && fields.length) {
+      fields.forEach(addFieldToInput, input);
+      fields = [];
+    }
+  }
+  // If any fields remain, create a trailing dummy input.
+  if (fields.length) {
+    var input = this.appendDummyInput()
+        .setAlign(dummyAlign);
+    fields.forEach(addFieldToInput, input);
+  }
+
+  // Verify that all inputs were used.
+  for (var i = 1; i < arguments.length - 1; i++) {
+    goog.asserts.assert(arguments[i] === null,
+        'Input "%%s" not used in message: "%s"', i, msg);
+  }
+  // Make the inputs inline unless there is only one input and
+  // no text follows it.
+  this.setInputsInline(!msg.match(this.interpolateMsg.INLINE_REGEX_));
+};
+
+Blockly.Block.prototype.interpolateMsg.SPLIT_REGEX_ = /(%\d+|\n)/;
+Blockly.Block.prototype.interpolateMsg.INLINE_REGEX_ = /%1\s*$/;

+ 969 - 0
blockly/core/block_render_svg.js

@@ -0,0 +1,969 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2016 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 Methods for graphically rendering a block as SVG.
+ * @author fenichel@google.com (Rachel Fenichel)
+ */
+
+'use strict';
+
+goog.provide('Blockly.BlockSvg.render');
+
+goog.require('Blockly.BlockSvg');
+
+
+// UI constants for rendering blocks.
+/**
+ * Horizontal space between elements.
+ * @const
+ */
+Blockly.BlockSvg.SEP_SPACE_X = 10;
+/**
+ * Vertical space between elements.
+ * @const
+ */
+Blockly.BlockSvg.SEP_SPACE_Y = 10;
+/**
+ * Vertical padding around inline elements.
+ * @const
+ */
+Blockly.BlockSvg.INLINE_PADDING_Y = 5;
+/**
+ * Minimum height of a block.
+ * @const
+ */
+Blockly.BlockSvg.MIN_BLOCK_Y = 25;
+/**
+ * Height of horizontal puzzle tab.
+ * @const
+ */
+Blockly.BlockSvg.TAB_HEIGHT = 20;
+/**
+ * Width of horizontal puzzle tab.
+ * @const
+ */
+Blockly.BlockSvg.TAB_WIDTH = 8;
+/**
+ * Width of vertical tab (inc left margin).
+ * @const
+ */
+Blockly.BlockSvg.NOTCH_WIDTH = 30;
+/**
+ * Rounded corner radius.
+ * @const
+ */
+Blockly.BlockSvg.CORNER_RADIUS = 8;
+/**
+ * Do blocks with no previous or output connections have a 'hat' on top?
+ * @const
+ */
+Blockly.BlockSvg.START_HAT = false;
+/**
+ * Height of the top hat.
+ * @const
+ */
+Blockly.BlockSvg.START_HAT_HEIGHT = 15;
+/**
+ * Path of the top hat's curve.
+ * @const
+ */
+Blockly.BlockSvg.START_HAT_PATH = 'c 30,-' +
+    Blockly.BlockSvg.START_HAT_HEIGHT + ' 70,-' +
+    Blockly.BlockSvg.START_HAT_HEIGHT + ' 100,0';
+/**
+ * Path of the top hat's curve's highlight in LTR.
+ * @const
+ */
+Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR =
+    'c 17.8,-9.2 45.3,-14.9 75,-8.7 M 100.5,0.5';
+/**
+ * Path of the top hat's curve's highlight in RTL.
+ * @const
+ */
+Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL =
+    'm 25,-8.7 c 29.7,-6.2 57.2,-0.5 75,8.7';
+/**
+ * Distance from shape edge to intersect with a curved corner at 45 degrees.
+ * Applies to highlighting on around the inside of a curve.
+ * @const
+ */
+Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) *
+    (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + 0.5;
+/**
+ * Distance from shape edge to intersect with a curved corner at 45 degrees.
+ * Applies to highlighting on around the outside of a curve.
+ * @const
+ */
+Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) *
+    (Blockly.BlockSvg.CORNER_RADIUS + 0.5) - 0.5;
+/**
+ * SVG path for drawing next/previous notch from left to right.
+ * @const
+ */
+Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 6,4 3,0 6,-4';
+/**
+ * SVG path for drawing next/previous notch from left to right with
+ * highlighting.
+ * @const
+ */
+Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT = 'l 6,4 3,0 6,-4';
+/**
+ * SVG path for drawing next/previous notch from right to left.
+ * @const
+ */
+Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -6,4 -3,0 -6,-4';
+/**
+ * SVG path for drawing jagged teeth at the end of collapsed blocks.
+ * @const
+ */
+Blockly.BlockSvg.JAGGED_TEETH = 'l 8,0 0,4 8,4 -16,8 8,4';
+/**
+ * Height of SVG path for jagged teeth at the end of collapsed blocks.
+ * @const
+ */
+Blockly.BlockSvg.JAGGED_TEETH_HEIGHT = 20;
+/**
+ * Width of SVG path for jagged teeth at the end of collapsed blocks.
+ * @const
+ */
+Blockly.BlockSvg.JAGGED_TEETH_WIDTH = 15;
+/**
+ * SVG path for drawing a horizontal puzzle tab from top to bottom.
+ * @const
+ */
+Blockly.BlockSvg.TAB_PATH_DOWN = 'v 5 c 0,10 -' + Blockly.BlockSvg.TAB_WIDTH +
+    ',-8 -' + Blockly.BlockSvg.TAB_WIDTH + ',7.5 s ' +
+    Blockly.BlockSvg.TAB_WIDTH + ',-2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',7.5';
+/**
+ * SVG path for drawing a horizontal puzzle tab from top to bottom with
+ * highlighting from the upper-right.
+ * @const
+ */
+Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL = 'v 6.5 m -' +
+    (Blockly.BlockSvg.TAB_WIDTH * 0.97) + ',3 q -' +
+    (Blockly.BlockSvg.TAB_WIDTH * 0.05) + ',10 ' +
+    (Blockly.BlockSvg.TAB_WIDTH * 0.3) + ',9.5 m ' +
+    (Blockly.BlockSvg.TAB_WIDTH * 0.67) + ',-1.9 v 1.4';
+
+/**
+ * SVG start point for drawing the top-left corner.
+ * @const
+ */
+Blockly.BlockSvg.TOP_LEFT_CORNER_START =
+    'm 0,' + Blockly.BlockSvg.CORNER_RADIUS;
+/**
+ * SVG start point for drawing the top-left corner's highlight in RTL.
+ * @const
+ */
+Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL =
+    'm ' + Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
+    Blockly.BlockSvg.DISTANCE_45_INSIDE;
+/**
+ * SVG start point for drawing the top-left corner's highlight in LTR.
+ * @const
+ */
+Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR =
+    'm 0.5,' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5);
+/**
+ * SVG path for drawing the rounded top-left corner.
+ * @const
+ */
+Blockly.BlockSvg.TOP_LEFT_CORNER =
+    'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
+    Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
+    Blockly.BlockSvg.CORNER_RADIUS + ',0';
+/**
+ * SVG path for drawing the highlight on the rounded top-left corner.
+ * @const
+ */
+Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT =
+    'A ' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' +
+    (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' +
+    Blockly.BlockSvg.CORNER_RADIUS + ',0.5';
+/**
+ * SVG path for drawing the top-left corner of a statement input.
+ * Includes the top notch, a horizontal space, and the rounded inside corner.
+ * @const
+ */
+Blockly.BlockSvg.INNER_TOP_LEFT_CORNER =
+    Blockly.BlockSvg.NOTCH_PATH_RIGHT + ' h -' +
+    (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS) +
+    ' a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
+    Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' +
+    Blockly.BlockSvg.CORNER_RADIUS + ',' +
+    Blockly.BlockSvg.CORNER_RADIUS;
+/**
+ * SVG path for drawing the bottom-left corner of a statement input.
+ * Includes the rounded inside corner.
+ * @const
+ */
+Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER =
+    'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
+    Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
+    Blockly.BlockSvg.CORNER_RADIUS + ',' +
+    Blockly.BlockSvg.CORNER_RADIUS;
+/**
+ * SVG path for drawing highlight on the top-left corner of a statement
+ * input in RTL.
+ * @const
+ */
+Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL =
+    'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
+    Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
+    (-Blockly.BlockSvg.DISTANCE_45_OUTSIDE - 0.5) + ',' +
+    (Blockly.BlockSvg.CORNER_RADIUS -
+    Blockly.BlockSvg.DISTANCE_45_OUTSIDE);
+/**
+ * SVG path for drawing highlight on the bottom-left corner of a statement
+ * input in RTL.
+ * @const
+ */
+Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL =
+    'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
+    (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' +
+    (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
+    (Blockly.BlockSvg.CORNER_RADIUS + 0.5);
+/**
+ * SVG path for drawing highlight on the bottom-left corner of a statement
+ * input in LTR.
+ * @const
+ */
+Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR =
+    'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' +
+    (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' +
+    (Blockly.BlockSvg.CORNER_RADIUS -
+    Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
+    (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5);
+
+/**
+ * Render the block.
+ * Lays out and reflows a block based on its contents and settings.
+ * @param {boolean=} opt_bubble If false, just render this block.
+ *   If true, also render block's parent, grandparent, etc.  Defaults to true.
+ */
+Blockly.BlockSvg.prototype.render = function(opt_bubble) {
+  Blockly.Field.startCache();
+  this.rendered = true;
+
+  var cursorX = Blockly.BlockSvg.SEP_SPACE_X;
+  if (this.RTL) {
+    cursorX = -cursorX;
+  }
+  // Move the icons into position.
+  var icons = this.getIcons();
+  for (var i = 0; i < icons.length; i++) {
+    cursorX = icons[i].renderIcon(cursorX);
+  }
+  cursorX += this.RTL ?
+      Blockly.BlockSvg.SEP_SPACE_X : -Blockly.BlockSvg.SEP_SPACE_X;
+  // If there are no icons, cursorX will be 0, otherwise it will be the
+  // width that the first label needs to move over by.
+
+  var inputRows = this.renderCompute_(cursorX);
+  this.renderDraw_(cursorX, inputRows);
+  this.renderMoveConnections_();
+
+  if (opt_bubble !== false) {
+    // Render all blocks above this one (propagate a reflow).
+    var parentBlock = this.getParent();
+    if (parentBlock) {
+      parentBlock.render(true);
+    } else {
+      // Top-most block.  Fire an event to allow scrollbars to resize.
+      Blockly.resizeSvgContents(this.workspace);
+    }
+  }
+  Blockly.Field.stopCache();
+};
+
+/**
+ * Render a list of fields starting at the specified location.
+ * @param {!Array.<!Blockly.Field>} fieldList List of fields.
+ * @param {number} cursorX X-coordinate to start the fields.
+ * @param {number} cursorY Y-coordinate to start the fields.
+ * @return {number} X-coordinate of the end of the field row (plus a gap).
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderFields_ =
+    function(fieldList, cursorX, cursorY) {
+  /* eslint-disable indent */
+  cursorY += Blockly.BlockSvg.INLINE_PADDING_Y;
+  if (this.RTL) {
+    cursorX = -cursorX;
+  }
+  for (var t = 0, field; field = fieldList[t]; t++) {
+    var root = field.getSvgRoot();
+    if (!root) {
+      continue;
+    }
+    if (this.RTL) {
+      cursorX -= field.renderSep + field.renderWidth;
+      root.setAttribute('transform',
+          'translate(' + cursorX + ',' + cursorY + ')');
+      if (field.renderWidth) {
+        cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
+      }
+    } else {
+      root.setAttribute('transform',
+          'translate(' + (cursorX + field.renderSep) + ',' + cursorY + ')');
+      if (field.renderWidth) {
+        cursorX += field.renderSep + field.renderWidth +
+            Blockly.BlockSvg.SEP_SPACE_X;
+      }
+    }
+  }
+  return this.RTL ? -cursorX : cursorX;
+};  /* eslint-enable indent */
+
+/**
+ * Computes the height and widths for each row and field.
+ * @param {number} iconWidth Offset of first row due to icons.
+ * @return {!Array.<!Array.<!Object>>} 2D array of objects, each containing
+ *     position information.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) {
+  var inputList = this.inputList;
+  var inputRows = [];
+  inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2;
+  if (this.previousConnection || this.nextConnection) {
+    inputRows.rightEdge = Math.max(inputRows.rightEdge,
+        Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X);
+  }
+  var fieldValueWidth = 0;  // Width of longest external value field.
+  var fieldStatementWidth = 0;  // Width of longest statement field.
+  var hasValue = false;
+  var hasStatement = false;
+  var hasDummy = false;
+  var lastType = undefined;
+  var isInline = this.getInputsInline() && !this.isCollapsed();
+  for (var i = 0, input; input = inputList[i]; i++) {
+    if (!input.isVisible()) {
+      continue;
+    }
+    var row;
+    if (!isInline || !lastType ||
+        lastType == Blockly.NEXT_STATEMENT ||
+        input.type == Blockly.NEXT_STATEMENT) {
+      // Create new row.
+      lastType = input.type;
+      row = [];
+      if (isInline && input.type != Blockly.NEXT_STATEMENT) {
+        row.type = Blockly.BlockSvg.INLINE;
+      } else {
+        row.type = input.type;
+      }
+      row.height = 0;
+      inputRows.push(row);
+    } else {
+      row = inputRows[inputRows.length - 1];
+    }
+    row.push(input);
+
+    // Compute minimum input size.
+    input.renderHeight = Blockly.BlockSvg.MIN_BLOCK_Y;
+    // The width is currently only needed for inline value inputs.
+    if (isInline && input.type == Blockly.INPUT_VALUE) {
+      input.renderWidth = Blockly.BlockSvg.TAB_WIDTH +
+          Blockly.BlockSvg.SEP_SPACE_X * 1.25;
+    } else {
+      input.renderWidth = 0;
+    }
+    // Expand input size if there is a connection.
+    if (input.connection && input.connection.isConnected()) {
+      var linkedBlock = input.connection.targetBlock();
+      var bBox = linkedBlock.getHeightWidth();
+      input.renderHeight = Math.max(input.renderHeight, bBox.height);
+      input.renderWidth = Math.max(input.renderWidth, bBox.width);
+    }
+    // Blocks have a one pixel shadow that should sometimes overhang.
+    if (!isInline && i == inputList.length - 1) {
+      // Last value input should overhang.
+      input.renderHeight--;
+    } else if (!isInline && input.type == Blockly.INPUT_VALUE &&
+        inputList[i + 1] && inputList[i + 1].type == Blockly.NEXT_STATEMENT) {
+      // Value input above statement input should overhang.
+      input.renderHeight--;
+    }
+
+    row.height = Math.max(row.height, input.renderHeight);
+    input.fieldWidth = 0;
+    if (inputRows.length == 1) {
+      // The first row gets shifted to accommodate any icons.
+      input.fieldWidth += this.RTL ? -iconWidth : iconWidth;
+    }
+    var previousFieldEditable = false;
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      if (j != 0) {
+        input.fieldWidth += Blockly.BlockSvg.SEP_SPACE_X;
+      }
+      // Get the dimensions of the field.
+      var fieldSize = field.getSize();
+      field.renderWidth = fieldSize.width;
+      field.renderSep = (previousFieldEditable && field.EDITABLE) ?
+          Blockly.BlockSvg.SEP_SPACE_X : 0;
+      input.fieldWidth += field.renderWidth + field.renderSep;
+      row.height = Math.max(row.height, fieldSize.height);
+      previousFieldEditable = field.EDITABLE;
+    }
+
+    if (row.type != Blockly.BlockSvg.INLINE) {
+      if (row.type == Blockly.NEXT_STATEMENT) {
+        hasStatement = true;
+        fieldStatementWidth = Math.max(fieldStatementWidth, input.fieldWidth);
+      } else {
+        if (row.type == Blockly.INPUT_VALUE) {
+          hasValue = true;
+        } else if (row.type == Blockly.DUMMY_INPUT) {
+          hasDummy = true;
+        }
+        fieldValueWidth = Math.max(fieldValueWidth, input.fieldWidth);
+      }
+    }
+  }
+
+  // Make inline rows a bit thicker in order to enclose the values.
+  for (var y = 0, row; row = inputRows[y]; y++) {
+    row.thicker = false;
+    if (row.type == Blockly.BlockSvg.INLINE) {
+      for (var z = 0, input; input = row[z]; z++) {
+        if (input.type == Blockly.INPUT_VALUE) {
+          row.height += 2 * Blockly.BlockSvg.INLINE_PADDING_Y;
+          row.thicker = true;
+          break;
+        }
+      }
+    }
+  }
+
+  // Compute the statement edge.
+  // This is the width of a block where statements are nested.
+  inputRows.statementEdge = 2 * Blockly.BlockSvg.SEP_SPACE_X +
+      fieldStatementWidth;
+  // Compute the preferred right edge.  Inline blocks may extend beyond.
+  // This is the width of the block where external inputs connect.
+  if (hasStatement) {
+    inputRows.rightEdge = Math.max(inputRows.rightEdge,
+        inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH);
+  }
+  if (hasValue) {
+    inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
+        Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH);
+  } else if (hasDummy) {
+    inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
+        Blockly.BlockSvg.SEP_SPACE_X * 2);
+  }
+
+  inputRows.hasValue = hasValue;
+  inputRows.hasStatement = hasStatement;
+  inputRows.hasDummy = hasDummy;
+  return inputRows;
+};
+
+
+/**
+ * Draw the path of the block.
+ * Move the fields to the correct locations.
+ * @param {number} iconWidth Offset of first row due to icons.
+ * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
+ *     containing position information.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) {
+  this.startHat_ = false;
+  // Reset the height to zero and let the rendering process add in
+  // portions of the block height as it goes. (e.g. hats, inputs, etc.)
+  this.height = 0;
+  // Should the top and bottom left corners be rounded or square?
+  if (this.outputConnection) {
+    this.squareTopLeftCorner_ = true;
+    this.squareBottomLeftCorner_ = true;
+  } else {
+    this.squareTopLeftCorner_ = false;
+    this.squareBottomLeftCorner_ = false;
+    // If this block is in the middle of a stack, square the corners.
+    if (this.previousConnection) {
+      var prevBlock = this.previousConnection.targetBlock();
+      if (prevBlock && prevBlock.getNextBlock() == this) {
+        this.squareTopLeftCorner_ = true;
+      }
+    } else if (Blockly.BlockSvg.START_HAT) {
+      // No output or previous connection.
+      this.squareTopLeftCorner_ = true;
+      this.startHat_ = true;
+      this.height += Blockly.BlockSvg.START_HAT_HEIGHT;
+      inputRows.rightEdge = Math.max(inputRows.rightEdge, 100);
+    }
+    var nextBlock = this.getNextBlock();
+    if (nextBlock) {
+      this.squareBottomLeftCorner_ = true;
+    }
+  }
+
+  // Assemble the block's path.
+  var steps = [];
+  var inlineSteps = [];
+  // The highlighting applies to edges facing the upper-left corner.
+  // Since highlighting is a two-pixel wide border, it would normally overhang
+  // the edge of the block by a pixel. So undersize all measurements by a pixel.
+  var highlightSteps = [];
+  var highlightInlineSteps = [];
+
+  this.renderDrawTop_(steps, highlightSteps, inputRows.rightEdge);
+  var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps,
+      highlightInlineSteps, inputRows, iconWidth);
+  this.renderDrawBottom_(steps, highlightSteps, cursorY);
+  this.renderDrawLeft_(steps, highlightSteps);
+
+  var pathString = steps.join(' ') + '\n' + inlineSteps.join(' ');
+  this.svgPath_.setAttribute('d', pathString);
+  this.svgPathDark_.setAttribute('d', pathString);
+  pathString = highlightSteps.join(' ') + '\n' + highlightInlineSteps.join(' ');
+  this.svgPathLight_.setAttribute('d', pathString);
+  if (this.RTL) {
+    // Mirror the block's path.
+    this.svgPath_.setAttribute('transform', 'scale(-1 1)');
+    this.svgPathLight_.setAttribute('transform', 'scale(-1 1)');
+    this.svgPathDark_.setAttribute('transform', 'translate(1,1) scale(-1 1)');
+  }
+};
+
+/**
+ * Update all of the connections on this block with the new locations calculated
+ * in renderCompute.  Also move all of the connected blocks based on the new
+ * connection locations.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderMoveConnections_ = function() {
+  var blockTL = this.getRelativeToSurfaceXY();
+  // Don't tighten previous or output connecitons because they are inferior
+  // connections.
+  if (this.previousConnection) {
+    this.previousConnection.moveToOffset(blockTL);
+  }
+  if (this.outputConnection) {
+    this.outputConnection.moveToOffset(blockTL);
+  }
+
+  for (var i = 0; i < this.inputList.length; i++) {
+    var conn = this.inputList[i].connection;
+    if (conn) {
+      conn.moveToOffset(blockTL);
+      if (conn.isConnected()) {
+        conn.tighten_();
+      }
+    }
+  }
+
+  if (this.nextConnection) {
+    this.nextConnection.moveToOffset(blockTL);
+    if (this.nextConnection.isConnected()) {
+      this.nextConnection.tighten_();
+    }
+  }
+
+};
+
+/**
+ * Render the top edge of the block.
+ * @param {!Array.<string>} steps Path of block outline.
+ * @param {!Array.<string>} highlightSteps Path of block highlights.
+ * @param {number} rightEdge Minimum width of block.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderDrawTop_ =
+    function(steps, highlightSteps, rightEdge) {
+  /* eslint-disable indent */
+  // Position the cursor at the top-left starting point.
+  if (this.squareTopLeftCorner_) {
+    steps.push('m 0,0');
+    highlightSteps.push('m 0.5,0.5');
+    if (this.startHat_) {
+      steps.push(Blockly.BlockSvg.START_HAT_PATH);
+      highlightSteps.push(this.RTL ?
+          Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL :
+          Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR);
+    }
+  } else {
+    steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START);
+    highlightSteps.push(this.RTL ?
+        Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL :
+        Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR);
+    // Top-left rounded corner.
+    steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER);
+    highlightSteps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT);
+  }
+
+  // Top edge.
+  if (this.previousConnection) {
+    steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
+    highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
+    steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT);
+    highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT);
+
+    var connectionX = (this.RTL ?
+        -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH);
+    this.previousConnection.setOffsetInBlock(connectionX, 0);
+  }
+  steps.push('H', rightEdge);
+  highlightSteps.push('H', rightEdge - 0.5);
+  this.width = rightEdge;
+};  /* eslint-enable indent */
+
+/**
+ * Render the right edge of the block.
+ * @param {!Array.<string>} steps Path of block outline.
+ * @param {!Array.<string>} highlightSteps Path of block highlights.
+ * @param {!Array.<string>} inlineSteps Inline block outlines.
+ * @param {!Array.<string>} highlightInlineSteps Inline block highlights.
+ * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
+ *     containing position information.
+ * @param {number} iconWidth Offset of first row due to icons.
+ * @return {number} Height of block.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps,
+    inlineSteps, highlightInlineSteps, inputRows, iconWidth) {
+  var cursorX;
+  var cursorY = 0;
+  var connectionX, connectionY;
+  for (var y = 0, row; row = inputRows[y]; y++) {
+    cursorX = Blockly.BlockSvg.SEP_SPACE_X;
+    if (y == 0) {
+      cursorX += this.RTL ? -iconWidth : iconWidth;
+    }
+    highlightSteps.push('M', (inputRows.rightEdge - 0.5) + ',' +
+        (cursorY + 0.5));
+    if (this.isCollapsed()) {
+      // Jagged right edge.
+      var input = row[0];
+      var fieldX = cursorX;
+      var fieldY = cursorY;
+      this.renderFields_(input.fieldRow, fieldX, fieldY);
+      steps.push(Blockly.BlockSvg.JAGGED_TEETH);
+      highlightSteps.push('h 8');
+      var remainder = row.height - Blockly.BlockSvg.JAGGED_TEETH_HEIGHT;
+      steps.push('v', remainder);
+      if (this.RTL) {
+        highlightSteps.push('v 3.9 l 7.2,3.4 m -14.5,8.9 l 7.3,3.5');
+        highlightSteps.push('v', remainder - 0.7);
+      }
+      this.width += Blockly.BlockSvg.JAGGED_TEETH_WIDTH;
+    } else if (row.type == Blockly.BlockSvg.INLINE) {
+      // Inline inputs.
+      for (var x = 0, input; input = row[x]; x++) {
+        var fieldX = cursorX;
+        var fieldY = cursorY;
+        if (row.thicker) {
+          // Lower the field slightly.
+          fieldY += Blockly.BlockSvg.INLINE_PADDING_Y;
+        }
+        // TODO: Align inline field rows (left/right/centre).
+        cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY);
+        if (input.type != Blockly.DUMMY_INPUT) {
+          cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
+        }
+        if (input.type == Blockly.INPUT_VALUE) {
+          inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) +
+                           ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y));
+          inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
+                           input.renderWidth);
+          inlineSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
+          inlineSteps.push('v', input.renderHeight + 1 -
+                                Blockly.BlockSvg.TAB_HEIGHT);
+          inlineSteps.push('h', input.renderWidth + 2 -
+                           Blockly.BlockSvg.TAB_WIDTH);
+          inlineSteps.push('z');
+          if (this.RTL) {
+            // Highlight right edge, around back of tab, and bottom.
+            highlightInlineSteps.push('M',
+                (cursorX - Blockly.BlockSvg.SEP_SPACE_X - 2.5 +
+                 Blockly.BlockSvg.TAB_WIDTH - input.renderWidth) + ',' +
+                (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5));
+            highlightInlineSteps.push(
+                Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
+            highlightInlineSteps.push('v',
+                input.renderHeight - Blockly.BlockSvg.TAB_HEIGHT + 2.5);
+            highlightInlineSteps.push('h',
+                input.renderWidth - Blockly.BlockSvg.TAB_WIDTH + 2);
+          } else {
+            // Highlight right edge, bottom.
+            highlightInlineSteps.push('M',
+                (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 0.5) + ',' +
+                (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5));
+            highlightInlineSteps.push('v', input.renderHeight + 1);
+            highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
+                                           input.renderWidth);
+            // Short highlight glint at bottom of tab.
+            highlightInlineSteps.push('M',
+                (cursorX - input.renderWidth - Blockly.BlockSvg.SEP_SPACE_X +
+                 0.9) + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y +
+                 Blockly.BlockSvg.TAB_HEIGHT - 0.7));
+            highlightInlineSteps.push('l',
+                (Blockly.BlockSvg.TAB_WIDTH * 0.46) + ',-2.1');
+          }
+          // Create inline input connection.
+          if (this.RTL) {
+            connectionX = -cursorX -
+                Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X +
+                input.renderWidth + 1;
+          } else {
+            connectionX = cursorX +
+                Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X -
+                input.renderWidth - 1;
+          }
+          connectionY = cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1;
+          input.connection.setOffsetInBlock(connectionX, connectionY);
+        }
+      }
+
+      cursorX = Math.max(cursorX, inputRows.rightEdge);
+      this.width = Math.max(this.width, cursorX);
+      steps.push('H', cursorX);
+      highlightSteps.push('H', cursorX - 0.5);
+      steps.push('v', row.height);
+      if (this.RTL) {
+        highlightSteps.push('v', row.height - 1);
+      }
+    } else if (row.type == Blockly.INPUT_VALUE) {
+      // External input.
+      var input = row[0];
+      var fieldX = cursorX;
+      var fieldY = cursorY;
+      if (input.align != Blockly.ALIGN_LEFT) {
+        var fieldRightX = inputRows.rightEdge - input.fieldWidth -
+            Blockly.BlockSvg.TAB_WIDTH - 2 * Blockly.BlockSvg.SEP_SPACE_X;
+        if (input.align == Blockly.ALIGN_RIGHT) {
+          fieldX += fieldRightX;
+        } else if (input.align == Blockly.ALIGN_CENTRE) {
+          fieldX += fieldRightX / 2;
+        }
+      }
+      this.renderFields_(input.fieldRow, fieldX, fieldY);
+      steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
+      var v = row.height - Blockly.BlockSvg.TAB_HEIGHT;
+      steps.push('v', v);
+      if (this.RTL) {
+        // Highlight around back of tab.
+        highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
+        highlightSteps.push('v', v + 0.5);
+      } else {
+        // Short highlight glint at bottom of tab.
+        highlightSteps.push('M', (inputRows.rightEdge - 5) + ',' +
+            (cursorY + Blockly.BlockSvg.TAB_HEIGHT - 0.7));
+        highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * 0.46) +
+            ',-2.1');
+      }
+      // Create external input connection.
+      connectionX = this.RTL ? -inputRows.rightEdge - 1 :
+          inputRows.rightEdge + 1;
+      input.connection.setOffsetInBlock(connectionX, cursorY);
+      if (input.connection.isConnected()) {
+        this.width = Math.max(this.width, inputRows.rightEdge +
+            input.connection.targetBlock().getHeightWidth().width -
+            Blockly.BlockSvg.TAB_WIDTH + 1);
+      }
+    } else if (row.type == Blockly.DUMMY_INPUT) {
+      // External naked field.
+      var input = row[0];
+      var fieldX = cursorX;
+      var fieldY = cursorY;
+      if (input.align != Blockly.ALIGN_LEFT) {
+        var fieldRightX = inputRows.rightEdge - input.fieldWidth -
+            2 * Blockly.BlockSvg.SEP_SPACE_X;
+        if (inputRows.hasValue) {
+          fieldRightX -= Blockly.BlockSvg.TAB_WIDTH;
+        }
+        if (input.align == Blockly.ALIGN_RIGHT) {
+          fieldX += fieldRightX;
+        } else if (input.align == Blockly.ALIGN_CENTRE) {
+          fieldX += fieldRightX / 2;
+        }
+      }
+      this.renderFields_(input.fieldRow, fieldX, fieldY);
+      steps.push('v', row.height);
+      if (this.RTL) {
+        highlightSteps.push('v', row.height - 1);
+      }
+    } else if (row.type == Blockly.NEXT_STATEMENT) {
+      // Nested statement.
+      var input = row[0];
+      if (y == 0) {
+        // If the first input is a statement stack, add a small row on top.
+        steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
+        if (this.RTL) {
+          highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
+        }
+        cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
+      }
+      var fieldX = cursorX;
+      var fieldY = cursorY;
+      if (input.align != Blockly.ALIGN_LEFT) {
+        var fieldRightX = inputRows.statementEdge - input.fieldWidth -
+            2 * Blockly.BlockSvg.SEP_SPACE_X;
+        if (input.align == Blockly.ALIGN_RIGHT) {
+          fieldX += fieldRightX;
+        } else if (input.align == Blockly.ALIGN_CENTRE) {
+          fieldX += fieldRightX / 2;
+        }
+      }
+      this.renderFields_(input.fieldRow, fieldX, fieldY);
+      cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH;
+      steps.push('H', cursorX);
+      steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER);
+      steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
+      steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER);
+      steps.push('H', inputRows.rightEdge);
+      if (this.RTL) {
+        highlightSteps.push('M',
+            (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
+             Blockly.BlockSvg.DISTANCE_45_OUTSIDE) +
+            ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
+        highlightSteps.push(
+            Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL);
+        highlightSteps.push('v',
+            row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
+        highlightSteps.push(
+            Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL);
+        highlightSteps.push('H', inputRows.rightEdge - 0.5);
+      } else {
+        highlightSteps.push('M',
+            (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
+             Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
+            (cursorY + row.height - Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
+        highlightSteps.push(
+            Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR);
+        highlightSteps.push('H', inputRows.rightEdge - 0.5);
+      }
+      // Create statement connection.
+      connectionX = this.RTL ? -cursorX : cursorX + 1;
+      input.connection.setOffsetInBlock(connectionX, cursorY + 1);
+
+      if (input.connection.isConnected()) {
+        this.width = Math.max(this.width, inputRows.statementEdge +
+            input.connection.targetBlock().getHeightWidth().width);
+      }
+      if (y == inputRows.length - 1 ||
+          inputRows[y + 1].type == Blockly.NEXT_STATEMENT) {
+        // If the final input is a statement stack, add a small row underneath.
+        // Consecutive statement stacks are also separated by a small divider.
+        steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
+        if (this.RTL) {
+          highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
+        }
+        cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
+      }
+    }
+    cursorY += row.height;
+  }
+  if (!inputRows.length) {
+    cursorY = Blockly.BlockSvg.MIN_BLOCK_Y;
+    steps.push('V', cursorY);
+    if (this.RTL) {
+      highlightSteps.push('V', cursorY - 1);
+    }
+  }
+  return cursorY;
+};
+
+/**
+ * Render the bottom edge of the block.
+ * @param {!Array.<string>} steps Path of block outline.
+ * @param {!Array.<string>} highlightSteps Path of block highlights.
+ * @param {number} cursorY Height of block.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderDrawBottom_ =
+    function(steps, highlightSteps, cursorY) {
+  /* eslint-disable indent */
+  this.height += cursorY + 1;  // Add one for the shadow.
+  if (this.nextConnection) {
+    steps.push('H', (Blockly.BlockSvg.NOTCH_WIDTH + (this.RTL ? 0.5 : - 0.5)) +
+        ' ' + Blockly.BlockSvg.NOTCH_PATH_RIGHT);
+    // Create next block connection.
+    var connectionX;
+    if (this.RTL) {
+      connectionX = -Blockly.BlockSvg.NOTCH_WIDTH;
+    } else {
+      connectionX = Blockly.BlockSvg.NOTCH_WIDTH;
+    }
+    this.nextConnection.setOffsetInBlock(connectionX, cursorY + 1);
+    this.height += 4;  // Height of tab.
+  }
+
+  // Should the bottom-left corner be rounded or square?
+  if (this.squareBottomLeftCorner_) {
+    steps.push('H 0');
+    if (!this.RTL) {
+      highlightSteps.push('M', '0.5,' + (cursorY - 0.5));
+    }
+  } else {
+    steps.push('H', Blockly.BlockSvg.CORNER_RADIUS);
+    steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
+               Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' +
+               Blockly.BlockSvg.CORNER_RADIUS + ',-' +
+               Blockly.BlockSvg.CORNER_RADIUS);
+    if (!this.RTL) {
+      highlightSteps.push('M', Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
+          (cursorY - Blockly.BlockSvg.DISTANCE_45_INSIDE));
+      highlightSteps.push('A', (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' +
+          (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' +
+          '0.5,' + (cursorY - Blockly.BlockSvg.CORNER_RADIUS));
+    }
+  }
+};  /* eslint-enable indent */
+
+/**
+ * Render the left edge of the block.
+ * @param {!Array.<string>} steps Path of block outline.
+ * @param {!Array.<string>} highlightSteps Path of block highlights.
+ * @private
+ */
+Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps, highlightSteps) {
+  if (this.outputConnection) {
+    // Create output connection.
+    this.outputConnection.setOffsetInBlock(0, 0);
+    steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
+    steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' +
+        Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH +
+        ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5');
+    if (this.RTL) {
+      highlightSteps.push('M', (Blockly.BlockSvg.TAB_WIDTH * -0.25) + ',8.4');
+      highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * -0.45) + ',-2.1');
+    } else {
+      highlightSteps.push('V', Blockly.BlockSvg.TAB_HEIGHT - 1.5);
+      highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * -0.92) +
+                          ',-0.5 q ' + (Blockly.BlockSvg.TAB_WIDTH * -0.19) +
+                          ',-5.5 0,-11');
+      highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * 0.92) +
+                          ',1 V 0.5 H 1');
+    }
+    this.width += Blockly.BlockSvg.TAB_WIDTH;
+  } else if (!this.RTL) {
+    if (this.squareTopLeftCorner_) {
+      // Statement block in a stack.
+      highlightSteps.push('V', 0.5);
+    } else {
+      highlightSteps.push('V', Blockly.BlockSvg.CORNER_RADIUS);
+    }
+  }
+  steps.push('z');
+};

+ 1845 - 0
blockly/core/block_svg.js

@@ -0,0 +1,1845 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Methods for graphically rendering a block as SVG.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.BlockSvg');
+
+goog.require('Blockly.Block');
+goog.require('Blockly.ContextMenu');
+goog.require('Blockly.RenderedConnection');
+goog.require('goog.Timer');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.math.Coordinate');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for a block's SVG representation.
+ * Not normally called directly, workspace.newBlock() is preferred.
+ * @param {!Blockly.Workspace} workspace The block's workspace.
+ * @param {?string} prototypeName Name of the language object containing
+ *     type-specific functions for this block.
+ * @param {=string} opt_id Optional ID.  Use this ID if provided, otherwise
+ *     create a new id.
+ * @extends {Blockly.Block}
+ * @constructor
+ */
+Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
+  // Create core elements for the block.
+  /**
+   * @type {SVGElement}
+   * @private
+   */
+  this.svgGroup_ = Blockly.createSvgElement('g', {}, null);
+
+  /**
+   * @type {SVGElement}
+   * @private
+   */
+  this.svgPathDark_ = Blockly.createSvgElement('path',
+      {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'},
+      this.svgGroup_);
+
+  /**
+   * @type {SVGElement}
+   * @private
+   */
+  this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'},
+      this.svgGroup_);
+
+  /**
+   * @type {SVGElement}
+   * @private
+   */
+  this.svgPathLight_ = Blockly.createSvgElement('path',
+      {'class': 'blocklyPathLight'}, this.svgGroup_);
+  this.svgPath_.tooltip = this;
+
+  /** @type {boolean} */
+  this.rendered = false;
+
+  Blockly.Tooltip.bindMouseEvents(this.svgPath_);
+  Blockly.BlockSvg.superClass_.constructor.call(this,
+      workspace, prototypeName, opt_id);
+};
+goog.inherits(Blockly.BlockSvg, Blockly.Block);
+
+/**
+ * Height of this block, not including any statement blocks above or below.
+ */
+Blockly.BlockSvg.prototype.height = 0;
+/**
+ * Width of this block, including any connected value blocks.
+ */
+Blockly.BlockSvg.prototype.width = 0;
+
+/**
+ * Original location of block being dragged.
+ * @type {goog.math.Coordinate}
+ * @private
+ */
+Blockly.BlockSvg.prototype.dragStartXY_ = null;
+
+/**
+ * Constant for identifying rows that are to be rendered inline.
+ * Don't collide with Blockly.INPUT_VALUE and friends.
+ * @const
+ */
+Blockly.BlockSvg.INLINE = -1;
+
+/**
+ * Create and initialize the SVG representation of the block.
+ * May be called more than once.
+ */
+Blockly.BlockSvg.prototype.initSvg = function() {
+  goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.');
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    input.init();
+  }
+  var icons = this.getIcons();
+  for (var i = 0; i < icons.length; i++) {
+    icons[i].createIcon();
+  }
+  
+  // not sure if we need this after the update - JY
+  // if (this.mutator) {
+  //   this.mutator.createIcon();
+  // }
+  // for BlocksCAD
+  if (this.mutatorPlus) {
+    this.mutatorPlus.createIcon();
+  }
+  if (this.mutatorMinus) {
+    this.mutatorMinus.createIcon();
+  }
+  this.updateColour();
+  this.updateMovable();
+  if (!this.workspace.options.readOnly && !this.eventsInit_) {
+    Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this,
+                       this.onMouseDown_);
+    var thisBlock = this;
+    Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
+                       function(e) {Blockly.longStart_(e, thisBlock);});
+  }
+  this.eventsInit_ = true;
+
+  if (!this.getSvgRoot().parentNode) {
+    this.workspace.getCanvas().appendChild(this.getSvgRoot());
+  }
+};
+
+/**
+ * Select this block.  Highlight it visually.
+ */
+Blockly.BlockSvg.prototype.select = function() {
+  if (this.isShadow() && this.getParent()) {
+    // Shadow blocks should not be selected.
+    this.getParent().select();
+    return;
+  }
+  if (Blockly.selected == this) {
+    return;
+  }
+  var oldId = null;
+  if (Blockly.selected) {
+    oldId = Blockly.selected.id;
+    // Unselect any previously selected block.
+    Blockly.Events.disable();
+    try {
+      Blockly.selected.unselect();
+    } finally {
+      Blockly.Events.enable();
+    }
+  }
+  var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
+  event.workspaceId = this.workspace.id;
+  Blockly.Events.fire(event);
+  Blockly.selected = this;
+  this.addSelect();
+  // for BlocksCAD - I want to turn off backlighting when the user selects the block
+  // console.log("in select: turning off any backlighting");
+  this.unbacklight();
+};
+
+/**
+ * Unselect this block.  Remove its highlighting.
+ */
+Blockly.BlockSvg.prototype.unselect = function() {
+  if (Blockly.selected != this) {
+    return;
+  }
+  var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
+  event.workspaceId = this.workspace.id;
+  Blockly.Events.fire(event);
+  Blockly.selected = null;
+  this.removeSelect();
+};
+
+/**
+ * Backlight this block.  Highlight it visually.  Added for BlocksCAD.
+ */
+Blockly.BlockSvg.prototype.backlight = function() {
+  var found_it = 0;
+  for(var i = Blockly.backlight.length; i--;) {
+    if(Blockly.backlight[i] == this.id) {
+      found_it = 1;
+      break;
+    }
+  }
+  if (!found_it && this) {
+    // Add this block to the list of backlight blocks
+    Blockly.backlight.push(this.id);
+  }
+  this.addBacklight();
+};
+
+/**
+ * Remove backlighting from this block.  Added for BlocksCAD.
+ * Fire a change event so procedures can turn their warning messages off.
+ */
+Blockly.BlockSvg.prototype.unbacklight = function() {
+  var found_it = 0;
+  for(var i = Blockly.backlight.length; i--;) {
+    if(Blockly.backlight[i] == this.id) {
+      Blockly.backlight.splice(i, 1);
+      found_it = 1;
+    }
+  }
+  if (found_it && this) {
+    this.removeBacklight();
+    // Take this id off the internal backlight list, id applicable
+    // console.log("in unbacklight with:",this);
+    // need to get the setter block to clear the block off of the list there, too.
+    if (this.type == 'varibles_get') {
+      var all_of_them = Blockly.Variables.getInstances(this.getFieldValue('VAR'), this.workspace);
+      var setters = [];
+      for (var i = 0; i < all_of_them.length; i++) {
+        if (all_of_them[i].type == 'variables_set')
+          setters.push(all_of_them[i]);
+      }
+      for (var i = 0; i < setters.length; i++) {
+        for (var j = 0; j < setters[i].backlightBlocks.length; j++) 
+          if (setters[i].backlightBlocks[j] == this.id) {
+            setters[i].backlightBlocks.splice(j,1);
+            if (setters[i].backlightBlocks.length == 0)
+              setters[i].setWarningText(null);
+          }
+      }
+    }
+    else if (this.type == 'procedures_callnoreturn' || this.type == 'procedures_callreturn') {
+      var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
+      this.workspace);
+      for (var i = 0; i < defBlock.backlightBlocks.length; i++) {
+        if (defBlock.backlightBlocks[i] == this.id) { 
+          defBlock.backlightBlocks.splice(i,1);
+          if (defBlock.backlightBlocks.length == 0)
+            defBlock.setWarningText(null);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Block's mutator icon (if any).
+ * @type {Blockly.Mutator}
+ */
+Blockly.BlockSvg.prototype.mutator = null;
+
+/**
+ * Block's comment icon (if any).
+ * @type {Blockly.Comment}
+ */
+Blockly.BlockSvg.prototype.comment = null;
+
+/**
+ * Block's warning icon (if any).
+ * @type {Blockly.Warning}
+ */
+Blockly.BlockSvg.prototype.warning = null;
+
+/**
+ * Returns a list of mutator, comment, and warning icons.
+ * @return {!Array} List of icons.
+ */
+Blockly.BlockSvg.prototype.getIcons = function() {
+  var icons = [];
+  if (this.mutator) {
+    icons.push(this.mutator);
+  }
+  // add mutator plus and minus icons for BlocksCAD
+  if (this.mutatorPlus) {
+    icons.push(this.mutatorPlus);
+  }
+  if (this.mutatorMinus) {
+    icons.push(this.mutatorMinus);
+  }
+  if (this.comment) {
+    icons.push(this.comment);
+  }
+  if (this.warning) {
+    icons.push(this.warning);
+  }
+  return icons;
+};
+
+/**
+ * Wrapper function called when a mouseUp occurs during a drag operation.
+ * @type {Array.<!Array>}
+ * @private
+ */
+Blockly.BlockSvg.onMouseUpWrapper_ = null;
+
+/**
+ * Wrapper function called when a mouseMove occurs during a drag operation.
+ * @type {Array.<!Array>}
+ * @private
+ */
+Blockly.BlockSvg.onMouseMoveWrapper_ = null;
+
+/**
+ * Stop binding to the global mouseup and mousemove events.
+ * @package
+ */
+Blockly.BlockSvg.terminateDrag = function() {
+  Blockly.BlockSvg.disconnectUiStop_();
+  if (Blockly.BlockSvg.onMouseUpWrapper_) {
+    Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
+    Blockly.BlockSvg.onMouseUpWrapper_ = null;
+  }
+  if (Blockly.BlockSvg.onMouseMoveWrapper_) {
+    Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
+    Blockly.BlockSvg.onMouseMoveWrapper_ = null;
+  }
+  var selected = Blockly.selected;
+  if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
+    // Terminate a drag operation.
+    if (selected) {
+      // Update the connection locations.
+      var xy = selected.getRelativeToSurfaceXY();
+      var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_);
+      var event = new Blockly.Events.Move(selected);
+      event.oldCoordinate = selected.dragStartXY_;
+      event.recordNew();
+      Blockly.Events.fire(event);
+
+      selected.moveConnections_(dxy.x, dxy.y);
+      delete selected.draggedBubbles_;
+      selected.setDragging_(false);
+      selected.render();
+      // Ensure that any stap and bump are part of this move's event group.
+      var group = Blockly.Events.getGroup();
+      setTimeout(function() {
+        Blockly.Events.setGroup(group);
+        selected.snapToGrid();
+        Blockly.Events.setGroup(false);
+      }, Blockly.BUMP_DELAY / 2);
+      setTimeout(function() {
+        Blockly.Events.setGroup(group);
+        selected.bumpNeighbours_();
+        Blockly.Events.setGroup(false);
+      }, Blockly.BUMP_DELAY);
+      // Fire an event to allow scrollbars to resize.
+      Blockly.resizeSvgContents(selected.workspace);
+    }
+  }
+  Blockly.dragMode_ = Blockly.DRAG_NONE;
+  Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
+};
+
+/**
+ * Set parent of this block to be a new block or null.
+ * @param {Blockly.BlockSvg} newParent New parent block.
+ */
+Blockly.BlockSvg.prototype.setParent = function(newParent) {
+  if (newParent == this.parentBlock_) {
+    return;
+  }
+  var svgRoot = this.getSvgRoot();
+  if (this.parentBlock_ && svgRoot) {
+    // Move this block up the DOM.  Keep track of x/y translations.
+    var xy = this.getRelativeToSurfaceXY();
+    this.workspace.getCanvas().appendChild(svgRoot);
+    svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
+  }
+
+  Blockly.Field.startCache();
+  Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
+  Blockly.Field.stopCache();
+
+  if (newParent) {
+    var oldXY = this.getRelativeToSurfaceXY();
+    newParent.getSvgRoot().appendChild(svgRoot);
+    var newXY = this.getRelativeToSurfaceXY();
+    // Move the connections to match the child's new position.
+    this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y);
+  }
+};
+
+/**
+ * Return the coordinates of the top-left corner of this block relative to the
+ * drawing surface's origin (0,0).
+ * @return {!goog.math.Coordinate} Object with .x and .y properties.
+ */
+Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
+  var x = 0;
+  var y = 0;
+  var element = this.getSvgRoot();
+  if (element) {
+    do {
+      // Loop through this block and every parent.
+      var xy = Blockly.getRelativeXY_(element);
+      x += xy.x;
+      y += xy.y;
+      element = element.parentNode;
+    } while (element && element != this.workspace.getCanvas());
+  }
+  return new goog.math.Coordinate(x, y);
+};
+
+/**
+ * Move a block by a relative offset.
+ * @param {number} dx Horizontal offset.
+ * @param {number} dy Vertical offset.
+ */
+Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
+  goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
+  var event = new Blockly.Events.Move(this);
+  var xy = this.getRelativeToSurfaceXY();
+  this.getSvgRoot().setAttribute('transform',
+      'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')');
+  this.moveConnections_(dx, dy);
+  event.recordNew();
+  Blockly.resizeSvgContents(this.workspace);
+  Blockly.Events.fire(event);
+};
+
+/**
+ * Snap this block to the nearest grid point.
+ */
+Blockly.BlockSvg.prototype.snapToGrid = function() {
+  if (!this.workspace) {
+    return;  // Deleted block.
+  }
+  if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
+    return;  // Don't bump blocks during a drag.
+  }
+  if (this.getParent()) {
+    return;  // Only snap top-level blocks.
+  }
+  if (this.isInFlyout) {
+    return;  // Don't move blocks around in a flyout.
+  }
+  if (!this.workspace.options.gridOptions ||
+      !this.workspace.options.gridOptions['snap']) {
+    return;  // Config says no snapping.
+  }
+  var spacing = this.workspace.options.gridOptions['spacing'];
+  var half = spacing / 2;
+  var xy = this.getRelativeToSurfaceXY();
+  var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
+  var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y;
+  dx = Math.round(dx);
+  dy = Math.round(dy);
+  if (dx != 0 || dy != 0) {
+    this.moveBy(dx, dy);
+  }
+};
+
+/**
+ * Returns a bounding box describing the dimensions of this block
+ * and any blocks stacked below it.
+ * @return {!{height: number, width: number}} Object with height and width
+ *    properties.
+ */
+Blockly.BlockSvg.prototype.getHeightWidth = function() {
+  var height = this.height;
+  var width = this.width;
+  // Recursively add size of subsequent blocks.
+  var nextBlock = this.getNextBlock();
+  if (nextBlock) {
+    var nextHeightWidth = nextBlock.getHeightWidth();
+    height += nextHeightWidth.height - 4;  // Height of tab.
+    width = Math.max(width, nextHeightWidth.width);
+  } else if (!this.nextConnection && !this.outputConnection) {
+    // Add a bit of margin under blocks with no bottom tab.
+    height += 2;
+  }
+  return {height: height, width: width};
+};
+
+/**
+ * Returns the coordinates of a bounding box describing the dimensions of this
+ * block and any blocks stacked below it.
+ * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}}
+ *    Object with top left and bottom right coordinates of the bounding box.
+ */
+Blockly.BlockSvg.prototype.getBoundingRectangle = function() {
+  var blockXY = this.getRelativeToSurfaceXY(this);
+  var tab = this.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
+  var blockBounds = this.getHeightWidth();
+  var topLeft;
+  var bottomRight;
+  if (this.RTL) {
+    // Width has the tab built into it already so subtract it here.
+    topLeft = new goog.math.Coordinate(blockXY.x - (blockBounds.width - tab),
+        blockXY.y);
+    // Add the width of the tab/puzzle piece knob to the x coordinate
+    // since X is the corner of the rectangle, not the whole puzzle piece.
+    bottomRight = new goog.math.Coordinate(blockXY.x + tab,
+        blockXY.y + blockBounds.height);
+  } else {
+    // Subtract the width of the tab/puzzle piece knob to the x coordinate
+    // since X is the corner of the rectangle, not the whole puzzle piece.
+    topLeft = new goog.math.Coordinate(blockXY.x - tab, blockXY.y);
+    // Width has the tab built into it already so subtract it here.
+    bottomRight = new goog.math.Coordinate(blockXY.x + blockBounds.width - tab,
+        blockXY.y + blockBounds.height);
+  }
+  return {topLeft: topLeft, bottomRight: bottomRight};
+};
+
+/**
+ * Set whether the block is collapsed or not.
+ * @param {boolean} collapsed True if collapsed.
+ */
+
+// Added the force parameter so that BlocksCAD can force an updated comment
+// to change the text on a collapsed block.  This is used in STL import.
+Blockly.BlockSvg.prototype.setCollapsed = function(collapsed,force) {
+  if (this.collapsed_ == collapsed && !force) {
+    return;
+  }
+  var renderList = [];
+  // Show/hide the inputs.
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    renderList.push.apply(renderList, input.setVisible(!collapsed));
+  }
+
+  var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
+  if (collapsed) {
+    var icons = this.getIcons();
+    for (var x = 0; x < icons.length; x++) {
+      icons[x].setVisible(false);
+    }
+          // when collapsing the block, display the first 27 characters
+          // of the top block comment (if any), then if there is room
+          // display a few characters of the normal block string
+          // representation - jayod
+
+          // Actually, I'd like to get the comment off of the first block to have one,
+          // even if it isn't the top block.  I'll write a little function to do this.
+
+          var text = '';
+          var comm = getTopComment(this); // any comments or procedure calls here? 
+          if (comm.length > Blockly.COLLAPSE_CHARS - 3) {
+            comm = comm.substring(0,Blockly.COLLAPSE_CHARS - 3)+"...";
+            text = comm;
+          } else {
+            if (!force) text = comm;
+            if (Blockly.COLLAPSE_CHARS - comm.length > 8) {
+              if (comm.length > 0) {
+              if (!force) text += " - ";
+              }
+              text += this.toString(Blockly.COLLAPSE_CHARS - comm.length);
+              // console.log('in collapse. this.toString is:',this.toString(Blockly.COLLAPSE_CHARS));
+            }
+          }
+    if (!force) this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text,'COLLAPSE_TEXT').init();
+    else {
+      // remove the collapse_text field, and append a new one
+      var inp = this.getInput(COLLAPSED_INPUT_NAME);
+      inp.setVisible(true);
+      inp.removeField('COLLAPSE_TEXT');
+      inp.appendField(text,'COLLAPSE_TEXT').init();
+
+
+    }
+  } else {
+    this.removeInput(COLLAPSED_INPUT_NAME);
+    // Clear any warnings inherited from enclosed blocks.
+    this.setWarningText(null);
+  }
+  Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);
+
+  if (!renderList.length) {
+    // No child blocks, just render this block.
+    renderList[0] = this;
+  }
+  if (this.rendered) {
+    for (var i = 0, block; block = renderList[i]; i++) {
+      block.render();
+    }
+    // Don't bump neighbours.
+    // Although bumping neighbours would make sense, users often collapse
+    // all their functions and store them next to each other.  Expanding and
+    // bumping causes all their definitions to go out of alignment.
+  }
+
+  // BLOCKSCAD - jayod - I need to fix typed blocks after they are expanded.
+  if (!collapsed) {
+    Blockscad.assignBlockTypes([this]);
+  }
+  // END BLOCKSCAD - jayod
+  // this.workspace.fireChangeEvent();
+};
+
+function getTopComment(block) {     // added for BlocksCAD
+  if (block.category && block.category != 'PROCEDURE') {
+    var comm = '';
+    var blockStack = block.getDescendants();
+    for (var i = 0; i < blockStack.length; i++) {
+      comm = blockStack[i].getCommentText();
+      if (comm.length > 0) {
+        return comm;
+      }
+    }
+  // there was no comment.  Were there any procedure calls?
+    for (i = 1; i < blockStack.length; i++) {
+      if (blockStack[i].type.lastIndexOf('procedures_call') != -1) {
+        comm = blockStack[i].inputList[0].fieldRow[0].getText();
+        return comm;
+      }
+    }
+  }
+  return '';
+} // end getTopComment - added for BlocksCAD
+
+/**
+ * Open the next (or previous) FieldTextInput.
+ * @param {Blockly.Field|Blockly.Block} start Current location.
+ * @param {boolean} forward If true go forward, otherwise backward.
+ */
+Blockly.BlockSvg.prototype.tab = function(start, forward) {
+  // This function need not be efficient since it runs once on a keypress.
+  // Create an ordered list of all text fields and connected inputs.
+  var list = [];
+  for (var i = 0, input; input = this.inputList[i]; i++) {
+    for (var j = 0, field; field = input.fieldRow[j]; j++) {
+      if (field instanceof Blockly.FieldTextInput) {
+        // TODO: Also support dropdown fields.
+        list.push(field);
+      }
+    }
+    if (input.connection) {
+      var block = input.connection.targetBlock();
+      if (block) {
+        list.push(block);
+      }
+    }
+  }
+  var i = list.indexOf(start);
+  if (i == -1) {
+    // No start location, start at the beginning or end.
+    i = forward ? -1 : list.length;
+  }
+  var target = list[forward ? i + 1 : i - 1];
+  if (!target) {
+    // Ran off of list.
+    var parent = this.getParent();
+    if (parent) {
+      parent.tab(this, forward);
+    }
+  } else if (target instanceof Blockly.Field) {
+    target.showEditor_();
+  } else {
+    target.tab(null, forward);
+  }
+};
+
+/**
+ * Handle a mouse-down on an SVG block.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
+  if (this.workspace.options.readOnly) {
+    return;
+  }
+  if (this.isInFlyout) {
+    return;
+  }
+  this.workspace.markFocused();
+  Blockly.terminateDrag_();
+  this.select();
+  Blockly.hideChaff();
+  if (Blockly.isRightButton(e)) {
+    // Right-click.
+    this.showContextMenu_(e);
+  } else if (!this.isMovable()) {
+    // Allow immovable blocks to be selected and context menued, but not
+    // dragged.  Let this event bubble up to document, so the workspace may be
+    // dragged instead.
+    return;
+  } else {
+    if (!Blockly.Events.getGroup()) {
+      Blockly.Events.setGroup(true);
+    }
+    // Left-click (or middle click)
+    Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
+
+    this.dragStartXY_ = this.getRelativeToSurfaceXY();
+    this.workspace.startDrag(e, this.dragStartXY_);
+
+    Blockly.dragMode_ = Blockly.DRAG_STICKY;
+    Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document,
+        'mouseup', this, this.onMouseUp_);
+    Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
+        'mousemove', this, this.onMouseMove_);
+    // Build a list of bubbles that need to be moved and where they started.
+    this.draggedBubbles_ = [];
+    var descendants = this.getDescendants();
+    for (var i = 0, descendant; descendant = descendants[i]; i++) {
+      var icons = descendant.getIcons();
+      for (var j = 0; j < icons.length; j++) {
+        var data = icons[j].getIconLocation();
+        data.bubble = icons[j];
+        this.draggedBubbles_.push(data);
+      }
+    }
+  }
+  // This event has been handled.  No need to bubble up to the document.
+  e.stopPropagation();
+  e.preventDefault();
+};
+
+/**
+ * Handle a mouse-up anywhere in the SVG pane.  Is only registered when a
+ * block is clicked.  We can't use mouseUp on the block since a fast-moving
+ * cursor can briefly escape the block before it catches up.
+ * @param {!Event} e Mouse up event.
+ * @private
+ */
+Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
+  if (Blockly.dragMode_ != Blockly.DRAG_FREE &&
+      !Blockly.WidgetDiv.isVisible()) {
+    Blockly.Events.fire(
+        new Blockly.Events.Ui(this, 'click', undefined, undefined));
+  }
+  Blockly.terminateDrag_();
+  if (Blockly.selected && Blockly.highlightedConnection_) {
+    // Connect two blocks together.
+    Blockly.localConnection_.connect(Blockly.highlightedConnection_);
+    if (this.rendered) {
+      // Trigger a connection animation.
+      // Determine which connection is inferior (lower in the source stack).
+      var inferiorConnection = Blockly.localConnection_.isSuperior() ?
+          Blockly.highlightedConnection_ : Blockly.localConnection_;
+      inferiorConnection.getSourceBlock().connectionUiEffect();
+    }
+    if (this.workspace.trashcan) {
+      // Don't throw an object in the trash can if it just got connected.
+      this.workspace.trashcan.close();
+    }
+  } else if (!this.getParent() && Blockly.selected.isDeletable() &&
+      this.workspace.isDeleteArea(e)) {
+    var trashcan = this.workspace.trashcan;
+    if (trashcan) {
+      goog.Timer.callOnce(trashcan.close, 100, trashcan);
+    }
+    Blockly.selected.dispose(false, true);
+  }
+  if (Blockly.highlightedConnection_) {
+    Blockly.highlightedConnection_.unhighlight();
+    Blockly.highlightedConnection_ = null;
+  }
+  // for BlocksCAD - unhighlight bumped away illegal connections.
+  if (Blockly.highlightedConnectionBad_) {
+    Blockly.highlightedConnectionBad_.unhighlight();
+    Blockly.highlightedConnectionBad_ = null;
+  } 
+  Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
+  if (!Blockly.WidgetDiv.isVisible()) {
+    Blockly.Events.setGroup(false);
+  }
+};
+
+/**
+ * Load the block's help page in a new window.
+ * @private
+ */
+Blockly.BlockSvg.prototype.showHelp_ = function() {
+  var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
+  if (url) {
+    window.open(url);
+  }
+};
+
+/**
+ * Show the context menu for this block.
+ * @param {!Event} e Mouse event.
+ * @private
+ */
+Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
+  if (this.workspace.options.readOnly || !this.contextMenu) {
+    return;
+  }
+  // Save the current block in a variable for use in closures.
+  var block = this;
+  var menuOptions = [];
+
+  if (this.isDeletable() && this.isMovable() && !block.isInFlyout) {
+    // Option to duplicate this block.
+    var duplicateOption = {
+      text: Blockly.Msg.DUPLICATE_BLOCK,
+      enabled: true,
+      callback: function() {
+        Blockly.duplicate_(block);
+      }
+    };
+    if (this.getDescendants().length > this.workspace.remainingCapacity()) {
+      duplicateOption.enabled = false;
+    }
+    menuOptions.push(duplicateOption);
+
+    if (this.isEditable() && !this.collapsed_ &&
+        this.workspace.options.comments) {
+      // Option to add/remove a comment.
+      var commentOption = {enabled: !goog.userAgent.IE};
+      if (this.comment) {
+        commentOption.text = Blockly.Msg.REMOVE_COMMENT;
+        commentOption.callback = function() {
+          block.setCommentText(null);
+        };
+      } else {
+        commentOption.text = Blockly.Msg.ADD_COMMENT;
+        commentOption.callback = function() {
+          block.setCommentText('');
+        };
+      }
+      menuOptions.push(commentOption);
+    }
+
+    // Option to make block inline.
+    if (!this.collapsed_) {
+      for (var i = 1; i < this.inputList.length; i++) {
+        if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT &&
+            this.inputList[i].type != Blockly.NEXT_STATEMENT) {
+          // Only display this option if there are two value or dummy inputs
+          // next to each other.
+          var inlineOption = {enabled: true};
+          var isInline = this.getInputsInline();
+          inlineOption.text = isInline ?
+              Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS;
+          inlineOption.callback = function() {
+            block.setInputsInline(!isInline);
+          };
+          menuOptions.push(inlineOption);
+          break;
+        }
+      }
+    }
+
+    if (this.workspace.options.collapse) {
+      // Option to collapse/expand block.
+      if (this.collapsed_) {
+        var expandOption = {enabled: true};
+        expandOption.text = Blockly.Msg.EXPAND_BLOCK;
+        expandOption.callback = function() {
+          block.setCollapsed(false);
+        };
+        menuOptions.push(expandOption);
+      } else {
+        var collapseOption = {enabled: true};
+        collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK;
+        collapseOption.callback = function() {
+          block.setCollapsed(true);
+        };
+        menuOptions.push(collapseOption);
+      }
+    }
+
+    if (this.workspace.options.disable) {
+      // Option to disable/enable block.
+      var disableOption = {
+        text: this.disabled ?
+            Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK,
+        enabled: !this.getInheritedDisabled(),
+        callback: function() {
+          block.setDisabled(!block.disabled);
+        }
+      };
+      menuOptions.push(disableOption);
+    }
+
+    // Option to delete this block.
+    // Count the number of blocks that are nested in this block.
+    var descendantCount = this.getDescendants().length;
+    var nextBlock = this.getNextBlock();
+    if (nextBlock) {
+      // Blocks in the current stack would survive this block's deletion.
+      descendantCount -= nextBlock.getDescendants().length;
+    }
+    var deleteOption = {
+      text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK :
+          Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)),
+      enabled: true,
+      callback: function() {
+        Blockly.Events.setGroup(true);
+        block.dispose(true, true);
+        Blockly.Events.setGroup(false);
+      }
+    };
+    menuOptions.push(deleteOption);
+  }
+
+  // Option to get help.
+  var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
+  var helpOption = {enabled: !!url};
+  helpOption.text = Blockly.Msg.HELP;
+  helpOption.callback = function() {
+    block.showHelp_();
+  };
+  menuOptions.push(helpOption);
+
+  // Allow the block to add or modify menuOptions.
+  if (this.customContextMenu && !block.isInFlyout) {
+    this.customContextMenu(menuOptions);
+  }
+
+  Blockly.ContextMenu.show(e, menuOptions, this.RTL);
+  Blockly.ContextMenu.currentBlock = this;
+};
+
+/**
+ * Move the connections for this block and all blocks attached under it.
+ * Also update any attached bubbles.
+ * @param {number} dx Horizontal offset from current location.
+ * @param {number} dy Vertical offset from current location.
+ * @private
+ */
+Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
+  if (!this.rendered) {
+    // Rendering is required to lay out the blocks.
+    // This is probably an invisible block attached to a collapsed block.
+    return;
+  }
+  var myConnections = this.getConnections_(false);
+  for (var i = 0; i < myConnections.length; i++) {
+    myConnections[i].moveBy(dx, dy);
+  }
+  var icons = this.getIcons();
+  for (var i = 0; i < icons.length; i++) {
+    icons[i].computeIconLocation();
+  }
+
+  // Recurse through all blocks attached under this one.
+  for (var i = 0; i < this.childBlocks_.length; i++) {
+    this.childBlocks_[i].moveConnections_(dx, dy);
+  }
+};
+
+/**
+ * Recursively adds or removes the dragging class to this node and its children.
+ * @param {boolean} adding True if adding, false if removing.
+ * @private
+ */
+Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
+  if (adding) {
+    var group = this.getSvgRoot();
+    group.translate_ = '';
+    group.skew_ = '';
+    this.addDragging();
+    Blockly.draggingConnections_ =
+        Blockly.draggingConnections_.concat(this.getConnections_(true));
+  } else {
+    this.removeDragging();
+    Blockly.draggingConnections_ = [];
+  }
+  // Recurse through all blocks attached under this one.
+  for (var i = 0; i < this.childBlocks_.length; i++) {
+    this.childBlocks_[i].setDragging_(adding);
+  }
+};
+
+/**
+ * Drag this block to follow the mouse.
+ * @param {!Event} e Mouse move event.
+ * @private
+ */
+Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
+  if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
+      e.button == 0) {
+    /* HACK:
+     Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
+     events on certain touch actions. Ignore events with these signatures.
+     This may result in a one-pixel blind spot in other browsers,
+     but this shouldn't be noticeable. */
+    e.stopPropagation();
+    return;
+  }
+
+  var oldXY = this.getRelativeToSurfaceXY();
+  var newXY = this.workspace.moveDrag(e);
+
+  if (Blockly.dragMode_ == Blockly.DRAG_STICKY) {
+    // Still dragging within the sticky DRAG_RADIUS.
+    var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale;
+    if (dr > Blockly.DRAG_RADIUS) {
+      // Switch to unrestricted dragging.
+      Blockly.dragMode_ = Blockly.DRAG_FREE;
+      Blockly.longStop_();
+      if (this.parentBlock_) {
+        // Push this block to the very top of the stack.
+        this.unplug();
+        var group = this.getSvgRoot();
+        group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
+        this.disconnectUiEffect();
+      }
+      this.setDragging_(true);
+    }
+  }
+  if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
+    // Unrestricted dragging.
+    var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_);
+    var group = this.getSvgRoot();
+    group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
+    group.setAttribute('transform', group.translate_ + group.skew_);
+    // Drag all the nested bubbles.
+    for (var i = 0; i < this.draggedBubbles_.length; i++) {
+      var commentData = this.draggedBubbles_[i];
+      commentData.bubble.setIconLocation(
+          goog.math.Coordinate.sum(commentData, dxy));
+    }
+
+    // Check to see if any of this block's connections are within range of
+    // another block's connection.
+    var myConnections = this.getConnections_(false);
+    // Also check the last connection on this stack
+    var lastOnStack = this.lastConnectionInStack_();
+    if (lastOnStack && lastOnStack != this.nextConnection) {
+      myConnections.push(lastOnStack);
+    }
+    var closestConnection = null;
+    var closestConnectionBad = null;
+    var localConnection = null;
+    var radiusConnection = Blockly.SNAP_RADIUS;
+
+    // for BlocksCAD, I have a "neighbour.allowed" field and use it to find bad connections.
+    for (var i = 0; i < myConnections.length; i++) {
+      var myConnection = myConnections[i];
+      var neighbour = myConnection.closest(radiusConnection, dxy);
+      if (neighbour.connection && neighbour.allowed) {
+        closestConnection = neighbour.connection;
+        localConnection = myConnection;
+        radiusConnection = neighbour.radius;
+      }
+      else if (neighbour.connection && !neighbour.allowed) {
+        closestConnectionBad = neighbour.connection;
+        localConnection = myConnection;
+        radiusConnection = neighbour.radius;
+      }
+    }
+    // Remove connection highlighting if needed.
+    if (Blockly.highlightedConnection_ &&
+        Blockly.highlightedConnection_ != closestConnection) {
+      Blockly.highlightedConnection_.unhighlight();
+      Blockly.highlightedConnection_ = null;
+      Blockly.localConnection_ = null;
+    }
+
+    // for BlocksCAD - add or remove bad connection highlighting
+    // Remove connection highlighting if needed.
+    if (Blockly.highlightedConnectionBad_ &&
+        Blockly.highlightedConnectionBad_ != closestConnectionBad) {
+      Blockly.highlightedConnectionBad_.unhighlight();
+      Blockly.highlightedConnectionBad_ = null;
+      Blockly.localConnection_ = null;
+    } 
+    // Add connection highlighting if needed.
+    if (closestConnection &&
+        closestConnection != Blockly.highlightedConnection_) {
+      closestConnection.highlight();
+      Blockly.highlightedConnection_ = closestConnection;
+      Blockly.localConnection_ = localConnection;
+    }
+
+    // Add connection highlighting if needed for illegal connections (BlocksCAD)
+    if (closestConnectionBad &&
+        closestConnectionBad != Blockly.highlightedConnectionBad_) {
+      closestConnectionBad.highlightBad();
+      Blockly.highlightedConnectionBad_ = closestConnectionBad;
+      Blockly.localConnection_ = localConnection;
+    }
+    // end of BlocksCAD bad connection highlighting
+    // Provide visual indication of whether the block will be deleted if
+    // dropped here.
+    if (this.isDeletable()) {
+      this.workspace.isDeleteArea(e);
+    }
+  }
+  // This event has been handled.  No need to bubble up to the document.
+  e.stopPropagation();
+  e.preventDefault();
+};
+
+/**
+ * Add or remove the UI indicating if this block is movable or not.
+ */
+Blockly.BlockSvg.prototype.updateMovable = function() {
+  if (this.isMovable()) {
+    Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
+                      'blocklyDraggable');
+  } else {
+    Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
+                         'blocklyDraggable');
+  }
+};
+
+/**
+ * Set whether this block is movable or not.
+ * @param {boolean} movable True if movable.
+ */
+Blockly.BlockSvg.prototype.setMovable = function(movable) {
+  Blockly.BlockSvg.superClass_.setMovable.call(this, movable);
+  this.updateMovable();
+};
+
+/**
+ * Set whether this block is editable or not.
+ * @param {boolean} editable True if editable.
+ */
+Blockly.BlockSvg.prototype.setEditable = function(editable) {
+  Blockly.BlockSvg.superClass_.setEditable.call(this, editable);
+  var icons = this.getIcons();
+  for (var i = 0; i < icons.length; i++) {
+    icons[i].updateEditable();
+  }
+};
+
+/**
+ * Set whether this block is a shadow block or not.
+ * @param {boolean} shadow True if a shadow.
+ */
+Blockly.BlockSvg.prototype.setShadow = function(shadow) {
+  Blockly.BlockSvg.superClass_.setShadow.call(this, shadow);
+  this.updateColour();
+};
+
+/**
+ * Return the root node of the SVG or null if none exists.
+ * @return {Element} The root SVG node (probably a group).
+ */
+Blockly.BlockSvg.prototype.getSvgRoot = function() {
+  return this.svgGroup_;
+};
+
+/**
+ * Dispose of this block.
+ * @param {boolean} healStack If true, then try to heal any gap by connecting
+ *     the next statement with the previous statement.  Otherwise, dispose of
+ *     all children of this block.
+ * @param {boolean} animate If true, show a disposal animation and sound.
+ */
+Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
+  Blockly.Tooltip.hide();
+  Blockly.Field.startCache();
+  // Save the block's workspace temporarily so we can resize the
+  // contents once the block is disposed.
+  var blockWorkspace = this.workspace;
+  // If this block is being dragged, unlink the mouse events.
+  if (Blockly.selected == this) {
+    this.unselect();
+    Blockly.terminateDrag_();
+  }
+  // If this block has a context menu open, close it.
+  if (Blockly.ContextMenu.currentBlock == this) {
+    Blockly.ContextMenu.hide();
+  }
+
+  if (animate && this.rendered) {
+    this.unplug(healStack);
+    this.disposeUiEffect();
+  }
+  // Stop rerendering.
+  this.rendered = false;
+
+  Blockly.Events.disable();
+  try {
+    var icons = this.getIcons();
+    for (var i = 0; i < icons.length; i++) {
+      icons[i].dispose();
+    }
+  } finally {
+    Blockly.Events.enable();
+  }
+  Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
+
+  goog.dom.removeNode(this.svgGroup_);
+  Blockly.resizeSvgContents(blockWorkspace);
+  // Sever JavaScript to DOM connections.
+  this.svgGroup_ = null;
+  this.svgPath_ = null;
+  this.svgPathLight_ = null;
+  this.svgPathDark_ = null;
+  Blockly.Field.stopCache();
+};
+
+/**
+ * Play some UI effects (sound, animation) when disposing of a block.
+ */
+Blockly.BlockSvg.prototype.disposeUiEffect = function() {
+  this.workspace.playAudio('delete');
+
+  var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
+                             this.workspace);
+  // Deeply clone the current block.
+  var clone = this.svgGroup_.cloneNode(true);
+  clone.translateX_ = xy.x;
+  clone.translateY_ = xy.y;
+  clone.setAttribute('transform',
+      'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')');
+  this.workspace.getParentSvg().appendChild(clone);
+  clone.bBox_ = clone.getBBox();
+  // Start the animation.
+  Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date,
+      this.workspace.scale);
+};
+
+/**
+ * Animate a cloned block and eventually dispose of it.
+ * This is a class method, not an instace method since the original block has
+ * been destroyed and is no longer accessible.
+ * @param {!Element} clone SVG element to animate and dispose of.
+ * @param {boolean} rtl True if RTL, false if LTR.
+ * @param {!Date} start Date of animation's start.
+ * @param {number} workspaceScale Scale of workspace.
+ * @private
+ */
+Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
+  var ms = new Date - start;
+  var percent = ms / 150;
+  if (percent > 1) {
+    goog.dom.removeNode(clone);
+  } else {
+    var x = clone.translateX_ +
+        (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
+    var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
+    var scale = (1 - percent) * workspaceScale;
+    clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
+        ' scale(' + scale + ')');
+    var closure = function() {
+      Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale);
+    };
+    setTimeout(closure, 10);
+  }
+};
+
+/**
+ * Play some UI effects (sound, ripple) after a connection has been established.
+ */
+Blockly.BlockSvg.prototype.connectionUiEffect = function() {
+  this.workspace.playAudio('click');
+  if (this.workspace.scale < 1) {
+    return;  // Too small to care about visual effects.
+  }
+  // Determine the absolute coordinates of the inferior block.
+  var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
+                             this.workspace);
+  // Offset the coordinates based on the two connection types, fix scale.
+  if (this.outputConnection) {
+    xy.x += (this.RTL ? 3 : -3) * this.workspace.scale;
+    xy.y += 13 * this.workspace.scale;
+  } else if (this.previousConnection) {
+    xy.x += (this.RTL ? -23 : 23) * this.workspace.scale;
+    xy.y += 3 * this.workspace.scale;
+  }
+  var ripple = Blockly.createSvgElement('circle',
+      {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none',
+       'stroke': '#888', 'stroke-width': 10},
+      this.workspace.getParentSvg());
+  // Start the animation.
+  Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale);
+};
+
+/**
+ * Expand a ripple around a connection.
+ * @param {!Element} ripple Element to animate.
+ * @param {!Date} start Date of animation's start.
+ * @param {number} workspaceScale Scale of workspace.
+ * @private
+ */
+Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
+  var ms = new Date - start;
+  var percent = ms / 150;
+  if (percent > 1) {
+    goog.dom.removeNode(ripple);
+  } else {
+    ripple.setAttribute('r', percent * 25 * workspaceScale);
+    ripple.style.opacity = 1 - percent;
+    var closure = function() {
+      Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale);
+    };
+    Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10);
+  }
+};
+
+/**
+ * Play some UI effects (sound, animation) when disconnecting a block.
+ */
+Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
+  this.workspace.playAudio('disconnect');
+  if (this.workspace.scale < 1) {
+    return;  // Too small to care about visual effects.
+  }
+  // Horizontal distance for bottom of block to wiggle.
+  var DISPLACEMENT = 10;
+  // Scale magnitude of skew to height of block.
+  var height = this.getHeightWidth().height;
+  var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
+  if (!this.RTL) {
+    magnitude *= -1;
+  }
+  // Start the animation.
+  Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date);
+};
+
+/**
+ * Animate a brief wiggle of a disconnected block.
+ * @param {!Element} group SVG element to animate.
+ * @param {number} magnitude Maximum degrees skew (reversed for RTL).
+ * @param {!Date} start Date of animation's start.
+ * @private
+ */
+Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) {
+  var DURATION = 200;  // Milliseconds.
+  var WIGGLES = 3;  // Half oscillations.
+
+  var ms = new Date - start;
+  var percent = ms / DURATION;
+
+  if (percent > 1) {
+    group.skew_ = '';
+  } else {
+    var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) *
+        (1 - percent) * magnitude);
+    group.skew_ = 'skewX(' + skew + ')';
+    var closure = function() {
+      Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start);
+    };
+    Blockly.BlockSvg.disconnectUiStop_.group = group;
+    Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10);
+  }
+  group.setAttribute('transform', group.translate_ + group.skew_);
+};
+
+/**
+ * Stop the disconnect UI animation immediately.
+ * @private
+ */
+Blockly.BlockSvg.disconnectUiStop_ = function() {
+  if (Blockly.BlockSvg.disconnectUiStop_.group) {
+    clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid);
+    var group = Blockly.BlockSvg.disconnectUiStop_.group;
+    group.skew_ = '';
+    group.setAttribute('transform', group.translate_);
+    Blockly.BlockSvg.disconnectUiStop_.group = null;
+  }
+};
+
+/**
+ * PID of disconnect UI animation.  There can only be one at a time.
+ * @type {number}
+ */
+Blockly.BlockSvg.disconnectUiStop_.pid = 0;
+
+/**
+ * SVG group of wobbling block.  There can only be one at a time.
+ * @type {Element}
+ */
+Blockly.BlockSvg.disconnectUiStop_.group = null;
+
+/**
+ * Change the colour of a block.
+ */
+Blockly.BlockSvg.prototype.updateColour = function() {
+  if (this.disabled) {
+    // Disabled blocks don't have colour.
+    return;
+  }
+  var hexColour = this.getColour();
+  var rgb = goog.color.hexToRgb(hexColour);
+  if (this.isShadow()) {
+    rgb = goog.color.lighten(rgb, 0.6);
+    hexColour = goog.color.rgbArrayToHex(rgb);
+    this.svgPathLight_.style.display = 'none';
+    this.svgPathDark_.setAttribute('fill', hexColour);
+  } else {
+    this.svgPathLight_.style.display = '';
+    var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3));
+    var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2));
+    this.svgPathLight_.setAttribute('stroke', hexLight);
+    this.svgPathDark_.setAttribute('fill', hexDark);
+  }
+  this.svgPath_.setAttribute('fill', hexColour);
+
+  var icons = this.getIcons();
+  for (var i = 0; i < icons.length; i++) {
+    icons[i].updateColour();
+  }
+
+  // Bump every dropdown to change its colour.
+  for (var x = 0, input; input = this.inputList[x]; x++) {
+    for (var y = 0, field; field = input.fieldRow[y]; y++) {
+      field.setText(null);
+    }
+  }
+};
+
+/**
+ * Enable or disable a block.
+ */
+Blockly.BlockSvg.prototype.updateDisabled = function() {
+  var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_),
+                                   'blocklyDisabled');
+  if (this.disabled || this.getInheritedDisabled()) {
+    if (!hasClass) {
+      Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
+                        'blocklyDisabled');
+      this.svgPath_.setAttribute('fill',
+          'url(#' + this.workspace.options.disabledPatternId + ')');
+    }
+  } else {
+    if (hasClass) {
+      Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
+                           'blocklyDisabled');
+      this.updateColour();
+    }
+  }
+  var children = this.getChildren();
+  for (var i = 0, child; child = children[i]; i++) {
+    child.updateDisabled();
+  }
+};
+
+/**
+ * Returns the comment on this block (or '' if none).
+ * @return {string} Block's comment.
+ */
+Blockly.BlockSvg.prototype.getCommentText = function() {
+  if (this.comment) {
+    var comment = this.comment.getText();
+    // Trim off trailing whitespace.
+    return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
+  }
+  return '';
+};
+
+/**
+ * Set this block's comment text.
+ * @param {?string} text The text, or null to delete.
+ */
+Blockly.BlockSvg.prototype.setCommentText = function(text) {
+  var changedState = false;
+  if (goog.isString(text)) {
+    if (!this.comment) {
+      this.comment = new Blockly.Comment(this);
+      changedState = true;
+    }
+    this.comment.setText(/** @type {string} */ (text));
+  } else {
+    if (this.comment) {
+      this.comment.dispose();
+      changedState = true;
+    }
+  }
+  if (changedState && this.rendered) {
+    this.render();
+    // Adding or removing a comment icon will cause the block to change shape.
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Set this block's warning text.
+ * @param {?string} text The text, or null to delete.
+ * @param {string=} opt_id An optional ID for the warning text to be able to
+ *     maintain multiple warnings.
+ */
+Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
+  if (!this.setWarningText.pid_) {
+    // Create a database of warning PIDs.
+    // Only runs once per block (and only those with warnings).
+    this.setWarningText.pid_ = Object.create(null);
+  }
+  var id = opt_id || '';
+  if (!id) {
+    // Kill all previous pending processes, this edit supercedes them all.
+    for (var n in this.setWarningText.pid_) {
+      clearTimeout(this.setWarningText.pid_[n]);
+      delete this.setWarningText.pid_[n];
+    }
+  } else if (this.setWarningText.pid_[id]) {
+    // Only queue up the latest change.  Kill any earlier pending process.
+    clearTimeout(this.setWarningText.pid_[id]);
+    delete this.setWarningText.pid_[id];
+  }
+  if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
+    // Don't change the warning text during a drag.
+    // Wait until the drag finishes.
+    var thisBlock = this;
+    this.setWarningText.pid_[id] = setTimeout(function() {
+      if (thisBlock.workspace) {  // Check block wasn't deleted.
+        delete thisBlock.setWarningText.pid_[id];
+        thisBlock.setWarningText(text, id);
+      }
+    }, 100);
+    return;
+  }
+  if (this.isInFlyout) {
+    text = null;
+  }
+
+  // Bubble up to add a warning on top-most collapsed block.
+  var parent = this.getSurroundParent();
+  var collapsedParent = null;
+  while (parent) {
+    if (parent.isCollapsed()) {
+      collapsedParent = parent;
+    }
+    parent = parent.getSurroundParent();
+  }
+  if (collapsedParent) {
+    collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id);
+  }
+
+  var changedState = false;
+  if (goog.isString(text)) {
+    if (!this.warning) {
+      this.warning = new Blockly.Warning(this);
+      changedState = true;
+    }
+    this.warning.setText(/** @type {string} */ (text), id);
+  } else {
+    // Dispose all warnings if no id is given.
+    if (this.warning && !id) {
+      this.warning.dispose();
+      changedState = true;
+    } else if (this.warning) {
+      var oldText = this.warning.getText();
+      this.warning.setText('', id);
+      var newText = this.warning.getText();
+      if (!newText) {
+        this.warning.dispose();
+      }
+      changedState = oldText == newText;
+    }
+  }
+  if (changedState && this.rendered) {
+    this.render();
+    // Adding or removing a warning icon will cause the block to change shape.
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Give this block a mutator dialog.
+ * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
+ */
+Blockly.BlockSvg.prototype.setMutator = function(mutator) {
+  if (this.mutator && this.mutator !== mutator) {
+    this.mutator.dispose();
+  }
+  if (mutator) {
+    mutator.block_ = this;
+    this.mutator = mutator;
+    mutator.createIcon();
+  }
+};
+/**
+ * Give this block a mutator dialog.
+ * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
+ */
+Blockly.BlockSvg.prototype.setMutatorPlus = function(mutatorPlus) {
+  if (this.mutatorPlus && this.mutatorPlus !== mutatorPlus) {
+    this.mutatorPlus.dispose();
+  }
+  if (mutatorPlus) {
+    mutatorPlus.block_ = this;
+    this.mutatorPlus = mutatorPlus;
+    if (this.rendered) {
+      mutatorPlus.createIcon();
+    }
+  }
+};
+/**
+ * Give this block a mutator dialog.
+ * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
+ */
+Blockly.BlockSvg.prototype.setMutatorMinus = function(mutatorMinus) {
+  if (this.mutatorMinus && this.mutatorMinus !== mutatorMinus) {
+    this.mutatorMinus.dispose();
+  }
+  if (mutatorMinus) {
+    mutatorMinus.block_ = this;
+    this.mutatorMinus = mutatorMinus;
+    if (this.rendered) {
+      mutatorMinus.createIcon();
+    }
+  }
+};
+/**
+ * Set whether the block is disabled or not.
+ * @param {boolean} disabled True if disabled.
+ */
+Blockly.BlockSvg.prototype.setDisabled = function(disabled) {
+  if (this.disabled != disabled) {
+    Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled);
+    if (this.rendered) {
+      this.updateDisabled();
+    }
+  }
+};
+
+/**
+ * Select this block.  Highlight it visually.
+ */
+Blockly.BlockSvg.prototype.addSelect = function() {
+  Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
+                    'blocklySelected');
+  // Move the selected block to the top of the stack.
+  var block = this;
+  do {
+    var root = block.getSvgRoot();
+    root.parentNode.appendChild(root);
+    block = block.getParent();
+  } while (block);
+};
+
+/**
+ * Unselect this block.  Remove its highlighting.
+ */
+Blockly.BlockSvg.prototype.removeSelect = function() {
+  Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
+                       'blocklySelected');
+};
+
+/**
+ * Backlight this block.  Highlight it visually. Added for BlocksCAD
+ */
+Blockly.BlockSvg.prototype.addBacklight = function(color) {
+  Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
+                    'blocklyBacklight');
+  // Move the selected block to the top of the stack.
+  this.svgGroup_.parentNode.appendChild(this.svgGroup_);
+};
+
+/**
+ * Unbacklight this block.  Remove its highlighting. Added for BlocksCAD
+ */
+Blockly.BlockSvg.prototype.removeBacklight = function(color) {
+  Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
+                       'blocklyBacklight');
+};
+/**
+ * Adds the dragging class to this block.
+ * Also disables the highlights/shadows to improve performance.
+ */
+Blockly.BlockSvg.prototype.addDragging = function() {
+  Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
+                    'blocklyDragging');
+};
+
+/**
+ * Removes the dragging class from this block.
+ */
+Blockly.BlockSvg.prototype.removeDragging = function() {
+  Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
+                       'blocklyDragging');
+};
+
+// Overrides of functions on Blockly.Block that take into account whether the
+// block has been rendered.
+
+/**
+ * Change the colour of a block.
+ * @param {number|string} colour HSV hue value, or #RRGGBB string.
+ */
+Blockly.BlockSvg.prototype.setColour = function(colour) {
+  Blockly.BlockSvg.superClass_.setColour.call(this, colour);
+
+  if (this.rendered) {
+    this.updateColour();
+  }
+};
+
+/**
+ * Set whether this block can chain onto the bottom of another block.
+ * @param {boolean} newBoolean True if there can be a previous statement.
+ * @param {string|Array.<string>|null|undefined} opt_check Statement type or
+ *     list of statement types.  Null/undefined if any type could be connected.
+ */
+Blockly.BlockSvg.prototype.setPreviousStatement =
+    function(newBoolean, opt_check) {
+  /* eslint-disable indent */
+  Blockly.BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean,
+      opt_check);
+
+  if (this.rendered) {
+    this.render();
+    this.bumpNeighbours_();
+  }
+};  /* eslint-enable indent */
+
+/**
+ * Set whether another block can chain onto the bottom of this block.
+ * @param {boolean} newBoolean True if there can be a next statement.
+ * @param {string|Array.<string>|null|undefined} opt_check Statement type or
+ *     list of statement types.  Null/undefined if any type could be connected.
+ */
+Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) {
+  Blockly.BlockSvg.superClass_.setNextStatement.call(this, newBoolean,
+      opt_check);
+
+  if (this.rendered) {
+    this.render();
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Set whether this block returns a value.
+ * @param {boolean} newBoolean True if there is an output.
+ * @param {string|Array.<string>|null|undefined} opt_check Returned type or list
+ *     of returned types.  Null or undefined if any type could be returned
+ *     (e.g. variable get).
+ */
+Blockly.BlockSvg.prototype.setOutput = function(newBoolean, opt_check) {
+  Blockly.BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check);
+
+  if (this.rendered) {
+    this.render();
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Set whether value inputs are arranged horizontally or vertically.
+ * @param {boolean} newBoolean True if inputs are horizontal.
+ */
+Blockly.BlockSvg.prototype.setInputsInline = function(newBoolean) {
+  Blockly.BlockSvg.superClass_.setInputsInline.call(this, newBoolean);
+
+  if (this.rendered) {
+    this.render();
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Remove an input from this block.
+ * @param {string} name The name of the input.
+ * @param {boolean=} opt_quiet True to prevent error if input is not present.
+ * @throws {goog.asserts.AssertionError} if the input is not present and
+ *     opt_quiet is not true.
+ */
+Blockly.BlockSvg.prototype.removeInput = function(name, opt_quiet) {
+  Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet);
+
+  if (this.rendered) {
+    this.render();
+    // Removing an input will cause the block to change shape.
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Move a numbered input to a different location on this block.
+ * @param {number} inputIndex Index of the input to move.
+ * @param {number} refIndex Index of input that should be after the moved input.
+ */
+Blockly.BlockSvg.prototype.moveNumberedInputBefore = function(
+    inputIndex, refIndex) {
+  Blockly.BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex,
+      refIndex);
+
+  if (this.rendered) {
+    this.render();
+    // Moving an input will cause the block to change shape.
+    this.bumpNeighbours_();
+  }
+};
+
+/**
+ * Add a value input, statement input or local variable to this block.
+ * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or
+ *     Blockly.DUMMY_INPUT.
+ * @param {string} name Language-neutral identifier which may used to find this
+ *     input again.  Should be unique to this block.
+ * @return {!Blockly.Input} The input object created.
+ * @private
+ */
+Blockly.BlockSvg.prototype.appendInput_ = function(type, name) {
+  var input = Blockly.BlockSvg.superClass_.appendInput_.call(this, type, name);
+
+  if (this.rendered) {
+    this.render();
+    // Adding an input will cause the block to change shape.
+    this.bumpNeighbours_();
+  }
+  return input;
+};
+
+/**
+ * Returns connections originating from this block.
+ * @param {boolean} all If true, return all connections even hidden ones.
+ *     Otherwise, for a non-rendered block return an empty list, and for a
+ *     collapsed block don't return inputs connections.
+ * @return {!Array.<!Blockly.Connection>} Array of connections.
+ * @private
+ */
+Blockly.BlockSvg.prototype.getConnections_ = function(all) {
+  var myConnections = [];
+  if (all || this.rendered) {
+    if (this.outputConnection) {
+      myConnections.push(this.outputConnection);
+    }
+    if (this.previousConnection) {
+      myConnections.push(this.previousConnection);
+    }
+    if (this.nextConnection) {
+      myConnections.push(this.nextConnection);
+    }
+    if (all || !this.collapsed_) {
+      for (var i = 0, input; input = this.inputList[i]; i++) {
+        if (input.connection) {
+          myConnections.push(input.connection);
+        }
+      }
+    }
+  }
+  return myConnections;
+};
+
+/**
+ * Create a connection of the specified type.
+ * @param {number} type The type of the connection to create.
+ * @return {!Blockly.RenderedConnection} A new connection of the specified type.
+ * @private
+ */
+Blockly.BlockSvg.prototype.makeConnection_ = function(type) {
+  return new Blockly.RenderedConnection(this, type);
+};

+ 590 - 0
blockly/core/blockly.js

@@ -0,0 +1,590 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Core JavaScript library for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+// Top level object for Blockly.
+goog.provide('Blockly');
+
+goog.require('Blockly.BlockSvg.render');
+goog.require('Blockly.Events');
+goog.require('Blockly.FieldAngle');
+goog.require('Blockly.FieldCheckbox');
+goog.require('Blockly.FieldColour');
+// Date picker commented out since it increases footprint by 60%.
+// Add it only if you need it.
+//goog.require('Blockly.FieldDate');
+goog.require('Blockly.FieldDropdown');
+goog.require('Blockly.FieldImage');
+goog.require('Blockly.FieldTextInput');
+goog.require('Blockly.FieldNumber');
+goog.require('Blockly.FieldVariable');
+goog.require('Blockly.Generator');
+goog.require('Blockly.Msg');
+goog.require('Blockly.Procedures');
+goog.require('Blockly.Toolbox');
+goog.require('Blockly.WidgetDiv');
+goog.require('Blockly.WorkspaceSvg');
+goog.require('Blockly.constants');
+goog.require('Blockly.inject');
+goog.require('Blockly.utils');
+goog.require('goog.color');
+goog.require('goog.userAgent');
+
+
+// Turn off debugging when compiled.
+var CLOSURE_DEFINES = {'goog.DEBUG': false};
+
+/**
+ * The main workspace most recently used.
+ * Set by Blockly.WorkspaceSvg.prototype.markFocused
+ * @type {Blockly.Workspace}
+ */
+Blockly.mainWorkspace = null;
+
+/**
+ * Currently selected block.
+ * @type {Blockly.Block}
+ */
+Blockly.selected = null;
+
+/**
+ * Currently highlighted connection (during a drag).
+ * @type {Blockly.Connection}
+ * @private
+ */
+Blockly.highlightedConnection_ = null;
+
+/**
+ * List of "backlight" blocks (drawn with highlighting, not selectable)
+ * @type {Blockly.Block}
+ * Added for BlocksCAD typing system
+ */
+Blockly.backlight = [];
+
+/**
+ * Currently highlighted illegal connection (during a drag). For BlocksCAD!
+ * @type {Blockly.Connection}
+ * @private
+ */
+Blockly.highlightedConnectionBad_ = null;
+
+/**
+ * Connection on dragged block that matches the highlighted connection.
+ * @type {Blockly.Connection}
+ * @private
+ */
+Blockly.localConnection_ = null;
+
+/**
+ * All of the connections on blocks that are currently being dragged.
+ * @type {!Array.<!Blockly.Connection>}
+ * @private
+ */
+Blockly.draggingConnections_ = [];
+
+/**
+ * Contents of the local clipboard.
+ * @type {Element}
+ * @private
+ */
+Blockly.clipboardXml_ = null;
+
+/**
+ * Source of the local clipboard.
+ * @type {Blockly.WorkspaceSvg}
+ * @private
+ */
+Blockly.clipboardSource_ = null;
+
+/**
+ * Is the mouse dragging a block?
+ * DRAG_NONE - No drag operation.
+ * DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
+ * DRAG_FREE - Freely draggable.
+ * @private
+ */
+Blockly.dragMode_ = Blockly.DRAG_NONE;
+
+/**
+ * Wrapper function called when a touch mouseUp occurs during a drag operation.
+ * @type {Array.<!Array>}
+ * @private
+ */
+Blockly.onTouchUpWrapper_ = null;
+
+/**
+ * Convert a hue (HSV model) into an RGB hex triplet.
+ * @param {number} hue Hue on a colour wheel (0-360).
+ * @return {string} RGB code, e.g. '#5ba65b'.
+ */
+Blockly.hueToRgb = function(hue) {
+  return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
+      Blockly.HSV_VALUE * 255);
+};
+
+/**
+ * Returns the dimensions of the specified SVG image.
+ * @param {!Element} svg SVG image.
+ * @return {!Object} Contains width and height properties.
+ */
+Blockly.svgSize = function(svg) {
+  return {width: svg.cachedWidth_,
+          height: svg.cachedHeight_};
+};
+
+/**
+ * Size the workspace when the contents change.  This also updates
+ * scrollbars accordingly.
+ * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
+ */
+Blockly.resizeSvgContents = function(workspace) {
+  workspace.resizeContents();
+};
+
+
+/**
+ * Size the SVG image to completely fill its container. Call this when the view
+ * actually changes sizes (e.g. on a window resize/device orientation change).
+ * See Blockly.resizeSvgContents to resize the workspace when the contents
+ * change (e.g. when a block is added or removed).
+ * Record the height/width of the SVG image.
+ * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
+ */
+Blockly.svgResize = function(workspace) {
+  var mainWorkspace = workspace;
+  while (mainWorkspace.options.parentWorkspace) {
+    mainWorkspace = mainWorkspace.options.parentWorkspace;
+  }
+  var svg = mainWorkspace.getParentSvg();
+  var div = svg.parentNode;
+  if (!div) {
+    // Workspace deleted, or something.
+    return;
+  }
+  var width = div.offsetWidth;
+  var height = div.offsetHeight;
+  if (svg.cachedWidth_ != width) {
+    svg.setAttribute('width', width + 'px');
+    svg.cachedWidth_ = width;
+  }
+  if (svg.cachedHeight_ != height) {
+    svg.setAttribute('height', height + 'px');
+    svg.cachedHeight_ = height;
+  }
+  mainWorkspace.resize();
+};
+
+/**
+ * Handle a mouse-up anywhere on the page.
+ * @param {!Event} e Mouse up event.
+ * @private
+ */
+Blockly.onMouseUp_ = function(e) {
+  var workspace = Blockly.getMainWorkspace();
+  Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
+  workspace.dragMode_ = Blockly.DRAG_NONE;
+  // Unbind the touch event if it exists.
+  if (Blockly.onTouchUpWrapper_) {
+    Blockly.unbindEvent_(Blockly.onTouchUpWrapper_);
+    Blockly.onTouchUpWrapper_ = null;
+  }
+  if (Blockly.onMouseMoveWrapper_) {
+    Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_);
+    Blockly.onMouseMoveWrapper_ = null;
+  }
+};
+
+/**
+ * Handle a mouse-move on SVG drawing surface.
+ * @param {!Event} e Mouse move event.
+ * @private
+ */
+Blockly.onMouseMove_ = function(e) {
+  if (e.touches && e.touches.length >= 2) {
+    return;  // Multi-touch gestures won't have e.clientX.
+  }
+  var workspace = Blockly.getMainWorkspace();
+  if (workspace.dragMode_ != Blockly.DRAG_NONE) {
+    var dx = e.clientX - workspace.startDragMouseX;
+    var dy = e.clientY - workspace.startDragMouseY;
+    var metrics = workspace.startDragMetrics;
+    var x = workspace.startScrollX + dx;
+    var y = workspace.startScrollY + dy;
+    x = Math.min(x, -metrics.contentLeft);
+    y = Math.min(y, -metrics.contentTop);
+    x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
+                 metrics.contentWidth);
+    y = Math.max(y, metrics.viewHeight - metrics.contentTop -
+                 metrics.contentHeight);
+
+    // Move the scrollbars and the page will scroll automatically.
+    workspace.scrollbar.set(-x - metrics.contentLeft,
+                            -y - metrics.contentTop);
+    // Cancel the long-press if the drag has moved too far.
+    if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
+      Blockly.longStop_();
+      workspace.dragMode_ = Blockly.DRAG_FREE;
+    }
+    e.stopPropagation();
+    e.preventDefault();
+  }
+};
+
+/**
+ * Handle a key-down on SVG drawing surface.
+ * @param {!Event} e Key down event.
+ * @private
+ */
+Blockly.onKeyDown_ = function(e) {
+  if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
+    // No key actions on readonly workspaces.
+    // When focused on an HTML text input widget, don't trap any keys.
+    return;
+  }
+  var deleteBlock = false;
+  if (e.keyCode == 27) {
+    // Pressing esc closes the context menu.
+    Blockly.hideChaff();
+  } else if (e.keyCode == 8 || e.keyCode == 46) {
+    // Delete or backspace.
+    // Stop the browser from going back to the previous page.
+    // Do this first to prevent an error in the delete code from resulting in
+    // data loss.
+    e.preventDefault();
+    if (Blockly.selected && Blockly.selected.isDeletable()) {
+      deleteBlock = true;
+    }
+  } else if (e.altKey || e.ctrlKey || e.metaKey) {
+    if (Blockly.selected &&
+        Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
+      if (e.keyCode == 67) {
+        // 'c' for copy.
+        Blockly.hideChaff();
+        Blockly.copy_(Blockly.selected);
+      } else if (e.keyCode == 88) {
+        // 'x' for cut.
+        Blockly.copy_(Blockly.selected);
+        deleteBlock = true;
+      }
+    }
+    if (e.keyCode == 86) {
+      // 'v' for paste.
+      if (Blockly.clipboardXml_) {
+        Blockly.Events.setGroup(true);
+        Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
+        Blockly.Events.setGroup(false);
+      }
+    } else if (e.keyCode == 90) {
+      // 'z' for undo 'Z' is for redo.
+      Blockly.hideChaff();
+      Blockly.mainWorkspace.undo(e.shiftKey);
+    }
+  }
+  if (deleteBlock) {
+    // Common code for delete and cut.
+    Blockly.Events.setGroup(true);
+    Blockly.hideChaff();
+    var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
+    Blockly.selected.dispose(heal, true);
+    if (Blockly.highlightedConnection_) {
+      Blockly.highlightedConnection_.unhighlight();
+      Blockly.highlightedConnection_ = null;
+    }
+    Blockly.Events.setGroup(false);
+  }
+};
+
+/**
+ * Stop binding to the global mouseup and mousemove events.
+ * @private
+ */
+Blockly.terminateDrag_ = function() {
+  Blockly.BlockSvg.terminateDrag();
+  Blockly.Flyout.terminateDrag_();
+};
+
+/**
+ * PID of queued long-press task.
+ * @private
+ */
+Blockly.longPid_ = 0;
+
+/**
+ * Context menus on touch devices are activated using a long-press.
+ * Unfortunately the contextmenu touch event is currently (2015) only suported
+ * by Chrome.  This function is fired on any touchstart event, queues a task,
+ * which after about a second opens the context menu.  The tasks is killed
+ * if the touch event terminates early.
+ * @param {!Event} e Touch start event.
+ * @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace
+ *   under the touchstart event.
+ * @private
+ */
+Blockly.longStart_ = function(e, uiObject) {
+  Blockly.longStop_();
+  Blockly.longPid_ = setTimeout(function() {
+    e.button = 2;  // Simulate a right button click.
+    uiObject.onMouseDown_(e);
+  }, Blockly.LONGPRESS);
+};
+
+/**
+ * Nope, that's not a long-press.  Either touchend or touchcancel was fired,
+ * or a drag hath begun.  Kill the queued long-press task.
+ * @private
+ */
+Blockly.longStop_ = function() {
+  if (Blockly.longPid_) {
+    clearTimeout(Blockly.longPid_);
+    Blockly.longPid_ = 0;
+  }
+};
+
+/**
+ * Copy a block onto the local clipboard.
+ * @param {!Blockly.Block} block Block to be copied.
+ * @private
+ */
+Blockly.copy_ = function(block) {
+  var xmlBlock = Blockly.Xml.blockToDom(block);
+  if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
+    Blockly.Xml.deleteNext(xmlBlock);
+  }
+  // Encode start position in XML.
+  var xy = block.getRelativeToSurfaceXY();
+  xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
+  xmlBlock.setAttribute('y', xy.y);
+  Blockly.clipboardXml_ = xmlBlock;
+  Blockly.clipboardSource_ = block.workspace;
+};
+
+/**
+ * Duplicate this block and its children.
+ * @param {!Blockly.Block} block Block to be copied.
+ * @private
+ */
+Blockly.duplicate_ = function(block) {
+  // Save the clipboard.
+  var clipboardXml = Blockly.clipboardXml_;
+  var clipboardSource = Blockly.clipboardSource_;
+
+  // Create a duplicate via a copy/paste operation.
+  Blockly.copy_(block);
+  block.workspace.paste(Blockly.clipboardXml_);
+
+  // Restore the clipboard.
+  Blockly.clipboardXml_ = clipboardXml;
+  Blockly.clipboardSource_ = clipboardSource;
+};
+
+/**
+ * Cancel the native context menu, unless the focus is on an HTML input widget.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.onContextMenu_ = function(e) {
+  if (!Blockly.isTargetInput_(e)) {
+    // When focused on an HTML text input widget, don't cancel the context menu.
+    e.preventDefault();
+  }
+};
+
+/**
+ * Close tooltips, context menus, dropdown selections, etc.
+ * @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
+ */
+Blockly.hideChaff = function(opt_allowToolbox) {
+  Blockly.Tooltip.hide();
+  Blockly.WidgetDiv.hide();
+  if (!opt_allowToolbox) {
+    var workspace = Blockly.getMainWorkspace();
+    if (workspace.toolbox_ &&
+        workspace.toolbox_.flyout_ &&
+        workspace.toolbox_.flyout_.autoClose) {
+      workspace.toolbox_.clearSelection();
+    }
+  }
+};
+
+/**
+ * Return an object with all the metrics required to size scrollbars for the
+ * main workspace.  The following properties are computed:
+ * .viewHeight: Height of the visible rectangle,
+ * .viewWidth: Width of the visible rectangle,
+ * .contentHeight: Height of the contents,
+ * .contentWidth: Width of the content,
+ * .viewTop: Offset of top edge of visible rectangle from parent,
+ * .viewLeft: Offset of left edge of visible rectangle from parent,
+ * .contentTop: Offset of the top-most content from the y=0 coordinate,
+ * .contentLeft: Offset of the left-most content from the x=0 coordinate.
+ * .absoluteTop: Top-edge of view.
+ * .absoluteLeft: Left-edge of view.
+ * .toolboxWidth: Width of toolbox, if it exists.  Otherwise zero.
+ * .toolboxHeight: Height of toolbox, if it exists.  Otherwise zero.
+ * .flyoutWidth: Width of the flyout if it is always open.  Otherwise zero.
+ * .flyoutHeight: Height of flyout if it is always open.  Otherwise zero.
+ * .toolboxPosition: Top, bottom, left or right.
+ * @return {Object} Contains size and position metrics of main workspace.
+ * @private
+ * @this Blockly.WorkspaceSvg
+ */
+Blockly.getMainWorkspaceMetrics_ = function() {
+  var svgSize = Blockly.svgSize(this.getParentSvg());
+  if (this.toolbox_) {
+    if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ||
+        this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
+      svgSize.height -= this.toolbox_.getHeight();
+    } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
+        this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
+      svgSize.width -= this.toolbox_.getWidth();
+    }
+  }
+  // Set the margin to match the flyout's margin so that the workspace does
+  // not jump as blocks are added.
+  var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
+  var viewWidth = svgSize.width - MARGIN;
+  var viewHeight = svgSize.height - MARGIN;
+  var blockBox = this.getBlocksBoundingBox();
+
+  // Fix scale.
+  var contentWidth = blockBox.width * this.scale;
+  var contentHeight = blockBox.height * this.scale;
+  var contentX = blockBox.x * this.scale;
+  var contentY = blockBox.y * this.scale;
+  if (this.scrollbar) {
+    // Add a border around the content that is at least half a screenful wide.
+    // Ensure border is wide enough that blocks can scroll over entire screen.
+    var leftEdge = Math.min(contentX - viewWidth / 2,
+                            contentX + contentWidth - viewWidth);
+    var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2,
+                             contentX + viewWidth);
+    var topEdge = Math.min(contentY - viewHeight / 2,
+                           contentY + contentHeight - viewHeight);
+    var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2,
+                              contentY + viewHeight);
+  } else {
+    var leftEdge = blockBox.x;
+    var rightEdge = leftEdge + blockBox.width;
+    var topEdge = blockBox.y;
+    var bottomEdge = topEdge + blockBox.height;
+  }
+  var absoluteLeft = 0;
+  if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
+    absoluteLeft = this.toolbox_.getWidth();
+  }
+  var absoluteTop = 0;
+  if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
+    absoluteTop = this.toolbox_.getHeight();
+  }
+
+  var metrics = {
+    viewHeight: svgSize.height,
+    viewWidth: svgSize.width,
+    contentHeight: bottomEdge - topEdge,
+    contentWidth: rightEdge - leftEdge,
+    viewTop: -this.scrollY,
+    viewLeft: -this.scrollX,
+    contentTop: topEdge,
+    contentLeft: leftEdge,
+    absoluteTop: absoluteTop,
+    absoluteLeft: absoluteLeft,
+    toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0,
+    toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0,
+    flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0,
+    flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0,
+    toolboxPosition: this.toolboxPosition
+  };
+  return metrics;
+};
+
+/**
+ * Sets the X/Y translations of the main workspace to match the scrollbars.
+ * @param {!Object} xyRatio Contains an x and/or y property which is a float
+ *     between 0 and 1 specifying the degree of scrolling.
+ * @private
+ * @this Blockly.WorkspaceSvg
+ */
+Blockly.setMainWorkspaceMetrics_ = function(xyRatio) {
+  if (!this.scrollbar) {
+    throw 'Attempt to set main workspace scroll without scrollbars.';
+  }
+  var metrics = this.getMetrics();
+  if (goog.isNumber(xyRatio.x)) {
+    this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
+  }
+  if (goog.isNumber(xyRatio.y)) {
+    this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
+  }
+  var x = this.scrollX + metrics.absoluteLeft;
+  var y = this.scrollY + metrics.absoluteTop;
+  this.translate(x, y);
+  if (this.options.gridPattern) {
+    this.options.gridPattern.setAttribute('x', x);
+    this.options.gridPattern.setAttribute('y', y);
+    if (goog.userAgent.IE) {
+      // IE doesn't notice that the x/y offsets have changed.  Force an update.
+      this.updateGridPattern_();
+    }
+  }
+};
+
+/**
+ * When something in Blockly's workspace changes, call a function.
+ * @param {!Function} func Function to call.
+ * @return {!Array.<!Array>} Opaque data that can be passed to
+ *     removeChangeListener.
+ * @deprecated April 2015
+ */
+Blockly.addChangeListener = function(func) {
+  // Backwards compatability from before there could be multiple workspaces.
+  console.warn('Deprecated call to Blockly.addChangeListener, ' +
+               'use workspace.addChangeListener instead.');
+  return Blockly.getMainWorkspace().addChangeListener(func);
+};
+
+/**
+ * Returns the main workspace.  Returns the last used main workspace (based on
+ * focus).  Try not to use this function, particularly if there are multiple
+ * Blockly instances on a page.
+ * @return {!Blockly.Workspace} The main workspace.
+ */
+Blockly.getMainWorkspace = function() {
+  return Blockly.mainWorkspace;
+};
+
+// IE9 does not have a console.  Create a stub to stop errors.
+if (!goog.global['console']) {
+  goog.global['console'] = {
+    'log': function() {},
+    'warn': function() {}
+  };
+}
+
+// Export symbols that would otherwise be renamed by Closure compiler.
+if (!goog.global['Blockly']) {
+  goog.global['Blockly'] = {};
+}
+goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
+goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;

+ 33 - 0
blockly/core/blocks.js

@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2013 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 Empty name space for the Blocks singleton.
+ * @author spertus@google.com (Ellen Spertus)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks');
+
+/**
+ * Allow for switching between one and zero based indexing for lists and text,
+ * one based by default.
+ */
+Blockly.Blocks.ONE_BASED_INDEXING = true;

+ 579 - 0
blockly/core/bubble.js

@@ -0,0 +1,579 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Object representing a UI bubble.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Bubble');
+
+goog.require('Blockly.Workspace');
+goog.require('goog.dom');
+goog.require('goog.math');
+goog.require('goog.math.Coordinate');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for UI bubble.
+ * @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the
+ *     bubble.
+ * @param {!Element} content SVG content for the bubble.
+ * @param {Element} shape SVG element to avoid eclipsing.
+ * @param {!goog.math.Coodinate} anchorXY Absolute position of bubble's anchor
+ *     point.
+ * @param {?number} bubbleWidth Width of bubble, or null if not resizable.
+ * @param {?number} bubbleHeight Height of bubble, or null if not resizable.
+ * @constructor
+ */
+Blockly.Bubble = function(workspace, content, shape, anchorXY,
+                          bubbleWidth, bubbleHeight) {
+  this.workspace_ = workspace;
+  this.content_ = content;
+  this.shape_ = shape;
+
+  var angle = Blockly.Bubble.ARROW_ANGLE;
+  if (this.workspace_.RTL) {
+    angle = -angle;
+  }
+  this.arrow_radians_ = goog.math.toRadians(angle);
+
+  var canvas = workspace.getBubbleCanvas();
+  canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
+
+  this.setAnchorLocation(anchorXY);
+  if (!bubbleWidth || !bubbleHeight) {
+    var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
+    bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH;
+    bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH;
+  }
+  this.setBubbleSize(bubbleWidth, bubbleHeight);
+
+  // Render the bubble.
+  this.positionBubble_();
+  this.renderArrow_();
+  this.rendered_ = true;
+
+  if (!workspace.options.readOnly) {
+    Blockly.bindEvent_(this.bubbleBack_, 'mousedown', this,
+                       this.bubbleMouseDown_);
+    if (this.resizeGroup_) {
+      Blockly.bindEvent_(this.resizeGroup_, 'mousedown', this,
+                         this.resizeMouseDown_);
+    }
+  }
+};
+
+/**
+ * Width of the border around the bubble.
+ */
+Blockly.Bubble.BORDER_WIDTH = 6;
+
+/**
+ * Determines the thickness of the base of the arrow in relation to the size
+ * of the bubble.  Higher numbers result in thinner arrows.
+ */
+Blockly.Bubble.ARROW_THICKNESS = 10;
+
+/**
+ * The number of degrees that the arrow bends counter-clockwise.
+ */
+Blockly.Bubble.ARROW_ANGLE = 20;
+
+/**
+ * The sharpness of the arrow's bend.  Higher numbers result in smoother arrows.
+ */
+Blockly.Bubble.ARROW_BEND = 4;
+
+/**
+ * Distance between arrow point and anchor point.
+ */
+Blockly.Bubble.ANCHOR_RADIUS = 8;
+
+/**
+ * Wrapper function called when a mouseUp occurs during a drag operation.
+ * @type {Array.<!Array>}
+ * @private
+ */
+Blockly.Bubble.onMouseUpWrapper_ = null;
+
+/**
+ * Wrapper function called when a mouseMove occurs during a drag operation.
+ * @type {Array.<!Array>}
+ * @private
+ */
+Blockly.Bubble.onMouseMoveWrapper_ = null;
+
+/**
+ * Function to call on resize of bubble.
+ * @type {Function}
+ */
+Blockly.Bubble.prototype.resizeCallback_ = null;
+
+/**
+ * Stop binding to the global mouseup and mousemove events.
+ * @private
+ */
+Blockly.Bubble.unbindDragEvents_ = function() {
+  if (Blockly.Bubble.onMouseUpWrapper_) {
+    Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_);
+    Blockly.Bubble.onMouseUpWrapper_ = null;
+  }
+  if (Blockly.Bubble.onMouseMoveWrapper_) {
+    Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_);
+    Blockly.Bubble.onMouseMoveWrapper_ = null;
+  }
+};
+
+/**
+ * Flag to stop incremental rendering during construction.
+ * @private
+ */
+Blockly.Bubble.prototype.rendered_ = false;
+
+/**
+ * Absolute coordinate of anchor point.
+ * @type {goog.math.Coordinate}
+ * @private
+ */
+Blockly.Bubble.prototype.anchorXY_ = null;
+
+/**
+ * Relative X coordinate of bubble with respect to the anchor's centre.
+ * In RTL mode the initial value is negated.
+ * @private
+ */
+Blockly.Bubble.prototype.relativeLeft_ = 0;
+
+/**
+ * Relative Y coordinate of bubble with respect to the anchor's centre.
+ * @private
+ */
+Blockly.Bubble.prototype.relativeTop_ = 0;
+
+/**
+ * Width of bubble.
+ * @private
+ */
+Blockly.Bubble.prototype.width_ = 0;
+
+/**
+ * Height of bubble.
+ * @private
+ */
+Blockly.Bubble.prototype.height_ = 0;
+
+/**
+ * Automatically position and reposition the bubble.
+ * @private
+ */
+Blockly.Bubble.prototype.autoLayout_ = true;
+
+/**
+ * Create the bubble's DOM.
+ * @param {!Element} content SVG content for the bubble.
+ * @param {boolean} hasResize Add diagonal resize gripper if true.
+ * @return {!Element} The bubble's SVG group.
+ * @private
+ */
+Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
+  /* Create the bubble.  Here's the markup that will be generated:
+  <g>
+    <g filter="url(#blocklyEmbossFilter837493)">
+      <path d="... Z" />
+      <rect class="blocklyDraggable" rx="8" ry="8" width="180" height="180"/>
+    </g>
+    <g transform="translate(165, 165)" class="blocklyResizeSE">
+      <polygon points="0,15 15,15 15,0"/>
+      <line class="blocklyResizeLine" x1="5" y1="14" x2="14" y2="5"/>
+      <line class="blocklyResizeLine" x1="10" y1="14" x2="14" y2="10"/>
+    </g>
+    [...content goes here...]
+  </g>
+  */
+  this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null);
+  var filter =
+      {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'};
+  if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) {
+    // Multiple reports that JavaFX can't handle filters.  UserAgent:
+    // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44
+    //     (KHTML, like Gecko) JavaFX/8.0 Safari/537.44
+    // https://github.com/google/blockly/issues/99
+    filter = {};
+  }
+  var bubbleEmboss = Blockly.createSvgElement('g',
+      filter, this.bubbleGroup_);
+  this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss);
+  this.bubbleBack_ = Blockly.createSvgElement('rect',
+      {'class': 'blocklyDraggable', 'x': 0, 'y': 0,
+      'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH},
+      bubbleEmboss);
+  if (hasResize) {
+    this.resizeGroup_ = Blockly.createSvgElement('g',
+        {'class': this.workspace_.RTL ?
+                  'blocklyResizeSW' : 'blocklyResizeSE'},
+        this.bubbleGroup_);
+    var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
+    Blockly.createSvgElement('polygon',
+        {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
+        this.resizeGroup_);
+    Blockly.createSvgElement('line',
+        {'class': 'blocklyResizeLine',
+        'x1': resizeSize / 3, 'y1': resizeSize - 1,
+        'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_);
+    Blockly.createSvgElement('line',
+        {'class': 'blocklyResizeLine',
+        'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1,
+        'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_);
+  } else {
+    this.resizeGroup_ = null;
+  }
+  this.bubbleGroup_.appendChild(content);
+  return this.bubbleGroup_;
+};
+
+/**
+ * Handle a mouse-down on bubble's border.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
+  this.promote_();
+  Blockly.Bubble.unbindDragEvents_();
+  if (Blockly.isRightButton(e)) {
+    // No right-click.
+    e.stopPropagation();
+    return;
+  } else if (Blockly.isTargetInput_(e)) {
+    // When focused on an HTML text input widget, don't trap any events.
+    return;
+  }
+  // Left-click (or middle click)
+  Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
+
+  this.workspace_.startDrag(e, new goog.math.Coordinate(
+      this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_,
+      this.relativeTop_));
+
+  Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document,
+      'mouseup', this, Blockly.Bubble.unbindDragEvents_);
+  Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
+      'mousemove', this, this.bubbleMouseMove_);
+  Blockly.hideChaff();
+  // This event has been handled.  No need to bubble up to the document.
+  e.stopPropagation();
+};
+
+/**
+ * Drag this bubble to follow the mouse.
+ * @param {!Event} e Mouse move event.
+ * @private
+ */
+Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) {
+  this.autoLayout_ = false;
+  var newXY = this.workspace_.moveDrag(e);
+  this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x;
+  this.relativeTop_ = newXY.y;
+  this.positionBubble_();
+  this.renderArrow_();
+};
+
+/**
+ * Handle a mouse-down on bubble's resize corner.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
+  this.promote_();
+  Blockly.Bubble.unbindDragEvents_();
+  if (Blockly.isRightButton(e)) {
+    // No right-click.
+    e.stopPropagation();
+    return;
+  }
+  // Left-click (or middle click)
+  Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
+
+  this.workspace_.startDrag(e, new goog.math.Coordinate(
+      this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
+
+  Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document,
+      'mouseup', this, Blockly.Bubble.unbindDragEvents_);
+  Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
+      'mousemove', this, this.resizeMouseMove_);
+  Blockly.hideChaff();
+  // This event has been handled.  No need to bubble up to the document.
+  e.stopPropagation();
+};
+
+/**
+ * Resize this bubble to follow the mouse.
+ * @param {!Event} e Mouse move event.
+ * @private
+ */
+Blockly.Bubble.prototype.resizeMouseMove_ = function(e) {
+  this.autoLayout_ = false;
+  var newXY = this.workspace_.moveDrag(e);
+  this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
+  if (this.workspace_.RTL) {
+    // RTL requires the bubble to move its left edge.
+    this.positionBubble_();
+  }
+};
+
+/**
+ * Register a function as a callback event for when the bubble is resized.
+ * @param {!Function} callback The function to call on resize.
+ */
+Blockly.Bubble.prototype.registerResizeEvent = function(callback) {
+  this.resizeCallback_ = callback;
+};
+
+/**
+ * Move this bubble to the top of the stack.
+ * @private
+ */
+Blockly.Bubble.prototype.promote_ = function() {
+  var svgGroup = this.bubbleGroup_.parentNode;
+  svgGroup.appendChild(this.bubbleGroup_);
+};
+
+/**
+ * Notification that the anchor has moved.
+ * Update the arrow and bubble accordingly.
+ * @param {!goog.math.Coordinate} xy Absolute location.
+ */
+Blockly.Bubble.prototype.setAnchorLocation = function(xy) {
+  this.anchorXY_ = xy;
+  if (this.rendered_) {
+    this.positionBubble_();
+  }
+};
+
+/**
+ * Position the bubble so that it does not fall off-screen.
+ * @private
+ */
+Blockly.Bubble.prototype.layoutBubble_ = function() {
+  // Compute the preferred bubble location.
+  var relativeLeft = -this.width_ / 4;
+  var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y;
+  // Prevent the bubble from being off-screen.
+  var metrics = this.workspace_.getMetrics();
+  metrics.viewWidth /= this.workspace_.scale;
+  metrics.viewLeft /= this.workspace_.scale;
+  var anchorX = this.anchorXY_.x;
+  if (this.workspace_.RTL) {
+    if (anchorX - metrics.viewLeft - relativeLeft - this.width_ <
+        Blockly.Scrollbar.scrollbarThickness) {
+      // Slide the bubble right until it is onscreen.
+      relativeLeft = anchorX - metrics.viewLeft - this.width_ -
+        Blockly.Scrollbar.scrollbarThickness;
+    } else if (anchorX - metrics.viewLeft - relativeLeft >
+               metrics.viewWidth) {
+      // Slide the bubble left until it is onscreen.
+      relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth;
+    }
+  } else {
+    if (anchorX + relativeLeft < metrics.viewLeft) {
+      // Slide the bubble right until it is onscreen.
+      relativeLeft = metrics.viewLeft - anchorX;
+    } else if (metrics.viewLeft + metrics.viewWidth <
+        anchorX + relativeLeft + this.width_ +
+        Blockly.BlockSvg.SEP_SPACE_X +
+        Blockly.Scrollbar.scrollbarThickness) {
+      // Slide the bubble left until it is onscreen.
+      relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX -
+          this.width_ - Blockly.Scrollbar.scrollbarThickness;
+    }
+  }
+  if (this.anchorXY_.y + relativeTop < metrics.viewTop) {
+    // Slide the bubble below the block.
+    var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox();
+    relativeTop = bBox.height;
+  }
+  this.relativeLeft_ = relativeLeft;
+  this.relativeTop_ = relativeTop;
+};
+
+/**
+ * Move the bubble to a location relative to the anchor's centre.
+ * @private
+ */
+Blockly.Bubble.prototype.positionBubble_ = function() {
+  var left = this.anchorXY_.x;
+  if (this.workspace_.RTL) {
+    left -= this.relativeLeft_ + this.width_;
+  } else {
+    left += this.relativeLeft_;
+  }
+  var top = this.relativeTop_ + this.anchorXY_.y;
+  this.bubbleGroup_.setAttribute('transform',
+      'translate(' + left + ',' + top + ')');
+};
+
+/**
+ * Get the dimensions of this bubble.
+ * @return {!Object} Object with width and height properties.
+ */
+Blockly.Bubble.prototype.getBubbleSize = function() {
+  return {width: this.width_, height: this.height_};
+};
+
+/**
+ * Size this bubble.
+ * @param {number} width Width of the bubble.
+ * @param {number} height Height of the bubble.
+ */
+Blockly.Bubble.prototype.setBubbleSize = function(width, height) {
+  var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
+  // Minimum size of a bubble.
+  width = Math.max(width, doubleBorderWidth + 45);
+  height = Math.max(height, doubleBorderWidth + 20);
+  this.width_ = width;
+  this.height_ = height;
+  this.bubbleBack_.setAttribute('width', width);
+  this.bubbleBack_.setAttribute('height', height);
+  if (this.resizeGroup_) {
+    if (this.workspace_.RTL) {
+      // Mirror the resize group.
+      var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
+      this.resizeGroup_.setAttribute('transform', 'translate(' +
+          resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)');
+    } else {
+      this.resizeGroup_.setAttribute('transform', 'translate(' +
+          (width - doubleBorderWidth) + ',' +
+          (height - doubleBorderWidth) + ')');
+    }
+  }
+  if (this.rendered_) {
+    if (this.autoLayout_) {
+      this.layoutBubble_();
+    }
+    this.positionBubble_();
+    this.renderArrow_();
+  }
+  // Allow the contents to resize.
+  if (this.resizeCallback_) {
+    this.resizeCallback_();
+  }
+};
+
+/**
+ * Draw the arrow between the bubble and the origin.
+ * @private
+ */
+Blockly.Bubble.prototype.renderArrow_ = function() {
+  var steps = [];
+  // Find the relative coordinates of the center of the bubble.
+  var relBubbleX = this.width_ / 2;
+  var relBubbleY = this.height_ / 2;
+  // Find the relative coordinates of the center of the anchor.
+  var relAnchorX = -this.relativeLeft_;
+  var relAnchorY = -this.relativeTop_;
+  if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) {
+    // Null case.  Bubble is directly on top of the anchor.
+    // Short circuit this rather than wade through divide by zeros.
+    steps.push('M ' + relBubbleX + ',' + relBubbleY);
+  } else {
+    // Compute the angle of the arrow's line.
+    var rise = relAnchorY - relBubbleY;
+    var run = relAnchorX - relBubbleX;
+    if (this.workspace_.RTL) {
+      run *= -1;
+    }
+    var hypotenuse = Math.sqrt(rise * rise + run * run);
+    var angle = Math.acos(run / hypotenuse);
+    if (rise < 0) {
+      angle = 2 * Math.PI - angle;
+    }
+    // Compute a line perpendicular to the arrow.
+    var rightAngle = angle + Math.PI / 2;
+    if (rightAngle > Math.PI * 2) {
+      rightAngle -= Math.PI * 2;
+    }
+    var rightRise = Math.sin(rightAngle);
+    var rightRun = Math.cos(rightAngle);
+
+    // Calculate the thickness of the base of the arrow.
+    var bubbleSize = this.getBubbleSize();
+    var thickness = (bubbleSize.width + bubbleSize.height) /
+                    Blockly.Bubble.ARROW_THICKNESS;
+    thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 2;
+
+    // Back the tip of the arrow off of the anchor.
+    var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse;
+    relAnchorX = relBubbleX + backoffRatio * run;
+    relAnchorY = relBubbleY + backoffRatio * rise;
+
+    // Coordinates for the base of the arrow.
+    var baseX1 = relBubbleX + thickness * rightRun;
+    var baseY1 = relBubbleY + thickness * rightRise;
+    var baseX2 = relBubbleX - thickness * rightRun;
+    var baseY2 = relBubbleY - thickness * rightRise;
+
+    // Distortion to curve the arrow.
+    var swirlAngle = angle + this.arrow_radians_;
+    if (swirlAngle > Math.PI * 2) {
+      swirlAngle -= Math.PI * 2;
+    }
+    var swirlRise = Math.sin(swirlAngle) *
+        hypotenuse / Blockly.Bubble.ARROW_BEND;
+    var swirlRun = Math.cos(swirlAngle) *
+        hypotenuse / Blockly.Bubble.ARROW_BEND;
+
+    steps.push('M' + baseX1 + ',' + baseY1);
+    steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) +
+               ' ' + relAnchorX + ',' + relAnchorY +
+               ' ' + relAnchorX + ',' + relAnchorY);
+    steps.push('C' + relAnchorX + ',' + relAnchorY +
+               ' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) +
+               ' ' + baseX2 + ',' + baseY2);
+  }
+  steps.push('z');
+  this.bubbleArrow_.setAttribute('d', steps.join(' '));
+};
+
+/**
+ * Change the colour of a bubble.
+ * @param {string} hexColour Hex code of colour.
+ */
+Blockly.Bubble.prototype.setColour = function(hexColour) {
+  this.bubbleBack_.setAttribute('fill', hexColour);
+  this.bubbleArrow_.setAttribute('fill', hexColour);
+};
+
+/**
+ * Dispose of this bubble.
+ */
+Blockly.Bubble.prototype.dispose = function() {
+  Blockly.Bubble.unbindDragEvents_();
+  // Dispose of and unlink the bubble.
+  goog.dom.removeNode(this.bubbleGroup_);
+  this.bubbleGroup_ = null;
+  this.bubbleArrow_ = null;
+  this.bubbleBack_ = null;
+  this.resizeGroup_ = null;
+  this.workspace_ = null;
+  this.content_ = null;
+  this.shape_ = null;
+};

+ 284 - 0
blockly/core/comment.js

@@ -0,0 +1,284 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Object representing a code comment.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Comment');
+
+goog.require('Blockly.Bubble');
+goog.require('Blockly.Icon');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for a comment.
+ * @param {!Blockly.Block} block The block associated with this comment.
+ * @extends {Blockly.Icon}
+ * @constructor
+ */
+Blockly.Comment = function(block) {
+  Blockly.Comment.superClass_.constructor.call(this, block);
+  this.createIcon();
+};
+goog.inherits(Blockly.Comment, Blockly.Icon);
+
+/**
+ * Icon in base64 format.
+ * @private
+ */
+Blockly.Comment.prototype.png_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAANyAAADcgBffIlqAAAAAd0SU1FB98DGgAnBf0Xj5sAAAIBSURBVDjLjZO9SxxRFMXPrFkWl2UFYSOIRtF210YtAiH/gGATRNZFgo19IBaB9Ipgk3SiEoKQgI19JIVgGaOIgpWJEAV1kZk3b1ad0V+KRYIzk5ALh1ecc88978tRSgHPg0Bjvq/BbFalMNR5oaBv+bzWHMfZjOudWPOg6+pDva6elRXlt7fVcnYmPX4sDQ3pdmpKQXu7frS16aXjON8T06OIMWOwtRp3jgNSEpkMTE5y5/v4UcSLePxnroutVNKb4xgYANfFAk/vDbLG8Gtk5P8M7jE6CsZwDDwSMLm5iYmLlpbg4ABOTmBjA4aHk0ZbWxigposLvlarScH5OSwvw9oaABwdJTW1GtTrfJHnUe/uTgqKxeZaKEAUgTEQP/CeHvA8LhRFhLlc+r6zWVhfbyaZn0/yuRxEEaGCAK9USjdZWGgarK5CS0uS7+gAa3EzjYaOy2WlludJi4vSzIx0e5vky2Xp6ko/M4WCPleruk4zsVa6vJSur9OHTEzoqljUJwEdQYDf25uMe3jY3E5fX5Lr7wdr8YGSJCkIeL23h9/a+lA4Pg7T039u6h75POzv4wcBrx5Ec11Wd3bwOzv//VK7umB3F991+Zj2/R1reWstdnaWm3L5YXOlAnNz3FiLbTR4Azj6WwFPjOG953EahoT1On4YEnoep8bwDuiO9/wG1sM4kG8A4fUAAAAASUVORK5CYII=';
+
+/**
+ * Comment text (if bubble is not visible).
+ * @private
+ */
+Blockly.Comment.prototype.text_ = '';
+
+/**
+ * Width of bubble.
+ * @private
+ */
+Blockly.Comment.prototype.width_ = 160;
+
+/**
+ * Height of bubble.
+ * @private
+ */
+Blockly.Comment.prototype.height_ = 80;
+
+/**
+ * Draw the comment icon.
+ * @param {!Element} group The icon group.
+ * @private
+ */
+Blockly.Comment.prototype.drawIcon_ = function(group) {
+  // Circle.
+  Blockly.createSvgElement('circle',
+      {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
+       group);
+  // Can't use a real '?' text character since different browsers and operating
+  // systems render it differently.
+  // Body of question mark.
+  Blockly.createSvgElement('path',
+      {'class': 'blocklyIconSymbol',
+       'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405 0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25 -1.201,0.998 -1.201,1.528 -1.204,2.19z'},
+       group);
+  // Dot of question point.
+  Blockly.createSvgElement('rect',
+      {'class': 'blocklyIconSymbol',
+       'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'},
+       group);
+};
+
+/**
+ * Create the editor for the comment's bubble.
+ * @return {!Element} The top-level node of the editor.
+ * @private
+ */
+Blockly.Comment.prototype.createEditor_ = function() {
+  /* Create the editor.  Here's the markup that will be generated:
+    <foreignObject x="8" y="8" width="164" height="164">
+      <body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
+        <textarea xmlns="http://www.w3.org/1999/xhtml"
+            class="blocklyCommentTextarea"
+            style="height: 164px; width: 164px;"></textarea>
+      </body>
+    </foreignObject>
+  */
+  this.foreignObject_ = Blockly.createSvgElement('foreignObject',
+      {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
+      null);
+  var body = document.createElementNS(Blockly.HTML_NS, 'body');
+  body.setAttribute('xmlns', Blockly.HTML_NS);
+  body.className = 'blocklyMinimalBody';
+  var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea');
+  textarea.className = 'blocklyCommentTextarea';
+  textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
+  body.appendChild(textarea);
+  this.textarea_ = textarea;
+  this.foreignObject_.appendChild(body);
+  Blockly.bindEvent_(textarea, 'mouseup', this, this.textareaFocus_);
+  // Don't zoom with mousewheel.
+  Blockly.bindEvent_(textarea, 'wheel', this, function(e) {
+    e.stopPropagation();
+  });
+  Blockly.bindEvent_(textarea, 'change', this, function(e) {
+    if (this.text_ != textarea.value) {
+      Blockly.Events.fire(new Blockly.Events.Change(
+        this.block_, 'comment', null, this.text_, textarea.value));
+      this.text_ = textarea.value;
+    }
+  });
+  setTimeout(function() {
+    textarea.focus();
+  }, 0);
+  return this.foreignObject_;
+};
+
+/**
+ * Add or remove editability of the comment.
+ * @override
+ */
+Blockly.Comment.prototype.updateEditable = function() {
+  if (this.isVisible()) {
+    // Toggling visibility will force a rerendering.
+    this.setVisible(false);
+    this.setVisible(true);
+  }
+  // Allow the icon to update.
+  Blockly.Icon.prototype.updateEditable.call(this);
+};
+
+/**
+ * Callback function triggered when the bubble has resized.
+ * Resize the text area accordingly.
+ * @private
+ */
+Blockly.Comment.prototype.resizeBubble_ = function() {
+  if (this.isVisible()) {
+    var size = this.bubble_.getBubbleSize();
+    var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
+    this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth);
+    this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth);
+    this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px';
+    this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px';
+  }
+};
+
+/**
+ * Show or hide the comment bubble.
+ * @param {boolean} visible True if the bubble should be visible.
+ */
+Blockly.Comment.prototype.setVisible = function(visible) {
+  if (visible == this.isVisible()) {
+    // No change.
+    return;
+  }
+  Blockly.Events.fire(
+      new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
+  if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) {
+    // Steal the code from warnings to make an uneditable text bubble.
+    // MSIE does not support foreignobject; textareas are impossible.
+    // http://msdn.microsoft.com/en-us/library/hh834675%28v=vs.85%29.aspx
+    // Always treat comments in IE as uneditable.
+    Blockly.Warning.prototype.setVisible.call(this, visible);
+    return;
+  }
+  // Save the bubble stats before the visibility switch.
+  var text = this.getText();
+  var size = this.getBubbleSize();
+  if (visible) {
+    // Create the bubble.
+    this.bubble_ = new Blockly.Bubble(
+        /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
+        this.createEditor_(), this.block_.svgPath_,
+        this.iconXY_, this.width_, this.height_);
+    this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
+    this.updateColour();
+  } else {
+    // Dispose of the bubble.
+    this.bubble_.dispose();
+    this.bubble_ = null;
+    this.textarea_ = null;
+    this.foreignObject_ = null;
+  }
+  // Restore the bubble stats after the visibility switch.
+  this.setText(text);
+  this.setBubbleSize(size.width, size.height);
+};
+
+/**
+ * Bring the comment to the top of the stack when clicked on.
+ * @param {!Event} e Mouse up event.
+ * @private
+ */
+Blockly.Comment.prototype.textareaFocus_ = function(e) {
+  // Ideally this would be hooked to the focus event for the comment.
+  // However doing so in Firefox swallows the cursor for unknown reasons.
+  // So this is hooked to mouseup instead.  No big deal.
+  this.bubble_.promote_();
+  // Since the act of moving this node within the DOM causes a loss of focus,
+  // we need to reapply the focus.
+  this.textarea_.focus();
+};
+
+/**
+ * Get the dimensions of this comment's bubble.
+ * @return {!Object} Object with width and height properties.
+ */
+Blockly.Comment.prototype.getBubbleSize = function() {
+  if (this.isVisible()) {
+    return this.bubble_.getBubbleSize();
+  } else {
+    return {width: this.width_, height: this.height_};
+  }
+};
+
+/**
+ * Size this comment's bubble.
+ * @param {number} width Width of the bubble.
+ * @param {number} height Height of the bubble.
+ */
+Blockly.Comment.prototype.setBubbleSize = function(width, height) {
+  if (this.textarea_) {
+    this.bubble_.setBubbleSize(width, height);
+  } else {
+    this.width_ = width;
+    this.height_ = height;
+  }
+};
+
+/**
+ * Returns this comment's text.
+ * @return {string} Comment text.
+ */
+Blockly.Comment.prototype.getText = function() {
+  return this.textarea_ ? this.textarea_.value : this.text_;
+};
+
+/**
+ * Set this comment's text.
+ * @param {string} text Comment text.
+ */
+Blockly.Comment.prototype.setText = function(text) {
+  if (this.text_ != text) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+      this.block_, 'comment', null, this.text_, text));
+    this.text_ = text;
+  }
+  if (this.textarea_) {
+    this.textarea_.value = text;
+  }
+};
+
+/**
+ * Dispose of this comment.
+ */
+Blockly.Comment.prototype.dispose = function() {
+  if (Blockly.Events.isEnabled()) {
+    this.setText('');  // Fire event to delete comment.
+  }
+  this.block_.comment = null;
+  Blockly.Icon.prototype.dispose.call(this);
+};

+ 623 - 0
blockly/core/connection.js

@@ -0,0 +1,623 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Components for creating connections between blocks.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Connection');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+
+
+/**
+ * Class for a connection between blocks.
+ * @param {!Blockly.Block} source The block establishing this connection.
+ * @param {number} type The type of the connection.
+ * @constructor
+ */
+Blockly.Connection = function(source, type) {
+  /**
+   * @type {!Blockly.Block}
+   * @private
+   */
+  this.sourceBlock_ = source;
+  /** @type {number} */
+  this.type = type;
+  // Shortcut for the databases for this connection's workspace.
+  if (source.workspace.connectionDBList) {
+    this.db_ = source.workspace.connectionDBList[type];
+    this.dbOpposite_ =
+        source.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]];
+    this.hidden_ = !this.db_;
+  }
+};
+
+/**
+ * Constants for checking whether two connections are compatible.
+ */
+Blockly.Connection.CAN_CONNECT = 0;
+Blockly.Connection.REASON_SELF_CONNECTION = 1;
+Blockly.Connection.REASON_WRONG_TYPE = 2;
+Blockly.Connection.REASON_TARGET_NULL = 3;
+Blockly.Connection.REASON_CHECKS_FAILED = 4;
+Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5;
+Blockly.Connection.REASON_SHADOW_PARENT = 6;
+
+/**
+ * Connection this connection connects to.  Null if not connected.
+ * @type {Blockly.Connection}
+ */
+Blockly.Connection.prototype.targetConnection = null;
+
+/**
+ * List of compatible value types.  Null if all types are compatible.
+ * @type {Array}
+ * @private
+ */
+Blockly.Connection.prototype.check_ = null;
+
+/**
+ * DOM representation of a shadow block, or null if none.
+ * @type {Element}
+ * @private
+ */
+Blockly.Connection.prototype.shadowDom_ = null;
+
+/**
+ * Horizontal location of this connection.
+ * @type {number}
+ * @private
+ */
+Blockly.Connection.prototype.x_ = 0;
+
+/**
+ * Vertical location of this connection.
+ * @type {number}
+ * @private
+ */
+Blockly.Connection.prototype.y_ = 0;
+
+/**
+ * Has this connection been added to the connection database?
+ * @type {boolean}
+ * @private
+ */
+Blockly.Connection.prototype.inDB_ = false;
+
+/**
+ * Connection database for connections of this type on the current workspace.
+ * @type {Blockly.ConnectionDB}
+ * @private
+ */
+Blockly.Connection.prototype.db_ = null;
+
+/**
+ * Connection database for connections compatible with this type on the
+ * current workspace.
+ * @type {Blockly.ConnectionDB}
+ * @private
+ */
+Blockly.Connection.prototype.dbOpposite_ = null;
+
+/**
+ * Whether this connections is hidden (not tracked in a database) or not.
+ * @type {boolean}
+ * @private
+ */
+Blockly.Connection.prototype.hidden_ = null;
+
+/**
+ * Connect two connections together.  This is the connection on the superior
+ * block.
+ * @param {!Blockly.Connection} childConnection Connection on inferior block.
+ * @private
+ */
+Blockly.Connection.prototype.connect_ = function(childConnection) {
+  var parentConnection = this;
+  var parentBlock = parentConnection.getSourceBlock();
+  var childBlock = childConnection.getSourceBlock();
+  // Disconnect any existing parent on the child connection.
+  if (childConnection.isConnected()) {
+    childConnection.disconnect();
+  }
+  if (parentConnection.isConnected()) {
+    // Other connection is already connected to something.
+    // Disconnect it and reattach it or bump it as needed.
+    var orphanBlock = parentConnection.targetBlock();
+    var shadowDom = parentConnection.getShadowDom();
+    // Temporarily set the shadow DOM to null so it does not respawn.
+    parentConnection.setShadowDom(null);
+    // Displaced shadow blocks dissolve rather than reattaching or bumping.
+    if (orphanBlock.isShadow()) {
+      // Save the shadow block so that field values are preserved.
+      shadowDom = Blockly.Xml.blockToDom(orphanBlock);
+      orphanBlock.dispose();
+      orphanBlock = null;
+    } else if (parentConnection.type == Blockly.INPUT_VALUE) {
+      // Value connections.
+      // If female block is already connected, disconnect and bump the male.
+      if (!orphanBlock.outputConnection) {
+        throw 'Orphan block does not have an output connection.';
+      }
+      // Attempt to reattach the orphan at the end of the newly inserted
+      // block.  Since this block may be a row, walk down to the end
+      // or to the first (and only) shadow block.
+      var connection = Blockly.Connection.lastConnectionInRow_(
+          childBlock, orphanBlock);
+      if (connection) {
+        orphanBlock.outputConnection.connect(connection);
+        orphanBlock = null;
+      }
+    } else if (parentConnection.type == Blockly.NEXT_STATEMENT) {
+      // Statement connections.
+      // Statement blocks may be inserted into the middle of a stack.
+      // Split the stack.
+      if (!orphanBlock.previousConnection) {
+        throw 'Orphan block does not have a previous connection.';
+      }
+      // Attempt to reattach the orphan at the bottom of the newly inserted
+      // block.  Since this block may be a stack, walk down to the end.
+      var newBlock = childBlock;
+      while (newBlock.nextConnection) {
+        var nextBlock = newBlock.getNextBlock();
+        if (nextBlock && !nextBlock.isShadow()) {
+          newBlock = nextBlock;
+        } else {
+          if (orphanBlock.previousConnection.checkType_(
+              newBlock.nextConnection)) {
+            newBlock.nextConnection.connect(orphanBlock.previousConnection);
+            orphanBlock = null;
+          }
+          break;
+        }
+      }
+    }
+    if (orphanBlock) {
+      // Unable to reattach orphan.
+      parentConnection.disconnect();
+      if (Blockly.Events.recordUndo) {
+        // Bump it off to the side after a moment.
+        var group = Blockly.Events.getGroup();
+        setTimeout(function() {
+          // Verify orphan hasn't been deleted or reconnected (user on meth).
+          if (orphanBlock.workspace && !orphanBlock.getParent()) {
+            Blockly.Events.setGroup(group);
+            if (orphanBlock.outputConnection) {
+              orphanBlock.outputConnection.bumpAwayFrom_(parentConnection);
+            } else if (orphanBlock.previousConnection) {
+              orphanBlock.previousConnection.bumpAwayFrom_(parentConnection);
+            }
+            Blockly.Events.setGroup(false);
+          }
+        }, Blockly.BUMP_DELAY);
+      }
+    }
+    // Restore the shadow DOM.
+    parentConnection.setShadowDom(shadowDom);
+  }
+
+  var event;
+  if (Blockly.Events.isEnabled()) {
+    event = new Blockly.Events.Move(childBlock);
+  }
+  // Establish the connections.
+  Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
+  // Demote the inferior block so that one is a child of the superior one.
+  childBlock.setParent(parentBlock);
+  if (event) {
+    event.recordNew();
+    Blockly.Events.fire(event);
+  }
+};
+
+/**
+ * Sever all links to this connection (not including from the source object).
+ */
+Blockly.Connection.prototype.dispose = function() {
+  if (this.isConnected()) {
+    throw 'Disconnect connection before disposing of it.';
+  }
+  if (this.inDB_) {
+    this.db_.removeConnection_(this);
+  }
+  if (Blockly.highlightedConnection_ == this) {
+    Blockly.highlightedConnection_ = null;
+  }
+  if (Blockly.highlightedConnectionBad_ == this) {
+    Blockly.highlightedConnectionBad_ = null;
+  }
+  if (Blockly.localConnection_ == this) {
+    Blockly.localConnection_ = null;
+  }
+  this.db_ = null;
+  this.dbOpposite_ = null;
+};
+
+/**
+ * Get the source block for this connection.
+ * @return {Blockly.Block} The source block, or null if there is none.
+ */
+Blockly.Connection.prototype.getSourceBlock = function() {
+  return this.sourceBlock_;
+};
+
+/**
+ * Does the connection belong to a superior block (higher in the source stack)?
+ * @return {boolean} True if connection faces down or right.
+ */
+Blockly.Connection.prototype.isSuperior = function() {
+  return this.type == Blockly.INPUT_VALUE ||
+      this.type == Blockly.NEXT_STATEMENT;
+};
+
+/**
+ * Is the connection connected?
+ * @return {boolean} True if connection is connected to another connection.
+ */
+Blockly.Connection.prototype.isConnected = function() {
+  return !!this.targetConnection;
+};
+
+/**
+ * Checks whether the current connection can connect with the target
+ * connection.
+ * @param {Blockly.Connection} target Connection to check compatibility with.
+ * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
+ *    an error code otherwise.
+ * @private
+ */
+Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
+  if (!target) {
+    return Blockly.Connection.REASON_TARGET_NULL;
+  }
+  if (this.isSuperior()) {
+    var blockA = this.sourceBlock_;
+    var blockB = target.getSourceBlock();
+  } else {
+    var blockB = this.sourceBlock_;
+    var blockA = target.getSourceBlock();
+  }
+  if (blockA && blockA == blockB) {
+    return Blockly.Connection.REASON_SELF_CONNECTION;
+  } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) {
+    return Blockly.Connection.REASON_WRONG_TYPE;
+  } else if (blockA && blockB && blockA.workspace !== blockB.workspace) {
+    return Blockly.Connection.REASON_DIFFERENT_WORKSPACES;
+  // for BlocksCAD, turn off type checking so we can highlight bad connections too.
+  // call checkType_ later to distinguish this condition.
+  // } else if (!this.checkType_(target)) {
+  //   return Blockly.Connection.REASON_CHECKS_FAILED;
+  } else if (blockA.isShadow() && !blockB.isShadow()) {
+    return Blockly.Connection.REASON_SHADOW_PARENT;
+  }
+  return Blockly.Connection.CAN_CONNECT;
+};
+
+/**
+ * Checks whether the current connection and target connection are compatible
+ * and throws an exception if they are not.
+ * @param {Blockly.Connection} target The connection to check compatibility
+ *    with.
+ * @private
+ */
+Blockly.Connection.prototype.checkConnection_ = function(target) {
+  switch (this.canConnectWithReason_(target)) {
+    case Blockly.Connection.CAN_CONNECT:
+      break;
+    case Blockly.Connection.REASON_SELF_CONNECTION:
+      throw 'Attempted to connect a block to itself.';
+    case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:
+      // Usually this means one block has been deleted.
+      throw 'Blocks not on same workspace.';
+    case Blockly.Connection.REASON_WRONG_TYPE:
+      throw 'Attempt to connect incompatible types.';
+    case Blockly.Connection.REASON_TARGET_NULL:
+      throw 'Target connection is null.';
+    case Blockly.Connection.REASON_CHECKS_FAILED:
+      throw 'Connection checks failed.';
+    case Blockly.Connection.REASON_SHADOW_PARENT:
+      throw 'Connecting non-shadow to shadow block.';
+    default:
+      throw 'Unknown connection failure: this should never happen!';
+  }
+};
+
+/**
+ * Check if the two connections can be dragged to connect to each other.
+ * @param {!Blockly.Connection} candidate A nearby connection to check.
+ * @return {boolean} True if the connection is allowed, false otherwise.
+ * For BlocksCAD: note that isConnectionAllowed no longer does type checking
+ * so that we can highlight both legal and illegal connections.
+ * CAN_CONNECT will not check to see if you have text going into a number block.
+ */
+Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
+  // Type checking.
+  var canConnect = this.canConnectWithReason_(candidate);
+  if (canConnect != Blockly.Connection.CAN_CONNECT) {
+    return false;
+  }
+
+  // Don't offer to connect an already connected left (male) value plug to
+  // an available right (female) value plug.  Don't offer to connect the
+  // bottom of a statement block to one that's already connected.
+  if (candidate.type == Blockly.OUTPUT_VALUE ||
+      candidate.type == Blockly.PREVIOUS_STATEMENT) {
+    if (candidate.isConnected() || this.isConnected()) {
+      return false;
+    }
+  }
+
+  // Offering to connect the left (male) of a value block to an already
+  // connected value pair is ok, we'll splice it in.
+  // However, don't offer to splice into an immovable block.
+  if (candidate.type == Blockly.INPUT_VALUE && candidate.isConnected() &&
+      !candidate.targetBlock().isMovable() &&
+      !candidate.targetBlock().isShadow()) {
+    return false;
+  }
+
+  // Don't let a block with no next connection bump other blocks out of the
+  // stack.  But covering up a shadow block or stack of shadow blocks is fine.
+  // Similarly, replacing a terminal statement with another terminal statement
+  // is allowed.
+  if (this.type == Blockly.PREVIOUS_STATEMENT &&
+      candidate.isConnected() &&
+      !this.sourceBlock_.nextConnection &&
+      !candidate.targetBlock().isShadow() &&
+      candidate.targetBlock().nextConnection) {
+    return false;
+  }
+
+  // Don't let blocks try to connect to themselves or ones they nest.
+  if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
+    return false;
+  }
+
+  return true;
+};
+
+/**
+ * Connect this connection to another connection.
+ * @param {!Blockly.Connection} otherConnection Connection to connect to.
+ */
+Blockly.Connection.prototype.connect = function(otherConnection) {
+  if (this.targetConnection == otherConnection) {
+    // Already connected together.  NOP.
+    return;
+  }
+  this.checkConnection_(otherConnection);
+  // Determine which block is superior (higher in the source stack).
+  if (this.isSuperior()) {
+    // Superior block.
+    this.connect_(otherConnection);
+  } else {
+    // Inferior block.
+    otherConnection.connect_(this);
+  }
+};
+
+/**
+ * Update two connections to target each other.
+ * @param {Blockly.Connection} first The first connection to update.
+ * @param {Blockly.Connection} second The second conneciton to update.
+ * @private
+ */
+Blockly.Connection.connectReciprocally_ = function(first, second) {
+  goog.asserts.assert(first && second, 'Cannot connect null connections.');
+  first.targetConnection = second;
+  second.targetConnection = first;
+};
+
+/**
+ * Does the given block have one and only one connection point that will accept
+ * an orphaned block?
+ * @param {!Blockly.Block} block The superior block.
+ * @param {!Blockly.Block} orphanBlock The inferior block.
+ * @return {Blockly.Connection} The suitable connection point on 'block',
+ *     or null.
+ * @private
+ */
+Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
+  var connection = false;
+  for (var i = 0; i < block.inputList.length; i++) {
+    var thisConnection = block.inputList[i].connection;
+    if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
+        orphanBlock.outputConnection.checkType_(thisConnection)) {
+      if (connection) {
+        return null;  // More than one connection.
+      }
+      connection = thisConnection;
+    }
+  }
+  return connection;
+};
+
+/**
+ * Walks down a row a blocks, at each stage checking if there are any
+ * connections that will accept the orphaned block.  If at any point there
+ * are zero or multiple eligible connections, returns null.  Otherwise
+ * returns the only input on the last block in the chain.
+ * Terminates early for shadow blocks.
+ * @param {!Blockly.Block} startBlock The block on which to start the search.
+ * @param {!Blockly.Block} orphanBlock The block that is looking for a home.
+ * @return {Blockly.Connection} The suitable connection point on the chain
+ *    of blocks, or null.
+ * @private
+ */
+Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) {
+  var newBlock = startBlock;
+  var connection;
+  while (connection = Blockly.Connection.singleConnection_(
+      /** @type {!Blockly.Block} */ (newBlock), orphanBlock)) {
+    // '=' is intentional in line above.
+    newBlock = connection.targetBlock();
+    if (!newBlock || newBlock.isShadow()) {
+      return connection;
+    }
+  }
+  return null;
+};
+
+/**
+ * Disconnect this connection.
+ */
+Blockly.Connection.prototype.disconnect = function() {
+  var otherConnection = this.targetConnection;
+  goog.asserts.assert(otherConnection, 'Source connection not connected.');
+  goog.asserts.assert(otherConnection.targetConnection == this,
+      'Target connection not connected to source connection.');
+
+  var parentBlock, childBlock, parentConnection;
+  if (this.isSuperior()) {
+    // Superior block.
+    parentBlock = this.sourceBlock_;
+    childBlock = otherConnection.getSourceBlock();
+    parentConnection = this;
+  } else {
+    // Inferior block.
+    parentBlock = otherConnection.getSourceBlock();
+    childBlock = this.sourceBlock_;
+    parentConnection = otherConnection;
+  }
+  this.disconnectInternal_(parentBlock, childBlock);
+  parentConnection.respawnShadow_();
+};
+
+/**
+ * Disconnect two blocks that are connected by this connection.
+ * @param {!Blockly.Block} parentBlock The superior block.
+ * @param {!Blockly.Block} childBlock The inferior block.
+ * @private
+ */
+Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
+    childBlock) {
+  var event;
+  if (Blockly.Events.isEnabled()) {
+    event = new Blockly.Events.Move(childBlock);
+  }
+  var otherConnection = this.targetConnection;
+  otherConnection.targetConnection = null;
+  this.targetConnection = null;
+  childBlock.setParent(null);
+  if (event) {
+    event.recordNew();
+    Blockly.Events.fire(event);
+  }
+};
+
+/**
+ * Respawn the shadow block if there was one connected to the this connection.
+ * @private
+ */
+Blockly.Connection.prototype.respawnShadow_ = function() {
+  var parentBlock = this.getSourceBlock();
+  var shadow = this.getShadowDom();
+  if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
+    var blockShadow =
+        Blockly.Xml.domToBlock(shadow, parentBlock.workspace);
+    if (blockShadow.outputConnection) {
+      this.connect(blockShadow.outputConnection);
+    } else if (blockShadow.previousConnection) {
+      this.connect(blockShadow.previousConnection);
+    } else {
+      throw 'Child block does not have output or previous statement.';
+    }
+  }
+};
+
+/**
+ * Returns the block that this connection connects to.
+ * @return {Blockly.Block} The connected block or null if none is connected.
+ */
+Blockly.Connection.prototype.targetBlock = function() {
+  if (this.isConnected()) {
+    return this.targetConnection.getSourceBlock();
+  }
+  return null;
+};
+
+/**
+ * Is this connection compatible with another connection with respect to the
+ * value type system.  E.g. square_root("Hello") is not compatible.
+ * @param {!Blockly.Connection} otherConnection Connection to compare against.
+ * @return {boolean} True if the connections share a type.
+ * @private
+ */
+Blockly.Connection.prototype.checkType_ = function(otherConnection) {
+  if (!this.check_ || !otherConnection.check_) {
+    // One or both sides are promiscuous enough that anything will fit.
+    return true;
+  }
+  // Find any intersection in the check lists.
+  for (var i = 0; i < this.check_.length; i++) {
+    if (otherConnection.check_.indexOf(this.check_[i]) != -1) {
+      return true;
+    }
+  }
+  // No intersection.
+  return false;
+};
+
+/**
+ * Change a connection's compatibility.
+ * @param {*} check Compatible value type or list of value types.
+ *     Null if all types are compatible.
+ * @return {!Blockly.Connection} The connection being modified
+ *     (to allow chaining).
+ */
+Blockly.Connection.prototype.setCheck = function(check) {
+  if (check) {
+    // Ensure that check is in an array.
+    if (!goog.isArray(check)) {
+      check = [check];
+    }
+    this.check_ = check;
+    // The new value type may not be compatible with the existing connection.
+    if (this.isConnected() && !this.checkType_(this.targetConnection)) {
+      var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
+      child.unplug();
+      // Bump away.
+      this.sourceBlock_.bumpNeighbours_();
+    }
+  } else {
+    this.check_ = null;
+  }
+  return this;
+};
+
+/**
+ * Change a connection's shadow block.
+ * @param {Element} shadow DOM representation of a block or null.
+ */
+Blockly.Connection.prototype.setShadowDom = function(shadow) {
+  this.shadowDom_ = shadow;
+};
+
+/**
+ * Return a connection's shadow block.
+ * @return {Element} shadow DOM representation of a block or null.
+ */
+Blockly.Connection.prototype.getShadowDom = function() {
+  return this.shadowDom_;
+};

+ 310 - 0
blockly/core/connection_db.js

@@ -0,0 +1,310 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Components for managing connections between blocks.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.ConnectionDB');
+
+goog.require('Blockly.Connection');
+
+
+/**
+ * Database of connections.
+ * Connections are stored in order of their vertical component.  This way
+ * connections in an area may be looked up quickly using a binary search.
+ * @constructor
+ */
+Blockly.ConnectionDB = function() {
+};
+
+Blockly.ConnectionDB.prototype = new Array();
+/**
+ * Don't inherit the constructor from Array.
+ * @type {!Function}
+ */
+Blockly.ConnectionDB.constructor = Blockly.ConnectionDB;
+
+/**
+ * Add a connection to the database.  Must not already exist in DB.
+ * @param {!Blockly.Connection} connection The connection to be added.
+ */
+Blockly.ConnectionDB.prototype.addConnection = function(connection) {
+  if (connection.inDB_) {
+    throw 'Connection already in database.';
+  }
+  if (connection.getSourceBlock().isInFlyout) {
+    // Don't bother maintaining a database of connections in a flyout.
+    return;
+  }
+  var position = this.findPositionForConnection_(connection);
+  this.splice(position, 0, connection);
+  connection.inDB_ = true;
+};
+
+/**
+ * Find the given connection.
+ * Starts by doing a binary search to find the approximate location, then
+ *     linearly searches nearby for the exact connection.
+ * @param {!Blockly.Connection} conn The connection to find.
+ * @return {number} The index of the connection, or -1 if the connection was
+ *     not found.
+ */
+Blockly.ConnectionDB.prototype.findConnection = function(conn) {
+  if (!this.length) {
+    return -1;
+  }
+
+  var bestGuess = this.findPositionForConnection_(conn);
+  if (bestGuess >= this.length) {
+    // Not in list
+    return -1;
+  }
+
+  var yPos = conn.y_;
+  // Walk forward and back on the y axis looking for the connection.
+  var pointerMin = bestGuess;
+  var pointerMax = bestGuess;
+  while (pointerMin >= 0 && this[pointerMin].y_ == yPos) {
+    if (this[pointerMin] == conn) {
+      return pointerMin;
+    }
+    pointerMin--;
+  }
+
+  while (pointerMax < this.length && this[pointerMax].y_ == yPos) {
+    if (this[pointerMax] == conn) {
+      return pointerMax;
+    }
+    pointerMax++;
+  }
+  return -1;
+};
+
+/**
+ * Finds a candidate position for inserting this connection into the list.
+ * This will be in the correct y order but makes no guarantees about ordering in
+ *     the x axis.
+ * @param {!Blockly.Connection} connection The connection to insert.
+ * @return {number} The candidate index.
+ * @private
+ */
+Blockly.ConnectionDB.prototype.findPositionForConnection_ =
+    function(connection) {
+  /* eslint-disable indent */
+  if (!this.length) {
+    return 0;
+  }
+  var pointerMin = 0;
+  var pointerMax = this.length;
+  while (pointerMin < pointerMax) {
+    var pointerMid = Math.floor((pointerMin + pointerMax) / 2);
+    if (this[pointerMid].y_ < connection.y_) {
+      pointerMin = pointerMid + 1;
+    } else if (this[pointerMid].y_ > connection.y_) {
+      pointerMax = pointerMid;
+    } else {
+      pointerMin = pointerMid;
+      break;
+    }
+  }
+  return pointerMin;
+};  /* eslint-enable indent */
+
+/**
+ * Remove a connection from the database.  Must already exist in DB.
+ * @param {!Blockly.Connection} connection The connection to be removed.
+ * @private
+ */
+Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) {
+  if (!connection.inDB_) {
+    throw 'Connection not in database.';
+  }
+  var removalIndex = this.findConnection(connection);
+  if (removalIndex == -1) {
+    throw 'Unable to find connection in connectionDB.';
+  }
+  connection.inDB_ = false;
+  this.splice(removalIndex, 1);
+};
+
+/**
+ * Find all nearby connections to the given connection.
+ * Type checking does not apply, since this function is used for bumping.
+ * @param {!Blockly.Connection} connection The connection whose neighbours
+ *     should be returned.
+ * @param {number} maxRadius The maximum radius to another connection.
+ * @return {!Array.<Blockly.Connection>} List of connections.
+ */
+Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
+  var db = this;
+  var currentX = connection.x_;
+  var currentY = connection.y_;
+
+  // Binary search to find the closest y location.
+  var pointerMin = 0;
+  var pointerMax = db.length - 2;
+  var pointerMid = pointerMax;
+  while (pointerMin < pointerMid) {
+    if (db[pointerMid].y_ < currentY) {
+      pointerMin = pointerMid;
+    } else {
+      pointerMax = pointerMid;
+    }
+    pointerMid = Math.floor((pointerMin + pointerMax) / 2);
+  }
+
+  var neighbours = [];
+  /**
+   * Computes if the current connection is within the allowed radius of another
+   * connection.
+   * This function is a closure and has access to outside variables.
+   * @param {number} yIndex The other connection's index in the database.
+   * @return {boolean} True if the current connection's vertical distance from
+   *     the other connection is less than the allowed radius.
+   */
+  function checkConnection_(yIndex) {
+    var dx = currentX - db[yIndex].x_;
+    var dy = currentY - db[yIndex].y_;
+    var r = Math.sqrt(dx * dx + dy * dy);
+    if (r <= maxRadius) {
+      neighbours.push(db[yIndex]);
+    }
+    return dy < maxRadius;
+  }
+
+  // Walk forward and back on the y axis looking for the closest x,y point.
+  pointerMin = pointerMid;
+  pointerMax = pointerMid;
+  if (db.length) {
+    while (pointerMin >= 0 && checkConnection_(pointerMin)) {
+      pointerMin--;
+    }
+    do {
+      pointerMax++;
+    } while (pointerMax < db.length && checkConnection_(pointerMax));
+  }
+
+  return neighbours;
+};
+
+
+/**
+ * Is the candidate connection close to the reference connection.
+ * Extremely fast; only looks at Y distance.
+ * @param {number} index Index in database of candidate connection.
+ * @param {number} baseY Reference connection's Y value.
+ * @param {number} maxRadius The maximum radius to another connection.
+ * @return {boolean} True if connection is in range.
+ * @private
+ */
+Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) {
+  return (Math.abs(this[index].y_ - baseY) <= maxRadius);
+};
+
+/**
+ * Find the closest compatible connection to this connection.
+ * @param {!Blockly.Connection} conn The connection searching for a compatible
+ *     mate.
+ * @param {number} maxRadius The maximum radius to another connection.
+ * @param {!goog.math.Coordinate} dxy Offset between this connection's location
+ *     in the database and the current location (as a result of dragging).
+ * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two
+ *     properties:' connection' which is either another connection or null,
+ *     and 'radius' which is the distance.
+ *    For BlocksCAD - add an "allowed" parameter, so I can return illegal neighbors to
+ *    be highlighted as bad connections
+ */
+Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
+    dxy) {
+  // Don't bother.
+  if (!this.length) {
+    return {connection: null, radius: maxRadius};
+  }
+
+  var legal = true;   // for BlocksCAD
+
+  // Stash the values of x and y from before the drag.
+  var baseY = conn.y_;
+  var baseX = conn.x_;
+
+  conn.x_ = baseX + dxy.x;
+  conn.y_ = baseY + dxy.y;
+
+  // findPositionForConnection finds an index for insertion, which is always
+  // after any block with the same y index.  We want to search both forward
+  // and back, so search on both sides of the index.
+  var closestIndex = this.findPositionForConnection_(conn);
+
+  var bestConnection = null;
+  var bestRadius = maxRadius;
+  var temp;
+
+  // Walk forward and back on the y axis looking for the closest x,y point.
+  var pointerMin = closestIndex - 1;
+  while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) {
+    temp = this[pointerMin];
+    if (conn.isConnectionAllowed(temp, bestRadius)) {
+      bestConnection = temp;
+      bestRadius = temp.distanceFrom(conn);
+      // check the type to distinguish legal from illegal connections
+      legal = conn.checkType_(temp);
+    }
+    pointerMin--;
+  }
+
+  var pointerMax = closestIndex;
+  while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_,
+      maxRadius)) {
+    temp = this[pointerMax];
+    if (conn.isConnectionAllowed(temp, bestRadius)) {
+      bestConnection = temp;
+      bestRadius = temp.distanceFrom(conn);
+      // check the type to distinguish legal from illegal connections
+      legal = conn.checkType_(temp);
+
+    }
+    pointerMax++;
+  }
+
+  // Reset the values of x and y.
+  conn.x_ = baseX;
+  conn.y_ = baseY;
+
+  // If there were no valid connections, bestConnection will be null.
+  return {connection: bestConnection, radius: bestRadius, allowed: legal};
+};
+
+/**
+ * Initialize a set of connection DBs for a specified workspace.
+ * @param {!Blockly.Workspace} workspace The workspace this DB is for.
+ */
+Blockly.ConnectionDB.init = function(workspace) {
+  // Create four databases, one for each connection type.
+  var dbList = [];
+  dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB();
+  dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB();
+  dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB();
+  dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB();
+  workspace.connectionDBList = dbList;
+};

+ 202 - 0
blockly/core/constants.js

@@ -0,0 +1,202 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2016 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 Blockly constants.
+ * @author fenichel@google.com (Rachel Fenichel)
+ */
+'use strict';
+
+goog.provide('Blockly.constants');
+
+
+/**
+ * Number of pixels the mouse must move before a drag starts.
+ */
+Blockly.DRAG_RADIUS = 5;
+
+/**
+ * Maximum misalignment between connections for them to snap together.
+ */
+Blockly.SNAP_RADIUS = 20;
+
+/**
+ * Delay in ms between trigger and bumping unconnected block out of alignment.
+ */
+Blockly.BUMP_DELAY = 0;
+
+/**
+ * Number of characters to truncate a collapsed block to.
+ */
+Blockly.COLLAPSE_CHARS = 30;
+
+/**
+ * Length in ms for a touch to become a long press.
+ */
+Blockly.LONGPRESS = 750;
+
+/**
+ * Prevent a sound from playing if another sound preceded it within this many
+ * miliseconds.
+ */
+Blockly.SOUND_LIMIT = 100;
+
+/**
+ * The richness of block colours, regardless of the hue.
+ * Must be in the range of 0 (inclusive) to 1 (exclusive).
+ */
+Blockly.HSV_SATURATION = 0.45;
+
+/**
+ * The intensity of block colours, regardless of the hue.
+ * Must be in the range of 0 (inclusive) to 1 (exclusive).
+ */
+Blockly.HSV_VALUE = 0.65;
+
+/**
+ * Sprited icons and images.
+ */
+Blockly.SPRITE = {
+  width: 96,
+  height: 124,
+  url: 'sprites.png'
+};
+
+// Constants below this point are not intended to be changed.
+
+/**
+ * Required name space for SVG elements.
+ * @const
+ */
+Blockly.SVG_NS = 'http://www.w3.org/2000/svg';
+
+/**
+ * Required name space for HTML elements.
+ * @const
+ */
+Blockly.HTML_NS = 'http://www.w3.org/1999/xhtml';
+
+/**
+ * ENUM for a right-facing value input.  E.g. 'set item to' or 'return'.
+ * @const
+ */
+Blockly.INPUT_VALUE = 1;
+
+/**
+ * ENUM for a left-facing value output.  E.g. 'random fraction'.
+ * @const
+ */
+Blockly.OUTPUT_VALUE = 2;
+
+/**
+ * ENUM for a down-facing block stack.  E.g. 'if-do' or 'else'.
+ * @const
+ */
+Blockly.NEXT_STATEMENT = 3;
+
+/**
+ * ENUM for an up-facing block stack.  E.g. 'break out of loop'.
+ * @const
+ */
+Blockly.PREVIOUS_STATEMENT = 4;
+
+/**
+ * ENUM for an dummy input.  Used to add field(s) with no input.
+ * @const
+ */
+Blockly.DUMMY_INPUT = 5;
+
+/**
+ * ENUM for left alignment.
+ * @const
+ */
+Blockly.ALIGN_LEFT = -1;
+
+/**
+ * ENUM for centre alignment.
+ * @const
+ */
+Blockly.ALIGN_CENTRE = 0;
+
+/**
+ * ENUM for right alignment.
+ * @const
+ */
+Blockly.ALIGN_RIGHT = 1;
+
+/**
+ * ENUM for no drag operation.
+ * @const
+ */
+Blockly.DRAG_NONE = 0;
+
+/**
+ * ENUM for inside the sticky DRAG_RADIUS.
+ * @const
+ */
+Blockly.DRAG_STICKY = 1;
+
+/**
+ * ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between
+ * clicks and drags.
+ * @const
+ */
+Blockly.DRAG_BEGIN = 1;
+
+/**
+ * ENUM for freely draggable (outside the DRAG_RADIUS, if one applies).
+ * @const
+ */
+Blockly.DRAG_FREE = 2;
+
+/**
+ * Lookup table for determining the opposite type of a connection.
+ * @const
+ */
+Blockly.OPPOSITE_TYPE = [];
+Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE] = Blockly.OUTPUT_VALUE;
+Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE] = Blockly.INPUT_VALUE;
+Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT] = Blockly.PREVIOUS_STATEMENT;
+Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT] = Blockly.NEXT_STATEMENT;
+
+
+/**
+ * ENUM for toolbox and flyout at top of screen.
+ * @const
+ */
+Blockly.TOOLBOX_AT_TOP = 0;
+
+/**
+ * ENUM for toolbox and flyout at bottom of screen.
+ * @const
+ */
+Blockly.TOOLBOX_AT_BOTTOM = 1;
+
+/**
+ * ENUM for toolbox and flyout at left of screen.
+ * @const
+ */
+Blockly.TOOLBOX_AT_LEFT = 2;
+
+/**
+ * ENUM for toolbox and flyout at right of screen.
+ * @const
+ */
+Blockly.TOOLBOX_AT_RIGHT = 3;

+ 148 - 0
blockly/core/contextmenu.js

@@ -0,0 +1,148 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Functionality for the right-click context menus.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.ContextMenu');
+
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.style');
+goog.require('goog.ui.Menu');
+goog.require('goog.ui.MenuItem');
+
+
+/**
+ * Which block is the context menu attached to?
+ * @type {Blockly.Block}
+ */
+Blockly.ContextMenu.currentBlock = null;
+
+/**
+ * Construct the menu based on the list of options and show the menu.
+ * @param {!Event} e Mouse event.
+ * @param {!Array.<!Object>} options Array of menu options.
+ * @param {boolean} rtl True if RTL, false if LTR.
+ */
+Blockly.ContextMenu.show = function(e, options, rtl) {
+  Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null);
+  if (!options.length) {
+    Blockly.ContextMenu.hide();
+    return;
+  }
+  /* Here's what one option object looks like:
+    {text: 'Make It So',
+     enabled: true,
+     callback: Blockly.MakeItSo}
+  */
+  var menu = new goog.ui.Menu();
+  menu.setRightToLeft(rtl);
+  for (var i = 0, option; option = options[i]; i++) {
+    var menuItem = new goog.ui.MenuItem(option.text);
+    menuItem.setRightToLeft(rtl);
+    menu.addChild(menuItem, true);
+    menuItem.setEnabled(option.enabled);
+    if (option.enabled) {
+      goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION,
+                         option.callback);
+    }
+  }
+  goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
+                     Blockly.ContextMenu.hide);
+  // Record windowSize and scrollOffset before adding menu.
+  var windowSize = goog.dom.getViewportSize();
+  var scrollOffset = goog.style.getViewportPageOffset(document);
+  var div = Blockly.WidgetDiv.DIV;
+  menu.render(div);
+  var menuDom = menu.getElement();
+  Blockly.addClass_(menuDom, 'blocklyContextMenu');
+  // Prevent system context menu when right-clicking a Blockly context menu.
+  Blockly.bindEvent_(menuDom, 'contextmenu', null, Blockly.noEvent);
+  // Record menuSize after adding menu.
+  var menuSize = goog.style.getSize(menuDom);
+
+  // Position the menu.
+  var x = e.clientX + scrollOffset.x;
+  var y = e.clientY + scrollOffset.y;
+  // Flip menu vertically if off the bottom.
+  if (e.clientY + menuSize.height >= windowSize.height) {
+    y -= menuSize.height;
+  }
+  // Flip menu horizontally if off the edge.
+  if (rtl) {
+    if (menuSize.width >= e.clientX) {
+      x += menuSize.width;
+    }
+  } else {
+    if (e.clientX + menuSize.width >= windowSize.width) {
+      x -= menuSize.width;
+    }
+  }
+  Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl);
+
+  menu.setAllowAutoFocus(true);
+  // 1ms delay is required for focusing on context menus because some other
+  // mouse event is still waiting in the queue and clears focus.
+  setTimeout(function() {menuDom.focus();}, 1);
+  Blockly.ContextMenu.currentBlock = null;  // May be set by Blockly.Block.
+};
+
+/**
+ * Hide the context menu.
+ */
+Blockly.ContextMenu.hide = function() {
+  Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
+  Blockly.ContextMenu.currentBlock = null;
+};
+
+/**
+ * Create a callback function that creates and configures a block,
+ *   then places the new block next to the original.
+ * @param {!Blockly.Block} block Original block.
+ * @param {!Element} xml XML representation of new block.
+ * @return {!Function} Function that creates a block.
+ */
+Blockly.ContextMenu.callbackFactory = function(block, xml) {
+  return function() {
+    Blockly.Events.disable();
+    try {
+      var newBlock = Blockly.Xml.domToBlock(xml, block.workspace);
+      // Move the new block next to the old block.
+      var xy = block.getRelativeToSurfaceXY();
+      if (block.RTL) {
+        xy.x -= Blockly.SNAP_RADIUS;
+      } else {
+        xy.x += Blockly.SNAP_RADIUS;
+      }
+      xy.y += Blockly.SNAP_RADIUS * 2;
+      newBlock.moveBy(xy.x, xy.y);
+    } finally {
+      Blockly.Events.enable();
+    }
+    if (Blockly.Events.isEnabled() && !newBlock.isShadow()) {
+      Blockly.Events.fire(new Blockly.Events.Create(newBlock));
+    }
+    newBlock.select();
+  };
+};

+ 821 - 0
blockly/core/css.js

@@ -0,0 +1,821 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2013 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 Inject Blockly's CSS synchronously.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Css');
+
+
+/**
+ * List of cursors.
+ * @enum {string}
+ */
+Blockly.Css.Cursor = {
+  OPEN: 'handopen',
+  CLOSED: 'handclosed',
+  DELETE: 'handdelete'
+};
+
+/**
+ * Current cursor (cached value).
+ * @type {string}
+ * @private
+ */
+Blockly.Css.currentCursor_ = '';
+
+/**
+ * Large stylesheet added by Blockly.Css.inject.
+ * @type {Element}
+ * @private
+ */
+Blockly.Css.styleSheet_ = null;
+
+/**
+ * Path to media directory, with any trailing slash removed.
+ * @type {string}
+ * @private
+ */
+Blockly.Css.mediaPath_ = '';
+
+/**
+ * Inject the CSS into the DOM.  This is preferable over using a regular CSS
+ * file since:
+ * a) It loads synchronously and doesn't force a redraw later.
+ * b) It speeds up loading by not blocking on a separate HTTP transfer.
+ * c) The CSS content may be made dynamic depending on init options.
+ * @param {boolean} hasCss If false, don't inject CSS
+ *     (providing CSS becomes the document's responsibility).
+ * @param {string} pathToMedia Path from page to the Blockly media directory.
+ */
+Blockly.Css.inject = function(hasCss, pathToMedia) {
+  // Only inject the CSS once.
+  if (Blockly.Css.styleSheet_) {
+    return;
+  }
+  // Placeholder for cursor rule.  Must be first rule (index 0).
+  var text = '.blocklyDraggable {}\n';
+  if (hasCss) {
+    text += Blockly.Css.CONTENT.join('\n');
+    if (Blockly.FieldDate) {
+      text += Blockly.FieldDate.CSS.join('\n');
+    }
+  }
+  // Strip off any trailing slash (either Unix or Windows).
+  Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, '');
+  text = text.replace(/<<<PATH>>>/g, Blockly.Css.mediaPath_);
+  // Inject CSS tag.
+  var cssNode = document.createElement('style');
+  document.head.appendChild(cssNode);
+  var cssTextNode = document.createTextNode(text);
+  cssNode.appendChild(cssTextNode);
+  Blockly.Css.styleSheet_ = cssNode.sheet;
+  Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
+};
+
+/**
+ * Set the cursor to be displayed when over something draggable.
+ * @param {Blockly.Css.Cursor} cursor Enum.
+ */
+Blockly.Css.setCursor = function(cursor) {
+  if (Blockly.Css.currentCursor_ == cursor) {
+    return;
+  }
+  Blockly.Css.currentCursor_ = cursor;
+  var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur), auto';
+  // There are potentially hundreds of draggable objects.  Changing their style
+  // properties individually is too slow, so change the CSS rule instead.
+  var rule = '.blocklyDraggable {\n  cursor: ' + url + ';\n}\n';
+  Blockly.Css.styleSheet_.deleteRule(0);
+  Blockly.Css.styleSheet_.insertRule(rule, 0);
+  // There is probably only one toolbox, so just change its style property.
+  var toolboxen = document.getElementsByClassName('blocklyToolboxDiv');
+  for (var i = 0, toolbox; toolbox = toolboxen[i]; i++) {
+    if (cursor == Blockly.Css.Cursor.DELETE) {
+      toolbox.style.cursor = url;
+    } else {
+      toolbox.style.cursor = '';
+    }
+  }
+  // Set cursor on the whole document, so that rapid movements
+  // don't result in cursor changing to an arrow momentarily.
+  var html = document.body.parentNode;
+  if (cursor == Blockly.Css.Cursor.OPEN) {
+    html.style.cursor = '';
+  } else {
+    html.style.cursor = url;
+  }
+};
+
+/**
+ * Array making up the CSS content for Blockly.
+ */
+Blockly.Css.CONTENT = [
+  '.blocklySvg {',
+    'background-color: #fff;',
+    'height: 481px;',
+    'outline: none;',
+    'overflow: hidden;',  /* IE overflows by default. */
+  '}',
+
+  '.blocklyWidgetDiv {',
+    'display: none;',
+    'position: absolute;',
+    'z-index: 999;',
+  '}',
+
+  '.blocklyTooltipDiv {',
+    'background-color: #ffffc7;',
+    'border: 1px solid #ddc;',
+    'box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);',
+    'color: #000;',
+    'display: none;',
+    'font-family: sans-serif;',
+    'font-size: 9pt;',
+    'opacity: 0.94;',
+    'padding: 2px;',
+    'position: absolute;',
+    'z-index: 1000;',
+  '}',
+
+  '.blocklyResizeSE {',
+    'cursor: se-resize;',
+    'fill: #aaa;',
+  '}',
+
+  '.blocklyResizeSW {',
+    'cursor: sw-resize;',
+    'fill: #aaa;',
+  '}',
+
+  '.blocklyResizeLine {',
+    'stroke: #888;',
+    'stroke-width: 1;',
+  '}',
+
+  '.blocklyHighlightedConnectionPath {',
+  '  fill: none;',
+  '  stroke: #0f0;',
+  '  stroke-width: 8px;',
+  '}',
+
+  '.blocklyHighlightedConnectionPathBad {', /* added for BlocksCAD */
+  '  fill: none;',
+  '  stroke: #f00;',
+  '  stroke-width: 4px;',
+  '}',
+
+  '.blocklyPathLight {',
+  '  fill: none;',
+  '  stroke-linecap: round;',
+  '  stroke-width: 2;',
+  '}',
+
+  '.blocklyBacklight>.blocklyPath {',  /* added for BlocksCAD */
+  '  stroke: #f00;',
+  '  stroke-width: 4px;',
+  '}',
+
+  '.blocklyBacklight>.blocklyPathLight {', /* added for BlocksCAD */
+  '  display: none;',
+  '}',
+
+  '.blocklySelected>.blocklyPath {',
+    'stroke: #fc3;',
+    'stroke-width: 3px;',
+  '}',
+
+  '.blocklySelected>.blocklyPathLight {',
+    'display: none;',
+  '}',
+
+  '.blocklyDragging>.blocklyPath,',
+  '.blocklyDragging>.blocklyPathLight {',
+    'fill-opacity: .8;',
+    'stroke-opacity: .8;',
+  '}',
+
+  '.blocklyDragging>.blocklyPathDark {',
+    'display: none;',
+  '}',
+
+  '.blocklyDisabled>.blocklyPath {',
+    'fill-opacity: .5;',
+    'stroke-opacity: .5;',
+  '}',
+
+  '.blocklyDisabled>.blocklyPathLight,',
+  '.blocklyDisabled>.blocklyPathDark {',
+    'display: none;',
+  '}',
+
+  '.blocklyText {',
+  '  cursor: default;',
+  '  fill: #fff;',
+  '  font-family: sans-serif;',
+  '  font-size: 11pt;',
+  '}',
+  '.blocklyButton {',
+  '  cursor: default;',
+  '  fill: #fff;',
+  '  border: 1px solid #000;',
+  '  font-size:12pt;',
+  '}',
+
+  '.blocklyNonEditableText>text {',
+    'pointer-events: none;',
+  '}',
+
+  '.blocklyNonEditableText>rect,',
+  '.blocklyEditableText>rect {',
+    'fill: #fff;',
+    'fill-opacity: .6;',
+  '}',
+
+  '.blocklyNonEditableText>text,',
+  '.blocklyEditableText>text {',
+    'fill: #000;',
+  '}',
+
+  '.blocklyEditableText:hover>rect {',
+    'stroke: #fff;',
+    'stroke-width: 2;',
+  '}',
+
+  '.blocklyBubbleText {',
+    'fill: #000;',
+  '}',
+
+  /*
+    Don't allow users to select text.  It gets annoying when trying to
+    drag a block and selected text moves instead.
+  */
+  '.blocklySvg text {',
+    'user-select: none;',
+    '-moz-user-select: none;',
+    '-webkit-user-select: none;',
+    'cursor: inherit;',
+  '}',
+
+  '.blocklyHidden {',
+    'display: none;',
+  '}',
+
+  '.blocklyFieldDropdown:not(.blocklyHidden) {',
+    'display: block;',
+  '}',
+
+  '.blocklyIconGroup {',
+    'cursor: default;',
+  '}',
+
+  '.blocklyIconGroup:not(:hover),',
+  '.blocklyIconGroupReadonly {',
+  '  opacity: .6;',
+  '}',
+
+  '.blocklyIconShield {',
+  '  cursor: default;',
+  '  fill: #00c;',
+  '  stroke: #ccc;',
+  '  stroke-width: 1px;',
+  '}',
+
+  '.blocklyIconGroup:hover>.blocklyIconShield {',
+  '  fill: #00f;',
+  '  stroke: #fff;',
+  '}',
+
+  '.blocklyIconGroup:hover>.blocklyIconMark {',
+  '  fill: #fff;',
+  '}',
+
+  '.blocklyIconMark {',
+  '  cursor: default !important;',
+  '  fill: #ccc;',
+  '  font-family: sans-serif;',
+  '  font-size: 9pt;',
+  '  font-weight: bold;',
+  '  text-anchor: middle;',
+  '}',
+
+  '.blocklyIconShape {',
+    'fill: #00f;',
+    'stroke: #fff;',
+    'stroke-width: 1px;',
+  '}',
+
+  '.blocklyIconSymbol {',
+    'fill: #fff;',
+  '}',
+
+  '.blocklyMinimalBody {',
+    'margin: 0;',
+    'padding: 0;',
+  '}',
+
+  '.blocklyCommentTextarea {',
+    'background-color: #ffc;',
+    'border: 0;',
+    'margin: 0;',
+    'padding: 2px;',
+    'resize: none;',
+  '}',
+
+  '.blocklyHtmlInput {',
+    'border: none;',
+    'border-radius: 4px;',
+    'font-family: sans-serif;',
+    'height: 100%;',
+    'margin: 0;',
+    'outline: none;',
+    'padding: 0 1px;',
+    'width: 100%',
+  '}',
+
+  '.blocklyMainBackground {',
+    'stroke-width: 1;',
+    'stroke: #c6c6c6;',  /* Equates to #ddd due to border being off-pixel. */
+  '}',
+
+  '.blocklyMutatorBackground {',
+    'fill: #fff;',
+    'stroke: #ddd;',
+    'stroke-width: 1;',
+  '}',
+
+  '.blocklyFlyoutBackground {',
+  '  fill: #ddd;',
+  '  fill-opacity: .8;',
+  '}',
+
+  '.blocklyColourBackground {',
+  '  fill: #666;',
+  '}',
+
+  '.blocklyScrollbarBackground {',
+    'opacity: 0;',
+  '}',
+
+  '.blocklyScrollbarHandle {',
+    'fill: #ccc;',
+  '}',
+
+  '.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,',
+  '.blocklyScrollbarHandle:hover {',
+    'fill: #bbb;',
+  '}',
+
+  '.blocklyZoom>image {',
+    'opacity: .4;',
+  '}',
+
+  '.blocklyZoom>image:hover {',
+    'opacity: .6;',
+  '}',
+
+  '.blocklyZoom>image:active {',
+    'opacity: .8;',
+  '}',
+
+  /* Darken flyout scrollbars due to being on a grey background. */
+  /* By contrast, workspace scrollbars are on a white background. */
+  '.blocklyFlyout .blocklyScrollbarHandle {',
+    'fill: #bbb;',
+  '}',
+
+  '.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,',
+  '.blocklyFlyout .blocklyScrollbarHandle:hover {',
+    'fill: #aaa;',
+  '}',
+
+  '.blocklyInvalidInput {',
+    'background: #faa;',
+  '}',
+
+  '.blocklyAngleCircle {',
+    'stroke: #444;',
+    'stroke-width: 1;',
+    'fill: #ddd;',
+    'fill-opacity: .8;',
+  '}',
+
+  '.blocklyAngleMarks {',
+    'stroke: #444;',
+    'stroke-width: 1;',
+  '}',
+
+  '.blocklyAngleGauge {',
+    'fill: #f88;',
+    'fill-opacity: .8;',
+  '}',
+
+  '.blocklyAngleLine {',
+    'stroke: #f00;',
+    'stroke-width: 2;',
+    'stroke-linecap: round;',
+  '}',
+
+  '.blocklyContextMenu {',
+    'border-radius: 4px;',
+  '}',
+
+  '.blocklyDropdownMenu {',
+    'padding: 0 !important;',
+  '}',
+
+  /* Override the default Closure URL. */
+  '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
+  '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {',
+    'background: url(<<<PATH>>>/sprites.png) no-repeat -48px -16px !important;',
+  '}',
+
+  /* Category tree in Toolbox. */
+  '.blocklyToolboxDiv {',
+    'overflow-x: visible;',
+    'overflow-y: auto;',
+    'position: absolute;',
+  '}',
+
+  '.blocklyTreeRoot {',
+  '  padding: 0;',
+  '  margin-top: -2px;',
+  '}',
+
+  '.blocklyTreeRoot:focus {',
+    'outline: none;',
+  '}',
+
+  '.blocklyTreeRow {',
+  '  line-height: 40px;',
+  '  height: 40px;',
+  '  padding-right: 1em;',
+  '  white-space: nowrap;',
+  '  margin-top: 3px;',
+  '-webkit-touch-callout: none;',
+  '-webkit-user-select: none;',
+  '-khtml-user-select: none;',
+  '-moz-user-select: none;',
+  '-ms-user-select: none;',
+  'user-select: none;', 
+  '}',
+  
+  '.blocklyHorizontalTree {',
+    'float: left;',
+    'margin: 1px 5px 8px 0;',
+  '}',
+
+  '.blocklyHorizontalTreeRtl {',
+    'float: right;',
+    'margin: 1px 0 8px 5px;',
+  '}',
+
+  '.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',
+  '  padding-right: 0;',
+  '  padding-left: 1em !important;',
+  '}',
+
+  '.blocklyTreeRow:hover {',
+    '-webkit-box-shadow:inset 0px 0px 0px 1px #fff;',
+    '-moz-box-shadow:inset 0px 0px 0px 1px #fff;',
+    'box-shadow:inset 0px 0px 0px 1px #fff;',
+  '}',
+
+  '.blocklyTreeSeparator {',
+    'border-bottom: solid #e5e5e5 1px;',
+    'height: 0;',
+    'margin: 5px 0;',
+  '}',
+
+  '.blocklyTreeSeparatorHorizontal {',
+    'border-right: solid #e5e5e5 1px;',
+    'width: 0;',
+    'padding: 5px 0;',
+    'margin: 0 5px;',
+  '}',
+
+
+  '.blocklyTreeIcon {',
+    'background-image: url(<<<PATH>>>/sprites.png);',
+    'height: 16px;',
+    'vertical-align: middle;',
+    'width: 16px;',
+  '}',
+
+  '.blocklyTreeIconClosedLtr {',
+    'background-position: -32px -1px;',
+  '}',
+
+  '.blocklyTreeIconClosedRtl {',
+    'background-position: 0px -1px;',
+  '}',
+
+  '.blocklyTreeIconOpen {',
+    'background-position: -16px -1px;',
+  '}',
+
+  '.blocklyTreeSelected>.blocklyTreeIconClosedLtr {',
+    'background-position: -32px -17px;',
+  '}',
+
+  '.blocklyTreeSelected>.blocklyTreeIconClosedRtl {',
+    'background-position: 0px -17px;',
+  '}',
+
+  '.blocklyTreeSelected>.blocklyTreeIconOpen {',
+    'background-position: -16px -17px;',
+  '}',
+
+  '.blocklyTreeIconNone,',
+  '.blocklyTreeSelected>.blocklyTreeIconNone {',
+    'background-position: -48px -1px;',
+  '}',
+
+  '.blocklyTreeLabel {',
+    'cursor: default;',
+    'font-family: sans-serif;',
+    'font-size: 16px;',
+    'padding: 0 3px;',
+    'vertical-align: middle;',
+    'color: #fff',
+  '}',
+
+  '.blocklyTreeSelected .blocklyTreeLabel {',
+    'color: #fff;',
+  '}',
+
+  /* Copied from: goog/css/colorpicker-simplegrid.css */
+  /*
+   * Copyright 2007 The Closure Library Authors. All Rights Reserved.
+   *
+   * Use of this source code is governed by the Apache License, Version 2.0.
+   * See the COPYING file for details.
+   */
+
+  /* Author: pupius@google.com (Daniel Pupius) */
+
+  /*
+    Styles to make the colorpicker look like the old gmail color picker
+    NOTE: without CSS scoping this will override styles defined in palette.css
+  */
+  '.blocklyWidgetDiv .goog-palette {',
+    'outline: none;',
+    'cursor: default;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-palette-table {',
+    'border: 1px solid #666;',
+    'border-collapse: collapse;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-palette-cell {',
+    'height: 13px;',
+    'width: 15px;',
+    'margin: 0;',
+    'border: 0;',
+    'text-align: center;',
+    'vertical-align: middle;',
+    'border-right: 1px solid #666;',
+    'font-size: 1px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-palette-colorswatch {',
+    'position: relative;',
+    'height: 13px;',
+    'width: 15px;',
+    'border: 1px solid #666;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {',
+    'border: 1px solid #FFF;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {',
+    'border: 1px solid #000;',
+    'color: #fff;',
+  '}',
+
+  /* Copied from: goog/css/menu.css */
+  /*
+   * Copyright 2009 The Closure Library Authors. All Rights Reserved.
+   *
+   * Use of this source code is governed by the Apache License, Version 2.0.
+   * See the COPYING file for details.
+   */
+
+  /**
+   * Standard styling for menus created by goog.ui.MenuRenderer.
+   *
+   * @author attila@google.com (Attila Bodis)
+   */
+
+  '.blocklyWidgetDiv .goog-menu {',
+    'background: #fff;',
+    'border-color: #ccc #666 #666 #ccc;',
+    'border-style: solid;',
+    'border-width: 1px;',
+    'cursor: default;',
+    'font: normal 13px Arial, sans-serif;',
+    'margin: 0;',
+    'outline: none;',
+    'padding: 4px 0;',
+    'position: absolute;',
+    'overflow-y: auto;',
+    'overflow-x: hidden;',
+    'max-height: 100%;',
+    'z-index: 20000;',  /* Arbitrary, but some apps depend on it... */
+  '}',
+
+  /* Copied from: goog/css/menuitem.css */
+  /*
+   * Copyright 2009 The Closure Library Authors. All Rights Reserved.
+   *
+   * Use of this source code is governed by the Apache License, Version 2.0.
+   * See the COPYING file for details.
+   */
+
+  /**
+   * Standard styling for menus created by goog.ui.MenuItemRenderer.
+   *
+   * @author attila@google.com (Attila Bodis)
+   */
+
+  /**
+   * State: resting.
+   *
+   * NOTE(mleibman,chrishenry):
+   * The RTL support in Closure is provided via two mechanisms -- "rtl" CSS
+   * classes and BiDi flipping done by the CSS compiler.  Closure supports RTL
+   * with or without the use of the CSS compiler.  In order for them not
+   * to conflict with each other, the "rtl" CSS classes need to have the #noflip
+   * annotation.  The non-rtl counterparts should ideally have them as well, but,
+   * since .goog-menuitem existed without .goog-menuitem-rtl for so long before
+   * being added, there is a risk of people having templates where they are not
+   * rendering the .goog-menuitem-rtl class when in RTL and instead rely solely
+   * on the BiDi flipping by the CSS compiler.  That's why we're not adding the
+   * #noflip to .goog-menuitem.
+   */
+  '.blocklyWidgetDiv .goog-menuitem {',
+    'color: #000;',
+    'font: normal 13px Arial, sans-serif;',
+    'list-style: none;',
+    'margin: 0;',
+     /* 28px on the left for icon or checkbox; 7em on the right for shortcut. */
+    'padding: 4px 7em 4px 28px;',
+    'white-space: nowrap;',
+  '}',
+
+  /* BiDi override for the resting state. */
+  /* #noflip */
+  '.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {',
+     /* Flip left/right padding for BiDi. */
+    'padding-left: 7em;',
+    'padding-right: 28px;',
+  '}',
+
+  /* If a menu doesn't have checkable items or items with icons, remove padding. */
+  '.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,',
+  '.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {',
+    'padding-left: 12px;',
+  '}',
+
+  /*
+   * If a menu doesn't have items with shortcuts, leave just enough room for
+   * submenu arrows, if they are rendered.
+   */
+  '.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {',
+    'padding-right: 20px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-menuitem-content {',
+    'color: #000;',
+    'font: normal 13px Arial, sans-serif;',
+  '}',
+
+  /* State: disabled. */
+  '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,',
+  '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {',
+    'color: #ccc !important;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {',
+    'opacity: 0.3;',
+    '-moz-opacity: 0.3;',
+    'filter: alpha(opacity=30);',
+  '}',
+
+  /* State: hover. */
+  '.blocklyWidgetDiv .goog-menuitem-highlight,',
+  '.blocklyWidgetDiv .goog-menuitem-hover {',
+    'background-color: #d6e9f8;',
+     /* Use an explicit top and bottom border so that the selection is visible',
+      * in high contrast mode. */
+    'border-color: #d6e9f8;',
+    'border-style: dotted;',
+    'border-width: 1px 0;',
+    'padding-bottom: 3px;',
+    'padding-top: 3px;',
+  '}',
+
+  /* State: selected/checked. */
+  '.blocklyWidgetDiv .goog-menuitem-checkbox,',
+  '.blocklyWidgetDiv .goog-menuitem-icon {',
+    'background-repeat: no-repeat;',
+    'height: 16px;',
+    'left: 6px;',
+    'position: absolute;',
+    'right: auto;',
+    'vertical-align: middle;',
+    'width: 16px;',
+  '}',
+
+  /* BiDi override for the selected/checked state. */
+  /* #noflip */
+  '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
+  '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {',
+     /* Flip left/right positioning. */
+    'left: auto;',
+    'right: 6px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
+  '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {',
+     /* Client apps may override the URL at which they serve the sprite. */
+    'background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;',
+  '}',
+
+  /* Keyboard shortcut ("accelerator") style. */
+  '.blocklyWidgetDiv .goog-menuitem-accel {',
+    'color: #999;',
+     /* Keyboard shortcuts are untranslated; always left-to-right. */
+     /* #noflip */
+    'direction: ltr;',
+    'left: auto;',
+    'padding: 0 6px;',
+    'position: absolute;',
+    'right: 0;',
+    'text-align: right;',
+  '}',
+
+  /* BiDi override for shortcut style. */
+  /* #noflip */
+  '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {',
+     /* Flip left/right positioning and text alignment. */
+    'left: 0;',
+    'right: auto;',
+    'text-align: left;',
+  '}',
+
+  /* Mnemonic styles. */
+  '.blocklyWidgetDiv .goog-menuitem-mnemonic-hint {',
+    'text-decoration: underline;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-menuitem-mnemonic-separator {',
+    'color: #999;',
+    'font-size: 12px;',
+    'padding-left: 4px;',
+  '}',
+
+  /* Copied from: goog/css/menuseparator.css */
+  /*
+   * Copyright 2009 The Closure Library Authors. All Rights Reserved.
+   *
+   * Use of this source code is governed by the Apache License, Version 2.0.
+   * See the COPYING file for details.
+   */
+
+  /**
+   * Standard styling for menus created by goog.ui.MenuSeparatorRenderer.
+   *
+   * @author attila@google.com (Attila Bodis)
+   */
+
+  '.blocklyWidgetDiv .goog-menuseparator {',
+    'border-top: 1px solid #ccc;',
+    'margin: 4px 0;',
+    'padding: 0;',
+  '}',
+
+  ''
+];

+ 912 - 0
blockly/core/events.js

@@ -0,0 +1,912 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2016 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 Events fired as a result of actions in Blockly's editor.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Events');
+
+goog.require('goog.math.Coordinate');
+
+
+/**
+ * Group ID for new events.  Grouped events are indivisible.
+ * @type {string}
+ * @private
+ */
+Blockly.Events.group_ = '';
+
+/**
+ * Sets whether events should be added to the undo stack.
+ * @type {boolean}
+ */
+Blockly.Events.recordUndo = true;
+
+/**
+ * Allow change events to be created and fired.
+ * @type {number}
+ * @private
+ */
+Blockly.Events.disabled_ = 0;
+
+/**
+ * Name of event that creates a block.
+ * @const
+ */
+Blockly.Events.CREATE = 'create';
+
+/**
+ * Name of event that deletes a block.
+ * @const
+ */
+Blockly.Events.DELETE = 'delete';
+
+/**
+ * Name of event that changes a block.
+ * @const
+ */
+Blockly.Events.CHANGE = 'change';
+
+/**
+ * Name of event that moves a block.
+ * @const
+ */
+Blockly.Events.MOVE = 'move';
+
+/**
+ * Name of event that records a UI change.
+ * @const
+ */
+Blockly.Events.UI = 'ui';
+
+/**
+ * Name of event that records a typing change (for blockscad).
+ * @const
+ */
+Blockly.Events.TYPING = 'typing';
+/**
+ * List of events queued for firing.
+ * @private
+ */
+Blockly.Events.FIRE_QUEUE_ = [];
+
+/**
+ * Create a custom event and fire it.
+ * @param {!Blockly.Events.Abstract} event Custom data for event.
+ */
+Blockly.Events.fire = function(event) {
+  if (!Blockly.Events.isEnabled()) {
+    return;
+  }
+  if (!Blockly.Events.FIRE_QUEUE_.length) {
+    // First event added; schedule a firing of the event queue.
+    setTimeout(Blockly.Events.fireNow_, 0);
+  }
+  Blockly.Events.FIRE_QUEUE_.push(event);
+};
+
+/**
+ * Fire all queued events.
+ * @private
+ */
+Blockly.Events.fireNow_ = function() {
+  var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
+  Blockly.Events.FIRE_QUEUE_.length = 0;
+  for (var i = 0, event; event = queue[i]; i++) {
+    var workspace = Blockly.Workspace.getById(event.workspaceId);
+    if (workspace) {
+      workspace.fireChangeListener(event);
+    }
+  }
+};
+
+/**
+ * Filter the queued events and merge duplicates.
+ * @param {!Array.<!Blockly.Events.Abstract>} queueIn Array of events.
+ * @param {boolean} forward True if forward (redo), false if backward (undo).
+ * @return {!Array.<!Blockly.Events.Abstract>} Array of filtered events.
+ */
+Blockly.Events.filter = function(queueIn, forward) {
+  var queue = goog.array.clone(queueIn);
+  if (!forward) {
+    // Undo is merged in reverse order.
+    queue.reverse();
+  }
+  // Merge duplicates.  O(n^2), but n should be very small.
+  for (var i = 0, event1; event1 = queue[i]; i++) {
+    for (var j = i + 1, event2; event2 = queue[j]; j++) {
+      if (event1.type == event2.type &&
+          event1.blockId == event2.blockId &&
+          event1.workspaceId == event2.workspaceId) {
+        if (event1.type == Blockly.Events.MOVE) {
+          // Merge move events.
+          event1.newParentId = event2.newParentId;
+          event1.newInputName = event2.newInputName;
+          event1.newCoordinate = event2.newCoordinate;
+          queue.splice(j, 1);
+          j--;
+        } else if (event1.type == Blockly.Events.CHANGE &&
+            event1.element == event2.element &&
+            event1.name == event2.name) {
+          // Merge change events.
+          event1.newValue = event2.newValue;
+          queue.splice(j, 1);
+          j--;
+        } else if (event1.type == Blockly.Events.UI &&
+            event2.element == 'click' &&
+            (event1.element == 'commentOpen' ||
+             event1.element == 'mutatorOpen' ||
+             event1.element == 'warningOpen')) {
+          // Merge change events.
+          event1.newValue = event2.newValue;
+          queue.splice(j, 1);
+          j--;
+        }
+      }
+    }
+  }
+  // Remove null events.
+  for (var i = queue.length - 1; i >= 0; i--) {
+    if (queue[i].isNull()) {
+      queue.splice(i, 1);
+    }
+  }
+  if (!forward) {
+    // Restore undo order.
+    queue.reverse();
+  }
+  // Move mutation events to the top of the queue.
+  // Intentionally skip first event.
+  for (var i = 1, event; event = queue[i]; i++) {
+    if (event.type == Blockly.Events.CHANGE &&
+        event.element == 'mutation') {
+      queue.unshift(queue.splice(i, 1)[0]);
+    }
+  }
+  return queue;
+};
+
+/**
+ * Modify pending undo events so that when they are fired they don't land
+ * in the undo stack.  Called by Blockly.Workspace.clearUndo.
+ */
+Blockly.Events.clearPendingUndo = function() {
+  for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) {
+    event.recordUndo = false;
+  }
+};
+
+/**
+ * Stop sending events.  Every call to this function MUST also call enable.
+ */
+Blockly.Events.disable = function() {
+  Blockly.Events.disabled_++;
+};
+
+/**
+ * Start sending events.  Unless events were already disabled when the
+ * corresponding call to disable was made.
+ */
+Blockly.Events.enable = function() {
+  Blockly.Events.disabled_--;
+};
+
+/**
+ * Returns whether events may be fired or not.
+ * @return {boolean} True if enabled.
+ */
+Blockly.Events.isEnabled = function() {
+  return Blockly.Events.disabled_ == 0;
+};
+
+/**
+ * Current group.
+ * @return {string} ID string.
+ */
+Blockly.Events.getGroup = function() {
+  return Blockly.Events.group_;
+};
+
+/**
+ * Start or stop a group.
+ * @param {boolean|string} state True to start new group, false to end group.
+ *   String to set group explicitly.
+ */
+Blockly.Events.setGroup = function(state) {
+  if (typeof state == 'boolean') {
+    Blockly.Events.group_ = state ? Blockly.genUid() : '';
+  } else {
+    Blockly.Events.group_ = state;
+  }
+};
+
+/**
+ * Compute a list of the IDs of the specified block and all its descendants.
+ * @param {!Blockly.Block} block The root block.
+ * @return {!Array.<string>} List of block IDs.
+ * @private
+ */
+Blockly.Events.getDescendantIds_ = function(block) {
+  var ids = [];
+  var descendants = block.getDescendants();
+  for (var i = 0, descendant; descendant = descendants[i]; i++) {
+    ids[i] = descendant.id;
+  }
+  return ids;
+};
+
+/**
+ * Decode the JSON into an event.
+ * @param {!Object} json JSON representation.
+ * @param {!Blockly.Workspace} workspace Target workspace for event.
+ * @return {!Blockly.Events.Abstract} The event represented by the JSON.
+ */
+Blockly.Events.fromJson = function(json, workspace) {
+  var event;
+  switch (json.type) {
+    case Blockly.Events.CREATE:
+      event = new Blockly.Events.Create(null);
+      break;
+    case Blockly.Events.DELETE:
+      event = new Blockly.Events.Delete(null);
+      break;
+    case Blockly.Events.CHANGE:
+      event = new Blockly.Events.Change(null);
+      break;
+    case Blockly.Events.MOVE:
+      event = new Blockly.Events.Move(null);
+      break;
+    case Blockly.Events.UI:
+      event = new Blockly.Events.Ui(null);
+      break;
+    default:
+      throw 'Unknown event type.';
+  }
+  event.fromJson(json);
+  event.workspaceId = workspace.id;
+  return event;
+};
+
+/**
+ * Abstract class for an event.
+ * @param {Blockly.Block} block The block.
+ * @constructor
+ */
+Blockly.Events.Abstract = function(block) {
+  if (block) {
+    this.blockId = block.id;
+    this.workspaceId = block.workspace.id;
+  }
+  this.group = Blockly.Events.group_;
+  this.recordUndo = Blockly.Events.recordUndo;
+};
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Abstract.prototype.toJson = function() {
+  var json = {
+    'type': this.type,
+  };
+  if (this.blockId) {
+    json['blockId'] = this.blockId;
+  }
+  if (this.group) {
+    json['group'] = this.group;
+  }
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Abstract.prototype.fromJson = function(json) {
+  this.blockId = json['blockId'];
+  this.group = json['group'];
+};
+
+/**
+ * Does this event record any change of state?
+ * @return {boolean} True if null, false if something changed.
+ */
+Blockly.Events.Abstract.prototype.isNull = function() {
+  return false;
+};
+
+/**
+ * Run an event.
+ * @param {boolean} forward True if run forward, false if run backward (undo).
+ */
+Blockly.Events.Abstract.prototype.run = function(forward) {
+  // Defined by subclasses.
+};
+
+/**
+ * Class for a block creation event.
+ * @param {Blockly.Block} block The created block.  Null for a blank event.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.Create = function(block) {
+  if (!block) {
+    return;  // Blank event to be populated by fromJson.
+  }
+  Blockly.Events.Create.superClass_.constructor.call(this, block);
+  this.xml = Blockly.Xml.blockToDomWithXY(block);
+  this.ids = Blockly.Events.getDescendantIds_(block);
+};
+goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Create.prototype.type = Blockly.Events.CREATE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Create.prototype.toJson = function() {
+  var json = Blockly.Events.Create.superClass_.toJson.call(this);
+  json['xml'] = Blockly.Xml.domToText(this.xml);
+  json['ids'] = this.ids;
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Create.prototype.fromJson = function(json) {
+  Blockly.Events.Create.superClass_.fromJson.call(this, json);
+  this.xml = Blockly.Xml.textToDom('<xml>' + json['xml'] + '</xml>').firstChild;
+  this.ids = json['ids'];
+};
+
+/**
+ * Run a creation event.
+ * @param {boolean} forward True if run forward, false if run backward (undo).
+ */
+Blockly.Events.Create.prototype.run = function(forward) {
+  var workspace = Blockly.Workspace.getById(this.workspaceId);
+  if (forward) {
+    var xml = goog.dom.createDom('xml');
+    xml.appendChild(this.xml);
+    Blockly.Xml.domToWorkspace(xml, workspace);
+  } else {
+    for (var i = 0, id; id = this.ids[i]; i++) {
+      var block = workspace.getBlockById(id);
+      if (block) {
+        block.dispose(false, false);
+      } else if (id == this.blockId) {
+        // Only complain about root-level block.
+        console.warn("Can't uncreate non-existant block: " + id);
+      }
+    }
+  }
+};
+
+/**
+ * Class for a block deletion event.
+ * @param {Blockly.Block} block The deleted block.  Null for a blank event.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.Delete = function(block) {
+  if (!block) {
+    return;  // Blank event to be populated by fromJson.
+  }
+  if (block.getParent()) {
+    throw 'Connected blocks cannot be deleted.';
+  }
+  Blockly.Events.Delete.superClass_.constructor.call(this, block);
+  this.oldXml = Blockly.Xml.blockToDomWithXY(block);
+  this.ids = Blockly.Events.getDescendantIds_(block);
+};
+goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Delete.prototype.type = Blockly.Events.DELETE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Delete.prototype.toJson = function() {
+  var json = Blockly.Events.Delete.superClass_.toJson.call(this);
+  json['ids'] = this.ids;
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Delete.prototype.fromJson = function(json) {
+  Blockly.Events.Delete.superClass_.fromJson.call(this, json);
+  this.ids = json['ids'];
+};
+
+/**
+ * Run a deletion event.
+ * @param {boolean} forward True if run forward, false if run backward (undo).
+ */
+Blockly.Events.Delete.prototype.run = function(forward) {
+  var workspace = Blockly.Workspace.getById(this.workspaceId);
+  if (forward) {
+    for (var i = 0, id; id = this.ids[i]; i++) {
+      var block = workspace.getBlockById(id);
+      if (block) {
+        block.dispose(false, false);
+      } else if (id == this.blockId) {
+        // Only complain about root-level block.
+        console.warn("Can't delete non-existant block: " + id);
+      }
+    }
+  } else {
+    var xml = goog.dom.createDom('xml');
+    xml.appendChild(this.oldXml);
+    Blockly.Xml.domToWorkspace(xml, workspace);
+  }
+};
+
+/**
+ * Class for a block change event.
+ * @param {Blockly.Block} block The changed block.  Null for a blank event.
+ * @param {string} element One of 'field', 'comment', 'disabled', etc.
+ * @param {?string} name Name of input or field affected, or null.
+ * @param {string} oldValue Previous value of element.
+ * @param {string} newValue New value of element.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.Change = function(block, element, name, oldValue, newValue) {
+  if (!block) {
+    return;  // Blank event to be populated by fromJson.
+  }
+  Blockly.Events.Change.superClass_.constructor.call(this, block);
+  this.element = element;
+  this.name = name;
+  this.oldValue = oldValue;
+  this.newValue = newValue;
+};
+goog.inherits(Blockly.Events.Change, Blockly.Events.Abstract);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Change.prototype.type = Blockly.Events.CHANGE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Change.prototype.toJson = function() {
+  var json = Blockly.Events.Change.superClass_.toJson.call(this);
+  json['element'] = this.element;
+  if (this.name) {
+    json['name'] = this.name;
+  }
+  json['newValue'] = this.newValue;
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Change.prototype.fromJson = function(json) {
+  Blockly.Events.Change.superClass_.fromJson.call(this, json);
+  this.element = json['element'];
+  this.name = json['name'];
+  this.newValue = json['newValue'];
+};
+
+/**
+ * Does this event record any change of state?
+ * @return {boolean} True if something changed.
+ */
+Blockly.Events.Change.prototype.isNull = function() {
+  return this.oldValue == this.newValue;
+};
+
+/**
+ * Run a change event.
+ * @param {boolean} forward True if run forward, false if run backward (undo).
+ */
+Blockly.Events.Change.prototype.run = function(forward) {
+  var workspace = Blockly.Workspace.getById(this.workspaceId);
+  var block = workspace.getBlockById(this.blockId);
+  if (!block) {
+    console.warn("Can't change non-existant block: " + this.blockId);
+    return;
+  }
+  if (block.mutator) {
+    // Close the mutator (if open) since we don't want to update it.
+    block.mutator.setVisible(false);
+  }
+  var value = forward ? this.newValue : this.oldValue;
+  switch (this.element) {
+    case 'field':
+      var field = block.getField(this.name);
+      if (field) {
+        // Run the validator for any side-effects it may have.
+        // The validator's opinion on validity is ignored.
+        field.callValidator(value);
+        field.setValue(value);
+      } else {
+        console.warn("Can't set non-existant field: " + this.name);
+      }
+      break;
+    case 'comment':
+      block.setCommentText(value || null);
+      break;
+    case 'collapsed':
+      block.setCollapsed(value);
+      break;
+    case 'disabled':
+      block.setDisabled(value);
+      break;
+    case 'inline':
+      block.setInputsInline(value);
+      break;
+    case 'mutation':
+      var oldMutation = '';
+      if (block.mutationToDom) {
+        var oldMutationDom = block.mutationToDom();
+        oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
+      }
+      if (block.domToMutation) {
+        value = value || '<mutation></mutation>';
+        var dom = Blockly.Xml.textToDom('<xml>' + value + '</xml>');
+        block.domToMutation(dom.firstChild);
+      }
+      Blockly.Events.fire(new Blockly.Events.Change(
+          block, 'mutation', null, oldMutation, value));
+      break;
+    default:
+      console.warn('Unknown change type: ' + this.element);
+  }
+};
+
+/**
+ * Class for a block typing event. FOR BLOCKSCAD
+ * @param {Blockly.Block} block The changed block.  Null for a blank event.
+ * @param {string} oldValue Previous value of type.
+ * @param {string} newValue New value of type.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.Typing = function(block, oldValue, newValue) {
+  if (!block) {
+    return;  // Blank event to be populated by fromJson.
+  }
+  // console.log("in typing event constructor with:" + oldValue + "," + newValue);
+  Blockly.Events.Change.superClass_.constructor.call(this, block);
+  this.oldValue = oldValue;
+  this.newValue = newValue;
+};
+goog.inherits(Blockly.Events.Typing, Blockly.Events.Abstract);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Typing.prototype.type = Blockly.Events.TYPING;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Typing.prototype.toJson = function() {
+  var json = Blockly.Events.Typing.superClass_.toJson.call(this);
+  json['newValue'] = this.newValue;
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Typing.prototype.fromJson = function(json) {
+  Blockly.Events.Typing.superClass_.fromJson.call(this, json);
+  this.newValue = json['newValue'];
+};
+
+/**
+ * Does this event record any change of state?
+ * @return {boolean} True if something changed.
+ */
+Blockly.Events.Typing.prototype.isNull = function() {
+  // return this.oldValue == this.newValue;
+  return false;
+};
+
+/**
+ * Run a typing event.
+ * @param {boolean} forward True if run forward, false if run backward (undo).
+ */
+Blockly.Events.Typing.prototype.run = function(forward) {
+  var workspace = Blockly.Workspace.getById(this.workspaceId);
+  var block = workspace.getBlockById(this.blockId);
+  if (!block) {
+    // console.warn("Can't change type on non-existant block: " + this.blockId);
+    return;
+  }
+  if (block.mutator) {
+    // Close the mutator (if open) since we don't want to update it.
+    block.mutator.setVisible(false);
+  }
+  var value = forward ? this.newValue : this.oldValue;
+
+  // this could be a procedure caller (with a previousConnection)
+  // this could be a variable getter (with an outputConnection)
+  // it should only have one or the other.
+
+  if (block.previousConnection) {
+    block.previousConnection.setCheck(value);
+    if (value == 'CSG')
+      block.category = 'PRIMITIVE_CSG';
+    else if (value == 'CAG')
+      block.category = 'PRIMITIVE_CAG';
+    else block.category = 'UNKNOWN';
+  }
+  else if (block.outputConnection) {
+    block.outputConnection.setCheck(value);
+  }
+
+  block.unbacklight();
+};
+
+/**
+ * Class for a block move event.  Created before the move.
+ * @param {Blockly.Block} block The moved block.  Null for a blank event.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.Move = function(block) {
+  if (!block) {
+    return;  // Blank event to be populated by fromJson.
+  }
+  Blockly.Events.Move.superClass_.constructor.call(this, block);
+  var location = this.currentLocation_();
+  this.oldParentId = location.parentId;
+  this.oldInputName = location.inputName;
+  this.oldCoordinate = location.coordinate;
+};
+goog.inherits(Blockly.Events.Move, Blockly.Events.Abstract);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Move.prototype.type = Blockly.Events.MOVE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Move.prototype.toJson = function() {
+  var json = Blockly.Events.Move.superClass_.toJson.call(this);
+  if (this.newParentId) {
+    json['newParentId'] = this.newParentId;
+  }
+  if (this.newInputName) {
+    json['newInputName'] = this.newInputName;
+  }
+  if (this.newCoordinate) {
+    json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' +
+        Math.round(this.newCoordinate.y);
+  }
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Move.prototype.fromJson = function(json) {
+  Blockly.Events.Move.superClass_.fromJson.call(this, json);
+  this.newParentId = json['newParentId'];
+  this.newInputName = json['newInputName'];
+  if (json['newCoordinate']) {
+    var xy = json['newCoordinate'].split(',');
+    this.newCoordinate =
+        new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1]));
+  }
+};
+
+/**
+ * Record the block's new location.  Called after the move.
+ */
+Blockly.Events.Move.prototype.recordNew = function() {
+  var location = this.currentLocation_();
+  this.newParentId = location.parentId;
+  this.newInputName = location.inputName;
+  this.newCoordinate = location.coordinate;
+};
+
+/**
+ * Returns the parentId and input if the block is connected,
+ *   or the XY location if disconnected.
+ * @return {!Object} Collection of location info.
+ * @private
+ */
+Blockly.Events.Move.prototype.currentLocation_ = function() {
+  var workspace = Blockly.Workspace.getById(this.workspaceId);
+  var block = workspace.getBlockById(this.blockId);
+  var location = {};
+  var parent = block.getParent();
+  if (parent) {
+    location.parentId = parent.id;
+    var input = parent.getInputWithBlock(block);
+    if (input) {
+      location.inputName = input.name;
+    }
+  } else {
+    location.coordinate = block.getRelativeToSurfaceXY();
+  }
+  return location;
+};
+
+/**
+ * Does this event record any change of state?
+ * @return {boolean} True if something changed.
+ */
+Blockly.Events.Move.prototype.isNull = function() {
+  return this.oldParentId == this.newParentId &&
+      this.oldInputName == this.newInputName &&
+      goog.math.Coordinate.equals(this.oldCoordinate, this.newCoordinate);
+};
+
+/**
+ * Run a move event.
+ * @param {boolean} forward True if run forward, false if run backward (undo).
+ */
+Blockly.Events.Move.prototype.run = function(forward) {
+  var workspace = Blockly.Workspace.getById(this.workspaceId);
+  var block = workspace.getBlockById(this.blockId);
+  if (!block) {
+    console.warn("Can't move non-existant block: " + this.blockId);
+    return;
+  }
+  var parentId = forward ? this.newParentId : this.oldParentId;
+  var inputName = forward ? this.newInputName : this.oldInputName;
+  var coordinate = forward ? this.newCoordinate : this.oldCoordinate;
+  var parentBlock = null;
+  if (parentId) {
+    parentBlock = workspace.getBlockById(parentId);
+    if (!parentBlock) {
+      console.warn("Can't connect to non-existant block: " + parentId);
+      return;
+    }
+  }
+  if (block.getParent()) {
+    block.unplug();
+  }
+  if (coordinate) {
+    var xy = block.getRelativeToSurfaceXY();
+    block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y);
+  } else {
+    var blockConnection = block.outputConnection || block.previousConnection;
+    var parentConnection;
+    if (inputName) {
+      var input = parentBlock.getInput(inputName);
+      if (input) {
+        parentConnection = input.connection;
+      }
+    } else if (blockConnection.type == Blockly.PREVIOUS_STATEMENT) {
+      parentConnection = parentBlock.nextConnection;
+    }
+    if (parentConnection) {
+      blockConnection.connect(parentConnection);
+    } else {
+      console.warn("Can't connect to non-existant input: " + inputName);
+    }
+  }
+};
+
+/**
+ * Class for a UI event.
+ * @param {Blockly.Block} block The affected block.
+ * @param {string} element One of 'selected', 'comment', 'mutator', etc.
+ * @param {string} oldValue Previous value of element.
+ * @param {string} newValue New value of element.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.Ui = function(block, element, oldValue, newValue) {
+  Blockly.Events.Ui.superClass_.constructor.call(this, block);
+  this.element = element;
+  this.oldValue = oldValue;
+  this.newValue = newValue;
+  this.recordUndo = false;
+};
+goog.inherits(Blockly.Events.Ui, Blockly.Events.Abstract);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Ui.prototype.type = Blockly.Events.UI;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Ui.prototype.toJson = function() {
+  var json = Blockly.Events.Ui.superClass_.toJson.call(this);
+  json['element'] = this.element;
+  if (this.newValue !== undefined) {
+    json['newValue'] = this.newValue;
+  }
+  return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Ui.prototype.fromJson = function(json) {
+  Blockly.Events.Ui.superClass_.fromJson.call(this, json);
+  this.element = json['element'];
+  this.newValue = json['newValue'];
+};
+
+/**
+ * Enable/disable a block depending on whether it is properly connected.
+ * Use this on applications where all blocks should be connected to a top block.
+ * Recommend setting the 'disable' option to 'false' in the config so that
+ * users don't try to reenable disabled orphan blocks.
+ * @param {!Blockly.Events.Abstract} event Custom data for event.
+ */
+Blockly.Events.disableOrphans = function(event) {
+  if (event.type == Blockly.Events.MOVE ||
+      event.type == Blockly.Events.CREATE) {
+    Blockly.Events.disable();
+    var workspace = Blockly.Workspace.getById(event.workspaceId);
+    var block = workspace.getBlockById(event.blockId);
+    if (block) {
+      if (block.getParent() && !block.getParent().disabled) {
+        var children = block.getDescendants();
+        for (var i = 0, child; child = children[i]; i++) {
+          child.setDisabled(false);
+        }
+      } else if ((block.outputConnection || block.previousConnection) &&
+                 Blockly.dragMode_ == Blockly.DRAG_NONE) {
+        do {
+          block.setDisabled(true);
+          block = block.getNextBlock();
+        } while (block);
+      }
+    }
+    Blockly.Events.enable();
+  }
+};

+ 498 - 0
blockly/core/field.js

@@ -0,0 +1,498 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Field.  Used for editable titles, variables, etc.
+ * This is an abstract class that defines the UI on the block.  Actual
+ * instances would be Blockly.FieldTextInput, Blockly.FieldDropdown, etc.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Field');
+
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.math.Size');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+/**
+ * Abstract class for an editable field.
+ * @param {string} text The initial content of the field.
+ * @param {Function=} opt_validator An optional function that is called
+ *     to validate any constraints on what the user entered.  Takes the new
+ *     text as an argument and returns either the accepted text, a replacement
+ *     text, or null to abort the change.
+ * @constructor
+ */
+Blockly.Field = function(text, opt_validator) {
+  this.size_ = new goog.math.Size(0, 25);
+  this.setValue(text);
+  this.setValidator(opt_validator);
+};
+
+/**
+ * Temporary cache of text widths.
+ * @type {Object}
+ * @private
+ */
+Blockly.Field.cacheWidths_ = null;
+
+/**
+ * Number of current references to cache.
+ * @type {number}
+ * @private
+ */
+Blockly.Field.cacheReference_ = 0;
+
+
+/**
+ * Name of field.  Unique within each block.
+ * Static labels are usually unnamed.
+ * @type {string=}
+ */
+Blockly.Field.prototype.name = undefined;
+
+/**
+ * Maximum characters of text to display before adding an ellipsis.
+ * @type {number}
+ */
+Blockly.Field.prototype.maxDisplayLength = 50;
+
+/**
+ * Visible text to display.
+ * @type {string}
+ * @private
+ */
+Blockly.Field.prototype.text_ = '';
+
+/**
+ * Block this field is attached to.  Starts as null, then in set in init.
+ * @type {Blockly.Block}
+ * @private
+ */
+Blockly.Field.prototype.sourceBlock_ = null;
+
+/**
+ * Is the field visible, or hidden due to the block being collapsed?
+ * @type {boolean}
+ * @private
+ */
+Blockly.Field.prototype.visible_ = true;
+
+/**
+ * Validation function called when user edits an editable field.
+ * @type {Function}
+ * @private
+ */
+Blockly.Field.prototype.validator_ = null;
+
+/**
+ * Non-breaking space.
+ * @const
+ */
+Blockly.Field.NBSP = '\u00A0';
+
+/**
+ * Editable fields are saved by the XML renderer, non-editable fields are not.
+ */
+Blockly.Field.prototype.EDITABLE = true;
+
+/**
+ * Attach this field to a block.
+ * @param {!Blockly.Block} block The block containing this field.
+ */
+Blockly.Field.prototype.setSourceBlock = function(block) {
+  goog.asserts.assert(!this.sourceBlock_, 'Field already bound to a block.');
+  this.sourceBlock_ = block;
+};
+
+/**
+ * Install this field on a block.
+ */
+Blockly.Field.prototype.init = function() {
+  if (this.fieldGroup_) {
+    // Field has already been initialized once.
+    return;
+  }
+  // Build the DOM.
+  this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
+  if (!this.visible_) {
+    this.fieldGroup_.style.display = 'none';
+  }
+  this.borderRect_ = Blockly.createSvgElement('rect',
+      {'rx': 4,
+       'ry': 4,
+       'x': -Blockly.BlockSvg.SEP_SPACE_X / 2,
+       'y': 0,
+       'height': 16}, this.fieldGroup_, this.sourceBlock_.workspace);
+  /** @type {!Element} */
+  this.textElement_ = Blockly.createSvgElement('text',
+      {'class': 'blocklyText', 'y': this.size_.height - 12.5},
+      this.fieldGroup_);
+
+  this.updateEditable();
+  this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
+  this.mouseUpWrapper_ =
+      Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_);
+  // Force a render.
+  this.updateTextNode_();
+};
+
+/**
+ * Dispose of all DOM objects belonging to this editable field.
+ */
+Blockly.Field.prototype.dispose = function() {
+  if (this.mouseUpWrapper_) {
+    Blockly.unbindEvent_(this.mouseUpWrapper_);
+    this.mouseUpWrapper_ = null;
+  }
+  this.sourceBlock_ = null;
+  goog.dom.removeNode(this.fieldGroup_);
+  this.fieldGroup_ = null;
+  this.textElement_ = null;
+  this.borderRect_ = null;
+  this.validator_ = null;
+};
+
+/**
+ * Add or remove the UI indicating if this field is editable or not.
+ */
+Blockly.Field.prototype.updateEditable = function() {
+  if (!this.EDITABLE || !this.fieldGroup_) {
+    return;
+  }
+  if (this.sourceBlock_.isEditable()) {
+    Blockly.addClass_(/** @type {!Element} */ (this.fieldGroup_),
+                      'blocklyEditableText');
+    Blockly.removeClass_(/** @type {!Element} */ (this.fieldGroup_),
+                         'blocklyNonEditableText');
+    this.fieldGroup_.style.cursor = this.CURSOR;
+  } else {
+    Blockly.addClass_(/** @type {!Element} */ (this.fieldGroup_),
+                      'blocklyNonEditableText');
+    Blockly.removeClass_(/** @type {!Element} */ (this.fieldGroup_),
+                         'blocklyEditableText');
+    this.fieldGroup_.style.cursor = '';
+  }
+};
+
+/**
+ * Gets whether this editable field is visible or not.
+ * @return {boolean} True if visible.
+ */
+Blockly.Field.prototype.isVisible = function() {
+  return this.visible_;
+};
+
+/**
+ * Sets whether this editable field is visible or not.
+ * @param {boolean} visible True if visible.
+ */
+Blockly.Field.prototype.setVisible = function(visible) {
+  if (this.visible_ == visible) {
+    return;
+  }
+  this.visible_ = visible;
+  var root = this.getSvgRoot();
+  if (root) {
+    root.style.display = visible ? 'block' : 'none';
+    this.render_();
+  }
+};
+
+/**
+ * Sets a new validation function for editable fields.
+ * @param {Function} handler New validation function, or null.
+ */
+Blockly.Field.prototype.setValidator = function(handler) {
+  this.validator_ = handler;
+};
+
+/**
+ * Gets the validation function for editable fields.
+ * @return {Function} Validation function, or null.
+ */
+Blockly.Field.prototype.getValidator = function() {
+  return this.validator_;
+};
+
+/**
+ * Validates a change.  Does nothing.  Subclasses may override this.
+ * @param {string} text The user's text.
+ * @return {string} No change needed.
+ */
+Blockly.Field.prototype.classValidator = function(text) {
+  return text;
+};
+
+/**
+ * Calls the validation function for this field, as well as all the validation
+ * function for the field's class and its parents.
+ * @param {string} text Proposed text.
+ * @return {?string} Revised text, or null if invalid.
+ */
+Blockly.Field.prototype.callValidator = function(text) {
+  var classResult = this.classValidator(text);
+  if (classResult === null) {
+    // Class validator rejects value.  Game over.
+    return null;
+  } else if (classResult !== undefined) {
+    text = classResult;
+  }
+  var userValidator = this.getValidator();
+  if (userValidator) {
+    var userResult = userValidator.call(this, text);
+    if (userResult === null) {
+      // User validator rejects value.  Game over.
+      return null;
+    } else if (userResult !== undefined) {
+      text = userResult;
+    }
+  }
+  return text;
+};
+
+/**
+ * Gets the group element for this editable field.
+ * Used for measuring the size and for positioning.
+ * @return {!Element} The group element.
+ */
+Blockly.Field.prototype.getSvgRoot = function() {
+  return /** @type {!Element} */ (this.fieldGroup_);
+};
+
+/**
+ * Draws the border with the correct width.
+ * Saves the computed width in a property.
+ * @private
+ */
+Blockly.Field.prototype.render_ = function() {
+  if (this.visible_ && this.textElement_) {
+    var key = this.textElement_.textContent + '\n' +
+        this.textElement_.className.baseVal;
+    if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) {
+      var width = Blockly.Field.cacheWidths_[key];
+    } else {
+      try {
+        var width = this.textElement_.getComputedTextLength();
+      } catch (e) {
+        // MSIE 11 is known to throw "Unexpected call to method or property
+        // access." if Blockly is hidden.
+        var width = this.textElement_.textContent.length * 8;
+      }
+      if (Blockly.Field.cacheWidths_) {
+        Blockly.Field.cacheWidths_[key] = width;
+      }
+    }
+    if (this.borderRect_) {
+      this.borderRect_.setAttribute('width',
+          width + Blockly.BlockSvg.SEP_SPACE_X);
+    }
+  } else {
+    var width = 0;
+  }
+  this.size_.width = width;
+};
+
+/**
+ * Start caching field widths.  Every call to this function MUST also call
+ * stopCache.  Caches must not survive between execution threads.
+ */
+Blockly.Field.startCache = function() {
+  Blockly.Field.cacheReference_++;
+  if (!Blockly.Field.cacheWidths_) {
+    Blockly.Field.cacheWidths_ = {};
+  }
+};
+
+/**
+ * Stop caching field widths.  Unless caching was already on when the
+ * corresponding call to startCache was made.
+ */
+Blockly.Field.stopCache = function() {
+  Blockly.Field.cacheReference_--;
+  if (!Blockly.Field.cacheReference_) {
+    Blockly.Field.cacheWidths_ = null;
+  }
+};
+
+/**
+ * Returns the height and width of the field.
+ * @return {!goog.math.Size} Height and width.
+ */
+Blockly.Field.prototype.getSize = function() {
+  if (!this.size_.width) {
+    this.render_();
+  }
+  return this.size_;
+};
+
+/**
+ * Returns the height and width of the field,
+ * accounting for the workspace scaling.
+ * @return {!goog.math.Size} Height and width.
+ * @private
+ */
+Blockly.Field.prototype.getScaledBBox_ = function() {
+  var bBox = this.borderRect_.getBBox();
+  // Create new object, as getBBox can return an uneditable SVGRect in IE.
+  return new goog.math.Size(bBox.width * this.sourceBlock_.workspace.scale,
+                            bBox.height * this.sourceBlock_.workspace.scale);
+};
+
+/**
+ * Get the text from this field.
+ * @return {string} Current text.
+ */
+Blockly.Field.prototype.getText = function() {
+  return this.text_;
+};
+
+/**
+ * Set the text in this field.  Trigger a rerender of the source block.
+ * @param {*} text New text.
+ */
+Blockly.Field.prototype.setText = function(text) {
+  if (text === null) {
+    // No change if null.
+    return;
+  }
+  text = String(text);
+  if (text === this.text_) {
+    // No change.
+    return;
+  }
+  this.text_ = text;
+  this.updateTextNode_();
+
+  if (this.sourceBlock_ && this.sourceBlock_.rendered) {
+    this.sourceBlock_.render();
+    this.sourceBlock_.bumpNeighbours_();
+  }
+};
+
+/**
+ * Update the text node of this field to display the current text.
+ * @private
+ */
+Blockly.Field.prototype.updateTextNode_ = function() {
+  if (!this.textElement_) {
+    // Not rendered yet.
+    return;
+  }
+  var text = this.text_;
+  if (text.length > this.maxDisplayLength) {
+    // Truncate displayed string and add an ellipsis ('...').
+    text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
+  }
+  // Empty the text element.
+  goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_));
+  // Replace whitespace with non-breaking spaces so the text doesn't collapse.
+  text = text.replace(/\s/g, Blockly.Field.NBSP);
+  if (this.sourceBlock_.RTL && text) {
+    // The SVG is LTR, force text to be RTL.
+    text += '\u200F';
+  }
+  if (!text) {
+    // Prevent the field from disappearing if empty.
+    text = Blockly.Field.NBSP;
+  }
+  var textNode = document.createTextNode(text);
+  this.textElement_.appendChild(textNode);
+
+  // Cached width is obsolete.  Clear it.
+  this.size_.width = 0;
+};
+
+/**
+ * By default there is no difference between the human-readable text and
+ * the language-neutral values.  Subclasses (such as dropdown) may define this.
+ * @return {string} Current text.
+ */
+Blockly.Field.prototype.getValue = function() {
+  return this.getText();
+};
+
+/**
+ * By default there is no difference between the human-readable text and
+ * the language-neutral values.  Subclasses (such as dropdown) may define this.
+ * @param {string} newText New text.
+ */
+Blockly.Field.prototype.setValue = function(newText) {
+  if (newText === null) {
+    // No change if null.
+    return;
+  }
+  var oldText = this.getValue();
+  if (oldText == newText) {
+    return;
+  }
+  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this.sourceBlock_, 'field', this.name, oldText, newText));
+  }
+  this.setText(newText);
+};
+
+/**
+ * Handle a mouse up event on an editable field.
+ * @param {!Event} e Mouse up event.
+ * @private
+ */
+Blockly.Field.prototype.onMouseUp_ = function(e) {
+  if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) &&
+      !goog.userAgent.isVersionOrHigher('537.51.2') &&
+      e.layerX !== 0 && e.layerY !== 0) {
+    // Old iOS spawns a bogus event on the next touch after a 'prompt()' edit.
+    // Unlike the real events, these have a layerX and layerY set.
+    return;
+  } else if (Blockly.isRightButton(e)) {
+    // Right-click.
+    return;
+  } else if (this.sourceBlock_.workspace.isDragging()) {
+    // Drag operation is concluding.  Don't open the editor.
+    return;
+  } else if (this.sourceBlock_.isEditable()) {
+    // Non-abstract sub-classes must define a showEditor_ method.
+    this.showEditor_();
+  }
+};
+
+/**
+ * Change the tooltip text for this field.
+ * @param {string|!Element} newTip Text for tooltip or a parent element to
+ *     link to for its tooltip.
+ */
+Blockly.Field.prototype.setTooltip = function(newTip) {
+  // Non-abstract sub-classes may wish to implement this.  See FieldLabel.
+};
+
+/**
+ * Return the absolute coordinates of the top-left corner of this field.
+ * The origin (0,0) is the top-left corner of the page body.
+ * @return {!goog.math.Coordinate} Object with .x and .y properties.
+ * @private
+ */
+Blockly.Field.prototype.getAbsoluteXY_ = function() {
+  return goog.style.getPageOffset(this.borderRect_);
+};

+ 294 - 0
blockly/core/field_angle.js

@@ -0,0 +1,294 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2013 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 Angle input field.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldAngle');
+
+goog.require('Blockly.FieldTextInput');
+goog.require('goog.math');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for an editable angle field.
+ * @param {string} text The initial content of the field.
+ * @param {Function=} opt_validator An optional function that is called
+ *     to validate any constraints on what the user entered.  Takes the new
+ *     text as an argument and returns the accepted text or null to abort
+ *     the change.
+ * @extends {Blockly.FieldTextInput}
+ * @constructor
+ */
+Blockly.FieldAngle = function(text, opt_validator) {
+  // Add degree symbol: "360°" (LTR) or "°360" (RTL)
+  this.symbol_ = Blockly.createSvgElement('tspan', {}, null);
+  this.symbol_.appendChild(document.createTextNode('\u00B0'));
+
+  Blockly.FieldAngle.superClass_.constructor.call(this, text, opt_validator);
+};
+goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
+
+/**
+ * Round angles to the nearest 15 degrees when using mouse.
+ * Set to 0 to disable rounding.
+ */
+Blockly.FieldAngle.ROUND = 15;
+
+/**
+ * Half the width of protractor image.
+ */
+Blockly.FieldAngle.HALF = 100 / 2;
+
+/* The following two settings work together to set the behaviour of the angle
+ * picker.  While many combinations are possible, two modes are typical:
+ * Math mode.
+ *   0 deg is right, 90 is up.  This is the style used by protractors.
+ *   Blockly.FieldAngle.CLOCKWISE = false;
+ *   Blockly.FieldAngle.OFFSET = 0;
+ * Compass mode.
+ *   0 deg is up, 90 is right.  This is the style used by maps.
+ *   Blockly.FieldAngle.CLOCKWISE = true;
+ *   Blockly.FieldAngle.OFFSET = 90;
+ */
+
+/**
+ * Angle increases clockwise (true) or counterclockwise (false).
+ */
+Blockly.FieldAngle.CLOCKWISE = false;
+
+/**
+ * Offset the location of 0 degrees (and all angles) by a constant.
+ * Usually either 0 (0 = right) or 90 (0 = up).
+ */
+Blockly.FieldAngle.OFFSET = 0;
+
+/**
+ * Maximum allowed angle before wrapping.
+ * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180).
+ */
+Blockly.FieldAngle.WRAP = 360;
+
+
+/**
+ * Radius of protractor circle.  Slightly smaller than protractor size since
+ * otherwise SVG crops off half the border at the edges.
+ */
+Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1;
+
+/**
+ * Clean up this FieldAngle, as well as the inherited FieldTextInput.
+ * @return {!Function} Closure to call on destruction of the WidgetDiv.
+ * @private
+ */
+Blockly.FieldAngle.prototype.dispose_ = function() {
+  var thisField = this;
+  return function() {
+    Blockly.FieldAngle.superClass_.dispose_.call(thisField)();
+    thisField.gauge_ = null;
+    if (thisField.clickWrapper_) {
+      Blockly.unbindEvent_(thisField.clickWrapper_);
+    }
+    if (thisField.moveWrapper1_) {
+      Blockly.unbindEvent_(thisField.moveWrapper1_);
+    }
+    if (thisField.moveWrapper2_) {
+      Blockly.unbindEvent_(thisField.moveWrapper2_);
+    }
+  };
+};
+
+/**
+ * Show the inline free-text editor on top of the text.
+ * @private
+ */
+Blockly.FieldAngle.prototype.showEditor_ = function() {
+  var noFocus =
+      goog.userAgent.MOBILE || goog.userAgent.ANDROID || goog.userAgent.IPAD;
+  // Mobile browsers have issues with in-line textareas (focus & keyboards).
+  Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus);
+  var div = Blockly.WidgetDiv.DIV;
+  if (!div.firstChild) {
+    // Mobile interface uses window.prompt.
+    return;
+  }
+  // Build the SVG DOM.
+  var svg = Blockly.createSvgElement('svg', {
+    'xmlns': 'http://www.w3.org/2000/svg',
+    'xmlns:html': 'http://www.w3.org/1999/xhtml',
+    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
+    'version': '1.1',
+    'height': (Blockly.FieldAngle.HALF * 2) + 'px',
+    'width': (Blockly.FieldAngle.HALF * 2) + 'px'
+  }, div);
+  var circle = Blockly.createSvgElement('circle', {
+    'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF,
+    'r': Blockly.FieldAngle.RADIUS,
+    'class': 'blocklyAngleCircle'
+  }, svg);
+  this.gauge_ = Blockly.createSvgElement('path',
+      {'class': 'blocklyAngleGauge'}, svg);
+  this.line_ = Blockly.createSvgElement('line',
+      {'x1': Blockly.FieldAngle.HALF,
+      'y1': Blockly.FieldAngle.HALF,
+      'class': 'blocklyAngleLine'}, svg);
+  // Draw markers around the edge.
+  for (var angle = 0; angle < 360; angle += 15) {
+    Blockly.createSvgElement('line', {
+      'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS,
+      'y1': Blockly.FieldAngle.HALF,
+      'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS -
+          (angle % 45 == 0 ? 10 : 5),
+      'y2': Blockly.FieldAngle.HALF,
+      'class': 'blocklyAngleMarks',
+      'transform': 'rotate(' + angle + ',' +
+          Blockly.FieldAngle.HALF + ',' + Blockly.FieldAngle.HALF + ')'
+    }, svg);
+  }
+  svg.style.marginLeft = (15 - Blockly.FieldAngle.RADIUS) + 'px';
+  this.clickWrapper_ =
+      Blockly.bindEvent_(svg, 'click', this, Blockly.WidgetDiv.hide);
+  this.moveWrapper1_ =
+      Blockly.bindEvent_(circle, 'mousemove', this, this.onMouseMove);
+  this.moveWrapper2_ =
+      Blockly.bindEvent_(this.gauge_, 'mousemove', this, this.onMouseMove);
+  this.updateGraph_();
+};
+
+/**
+ * Set the angle to match the mouse's position.
+ * @param {!Event} e Mouse move event.
+ */
+Blockly.FieldAngle.prototype.onMouseMove = function(e) {
+  var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
+  var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF;
+  var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF;
+  var angle = Math.atan(-dy / dx);
+  if (isNaN(angle)) {
+    // This shouldn't happen, but let's not let this error propogate further.
+    return;
+  }
+  angle = goog.math.toDegrees(angle);
+  // 0: East, 90: North, 180: West, 270: South.
+  if (dx < 0) {
+    angle += 180;
+  } else if (dy > 0) {
+    angle += 360;
+  }
+  if (Blockly.FieldAngle.CLOCKWISE) {
+    angle = Blockly.FieldAngle.OFFSET + 360 - angle;
+  } else {
+    angle -= Blockly.FieldAngle.OFFSET;
+  }
+  if (Blockly.FieldAngle.ROUND) {
+    angle = Math.round(angle / Blockly.FieldAngle.ROUND) *
+        Blockly.FieldAngle.ROUND;
+  }
+  angle = this.callValidator(angle);
+  Blockly.FieldTextInput.htmlInput_.value = angle;
+  this.setValue(angle);
+  this.validate_();
+  this.resizeEditor_();
+};
+
+/**
+ * Insert a degree symbol.
+ * @param {?string} text New text.
+ */
+Blockly.FieldAngle.prototype.setText = function(text) {
+  Blockly.FieldAngle.superClass_.setText.call(this, text);
+  if (!this.textElement_) {
+    // Not rendered yet.
+    return;
+  }
+  this.updateGraph_();
+  // Insert degree symbol.
+  if (this.sourceBlock_.RTL) {
+    this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild);
+  } else {
+    this.textElement_.appendChild(this.symbol_);
+  }
+  // Cached width is obsolete.  Clear it.
+  this.size_.width = 0;
+};
+
+/**
+ * Redraw the graph with the current angle.
+ * @private
+ */
+Blockly.FieldAngle.prototype.updateGraph_ = function() {
+  if (!this.gauge_) {
+    return;
+  }
+  var angleDegrees = Number(this.getText()) + Blockly.FieldAngle.OFFSET;
+  var angleRadians = goog.math.toRadians(angleDegrees);
+  var path = ['M ', Blockly.FieldAngle.HALF, ',', Blockly.FieldAngle.HALF];
+  var x2 = Blockly.FieldAngle.HALF;
+  var y2 = Blockly.FieldAngle.HALF;
+  if (!isNaN(angleRadians)) {
+    var angle1 = goog.math.toRadians(Blockly.FieldAngle.OFFSET);
+    var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS;
+    var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS;
+    if (Blockly.FieldAngle.CLOCKWISE) {
+      angleRadians = 2 * angle1 - angleRadians;
+    }
+    x2 += Math.cos(angleRadians) * Blockly.FieldAngle.RADIUS;
+    y2 -= Math.sin(angleRadians) * Blockly.FieldAngle.RADIUS;
+    // Don't ask how the flag calculations work.  They just do.
+    var largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
+    if (Blockly.FieldAngle.CLOCKWISE) {
+      largeFlag = 1 - largeFlag;
+    }
+    var sweepFlag = Number(Blockly.FieldAngle.CLOCKWISE);
+    path.push(' l ', x1, ',', y1,
+        ' A ', Blockly.FieldAngle.RADIUS, ',', Blockly.FieldAngle.RADIUS,
+        ' 0 ', largeFlag, ' ', sweepFlag, ' ', x2, ',', y2, ' z');
+  }
+  this.gauge_.setAttribute('d', path.join(''));
+  this.line_.setAttribute('x2', x2);
+  this.line_.setAttribute('y2', y2);
+};
+
+/**
+ * Ensure that only an angle may be entered.
+ * @param {string} text The user's text.
+ * @return {?string} A string representing a valid angle, or null if invalid.
+ */
+Blockly.FieldAngle.prototype.classValidator = function(text) {
+  if (text === null) {
+    return null;
+  }
+  var n = parseFloat(text || 0);
+  if (isNaN(n)) {
+    return null;
+  }
+  n = n % 360;
+  if (n < 0) {
+    n += 360;
+  }
+  if (n > Blockly.FieldAngle.WRAP) {
+    n -= 360;
+  }
+  return String(n);
+};

+ 170 - 0
blockly/core/field_checkbox.js

@@ -0,0 +1,170 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Checkbox field.  Checked or not checked.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldCheckbox');
+
+goog.require('Blockly.Field');
+
+// how big should toggleable images be?
+var sz = 25;
+
+/**
+ * Class for a checkbox field.
+ * @param {string} state The initial state of the field ('TRUE' or 'FALSE').
+ * @param {Function=} opt_validator A function that is executed when a new
+ *     option is selected.  Its sole argument is the new checkbox state.  If
+ *     it returns a value, this becomes the new checkbox state, unless the
+ *     value is null, in which case the change is aborted.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldCheckbox = function(state, opt_validator,img1,img2) {
+  Blockly.FieldCheckbox.superClass_.constructor.call(this, '', opt_validator);
+  // do I have two images?
+  if (img1 && img2) {
+    this.img1 = img1;
+    this.img2 = img2;
+    this.size_ = new goog.math.Size(sz, sz * 1.1);
+  }
+  // Set the initial state.
+  // console.log("setting fieldCheckbox initial state to:",state);
+  this.setValue(state);
+};
+goog.inherits(Blockly.FieldCheckbox, Blockly.Field);
+
+/**
+ * Character for the checkmark.
+ */
+Blockly.FieldCheckbox.CHECK_CHAR = '\u2713';
+
+/**
+ * Mouse cursor style when over the hotspot that initiates editability.
+ */
+Blockly.FieldCheckbox.prototype.CURSOR = 'default';
+
+Blockly.FieldCheckbox.prototype.EDITABLE = true;
+
+/**
+ * Install this checkbox on a block.
+ */
+Blockly.FieldCheckbox.prototype.init = function() {
+  if (this.fieldGroup_) {
+    // Checkbox has already been initialized once.
+    return;
+  }
+
+  if (!(this.img1 && this.img2)) {
+    Blockly.FieldCheckbox.superClass_.init.call(this);
+    // The checkbox doesn't use the inherited text element.
+    // Instead it uses a custom checkmark element that is either visible or not.
+    this.checkElement_ = Blockly.createSvgElement('text',
+        {'class': 'blocklyText blocklyCheckbox', 'x': -3, 'y': 14},
+        this.fieldGroup_);
+    var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);
+    this.checkElement_.appendChild(textNode);
+    this.checkElement_.style.display = this.state_ ? 'block' : 'none';
+  }
+
+  else {
+    // code from field_image and field prototype init
+    // console.log("got some images");
+    // Build the DOM.
+    this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
+    if (!this.visible_) {
+      this.fieldGroup_.style.display = 'none';
+    }
+
+    this.imageElement_ = Blockly.createSvgElement('image',
+        {'height': sz + 'px',
+         'width': sz * 1.1 + 'px',
+         'x': 5, 'y': -5}, this.fieldGroup_);
+    this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
+        'xlink:href', (this.state_)  ? this.img1 : this.img2);
+
+    // if (goog.userAgent.GECKO) {
+    //   // Due to a Firefox bug which eats mouse events on image elements,
+    //   // a transparent rectangle needs to be placed on top of the image.
+    //   this.rectElement_ = Blockly.createSvgElement('rect',
+    //       {'height': sz + 'px',
+    //        'width': sz + 'px',
+    //        'fill-opacity': 0}, this.fieldGroup_);
+    // }
+
+    this.updateEditable();
+    this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
+    this.mouseUpWrapper_ =
+        Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_);
+  }
+};
+
+/**
+ * Return 'TRUE' if the checkbox is checked, 'FALSE' otherwise.
+ * @return {string} Current state.
+ */
+Blockly.FieldCheckbox.prototype.getValue = function() {
+  return String(this.state_).toUpperCase();
+};
+
+/**
+ * Set the checkbox to be checked if strBool is 'TRUE', unchecks otherwise.
+ * Can also toggle between two images if they exist.
+ * @param {string} strBool New state.
+ */
+Blockly.FieldCheckbox.prototype.setValue = function(strBool) {
+  var newState = (strBool == 'TRUE');
+
+  if (this.state_ !== newState) {
+    if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
+      Blockly.Events.fire(new Blockly.Events.Change(
+          this.sourceBlock_, 'field', this.name, this.state_, newState));
+    }
+    this.state_ = newState;
+    // console.log("setting checkbox to:",this.state_);
+    if (this.checkElement_) {
+      this.checkElement_.style.display = newState ? 'block' : 'none';
+    }
+    else if (this.imageElement_) {
+      this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
+          'xlink:href', newState ? this.img1 : this.img2);
+    }
+
+  }
+};
+
+/**
+ * Toggle the state of the checkbox.
+ * @private
+ */
+Blockly.FieldCheckbox.prototype.showEditor_ = function() {
+  var newState = !this.state_;
+  if (this.sourceBlock_) {
+    // Call any validation function, and allow it to override.
+    newState = this.callValidator(newState);
+  }
+  if (newState !== null) {
+    this.setValue(String(newState).toUpperCase());
+  }
+};

+ 262 - 0
blockly/core/field_colour.js

@@ -0,0 +1,262 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Colour input field.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldColour');
+
+goog.require('Blockly.Field');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.style');
+goog.require('goog.ui.ColorPicker');
+
+
+/**
+ * Array of colors for a 7-cell wide simple-grid color picker.
+ * @type {Array.<string>}
+ */
+Blockly.SIMPLE_GRID_COLORS = [
+  // grays
+  '#ffffff', '#cccccc', '#c0c0c0', '#999999', '#666666', '#333333', '#000000',
+  // reds
+  '#ffcccc', '#ff6666', '#ee0000', '#cc0000', '#990000', '#660000', '#330000',
+  // oranges
+  '#ffcc99', '#ff9966', '#ff9900', '#ff6600', '#cc6600', '#993300', '#663300',
+  // yellows
+  '#ffff99', '#ffff66', '#ffcc66', '#ffcc33', '#cc9933', '#996633', '#663333',
+  // olives
+  '#e2c48c', '#ffff33', '#ffff00', '#ffcc00', '#999900', '#666600', '#333300',
+  // greens
+  '#99ff99', '#66ff99', '#33ff33', '#33cc00', '#009900', '#006600', '#003300',
+  // turquoises
+  '#99ffff', '#33ffff', '#66cccc', '#00cccc', '#339999', '#336666', '#003333',
+  // blues
+  '#ccffff', '#66ffff', '#33ccff', '#3366ff', '#3333ff', '#000099', '#000066',
+  // purples
+  '#ccccff', '#9999ff', '#6666cc', '#6633ff', '#6600cc', '#333399', '#330099',
+  // violets
+  '#ffccff', '#ff80ff', '#cc66cc', '#cc33cc', '#993399', '#663366', '#330033'
+];
+
+/**
+ * Class for a colour input field.
+ * @param {string} colour The initial colour in '#rrggbb' format.
+ * @param {Function=} opt_validator A function that is executed when a new
+ *     colour is selected.  Its sole argument is the new colour value.  Its
+ *     return value becomes the selected colour, unless it is undefined, in
+ *     which case the new colour stands, or it is null, in which case the change
+ *     is aborted.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldColour = function(colour, opt_validator) {
+  Blockly.FieldColour.superClass_.constructor.call(this, colour, opt_validator);
+  this.setText(Blockly.Field.NBSP + Blockly.Field.NBSP + Blockly.Field.NBSP);
+};
+goog.inherits(Blockly.FieldColour, Blockly.Field);
+
+/**
+ * By default use the global constants for colours.
+ * @type {Array.<string>}
+ * @private
+ */
+Blockly.FieldColour.prototype.colours_ = null;
+
+/**
+ * By default use the global constants for columns.
+ * @type {number}
+ * @private
+ */
+Blockly.FieldColour.prototype.columns_ = 0;
+
+/**
+ * Install this field on a block.
+ */
+Blockly.FieldColour.prototype.init = function() {
+  Blockly.FieldColour.superClass_.init.call(this);
+  this.borderRect_.style['fillOpacity'] = 1;
+  this.setValue(this.getValue());
+};
+
+/**
+ * Mouse cursor style when over the hotspot that initiates the editor.
+ */
+Blockly.FieldColour.prototype.CURSOR = 'default';
+
+/**
+ * Close the colour picker if this input is being deleted.
+ */
+Blockly.FieldColour.prototype.dispose = function() {
+  Blockly.WidgetDiv.hideIfOwner(this);
+  Blockly.FieldColour.superClass_.dispose.call(this);
+};
+
+/**
+ * Return the current colour.
+ * @return {string} Current colour in '#rrggbb' format.
+ */
+Blockly.FieldColour.prototype.getValue = function() {
+  return this.colour_;
+};
+
+/**
+ * Set the colour.
+ * @param {string} colour The new colour in '#rrggbb' format.
+ */
+Blockly.FieldColour.prototype.setValue = function(colour) {
+  if (this.sourceBlock_ && Blockly.Events.isEnabled() &&
+      this.colour_ != colour) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this.sourceBlock_, 'field', this.name, this.colour_, colour));
+  }
+  this.colour_ = colour;
+  if (this.borderRect_) {
+    this.borderRect_.style.fill = colour;
+  }
+};
+
+/**
+ * Get the text from this field.  Used when the block is collapsed.
+ * @return {string} Current text.
+ */
+Blockly.FieldColour.prototype.getText = function() {
+  var colour = this.colour_;
+  // Try to use #rgb format if possible, rather than #rrggbb.
+  var m = colour.match(/^#(.)\1(.)\2(.)\3$/);
+  if (m) {
+    colour = '#' + m[1] + m[2] + m[3];
+  }
+  return colour;
+};
+
+/**
+ * An array of colour strings for the palette.
+ * See bottom of this page for the default:
+ * http://docs.closure-library.googlecode.com/git/closure_goog_ui_colorpicker.js.source.html
+ * @type {!Array.<string>}
+ */
+Blockly.FieldColour.COLOURS = Blockly.SIMPLE_GRID_COLORS;
+
+/**
+ * Number of columns in the palette.
+ */
+Blockly.FieldColour.COLUMNS = 7;
+
+/**
+ * Set a custom colour grid for this field.
+ * @param {Array.<string>} colours Array of colours for this block,
+ *     or null to use default (Blockly.FieldColour.COLOURS).
+ * @return {!Blockly.FieldColour} Returns itself (for method chaining).
+ */
+Blockly.FieldColour.prototype.setColours = function(colours) {
+  this.colours_ = colours;
+  return this;
+};
+
+/**
+ * Set a custom grid size for this field.
+ * @param {number} columns Number of columns for this block,
+ *     or 0 to use default (Blockly.FieldColour.COLUMNS).
+ * @return {!Blockly.FieldColour} Returns itself (for method chaining).
+ */
+Blockly.FieldColour.prototype.setColumns = function(columns) {
+  this.columns_ = columns;
+  return this;
+};
+
+/**
+ * Create a palette under the colour field.
+ * @private
+ */
+Blockly.FieldColour.prototype.showEditor_ = function() {
+  Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
+      Blockly.FieldColour.widgetDispose_);
+  // Create the palette using Closure.
+  var picker = new goog.ui.ColorPicker();
+  picker.setSize(this.columns_ || Blockly.FieldColour.COLUMNS);
+  picker.setColors(this.colours_ || Blockly.FieldColour.COLOURS);
+
+  // Position the palette to line up with the field.
+  // Record windowSize and scrollOffset before adding the palette.
+  var windowSize = goog.dom.getViewportSize();
+  var scrollOffset = goog.style.getViewportPageOffset(document);
+  var xy = this.getAbsoluteXY_();
+  var borderBBox = this.getScaledBBox_();
+  var div = Blockly.WidgetDiv.DIV;
+  picker.render(div);
+  picker.setSelectedColor(this.getValue());
+  // Record paletteSize after adding the palette.
+  var paletteSize = goog.style.getSize(picker.getElement());
+
+  // Flip the palette vertically if off the bottom.
+  if (xy.y + paletteSize.height + borderBBox.height >=
+      windowSize.height + scrollOffset.y) {
+    xy.y -= paletteSize.height - 1;
+  } else {
+    xy.y += borderBBox.height - 1;
+  }
+  if (this.sourceBlock_.RTL) {
+    xy.x += borderBBox.width;
+    xy.x -= paletteSize.width;
+    // Don't go offscreen left.
+    if (xy.x < scrollOffset.x) {
+      xy.x = scrollOffset.x;
+    }
+  } else {
+    // Don't go offscreen right.
+    if (xy.x > windowSize.width + scrollOffset.x - paletteSize.width) {
+      xy.x = windowSize.width + scrollOffset.x - paletteSize.width;
+    }
+  }
+  Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
+                             this.sourceBlock_.RTL);
+
+  // Configure event handler.
+  var thisField = this;
+  Blockly.FieldColour.changeEventKey_ = goog.events.listen(picker,
+      goog.ui.ColorPicker.EventType.CHANGE,
+      function(event) {
+        var colour = event.target.getSelectedColor() || '#000000';
+        Blockly.WidgetDiv.hide();
+        if (thisField.sourceBlock_) {
+          // Call any validation function, and allow it to override.
+          colour = thisField.callValidator(colour);
+        }
+        if (colour !== null) {
+          thisField.setValue(colour);
+        }
+      });
+};
+
+/**
+ * Hide the colour palette.
+ * @private
+ */
+Blockly.FieldColour.widgetDispose_ = function() {
+  if (Blockly.FieldColour.changeEventKey_) {
+    goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_);
+  }
+};
+

+ 346 - 0
blockly/core/field_date.js

@@ -0,0 +1,346 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2015 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 Date input field.
+ * @author pkendall64@gmail.com (Paul Kendall)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldDate');
+
+goog.require('Blockly.Field');
+goog.require('goog.date');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.i18n.DateTimeSymbols');
+goog.require('goog.i18n.DateTimeSymbols_he');
+goog.require('goog.style');
+goog.require('goog.ui.DatePicker');
+
+
+/**
+ * Class for a date input field.
+ * @param {string} date The initial date.
+ * @param {Function=} opt_validator A function that is executed when a new
+ *     date is selected.  Its sole argument is the new date value.  Its
+ *     return value becomes the selected date, unless it is undefined, in
+ *     which case the new date stands, or it is null, in which case the change
+ *     is aborted.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldDate = function(date, opt_validator) {
+  if (!date) {
+    date = new goog.date.Date().toIsoString(true);
+  }
+  Blockly.FieldDate.superClass_.constructor.call(this, date, opt_validator);
+  this.setValue(date);
+};
+goog.inherits(Blockly.FieldDate, Blockly.Field);
+
+/**
+ * Mouse cursor style when over the hotspot that initiates the editor.
+ */
+Blockly.FieldDate.prototype.CURSOR = 'text';
+
+/**
+ * Close the colour picker if this input is being deleted.
+ */
+Blockly.FieldDate.prototype.dispose = function() {
+  Blockly.WidgetDiv.hideIfOwner(this);
+  Blockly.FieldDate.superClass_.dispose.call(this);
+};
+
+/**
+ * Return the current date.
+ * @return {string} Current date.
+ */
+Blockly.FieldDate.prototype.getValue = function() {
+  return this.date_;
+};
+
+/**
+ * Set the date.
+ * @param {string} date The new date.
+ */
+Blockly.FieldDate.prototype.setValue = function(date) {
+  if (this.sourceBlock_) {
+    var validated = this.callValidator(date);
+    // If the new date is invalid, validation returns null.
+    // In this case we still want to display the illegal result.
+    if (validated !== null) {
+      date = validated;
+    }
+  }
+  this.date_ = date;
+  Blockly.Field.prototype.setText.call(this, date);
+};
+
+/**
+ * Create a date picker under the date field.
+ * @private
+ */
+Blockly.FieldDate.prototype.showEditor_ = function() {
+  Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
+      Blockly.FieldDate.widgetDispose_);
+  // Create the date picker using Closure.
+  Blockly.FieldDate.loadLanguage_();
+  var picker = new goog.ui.DatePicker();
+  picker.setAllowNone(false);
+  picker.setShowWeekNum(false);
+
+  // Position the picker to line up with the field.
+  // Record windowSize and scrollOffset before adding the picker.
+  var windowSize = goog.dom.getViewportSize();
+  var scrollOffset = goog.style.getViewportPageOffset(document);
+  var xy = this.getAbsoluteXY_();
+  var borderBBox = this.getScaledBBox_();
+  var div = Blockly.WidgetDiv.DIV;
+  picker.render(div);
+  picker.setDate(goog.date.fromIsoString(this.getValue()));
+  // Record pickerSize after adding the date picker.
+  var pickerSize = goog.style.getSize(picker.getElement());
+
+  // Flip the picker vertically if off the bottom.
+  if (xy.y + pickerSize.height + borderBBox.height >=
+      windowSize.height + scrollOffset.y) {
+    xy.y -= pickerSize.height - 1;
+  } else {
+    xy.y += borderBBox.height - 1;
+  }
+  if (this.sourceBlock_.RTL) {
+    xy.x += borderBBox.width;
+    xy.x -= pickerSize.width;
+    // Don't go offscreen left.
+    if (xy.x < scrollOffset.x) {
+      xy.x = scrollOffset.x;
+    }
+  } else {
+    // Don't go offscreen right.
+    if (xy.x > windowSize.width + scrollOffset.x - pickerSize.width) {
+      xy.x = windowSize.width + scrollOffset.x - pickerSize.width;
+    }
+  }
+  Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
+                             this.sourceBlock_.RTL);
+
+  // Configure event handler.
+  var thisField = this;
+  Blockly.FieldDate.changeEventKey_ = goog.events.listen(picker,
+      goog.ui.DatePicker.Events.CHANGE,
+      function(event) {
+        var date = event.date ? event.date.toIsoString(true) : '';
+        Blockly.WidgetDiv.hide();
+        if (thisField.sourceBlock_) {
+          // Call any validation function, and allow it to override.
+          date = thisField.callValidator(date);
+        }
+        thisField.setValue(date);
+      });
+};
+
+/**
+ * Hide the date picker.
+ * @private
+ */
+Blockly.FieldDate.widgetDispose_ = function() {
+  if (Blockly.FieldDate.changeEventKey_) {
+    goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_);
+  }
+};
+
+/**
+ * Load the best language pack by scanning the Blockly.Msg object for a
+ * language that matches the available languages in Closure.
+ * @private
+ */
+Blockly.FieldDate.loadLanguage_ = function() {
+  var reg = /^DateTimeSymbols_(.+)$/;
+  for (var prop in goog.i18n) {
+    var m = prop.match(reg);
+    if (m) {
+      var lang = m[1].toLowerCase().replace('_', '.');  // E.g. 'pt.br'
+      if (goog.getObjectByName(lang, Blockly.Msg)) {
+        goog.i18n.DateTimeSymbols = goog.i18n[prop];
+      }
+    }
+  }
+};
+
+/**
+ * CSS for date picker.  See css.js for use.
+ */
+Blockly.FieldDate.CSS = [
+  /* Copied from: goog/css/datepicker.css */
+  /**
+   * Copyright 2009 The Closure Library Authors. All Rights Reserved.
+   *
+   * Use of this source code is governed by the Apache License, Version 2.0.
+   * See the COPYING file for details.
+   */
+
+  /**
+   * Standard styling for a goog.ui.DatePicker.
+   *
+   * @author arv@google.com (Erik Arvidsson)
+   */
+
+  '.blocklyWidgetDiv .goog-date-picker,',
+  '.blocklyWidgetDiv .goog-date-picker th,',
+  '.blocklyWidgetDiv .goog-date-picker td {',
+  '  font: 13px Arial, sans-serif;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker {',
+  '  -moz-user-focus: normal;',
+  '  -moz-user-select: none;',
+  '  position: relative;',
+  '  border: 1px solid #000;',
+  '  float: left;',
+  '  padding: 2px;',
+  '  color: #000;',
+  '  background: #c3d9ff;',
+  '  cursor: default;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker th {',
+  '  text-align: center;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker td {',
+  '  text-align: center;',
+  '  vertical-align: middle;',
+  '  padding: 1px 3px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-menu {',
+  '  position: absolute;',
+  '  background: threedface;',
+  '  border: 1px solid gray;',
+  '  -moz-user-focus: normal;',
+  '  z-index: 1;',
+  '  outline: none;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-menu ul {',
+  '  list-style: none;',
+  '  margin: 0px;',
+  '  padding: 0px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-menu ul li {',
+  '  cursor: default;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-menu-selected {',
+  '  background: #ccf;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker th {',
+  '  font-size: .9em;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker td div {',
+  '  float: left;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker button {',
+  '  padding: 0px;',
+  '  margin: 1px 0;',
+  '  border: 0;',
+  '  color: #20c;',
+  '  font-weight: bold;',
+  '  background: transparent;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-date {',
+  '  background: #fff;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-week,',
+  '.blocklyWidgetDiv .goog-date-picker-wday {',
+  '  padding: 1px 3px;',
+  '  border: 0;',
+  '  border-color: #a2bbdd;',
+  '  border-style: solid;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-week {',
+  '  border-right-width: 1px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-wday {',
+  '  border-bottom-width: 1px;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-head td {',
+  '  text-align: center;',
+  '}',
+
+  /** Use td.className instead of !important */
+  '.blocklyWidgetDiv td.goog-date-picker-today-cont {',
+  '  text-align: center;',
+  '}',
+
+  /** Use td.className instead of !important */
+  '.blocklyWidgetDiv td.goog-date-picker-none-cont {',
+  '  text-align: center;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-month {',
+  '  min-width: 11ex;',
+  '  white-space: nowrap;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-year {',
+  '  min-width: 6ex;',
+  '  white-space: nowrap;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-monthyear {',
+  '  white-space: nowrap;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker table {',
+  '  border-collapse: collapse;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-other-month {',
+  '  color: #888;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-wkend-start,',
+  '.blocklyWidgetDiv .goog-date-picker-wkend-end {',
+  '  background: #eee;',
+  '}',
+
+  /** Use td.className instead of !important */
+  '.blocklyWidgetDiv td.goog-date-picker-selected {',
+  '  background: #c3d9ff;',
+  '}',
+
+  '.blocklyWidgetDiv .goog-date-picker-today {',
+  '  background: #9ab;',
+  '  font-weight: bold !important;',
+  '  border-color: #246 #9bd #9bd #246;',
+  '  color: #fff;',
+  '}'
+];

+ 320 - 0
blockly/core/field_dropdown.js

@@ -0,0 +1,320 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Dropdown input field.  Used for editable titles and variables.
+ * In the interests of a consistent UI, the toolbox shares some functions and
+ * properties with the context menu.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldDropdown');
+
+goog.require('Blockly.Field');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.style');
+goog.require('goog.ui.Menu');
+goog.require('goog.ui.MenuItem');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for an editable dropdown field.
+ * @param {(!Array.<!Array.<string>>|!Function)} menuGenerator An array of
+ *     options for a dropdown list, or a function which generates these options.
+ * @param {Function=} opt_validator A function that is executed when a new
+ *     option is selected, with the newly selected value as its sole argument.
+ *     If it returns a value, that value (which must be one of the options) will
+ *     become selected in place of the newly selected option, unless the return
+ *     value is null, in which case the change is aborted.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldDropdown = function(menuGenerator, opt_validator) {
+  this.menuGenerator_ = menuGenerator;
+  this.trimOptions_();
+  var firstTuple = this.getOptions_()[0];
+
+  // Call parent's constructor.
+  Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1],
+      opt_validator);
+};
+goog.inherits(Blockly.FieldDropdown, Blockly.Field);
+
+/**
+ * Horizontal distance that a checkmark ovehangs the dropdown.
+ */
+Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25;
+
+/**
+ * Android can't (in 2014) display "▾", so use "▼" instead.
+ */
+Blockly.FieldDropdown.ARROW_CHAR = goog.userAgent.ANDROID ? '\u25BC' : '\u25BE';
+
+/**
+ * Mouse cursor style when over the hotspot that initiates the editor.
+ */
+Blockly.FieldDropdown.prototype.CURSOR = 'default';
+
+/**
+ * Install this dropdown on a block.
+ */
+Blockly.FieldDropdown.prototype.init = function() {
+  if (this.fieldGroup_) {
+    // Dropdown has already been initialized once.
+    return;
+  }
+  // Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL)
+  this.arrow_ = Blockly.createSvgElement('tspan', {}, null);
+  this.arrow_.appendChild(document.createTextNode(
+      this.sourceBlock_.RTL ? Blockly.FieldDropdown.ARROW_CHAR + ' ' :
+          ' ' + Blockly.FieldDropdown.ARROW_CHAR));
+
+  Blockly.FieldDropdown.superClass_.init.call(this);
+  // Force a reset of the text to add the arrow.
+  var text = this.text_;
+  this.text_ = null;
+  this.setText(text);
+};
+
+/**
+ * Create a dropdown menu under the text.
+ * @private
+ */
+Blockly.FieldDropdown.prototype.showEditor_ = function() {
+  Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null);
+  var thisField = this;
+
+  function callback(e) {
+    var menuItem = e.target;
+    if (menuItem) {
+      var value = menuItem.getValue();
+      if (thisField.sourceBlock_) {
+        // Call any validation function, and allow it to override.
+        value = thisField.callValidator(value);
+      }
+      if (value !== null) {
+        thisField.setValue(value);
+      }
+    }
+    Blockly.WidgetDiv.hideIfOwner(thisField);
+  }
+
+  var menu = new goog.ui.Menu();
+  menu.setRightToLeft(this.sourceBlock_.RTL);
+  var options = this.getOptions_();
+  for (var i = 0; i < options.length; i++) {
+    var text = options[i][0];  // Human-readable text.
+    var value = options[i][1]; // Language-neutral value.
+    var menuItem = new goog.ui.MenuItem(text);
+    menuItem.setRightToLeft(this.sourceBlock_.RTL);
+    menuItem.setValue(value);
+    menuItem.setCheckable(true);
+    menu.addChild(menuItem, true);
+    menuItem.setChecked(value == this.value_);
+  }
+  // Listen for mouse/keyboard events.
+  goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback);
+  // Listen for touch events (why doesn't Closure handle this already?).
+  function callbackTouchStart(e) {
+    var control = this.getOwnerControl(/** @type {Node} */ (e.target));
+    // Highlight the menu item.
+    control.handleMouseDown(e);
+  }
+  function callbackTouchEnd(e) {
+    var control = this.getOwnerControl(/** @type {Node} */ (e.target));
+    // Activate the menu item.
+    control.performActionInternal(e);
+  }
+  menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART,
+                           callbackTouchStart);
+  menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND,
+                           callbackTouchEnd);
+
+  // Record windowSize and scrollOffset before adding menu.
+  var windowSize = goog.dom.getViewportSize();
+  var scrollOffset = goog.style.getViewportPageOffset(document);
+  var xy = this.getAbsoluteXY_();
+  var borderBBox = this.getScaledBBox_();
+  var div = Blockly.WidgetDiv.DIV;
+  menu.render(div);
+  var menuDom = menu.getElement();
+  Blockly.addClass_(menuDom, 'blocklyDropdownMenu');
+  // Record menuSize after adding menu.
+  var menuSize = goog.style.getSize(menuDom);
+  // Recalculate height for the total content, not only box height.
+  menuSize.height = menuDom.scrollHeight;
+
+  // Position the menu.
+  // Flip menu vertically if off the bottom.
+  if (xy.y + menuSize.height + borderBBox.height >=
+      windowSize.height + scrollOffset.y) {
+    xy.y -= menuSize.height + 2;
+  } else {
+    xy.y += borderBBox.height;
+  }
+  if (this.sourceBlock_.RTL) {
+    xy.x += borderBBox.width;
+    xy.x += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
+    // Don't go offscreen left.
+    if (xy.x < scrollOffset.x + menuSize.width) {
+      xy.x = scrollOffset.x + menuSize.width;
+    }
+  } else {
+    xy.x -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
+    // Don't go offscreen right.
+    if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) {
+      xy.x = windowSize.width + scrollOffset.x - menuSize.width;
+    }
+  }
+  Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
+                             this.sourceBlock_.RTL);
+  menu.setAllowAutoFocus(true);
+  menuDom.focus();
+};
+
+/**
+ * Factor out common words in statically defined options.
+ * Create prefix and/or suffix labels.
+ * @private
+ */
+Blockly.FieldDropdown.prototype.trimOptions_ = function() {
+  this.prefixField = null;
+  this.suffixField = null;
+  var options = this.menuGenerator_;
+  if (!goog.isArray(options) || options.length < 2) {
+    return;
+  }
+  var strings = options.map(function(t) {return t[0];});
+  var shortest = Blockly.shortestStringLength(strings);
+  var prefixLength = Blockly.commonWordPrefix(strings, shortest);
+  var suffixLength = Blockly.commonWordSuffix(strings, shortest);
+  if (!prefixLength && !suffixLength) {
+    return;
+  }
+  if (shortest <= prefixLength + suffixLength) {
+    // One or more strings will entirely vanish if we proceed.  Abort.
+    return;
+  }
+  if (prefixLength) {
+    this.prefixField = strings[0].substring(0, prefixLength - 1);
+  }
+  if (suffixLength) {
+    this.suffixField = strings[0].substr(1 - suffixLength);
+  }
+  // Remove the prefix and suffix from the options.
+  var newOptions = [];
+  for (var i = 0; i < options.length; i++) {
+    var text = options[i][0];
+    var value = options[i][1];
+    text = text.substring(prefixLength, text.length - suffixLength);
+    newOptions[i] = [text, value];
+  }
+  this.menuGenerator_ = newOptions;
+};
+
+/**
+ * Return a list of the options for this dropdown.
+ * @return {!Array.<!Array.<string>>} Array of option tuples:
+ *     (human-readable text, language-neutral name).
+ * @private
+ */
+Blockly.FieldDropdown.prototype.getOptions_ = function() {
+  if (goog.isFunction(this.menuGenerator_)) {
+    return this.menuGenerator_.call(this);
+  }
+  return /** @type {!Array.<!Array.<string>>} */ (this.menuGenerator_);
+};
+
+/**
+ * Get the language-neutral value from this dropdown menu.
+ * @return {string} Current text.
+ */
+Blockly.FieldDropdown.prototype.getValue = function() {
+  return this.value_;
+};
+
+/**
+ * Set the language-neutral value for this dropdown menu.
+ * @param {string} newValue New value to set.
+ */
+Blockly.FieldDropdown.prototype.setValue = function(newValue) {
+  if (newValue === null || newValue === this.value_) {
+    return;  // No change if null.
+  }
+  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this.sourceBlock_, 'field', this.name, this.value_, newValue));
+  }
+  this.value_ = newValue;
+  // Look up and display the human-readable text.
+  var options = this.getOptions_();
+  for (var i = 0; i < options.length; i++) {
+    // Options are tuples of human-readable text and language-neutral values.
+    if (options[i][1] == newValue) {
+      this.setText(options[i][0]);
+      return;
+    }
+  }
+  // Value not found.  Add it, maybe it will become valid once set
+  // (like variable names).
+  this.setText(newValue);
+};
+
+/**
+ * Set the text in this field.  Trigger a rerender of the source block.
+ * @param {?string} text New text.
+ */
+Blockly.FieldDropdown.prototype.setText = function(text) {
+  if (this.sourceBlock_ && this.arrow_) {
+    // Update arrow's colour.
+    this.arrow_.style.fill = this.sourceBlock_.getColour();
+  }
+  if (text === null || text === this.text_) {
+    // No change if null.
+    return;
+  }
+  this.text_ = text;
+  this.updateTextNode_();
+
+  if (this.textElement_) {
+    // Insert dropdown arrow.
+    if (this.sourceBlock_.RTL) {
+      this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild);
+    } else {
+      this.textElement_.appendChild(this.arrow_);
+    }
+  }
+
+  if (this.sourceBlock_ && this.sourceBlock_.rendered) {
+    this.sourceBlock_.render();
+    this.sourceBlock_.bumpNeighbours_();
+  }
+};
+
+/**
+ * Close the dropdown menu if this input is being deleted.
+ */
+Blockly.FieldDropdown.prototype.dispose = function() {
+  Blockly.WidgetDiv.hideIfOwner(this);
+  Blockly.FieldDropdown.superClass_.dispose.call(this);
+};

+ 171 - 0
blockly/core/field_image.js

@@ -0,0 +1,171 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Image field.  Used for titles, labels, etc.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldImage');
+
+goog.require('Blockly.Field');
+goog.require('goog.dom');
+goog.require('goog.math.Size');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for an image.
+ * @param {string} src The URL of the image.
+ * @param {number} width Width of the image.
+ * @param {number} height Height of the image.
+ * @param {string=} opt_alt Optional alt text for when block is collapsed.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldImage = function(src, width, height, opt_alt) {
+  this.sourceBlock_ = null;
+  // Ensure height and width are numbers.  Strings are bad at math.
+  this.height_ = Number(height);
+  this.width_ = Number(width);
+  this.size_ = new goog.math.Size(this.width_,
+      this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y);
+  this.text_ = opt_alt || '';
+  this.setValue(src);
+};
+goog.inherits(Blockly.FieldImage, Blockly.Field);
+
+/**
+ * Rectangular mask used by Firefox.
+ * @type {Element}
+ * @private
+ */
+Blockly.FieldImage.prototype.rectElement_ = null;
+
+/**
+ * Editable fields are saved by the XML renderer, non-editable fields are not.
+ */
+Blockly.FieldImage.prototype.EDITABLE = false;
+
+/**
+ * Install this image on a block.
+ */
+Blockly.FieldImage.prototype.init = function() {
+  if (this.fieldGroup_) {
+    // Image has already been initialized once.
+    return;
+  }
+  // Build the DOM.
+  /** @type {SVGElement} */
+  this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
+  if (!this.visible_) {
+    this.fieldGroup_.style.display = 'none';
+  }
+  /** @type {SVGElement} */
+  this.imageElement_ = Blockly.createSvgElement('image',
+      {'height': this.height_ + 'px',
+       'width': this.width_ + 'px'}, this.fieldGroup_);
+  this.setValue(this.src_);
+  if (goog.userAgent.GECKO) {
+    /**
+     * Due to a Firefox bug which eats mouse events on image elements,
+     * a transparent rectangle needs to be placed on top of the image.
+     * @type {SVGElement}
+     */
+    this.rectElement_ = Blockly.createSvgElement('rect',
+        {'height': this.height_ + 'px',
+         'width': this.width_ + 'px',
+         'fill-opacity': 0}, this.fieldGroup_);
+  }
+  this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
+
+  // Configure the field to be transparent with respect to tooltips.
+  var topElement = this.rectElement_ || this.imageElement_;
+  topElement.tooltip = this.sourceBlock_;
+  Blockly.Tooltip.bindMouseEvents(topElement);
+};
+
+/**
+ * Dispose of all DOM objects belonging to this text.
+ */
+Blockly.FieldImage.prototype.dispose = function() {
+  goog.dom.removeNode(this.fieldGroup_);
+  this.fieldGroup_ = null;
+  this.imageElement_ = null;
+  this.rectElement_ = null;
+};
+
+/**
+ * Change the tooltip text for this field.
+ * @param {string|!Element} newTip Text for tooltip or a parent element to
+ *     link to for its tooltip.
+ */
+Blockly.FieldImage.prototype.setTooltip = function(newTip) {
+  var topElement = this.rectElement_ || this.imageElement_;
+  topElement.tooltip = newTip;
+};
+
+/**
+ * Get the source URL of this image.
+ * @return {string} Current text.
+ * @override
+ */
+Blockly.FieldImage.prototype.getValue = function() {
+  return this.src_;
+};
+
+/**
+ * Set the source URL of this image.
+ * @param {?string} src New source.
+ * @override
+ */
+Blockly.FieldImage.prototype.setValue = function(src) {
+  if (src === null) {
+    // No change if null.
+    return;
+  }
+  this.src_ = src;
+  if (this.imageElement_) {
+    this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
+        'xlink:href', goog.isString(src) ? src : '');
+  }
+};
+
+/**
+ * Set the alt text of this image.
+ * @param {?string} alt New alt text.
+ * @override
+ */
+Blockly.FieldImage.prototype.setText = function(alt) {
+  if (alt === null) {
+    // No change if null.
+    return;
+  }
+  this.text_ = alt;
+};
+
+/**
+ * Images are fixed width, no need to render.
+ * @private
+ */
+Blockly.FieldImage.prototype.render_ = function() {
+  // NOP
+};

+ 223 - 0
blockly/core/field_label.js

@@ -0,0 +1,223 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Non-editable text field.  Used for titles, labels, etc.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldLabel');
+
+goog.require('Blockly.Field');
+goog.require('Blockly.Tooltip');
+goog.require('goog.dom');
+goog.require('goog.math.Size');
+
+var Blockscad = Blockscad || {};
+
+
+/**
+ * Class for a non-editable field.
+ * @param {string} text The initial content of the field.
+ * @param {string=} opt_class Optional CSS class for the field's text.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldLabel = function(text, opt_class) {
+  this.size_ = new goog.math.Size(0, 17.5);
+  this.class_ = opt_class;
+  this.setValue(text);
+};
+goog.inherits(Blockly.FieldLabel, Blockly.Field);
+
+/**
+ * Editable fields are saved by the XML renderer, non-editable fields are not.
+ */
+Blockly.FieldLabel.prototype.EDITABLE = false;
+
+/**
+ * Install this text on a block.
+ */
+Blockly.FieldLabel.prototype.init = function() {
+  if (this.textElement_) {
+    // Text has already been initialized once.
+    return;
+  }
+  // Build the DOM.
+  this.textElement_ = Blockly.createSvgElement('text',
+      {'class': 'blocklyText', 'y': this.size_.height - 5}, null);
+  if (this.class_) {
+    Blockly.addClass_(this.textElement_, this.class_);
+  }
+  if (!this.visible_) {
+    this.textElement_.style.display = 'none';
+  }
+  this.sourceBlock_.getSvgRoot().appendChild(this.textElement_);
+
+  // Configure the field to be transparent with respect to tooltips.
+  this.textElement_.tooltip = this.sourceBlock_;
+  Blockly.Tooltip.bindMouseEvents(this.textElement_);
+  // Force a render.
+  this.updateTextNode_();
+};
+
+/**
+ * Dispose of all DOM objects belonging to this text.
+ */
+Blockly.FieldLabel.prototype.dispose = function() {
+  goog.dom.removeNode(this.textElement_);
+  this.textElement_ = null;
+};
+
+/**
+ * Gets the group element for this field.
+ * Used for measuring the size and for positioning.
+ * @return {!Element} The group element.
+ */
+Blockly.FieldLabel.prototype.getSvgRoot = function() {
+  return /** @type {!Element} */ (this.textElement_);
+};
+
+/**
+ * Change the tooltip text for this field.
+ * @param {string|!Element} newTip Text for tooltip or a parent element to
+ *     link to for its tooltip.
+ */
+Blockly.FieldLabel.prototype.setTooltip = function(newTip) {
+  this.textElement_.tooltip = newTip;
+};
+
+// lets try adding in a button, which will be a text element that when you click on it, 
+// the "editor" is to do the file choose menu.
+goog.provide('Blockly.FieldButton');
+
+goog.require('Blockly.Field');
+goog.require('Blockly.Msg');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for an editable text field.
+ * @param {string} text The initial content of the field.
+ * @param {Function=} opt_changeHandler An optional function that is called
+ *     to validate any constraints on what the user entered.  Takes the new
+ *     text as an argument and returns either the accepted text, a replacement
+ *     text, or null to abort the change.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldButton = function(text, opt_validator) {
+  Blockly.FieldTextInput.superClass_.constructor.call(this, text,
+      opt_validator);
+};
+goog.inherits(Blockly.FieldButton, Blockly.Field);
+/**
+ * Mouse cursor style when over the hotspot that initiates the editor.
+ */
+Blockly.FieldButton.prototype.CURSOR = 'default';
+/**
+ * Editable fields are saved by the XML renderer, non-editable fields are not.
+ */
+Blockly.FieldButton.prototype.EDITABLE = true;
+
+/**
+ * Close the input widget if this input is being deleted.
+ */
+Blockly.FieldButton.prototype.dispose = function() {
+  Blockly.WidgetDiv.hideIfOwner(this);
+  Blockly.FieldButton.superClass_.dispose.call(this);
+};
+
+
+/**
+ * Set the text in this field.
+ * @param {?string} text New text.
+ * @override
+ */
+Blockly.FieldButton.prototype.setValue = function(text) {
+  if (text === null) {
+    return;  // No change if null.
+  }
+  if (this.sourceBlock_) {
+    var validated = this.callValidator(text);
+    // If the new text is invalid, validation returns null.
+    // In this case we still want to display the illegal result.
+    if (validated !== null) {
+      text = validated;
+    }
+  }
+  Blockly.Field.prototype.setValue.call(this, text);
+};
+
+
+/**
+ * Show the inline free-text editor on top of the text.
+ * @param {boolean=} opt_quietInput True if editor should be created without
+ *     focus.  Defaults to false.
+ * @private
+ */
+Blockly.FieldButton.prototype.showEditor_ = function(opt_quietInput) {
+  // console.log("editor activated");
+  Blockscad.currentInterestingBlock = this.sourceBlock_;
+  $('#importStl').click();
+};
+
+/**
+ * Close the editor, save the results, and dispose of the editable
+ * text field's elements.
+ * @return {!Function} Closure to call on destruction of the WidgetDiv.
+ * @private
+ */
+Blockly.FieldButton.prototype.widgetDispose_ = function() {
+  var thisField = this;
+  return function() {
+    var htmlInput = Blockly.FieldButton.htmlInput_;
+    // Save the edit (if it validates).
+    var text = htmlInput.value;
+    if (thisField.sourceBlock_) {
+      var text1 = text;
+      if (text1 === null) {
+        // Invalid edit.
+        text = htmlInput.defaultValue;
+      } else if (text1 !== undefined) {
+        // Change handler has changed the text.
+        text = text1;
+      }
+    }
+    thisField.setValue(text);
+    thisField.sourceBlock_.rendered && thisField.sourceBlock_.render();
+    Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
+    Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
+    Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
+    thisField.workspace_.removeChangeListener(
+        htmlInput.onWorkspaceChangeWrapper_);
+    Blockly.FieldTextInput.htmlInput_ = null;
+    // Delete style properties.
+    var style = Blockly.WidgetDiv.DIV.style;
+    style.width = 'auto';
+    style.height = 'auto';
+    style.fontSize = '';
+  };
+};
+
+

+ 101 - 0
blockly/core/field_number.js

@@ -0,0 +1,101 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2016 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 Number input field
+ * @author fenichel@google.com (Rachel Fenichel)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldNumber');
+
+goog.require('Blockly.FieldTextInput');
+goog.require('goog.math');
+
+/**
+ * Class for an editable number field.
+ * @param {number|string} value The initial content of the field.
+ * @param {number|string|undefined} opt_min Minimum value.
+ * @param {number|string|undefined} opt_max Maximum value.
+ * @param {number|string|undefined} opt_precision Precision for value.
+ * @param {Function=} opt_validator An optional function that is called
+ *     to validate any constraints on what the user entered.  Takes the new
+ *     text as an argument and returns either the accepted text, a replacement
+ *     text, or null to abort the change.
+ * @extends {Blockly.FieldTextInput}
+ * @constructor
+ */
+Blockly.FieldNumber =
+    function(value, opt_min, opt_max, opt_precision, opt_validator) {
+  value = String(value);
+  Blockly.FieldNumber.superClass_.constructor.call(this, value, opt_validator);
+  this.setConstraints(opt_min, opt_max, opt_precision);
+};
+goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput);
+
+/**
+ * Set the maximum, minimum and precision constraints on this field.
+ * Any of these properties may be undefiend or NaN to be disabled.
+ * Setting precision (usually a power of 10) enforces a minimum step between
+ * values. That is, the user's value will rounded to the closest multiple of
+ * precision. The least significant digit place is inferred from the precision.
+ * Integers values can be enforces by choosing an integer precision.
+ * @param {number|string|undefined} min Minimum value.
+ * @param {number|string|undefined} max Maximum value.
+ * @param {number|string|undefined} precision Precision for value.
+ */
+Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) {
+  precision = parseFloat(precision);
+  this.precision_ = isNaN(precision) ? 0 : precision;
+  min = parseFloat(min);
+  this.min_ = isNaN(min) ? -Infinity : min;
+  max = parseFloat(max);
+  this.max_ = isNaN(max) ? Infinity : max;
+  this.setValue(this.callValidator(this.getValue()));
+};
+
+/**
+ * Ensure that only a number in the correct range may be entered.
+ * @param {string} text The user's text.
+ * @return {?string} A string representing a valid number, or null if invalid.
+ */
+Blockly.FieldNumber.prototype.classValidator = function(text) {
+  if (text === null) {
+    return null;
+  }
+  text = String(text);
+  // TODO: Handle cases like 'ten', '1.203,14', etc.
+  // 'O' is sometimes mistaken for '0' by inexperienced users.
+  text = text.replace(/O/ig, '0');
+  // Strip out thousands separators.
+  text = text.replace(/,/g, '');
+  var n = parseFloat(text || 0);
+  if (isNaN(n)) {
+    // Invalid number.
+    return null;
+  }
+  // Round to nearest multiple of precision.
+  if (this.precision_ && Number.isFinite(n)) {
+    n = Math.round(n / this.precision_) * this.precision_;
+  }
+  // Get the value in range.
+  n = goog.math.clamp(n, this.min_, this.max_);
+  return String(n);
+};

+ 322 - 0
blockly/core/field_textinput.js

@@ -0,0 +1,322 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Text input field.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldTextInput');
+
+goog.require('Blockly.Field');
+goog.require('Blockly.Msg');
+goog.require('goog.asserts');
+goog.require('goog.dom');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for an editable text field.
+ * @param {string} text The initial content of the field.
+ * @param {Function=} opt_validator An optional function that is called
+ *     to validate any constraints on what the user entered.  Takes the new
+ *     text as an argument and returns either the accepted text, a replacement
+ *     text, or null to abort the change.
+ * @extends {Blockly.Field}
+ * @constructor
+ */
+Blockly.FieldTextInput = function(text, opt_validator) {
+  Blockly.FieldTextInput.superClass_.constructor.call(this, text,
+      opt_validator);
+};
+goog.inherits(Blockly.FieldTextInput, Blockly.Field);
+
+/**
+ * Point size of text.  Should match blocklyText's font-size in CSS.
+ */
+Blockly.FieldTextInput.FONTSIZE = 11;
+
+/**
+ * Mouse cursor style when over the hotspot that initiates the editor.
+ */
+Blockly.FieldTextInput.prototype.CURSOR = 'text';
+
+/**
+ * Allow browser to spellcheck this field.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.spellcheck_ = true;
+
+/**
+ * Close the input widget if this input is being deleted.
+ */
+Blockly.FieldTextInput.prototype.dispose = function() {
+  Blockly.WidgetDiv.hideIfOwner(this);
+  Blockly.FieldTextInput.superClass_.dispose.call(this);
+};
+
+/**
+ * Set the text in this field.
+ * @param {?string} text New text.
+ * @override
+ */
+Blockly.FieldTextInput.prototype.setValue = function(text) {
+  if (text === null) {
+    return;  // No change if null.
+  }
+  if (this.sourceBlock_) {
+    var validated = this.callValidator(text);
+    // If the new text is invalid, validation returns null.
+    // In this case we still want to display the illegal result.
+    if (validated !== null) {
+      text = validated;
+    }
+  }
+  Blockly.Field.prototype.setValue.call(this, text);
+};
+
+/**
+ * Set whether this field is spellchecked by the browser.
+ * @param {boolean} check True if checked.
+ */
+Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
+  this.spellcheck_ = check;
+};
+
+/**
+ * Show the inline free-text editor on top of the text.
+ * @param {boolean=} opt_quietInput True if editor should be created without
+ *     focus.  Defaults to false.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
+  this.workspace_ = this.sourceBlock_.workspace;
+  var quietInput = opt_quietInput || false;
+  if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID ||
+                      goog.userAgent.IPAD)) {
+    // Mobile browsers have issues with in-line textareas (focus & keyboards).
+    var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_);
+    if (this.sourceBlock_) {
+      newValue = this.callValidator(newValue);
+    }
+    this.setValue(newValue);
+    return;
+  }
+
+  Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
+  var div = Blockly.WidgetDiv.DIV;
+  // Create the input.
+  var htmlInput = goog.dom.createDom('input', 'blocklyHtmlInput');
+  htmlInput.setAttribute('spellcheck', this.spellcheck_);
+  var fontSize =
+      (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
+  div.style.fontSize = fontSize;
+  htmlInput.style.fontSize = fontSize;
+  /** @type {!HTMLInputElement} */
+  Blockly.FieldTextInput.htmlInput_ = htmlInput;
+  div.appendChild(htmlInput);
+
+  htmlInput.value = htmlInput.defaultValue = this.text_;
+  htmlInput.oldValue_ = null;
+  this.validate_();
+  this.resizeEditor_();
+  if (!quietInput) {
+    htmlInput.focus();
+    htmlInput.select();
+  }
+
+  // Bind to keydown -- trap Enter without IME and Esc to hide.
+  htmlInput.onKeyDownWrapper_ =
+      Blockly.bindEvent_(htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
+  // Bind to keyup -- trap Enter; resize after every keystroke.
+  htmlInput.onKeyUpWrapper_ =
+      Blockly.bindEvent_(htmlInput, 'keyup', this, this.onHtmlInputChange_);
+  // Bind to keyPress -- repeatedly resize when holding down a key.
+  htmlInput.onKeyPressWrapper_ =
+      Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_);
+  htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
+  this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_);
+};
+
+/**
+ * Handle key down to the editor.
+ * @param {!Event} e Keyboard event.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
+  var htmlInput = Blockly.FieldTextInput.htmlInput_;
+  var tabKey = 9, enterKey = 13, escKey = 27;
+  if (e.keyCode == enterKey) {
+    Blockly.WidgetDiv.hide();
+  } else if (e.keyCode == escKey) {
+    htmlInput.value = htmlInput.defaultValue;
+    Blockly.WidgetDiv.hide();
+  } else if (e.keyCode == tabKey) {
+    Blockly.WidgetDiv.hide();
+    this.sourceBlock_.tab(this, !e.shiftKey);
+    e.preventDefault();
+  }
+};
+
+/**
+ * Handle a change to the editor.
+ * @param {!Event} e Keyboard event.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) {
+  var htmlInput = Blockly.FieldTextInput.htmlInput_;
+  // Update source block.
+  var text = htmlInput.value;
+  if (text !== htmlInput.oldValue_) {
+    htmlInput.oldValue_ = text;
+    this.setValue(text);
+    this.validate_();
+  } else if (goog.userAgent.WEBKIT) {
+    // Cursor key.  Render the source block to show the caret moving.
+    // Chrome only (version 26, OS X).
+    this.sourceBlock_.render();
+  }
+  this.resizeEditor_();
+  Blockly.svgResize(this.sourceBlock_.workspace);
+};
+
+/**
+ * Check to see if the contents of the editor validates.
+ * Style the editor accordingly.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.validate_ = function() {
+  var valid = true;
+  goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_);
+  var htmlInput = Blockly.FieldTextInput.htmlInput_;
+  if (this.sourceBlock_) {
+    valid = this.callValidator(htmlInput.value);
+  }
+  if (valid === null) {
+    Blockly.addClass_(htmlInput, 'blocklyInvalidInput');
+  } else {
+    Blockly.removeClass_(htmlInput, 'blocklyInvalidInput');
+  }
+};
+
+/**
+ * Resize the editor and the underlying block to fit the text.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
+  var div = Blockly.WidgetDiv.DIV;
+  var bBox = this.fieldGroup_.getBBox();
+  div.style.width = bBox.width * this.workspace_.scale + 'px';
+  div.style.height = bBox.height * this.workspace_.scale + 'px';
+  var xy = this.getAbsoluteXY_();
+  // In RTL mode block fields and LTR input fields the left edge moves,
+  // whereas the right edge is fixed.  Reposition the editor.
+  if (this.sourceBlock_.RTL) {
+    var borderBBox = this.getScaledBBox_();
+    xy.x += borderBBox.width;
+    xy.x -= div.offsetWidth;
+  }
+  // Shift by a few pixels to line up exactly.
+  xy.y += 1;
+  if (goog.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
+    // Firefox mis-reports the location of the border by a pixel
+    // once the WidgetDiv is moved into position.
+    xy.x -= 1;
+    xy.y -= 1;
+  }
+  if (goog.userAgent.WEBKIT) {
+    xy.y -= 3;
+  }
+  div.style.left = xy.x + 'px';
+  div.style.top = xy.y + 'px';
+};
+
+/**
+ * Close the editor, save the results, and dispose of the editable
+ * text field's elements.
+ * @return {!Function} Closure to call on destruction of the WidgetDiv.
+ * @private
+ */
+Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
+  var thisField = this;
+  return function() {
+    var htmlInput = Blockly.FieldTextInput.htmlInput_;
+    // Save the edit (if it validates).
+    var text = htmlInput.value;
+    if (thisField.sourceBlock_) {
+      var text1 = thisField.callValidator(text);
+      if (text1 === null) {
+        // Invalid edit.
+        text = htmlInput.defaultValue;
+      } else {
+        // Validation function has changed the text.
+        text = text1;
+      }
+    }
+    thisField.setValue(text);
+    thisField.sourceBlock_.rendered && thisField.sourceBlock_.render();
+    Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
+    Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
+    Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
+    thisField.workspace_.removeChangeListener(
+        htmlInput.onWorkspaceChangeWrapper_);
+    Blockly.FieldTextInput.htmlInput_ = null;
+    // Delete style properties.
+    var style = Blockly.WidgetDiv.DIV.style;
+    style.width = 'auto';
+    style.height = 'auto';
+    style.fontSize = '';
+  };
+};
+
+/**
+ * Ensure that only a number may be entered.
+ * @param {string} text The user's text.
+ * @return {?string} A string representing a valid number, or null if invalid.
+ */
+Blockly.FieldTextInput.numberValidator = function(text) {
+  console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
+               'Use Blockly.FieldNumber instead.');
+  if (text === null) {
+    return null;
+  }
+  text = String(text);
+  // TODO: Handle cases like 'ten', '1.203,14', etc.
+  // 'O' is sometimes mistaken for '0' by inexperienced users.
+  text = text.replace(/O/ig, '0');
+  // Strip out thousands separators.
+  text = text.replace(/,/g, '');
+  var n = parseFloat(text || 0);
+  return isNaN(n) ? null : String(n);
+};
+
+/**
+ * Ensure that only a nonnegative integer may be entered.
+ * @param {string} text The user's text.
+ * @return {?string} A string representing a valid int, or null if invalid.
+ */
+Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
+  var n = Blockly.FieldTextInput.numberValidator(text);
+  if (n) {
+    n = String(Math.max(0, Math.floor(n)));
+  }
+  return n;
+};

+ 167 - 0
blockly/core/field_variable.js

@@ -0,0 +1,167 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Variable input field.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.FieldVariable');
+
+goog.require('Blockly.FieldDropdown');
+goog.require('Blockly.Msg');
+goog.require('Blockly.Variables');
+goog.require('goog.string');
+
+
+/**
+ * Class for a variable's dropdown field.
+ * @param {?string} varname The default name for the variable.  If null,
+ *     a unique variable name will be generated.
+ * @param {Function=} opt_validator A function that is executed when a new
+ *     option is selected.  Its sole argument is the new option value.
+ * @extends {Blockly.FieldDropdown}
+ * @constructor
+ */
+Blockly.FieldVariable = function(varname, opt_validator) {
+  Blockly.FieldVariable.superClass_.constructor.call(this,
+      Blockly.FieldVariable.dropdownCreate, opt_validator);
+  this.setValue(varname || '');
+};
+goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);
+
+/**
+ * Install this dropdown on a block.
+ */
+Blockly.FieldVariable.prototype.init = function() {
+  if (this.fieldGroup_) {
+    // Dropdown has already been initialized once.
+    return;
+  }
+  Blockly.FieldVariable.superClass_.init.call(this);
+  if (!this.getValue()) {
+    // Variables without names get uniquely named for this workspace.
+    var workspace =
+        this.sourceBlock_.isInFlyout ?
+            this.sourceBlock_.workspace.targetWorkspace :
+            this.sourceBlock_.workspace;
+    this.setValue(Blockly.Variables.generateUniqueName(workspace));
+  }
+};
+
+/**
+ * Get the variable's name (use a variableDB to convert into a real name).
+ * Unline a regular dropdown, variables are literal and have no neutral value.
+ * @return {string} Current text.
+ */
+Blockly.FieldVariable.prototype.getValue = function() {
+  return this.getText();
+};
+
+/**
+ * Set the variable name.
+ * @param {string} newValue New text.
+ */
+Blockly.FieldVariable.prototype.setValue = function(newValue) {
+  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
+    Blockly.Events.fire(new Blockly.Events.Change(
+        this.sourceBlock_, 'field', this.name, this.value_, newValue));
+  }
+  this.value_ = newValue;
+  this.setText(newValue);
+};
+
+/**
+ * Return a sorted list of variable names for variable dropdown menus.
+ * Include a special option at the end for creating a new variable name.
+ * @return {!Array.<string>} Array of variable names.
+ * @this {!Blockly.FieldVariable}
+ */
+Blockly.FieldVariable.dropdownCreate = function() {
+  if (this.sourceBlock_ && this.sourceBlock_.workspace) {
+    var variableList =
+        Blockly.Variables.allVariables(this.sourceBlock_.workspace);
+  } else {
+    var variableList = [];
+  }
+  // Ensure that the currently selected variable is an option.
+  var name = this.getText();
+  if (name && variableList.indexOf(name) == -1) {
+    variableList.push(name);
+  }
+  variableList.sort(goog.string.caseInsensitiveCompare);
+  variableList.push(Blockly.Msg.RENAME_VARIABLE);
+  variableList.push(Blockly.Msg.NEW_VARIABLE);
+  // Variables are not language-specific, use the name as both the user-facing
+  // text and the internal representation.
+  var options = [];
+  for (var i = 0; i < variableList.length; i++) {
+    options[i] = [variableList[i], variableList[i]];
+  }
+  return options;
+};
+
+/**
+ * Event handler for a change in variable name.
+ * Special case the 'New variable...' and 'Rename variable...' options.
+ * In both of these special cases, prompt the user for a new name.
+ * @param {string} text The selected dropdown menu option.
+ * @return {null|undefined|string} An acceptable new variable name, or null if
+ *     change is to be either aborted (cancel button) or has been already
+ *     handled (rename), or undefined if an existing variable was chosen.
+ */
+Blockly.FieldVariable.prototype.classValidator = function(text) {
+  function promptName(promptText, defaultText) {
+    Blockly.hideChaff();
+    var newVar = window.prompt(promptText, defaultText);
+    // Merge runs of whitespace.  Strip leading and trailing whitespace.
+    // Beyond this, all names are legal.
+    if (newVar) {
+      newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
+      if (newVar == Blockly.Msg.RENAME_VARIABLE ||
+          newVar == Blockly.Msg.NEW_VARIABLE) {
+        // Ok, not ALL names are legal...
+        newVar = null;
+      }
+    }
+    return newVar;
+  }
+  var workspace = this.sourceBlock_.workspace;
+  if (text == Blockly.Msg.RENAME_VARIABLE) {
+    var oldVar = this.getText();
+    text = promptName(Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldVar),
+                      oldVar);
+    if (text) {
+      Blockly.Variables.renameVariable(oldVar, text, workspace);
+    }
+    return null;
+  } else if (text == Blockly.Msg.NEW_VARIABLE) {
+    text = promptName(Blockly.Msg.NEW_VARIABLE_TITLE, '');
+    // Since variables are case-insensitive, ensure that if the new variable
+    // matches with an existing variable, the new case prevails throughout.
+    if (text) {
+      Blockly.Variables.renameVariable(text, text, workspace);
+      return text;
+    }
+    return null;
+  }
+  return undefined;
+};

+ 1248 - 0
blockly/core/flyout.js

@@ -0,0 +1,1248 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Flyout tray containing blocks which may be created.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Flyout');
+
+goog.require('Blockly.Block');
+goog.require('Blockly.Comment');
+goog.require('Blockly.Events');
+goog.require('Blockly.WorkspaceSvg');
+goog.require('goog.dom');
+goog.require('goog.events');
+goog.require('goog.math.Rect');
+goog.require('goog.userAgent');
+
+
+/**
+ * Class for a flyout.
+ * @param {!Object} workspaceOptions Dictionary of options for the workspace.
+ * @constructor
+ */
+Blockly.Flyout = function(workspaceOptions) {
+  workspaceOptions.getMetrics = this.getMetrics_.bind(this);
+  workspaceOptions.setMetrics = this.setMetrics_.bind(this);
+  /**
+   * @type {!Blockly.Workspace}
+   * @private
+   */
+  this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
+  this.workspace_.isFlyout = true;
+
+  /**
+   * Is RTL vs LTR.
+   * @type {boolean}
+   */
+  this.RTL = !!workspaceOptions.RTL;
+
+  /**
+   * Flyout should be laid out horizontally vs vertically.
+   * @type {boolean}
+   * @private
+   */
+  this.horizontalLayout_ = workspaceOptions.horizontalLayout;
+
+  /**
+   * Position of the toolbox and flyout relative to the workspace.
+   * @type {number}
+   * @private
+   */
+  this.toolboxPosition_ = workspaceOptions.toolboxPosition;
+
+  /**
+   * Opaque data that can be passed to Blockly.unbindEvent_.
+   * @type {!Array.<!Array>}
+   * @private
+   */
+  this.eventWrappers_ = [];
+
+  /**
+   * List of background buttons that lurk behind each block to catch clicks
+   * landing in the blocks' lakes and bays.
+   * @type {!Array.<!Element>}
+   * @private
+   */
+  this.buttons_ = [];
+
+  /**
+   * List of event listeners.
+   * @type {!Array.<!Array>}
+   * @private
+   */
+  this.listeners_ = [];
+
+  /**
+   * List of blocks that should always be disabled.
+   * @type {!Array.<!Blockly.Block>}
+   * @private
+   */
+  this.permanentlyDisabled_ = [];
+};
+
+/**
+ * Does the flyout automatically close when a block is created?
+ * @type {boolean}
+ */
+Blockly.Flyout.prototype.autoClose = true;
+
+/**
+ * Corner radius of the flyout background.
+ * @type {number}
+ * @const
+ */
+Blockly.Flyout.prototype.CORNER_RADIUS = 8;
+
+/**
+ * Number of pixels the mouse must move before a drag/scroll starts. Because the
+ * drag-intention is determined when this is reached, it is larger than
+ * Blockly.DRAG_RADIUS so that the drag-direction is clearer.
+ */
+Blockly.Flyout.prototype.DRAG_RADIUS = 10;
+
+/**
+ * Margin around the edges of the blocks in the flyout.
+ * @type {number}
+ * @const
+ */
+Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS;
+
+/**
+ * Top/bottom padding between scrollbar and edge of flyout background.
+ * @type {number}
+ * @const
+ */
+Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2;
+
+/**
+ * Width of flyout.
+ * @type {number}
+ * @private
+ */
+Blockly.Flyout.prototype.width_ = 0;
+
+/**
+ * Height of flyout.
+ * @type {number}
+ * @private
+ */
+Blockly.Flyout.prototype.height_ = 0;
+
+/**
+ * Is the flyout dragging (scrolling)?
+ * DRAG_NONE - no drag is ongoing or state is undetermined.
+ * DRAG_STICKY - still within the sticky drag radius.
+ * DRAG_FREE - in scroll mode (never create a new block).
+ * @private
+ */
+Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE;
+
+/**
+ * Range of a drag angle from a flyout considered "dragging toward workspace".
+ * Drags that are within the bounds of this many degrees from the orthogonal
+ * line to the flyout edge are considered to be "drags toward the workspace".
+ * Example:
+ * Flyout                                                  Edge   Workspace
+ * [block] /  <-within this angle, drags "toward workspace" |
+ * [block] ---- orthogonal to flyout boundary ----          |
+ * [block] \                                                |
+ * The angle is given in degrees from the orthogonal.
+ *
+ * This is used to know when to create a new block and when to scroll the
+ * flyout. Setting it to 360 means that all drags create a new block.
+ * @type {number}
+ * @private
+*/
+Blockly.Flyout.prototype.dragAngleRange_ = 80;
+
+/**
+ * Creates the flyout's DOM.  Only needs to be called once.
+ * @return {!Element} The flyout's SVG group.
+ */
+Blockly.Flyout.prototype.createDom = function() {
+  /*
+  <g>
+    <path class="blocklyFlyoutBackground"/>
+    <g class="blocklyFlyout"></g>
+  </g>
+  */
+  this.svgGroup_ = Blockly.createSvgElement('g',
+      {'class': 'blocklyFlyout'}, null);
+  this.svgBackground_ = Blockly.createSvgElement('path',
+      {'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
+  this.svgGroup_.appendChild(this.workspace_.createDom());
+  return this.svgGroup_;
+};
+
+/**
+ * Initializes the flyout.
+ * @param {!Blockly.Workspace} targetWorkspace The workspace in which to create
+ *     new blocks.
+ */
+Blockly.Flyout.prototype.init = function(targetWorkspace) {
+  this.targetWorkspace_ = targetWorkspace;
+  this.workspace_.targetWorkspace = targetWorkspace;
+  // Add scrollbar.
+  this.scrollbar_ = new Blockly.Scrollbar(this.workspace_,
+      this.horizontalLayout_, false);
+
+  this.hide();
+
+  Array.prototype.push.apply(this.eventWrappers_,
+      Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.wheel_));
+  if (!this.autoClose) {
+    this.filterWrapper_ = this.filterForCapacity_.bind(this);
+    this.targetWorkspace_.addChangeListener(this.filterWrapper_);
+  }
+  // Dragging the flyout up and down.
+  Array.prototype.push.apply(this.eventWrappers_,
+      Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_));
+};
+
+/**
+ * Dispose of this flyout.
+ * Unlink from all DOM elements to prevent memory leaks.
+ */
+Blockly.Flyout.prototype.dispose = function() {
+  this.hide();
+  Blockly.unbindEvent_(this.eventWrappers_);
+  if (this.filterWrapper_) {
+    this.targetWorkspace_.removeChangeListener(this.filterWrapper_);
+    this.filterWrapper_ = null;
+  }
+  if (this.scrollbar_) {
+    this.scrollbar_.dispose();
+    this.scrollbar_ = null;
+  }
+  if (this.workspace_) {
+    this.workspace_.targetWorkspace = null;
+    this.workspace_.dispose();
+    this.workspace_ = null;
+  }
+  if (this.svgGroup_) {
+    goog.dom.removeNode(this.svgGroup_);
+    this.svgGroup_ = null;
+  }
+  this.svgBackground_ = null;
+  this.targetWorkspace_ = null;
+};
+
+/**
+ * Get the width of the flyout.
+ * @return {number} The width of the flyout.
+ */
+Blockly.Flyout.prototype.getWidth = function() {
+  return this.width_;
+};
+
+/**
+ * Get the height of the flyout.
+ * @return {number} The width of the flyout.
+ */
+Blockly.Flyout.prototype.getHeight = function() {
+  return this.height_;
+};
+
+/**
+ * Return an object with all the metrics required to size scrollbars for the
+ * flyout.  The following properties are computed:
+ * .viewHeight: Height of the visible rectangle,
+ * .viewWidth: Width of the visible rectangle,
+ * .contentHeight: Height of the contents,
+ * .contentWidth: Width of the contents,
+ * .viewTop: Offset of top edge of visible rectangle from parent,
+ * .contentTop: Offset of the top-most content from the y=0 coordinate,
+ * .absoluteTop: Top-edge of view.
+ * .viewLeft: Offset of the left edge of visible rectangle from parent,
+ * .contentLeft: Offset of the left-most content from the x=0 coordinate,
+ * .absoluteLeft: Left-edge of view.
+ * @return {Object} Contains size and position metrics of the flyout.
+ * @private
+ */
+Blockly.Flyout.prototype.getMetrics_ = function() {
+  if (!this.isVisible()) {
+    // Flyout is hidden.
+    return null;
+  }
+
+  try {
+    var optionBox = this.workspace_.getCanvas().getBBox();
+  } catch (e) {
+    // Firefox has trouble with hidden elements (Bug 528969).
+    var optionBox = {height: 0, y: 0, width: 0, x: 0};
+  }
+
+  var absoluteTop = this.SCROLLBAR_PADDING;
+  var absoluteLeft = this.SCROLLBAR_PADDING;
+  if (this.horizontalLayout_) {
+    if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
+      absoluteTop = 0;
+    }
+    var viewHeight = this.height_;
+    if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
+      viewHeight += this.MARGIN - this.SCROLLBAR_PADDING;
+    }
+    var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING;
+  } else {
+    absoluteLeft = 0;
+    var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING;
+    var viewWidth = this.width_;
+    if (!this.RTL) {
+      viewWidth -= this.SCROLLBAR_PADDING;
+    }
+  }
+
+  var metrics = {
+    viewHeight: viewHeight,
+    viewWidth: viewWidth,
+    contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale,
+    contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale,
+    viewTop: -this.workspace_.scrollY,
+    viewLeft: -this.workspace_.scrollX,
+    contentTop: optionBox.y,
+    contentLeft: optionBox.x,
+    absoluteTop: absoluteTop,
+    absoluteLeft: absoluteLeft
+  };
+  return metrics;
+};
+
+/**
+ * Sets the translation of the flyout to match the scrollbars.
+ * @param {!Object} xyRatio Contains a y property which is a float
+ *     between 0 and 1 specifying the degree of scrolling and a
+ *     similar x property.
+ * @private
+ */
+Blockly.Flyout.prototype.setMetrics_ = function(xyRatio) {
+  var metrics = this.getMetrics_();
+  // This is a fix to an apparent race condition.
+  if (!metrics) {
+    return;
+  }
+  if (!this.horizontalLayout_ && goog.isNumber(xyRatio.y)) {
+    this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y;
+  } else if (this.horizontalLayout_ && goog.isNumber(xyRatio.x)) {
+    this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x;
+  }
+
+  this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft,
+      this.workspace_.scrollY + metrics.absoluteTop);
+};
+
+/**
+ * Move the flyout to the edge of the workspace.
+ */
+Blockly.Flyout.prototype.position = function() {
+  if (!this.isVisible()) {
+    return;
+  }
+  var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
+  if (!targetWorkspaceMetrics) {
+    // Hidden components will return null.
+    return;
+  }
+  var edgeWidth = this.horizontalLayout_ ?
+      targetWorkspaceMetrics.viewWidth : this.width_;
+  edgeWidth -= this.CORNER_RADIUS;
+  if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
+    edgeWidth *= -1;
+  }
+
+  this.setBackgroundPath_(edgeWidth,
+      this.horizontalLayout_ ? this.height_ :
+      targetWorkspaceMetrics.viewHeight);
+
+  var x = targetWorkspaceMetrics.absoluteLeft;
+  if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
+    x += targetWorkspaceMetrics.viewWidth;
+    x -= this.width_;
+  }
+
+  var y = targetWorkspaceMetrics.absoluteTop;
+  if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
+    y += targetWorkspaceMetrics.viewHeight;
+    y -= this.height_;
+  }
+
+  this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')');
+
+  // Record the height for Blockly.Flyout.getMetrics_, or width if the layout is
+  // horizontal.
+  if (this.horizontalLayout_) {
+    this.width_ = targetWorkspaceMetrics.viewWidth;
+  } else {
+    this.height_ = targetWorkspaceMetrics.viewHeight;
+  }
+
+  // Update the scrollbar (if one exists).
+  if (this.scrollbar_) {
+    this.scrollbar_.resize();
+  }
+};
+
+/**
+ * Create and set the path for the visible boundaries of the flyout.
+ * @param {number} width The width of the flyout, not including the
+ *     rounded corners.
+ * @param {number} height The height of the flyout, not including
+ *     rounded corners.
+ * @private
+ */
+Blockly.Flyout.prototype.setBackgroundPath_ = function(width, height) {
+  if (this.horizontalLayout_) {
+    this.setBackgroundPathHorizontal_(width, height);
+  } else {
+    this.setBackgroundPathVertical_(width, height);
+  }
+};
+
+/**
+ * Create and set the path for the visible boundaries of the flyout in vertical
+ * mode.
+ * @param {number} width The width of the flyout, not including the
+ *     rounded corners.
+ * @param {number} height The height of the flyout, not including
+ *     rounded corners.
+ * @private
+ */
+Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) {
+  var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT;
+  // Decide whether to start on the left or right.
+  var path = ['M ' + (atRight ? this.width_ : 0) + ',0'];
+  // Top.
+  path.push('h', width);
+  // Rounded corner.
+  path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
+      atRight ? 0 : 1,
+      atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS,
+      this.CORNER_RADIUS);
+  // Side closest to workspace.
+  path.push('v', Math.max(0, height - this.CORNER_RADIUS * 2));
+  // Rounded corner.
+  path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0,
+      atRight ? 0 : 1,
+      atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS,
+      this.CORNER_RADIUS);
+  // Bottom.
+  path.push('h', -width);
+  path.push('z');
+  this.svgBackground_.setAttribute('d', path.join(' '));
+};
+
+/**
+ * Create and set the path for the visible boundaries of the flyout in
+ * horizontal mode.
+ * @param {number} width The width of the flyout, not including the
+ *     rounded corners.
+ * @param {number} height The height of the flyout, not including
+ *     rounded corners.
+ * @private
+ */
+Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = function(width,
+    height) {
+  var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP;
+  // Start at top left.
+  var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)];
+
+  if (atTop) {
+    // Top.
+    path.push('h', width + this.CORNER_RADIUS);
+    // Right.
+    path.push('v', height);
+    // Bottom.
+    path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
+        -this.CORNER_RADIUS, this.CORNER_RADIUS);
+    path.push('h', -1 * (width - this.CORNER_RADIUS));
+    // Left.
+    path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
+        -this.CORNER_RADIUS, -this.CORNER_RADIUS);
+    path.push('z');
+  } else {
+    // Top.
+    path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
+        this.CORNER_RADIUS, -this.CORNER_RADIUS);
+    path.push('h', width - this.CORNER_RADIUS);
+     // Right.
+    path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
+        this.CORNER_RADIUS, this.CORNER_RADIUS);
+    path.push('v', height - this.CORNER_RADIUS);
+    // Bottom.
+    path.push('h', -width - this.CORNER_RADIUS);
+    // Left.
+    path.push('z');
+  }
+  this.svgBackground_.setAttribute('d', path.join(' '));
+};
+
+/**
+ * Scroll the flyout to the top.
+ */
+Blockly.Flyout.prototype.scrollToStart = function() {
+  this.scrollbar_.set((this.horizontalLayout_ && this.RTL) ? Infinity : 0);
+};
+
+/**
+ * Scroll the flyout.
+ * @param {!Event} e Mouse wheel scroll event.
+ * @private
+ */
+Blockly.Flyout.prototype.wheel_ = function(e) {
+  var delta = this.horizontalLayout_ ? e.deltaX : e.deltaY;
+
+  if (delta) {
+    if (goog.userAgent.GECKO) {
+      // Firefox's deltas are a tenth that of Chrome/Safari.
+      delta *= 10;
+    }
+    var metrics = this.getMetrics_();
+    var pos = this.horizontalLayout_ ? metrics.viewLeft + delta :
+        metrics.viewTop + delta;
+    var limit = this.horizontalLayout_ ?
+        metrics.contentWidth - metrics.viewWidth :
+        metrics.contentHeight - metrics.viewHeight;
+    pos = Math.min(pos, limit);
+    pos = Math.max(pos, 0);
+    this.scrollbar_.set(pos);
+  }
+
+  // Don't scroll the page.
+  e.preventDefault();
+  // Don't propagate mousewheel event (zooming).
+  e.stopPropagation();
+};
+
+/**
+ * Is the flyout visible?
+ * @return {boolean} True if visible.
+ */
+Blockly.Flyout.prototype.isVisible = function() {
+  return this.svgGroup_ && this.svgGroup_.style.display == 'block';
+};
+
+/**
+ * Hide and empty the flyout.
+ */
+Blockly.Flyout.prototype.hide = function() {
+  if (!this.isVisible()) {
+    return;
+  }
+  this.svgGroup_.style.display = 'none';
+  // Delete all the event listeners.
+  for (var x = 0, listen; listen = this.listeners_[x]; x++) {
+    Blockly.unbindEvent_(listen);
+  }
+  this.listeners_.length = 0;
+  if (this.reflowWrapper_) {
+    this.workspace_.removeChangeListener(this.reflowWrapper_);
+    this.reflowWrapper_ = null;
+  }
+  // Do NOT delete the blocks here.  Wait until Flyout.show.
+  // https://neil.fraser.name/news/2014/08/09/
+};
+
+/**
+ * Show and populate the flyout.
+ * @param {!Array|string} xmlList List of blocks to show.
+ *     Variables and procedures have a custom set of blocks.
+ */
+Blockly.Flyout.prototype.show = function(xmlList) {
+  this.hide();
+  this.clearOldBlocks_();
+
+  if (xmlList == Blockly.Variables.NAME_TYPE) {
+    // Special category for variables.
+    xmlList =
+        Blockly.Variables.flyoutCategory(this.workspace_.targetWorkspace);
+  } else if (xmlList == Blockly.Procedures.NAME_TYPE) {
+    // Special category for procedures.
+    xmlList =
+        Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace);
+  }
+
+  this.svgGroup_.style.display = 'block';
+  // Create the blocks to be shown in this flyout.
+  var blocks = [];
+  var gaps = [];
+  this.permanentlyDisabled_.length = 0;
+  for (var i = 0, xml; xml = xmlList[i]; i++) {
+    if (xml.tagName && xml.tagName.toUpperCase() == 'BLOCK') {
+      var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
+      if (curBlock.disabled) {
+        // Record blocks that were initially disabled.
+        // Do not enable these blocks as a result of capacity filtering.
+        this.permanentlyDisabled_.push(curBlock);
+      }
+      blocks.push(curBlock);
+      var gap = parseInt(xml.getAttribute('gap'), 10);
+      gaps.push(isNaN(gap) ? this.MARGIN * 3 : gap);
+    }
+  }
+
+  this.layoutBlocks_(blocks, gaps);
+
+  // IE 11 is an incompetent browser that fails to fire mouseout events.
+  // When the mouse is over the background, deselect all blocks.
+  var deselectAll = function() {
+    var topBlocks = this.workspace_.getTopBlocks(false);
+    for (var i = 0, block; block = topBlocks[i]; i++) {
+      block.removeSelect();
+    }
+  };
+
+  this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover',
+      this, deselectAll));
+
+  if (this.horizontalLayout_) {
+    this.height_ = 0;
+  } else {
+    this.width_ = 0;
+  }
+  this.reflow();
+
+  this.filterForCapacity_();
+
+  // Correctly position the flyout's scrollbar when it opens.
+  this.position();
+
+  this.reflowWrapper_ = this.reflow.bind(this);
+  this.workspace_.addChangeListener(this.reflowWrapper_);
+};
+
+/**
+ * Lay out the blocks in the flyout.
+ * @param {!Array.<!Blockly.BlockSvg>} blocks The blocks to lay out.
+ * @param {!Array.<number>} gaps The visible gaps between blocks.
+ * @private
+ */
+Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) {
+  var margin = this.MARGIN;
+  var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
+  var cursorY = margin;
+  if (this.horizontalLayout_ && this.RTL) {
+    blocks = blocks.reverse();
+  }
+  for (var i = 0, block; block = blocks[i]; i++) {
+    var allBlocks = block.getDescendants();
+    for (var j = 0, child; child = allBlocks[j]; j++) {
+      // Mark blocks as being inside a flyout.  This is used to detect and
+      // prevent the closure of the flyout if the user right-clicks on such a
+      // block.
+      child.isInFlyout = true;
+    }
+    block.render();
+    var root = block.getSvgRoot();
+    var blockHW = block.getHeightWidth();
+    var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
+    if (this.horizontalLayout_) {
+      cursorX += tab;
+    }
+
+    if (this.horizontalLayout_ && this.RTL) {
+      block.moveBy(cursorX + blockHW.width - tab, cursorY);
+    } else {
+      block.moveBy(cursorX, cursorY);
+    }
+
+    if (this.horizontalLayout_) {
+      cursorX += (blockHW.width + gaps[i] - tab);
+    } else {
+      cursorY += blockHW.height + gaps[i];
+    }
+
+    // Create an invisible rectangle under the block to act as a button.  Just
+    // using the block as a button is poor, since blocks have holes in them.
+    var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null);
+    rect.tooltip = block;
+    Blockly.Tooltip.bindMouseEvents(rect);
+    // Add the rectangles under the blocks, so that the blocks' tooltips work.
+    this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
+    block.flyoutRect_ = rect;
+    this.buttons_[i] = rect;
+
+    this.addBlockListeners_(root, block, rect);
+  }
+};
+
+/**
+ * Delete blocks and background buttons from a previous showing of the flyout.
+ * @private
+ */
+Blockly.Flyout.prototype.clearOldBlocks_ = function() {
+  // Delete any blocks from a previous showing.
+  var oldBlocks = this.workspace_.getTopBlocks(false);
+  for (var i = 0, block; block = oldBlocks[i]; i++) {
+    if (block.workspace == this.workspace_) {
+      block.dispose(false, false);
+    }
+  }
+  // Delete any background buttons from a previous showing.
+  for (var j = 0, rect; rect = this.buttons_[j]; j++) {
+    goog.dom.removeNode(rect);
+  }
+  this.buttons_.length = 0;
+};
+
+/**
+ * Add listeners to a block that has been added to the flyout.
+ * @param {!Element} root The root node of the SVG group the block is in.
+ * @param {!Blockly.Block} block The block to add listeners for.
+ * @param {!Element} rect The invisible rectangle under the block that acts as
+ *     a button for that block.
+ * @private
+ */
+Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
+  this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null,
+      this.blockMouseDown_(block)));
+  this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null,
+      this.blockMouseDown_(block)));
+  this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block,
+      block.addSelect));
+  this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block,
+      block.removeSelect));
+  this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block,
+      block.addSelect));
+  this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block,
+      block.removeSelect));
+};
+
+/**
+ * Handle a mouse-down on an SVG block in a non-closing flyout.
+ * @param {!Blockly.Block} block The flyout block to copy.
+ * @return {!Function} Function to call when block is clicked.
+ * @private
+ */
+Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
+  var flyout = this;
+  return function(e) {
+    Blockly.terminateDrag_();
+    Blockly.hideChaff(true);
+    if (Blockly.isRightButton(e)) {
+      // Right-click.
+      block.showContextMenu_(e);
+    } else {
+      // Left-click (or middle click)
+      Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
+      // Record the current mouse position.
+      flyout.startDragMouseY_ = e.clientY;
+      flyout.startDragMouseX_ = e.clientX;
+      Blockly.Flyout.startDownEvent_ = e;
+      Blockly.Flyout.startBlock_ = block;
+      Blockly.Flyout.startFlyout_ = flyout;
+      Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document,
+          'mouseup', flyout, flyout.onMouseUp_);
+      Blockly.Flyout.onMouseMoveBlockWrapper_ = Blockly.bindEvent_(document,
+          'mousemove', flyout, flyout.onMouseMoveBlock_);
+    }
+    // This event has been handled.  No need to bubble up to the document.
+    e.stopPropagation();
+    e.preventDefault();
+  };
+};
+
+/**
+ * Mouse down on the flyout background.  Start a vertical scroll drag.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.Flyout.prototype.onMouseDown_ = function(e) {
+  if (Blockly.isRightButton(e)) {
+    return;
+  }
+  Blockly.hideChaff(true);
+  this.dragMode_ = Blockly.DRAG_FREE;
+  this.startDragMouseY_ = e.clientY;
+  this.startDragMouseX_ = e.clientX;
+  Blockly.Flyout.startFlyout_ = this;
+  Blockly.Flyout.onMouseMoveWrapper_ = Blockly.bindEvent_(document, 'mousemove',
+      this, this.onMouseMove_);
+  Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, 'mouseup',
+      this, Blockly.Flyout.terminateDrag_);
+  // This event has been handled.  No need to bubble up to the document.
+  e.preventDefault();
+  e.stopPropagation();
+};
+
+/**
+ * Handle a mouse-up anywhere in the SVG pane.  Is only registered when a
+ * block is clicked.  We can't use mouseUp on the block since a fast-moving
+ * cursor can briefly escape the block before it catches up.
+ * @param {!Event} e Mouse up event.
+ * @private
+ */
+Blockly.Flyout.prototype.onMouseUp_ = function(e) {
+  if (!this.workspace_.isDragging()) {
+    if (this.autoClose) {
+      this.createBlockFunc_(Blockly.Flyout.startBlock_)(
+          Blockly.Flyout.startDownEvent_);
+    } else if (!Blockly.WidgetDiv.isVisible()) {
+      Blockly.Events.fire(
+          new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click',
+                                undefined, undefined));
+    }
+  }
+  Blockly.terminateDrag_();
+};
+
+/**
+ * Handle a mouse-move to vertically drag the flyout.
+ * @param {!Event} e Mouse move event.
+ * @private
+ */
+Blockly.Flyout.prototype.onMouseMove_ = function(e) {
+  var metrics = this.getMetrics_();
+  if (this.horizontalLayout_) {
+    if (metrics.contentWidth - metrics.viewWidth < 0) {
+      return;
+    }
+    var dx = e.clientX - this.startDragMouseX_;
+    this.startDragMouseX_ = e.clientX;
+    var x = metrics.viewLeft - dx;
+    x = goog.math.clamp(x, 0, metrics.contentWidth - metrics.viewWidth);
+    this.scrollbar_.set(x);
+  } else {
+    if (metrics.contentHeight - metrics.viewHeight < 0) {
+      return;
+    }
+    var dy = e.clientY - this.startDragMouseY_;
+    this.startDragMouseY_ = e.clientY;
+    var y = metrics.viewTop - dy;
+    y = goog.math.clamp(y, 0, metrics.contentHeight - metrics.viewHeight);
+    this.scrollbar_.set(y);
+  }
+};
+
+/**
+ * Mouse button is down on a block in a non-closing flyout.  Create the block
+ * if the mouse moves beyond a small radius.  This allows one to play with
+ * fields without instantiating blocks that instantly self-destruct.
+ * @param {!Event} e Mouse move event.
+ * @private
+ */
+Blockly.Flyout.prototype.onMouseMoveBlock_ = function(e) {
+  if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
+      e.button == 0) {
+    /* HACK:
+     Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events
+     on certain touch actions. Ignore events with these signatures.
+     This may result in a one-pixel blind spot in other browsers,
+     but this shouldn't be noticeable. */
+    e.stopPropagation();
+    return;
+  }
+  var dx = e.clientX - Blockly.Flyout.startDownEvent_.clientX;
+  var dy = e.clientY - Blockly.Flyout.startDownEvent_.clientY;
+
+  var createBlock = this.determineDragIntention_(dx, dy);
+  if (createBlock) {
+    this.createBlockFunc_(Blockly.Flyout.startBlock_)(
+        Blockly.Flyout.startDownEvent_);
+  } else if (this.dragMode_ == Blockly.DRAG_FREE) {
+    // Do a scroll.
+    this.onMouseMove_(e);
+  }
+  e.stopPropagation();
+};
+
+/**
+ * Determine the intention of a drag.
+ * Updates dragMode_ based on a drag delta and the current mode,
+ * and returns true if we should create a new block.
+ * @param {number} dx X delta of the drag.
+ * @param {number} dy Y delta of the drag.
+ * @return {boolean} True if a new block should be created.
+ * @private
+ */
+Blockly.Flyout.prototype.determineDragIntention_ = function(dx, dy) {
+  if (this.dragMode_ == Blockly.DRAG_FREE) {
+    // Once in free mode, always stay in free mode and never create a block.
+    return false;
+  }
+  var dragDistance = Math.sqrt(dx * dx + dy * dy);
+  if (dragDistance < this.DRAG_RADIUS) {
+    // Still within the sticky drag radius.
+    this.dragMode_ = Blockly.DRAG_STICKY;
+    return false;
+  } else {
+    if (this.isDragTowardWorkspace_(dx, dy) || !this.scrollbar_.isVisible()) {
+      // Immediately create a block.
+      return true;
+    } else {
+      // Immediately move to free mode - the drag is away from the workspace.
+      this.dragMode_ = Blockly.DRAG_FREE;
+      return false;
+    }
+  }
+};
+
+/**
+ * Determine if a drag delta is toward the workspace, based on the position
+ * and orientation of the flyout. This is used in determineDragIntention_ to
+ * determine if a new block should be created or if the flyout should scroll.
+ * @param {number} dx X delta of the drag.
+ * @param {number} dy Y delta of the drag.
+ * @return {boolean} true if the drag is toward the workspace.
+ * @private
+ */
+Blockly.Flyout.prototype.isDragTowardWorkspace_ = function(dx, dy) {
+  // Direction goes from -180 to 180, with 0 toward the right and 90 on top.
+  var dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
+
+  var draggingTowardWorkspace = false;
+  var range = this.dragAngleRange_;
+  if (this.horizontalLayout_) {
+    if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
+      // Horizontal at top.
+      if (dragDirection < 90 + range && dragDirection > 90 - range) {
+        draggingTowardWorkspace = true;
+      }
+    } else {
+      // Horizontal at bottom.
+      if (dragDirection > -90 - range && dragDirection < -90 + range) {
+        draggingTowardWorkspace = true;
+      }
+    }
+  } else {
+    if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
+      // Vertical at left.
+      if (dragDirection < range && dragDirection > -range) {
+        draggingTowardWorkspace = true;
+      }
+    } else {
+      // Vertical at right.
+      if (dragDirection < -180 + range || dragDirection > 180 - range) {
+        draggingTowardWorkspace = true;
+      }
+    }
+  }
+  return draggingTowardWorkspace;
+};
+
+/**
+ * Create a copy of this block on the workspace.
+ * @param {!Blockly.Block} originBlock The flyout block to copy.
+ * @return {!Function} Function to call when block is clicked.
+ * @private
+ */
+Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
+  var flyout = this;
+  return function(e) {
+    if (Blockly.isRightButton(e)) {
+      // Right-click.  Don't create a block, let the context menu show.
+      return;
+    }
+    if (originBlock.disabled) {
+      // Beyond capacity.
+      return;
+    }
+    Blockly.Events.disable();
+    try {
+      var block = flyout.placeNewBlock_(originBlock);
+    } finally {
+      Blockly.Events.enable();
+    }
+    if (Blockly.Events.isEnabled()) {
+      Blockly.Events.setGroup(true);
+      Blockly.Events.fire(new Blockly.Events.Create(block));
+    }
+    if (flyout.autoClose) {
+      flyout.hide();
+    } else {
+      flyout.filterForCapacity_();
+    }
+    // Start a dragging operation on the new block.
+    block.onMouseDown_(e);
+    Blockly.dragMode_ = Blockly.DRAG_FREE;
+    block.setDragging_(true);
+  };
+};
+
+/**
+ * Copy a block from the flyout to the workspace and position it correctly.
+ * @param {!Blockly.Block} originBlock The flyout block to copy..
+ * @return {!Blockly.Block} The new block in the main workspace.
+ * @private
+ */
+Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock) {
+  var targetWorkspace = this.targetWorkspace_;
+  var svgRootOld = originBlock.getSvgRoot();
+  if (!svgRootOld) {
+    throw 'originBlock is not rendered.';
+  }
+  // Figure out where the original block is on the screen, relative to the upper
+  // left corner of the main workspace.
+  var xyOld = Blockly.getSvgXY_(svgRootOld, targetWorkspace);
+  // Take into account that the flyout might have been scrolled horizontally
+  // (separately from the main workspace).
+  // Generally a no-op in vertical mode but likely to happen in horizontal
+  // mode.
+  var scrollX = this.workspace_.scrollX;
+  var scale = this.workspace_.scale;
+  xyOld.x += scrollX / scale - scrollX;
+  // If the flyout is on the right side, (0, 0) in the flyout is offset to
+  // the right of (0, 0) in the main workspace.  Add an offset to take that
+  // into account.
+  if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
+    scrollX = targetWorkspace.getMetrics().viewWidth - this.width_;
+    scale = targetWorkspace.scale;
+    // Scale the scroll (getSvgXY_ did not do this).
+    xyOld.x += scrollX / scale - scrollX;
+  }
+
+  // Take into account that the flyout might have been scrolled vertically
+  // (separately from the main workspace).
+  // Generally a no-op in horizontal mode but likely to happen in vertical
+  // mode.
+  var scrollY = this.workspace_.scrollY;
+  scale = this.workspace_.scale;
+  xyOld.y += scrollY / scale - scrollY;
+  // If the flyout is on the bottom, (0, 0) in the flyout is offset to be below
+  // (0, 0) in the main workspace.  Add an offset to take that into account.
+  if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
+    scrollY = targetWorkspace.getMetrics().viewHeight - this.height_;
+    scale = targetWorkspace.scale;
+    xyOld.y += scrollY / scale - scrollY;
+  }
+
+  // Create the new block by cloning the block in the flyout (via XML).
+  var xml = Blockly.Xml.blockToDom(originBlock);
+  var block = Blockly.Xml.domToBlock(xml, targetWorkspace);
+  var svgRootNew = block.getSvgRoot();
+  if (!svgRootNew) {
+    throw 'block is not rendered.';
+  }
+  // Figure out where the new block got placed on the screen, relative to the
+  // upper left corner of the workspace.  This may not be the same as the
+  // original block because the flyout's origin may not be the same as the
+  // main workspace's origin.
+  var xyNew = Blockly.getSvgXY_(svgRootNew, targetWorkspace);
+  // Scale the scroll (getSvgXY_ did not do this).
+  xyNew.x +=
+      targetWorkspace.scrollX / targetWorkspace.scale - targetWorkspace.scrollX;
+  xyNew.y +=
+      targetWorkspace.scrollY / targetWorkspace.scale - targetWorkspace.scrollY;
+  // If the flyout is collapsible and the workspace can't be scrolled.
+  if (targetWorkspace.toolbox_ && !targetWorkspace.scrollbar) {
+    xyNew.x += targetWorkspace.toolbox_.getWidth() / targetWorkspace.scale;
+    xyNew.y += targetWorkspace.toolbox_.getHeight() / targetWorkspace.scale;
+  }
+
+  // Move the new block to where the old block is.
+  block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y);
+  return block;
+};
+
+/**
+ * Filter the blocks on the flyout to disable the ones that are above the
+ * capacity limit.
+ * @private
+ */
+Blockly.Flyout.prototype.filterForCapacity_ = function() {
+  var remainingCapacity = this.targetWorkspace_.remainingCapacity();
+  var blocks = this.workspace_.getTopBlocks(false);
+  for (var i = 0, block; block = blocks[i]; i++) {
+    if (this.permanentlyDisabled_.indexOf(block) == -1) {
+      var allBlocks = block.getDescendants();
+      block.setDisabled(allBlocks.length > remainingCapacity);
+    }
+  }
+};
+
+/**
+ * Return the deletion rectangle for this flyout.
+ * @return {goog.math.Rect} Rectangle in which to delete.
+ */
+Blockly.Flyout.prototype.getClientRect = function() {
+  if (!this.svgGroup_) {
+    return null;
+  }
+
+  var flyoutRect = this.svgGroup_.getBoundingClientRect();
+  // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout
+  // area are still deleted.  Must be larger than the largest screen size,
+  // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE).
+  var BIG_NUM = 1000000000;
+  var x = flyoutRect.left;
+  var y = flyoutRect.top;
+  var width = flyoutRect.width;
+  var height = flyoutRect.height;
+
+  if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
+    return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2,
+        BIG_NUM + height);
+  } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
+    return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2,
+        BIG_NUM + height);
+  } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
+    return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width,
+        BIG_NUM * 2);
+  } else {  // Right
+    return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2);
+  }
+};
+
+/**
+ * Stop binding to the global mouseup and mousemove events.
+ * @private
+ */
+Blockly.Flyout.terminateDrag_ = function() {
+  if (Blockly.Flyout.startFlyout_) {
+    Blockly.Flyout.startFlyout_.dragMode_ = Blockly.DRAG_NONE;
+  }
+  if (Blockly.Flyout.onMouseUpWrapper_) {
+    Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_);
+    Blockly.Flyout.onMouseUpWrapper_ = null;
+  }
+  if (Blockly.Flyout.onMouseMoveBlockWrapper_) {
+    Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_);
+    Blockly.Flyout.onMouseMoveBlockWrapper_ = null;
+  }
+  if (Blockly.Flyout.onMouseMoveWrapper_) {
+    Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_);
+    Blockly.Flyout.onMouseMoveWrapper_ = null;
+  }
+  if (Blockly.Flyout.onMouseUpWrapper_) {
+    Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_);
+    Blockly.Flyout.onMouseUpWrapper_ = null;
+  }
+  Blockly.Flyout.startDownEvent_ = null;
+  Blockly.Flyout.startBlock_ = null;
+  Blockly.Flyout.startFlyout_ = null;
+};
+
+/**
+ * Compute height of flyout.  Position button under each block.
+ * For RTL: Lay out the blocks right-aligned.
+ * @param {!Array<!Blockly.Block>} blocks The blocks to reflow.
+ */
+Blockly.Flyout.prototype.reflowHorizontal = function(blocks) {
+  this.workspace_.scale = this.targetWorkspace_.scale;
+  var flyoutHeight = 0;
+  for (var i = 0, block; block = blocks[i]; i++) {
+    flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height);
+  }
+  flyoutHeight += this.MARGIN * 1.5;
+  flyoutHeight *= this.workspace_.scale;
+  flyoutHeight += Blockly.Scrollbar.scrollbarThickness;
+  if (this.height_ != flyoutHeight) {
+    for (var i = 0, block; block = blocks[i]; i++) {
+      var blockHW = block.getHeightWidth();
+      if (block.flyoutRect_) {
+        block.flyoutRect_.setAttribute('width', blockHW.width);
+        block.flyoutRect_.setAttribute('height', blockHW.height);
+        // Rectangles behind blocks with output tabs are shifted a bit.
+        var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
+        var blockXY = block.getRelativeToSurfaceXY();
+        block.flyoutRect_.setAttribute('y', blockXY.y);
+        block.flyoutRect_.setAttribute('x',
+            this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
+        // For hat blocks we want to shift them down by the hat height
+        // since the y coordinate is the corner, not the top of the hat.
+        var hatOffset =
+            block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0;
+        if (hatOffset) {
+          block.moveBy(0, hatOffset);
+        }
+        block.flyoutRect_.setAttribute('y', blockXY.y);
+      }
+    }
+    // Record the height for .getMetrics_ and .position.
+    this.height_ = flyoutHeight;
+    // Call this since it is possible the trash and zoom buttons need
+    // to move. e.g. on a bottom positioned flyout when zoom is clicked.
+    this.targetWorkspace_.resize();
+  }
+};
+
+/**
+ * Compute width of flyout.  Position button under each block.
+ * For RTL: Lay out the blocks right-aligned.
+ * @param {!Array<!Blockly.Block>} blocks The blocks to reflow.
+ */
+Blockly.Flyout.prototype.reflowVertical = function(blocks) {
+  this.workspace_.scale = this.targetWorkspace_.scale;
+  var flyoutWidth = 0;
+  for (var i = 0, block; block = blocks[i]; i++) {
+    var width = block.getHeightWidth().width;
+    if (block.outputConnection) {
+      width -= Blockly.BlockSvg.TAB_WIDTH;
+    }
+    flyoutWidth = Math.max(flyoutWidth, width);
+  }
+  flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH;
+  flyoutWidth *= this.workspace_.scale;
+  flyoutWidth += Blockly.Scrollbar.scrollbarThickness;
+  if (this.width_ != flyoutWidth) {
+    for (var i = 0, block; block = blocks[i]; i++) {
+      var blockHW = block.getHeightWidth();
+      if (this.RTL) {
+        // With the flyoutWidth known, right-align the blocks.
+        var oldX = block.getRelativeToSurfaceXY().x;
+        var newX = flyoutWidth / this.workspace_.scale - this.MARGIN;
+        newX -= Blockly.BlockSvg.TAB_WIDTH;
+        block.moveBy(newX - oldX, 0);
+      }
+      if (block.flyoutRect_) {
+        block.flyoutRect_.setAttribute('width', blockHW.width);
+        block.flyoutRect_.setAttribute('height', blockHW.height);
+        // Blocks with output tabs are shifted a bit.
+        var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
+        var blockXY = block.getRelativeToSurfaceXY();
+        block.flyoutRect_.setAttribute('x',
+            this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
+        // For hat blocks we want to shift them down by the hat height
+        // since the y coordinate is the corner, not the top of the hat.
+        var hatOffset =
+            block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0;
+        if (hatOffset) {
+          block.moveBy(0, hatOffset);
+        }
+        block.flyoutRect_.setAttribute('y', blockXY.y);
+      }
+    }
+    // Record the width for .getMetrics_ and .position.
+    this.width_ = flyoutWidth;
+    // Call this since it is possible the trash and zoom buttons need
+    // to move. e.g. on a bottom positioned flyout when zoom is clicked.
+    this.targetWorkspace_.resize();
+  }
+};
+
+/**
+ * Reflow blocks and their buttons.
+ */
+Blockly.Flyout.prototype.reflow = function() {
+  if (this.reflowWrapper_) {
+    this.workspace_.removeChangeListener(this.reflowWrapper_);
+  }
+  var blocks = this.workspace_.getTopBlocks(false);
+  if (this.horizontalLayout_) {
+    this.reflowHorizontal(blocks);
+  } else {
+    this.reflowVertical(blocks);
+  }
+  if (this.reflowWrapper_) {
+    this.workspace_.addChangeListener(this.reflowWrapper_);
+  }
+};

+ 369 - 0
blockly/core/generator.js

@@ -0,0 +1,369 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Utility functions for generating executable code from
+ * Blockly code.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Generator');
+
+goog.require('Blockly.Block');
+goog.require('goog.asserts');
+
+
+/**
+ * Class for a code generator that translates the blocks into a language.
+ * @param {string} name Language name of this generator.
+ * @constructor
+ */
+Blockly.Generator = function(name) {
+  this.name_ = name;
+  this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
+      new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
+};
+
+/**
+ * Category to separate generated function names from variables and procedures.
+ */
+Blockly.Generator.NAME_TYPE = 'generated_function';
+
+/**
+ * Arbitrary code to inject into locations that risk causing infinite loops.
+ * Any instances of '%1' will be replaced by the block ID that failed.
+ * E.g. '  checkTimeout(%1);\n'
+ * @type {?string}
+ */
+Blockly.Generator.prototype.INFINITE_LOOP_TRAP = null;
+
+/**
+ * Arbitrary code to inject before every statement.
+ * Any instances of '%1' will be replaced by the block ID of the statement.
+ * E.g. 'highlight(%1);\n'
+ * @type {?string}
+ */
+Blockly.Generator.prototype.STATEMENT_PREFIX = null;
+
+/**
+ * The method of indenting.  Defaults to two spaces, but language generators
+ * may override this to increase indent or change to tabs.
+ * @type {string}
+ */
+Blockly.Generator.prototype.INDENT = '  ';
+
+/**
+ * Maximum length for a comment before wrapping.  Does not account for
+ * indenting level.
+ * @type {number}
+ */
+Blockly.Generator.prototype.COMMENT_WRAP = 60;
+
+/**
+ * List of outer-inner pairings that do NOT require parentheses.
+ * @type {!Array.<!Array.<number>>}
+ */
+Blockly.Generator.prototype.ORDER_OVERRIDES = [];
+
+/**
+ * Generate code for all blocks in the workspace to the specified language.
+ * @param {Blockly.Workspace} workspace Workspace to generate code from.
+ * @return {string} Generated code.
+ */
+Blockly.Generator.prototype.workspaceToCode = function(workspace) {
+  if (!workspace) {
+    // Backwards compatibility from before there could be multiple workspaces.
+    console.warn('No workspace specified in workspaceToCode call.  Guessing.');
+    workspace = Blockly.getMainWorkspace();
+  }
+  var code = [];
+  this.init(workspace);
+  var blocks = workspace.getTopBlocks(true);
+  for (var x = 0, block; block = blocks[x]; x++) {
+    var line = this.blockToCode(block);
+    if (goog.isArray(line)) {
+      // Value blocks return tuples of code and operator order.
+      // Top-level blocks don't care about operator order.
+      line = line[0];
+    }
+    if (line) {
+      if (block.outputConnection && this.scrubNakedValue) {
+        // This block is a naked value.  Ask the language's code generator if
+        // it wants to append a semicolon, or something.
+        line = this.scrubNakedValue(line);
+      }
+      code.push(line);
+    }
+  }
+  code = code.join('\n');  // Blank line between each section.
+  code = this.finish(code);
+  // Final scrubbing of whitespace.
+  code = code.replace(/^\s+\n/, '');
+  code = code.replace(/\n\s+$/, '\n');
+  code = code.replace(/[ \t]+\n/g, '\n');
+  return code;
+};
+
+// The following are some helpful functions which can be used by multiple
+// languages.
+
+/**
+ * Prepend a common prefix onto each line of code.
+ * @param {string} text The lines of code.
+ * @param {string} prefix The common prefix.
+ * @return {string} The prefixed lines of code.
+ */
+Blockly.Generator.prototype.prefixLines = function(text, prefix) {
+  return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix);
+};
+
+/**
+ * Recursively spider a tree of blocks, returning all their comments.
+ * @param {!Blockly.Block} block The block from which to start spidering.
+ * @return {string} Concatenated list of comments.
+ */
+Blockly.Generator.prototype.allNestedComments = function(block) {
+  var comments = [];
+  var blocks = block.getDescendants();
+  for (var i = 0; i < blocks.length; i++) {
+    var comment = blocks[i].getCommentText();
+    if (comment) {
+      comments.push(comment);
+    }
+  }
+  // Append an empty string to create a trailing line break when joined.
+  if (comments.length) {
+    comments.push('');
+  }
+  return comments.join('\n');
+};
+
+/**
+ * Generate code for the specified block (and attached blocks).
+ * @param {Blockly.Block} block The block to generate code for.
+ * @return {string|!Array} For statement blocks, the generated code.
+ *     For value blocks, an array containing the generated code and an
+ *     operator order value.  Returns '' if block is null.
+ */
+Blockly.Generator.prototype.blockToCode = function(block) {
+  if (!block) {
+    return '';
+  }
+  if (block.disabled) {
+    // Skip past this block if it is disabled.
+    return this.blockToCode(block.getNextBlock());
+  }
+
+  var func = this[block.type];
+  goog.asserts.assertFunction(func,
+      'Language "%s" does not know how to generate code for block type "%s".',
+      this.name_, block.type);
+  // First argument to func.call is the value of 'this' in the generator.
+  // Prior to 24 September 2013 'this' was the only way to access the block.
+  // The current prefered method of accessing the block is through the second
+  // argument to func.call, which becomes the first parameter to the generator.
+  var code = func.call(block, block);
+  if (goog.isArray(code)) {
+    // Value blocks return tuples of code and operator order.
+    goog.asserts.assert(block.outputConnection,
+        'Expecting string from statement block "%s".', block.type);
+    return [this.scrub_(block, code[0]), code[1]];
+  } else if (goog.isString(code)) {
+    if (this.STATEMENT_PREFIX) {
+      code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\'') +
+          code;
+    }
+    return this.scrub_(block, code);
+  } else if (code === null) {
+    // Block has handled code generation itself.
+    return '';
+  } else {
+    goog.asserts.fail('Invalid code generated: %s', code);
+  }
+};
+
+/**
+ * Generate code representing the specified value input.
+ * @param {!Blockly.Block} block The block containing the input.
+ * @param {string} name The name of the input.
+ * @param {number} outerOrder The maximum binding strength (minimum order value)
+ *     of any operators adjacent to "block".
+ * @return {string} Generated code or '' if no blocks are connected or the
+ *     specified input does not exist.
+ */
+Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) {
+  if (isNaN(outerOrder)) {
+    goog.asserts.fail('Expecting valid order from block "%s".', block.type);
+  }
+  var targetBlock = block.getInputTargetBlock(name);
+  if (!targetBlock) {
+    return '';
+  }
+  var tuple = this.blockToCode(targetBlock);
+  if (tuple === '') {
+    // Disabled block.
+    return '';
+  }
+  // Value blocks must return code and order of operations info.
+  // Statement blocks must only return code.
+  goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".',
+      targetBlock.type);
+  var code = tuple[0];
+  var innerOrder = tuple[1];
+  if (isNaN(innerOrder)) {
+    goog.asserts.fail('Expecting valid order from value block "%s".',
+        targetBlock.type);
+  }
+  if (!code) {
+    return '';
+  }
+
+  // Add parentheses if needed.
+  var parensNeeded = false;
+  var outerOrderClass = Math.floor(outerOrder);
+  var innerOrderClass = Math.floor(innerOrder);
+  if (outerOrderClass <= innerOrderClass) {
+    if (outerOrderClass == innerOrderClass &&
+        (outerOrderClass == 0 || outerOrderClass == 99)) {
+      // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
+      // 0 is the atomic order, 99 is the none order.  No parentheses needed.
+      // In all known languages multiple such code blocks are not order
+      // sensitive.  In fact in Python ('a' 'b') 'c' would fail.
+    } else {
+      // The operators outside this code are stonger than the operators
+      // inside this code.  To prevent the code from being pulled apart,
+      // wrap the code in parentheses.
+      parensNeeded = true;
+      // Check for special exceptions.
+      for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) {
+        if (this.ORDER_OVERRIDES[i][0] == outerOrder &&
+            this.ORDER_OVERRIDES[i][1] == innerOrder) {
+          parensNeeded = false;
+          break;
+        }
+      }
+    }
+  }
+  if (parensNeeded) {
+    // Technically, this should be handled on a language-by-language basis.
+    // However all known (sane) languages use parentheses for grouping.
+    code = '(' + code + ')';
+  }
+  return code;
+};
+
+/**
+ * Generate code representing the statement.  Indent the code.
+ * @param {!Blockly.Block} block The block containing the input.
+ * @param {string} name The name of the input.
+ * @return {string} Generated code or '' if no blocks are connected.
+ */
+Blockly.Generator.prototype.statementToCode = function(block, name) {
+  var targetBlock = block.getInputTargetBlock(name);
+  var code = this.blockToCode(targetBlock);
+  // Value blocks must return code and order of operations info.
+  // Statement blocks must only return code.
+  goog.asserts.assertString(code, 'Expecting code from statement block "%s".',
+      targetBlock && targetBlock.type);
+  if (code) {
+    code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
+  }
+  return code;
+};
+
+/**
+ * Add an infinite loop trap to the contents of a loop.
+ * If loop is empty, add a statment prefix for the loop block.
+ * @param {string} branch Code for loop contents.
+ * @param {string} id ID of enclosing block.
+ * @return {string} Loop contents, with infinite loop trap added.
+ */
+Blockly.Generator.prototype.addLoopTrap = function(branch, id) {
+  if (this.INFINITE_LOOP_TRAP) {
+    branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch;
+  }
+  if (this.STATEMENT_PREFIX) {
+    branch += this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g,
+        '\'' + id + '\''), this.INDENT);
+  }
+  return branch;
+};
+
+/**
+ * Comma-separated list of reserved words.
+ * @type {string}
+ * @private
+ */
+Blockly.Generator.prototype.RESERVED_WORDS_ = '';
+
+/**
+ * Add one or more words to the list of reserved words for this language.
+ * @param {string} words Comma-separated list of words to add to the list.
+ *     No spaces.  Duplicates are ok.
+ */
+Blockly.Generator.prototype.addReservedWords = function(words) {
+  this.RESERVED_WORDS_ += words + ',';
+};
+
+/**
+ * This is used as a placeholder in functions defined using
+ * Blockly.Generator.provideFunction_.  It must not be legal code that could
+ * legitimately appear in a function definition (or comment), and it must
+ * not confuse the regular expression parser.
+ * @type {string}
+ * @private
+ */
+Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
+
+/**
+ * Define a function to be included in the generated code.
+ * The first time this is called with a given desiredName, the code is
+ * saved and an actual name is generated.  Subsequent calls with the
+ * same desiredName have no effect but have the same return value.
+ *
+ * It is up to the caller to make sure the same desiredName is not
+ * used for different code values.
+ *
+ * The code gets output when Blockly.Generator.finish() is called.
+ *
+ * @param {string} desiredName The desired name of the function (e.g., isPrime).
+ * @param {!Array.<string>} code A list of statements.  Use '  ' for indents.
+ * @return {string} The actual name of the new function.  This may differ
+ *     from desiredName if the former has already been taken by the user.
+ * @private
+ */
+Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) {
+  if (!this.definitions_[desiredName]) {
+    var functionName = this.variableDB_.getDistinctName(desiredName,
+        Blockly.Procedures.NAME_TYPE);
+    this.functionNames_[desiredName] = functionName;
+    var codeText = code.join('\n').replace(
+        this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
+    // Change all '  ' indents into the desired indent.
+    var oldCodeText;
+    while (oldCodeText != codeText) {
+      oldCodeText = codeText;
+      codeText = codeText.replace(/^((  )*)  /gm, '$1' + this.INDENT);
+    }
+    this.definitions_[desiredName] = codeText;
+  }
+  return this.functionNames_[desiredName];
+};

+ 221 - 0
blockly/core/icon.js

@@ -0,0 +1,221 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2013 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 Object representing an icon on a block.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Icon');
+
+goog.require('goog.dom');
+goog.require('goog.math.Coordinate');
+
+
+/**
+ * Class for an icon.
+ * @param {Blockly.Block} block The block associated with this icon.
+ * @constructor
+ */
+Blockly.Icon = function(block) {
+  this.block_ = block;
+};
+
+/**
+ * Does this icon get hidden when the block is collapsed.
+ */
+Blockly.Icon.prototype.collapseHidden = true;
+
+/**
+ * Height and width of icons.
+ */
+Blockly.Icon.prototype.SIZE = 17;
+
+/**
+ * Bubble UI (if visible).
+ * @type {Blockly.Bubble}
+ * @private
+ */
+Blockly.Icon.prototype.bubble_ = null;
+
+/**
+ * Absolute coordinate of icon's center.
+ * @type {goog.math.Coordinate}
+ * @private
+ */
+Blockly.Icon.prototype.iconXY_ = null;
+
+/**
+ * Create the icon on the block.
+ */
+Blockly.Icon.prototype.createIcon = function() {
+  if (this.iconGroup_) {
+    // Icon already exists.
+    return;
+  }
+  /* Here's the markup that will be generated:
+  <g class="blocklyIconGroup">
+    ...
+  </g>
+  */
+  this.iconGroup_ = Blockly.createSvgElement('g',
+      {'class': 'blocklyIconGroup'}, null);
+  if (this.block_.isInFlyout) {
+    Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
+                      'blocklyIconGroupReadonly');
+  }
+  this.drawIcon_(this.iconGroup_);
+
+  this.block_.getSvgRoot().appendChild(this.iconGroup_);
+  Blockly.bindEvent_(this.iconGroup_, 'mouseup', this, this.iconClick_);
+  this.updateEditable();
+};
+
+/**
+ * Dispose of this icon.
+ */
+Blockly.Icon.prototype.dispose = function() {
+  // Dispose of and unlink the icon.
+  goog.dom.removeNode(this.iconGroup_);
+  this.iconGroup_ = null;
+  // Dispose of and unlink the bubble.
+  this.setVisible(false);
+  this.block_ = null;
+};
+
+/**
+ * Add or remove the UI indicating if this icon may be clicked or not.
+ */
+Blockly.Icon.prototype.updateEditable = function() {
+};
+
+/**
+ * Is the associated bubble visible?
+ * @return {boolean} True if the bubble is visible.
+ */
+Blockly.Icon.prototype.isVisible = function() {
+  return !!this.bubble_;
+};
+
+/**
+ * Clicking on the icon toggles if the bubble is visible.
+ * @param {!Event} e Mouse click event.
+ * @private
+ */
+Blockly.Icon.prototype.iconClick_ = function(e) {
+  if (this.block_.workspace.isDragging()) {
+    // Drag operation is concluding.  Don't open the editor.
+    return;
+  }
+  if (!this.block_.isInFlyout && !Blockly.isRightButton(e)) {
+    this.setVisible(!this.isVisible());
+  }
+};
+
+/**
+ * Change the colour of the associated bubble to match its block.
+ */
+Blockly.Icon.prototype.updateColour = function() {
+  if (this.isVisible()) {
+    this.bubble_.setColour(this.block_.getColour());
+  }
+};
+
+/**
+ * Render the icon.
+ * @param {number} cursorX Horizontal offset at which to position the icon.
+ * @return {number} Horizontal offset for next item to draw.
+ */
+Blockly.Icon.prototype.renderIcon = function(cursorX) {
+  if (this.collapseHidden && this.block_.isCollapsed()) {
+    this.iconGroup_.setAttribute('display', 'none');
+    return cursorX;
+  }
+  this.iconGroup_.setAttribute('display', 'block');
+
+  var TOP_MARGIN = 5;
+  var width = this.SIZE;
+  if (this.block_.RTL) {
+    cursorX -= width;
+  }
+  this.iconGroup_.setAttribute('transform',
+      'translate(' + cursorX + ',' + TOP_MARGIN + ')');
+  this.computeIconLocation();
+  if (this.block_.RTL) {
+    cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
+  } else {
+    cursorX += width + Blockly.BlockSvg.SEP_SPACE_X;
+  }
+  return cursorX;
+};
+
+/**
+ * Notification that the icon has moved.  Update the arrow accordingly.
+ * @param {!goog.math.Coordinate} xy Absolute location.
+ */
+Blockly.Icon.prototype.setIconLocation = function(xy) {
+  this.iconXY_ = xy;
+  if (this.isVisible()) {
+    this.bubble_.setAnchorLocation(xy);
+  }
+};
+
+/**
+ * Notification that the icon has moved, but we don't really know where.
+ * Recompute the icon's location from scratch.
+ */
+Blockly.Icon.prototype.computeIconLocation = function() {
+  // Find coordinates for the centre of the icon and update the arrow.
+  var blockXY = this.block_.getRelativeToSurfaceXY();
+  var iconXY = Blockly.getRelativeXY_(this.iconGroup_);
+  var newXY = new goog.math.Coordinate(
+      blockXY.x + iconXY.x + this.SIZE / 2,
+      blockXY.y + iconXY.y + this.SIZE / 2);
+  if (!goog.math.Coordinate.equals(this.getIconLocation(), newXY)) {
+    this.setIconLocation(newXY);
+  }
+};
+
+/**
+ * Returns the center of the block's icon relative to the surface.
+ * @return {!goog.math.Coordinate} Object with x and y properties.
+ */
+Blockly.Icon.prototype.getIconLocation = function() {
+  return this.iconXY_;
+};
+
+/**
+ * Create the icon on the block.
+ * @private
+ */
+Blockly.Icon.prototype.createIconOld = function() {
+  if (this.iconGroup_) {
+    // Icon already exists.
+    return;
+  }
+  /* Here's the markup that will be generated:
+  <g class="blocklyIconGroup"></g>
+  */
+  this.iconGroup_ = Blockly.createSvgElement('g', {}, null);
+  this.block_.getSvgRoot().appendChild(this.iconGroup_);
+  Blockly.bindEvent_(this.iconGroup_, 'mouseup', this, this.iconClick_);
+  this.updateEditable();
+};

+ 380 - 0
blockly/core/inject.js

@@ -0,0 +1,380 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Functions for injecting Blockly into a web page.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.inject');
+
+goog.require('Blockly.Css');
+goog.require('Blockly.Options');
+goog.require('Blockly.WorkspaceSvg');
+goog.require('goog.dom');
+goog.require('goog.ui.Component');
+goog.require('goog.userAgent');
+
+
+/**
+ * Inject a Blockly editor into the specified container element (usually a div).
+ * @param {!Element|string} container Containing element, or its ID,
+ *     or a CSS selector.
+ * @param {Object=} opt_options Optional dictionary of options.
+ * @return {!Blockly.Workspace} Newly created main workspace.
+ */
+Blockly.inject = function(container, opt_options) {
+  if (goog.isString(container)) {
+    container = document.getElementById(container) ||
+        document.querySelector(container);
+  }
+  // Verify that the container is in document.
+  if (!goog.dom.contains(document, container)) {
+    throw 'Error: container is not in current document.';
+  }
+  var options = new Blockly.Options(opt_options || {});
+  var svg = Blockly.createDom_(container, options);
+  var workspace = Blockly.createMainWorkspace_(svg, options);
+  Blockly.init_(workspace);
+  workspace.markFocused();
+  Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused);
+  Blockly.svgResize(workspace);
+  return workspace;
+};
+
+/**
+ * Create the SVG image.
+ * @param {!Element} container Containing element.
+ * @param {!Blockly.Options} options Dictionary of options.
+ * @return {!Element} Newly created SVG image.
+ * @private
+ */
+Blockly.createDom_ = function(container, options) {
+  // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
+  // out content in RTL mode.  Therefore Blockly forces the use of LTR,
+  // then manually positions content in RTL as needed.
+  container.setAttribute('dir', 'LTR');
+  // Closure can be trusted to create HTML widgets with the proper direction.
+  goog.ui.Component.setDefaultRightToLeft(options.RTL);
+
+  // Load CSS.
+  Blockly.Css.inject(options.hasCss, options.pathToMedia);
+
+  // Build the SVG DOM.
+  /*
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:html="http://www.w3.org/1999/xhtml"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    version="1.1"
+    class="blocklySvg">
+    ...
+  </svg>
+  */
+  var svg = Blockly.createSvgElement('svg', {
+    'xmlns': 'http://www.w3.org/2000/svg',
+    'xmlns:html': 'http://www.w3.org/1999/xhtml',
+    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
+    'version': '1.1',
+    'class': 'blocklySvg'
+  }, container);
+  /*
+  <defs>
+    ... filters go here ...
+  </defs>
+  */
+  var defs = Blockly.createSvgElement('defs', {}, svg);
+  var rnd = String(Math.random()).substring(2);
+  /*
+    <filter id="blocklyEmbossFilter837493">
+      <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur"/>
+      <feSpecularLighting in="blur" surfaceScale="1" specularConstant="0.5"
+                          specularExponent="10" lighting-color="white"
+                          result="specOut">
+        <fePointLight x="-5000" y="-10000" z="20000"/>
+      </feSpecularLighting>
+      <feComposite in="specOut" in2="SourceAlpha" operator="in"
+                   result="specOut"/>
+      <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic"
+                   k1="0" k2="1" k3="1" k4="0"/>
+    </filter>
+  */
+  var embossFilter = Blockly.createSvgElement('filter',
+      {'id': 'blocklyEmbossFilter' + rnd}, defs);
+  Blockly.createSvgElement('feGaussianBlur',
+      {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter);
+  var feSpecularLighting = Blockly.createSvgElement('feSpecularLighting',
+      {'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5,
+       'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'},
+      embossFilter);
+  Blockly.createSvgElement('fePointLight',
+      {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting);
+  Blockly.createSvgElement('feComposite',
+      {'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in',
+       'result': 'specOut'}, embossFilter);
+  Blockly.createSvgElement('feComposite',
+      {'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic',
+       'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter);
+  options.embossFilterId = embossFilter.id;
+  /*
+    <pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
+             width="10" height="10">
+      <rect width="10" height="10" fill="#aaa" />
+      <path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="#cc0" />
+    </pattern>
+  */
+  var disabledPattern = Blockly.createSvgElement('pattern',
+      {'id': 'blocklyDisabledPattern' + rnd,
+       'patternUnits': 'userSpaceOnUse',
+       'width': 10, 'height': 10}, defs);
+  Blockly.createSvgElement('rect',
+      {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
+  Blockly.createSvgElement('path',
+      {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
+  options.disabledPatternId = disabledPattern.id;
+  /*
+    <pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
+      <rect stroke="#888" />
+      <rect stroke="#888" />
+    </pattern>
+  */
+  var gridPattern = Blockly.createSvgElement('pattern',
+      {'id': 'blocklyGridPattern' + rnd,
+       'patternUnits': 'userSpaceOnUse'}, defs);
+  if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
+    Blockly.createSvgElement('line',
+        {'stroke': options.gridOptions['colour']},
+        gridPattern);
+    if (options.gridOptions['length'] > 1) {
+      Blockly.createSvgElement('line',
+          {'stroke': options.gridOptions['colour']},
+          gridPattern);
+    }
+    // x1, y1, x1, x2 properties will be set later in updateGridPattern_.
+  }
+  options.gridPattern = gridPattern;
+  return svg;
+};
+
+/**
+ * Create a main workspace and add it to the SVG.
+ * @param {!Element} svg SVG element with pattern defined.
+ * @param {!Blockly.Options} options Dictionary of options.
+ * @return {!Blockly.Workspace} Newly created main workspace.
+ * @private
+ */
+Blockly.createMainWorkspace_ = function(svg, options) {
+  options.parentWorkspace = null;
+  options.getMetrics = Blockly.getMainWorkspaceMetrics_;
+  options.setMetrics = Blockly.setMainWorkspaceMetrics_;
+  var mainWorkspace = new Blockly.WorkspaceSvg(options);
+  mainWorkspace.scale = options.zoomOptions.startScale;
+  svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
+  // A null translation will also apply the correct initial scale.
+  mainWorkspace.translate(0, 0);
+  mainWorkspace.markFocused();
+
+  if (!options.readOnly && !options.hasScrollbars) {
+    var workspaceChanged = function() {
+      if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
+        var metrics = mainWorkspace.getMetrics();
+        var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
+        var edgeTop = metrics.viewTop + metrics.absoluteTop;
+        if (metrics.contentTop < edgeTop ||
+            metrics.contentTop + metrics.contentHeight >
+            metrics.viewHeight + edgeTop ||
+            metrics.contentLeft <
+                (options.RTL ? metrics.viewLeft : edgeLeft) ||
+            metrics.contentLeft + metrics.contentWidth > (options.RTL ?
+                metrics.viewWidth : metrics.viewWidth + edgeLeft)) {
+          // One or more blocks may be out of bounds.  Bump them back in.
+          var MARGIN = 25;
+          var blocks = mainWorkspace.getTopBlocks(false);
+          for (var b = 0, block; block = blocks[b]; b++) {
+            var blockXY = block.getRelativeToSurfaceXY();
+            var blockHW = block.getHeightWidth();
+            // Bump any block that's above the top back inside.
+            var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y;
+            if (overflowTop > 0) {
+              block.moveBy(0, overflowTop);
+            }
+            // Bump any block that's below the bottom back inside.
+            var overflowBottom =
+                edgeTop + metrics.viewHeight - MARGIN - blockXY.y;
+            if (overflowBottom < 0) {
+              block.moveBy(0, overflowBottom);
+            }
+            // Bump any block that's off the left back inside.
+            var overflowLeft = MARGIN + edgeLeft -
+                blockXY.x - (options.RTL ? 0 : blockHW.width);
+            if (overflowLeft > 0) {
+              block.moveBy(overflowLeft, 0);
+            }
+            // Bump any block that's off the right back inside.
+            var overflowRight = edgeLeft + metrics.viewWidth - MARGIN -
+                blockXY.x + (options.RTL ? blockHW.width : 0);
+            if (overflowRight < 0) {
+              block.moveBy(overflowRight, 0);
+            }
+          }
+        }
+      }
+    };
+    mainWorkspace.addChangeListener(workspaceChanged);
+  }
+  // The SVG is now fully assembled.
+  Blockly.svgResize(mainWorkspace);
+  Blockly.WidgetDiv.createDom();
+  Blockly.Tooltip.createDom();
+  return mainWorkspace;
+};
+
+/**
+ * Initialize Blockly with various handlers.
+ * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace.
+ * @private
+ */
+Blockly.init_ = function(mainWorkspace) {
+  var options = mainWorkspace.options;
+  var svg = mainWorkspace.getParentSvg();
+
+  // Supress the browser's context menu.
+  // Note: for blockscad, we have to suppress context menus on the whole document
+  // not just the svg for some reason.??
+  Blockly.bindEvent_(document, 'contextmenu', null,
+      function(e) {
+        if (!Blockly.isTargetInput_(e)) {
+          e.preventDefault();
+        }
+      });
+
+  var workspaceResizeHandler = Blockly.bindEvent_(window, 'resize', null,
+       function() {
+         Blockly.hideChaff(true);
+         Blockly.svgResize(mainWorkspace);
+       });
+  mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);
+
+  Blockly.inject.bindDocumentEvents_();
+
+  if (options.languageTree) {
+    if (mainWorkspace.toolbox_) {
+      mainWorkspace.toolbox_.init(mainWorkspace);
+    } else if (mainWorkspace.flyout_) {
+      // Build a fixed flyout with the root blocks.
+      mainWorkspace.flyout_.init(mainWorkspace);
+      mainWorkspace.flyout_.show(options.languageTree.childNodes);
+      mainWorkspace.flyout_.scrollToStart();
+      // Translate the workspace sideways to avoid the fixed flyout.
+      mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
+      if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
+        mainWorkspace.scrollX *= -1;
+      }
+      mainWorkspace.translate(mainWorkspace.scrollX, 0);
+    }
+  }
+
+  if (options.hasScrollbars) {
+    mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace);
+    mainWorkspace.scrollbar.resize();
+  }
+
+  // Load the sounds.
+  if (options.hasSounds) {
+    Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace);
+  }
+};
+
+/**
+ * Bind document events, but only once.  Destroying and reinjecting Blockly
+ * should not bind again.
+ * Bind events for scrolling the workspace.
+ * Most of these events should be bound to the SVG's surface.
+ * However, 'mouseup' has to be on the whole document so that a block dragged
+ * out of bounds and released will know that it has been released.
+ * Also, 'keydown' has to be on the whole document since the browser doesn't
+ * understand a concept of focus on the SVG image.
+ * @private
+ */
+Blockly.inject.bindDocumentEvents_ = function() {
+  if (!Blockly.documentEventsBound_) {
+    Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_);
+    Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
+    Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_);
+    // Don't use bindEvent_ for document's mouseup since that would create a
+    // corresponding touch handler that would squeltch the ability to interact
+    // with non-Blockly elements.
+    document.addEventListener('mouseup', Blockly.onMouseUp_, false);
+    // Some iPad versions don't fire resize after portrait to landscape change.
+    if (goog.userAgent.IPAD) {
+      Blockly.bindEvent_(window, 'orientationchange', document, function() {
+        // TODO(#397): Fix for multiple blockly workspaces.
+        Blockly.svgResize(Blockly.getMainWorkspace());
+      });
+    }
+  }
+  Blockly.documentEventsBound_ = true;
+};
+
+/**
+ * Load sounds for the given workspace.
+ * @param {string} pathToMedia The path to the media directory.
+ * @param {!Blockly.Workspace} workspace The workspace to load sounds for.
+ * @private
+ */
+Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
+  workspace.loadAudio_(
+      [pathToMedia + 'click.mp3',
+       pathToMedia + 'click.wav',
+       pathToMedia + 'click.ogg'], 'click');
+  workspace.loadAudio_(
+      [pathToMedia + 'disconnect.wav',
+       pathToMedia + 'disconnect.mp3',
+       pathToMedia + 'disconnect.ogg'], 'disconnect');
+  workspace.loadAudio_(
+      [pathToMedia + 'delete.mp3',
+       pathToMedia + 'delete.ogg',
+       pathToMedia + 'delete.wav'], 'delete');
+
+  // Bind temporary hooks that preload the sounds.
+  var soundBinds = [];
+  var unbindSounds = function() {
+    while (soundBinds.length) {
+      Blockly.unbindEvent_(soundBinds.pop());
+    }
+    workspace.preloadAudio_();
+  };
+  // Android ignores any sound not loaded as a result of a user action.
+  soundBinds.push(
+      Blockly.bindEvent_(document, 'mousemove', null, unbindSounds));
+  soundBinds.push(
+      Blockly.bindEvent_(document, 'touchstart', null, unbindSounds));
+};
+
+/**
+ * Modify the block tree on the existing toolbox.
+ * @param {Node|string} tree DOM tree of blocks, or text representation of same.
+ */
+Blockly.updateToolbox = function(tree) {
+  console.warn('Deprecated call to Blockly.updateToolbox, ' +
+               'use workspace.updateToolbox instead.');
+  Blockly.getMainWorkspace().updateToolbox(tree);
+};

+ 241 - 0
blockly/core/input.js

@@ -0,0 +1,241 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Object representing an input (value, statement, or dummy).
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Input');
+
+goog.require('Blockly.Connection');
+goog.require('Blockly.FieldLabel');
+goog.require('goog.asserts');
+
+
+/**
+ * Class for an input with an optional field.
+ * @param {number} type The type of the input.
+ * @param {string} name Language-neutral identifier which may used to find this
+ *     input again.
+ * @param {!Blockly.Block} block The block containing this input.
+ * @param {Blockly.Connection} connection Optional connection for this input.
+ * @constructor
+ */
+Blockly.Input = function(type, name, block, connection) {
+  /** @type {number} */
+  this.type = type;
+  /** @type {string} */
+  this.name = name;
+  /**
+   * @type {!Blockly.Block}
+   * @private
+   */
+  this.sourceBlock_ = block;
+  /** @type {Blockly.Connection} */
+  this.connection = connection;
+  /** @type {!Array.<!Blockly.Field>} */
+  this.fieldRow = [];
+};
+
+/**
+ * Alignment of input's fields (left, right or centre).
+ * @type {number}
+ */
+Blockly.Input.prototype.align = Blockly.ALIGN_LEFT;
+
+/**
+ * Is the input visible?
+ * @type {boolean}
+ * @private
+ */
+Blockly.Input.prototype.visible_ = true;
+
+/**
+ * Add an item to the end of the input's field row.
+ * @param {string|!Blockly.Field} field Something to add as a field.
+ * @param {string=} opt_name Language-neutral identifier which may used to find
+ *     this field again.  Should be unique to the host block.
+ * @return {!Blockly.Input} The input being append to (to allow chaining).
+ */
+Blockly.Input.prototype.appendField = function(field, opt_name) {
+  // Empty string, Null or undefined generates no field, unless field is named.
+  if (!field && !opt_name) {
+    return this;
+  }
+  // Generate a FieldLabel when given a plain text field.
+  if (goog.isString(field)) {
+    field = new Blockly.FieldLabel(/** @type {string} */ (field));
+  }
+  field.setSourceBlock(this.sourceBlock_);
+  if (this.sourceBlock_.rendered) {
+    field.init();
+  }
+  field.name = opt_name;
+
+  if (field.prefixField) {
+    // Add any prefix.
+    this.appendField(field.prefixField);
+  }
+  // Add the field to the field row.
+  this.fieldRow.push(field);
+  if (field.suffixField) {
+    // Add any suffix.
+    this.appendField(field.suffixField);
+  }
+
+  if (this.sourceBlock_.rendered) {
+    this.sourceBlock_.render();
+    // Adding a field will cause the block to change shape.
+    this.sourceBlock_.bumpNeighbours_();
+  }
+  return this;
+};
+
+/**
+ * Add an item to the end of the input's field row.
+ * @param {*} field Something to add as a field.
+ * @param {string=} opt_name Language-neutral identifier which may used to find
+ *     this field again.  Should be unique to the host block.
+ * @return {!Blockly.Input} The input being append to (to allow chaining).
+ * @deprecated December 2013
+ */
+Blockly.Input.prototype.appendTitle = function(field, opt_name) {
+  console.warn('Deprecated call to appendTitle, use appendField instead.');
+  return this.appendField(field, opt_name);
+};
+
+/**
+ * Remove a field from this input.
+ * @param {string} name The name of the field.
+ * @throws {goog.asserts.AssertionError} if the field is not present.
+ */
+Blockly.Input.prototype.removeField = function(name) {
+  for (var i = 0, field; field = this.fieldRow[i]; i++) {
+    if (field.name === name) {
+      field.dispose();
+      this.fieldRow.splice(i, 1);
+      if (this.sourceBlock_.rendered) {
+        this.sourceBlock_.render();
+        // Removing a field will cause the block to change shape.
+        this.sourceBlock_.bumpNeighbours_();
+      }
+      return;
+    }
+  }
+  goog.asserts.fail('Field "%s" not found.', name);
+};
+
+/**
+ * Gets whether this input is visible or not.
+ * @return {boolean} True if visible.
+ */
+Blockly.Input.prototype.isVisible = function() {
+  return this.visible_;
+};
+
+/**
+ * Sets whether this input is visible or not.
+ * Used to collapse/uncollapse a block.
+ * @param {boolean} visible True if visible.
+ * @return {!Array.<!Blockly.Block>} List of blocks to render.
+ */
+Blockly.Input.prototype.setVisible = function(visible) {
+  var renderList = [];
+  if (this.visible_ == visible) {
+    return renderList;
+  }
+  this.visible_ = visible;
+
+  var display = visible ? 'block' : 'none';
+  for (var y = 0, field; field = this.fieldRow[y]; y++) {
+    field.setVisible(visible);
+  }
+  if (this.connection) {
+    // Has a connection.
+    if (visible) {
+      renderList = this.connection.unhideAll();
+    } else {
+      this.connection.hideAll();
+    }
+    var child = this.connection.targetBlock();
+    if (child) {
+      child.getSvgRoot().style.display = display;
+      if (!visible) {
+        child.rendered = false;
+      }
+    }
+  }
+  return renderList;
+};
+
+/**
+ * Change a connection's compatibility.
+ * @param {string|Array.<string>|null} check Compatible value type or
+ *     list of value types.  Null if all types are compatible.
+ * @return {!Blockly.Input} The input being modified (to allow chaining).
+ */
+Blockly.Input.prototype.setCheck = function(check) {
+  if (!this.connection) {
+    throw 'This input does not have a connection.';
+  }
+  this.connection.setCheck(check);
+  return this;
+};
+
+/**
+ * Change the alignment of the connection's field(s).
+ * @param {number} align One of Blockly.ALIGN_LEFT, ALIGN_CENTRE, ALIGN_RIGHT.
+ *   In RTL mode directions are reversed, and ALIGN_RIGHT aligns to the left.
+ * @return {!Blockly.Input} The input being modified (to allow chaining).
+ */
+Blockly.Input.prototype.setAlign = function(align) {
+  this.align = align;
+  if (this.sourceBlock_.rendered) {
+    this.sourceBlock_.render();
+  }
+  return this;
+};
+
+/**
+ * Initialize the fields on this input.
+ */
+Blockly.Input.prototype.init = function() {
+  if (!this.sourceBlock_.workspace.rendered) {
+    return;  // Headless blocks don't need fields initialized.
+  }
+  for (var i = 0; i < this.fieldRow.length; i++) {
+    this.fieldRow[i].init();
+  }
+};
+
+/**
+ * Sever all links to this input.
+ */
+Blockly.Input.prototype.dispose = function() {
+  for (var i = 0, field; field = this.fieldRow[i]; i++) {
+    field.dispose();
+  }
+  if (this.connection) {
+    this.connection.dispose();
+  }
+  this.sourceBlock_ = null;
+};

+ 62 - 0
blockly/core/msg.js

@@ -0,0 +1,62 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2013 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 Empty name space for the Message singleton.
+ * @author scr@google.com (Sheridan Rawlins)
+ */
+'use strict';
+
+/**
+ * Name space for the Msg singleton.
+ * Msg gets populated in the message files.
+ */
+goog.provide('Blockly.Msg');
+
+
+/**
+ * Back up original getMsg function.
+ * @type {!Function}
+ */
+goog.getMsgOrig = goog.getMsg;
+
+/**
+ * Gets a localized message.
+ * Overrides the default Closure function to check for a Blockly.Msg first.
+ * Used infrequently, only known case is TODAY button in date picker.
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object<string, string>=} opt_values Maps place holder name to value.
+ * @return {string} message with placeholders filled.
+ * @suppress {duplicate}
+ */
+goog.getMsg = function(str, opt_values) {
+  var key = goog.getMsg.blocklyMsgMap[str];
+  if (key) {
+    str = Blockly.Msg[key];
+  }
+  return goog.getMsgOrig(str, opt_values);
+};
+
+/**
+ * Mapping of Closure messages to Blockly.Msg names.
+ */
+goog.getMsg.blocklyMsgMap = {
+  'Today': 'TODAY'
+};

+ 388 - 0
blockly/core/mutator.js

@@ -0,0 +1,388 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Object representing a mutator dialog.  A mutator allows the
+ * user to change the shape of a block using a nested blocks editor.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Mutator');
+
+goog.require('Blockly.Bubble');
+goog.require('Blockly.Icon');
+goog.require('Blockly.WorkspaceSvg');
+goog.require('goog.Timer');
+goog.require('goog.dom');
+
+
+/**
+ * Class for a mutator dialog.
+ * @param {!Array.<string>} quarkNames List of names of sub-blocks for flyout.
+ * @extends {Blockly.Icon}
+ * @constructor
+ */
+Blockly.Mutator = function(quarkNames) {
+  Blockly.Mutator.superClass_.constructor.call(this, null);
+  this.quarkNames_ = quarkNames;
+};
+goog.inherits(Blockly.Mutator, Blockly.Icon);
+
+/**
+ * Width of workspace.
+ * @private
+ */
+Blockly.Mutator.prototype.workspaceWidth_ = 0;
+
+/**
+ * Height of workspace.
+ * @private
+ */
+Blockly.Mutator.prototype.workspaceHeight_ = 0;
+
+/**
+ * Draw the mutator icon.
+ * @param {!Element} group The icon group.
+ * @private
+ */
+Blockly.Mutator.prototype.drawIcon_ = function(group) {
+  // Square with rounded corners.
+  Blockly.createSvgElement('rect',
+      {'class': 'blocklyIconShape',
+       'rx': '4', 'ry': '4',
+       'height': '16', 'width': '16'},
+       group);
+  // Gear teeth.
+  Blockly.createSvgElement('path',
+      {'class': 'blocklyIconSymbol',
+       'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 -0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z'},
+       group);
+  // Axle hole.
+  Blockly.createSvgElement('circle',
+      {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'},
+       group);
+};
+
+/**
+ * Clicking on the icon toggles if the mutator bubble is visible.
+ * Disable if block is uneditable.
+ * @param {!Event} e Mouse click event.
+ * @private
+ * @override
+ */
+Blockly.Mutator.prototype.iconClick_ = function(e) {
+  if (this.block_.isEditable()) {
+    Blockly.Icon.prototype.iconClick_.call(this, e);
+  }
+};
+
+/**
+ * Create the editor for the mutator's bubble.
+ * @return {!Element} The top-level node of the editor.
+ * @private
+ */
+Blockly.Mutator.prototype.createEditor_ = function() {
+  /* Create the editor.  Here's the markup that will be generated:
+  <svg>
+    [Workspace]
+  </svg>
+  */
+  this.svgDialog_ = Blockly.createSvgElement('svg',
+      {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
+      null);
+  // Convert the list of names into a list of XML objects for the flyout.
+  if (this.quarkNames_.length) {
+    var quarkXml = goog.dom.createDom('xml');
+    for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) {
+      quarkXml.appendChild(goog.dom.createDom('block', {'type': quarkName}));
+    }
+  } else {
+    var quarkXml = null;
+  }
+  var workspaceOptions = {
+    languageTree: quarkXml,
+    parentWorkspace: this.block_.workspace,
+    pathToMedia: this.block_.workspace.options.pathToMedia,
+    RTL: this.block_.RTL,
+    toolboxPosition: this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT :
+        Blockly.TOOLBOX_AT_LEFT,
+    horizontalLayout: false,
+    getMetrics: this.getFlyoutMetrics_.bind(this),
+    setMetrics: null
+  };
+  this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
+  this.svgDialog_.appendChild(
+      this.workspace_.createDom('blocklyMutatorBackground'));
+  return this.svgDialog_;
+};
+
+/**
+ * Add or remove the UI indicating if this icon may be clicked or not.
+ */
+Blockly.Mutator.prototype.updateEditable = function() {
+  if (!this.block_.isInFlyout) {
+    if (this.block_.isEditable()) {
+      if (this.iconGroup_) {
+        Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_),
+                             'blocklyIconGroupReadonly');
+      }
+    } else {
+      // Close any mutator bubble.  Icon is not clickable.
+      this.setVisible(false);
+      if (this.iconGroup_) {
+        Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
+                          'blocklyIconGroupReadonly');
+      }
+    }
+  }
+  // Default behaviour for an icon.
+  Blockly.Icon.prototype.updateEditable.call(this);
+};
+
+/**
+ * Callback function triggered when the bubble has resized.
+ * Resize the workspace accordingly.
+ * @private
+ */
+Blockly.Mutator.prototype.resizeBubble_ = function() {
+  var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
+  var workspaceSize = this.workspace_.getCanvas().getBBox();
+  var width;
+  if (this.block_.RTL) {
+    width = -workspaceSize.x;
+  } else {
+    width = workspaceSize.width + workspaceSize.x;
+  }
+  var height = workspaceSize.height + doubleBorderWidth * 3;
+  if (this.workspace_.flyout_) {
+    var flyoutMetrics = this.workspace_.flyout_.getMetrics_();
+    height = Math.max(height, flyoutMetrics.contentHeight + 20);
+  }
+  width += doubleBorderWidth * 3;
+  // Only resize if the size difference is significant.  Eliminates shuddering.
+  if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth ||
+      Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) {
+    // Record some layout information for getFlyoutMetrics_.
+    this.workspaceWidth_ = width;
+    this.workspaceHeight_ = height;
+    // Resize the bubble.
+    this.bubble_.setBubbleSize(width + doubleBorderWidth,
+                               height + doubleBorderWidth);
+    this.svgDialog_.setAttribute('width', this.workspaceWidth_);
+    this.svgDialog_.setAttribute('height', this.workspaceHeight_);
+  }
+
+  if (this.block_.RTL) {
+    // Scroll the workspace to always left-align.
+    var translation = 'translate(' + this.workspaceWidth_ + ',0)';
+    this.workspace_.getCanvas().setAttribute('transform', translation);
+  }
+  this.workspace_.resize();
+};
+
+/**
+ * Show or hide the mutator bubble.
+ * @param {boolean} visible True if the bubble should be visible.
+ */
+Blockly.Mutator.prototype.setVisible = function(visible) {
+  if (visible == this.isVisible()) {
+    // No change.
+    return;
+  }
+  Blockly.Events.fire(
+      new Blockly.Events.Ui(this.block_, 'mutatorOpen', !visible, visible));
+  if (visible) {
+    // Create the bubble.
+    this.bubble_ = new Blockly.Bubble(
+        /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
+        this.createEditor_(), this.block_.svgPath_, this.iconXY_, null, null);
+    var tree = this.workspace_.options.languageTree;
+    if (tree) {
+      this.workspace_.flyout_.init(this.workspace_);
+      this.workspace_.flyout_.show(tree.childNodes);
+    }
+
+    this.rootBlock_ = this.block_.decompose(this.workspace_);
+    var blocks = this.rootBlock_.getDescendants();
+    for (var i = 0, child; child = blocks[i]; i++) {
+      child.render();
+    }
+    // The root block should not be dragable or deletable.
+    this.rootBlock_.setMovable(false);
+    this.rootBlock_.setDeletable(false);
+    if (this.workspace_.flyout_) {
+      var margin = this.workspace_.flyout_.CORNER_RADIUS * 2;
+      var x = this.workspace_.flyout_.width_ + margin;
+    } else {
+      var margin = 16;
+      var x = margin;
+    }
+    if (this.block_.RTL) {
+      x = -x;
+    }
+    this.rootBlock_.moveBy(x, margin);
+    // Save the initial connections, then listen for further changes.
+    if (this.block_.saveConnections) {
+      var thisMutator = this;
+      this.block_.saveConnections(this.rootBlock_);
+      this.sourceListener_ = function() {
+        thisMutator.block_.saveConnections(thisMutator.rootBlock_);
+      };
+      this.block_.workspace.addChangeListener(this.sourceListener_);
+    }
+    this.resizeBubble_();
+    // When the mutator's workspace changes, update the source block.
+    this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));
+    this.updateColour();
+  } else {
+    // Dispose of the bubble.
+    this.svgDialog_ = null;
+    this.workspace_.dispose();
+    this.workspace_ = null;
+    this.rootBlock_ = null;
+    this.bubble_.dispose();
+    this.bubble_ = null;
+    this.workspaceWidth_ = 0;
+    this.workspaceHeight_ = 0;
+    if (this.sourceListener_) {
+      this.block_.workspace.removeChangeListener(this.sourceListener_);
+      this.sourceListener_ = null;
+    }
+  }
+};
+
+/**
+ * Update the source block when the mutator's blocks are changed.
+ * Bump down any block that's too high.
+ * Fired whenever a change is made to the mutator's workspace.
+ * @private
+ */
+Blockly.Mutator.prototype.workspaceChanged_ = function() {
+  if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
+    var blocks = this.workspace_.getTopBlocks(false);
+    var MARGIN = 20;
+    for (var b = 0, block; block = blocks[b]; b++) {
+      var blockXY = block.getRelativeToSurfaceXY();
+      var blockHW = block.getHeightWidth();
+      if (blockXY.y + blockHW.height < MARGIN) {
+        // Bump any block that's above the top back inside.
+        block.moveBy(0, MARGIN - blockHW.height - blockXY.y);
+      }
+    }
+  }
+
+  // When the mutator's workspace changes, update the source block.
+  if (this.rootBlock_.workspace == this.workspace_) {
+    Blockly.Events.setGroup(true);
+    var block = this.block_;
+    var oldMutationDom = block.mutationToDom();
+    var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
+    // Switch off rendering while the source block is rebuilt.
+    var savedRendered = block.rendered;
+    block.rendered = false;
+    // Allow the source block to rebuild itself.
+    block.compose(this.rootBlock_);
+    // Restore rendering and show the changes.
+    block.rendered = savedRendered;
+    // Mutation may have added some elements that need initalizing.
+    block.initSvg();
+    var newMutationDom = block.mutationToDom();
+    var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
+    if (oldMutation != newMutation) {
+      Blockly.Events.fire(new Blockly.Events.Change(
+          block, 'mutation', null, oldMutation, newMutation));
+      // Ensure that any bump is part of this mutation's event group.
+      var group = Blockly.Events.getGroup();
+      setTimeout(function() {
+        Blockly.Events.setGroup(group);
+        block.bumpNeighbours_();
+        Blockly.Events.setGroup(false);
+      }, Blockly.BUMP_DELAY);
+    }
+    if (block.rendered) {
+      block.render();
+    }
+    this.resizeBubble_();
+    Blockly.Events.setGroup(false);
+  }
+};
+
+/**
+ * Return an object with all the metrics required to size scrollbars for the
+ * mutator flyout.  The following properties are computed:
+ * .viewHeight: Height of the visible rectangle,
+ * .viewWidth: Width of the visible rectangle,
+ * .absoluteTop: Top-edge of view.
+ * .absoluteLeft: Left-edge of view.
+ * @return {!Object} Contains size and position metrics of mutator dialog's
+ *     workspace.
+ * @private
+ */
+Blockly.Mutator.prototype.getFlyoutMetrics_ = function() {
+  return {
+    viewHeight: this.workspaceHeight_,
+    viewWidth: this.workspaceWidth_,
+    absoluteTop: 0,
+    absoluteLeft: 0
+  };
+};
+
+/**
+ * Dispose of this mutator.
+ */
+Blockly.Mutator.prototype.dispose = function() {
+  this.block_.mutator = null;
+  Blockly.Icon.prototype.dispose.call(this);
+};
+
+/**
+ * Reconnect an block to a mutated input.
+ * @param {Blockly.Connection} connectionChild Connection on child block.
+ * @param {!Blockly.Block} block Parent block.
+ * @param {string} inputName Name of input on parent block.
+ * @return {boolean} True iff a reconnection was made, false otherwise.
+ */
+Blockly.Mutator.reconnect = function(connectionChild, block, inputName) {
+  if (!connectionChild || !connectionChild.getSourceBlock().workspace) {
+    return false;  // No connection or block has been deleted.
+  }
+  var connectionParent = block.getInput(inputName).connection;
+  var currentParent = connectionChild.targetBlock();
+  if ((!currentParent || currentParent == block) &&
+      connectionParent.targetConnection != connectionChild) {
+    if (connectionParent.isConnected()) {
+      // There's already something connected here.  Get rid of it.
+      connectionParent.disconnect();
+    }
+    connectionParent.connect(connectionChild);
+    return true;
+  }
+  return false;
+};
+
+// Export symbols that would otherwise be renamed by Closure compiler.
+if (!goog.global['Blockly']) {
+  goog.global['Blockly'] = {};
+}
+if (!goog.global['Blockly']['Mutator']) {
+  goog.global['Blockly']['Mutator'] = {};
+}
+goog.global['Blockly']['Mutator']['reconnect'] = Blockly.Mutator.reconnect;

+ 60 - 0
blockly/core/mutator_minus.js

@@ -0,0 +1,60 @@
+'use strict';
+
+goog.provide('Blockly.MutatorMinus');
+
+goog.require('Blockly.Mutator');
+goog.require('Blockly.Bubble');
+goog.require('Blockly.Icon');
+goog.require('goog.dom');
+
+Blockly.MutatorMinus = function(quarkNames) {
+    Blockly.MutatorMinus.superClass_.constructor.call(this, this, null);
+};
+goog.inherits(Blockly.MutatorMinus, Blockly.Mutator,Blockly.Icon);
+
+Blockly.MutatorMinus.prototype.clicked_ = false;
+
+/**
+ * Icon in base64 format.
+ * @private
+ */
+// Blockly.Mutator.prototype.png_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAANyAAADcgBffIlqAAAAAd0SU1FB98DGhULOIajyb8AAAHMSURBVDjLnZS9SiRBFIXP/CQ9iIHgPoGBTo8vIAaivoKaKJr6DLuxYqKYKIqRgSCMrblmIxqsICgOmAriziIiRXWjYPdnUDvT2+PMsOyBoop7qk71vedWS5KAkrWsGUMjSYjpgSQhNoZGFLEKeGoKGMNttUpULkOhAFL3USiA70MQEBnDDeDJWtaqVaJeB7uNICAKQ1ZkDI1yufOm+XnY2YHl5c6874MxPClJiDulkMvBxYWrw/095POdU0sS4hxALqcWtreloSGpVJLGxtL49bX0+Ci9vUkzM2kcXGFbypUKxHHLBXZ3YW4ONjfh4yN1aGIiPQOQEenrg6MjR+zvZz99Y8PFT09hYCArktdfsFY6PHTr83NlUKu5+eREennJchmR/n5pYcGtJyezG6em3Dw7Kw0OZrlMOr6f1gTg4ACWlmBvz9WoifHxbDpf3Flfd+54njQ9ncYvL6WHB+n9XVpcbHOnW59IUKu5m+p11zftfLHo+qRorZ6Hh/Xt7k5fsLUl1evS1dWfG9swMiJZq9+KIlaD4P/eztkZNgz5LsAzhpvjY6JK5d9e8eioE3h95SdQbDrkhSErxvArjkl6/U/imMQYnsKQH02BT7vbZZfVOiWhAAAAAElFTkSuQmCC';
+
+/**
+ * Create the icon on the block.
+ */
+Blockly.MutatorMinus.prototype.createIcon = function() {
+  if (this.iconMark_) {
+    // Icon already exists.
+    return;
+  }
+  Blockly.Icon.prototype.createIconOld.call(this);
+  Blockly.Icon.radius = 8;
+  /* Here's the markup that will be generated:
+  <rect class="blocklyIconShield" width="16" height="16" rx="4" ry="4"/>
+  <text class="blocklyIconMark" x="8" y="12">+</text>
+  */
+  var quantum = Blockly.Icon.radius / 2;
+  var iconShield = Blockly.createSvgElement('rect',
+      {'class': 'blocklyIconShield',
+       'width': 4 * quantum,
+       'height': 4 * quantum,
+       'rx': quantum,
+       'ry': quantum}, this.iconGroup_);
+  this.iconMark_ = Blockly.createSvgElement('text',
+      {'class': 'blocklyIconMark',
+       'x': Blockly.Icon.radius,
+       'y': 2 * Blockly.Icon.radius - 4}, this.iconGroup_);
+  this.iconMark_.appendChild(document.createTextNode('\u2212'));
+};
+
+
+Blockly.MutatorMinus.prototype.iconClick_ = function(e) {
+  if (Blockly.dragMode_ == 2) {
+    // Drag operation is concluding.  Don't activate the mutator.
+    return;
+  }
+  if (this.block_.isEditable()) {
+      this.block_.updateShape_(-1);
+  }
+};

+ 62 - 0
blockly/core/mutator_plus.js

@@ -0,0 +1,62 @@
+'use strict';
+
+goog.provide('Blockly.MutatorPlus');
+
+goog.require('Blockly.Mutator');
+goog.require('Blockly.Bubble');
+goog.require('Blockly.Icon');
+goog.require('goog.Timer');
+goog.require('goog.dom');
+
+Blockly.MutatorPlus = function(quarkNames) {
+    Blockly.MutatorPlus.superClass_.constructor.call(this, this, null);
+};
+goog.inherits(Blockly.MutatorPlus, Blockly.Mutator, Blockly.Icon);
+
+Blockly.MutatorPlus.prototype.clicked_ = false;
+
+/**
+ * Icon in base64 format.
+ * @private
+ */
+//Blockly.Mutator.prototype.png_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAANyAAADcgBffIlqAAAAAd0SU1FB98DGhULOIajyb8AAAHMSURBVDjLnZS9SiRBFIXP/CQ9iIHgPoGBTo8vIAaivoKaKJr6DLuxYqKYKIqRgSCMrblmIxqsICgOmAriziIiRXWjYPdnUDvT2+PMsOyBoop7qk71vedWS5KAkrWsGUMjSYjpgSQhNoZGFLEKeGoKGMNttUpULkOhAFL3USiA70MQEBnDDeDJWtaqVaJeB7uNICAKQ1ZkDI1yufOm+XnY2YHl5c6874MxPClJiDulkMvBxYWrw/095POdU0sS4hxALqcWtreloSGpVJLGxtL49bX0+Ci9vUkzM2kcXGFbypUKxHHLBXZ3YW4ONjfh4yN1aGIiPQOQEenrg6MjR+zvZz99Y8PFT09hYCArktdfsFY6PHTr83NlUKu5+eREennJchmR/n5pYcGtJyezG6em3Dw7Kw0OZrlMOr6f1gTg4ACWlmBvz9WoifHxbDpf3Flfd+54njQ9ncYvL6WHB+n9XVpcbHOnW59IUKu5m+p11zftfLHo+qRorZ6Hh/Xt7k5fsLUl1evS1dWfG9swMiJZq9+KIlaD4P/eztkZNgz5LsAzhpvjY6JK5d9e8eioE3h95SdQbDrkhSErxvArjkl6/U/imMQYnsKQH02BT7vbZZfVOiWhAAAAAElFTkSuQmCC';
+
+/**
+ * Create the icon on the block.
+ */
+Blockly.MutatorPlus.prototype.createIcon = function() {
+  if (this.iconMark_) {
+    // Icon already exists.
+    return;
+  }
+  Blockly.Icon.prototype.createIconOld.call(this);
+  Blockly.Icon.radius = 8;
+  /* Here's the markup that will be generated:
+  <rect class="blocklyIconShield" width="16" height="16" rx="4" ry="4"/>
+  <text class="blocklyIconMark" x="8" y="12">+</text>
+  */
+  var quantum = Blockly.Icon.radius / 2;
+  var iconShield = Blockly.createSvgElement('rect',
+      {'class': 'blocklyIconShield',
+       'width': 4 * quantum,
+       'height': 4 * quantum,
+       'rx': quantum,
+       'ry': quantum}, this.iconGroup_);
+  this.iconMark_ = Blockly.createSvgElement('text',
+      {'class': 'blocklyIconMark',
+       'x': Blockly.Icon.radius,
+       'y': 2 * Blockly.Icon.radius - 4}, this.iconGroup_);
+  this.iconMark_.appendChild(document.createTextNode('\u002b'));
+};
+
+Blockly.MutatorPlus.prototype.iconClick_ = function(e) {
+  if (Blockly.dragMode_ == 2) {
+    // Drag operation is concluding.  Don't activate the mutator.
+    return;
+  }
+  if (this.block_.isEditable()) {
+      this.block_.updateShape_(1);
+  }
+  goog.Timer.callOnce(
+    this.block_.bumpNeighbours_, Blockly.BUMP_DELAY, this.block_);
+};

+ 143 - 0
blockly/core/names.js

@@ -0,0 +1,143 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Utility functions for handling variables and procedure names.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Names');
+
+
+/**
+ * Class for a database of entity names (variables, functions, etc).
+ * @param {string} reservedWords A comma-separated string of words that are
+ *     illegal for use as names in a language (e.g. 'new,if,this,...').
+ * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace
+ *     before all variable names.
+ * @constructor
+ */
+Blockly.Names = function(reservedWords, opt_variablePrefix) {
+  this.variablePrefix_ = opt_variablePrefix || '';
+  this.reservedDict_ = Object.create(null);
+  if (reservedWords) {
+    var splitWords = reservedWords.split(',');
+    for (var i = 0; i < splitWords.length; i++) {
+      this.reservedDict_[splitWords[i]] = true;
+    }
+  }
+  this.reset();
+};
+
+/**
+ * When JavaScript (or most other languages) is generated, variable 'foo' and
+ * procedure 'foo' would collide.  However, Blockly has no such problems since
+ * variable get 'foo' and procedure call 'foo' are unambiguous.
+ * Therefore, Blockly keeps a separate type name to disambiguate.
+ * getName('foo', 'variable') -> 'foo'
+ * getName('foo', 'procedure') -> 'foo2'
+ */
+
+/**
+ * Empty the database and start from scratch.  The reserved words are kept.
+ */
+Blockly.Names.prototype.reset = function() {
+  this.db_ = Object.create(null);
+  this.dbReverse_ = Object.create(null);
+};
+
+/**
+ * Convert a Blockly entity name to a legal exportable entity name.
+ * @param {string} name The Blockly entity name (no constraints).
+ * @param {string} type The type of entity in Blockly
+ *     ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...).
+ * @return {string} An entity name legal for the exported language.
+ */
+Blockly.Names.prototype.getName = function(name, type) {
+  var normalized = name.toLowerCase() + '_' + type;
+  var prefix = (type == Blockly.Variables.NAME_TYPE) ?
+      this.variablePrefix_ : '';
+  if (normalized in this.db_) {
+    return prefix + this.db_[normalized];
+  }
+  var safeName = this.getDistinctName(name, type);
+  this.db_[normalized] = safeName.substr(prefix.length);
+  return safeName;
+};
+
+/**
+ * Convert a Blockly entity name to a legal exportable entity name.
+ * Ensure that this is a new name not overlapping any previously defined name.
+ * Also check against list of reserved words for the current language and
+ * ensure name doesn't collide.
+ * @param {string} name The Blockly entity name (no constraints).
+ * @param {string} type The type of entity in Blockly
+ *     ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...).
+ * @return {string} An entity name legal for the exported language.
+ */
+Blockly.Names.prototype.getDistinctName = function(name, type) {
+  var safeName = this.safeName_(name);
+  var i = '';
+  while (this.dbReverse_[safeName + i] ||
+         (safeName + i) in this.reservedDict_) {
+    // Collision with existing name.  Create a unique name.
+    i = i ? i + 1 : 2;
+  }
+  safeName += i;
+  this.dbReverse_[safeName] = true;
+  var prefix = (type == Blockly.Variables.NAME_TYPE) ?
+      this.variablePrefix_ : '';
+  return prefix + safeName;
+};
+
+/**
+ * Given a proposed entity name, generate a name that conforms to the
+ * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for
+ * variables.
+ * @param {string} name Potentially illegal entity name.
+ * @return {string} Safe entity name.
+ * @private
+ */
+Blockly.Names.prototype.safeName_ = function(name) {
+  if (!name) {
+    name = 'unnamed';
+  } else {
+    // Unfortunately names in non-latin characters will look like
+    // _E9_9F_B3_E4_B9_90 which is pretty meaningless.
+    name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_');
+    // Most languages don't allow names with leading numbers.
+    if ('0123456789'.indexOf(name[0]) != -1) {
+      name = 'my_' + name;
+    }
+  }
+  return name;
+};
+
+/**
+ * Do the given two entity names refer to the same entity?
+ * Blockly names are case-insensitive.
+ * @param {string} name1 First name.
+ * @param {string} name2 Second name.
+ * @return {boolean} True if names are the same.
+ */
+Blockly.Names.equals = function(name1, name2) {
+  return name1.toLowerCase() == name2.toLowerCase();
+};

+ 233 - 0
blockly/core/options.js

@@ -0,0 +1,233 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2016 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 Object that controls settings for the workspace.
+ * @author fenichel@google.com (Rachel Fenichel)
+ */
+'use strict';
+
+goog.provide('Blockly.Options');
+
+
+/**
+ * Parse the user-specified options, using reasonable defaults where behaviour
+ * is unspecified.
+ * @param {!Object} options Dictionary of options.  Specification:
+ *   https://developers.google.com/blockly/guides/get-started/web#configuration
+ * @constructor
+ */
+Blockly.Options = function(options) {
+  var readOnly = !!options['readOnly'];
+  if (readOnly) {
+    var languageTree = null;
+    var hasCategories = false;
+    var hasTrashcan = false;
+    var hasCollapse = false;
+    var hasComments = false;
+    var hasDisable = false;
+    var hasSounds = false;
+  } else {
+    var languageTree = Blockly.Options.parseToolboxTree(options['toolbox']);
+    var hasCategories = Boolean(languageTree &&
+        languageTree.getElementsByTagName('category').length);
+    var hasTrashcan = options['trashcan'];
+    if (hasTrashcan === undefined) {
+      hasTrashcan = hasCategories;
+    }
+    var hasCollapse = options['collapse'];
+    if (hasCollapse === undefined) {
+      hasCollapse = hasCategories;
+    }
+    var hasComments = options['comments'];
+    if (hasComments === undefined) {
+      hasComments = hasCategories;
+    }
+    var hasDisable = options['disable'];
+    if (hasDisable === undefined) {
+      hasDisable = hasCategories;
+    }
+    var hasSounds = options['sounds'];
+    if (hasSounds === undefined) {
+      hasSounds = true;
+    }
+  }
+  var rtl = !!options['rtl'];
+  var horizontalLayout = options['horizontalLayout'];
+  if (horizontalLayout === undefined) {
+    horizontalLayout = false;
+  }
+  var toolboxAtStart = options['toolboxPosition'];
+  if (toolboxAtStart === 'end') {
+    toolboxAtStart = false;
+  } else {
+    toolboxAtStart = true;
+  }
+
+  if (horizontalLayout) {
+    var toolboxPosition = toolboxAtStart ?
+        Blockly.TOOLBOX_AT_TOP : Blockly.TOOLBOX_AT_BOTTOM;
+  } else {
+    var toolboxPosition = (toolboxAtStart == rtl) ?
+        Blockly.TOOLBOX_AT_RIGHT : Blockly.TOOLBOX_AT_LEFT;
+  }
+
+  var hasScrollbars = options['scrollbars'];
+  if (hasScrollbars === undefined) {
+    hasScrollbars = hasCategories;
+  }
+  var hasCss = options['css'];
+  if (hasCss === undefined) {
+    hasCss = true;
+  }
+  var pathToMedia = 'https://blockly-demo.appspot.com/static/media/';
+  if (options['media']) {
+    pathToMedia = options['media'];
+  } else if (options['path']) {
+    // 'path' is a deprecated option which has been replaced by 'media'.
+    pathToMedia = options['path'] + 'media/';
+  }
+
+  this.RTL = rtl;
+  this.collapse = hasCollapse;
+  this.comments = hasComments;
+  this.disable = hasDisable;
+  this.readOnly = readOnly;
+  this.maxBlocks = options['maxBlocks'] || Infinity;
+  this.pathToMedia = pathToMedia;
+  this.hasCategories = hasCategories;
+  this.hasScrollbars = hasScrollbars;
+  this.hasTrashcan = hasTrashcan;
+  this.hasSounds = hasSounds;
+  this.hasCss = hasCss;
+  this.horizontalLayout = horizontalLayout;
+  this.languageTree = languageTree;
+  this.gridOptions = Blockly.Options.parseGridOptions_(options);
+  this.zoomOptions = Blockly.Options.parseZoomOptions_(options);
+  this.toolboxPosition = toolboxPosition;
+};
+
+/**
+ * @type {Blockly.Workspace} the parent of the current workspace, or null if
+ *    there is no parent workspace.
+ **/
+Blockly.Options.prototype.parentWorkspace = null;
+
+/**
+ * If set, sets the translation of the workspace to match the scrollbars.
+ * No-op if unset.
+ */
+Blockly.Options.prototype.setMetrics = function() { return; };
+
+/**
+ * Return an object with the metrics required to size the workspace, or null
+ * if unset.
+ * @return {Object} Contains size an position metrics, or null.
+ */
+Blockly.Options.prototype.getMetrics = function() { return null; };
+
+/**
+ * Parse the user-specified zoom options, using reasonable defaults where
+ * behaviour is unspecified.  See zoom documentation:
+ *   https://developers.google.com/blockly/guides/configure/web/zoom
+ * @param {!Object} options Dictionary of options.
+ * @return {!Object} A dictionary of normalized options.
+ * @private
+ */
+Blockly.Options.parseZoomOptions_ = function(options) {
+  var zoom = options['zoom'] || {};
+  var zoomOptions = {};
+  if (zoom['controls'] === undefined) {
+    zoomOptions.controls = false;
+  } else {
+    zoomOptions.controls = !!zoom['controls'];
+  }
+  if (zoom['wheel'] === undefined) {
+    zoomOptions.wheel = false;
+  } else {
+    zoomOptions.wheel = !!zoom['wheel'];
+  }
+  if (zoom['startScale'] === undefined) {
+    zoomOptions.startScale = 1;
+  } else {
+    zoomOptions.startScale = parseFloat(zoom['startScale']);
+  }
+  if (zoom['maxScale'] === undefined) {
+    zoomOptions.maxScale = 3;
+  } else {
+    zoomOptions.maxScale = parseFloat(zoom['maxScale']);
+  }
+  if (zoom['minScale'] === undefined) {
+    zoomOptions.minScale = 0.3;
+  } else {
+    zoomOptions.minScale = parseFloat(zoom['minScale']);
+  }
+  if (zoom['scaleSpeed'] === undefined) {
+    zoomOptions.scaleSpeed = 1.2;
+  } else {
+    zoomOptions.scaleSpeed = parseFloat(zoom['scaleSpeed']);
+  }
+  return zoomOptions;
+};
+
+/**
+ * Parse the user-specified grid options, using reasonable defaults where
+ * behaviour is unspecified. See grid documentation:
+ *   https://developers.google.com/blockly/guides/configure/web/grid
+ * @param {!Object} options Dictionary of options.
+ * @return {!Object} A dictionary of normalized options.
+ * @private
+ */
+Blockly.Options.parseGridOptions_ = function(options) {
+  var grid = options['grid'] || {};
+  var gridOptions = {};
+  gridOptions.spacing = parseFloat(grid['spacing']) || 0;
+  gridOptions.colour = grid['colour'] || '#888';
+  gridOptions.length = parseFloat(grid['length']) || 1;
+  gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
+  return gridOptions;
+};
+
+/**
+ * Parse the provided toolbox tree into a consistent DOM format.
+ * @param {Node|string} tree DOM tree of blocks, or text representation of same.
+ * @return {Node} DOM tree of blocks, or null.
+ */
+Blockly.Options.parseToolboxTree = function(tree) {
+  if (tree) {
+    if (typeof tree != 'string') {
+      if (typeof XSLTProcessor == 'undefined' && tree.outerHTML) {
+        // In this case the tree will not have been properly built by the
+        // browser. The HTML will be contained in the element, but it will
+        // not have the proper DOM structure since the browser doesn't support
+        // XSLTProcessor (XML -> HTML). This is the case in IE 9+.
+        tree = tree.outerHTML;
+      } else if (!(tree instanceof Element)) {
+        tree = null;
+      }
+    }
+    if (typeof tree == 'string') {
+      tree = Blockly.Xml.textToDom(tree);
+    }
+  } else {
+    tree = null;
+  }
+  return tree;
+};

+ 287 - 0
blockly/core/procedures.js

@@ -0,0 +1,287 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Utility functions for handling procedures.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Procedures');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly.Field');
+goog.require('Blockly.Names');
+goog.require('Blockly.Workspace');
+
+
+/**
+ * Category to separate procedure names from variables and generated functions.
+ */
+Blockly.Procedures.NAME_TYPE = 'PROCEDURE';
+
+/**
+ * Find all user-created procedure definitions in a workspace.
+ * @param {!Blockly.Workspace} root Root workspace.
+ * @return {!Array.<!Array.<!Array>>} Pair of arrays, the
+ *     first contains procedures without return variables, the second with.
+ *     Each procedure is defined by a three-element list of name, parameter
+ *     list, and return value boolean.
+ */
+Blockly.Procedures.allProcedures = function(root) {
+  var blocks = root.getAllBlocks();
+  var proceduresReturn = [];
+  var proceduresNoReturn = [];
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i].getProcedureDef) {
+      var tuple = blocks[i].getProcedureDef();
+      if (tuple) {
+        if (tuple[2]) {
+          proceduresReturn.push(tuple);
+        } else {
+          proceduresNoReturn.push(tuple);
+        }
+      }
+    }
+  }
+  proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_);
+  proceduresReturn.sort(Blockly.Procedures.procTupleComparator_);
+  return [proceduresNoReturn, proceduresReturn];
+};
+
+/**
+ * Comparison function for case-insensitive sorting of the first element of
+ * a tuple.
+ * @param {!Array} ta First tuple.
+ * @param {!Array} tb Second tuple.
+ * @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
+ * @private
+ */
+Blockly.Procedures.procTupleComparator_ = function(ta, tb) {
+  return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase());
+};
+
+/**
+ * Ensure two identically-named procedures don't exist.
+ * @param {string} name Proposed procedure name.
+ * @param {!Blockly.Block} block Block to disambiguate.
+ * @return {string} Non-colliding name.
+ */
+Blockly.Procedures.findLegalName = function(name, block) {
+  if (block.isInFlyout) {
+    // Flyouts can have multiple procedures called 'do something'.
+    return name;
+  }
+  while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) {
+    // Collision with another procedure.
+    var r = name.match(/^(.*?)(\d+)$/);
+    if (!r) {
+      name += '2';
+    } else {
+      name = r[1] + (parseInt(r[2], 10) + 1);
+    }
+  }
+  return name;
+};
+
+/**
+ * Does this procedure have a legal name?  Illegal names include names of
+ * procedures already defined.
+ * @param {string} name The questionable name.
+ * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
+ * @param {Blockly.Block=} opt_exclude Optional block to exclude from
+ *     comparisons (one doesn't want to collide with oneself).
+ * @return {boolean} True if the name is legal.
+ * @private
+ */
+Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
+  var blocks = workspace.getAllBlocks();
+  // Iterate through every block and check the name.
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i] == opt_exclude) {
+      continue;
+    }
+    if (blocks[i].getProcedureDef) {
+      var procName = blocks[i].getProcedureDef();
+      if (Blockly.Names.equals(procName[0], name)) {
+        return false;
+      }
+    }
+  }
+  return true;
+};
+
+/**
+ * Rename a procedure.  Called by the editable field.
+ * @param {string} name The proposed new name.
+ * @return {string} The accepted name.
+ * @this {!Blockly.Field}
+ */
+Blockly.Procedures.rename = function(name) {
+  // Strip leading and trailing whitespace.  Beyond this, all names are legal.
+  name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+
+  // Ensure two identically-named procedures don't exist.
+  var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_);
+  var oldName = this.text_;
+  if (oldName != name && oldName != legalName) {
+    // Rename any callers.
+    var blocks = this.sourceBlock_.workspace.getAllBlocks();
+    for (var i = 0; i < blocks.length; i++) {
+      if (blocks[i].renameProcedure) {
+        blocks[i].renameProcedure(oldName, legalName);
+      }
+    }
+  }
+  return legalName;
+};
+
+/**
+ * Construct the blocks required by the flyout for the procedure category.
+ * @param {!Blockly.Workspace} workspace The workspace contianing procedures.
+ * @return {!Array.<!Element>} Array of XML block elements.
+ */
+Blockly.Procedures.flyoutCategory = function(workspace) {
+  var xmlList = [];
+  if (Blockly.Blocks['procedures_defnoreturn']) {
+    // <block type="procedures_defnoreturn" gap="16"></block>
+    var block = goog.dom.createDom('block');
+    block.setAttribute('type', 'procedures_defnoreturn');
+    block.setAttribute('gap', 16);
+    xmlList.push(block);
+  }
+  if (Blockly.Blocks['procedures_defreturn']) {
+    // <block type="procedures_defreturn" gap="16"></block>
+    var block = goog.dom.createDom('block');
+    block.setAttribute('type', 'procedures_defreturn');
+    block.setAttribute('gap', 16);
+    xmlList.push(block);
+  }
+  if (Blockly.Blocks['procedures_ifreturn']) {
+    // <block type="procedures_ifreturn" gap="16"></block>
+    var block = goog.dom.createDom('block');
+    block.setAttribute('type', 'procedures_ifreturn');
+    block.setAttribute('gap', 16);
+    xmlList.push(block);
+  }
+  if (xmlList.length) {
+    // Add slightly larger gap between system blocks and user calls.
+    xmlList[xmlList.length - 1].setAttribute('gap', 24);
+  }
+
+  function populateProcedures(procedureList, templateName) {
+    for (var i = 0; i < procedureList.length; i++) {
+      var name = procedureList[i][0];
+      var args = procedureList[i][1];
+      // <block type="procedures_callnoreturn" gap="16">
+      //   <mutation name="do something">
+      //     <arg name="x"></arg>
+      //   </mutation>
+      // </block>
+      var block = goog.dom.createDom('block');
+      block.setAttribute('type', templateName);
+      block.setAttribute('gap', 16);
+      var mutation = goog.dom.createDom('mutation');
+      mutation.setAttribute('name', name);
+      block.appendChild(mutation);
+      for (var j = 0; j < args.length; j++) {
+        var arg = goog.dom.createDom('arg');
+        arg.setAttribute('name', args[j]);
+        mutation.appendChild(arg);
+      }
+      xmlList.push(block);
+    }
+  }
+
+  var tuple = Blockly.Procedures.allProcedures(workspace);
+  populateProcedures(tuple[0], 'procedures_callnoreturn');
+  populateProcedures(tuple[1], 'procedures_callreturn');
+  return xmlList;
+};
+
+/**
+ * Find all the callers of a named procedure.
+ * @param {string} name Name of procedure.
+ * @param {!Blockly.Workspace} workspace The workspace to find callers in.
+ * @return {!Array.<!Blockly.Block>} Array of caller blocks.
+ */
+Blockly.Procedures.getCallers = function(name, workspace) {
+  var callers = [];
+  var blocks = workspace.getAllBlocks();
+  // Iterate through every block and check the name.
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i].getProcedureCall) {
+      var procName = blocks[i].getProcedureCall();
+      // Procedure name may be null if the block is only half-built.
+      if (procName && Blockly.Names.equals(procName, name)) {
+        callers.push(blocks[i]);
+      }
+    }
+  }
+  return callers;
+};
+
+/**
+ * When a procedure definition changes its parameters, find and edit all its
+ * callers.
+ * @param {!Blockly.Block} defBlock Procedure definition block.
+ */
+Blockly.Procedures.mutateCallers = function(defBlock) {
+  var oldRecordUndo = Blockly.Events.recordUndo;
+  var name = defBlock.getProcedureDef()[0];
+  var xmlElement = defBlock.mutationToDom(true);
+  var callers = Blockly.Procedures.getCallers(name, defBlock.workspace);
+  for (var i = 0, caller; caller = callers[i]; i++) {
+    var oldMutationDom = caller.mutationToDom();
+    var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
+    caller.domToMutation(xmlElement);
+    var newMutationDom = caller.mutationToDom();
+    var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
+    if (oldMutation != newMutation) {
+      // Fire a mutation on every caller block.  But don't record this as an
+      // undo action since it is deterministically tied to the procedure's
+      // definition mutation.
+      Blockly.Events.recordUndo = false;
+      Blockly.Events.fire(new Blockly.Events.Change(
+          caller, 'mutation', null, oldMutation, newMutation));
+      Blockly.Events.recordUndo = oldRecordUndo;
+    }
+  }
+};
+
+/**
+ * Find the definition block for the named procedure.
+ * @param {string} name Name of procedure.
+ * @param {!Blockly.Workspace} workspace The workspace to search.
+ * @return {Blockly.Block} The procedure definition block, or null not found.
+ */
+Blockly.Procedures.getDefinition = function(name, workspace) {
+  // Assume that a procedure definition is a top block.
+  var blocks = workspace.getTopBlocks(false);
+  for (var i = 0; i < blocks.length; i++) {
+    if (blocks[i].getProcedureDef) {
+      var tuple = blocks[i].getProcedureDef();
+      if (tuple && Blockly.Names.equals(tuple[0], name)) {
+        return blocks[i];
+      }
+    }
+  }
+  return null;
+};

+ 424 - 0
blockly/core/rendered_connection.js

@@ -0,0 +1,424 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2016 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 Components for creating connections between blocks.
+ * @author fenichel@google.com (Rachel Fenichel)
+ */
+'use strict';
+
+goog.provide('Blockly.RenderedConnection');
+
+goog.require('Blockly.Connection');
+
+
+/**
+ * Class for a connection between blocks that may be rendered on screen.
+ * @param {!Blockly.Block} source The block establishing this connection.
+ * @param {number} type The type of the connection.
+ * @extends {Blockly.Connection}
+ * @constructor
+ */
+Blockly.RenderedConnection = function(source, type) {
+  Blockly.RenderedConnection.superClass_.constructor.call(this, source, type);
+  this.offsetInBlock_ = new goog.math.Coordinate(0, 0);
+};
+goog.inherits(Blockly.RenderedConnection, Blockly.Connection);
+
+/**
+ * Returns the distance between this connection and another connection.
+ * @param {!Blockly.Connection} otherConnection The other connection to measure
+ *     the distance to.
+ * @return {number} The distance between connections.
+ */
+Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
+  var xDiff = this.x_ - otherConnection.x_;
+  var yDiff = this.y_ - otherConnection.y_;
+  return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+};
+
+/**
+ * Move the block(s) belonging to the connection to a point where they don't
+ * visually interfere with the specified connection.
+ * @param {!Blockly.Connection} staticConnection The connection to move away
+ *     from.
+ * @private
+ */
+Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) {
+  if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
+    // Don't move blocks around while the user is doing the same.
+    return;
+  }
+  // Move the root block.
+  var rootBlock = this.sourceBlock_.getRootBlock();
+  if (rootBlock.isInFlyout) {
+    // Don't move blocks around in a flyout.
+    return;
+  }
+  var reverse = false;
+  if (!rootBlock.isMovable()) {
+    // Can't bump an uneditable block away.
+    // Check to see if the other block is movable.
+    rootBlock = staticConnection.getSourceBlock().getRootBlock();
+    if (!rootBlock.isMovable()) {
+      return;
+    }
+    // Swap the connections and move the 'static' connection instead.
+    staticConnection = this;
+    reverse = true;
+  }
+  // Raise it to the top for extra visibility.
+  var selected = Blockly.selected == rootBlock;
+  selected || rootBlock.select();
+  var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_;
+  var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_;
+  if (reverse) {
+    // When reversing a bump due to an uneditable block, bump up.
+    dy = -dy;
+  }
+  if (rootBlock.RTL) {
+    dx = -dx;
+  }
+  rootBlock.moveBy(dx, dy);
+  selected || rootBlock.unselect();
+};
+
+/**
+ * Change the connection's coordinates.
+ * @param {number} x New absolute x coordinate.
+ * @param {number} y New absolute y coordinate.
+ */
+Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
+  // Remove it from its old location in the database (if already present)
+  if (this.inDB_) {
+    this.db_.removeConnection_(this);
+  }
+  this.x_ = x;
+  this.y_ = y;
+  // Insert it into its new location in the database.
+  if (!this.hidden_) {
+    this.db_.addConnection(this);
+  }
+};
+
+/**
+ * Change the connection's coordinates.
+ * @param {number} dx Change to x coordinate.
+ * @param {number} dy Change to y coordinate.
+ */
+Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) {
+  this.moveTo(this.x_ + dx, this.y_ + dy);
+};
+
+/**
+ * Move this connection to the location given by its offset within the block and
+ * the coordinate of the block's top left corner.
+ * @param {!goog.math.Coordinate} blockTL The coordinate of the top left corner
+ *     of the block.
+ */
+Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) {
+  this.moveTo(blockTL.x + this.offsetInBlock_.x,
+      blockTL.y + this.offsetInBlock_.y);
+};
+
+/**
+ * Set the offset of this connection relative to the top left of its block.
+ * @param {number} x The new relative x.
+ * @param {number} y The new relative y.
+ */
+Blockly.RenderedConnection.prototype.setOffsetInBlock = function(x, y) {
+  this.offsetInBlock_.x = x;
+  this.offsetInBlock_.y = y;
+};
+
+/**
+ * Move the blocks on either side of this connection right next to each other.
+ * @private
+ */
+Blockly.RenderedConnection.prototype.tighten_ = function() {
+  var dx = this.targetConnection.x_ - this.x_;
+  var dy = this.targetConnection.y_ - this.y_;
+  if (dx != 0 || dy != 0) {
+    var block = this.targetBlock();
+    var svgRoot = block.getSvgRoot();
+    if (!svgRoot) {
+      throw 'block is not rendered.';
+    }
+    var xy = Blockly.getRelativeXY_(svgRoot);
+    block.getSvgRoot().setAttribute('transform',
+        'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
+    block.moveConnections_(-dx, -dy);
+  }
+};
+
+/**
+ * Find the closest compatible connection to this connection.
+ * @param {number} maxLimit The maximum radius to another connection.
+ * @param {number} dx Horizontal offset between this connection's location
+ *     in the database and the current location (as a result of dragging).
+ * @param {number} dy Vertical offset between this connection's location
+ *     in the database and the current location (as a result of dragging).
+ * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two
+ *     properties: 'connection' which is either another connection or null,
+ *     and 'radius' which is the distance.
+ */
+Blockly.RenderedConnection.prototype.closest = function(maxLimit, dx, dy) {
+  return this.dbOpposite_.searchForClosest(this, maxLimit, dx, dy);
+};
+
+/**
+ * Add highlighting around this connection.
+ */
+Blockly.RenderedConnection.prototype.highlight = function() {
+  var steps;
+  if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) {
+    steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5';
+  } else {
+    steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5';
+  }
+  var xy = this.sourceBlock_.getRelativeToSurfaceXY();
+  var x = this.x_ - xy.x;
+  var y = this.y_ - xy.y;
+  Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path',
+      {'class': 'blocklyHighlightedConnectionPath',
+       'd': steps,
+       transform: 'translate(' + x + ',' + y + ')' +
+           (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')},
+      this.sourceBlock_.getSvgRoot());
+};
+/**
+ * Add highlighting around this illegal connection for BlocksCAD.
+ */
+Blockly.Connection.prototype.highlightBad = function() {
+  var steps;
+
+  
+  if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) {
+    steps = 'm -10,5 l 15,15 m -15,0 l 15,-15';
+  } else {
+      steps = 'm -15,-5 l 15,15 m -15,0 l 15,-15 ';
+  }
+ 
+  var xy = this.sourceBlock_.getRelativeToSurfaceXY();
+  var x = this.x_ - xy.x;
+  var y = this.y_ - xy.y;
+  Blockly.Connection.highlightedPathBad_ = Blockly.createSvgElement('path',
+      {'class': 'blocklyHighlightedConnectionPathBad',
+       'd': steps,
+       transform: 'translate(' + x + ',' + y + ')' +
+           (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')},
+      this.sourceBlock_.getSvgRoot());
+};
+/**
+ * Unhide this connection, as well as all down-stream connections on any block
+ * attached to this connection.  This happens when a block is expanded.
+ * Also unhides down-stream comments.
+ * @return {!Array.<!Blockly.Block>} List of blocks to render.
+ */
+Blockly.RenderedConnection.prototype.unhideAll = function() {
+  this.setHidden(false);
+  // All blocks that need unhiding must be unhidden before any rendering takes
+  // place, since rendering requires knowing the dimensions of lower blocks.
+  // Also, since rendering a block renders all its parents, we only need to
+  // render the leaf nodes.
+  var renderList = [];
+  if (this.type != Blockly.INPUT_VALUE && this.type != Blockly.NEXT_STATEMENT) {
+    // Only spider down.
+    return renderList;
+  }
+  var block = this.targetBlock();
+  if (block) {
+    var connections;
+    if (block.isCollapsed()) {
+      // This block should only be partially revealed since it is collapsed.
+      connections = [];
+      block.outputConnection && connections.push(block.outputConnection);
+      block.nextConnection && connections.push(block.nextConnection);
+      block.previousConnection && connections.push(block.previousConnection);
+    } else {
+      // Show all connections of this block.
+      connections = block.getConnections_(true);
+    }
+    for (var i = 0; i < connections.length; i++) {
+      renderList.push.apply(renderList, connections[i].unhideAll());
+    }
+    if (!renderList.length) {
+      // Leaf block.
+      renderList[0] = block;
+    }
+  }
+  return renderList;
+};
+
+/**
+ * Remove the highlighting around this connection.
+ * It could be a legal or illegal connection (BlocksCAD)
+ */
+Blockly.RenderedConnection.prototype.unhighlight = function() {
+  if (Blockly.Connection.highlightedPath_) {
+    goog.dom.removeNode(Blockly.Connection.highlightedPath_);
+    delete Blockly.Connection.highlightedPath_;
+  }
+  if (Blockly.Connection.highlightedPathBad_) {
+    goog.dom.removeNode(Blockly.Connection.highlightedPathBad_);
+    delete Blockly.Connection.highlightedPathBad_;
+  }
+};
+
+/**
+ * Set whether this connections is hidden (not tracked in a database) or not.
+ * @param {boolean} hidden True if connection is hidden.
+ */
+Blockly.RenderedConnection.prototype.setHidden = function(hidden) {
+  this.hidden_ = hidden;
+  if (hidden && this.inDB_) {
+    this.db_.removeConnection_(this);
+  } else if (!hidden && !this.inDB_) {
+    this.db_.addConnection(this);
+  }
+};
+
+/**
+ * Hide this connection, as well as all down-stream connections on any block
+ * attached to this connection.  This happens when a block is collapsed.
+ * Also hides down-stream comments.
+ */
+Blockly.RenderedConnection.prototype.hideAll = function() {
+  this.setHidden(true);
+  if (this.targetConnection) {
+    var blocks = this.targetBlock().getDescendants();
+    for (var i = 0; i < blocks.length; i++) {
+      var block = blocks[i];
+      // Hide all connections of all children.
+      var connections = block.getConnections_(true);
+      for (var j = 0; j < connections.length; j++) {
+        connections[j].setHidden(true);
+      }
+      // Close all bubbles of all children.
+      var icons = block.getIcons();
+      for (var j = 0; j < icons.length; j++) {
+        icons[j].setVisible(false);
+      }
+    }
+  }
+};
+
+/**
+ * Check if the two connections can be dragged to connect to each other.
+ * @param {!Blockly.Connection} candidate A nearby connection to check.
+ * @param {number} maxRadius The maximum radius allowed for connections.
+ * @return {boolean} True if the connection is allowed, false otherwise.
+ */
+Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
+    maxRadius) {
+  if (this.distanceFrom(candidate) > maxRadius) {
+    return false;
+  }
+
+  return Blockly.RenderedConnection.superClass_.isConnectionAllowed.call(this,
+      candidate);
+};
+
+/**
+ * Disconnect two blocks that are connected by this connection.
+ * @param {!Blockly.Block} parentBlock The superior block.
+ * @param {!Blockly.Block} childBlock The inferior block.
+ * @private
+ */
+Blockly.RenderedConnection.prototype.disconnectInternal_ = function(parentBlock,
+    childBlock) {
+  Blockly.RenderedConnection.superClass_.disconnectInternal_.call(this,
+      parentBlock, childBlock);
+  // Rerender the parent so that it may reflow.
+  if (parentBlock.rendered) {
+    parentBlock.render();
+  }
+  if (childBlock.rendered) {
+    childBlock.updateDisabled();
+    childBlock.render();
+  }
+};
+
+/**
+ * Respawn the shadow block if there was one connected to the this connection.
+ * Render/rerender blocks as needed.
+ * @private
+ */
+Blockly.RenderedConnection.prototype.respawnShadow_ = function() {
+  var parentBlock = this.getSourceBlock();
+  // Respawn the shadow block if there is one.
+  var shadow = this.getShadowDom();
+  if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
+    Blockly.RenderedConnection.superClass_.respawnShadow_.call(this);
+    var blockShadow = this.targetBlock();
+    if (!blockShadow) {
+      throw 'Couldn\'t respawn the shadow block that should exist here.';
+    }
+    blockShadow.initSvg();
+    blockShadow.render(false);
+    if (parentBlock.rendered) {
+      parentBlock.render();
+    }
+  }
+};
+
+/**
+ * Find all nearby compatible connections to this connection.
+ * Type checking does not apply, since this function is used for bumping.
+ * @param {number} maxLimit The maximum radius to another connection.
+ * @return {!Array.<!Blockly.Connection>} List of connections.
+ * @private
+ */
+Blockly.RenderedConnection.prototype.neighbours_ = function(maxLimit) {
+  return this.dbOpposite_.getNeighbours(this, maxLimit);
+};
+
+/**
+ * Connect two connections together.  This is the connection on the superior
+ * block.  Rerender blocks as needed.
+ * @param {!Blockly.Connection} childConnection Connection on inferior block.
+ * @private
+ */
+Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
+  Blockly.RenderedConnection.superClass_.connect_.call(this, childConnection);
+
+  var parentConnection = this;
+  var parentBlock = parentConnection.getSourceBlock();
+  var childBlock = childConnection.getSourceBlock();
+
+  if (parentBlock.rendered) {
+    parentBlock.updateDisabled();
+  }
+  if (childBlock.rendered) {
+    childBlock.updateDisabled();
+  }
+  if (parentBlock.rendered && childBlock.rendered) {
+    if (parentConnection.type == Blockly.NEXT_STATEMENT ||
+        parentConnection.type == Blockly.PREVIOUS_STATEMENT) {
+      // Child block may need to square off its corners if it is in a stack.
+      // Rendering a child will render its parent.
+      childBlock.render();
+    } else {
+      // Child block does not change shape.  Rendering the parent node will
+      // move its connected children into position.
+      parentBlock.render();
+    }
+  }
+};

+ 750 - 0
blockly/core/scrollbar.js

@@ -0,0 +1,750 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2011 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 Library for creating scrollbars.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Scrollbar');
+goog.provide('Blockly.ScrollbarPair');
+
+goog.require('goog.dom');
+goog.require('goog.events');
+
+
+/**
+ * Class for a pair of scrollbars.  Horizontal and vertical.
+ * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to.
+ * @constructor
+ */
+Blockly.ScrollbarPair = function(workspace) {
+  this.workspace_ = workspace;
+  this.hScroll = new Blockly.Scrollbar(workspace, true, true);
+  this.vScroll = new Blockly.Scrollbar(workspace, false, true);
+  this.corner_ = Blockly.createSvgElement('rect',
+      {'height': Blockly.Scrollbar.scrollbarThickness,
+      'width': Blockly.Scrollbar.scrollbarThickness,
+      'class': 'blocklyScrollbarBackground'}, null);
+  Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas());
+};
+
+/**
+ * Previously recorded metrics from the workspace.
+ * @type {Object}
+ * @private
+ */
+Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null;
+
+/**
+ * Dispose of this pair of scrollbars.
+ * Unlink from all DOM elements to prevent memory leaks.
+ */
+Blockly.ScrollbarPair.prototype.dispose = function() {
+  goog.dom.removeNode(this.corner_);
+  this.corner_ = null;
+  this.workspace_ = null;
+  this.oldHostMetrics_ = null;
+  this.hScroll.dispose();
+  this.hScroll = null;
+  this.vScroll.dispose();
+  this.vScroll = null;
+};
+
+/**
+ * Recalculate both of the scrollbars' locations and lengths.
+ * Also reposition the corner rectangle.
+ */
+Blockly.ScrollbarPair.prototype.resize = function() {
+  // Look up the host metrics once, and use for both scrollbars.
+  var hostMetrics = this.workspace_.getMetrics();
+  if (!hostMetrics) {
+    // Host element is likely not visible.
+    return;
+  }
+
+  // Only change the scrollbars if there has been a change in metrics.
+  var resizeH = false;
+  var resizeV = false;
+  if (!this.oldHostMetrics_ ||
+      this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
+      this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
+      this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop ||
+      this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
+    // The window has been resized or repositioned.
+    resizeH = true;
+    resizeV = true;
+  } else {
+    // Has the content been resized or moved?
+    if (!this.oldHostMetrics_ ||
+        this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth ||
+        this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft ||
+        this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) {
+      resizeH = true;
+    }
+    if (!this.oldHostMetrics_ ||
+        this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight ||
+        this.oldHostMetrics_.viewTop != hostMetrics.viewTop ||
+        this.oldHostMetrics_.contentTop != hostMetrics.contentTop) {
+      resizeV = true;
+    }
+  }
+  if (resizeH) {
+    this.hScroll.resize(hostMetrics);
+  }
+  if (resizeV) {
+    this.vScroll.resize(hostMetrics);
+  }
+
+  // Reposition the corner square.
+  if (!this.oldHostMetrics_ ||
+      this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
+      this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
+    this.corner_.setAttribute('x', this.vScroll.position_.x);
+  }
+  if (!this.oldHostMetrics_ ||
+      this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
+      this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) {
+    this.corner_.setAttribute('y', this.hScroll.position_.y);
+  }
+
+  // Cache the current metrics to potentially short-cut the next resize event.
+  this.oldHostMetrics_ = hostMetrics;
+};
+
+/**
+ * Set the sliders of both scrollbars to be at a certain position.
+ * @param {number} x Horizontal scroll value.
+ * @param {number} y Vertical scroll value.
+ */
+Blockly.ScrollbarPair.prototype.set = function(x, y) {
+  // This function is equivalent to:
+  //   this.hScroll.set(x);
+  //   this.vScroll.set(y);
+  // However, that calls setMetrics twice which causes a chain of
+  // getAttribute->setAttribute->getAttribute resulting in an extra layout pass.
+  // Combining them speeds up rendering.
+  var xyRatio = {};
+
+  var hHandlePosition = x * this.hScroll.ratio_;
+  var vHandlePosition = y * this.vScroll.ratio_;
+
+  var hBarLength = this.hScroll.scrollViewSize_;
+  var vBarLength = this.vScroll.scrollViewSize_;
+
+  xyRatio.x = this.getRatio_(hHandlePosition, hBarLength);
+  xyRatio.y = this.getRatio_(vHandlePosition, vBarLength);
+  this.workspace_.setMetrics(xyRatio);
+
+  this.hScroll.setHandlePosition(hHandlePosition);
+  this.vScroll.setHandlePosition(vHandlePosition);
+};
+
+/**
+ * Helper to calculate the ratio of handle position to scrollbar view size.
+ * @param {number} handlePosition The value of the handle.
+ * @param {number} viewSize The total size of the scrollbar's view.
+ * @return {number} Ratio.
+ * @private
+ */
+Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) {
+  var ratio = handlePosition / viewSize;
+  if (isNaN(ratio)) {
+    return 0;
+  }
+  return ratio;
+};
+
+// --------------------------------------------------------------------
+
+/**
+ * Class for a pure SVG scrollbar.
+ * This technique offers a scrollbar that is guaranteed to work, but may not
+ * look or behave like the system's scrollbars.
+ * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to.
+ * @param {boolean} horizontal True if horizontal, false if vertical.
+ * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair.
+ * @constructor
+ */
+Blockly.Scrollbar = function(workspace, horizontal, opt_pair) {
+  this.workspace_ = workspace;
+  this.pair_ = opt_pair || false;
+  this.horizontal_ = horizontal;
+  this.oldHostMetrics_ = null;
+
+  this.createDom_();
+
+  /**
+   * The upper left corner of the scrollbar's svg group.
+   * @type {goog.math.Coordinate}
+   * @private
+   */
+  this.position_ = new goog.math.Coordinate(0, 0);
+
+  if (horizontal) {
+    this.svgBackground_.setAttribute('height',
+        Blockly.Scrollbar.scrollbarThickness);
+    this.svgHandle_.setAttribute('height',
+        Blockly.Scrollbar.scrollbarThickness - 5);
+    this.svgHandle_.setAttribute('y', 2.5);
+
+    this.lengthAttribute_ = 'width';
+    this.positionAttribute_ = 'x';
+  } else {
+    this.svgBackground_.setAttribute('width',
+        Blockly.Scrollbar.scrollbarThickness);
+    this.svgHandle_.setAttribute('width',
+        Blockly.Scrollbar.scrollbarThickness - 5);
+    this.svgHandle_.setAttribute('x', 2.5);
+
+    this.lengthAttribute_ = 'height';
+    this.positionAttribute_ = 'y';
+  }
+  var scrollbar = this;
+  this.onMouseDownBarWrapper_ = Blockly.bindEvent_(this.svgBackground_,
+      'mousedown', scrollbar, scrollbar.onMouseDownBar_);
+  this.onMouseDownHandleWrapper_ = Blockly.bindEvent_(this.svgHandle_,
+      'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
+};
+
+/**
+ * The size of the area within which the scrollbar handle can move.
+ * @type {number}
+ * @private
+ */
+Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
+
+/**
+ * The length of the scrollbar handle.
+ * @type {number}
+ * @private
+ */
+Blockly.Scrollbar.prototype.handleLength_ = 0;
+
+/**
+ * The offset of the start of the handle from the start of the scrollbar range.
+ * @type {number}
+ * @private
+ */
+Blockly.Scrollbar.prototype.handlePosition_ = 0;
+
+/**
+ * Whether the scrollbar handle is visible.
+ * @type {boolean}
+ * @private
+ */
+Blockly.Scrollbar.prototype.isVisible_ = true;
+
+/**
+ * Width of vertical scrollbar or height of horizontal scrollbar.
+ * Increase the size of scrollbars on touch devices.
+ * Don't define if there is no document object (e.g. node.js).
+ */
+Blockly.Scrollbar.scrollbarThickness = 15;
+if (goog.events.BrowserFeature.TOUCH_ENABLED) {
+  Blockly.Scrollbar.scrollbarThickness = 25;
+}
+
+/**
+ * @param {!Object} first An object containing computed measurements of a
+ *    workspace.
+ * @param {!Object} second Another object containing computed measurements of a
+ *    workspace.
+ * @return {boolean} Whether the two sets of metrics are equivalent.
+ * @private
+ */
+Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) {
+  if (!(first && second)) {
+    return false;
+  }
+
+  if (first.viewWidth != second.viewWidth ||
+      first.viewHeight != second.viewHeight ||
+      first.viewLeft != second.viewLeft ||
+      first.viewTop != second.viewTop ||
+      first.absoluteTop != second.absoluteTop ||
+      first.absoluteLeft != second.absoluteLeft ||
+      first.contentWidth != second.contentWidth ||
+      first.contentHeight != second.contentHeight ||
+      first.contentLeft != second.contentLeft ||
+      first.contentTop != second.contentTop) {
+    return false;
+  }
+
+  return true;
+};
+
+/**
+ * Dispose of this scrollbar.
+ * Unlink from all DOM elements to prevent memory leaks.
+ */
+Blockly.Scrollbar.prototype.dispose = function() {
+  this.onMouseUpHandle_();
+  Blockly.unbindEvent_(this.onMouseDownBarWrapper_);
+  this.onMouseDownBarWrapper_ = null;
+  Blockly.unbindEvent_(this.onMouseDownHandleWrapper_);
+  this.onMouseDownHandleWrapper_ = null;
+
+  goog.dom.removeNode(this.svgGroup_);
+  this.svgGroup_ = null;
+  this.svgBackground_ = null;
+  this.svgHandle_ = null;
+  this.workspace_ = null;
+};
+
+/**
+ * Set the length of the scrollbar's handle and change the SVG attribute
+ * accordingly.
+ * @param {number} newLength The new scrollbar handle length.
+ */
+Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) {
+  this.handleLength_ = newLength;
+  this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_);
+};
+
+/**
+ * Set the offset of the scrollbar's handle and change the SVG attribute
+ * accordingly.
+ * @param {number} newPosition The new scrollbar handle offset.
+ */
+Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
+  this.handlePosition_ = newPosition;
+  this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
+};
+
+/**
+ * Set the size of the scrollbar's background and change the SVG attribute
+ * accordingly.
+ * @param {number} newSize The new scrollbar background length.
+ * @private
+ */
+Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) {
+  this.scrollViewSize_ = newSize;
+  this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
+};
+
+/**
+ * Set the position of the scrollbar's svg group.
+ * @param {number} x The new x coordinate.
+ * @param {number} y The new y coordinate.
+ */
+Blockly.Scrollbar.prototype.setPosition = function(x, y) {
+  this.position_.x = x;
+  this.position_.y = y;
+
+  this.svgGroup_.setAttribute('transform',
+      'translate(' + this.position_.x + ',' + this.position_.y + ')');
+};
+
+/**
+ * Recalculate the scrollbar's location and its length.
+ * @param {Object=} opt_metrics A data structure of from the describing all the
+ * required dimensions.  If not provided, it will be fetched from the host
+ * object.
+ */
+Blockly.Scrollbar.prototype.resize = function(opt_metrics) {
+  // Determine the location, height and width of the host element.
+  var hostMetrics = opt_metrics;
+  if (!hostMetrics) {
+    hostMetrics = this.workspace_.getMetrics();
+    if (!hostMetrics) {
+      // Host element is likely not visible.
+      return;
+    }
+  }
+
+  if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics,
+      this.oldHostMetrics_)) {
+    return;
+  }
+  this.oldHostMetrics_ = hostMetrics;
+
+  /* hostMetrics is an object with the following properties.
+   * .viewHeight: Height of the visible rectangle,
+   * .viewWidth: Width of the visible rectangle,
+   * .contentHeight: Height of the contents,
+   * .contentWidth: Width of the content,
+   * .viewTop: Offset of top edge of visible rectangle from parent,
+   * .viewLeft: Offset of left edge of visible rectangle from parent,
+   * .contentTop: Offset of the top-most content from the y=0 coordinate,
+   * .contentLeft: Offset of the left-most content from the x=0 coordinate,
+   * .absoluteTop: Top-edge of view.
+   * .absoluteLeft: Left-edge of view.
+   */
+  if (this.horizontal_) {
+    this.resizeHorizontal_(hostMetrics);
+  } else {
+    this.resizeVertical_(hostMetrics);
+  }
+  // Resizing may have caused some scrolling.
+  this.onScroll_();
+};
+
+/**
+ * Recalculate a horizontal scrollbar's location and length.
+ * @param {!Object} hostMetrics A data structure describing all the
+ *     required dimensions, possibly fetched from the host object.
+ * @private
+ */
+Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) {
+  // TODO: Inspect metrics to determine if we can get away with just a content
+  // resize.
+  this.resizeViewHorizontal(hostMetrics);
+};
+
+/**
+ * Recalculate a horizontal scrollbar's location on the screen and path length.
+ * This should be called when the layout or size of the window has changed.
+ * @param {!Object} hostMetrics A data structure describing all the
+ *     required dimensions, possibly fetched from the host object.
+ */
+Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
+  var viewSize = hostMetrics.viewWidth - 1;
+  if (this.pair_) {
+    // Shorten the scrollbar to make room for the corner square.
+    viewSize -= Blockly.Scrollbar.scrollbarThickness;
+  }
+  this.setScrollViewSize_(Math.max(0, viewSize));
+
+  var xCoordinate = hostMetrics.absoluteLeft + 0.5;
+  if (this.pair_ && this.workspace_.RTL) {
+    xCoordinate += Blockly.Scrollbar.scrollbarThickness;
+  }
+
+  // Horizontal toolbar should always be just above the bottom of the workspace.
+  var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight -
+      Blockly.Scrollbar.scrollbarThickness - 0.5;
+  this.setPosition(xCoordinate, yCoordinate);
+
+  // If the view has been resized, a content resize will also be necessary.  The
+  // reverse is not true.
+  this.resizeContentHorizontal(hostMetrics);
+};
+
+/**
+ * Recalculate a horizontal scrollbar's location within its path and length.
+ * This should be called when the contents of the workspace have changed.
+ * @param {!Object} hostMetrics A data structure describing all the
+ *     required dimensions, possibly fetched from the host object.
+ */
+Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
+  if (!this.pair_) {
+    // Only show the scrollbar if needed.
+    // Ideally this would also apply to scrollbar pairs, but that's a bigger
+    // headache (due to interactions with the corner square).
+    this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth);
+  }
+
+  this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth;
+  if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
+      isNaN(this.ratio_)) {
+    this.ratio_ = 0;
+  }
+
+  var handleLength = hostMetrics.viewWidth * this.ratio_;
+  this.setHandleLength_(Math.max(0, handleLength));
+
+  var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) *
+      this.ratio_;
+  this.setHandlePosition(this.constrainHandle_(handlePosition));
+};
+
+/**
+ * Recalculate a vertical scrollbar's location and length.
+ * @param {!Object} hostMetrics A data structure describing all the
+ *     required dimensions, possibly fetched from the host object.
+ * @private
+ */
+Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) {
+  // TODO: Inspect metrics to determine if we can get away with just a content
+  // resize.
+  this.resizeViewVertical(hostMetrics);
+};
+
+/**
+ * Recalculate a vertical scrollbar's location on the screen and path length.
+ * This should be called when the layout or size of the window has changed.
+ * @param {!Object} hostMetrics A data structure describing all the
+ *     required dimensions, possibly fetched from the host object.
+ */
+Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
+  var viewSize = hostMetrics.viewHeight - 1;
+  if (this.pair_) {
+    // Shorten the scrollbar to make room for the corner square.
+    viewSize -= Blockly.Scrollbar.scrollbarThickness;
+  }
+  this.setScrollViewSize_(Math.max(0, viewSize));
+
+  var xCoordinate = hostMetrics.absoluteLeft + 0.5;
+  if (!this.workspace_.RTL) {
+    xCoordinate += hostMetrics.viewWidth -
+        Blockly.Scrollbar.scrollbarThickness - 1;
+  }
+  var yCoordinate = hostMetrics.absoluteTop + 0.5;
+  this.setPosition(xCoordinate, yCoordinate);
+
+  // If the view has been resized, a content resize will also be necessary.  The
+  // reverse is not true.
+  this.resizeContentVertical(hostMetrics);
+};
+
+/**
+ * Recalculate a vertical scrollbar's location within its path and length.
+ * This should be called when the contents of the workspace have changed.
+ * @param {!Object} hostMetrics A data structure describing all the
+ *     required dimensions, possibly fetched from the host object.
+ */
+Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
+  if (!this.pair_) {
+    // Only show the scrollbar if needed.
+    this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight);
+  }
+
+  this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight;
+  if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
+      isNaN(this.ratio_)) {
+    this.ratio_ = 0;
+  }
+
+  var handleLength = hostMetrics.viewHeight * this.ratio_;
+  this.setHandleLength_(Math.max(0, handleLength));
+
+  var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) *
+      this.ratio_;
+  this.setHandlePosition(this.constrainHandle_(handlePosition));
+};
+
+/**
+ * Create all the DOM elements required for a scrollbar.
+ * The resulting widget is not sized.
+ * @private
+ */
+Blockly.Scrollbar.prototype.createDom_ = function() {
+  /* Create the following DOM:
+  <g class="blocklyScrollbarHorizontal">
+    <rect class="blocklyScrollbarBackground" />
+    <rect class="blocklyScrollbarHandle" rx="8" ry="8" />
+  </g>
+  */
+  var className = 'blocklyScrollbar' +
+      (this.horizontal_ ? 'Horizontal' : 'Vertical');
+  this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null);
+  this.svgBackground_ = Blockly.createSvgElement('rect',
+      {'class': 'blocklyScrollbarBackground'}, this.svgGroup_);
+  var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2);
+  this.svgHandle_ = Blockly.createSvgElement('rect',
+      {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius},
+      this.svgGroup_);
+  Blockly.Scrollbar.insertAfter_(this.svgGroup_,
+                                 this.workspace_.getBubbleCanvas());
+};
+
+/**
+ * Is the scrollbar visible.  Non-paired scrollbars disappear when they aren't
+ * needed.
+ * @return {boolean} True if visible.
+ */
+Blockly.Scrollbar.prototype.isVisible = function() {
+  return this.isVisible_;
+};
+
+/**
+ * Set whether the scrollbar is visible.
+ * Only applies to non-paired scrollbars.
+ * @param {boolean} visible True if visible.
+ */
+Blockly.Scrollbar.prototype.setVisible = function(visible) {
+  if (visible == this.isVisible()) {
+    return;
+  }
+  // Ideally this would also apply to scrollbar pairs, but that's a bigger
+  // headache (due to interactions with the corner square).
+  if (this.pair_) {
+    throw 'Unable to toggle visibility of paired scrollbars.';
+  }
+
+  this.isVisible_ = visible;
+
+  if (visible) {
+    this.svgGroup_.setAttribute('display', 'block');
+  } else {
+    // Hide the scrollbar.
+    this.workspace_.setMetrics({x: 0, y: 0});
+    this.svgGroup_.setAttribute('display', 'none');
+  }
+};
+
+/**
+ * Scroll by one pageful.
+ * Called when scrollbar background is clicked.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
+  this.onMouseUpHandle_();
+  if (Blockly.isRightButton(e)) {
+    // Right-click.
+    // Scrollbars have no context menu.
+    e.stopPropagation();
+    return;
+  }
+  var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(),
+      this.workspace_.getInverseScreenCTM());
+  var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
+
+  var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_);
+  var handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
+  var handlePosition = this.handlePosition_;
+
+  var pageLength = this.handleLength_ * 0.95;
+  if (mouseLocation <= handleStart) {
+    // Decrease the scrollbar's value by a page.
+    handlePosition -= pageLength;
+  } else if (mouseLocation >= handleStart + this.handleLength_) {
+    // Increase the scrollbar's value by a page.
+    handlePosition += pageLength;
+  }
+
+  this.setHandlePosition(this.constrainHandle_(handlePosition));
+
+  this.onScroll_();
+  e.stopPropagation();
+  e.preventDefault();
+};
+
+/**
+ * Start a dragging operation.
+ * Called when scrollbar handle is clicked.
+ * @param {!Event} e Mouse down event.
+ * @private
+ */
+Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
+  this.onMouseUpHandle_();
+  if (Blockly.isRightButton(e)) {
+    // Right-click.
+    // Scrollbars have no context menu.
+    e.stopPropagation();
+    return;
+  }
+  // Look up the current translation and record it.
+  this.startDragHandle = this.handlePosition_;
+  // Record the current mouse position.
+  this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY;
+  Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEvent_(document,
+      'mouseup', this, this.onMouseUpHandle_);
+  Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
+      'mousemove', this, this.onMouseMoveHandle_);
+  e.stopPropagation();
+  e.preventDefault();
+};
+
+/**
+ * Drag the scrollbar's handle.
+ * @param {!Event} e Mouse up event.
+ * @private
+ */
+Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
+  var currentMouse = this.horizontal_ ? e.clientX : e.clientY;
+  var mouseDelta = currentMouse - this.startDragMouse;
+  var handlePosition = this.startDragHandle + mouseDelta;
+  // Position the bar.
+  this.setHandlePosition(this.constrainHandle_(handlePosition));
+  this.onScroll_();
+};
+
+/**
+ * Stop binding to the global mouseup and mousemove events.
+ * @private
+ */
+Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() {
+  Blockly.hideChaff(true);
+  if (Blockly.Scrollbar.onMouseUpWrapper_) {
+    Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_);
+    Blockly.Scrollbar.onMouseUpWrapper_ = null;
+  }
+  if (Blockly.Scrollbar.onMouseMoveWrapper_) {
+    Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_);
+    Blockly.Scrollbar.onMouseMoveWrapper_ = null;
+  }
+};
+
+/**
+ * Constrain the handle's position within the minimum (0) and maximum
+ * (length of scrollbar) values allowed for the scrollbar.
+ * @param {number} value Value that is potentially out of bounds.
+ * @return {number} Constrained value.
+ * @private
+ */
+Blockly.Scrollbar.prototype.constrainHandle_ = function(value) {
+  if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) {
+    value = 0;
+  } else {
+    value = Math.min(value, this.scrollViewSize_ - this.handleLength_);
+  }
+  return value;
+};
+
+/**
+ * Called when scrollbar is moved.
+ * @private
+ */
+Blockly.Scrollbar.prototype.onScroll_ = function() {
+  var ratio = this.handlePosition_ / this.scrollViewSize_;
+  if (isNaN(ratio)) {
+    ratio = 0;
+  }
+  var xyRatio = {};
+  if (this.horizontal_) {
+    xyRatio.x = ratio;
+  } else {
+    xyRatio.y = ratio;
+  }
+  this.workspace_.setMetrics(xyRatio);
+};
+
+/**
+ * Set the scrollbar slider's position.
+ * @param {number} value The distance from the top/left end of the bar.
+ */
+Blockly.Scrollbar.prototype.set = function(value) {
+  this.setHandlePosition(this.constrainHandle_(value * this.ratio_));
+  this.onScroll_();
+};
+
+/**
+ * Insert a node after a reference node.
+ * Contrast with node.insertBefore function.
+ * @param {!Element} newNode New element to insert.
+ * @param {!Element} refNode Existing element to precede new node.
+ * @private
+ */
+Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) {
+  var siblingNode = refNode.nextSibling;
+  var parentNode = refNode.parentNode;
+  if (!parentNode) {
+    throw 'Reference node has no parent.';
+  }
+  if (siblingNode) {
+    parentNode.insertBefore(newNode, siblingNode);
+  } else {
+    parentNode.appendChild(newNode);
+  }
+};

Some files were not shown because too many files changed in this diff