201 Commits

Autor SHA1 Mensagem Data
Neil Fraser f34a2c2ed4 Properly escape IDs in procedure generators. (#1305)
Issue #251 and #1304.
2017-09-04 08:51:16 -07:00
Isaac Dunn f8f807f53c Store Block hue so it can be extracted reliably (#1293)
Adds `Block.getHue()` to retrieve the block colour when set via a hue.
The hue value is stored in the new `.hue_` field, which is null if the colour was set via a hex string.
2017-09-04 08:45:33 -07:00
picklesrus 5518873389 Revert "Remove all all instances calling setValue on variable fields with the name instead of the id" (#1296)
* Revert "Create WorkspaceViewport class (#1291)"

This reverts commit 6c00d77c9e.

* Revert "Remove all all instances calling setValue with name. (#1254)"

This reverts commit 8e8b6b27af.
2017-08-28 16:55:44 -07:00
Rachel Fenichel 6c00d77c9e Create WorkspaceViewport class (#1291)
* Create WorkspaceViewport class

* Update comments

* Move workspace viewport functions back to the workspace for now

* whitespace
2017-08-24 15:51:31 -07:00
marisaleung 8e8b6b27af Remove all all instances calling setValue with name. (#1254) 2017-08-24 14:34:01 -07:00
Rachel Fenichel 62e4730ba5 Add annotations for units to scrollbar.js (#1290)
* Add annotations for units to scrollbar.js

* Update comments
2017-08-24 10:58:16 -07:00
Rachel Fenichel 00b2b85e8c Decompose the showEditor_ function in FieldTextInput (#1285)
* Explicit annotations

* Decompose the showEditor_ function in FieldTextInput

* Remove extra newline
2017-08-18 15:19:05 -07:00
Rachel Fenichel 2d5f1156d7 Clean up code in FieldTextInput (#1284)
* Clean up code in FieldTextInput

* Explicit annotations

* Remove extra newline
2017-08-18 14:55:40 -07:00
Evan W. Patton e1e94271c4 Implement Blockly.Events.filter in linear time (#1205)
* Implement Blockly.Events.filter in linear time

For large App Inventor projects (order 1k+ blocks, 100+ top-level
blocks), the O(n^2) behavior of Blockly.Event.filter was causing
performance issues when rearranging blocks or pasting from the
backpack. This commit provides a linear merge implementation using a
key that uniquely identifies a block so that multiple events targeting
the same block are merged. This change benefits from O(1) amortized
lookup using an object as a key-value store.

* Add event filter unit tests and fix logic bugs

* Update Blockly.Events.filter unit tests
2017-08-18 14:18:47 -07:00
Rachel Fenichel a3586db023 Bring the most recently edited stack to the front at the end of a drag. (#1283) 2017-08-18 14:16:46 -07:00
Rachel Fenichel b2492dec67 Escape variable names correctly when serializing to XML (#1279) 2017-08-18 11:18:49 -07:00
Andrew n marshall 1956baa963 BlockFactory: Adding JSON/JavaScript import support (#1235)
Updates the BlockFactory main workspace based on the preview block generated from manual construction.

This is a continuation of PR #1216 by @JC-Orozco, rebased on latest develop branch. Bring it latest code up to Google styling and standards.
2017-08-17 12:40:58 -07:00
CoryDCode 29582ba0d7 Fixing no-category toolboxes so they populate immediately, rather (#1255)
than on a timeout.
2017-08-16 16:05:35 -07:00
picklesrus 859d63633e Fix #1275 by adding a more specific rule for overflow:visible on the … (#1280)
* Fix #1275 by adding a more specific rule for overflow:visible on the drag surface svg.  This wins out over a common bootstrap rule that says: svg:not(:root) {overflow:hidden} and helps avoid a difficult problem for Blockly users to diagnose.

* Update css.js
2017-08-15 13:20:51 -07:00
Sam El-Husseini 4c4c8be142 Fix wrong width of field_dropdown with an image on Edge / IE (#1278) 2017-08-14 16:09:27 -07:00
Rachel Fenichel 4b9fc09fed Code cleanup in BlockSvg.prototype.tab (#1277) 2017-08-14 15:34:44 -07:00
Tom a6245f4c6e Compile messages with externs and demo with advanced optimizations (#1259)
* Revert "Fix synonyms when compiled. (#1248)"

This reverts commit f08afbb351.

* Revert "Compatibility for Closure Compiler. (#1240)" [fc8d4c9]

* Adding exports to all messages.

* Fixed missing dependency to Blockly.defineBlocksWithJsonArray()

* Adding a fully compiled demo draft (still simple optimizations).

* Demo optimizations switched to advanced and enabled exports (for Blockly.Msg).

* Message interpolation updated to use the exported (global) Blockly.Msg array.

* Adding some debug compilation options to the build script.

* Adding SVG externs.

* Fixed Blockly.inject's config array to work with compilation.

* Reverting all compiled code.

This fixes commit b307ba11512d8c1d99eddd3203ff29295c07716d.
This fixes commit dec6910b6753852e3d3f239c6b2d9553df66341c.

* Reverting all compiled code.

This fixes commit 824c806ec315bd545954df47efb189142f237c44.

* Removing old todo

* Merge commit 'fe96bec765f0eb58c5321101965100c2716760ed' into compile-messages-with-externs

* commit 'fe96bec765f0eb58c5321101965100c2716760ed':
  Fixes positional index for Czech translation (#1264)
  Missed one use of string instead of .property in extensions.js (#1262)
  Update extensions.js to be compatible with ADVANCED_OPTIMIZATIONS (#1253)
  Fix type tags and todo placement.
  Procedure block renames variable in mutator if there is a case change.

* Fixes based on review by @NeilFraser
- 80 cols
- using goog.global instead of window
- @export on the same line as messages

* BF: Moving the msg dependency earlier, since Blockly.Msg.en is filling the Blockly.Msg object, which is empty without Blockly.Msg.en (and the rest of the code is using it as Blockly.Msg).

* Updating some texts in the demo's html file to be more descriptive.

* Commenting the debug options in the build, to maximize the optimizations. They are not removed, to allow anybody to turn them on if needed (since they are not documented on the Closure Compiler's REST API pages).

* BF: fixed blocks_compressed.js compilation, as it now requires Blockly namespace to exist.

* SVG externs file updated based on the one in https://github.com/google/closure-compiler/blob/master/contrib/externs/svg.js (eliminating 2 warnings)
2017-08-08 11:16:49 -07:00
vicng fe96bec765 Fixes positional index for Czech translation (#1264) 2017-08-07 16:54:41 -07:00
marisaleung 98494ad951 Merge pull request #1257 from marisaleung/develop_procedureMutatorRenamesVariables
Procedure block renames variable in mutator if there is a case change.
2017-08-04 14:24:01 -07:00
marisaleung 685a41d599 Merge pull request #1261 from marisaleung/develop
Fix type tags and todo placement.
2017-08-04 13:56:36 -07:00
RoboErikG 26e74f72d6 Missed one use of string instead of .property in extensions.js (#1262) 2017-08-04 11:34:33 -07:00
RoboErikG fab59facee Update extensions.js to be compatible with ADVANCED_OPTIMIZATIONS (#1253)
* Update extensions.js to be compatible with ADVANCED_OPTIMIZATIONS

We were using strings to check for the existence of properties on
a mutator, which breaks if those properties were renamed by the
closure compiler. This updates all of the uses to direct function
references so that anyone building with advanced optimizations turned
on will get the correct method references in their mutators.

fixes #1251

* Update to extensions.js for advanced optimizations

Some minor updates to making extensions.js work with advanced
optimizations.

* use === undefined instead of typeof == 'undefined'
2017-08-03 15:18:41 -07:00
marisaleung 5f21a43caf Fix type tags and todo placement. 2017-08-03 12:35:14 -07:00
marisaleung f3fa660c41 Merge pull request #1236 from marisaleung/develop_changeWorkspaceToPlaygroundWorkspace
Change workspace name to playgroundWorkspace.
2017-08-02 17:16:26 -07:00
marisaleung 103c10eddb Change workspace name to playgroundWorkspace. 2017-08-02 17:03:22 -07:00
marisaleung d37309497f Procedure block renames variable in mutator if there is a case change. 2017-08-02 16:49:06 -07:00
Neil Fraser f08afbb351 Fix synonyms when compiled. (#1248) 2017-08-01 18:03:09 -07:00
CoryDCode b68f27a2fa Fixed the buildscript so it doesn't include the parent directory name in accessible. (#1252) 2017-08-01 17:10:40 -07:00
Tony Lian bfa45f136c Change the Lua interpreter (#1246)
Change the Lua interpreter to another one with version 5.3 so that it
would support goto statement which Blockly uses currently
2017-08-01 14:29:55 -07:00
Rachel Fenichel 2bd056ac35 Master to develop 07 31 17 (#1243)
* Rebuild for master push

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Enable google/blockly with continuous build on travis ci (#1023)

* create .travis for ci job

* initial checkin for blocky-web travis ci job

* rename file to .travis.yaml for typo

* remove after_script

* added cache

* rename .travis.yaml to .travis.yml

* Update .travis.yml

* include build script

* fix yaml file format issue

* debug install part

* debug build issue

* Update .travis.yml

* remove cache for now

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* more debug info

* Update .travis.yml

* Update .travis.yml

* fix typo

* installing chrome browser

* remove chrome setting config

* run build.py as part of npm install

* Update .travis.yml

* update karma dependency

* use karma as test runner

* fix typo

* remove karma test for now

* Update .travis.yml

* Update package.json

* add npm test target

* add browserstack-runner depdendency

* update browser support

* fix typo for test target

* fix chrome typo

* added closure dependency

* add google-closure-library

* include blockly_uncompressed.js and core.js dependency

* uncomment out core/*.js files

* add kama job as part of install

* remove browserstack add on for now

* fix karma config typo

* add karma-closure

* add os support

* remove typo config

* include more closure files

* change os back to linux

* use closure-library from node_modules

* change log level back to INFO

* change npm test target to use open browser command instead of karma

* change travis test target to use open command instead of karma

* list current directory

* find what's in current dir

* typo command

* Update .travis.yml

* typo again

* open right index.html

* use right path for index.html

* xdg-open to open default browser on travis

* exit browser after 5s wait

* change timeout to 1 min

* exit after opening up browser

* use browser only

* use karma

* remove un-needed dependency

* clean up script section

* fix typo

* update build status on readme

* initial commit for selenium integration tests

* update selenium jar path

* fix test_runner.js typo

* add more debug info

* check java version

* add && instead of 9288

* fix java path

* add logic to check if selenium is running or not

* add some deugging info

* initial commit to get chromedriver

* add chromedriver flag

* add get_chromedriver.sh to package.json and .travel

* change browser to chrome for now

* fix path issue

* update chromdriver path

* fix path issue again

* more debugging

* add debug msg

* fix typo

* minor fix for getting chromedriver

* install latest chrome browser

* clean up pakcage.json

* use npm target for test run

* remove removing trailing comma

* fix another trailing comma

* updated travis test target

* clean up scripts

* not sure nmp run preinstall

* redirect selenium log to tmp file

* revert writing console log to file

* update test summary

* more clean up

* minor clean up before pull request

* resolved closure-library conflict

1. add closure-library to dependencies instead of devDependencies.
2. add lint back in scripts block

* fix typo (adding comma) in script section

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Merge develop into master (#1064)

* Adding new minimap demo

* Basic code style changes. Adding a few more comments. Return early if disableScrollChange in onScrollChange listener.

* Adding horizontal scrolling. Changed scroll change callbacks from onScroll_ to setHandlePosition. onScroll_ is not challed when workspace is dragged.

* Registering mousemove and mouseup listener in mousedown event. Mousemove and Mouseup events are now listening over document.

* Adding the remove variable modal and functionality to accessible Blockly. (#1011)

* Minimap position bug fix for browsers other than chrome. Added touch support.

* Adding an add variable modal to accessible Blockly. (#1015)

* Adding the remove variable modal and functionality to accessible Blockly.

* Adding the add variable modal for accessible Blockly.

* Block browser context menu in the toolbox and flyout

* Add links to the dev registration form and contributor guidelines

* Miscellaneous comment cleanup

* Adding the common modal class. (#1017)

Centralizes accessible modal behavior.

* - Changed error message referencing 'procedure' instead of 'function' (#1019)

- Added iOS specific UI messages
- Fixed bug with js_to_json.py script where it didn't recognize ' character

* - Allows use of Blockly's messaging format for category name, colour,… (#1028)

...in toolbox XML.
- Updated code editor demo to use this message format
- Re-built blockly_compressed.js

* Making text_count use a text color (like text_length, which also returns a number). (#1027)

* Enable google/blockly with continuous build on travis ci (#1023) (#1035)

* create .travis for ci job

* initial checkin for blocky-web travis ci job

* rename file to .travis.yaml for typo

* remove after_script

* added cache

* rename .travis.yaml to .travis.yml

* Update .travis.yml

* include build script

* fix yaml file format issue

* debug install part

* debug build issue

* Update .travis.yml

* remove cache for now

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* more debug info

* Update .travis.yml

* Update .travis.yml

* fix typo

* installing chrome browser

* remove chrome setting config

* run build.py as part of npm install

* Update .travis.yml

* update karma dependency

* use karma as test runner

* fix typo

* remove karma test for now

* Update .travis.yml

* Update package.json

* add npm test target

* add browserstack-runner depdendency

* update browser support

* fix typo for test target

* fix chrome typo

* added closure dependency

* add google-closure-library

* include blockly_uncompressed.js and core.js dependency

* uncomment out core/*.js files

* add kama job as part of install

* remove browserstack add on for now

* fix karma config typo

* add karma-closure

* add os support

* remove typo config

* include more closure files

* change os back to linux

* use closure-library from node_modules

* change log level back to INFO

* change npm test target to use open browser command instead of karma

* change travis test target to use open command instead of karma

* list current directory

* find what's in current dir

* typo command

* Update .travis.yml

* typo again

* open right index.html

* use right path for index.html

* xdg-open to open default browser on travis

* exit browser after 5s wait

* change timeout to 1 min

* exit after opening up browser

* use browser only

* use karma

* remove un-needed dependency

* clean up script section

* fix typo

* update build status on readme

* initial commit for selenium integration tests

* update selenium jar path

* fix test_runner.js typo

* add more debug info

* check java version

* add && instead of 9288

* fix java path

* add logic to check if selenium is running or not

* add some deugging info

* initial commit to get chromedriver

* add chromedriver flag

* add get_chromedriver.sh to package.json and .travel

* change browser to chrome for now

* fix path issue

* update chromdriver path

* fix path issue again

* more debugging

* add debug msg

* fix typo

* minor fix for getting chromedriver

* install latest chrome browser

* clean up pakcage.json

* use npm target for test run

* remove removing trailing comma

* fix another trailing comma

* updated travis test target

* clean up scripts

* not sure nmp run preinstall

* redirect selenium log to tmp file

* revert writing console log to file

* update test summary

* more clean up

* minor clean up before pull request

* resolved closure-library conflict

1. add closure-library to dependencies instead of devDependencies.
2. add lint back in scripts block

* fix typo (adding comma) in script section

* Renames Blockly.workspaceDragSurface to Blockly.WorkspaceDragSurface.

Fixes #880.

* Ensure useDragSurface is a boolean.

Fixed #988

* use pretest instead of preinstall in package.json (#1043)

* cherry pick for pretest fix

* put pretest target to test_setup.sh

* fix conflict

* cherry pick for get_chromedriver.sh

* add some sleep to wait download to finish

* use node.js stable

* use npm test target

* field_angle renders degree symbol consistently.

Fixes #973

* bumpNeighbours_ function moved to block_svg.

Fixed #1009

* Update RegEx in js-to-json to match windowi eol (#1050)

The current regex only works with the "\n" line endings as it expects no characters after the optional ";" at the end of the line. In windows, if it adds the "\r" it counts as a characters and is not part of the line terminator so it doesn't match.

* Fix French translation of "colour with rgb" block (#1053)

"colorier", which is currently used, is a verb and proposed "couleur" is
a noun: the block in question does not change colour of anything, it
creates new colour instead, thus noun is more applicable.

Also, noun is used in French translation of "random colour" block:
"couleur aléatoire".

* Enforcing non-empty names on value inputs and statement inputs. (#1054)

* Correcting #1054 (#1056)

single quotes. better logic.

* Created a variable model with name, id, and type.

Created a jsunit test file for variable model.

* Change how blockly handles cursors.  The old way was quite slow becau… (#1057)

* Change how blockly handles cursors.  The old way was quite slow because it changed the stylesheet directly.  See issue #981 for more details on implementation and tradeoffs.  This changes makes the following high level changes: deprecate Blockly.Css.setCursor, use built in open and closed hand cursor instead of custom .cur files, add css to draggable objects to set the open and closed hand cursors.

* Rebuild blockly_uncompressed to pick up a testing change to make travis happy.  Fix a build warning from a multi-line string in the process. (#1059)

* Merge master into develop (#1063)

- pick up translation changes
- clean up trailing spaces

* Rebuild for translations

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Cherrypick a fix for #1069 and rebuild (#1075)

* Fix #1069 (#1073)

* rebuild

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Develop to master (#1209)

* Adding new minimap demo

* Basic code style changes. Adding a few more comments. Return early if disableScrollChange in onScrollChange listener.

* Adding horizontal scrolling. Changed scroll change callbacks from onScroll_ to setHandlePosition. onScroll_ is not challed when workspace is dragged.

* Registering mousemove and mouseup listener in mousedown event. Mousemove and Mouseup events are now listening over document.

* Adding the remove variable modal and functionality to accessible Blockly. (#1011)

* Minimap position bug fix for browsers other than chrome. Added touch support.

* Adding an add variable modal to accessible Blockly. (#1015)

* Adding the remove variable modal and functionality to accessible Blockly.

* Adding the add variable modal for accessible Blockly.

* Block browser context menu in the toolbox and flyout

* Add links to the dev registration form and contributor guidelines

* Miscellaneous comment cleanup

* Adding the common modal class. (#1017)

Centralizes accessible modal behavior.

* - Changed error message referencing 'procedure' instead of 'function' (#1019)

- Added iOS specific UI messages
- Fixed bug with js_to_json.py script where it didn't recognize ' character

* - Allows use of Blockly's messaging format for category name, colour,… (#1028)

...in toolbox XML.
- Updated code editor demo to use this message format
- Re-built blockly_compressed.js

* Making text_count use a text color (like text_length, which also returns a number). (#1027)

* Enable google/blockly with continuous build on travis ci (#1023) (#1035)

* create .travis for ci job

* initial checkin for blocky-web travis ci job

* rename file to .travis.yaml for typo

* remove after_script

* added cache

* rename .travis.yaml to .travis.yml

* Update .travis.yml

* include build script

* fix yaml file format issue

* debug install part

* debug build issue

* Update .travis.yml

* remove cache for now

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* more debug info

* Update .travis.yml

* Update .travis.yml

* fix typo

* installing chrome browser

* remove chrome setting config

* run build.py as part of npm install

* Update .travis.yml

* update karma dependency

* use karma as test runner

* fix typo

* remove karma test for now

* Update .travis.yml

* Update package.json

* add npm test target

* add browserstack-runner depdendency

* update browser support

* fix typo for test target

* fix chrome typo

* added closure dependency

* add google-closure-library

* include blockly_uncompressed.js and core.js dependency

* uncomment out core/*.js files

* add kama job as part of install

* remove browserstack add on for now

* fix karma config typo

* add karma-closure

* add os support

* remove typo config

* include more closure files

* change os back to linux

* use closure-library from node_modules

* change log level back to INFO

* change npm test target to use open browser command instead of karma

* change travis test target to use open command instead of karma

* list current directory

* find what's in current dir

* typo command

* Update .travis.yml

* typo again

* open right index.html

* use right path for index.html

* xdg-open to open default browser on travis

* exit browser after 5s wait

* change timeout to 1 min

* exit after opening up browser

* use browser only

* use karma

* remove un-needed dependency

* clean up script section

* fix typo

* update build status on readme

* initial commit for selenium integration tests

* update selenium jar path

* fix test_runner.js typo

* add more debug info

* check java version

* add && instead of 9288

* fix java path

* add logic to check if selenium is running or not

* add some deugging info

* initial commit to get chromedriver

* add chromedriver flag

* add get_chromedriver.sh to package.json and .travel

* change browser to chrome for now

* fix path issue

* update chromdriver path

* fix path issue again

* more debugging

* add debug msg

* fix typo

* minor fix for getting chromedriver

* install latest chrome browser

* clean up pakcage.json

* use npm target for test run

* remove removing trailing comma

* fix another trailing comma

* updated travis test target

* clean up scripts

* not sure nmp run preinstall

* redirect selenium log to tmp file

* revert writing console log to file

* update test summary

* more clean up

* minor clean up before pull request

* resolved closure-library conflict

1. add closure-library to dependencies instead of devDependencies.
2. add lint back in scripts block

* fix typo (adding comma) in script section

* Renames Blockly.workspaceDragSurface to Blockly.WorkspaceDragSurface.

Fixes #880.

* Ensure useDragSurface is a boolean.

Fixed #988

* use pretest instead of preinstall in package.json (#1043)

* cherry pick for pretest fix

* put pretest target to test_setup.sh

* fix conflict

* cherry pick for get_chromedriver.sh

* add some sleep to wait download to finish

* use node.js stable

* use npm test target

* field_angle renders degree symbol consistently.

Fixes #973

* bumpNeighbours_ function moved to block_svg.

Fixed #1009

* Update RegEx in js-to-json to match windowi eol (#1050)

The current regex only works with the "\n" line endings as it expects no characters after the optional ";" at the end of the line. In windows, if it adds the "\r" it counts as a characters and is not part of the line terminator so it doesn't match.

* Fix French translation of "colour with rgb" block (#1053)

"colorier", which is currently used, is a verb and proposed "couleur" is
a noun: the block in question does not change colour of anything, it
creates new colour instead, thus noun is more applicable.

Also, noun is used in French translation of "random colour" block:
"couleur aléatoire".

* Enforcing non-empty names on value inputs and statement inputs. (#1054)

* Correcting #1054 (#1056)

single quotes. better logic.

* Created a variable model with name, id, and type.

Created a jsunit test file for variable model.

* Change how blockly handles cursors.  The old way was quite slow becau… (#1057)

* Change how blockly handles cursors.  The old way was quite slow because it changed the stylesheet directly.  See issue #981 for more details on implementation and tradeoffs.  This changes makes the following high level changes: deprecate Blockly.Css.setCursor, use built in open and closed hand cursor instead of custom .cur files, add css to draggable objects to set the open and closed hand cursors.

* Rebuild blockly_uncompressed to pick up a testing change to make travis happy.  Fix a build warning from a multi-line string in the process. (#1059)

* Merge master into develop (#1063)

- pick up translation changes
- clean up trailing spaces

* use goog.string.startswith instead of string.startswith (#1065)

* New jsinterpreter demo includes wait block. Both demos have improved UI for clarity. (#1001)

Refactor of interpreter demo
 * Renamed demos/interpreter/index.html as demos/interpreter/step-execution.html (including redirect), and added demos/interpreter/async-execution.html.
 * Refactored code to automatically generate/parse the blocks, eliminating the need for a "Parse JavaScript" button. Code is still shown in alert upon stepping to the first statement. Print statements now write to output <textarea> instead of modal dialogs.

* VariableMap and functions added.

* Create separate file for VariableMap and its functions.

* Fix #1069 (#1073)

* VariableMap and functions added.

* Fix #1051 (#1084)

* Improve errors when validating JSON block definitions. (#1086)

goog.asserts to not run from blockly_compressed.js. User data validation should always run.

* Dragging changes, rebased on develop (#1078)

* Add block drag surface translateSurfaceBy

* Add dragged connection manager

* Add gesture.js

* Add GestureHandler

* Implemented gesture skeleton

* Most basic workspace dragging

* Add dragged connection manager

* cleanup

* doc

* more cleanup

* Add gesture handler

* Add translateSurfaceInternal

* core/block_dragger.js

* cleanup

* Pull in changes to dragged connection manager

* Pull in changes to dragged connection manager

* comments

* more annotations

* Add workspace dragger

* Add coordinate annotations

* Start on block dragging

* Limit number of concurrent gestures

* Add some TODOs

* start using dragged connection manager

* Set origin correctly for dragging blocks

* Connect or delete at the end of a block drag.

* cleanup

* handle field clicks and block + workspace right-clicks

* move code into BlockDragger class, but still reach into Gesture internals a lot

* Clean up block dragger

* Call blockDragger constructor with correct arguments

* Enable block dragging in a mutator workspace

* Add workspace dragger

* click todos

* Drag flyout with background

* more dragging from flyout

* nit

* fix dragging from flyouts

* Remove unused code and rename gestureHandler to gestureDB

* Rename gesture handler

* Added some jsdoc in gesture.js

* Update some docs

* Move some code to block_svg and clean up code

* Lots of coordinate annotations

* Fix block dragging when zoomed.

* Remove built files from branch

* More dragging work (#1026)

-- Drag bubbles while dragging blocks
-- Use bindEventWithChecks to work in touch on Android. Not tested anywhere else yet.
-- Handle dragging blocks while zoomed
-- Handle dragging blocks in mutators
-- Handle right-clicks (I hope)
-- Removed lots of unused code

* More dragging work (#1048)

- Removed gestureDB
- Removing uses of terminateDrag
- Cleaned up disposal code

* Dragging bugfixes (#1058)

- Get rid of flyout.dragMode_ and blockly.dragMode_
- Make drags from the flyout start from the top block in the group
- Block tooltips from being scheduled or shown during gestures
- Don't resize mutator bubbles mid-drag

* Fix events in new dragging (#1060)

* rebuild for testing

* unbuild

* Fix events

* rebuild

* Fix up cursors

* Use language files from develop

* Remove handled TODOS

* attempt to fix IE rerendering bug, and recalculate workspace positions on scroll

* Rebuild all the things

* Comment cleanup; annotations; delete unused variables.

* Tidy up context menu code. (#1081)

* add osx travis test run job (#1074)

* Names are correctly fetched from VariableModels!

* add more wait time for test setup (#1091)

* Work around timing issue with travis osx issue (#1092)

* add more wait time for test setup

* increase selenium wait time

* add more wait

* Fix #1077 by adding a rule to cover the toolbox labels too. (#1099)

* Assign variable UUID to field_variable dropdown.

* Change registration link to a static one (#1106)

This lets us redirect to a different form if we change it in the future.

* Edit generators to read in Variable Models.

* Add VariableMap requirement to workspace.

* Changed parameter name in workspace for clarity.

* Add type, id, and info to the generated xml.

Add xml tests for fieldToDom.
Update workspace tests to pass with new changes.

* Fix apostrophe in tooltips and helpurls (#1111)

* Click events on shadow blocks have the correct id (#1089)

* Add image_onclick option (#1080)

* Cleanup: semicolons, spacing, etc. (#1116)

* Spelling.  Spelling is hard.

* Add variable info to xml generated in variables.js

* Add missing CLAs info to the contributing file (#1119)

* Add missing CLAs info to the contributing file

* Added larger changes paragraph

* Replacing latest prettifier hosted in repo with latest version at rawgit CDN. (#1120)

* Forgot update code demo (#1121)

* Move audio code to a new file (#1122)

* move audio code to a new file

* dispose

* null check

* Make flyout get variables from target workspace's variableMap.

* Require VariableModel in field_variable.js.

* Update contributing.md (#1126)

* Include variables at top of serialization.

* Move blockSvg.getHeightWidth to block_render_svg.js (#1118)

* Deserialization variables at top.

* Create grid object (#1131)

* Create grid object

* Doc

* Units!  Thanks @RoboErikG

* Appease eslint: semicolons and such.

* Deserialization variables at top.

* Setting up accessible Blockly for Closure compilation. (#1134)

Moving closure compilation into the build file.

Fixing up goog.requires for accessible blockly. Adding accessible to the build script.

* Fix #1109

* Adding command-line options to the buildscript. (#1136)

* Fix for travis-ci testing failure (#1141)

It looks like the default configuration for Travis might have changed. Adding a manual step to install webdriverio.

* Convert text_join block to JSON + mutator format (#1140)

* Clean up and create test utilities file.

* Add BLOCK events and constructor tests.

* Convert more text blocks to json (#1147)

* Convert more text blocks to json

Converts the mutator blocks for text_join and the text_append block
to JSON format.

* Fixing modals so they're announced on focus, and changing variables t… (#1030)

* Fixing modals so they're announced on focus, and changing variables to only react to enter, not onChange.

* Adding a temp index.

* Whoops - added it in the wrong spot.

* Adding automatically-generated variable lists to the accessible toolbox. (#1149)

Fixing a bug with the core-only uncompressed file not finding its own directory.

* Field Variable setValue() looks up variable.

* Wrap Error in Try Finally Block.

* Changing the build file to allow forced rebuilding of the msg files. (#1158)

* Split flyout into flyout_base, flyout_horizontal, and flyou_vertical

* Rename flyout to flyout_base

* flyout_base minus horizontal and vertical code

* Add flyout_vertical and flyout_horizontal

* review fixes + toolbox and workspace use

* Fix hat block case

* rebuild uncompressed

* Fix travis problem

* Fix build problem

* Add VAR events.

* Correctly named block events called.

* Fire VAR events and test.

* Create utility function for checking variable values.

* In DropdownCreate check for Msg.DELETE_VARIABLE.

* Test Delete Variable Twice.

* Convert more text blocks to JSON format (#1163)

* Convert more text blocks to JSON format

Converts text_charAt, text_indexOf, text_isEmpty, and text_length
to JSON.

Includes a rebuild to pick up message changes.

* Fixing the accessible variable stuff so it interacts correctly with (#1170)

variableMap.

* createVariable in workspaceSvg takes in id and type.

* Followup RefreshToolbox.

* Fixing an error with block messages and ordering. (#1171)

* Remove out of date todo comments.

* Fix checkbox delete bug.

* Fixing variable dropdowns so they select the correct option. (#1184)

* Always open flyout fields are editable.

* Fixing the tree service so it doesn't treat unknown block deletion (#1182)

as an error, and turning off keypresses on the workspace when the
variable modals are open.

* Revamping mostly gesture tests.

* Merge master to develop (#1189)



* Merge master into develop (#1063)

- pick up translation changes
- clean up trailing spaces

* Rebuild for translations

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Cherrypick a fix for #1069 and rebuild (#1075)

* Fix #1069 (#1073)

* rebuild

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Rebuild compressed files

* Add some translatewiki annotations back into msg/messages.js

* Rebuild msg files

* Fix flyout dropdown bug.

* Fix python and php procedures.

* Dropdown Create does not create a missing variable twice.

* Fire field variable change event with variable value, not name.

* Fix #1160 (#1197)

Fix "Connection UI Effect not playing on block connect"

* Only add a block in the flyout if it is not disabled (#1204)

* Only add a block in the flyout if it is not disabled

* PR feedback

* remove previous fix

* Fix issue with compression stripping dropdown options (#1207)

* Get some accessible files back from develop

* rebuild

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Rebuild all the things

* Fix Blockly.Procedures.isNameUsed return values.

Add tests for Procedures.isNameUsed() so this bug never happens again.

* rebuild
2017-08-01 10:50:42 -07:00
marisaleung 0fce3e9a3d Merge pull request #1238 from marisaleung/develop_fixFunctionTouch
Fix Blockly.Procedures.isNameUsed return values.
2017-08-01 09:55:18 -07:00
Neil Fraser fc8d4c90b4 Compatibility for Closure Compiler. (#1240) 2017-07-31 09:07:41 -07:00
marisaleung 234c53157f Fix Blockly.Procedures.isNameUsed return values.
Add tests for Procedures.isNameUsed() so this bug never happens again.
2017-07-28 12:02:14 -07:00
marisaleung 022010d722 Merge pull request #1233 from marisaleung/develop_addOptTypeInCreateVariable
Add opt_type to Blockly.Variables.CreateVariable.
2017-07-21 14:54:08 -07:00
marisaleung db2f9a2b09 Add opt_type to Blockly.Variables.CreateVariable. 2017-07-21 13:43:13 -07:00
marisaleung baf2d68262 Merge pull request #1230 from marisaleung/develop_addVariableTypes
Add VariableType checks.
2017-07-21 10:54:59 -07:00
marisaleung 733d869f8e Add VariableType checks. 2017-07-20 16:47:37 -07:00
Rachel Fenichel d2f7d5a71a Use the same math for dragging blocks out of horizontal and vertical flyouts. (#1215)
* Use the same math for dragging blocks out of horizontal and vertical flyouts.

* Update flyout_base.js
2017-07-19 11:30:08 -07:00
Tom c85106a920 Typo in a deprecated variable's value (#1228)
Misspelled variable name, should be Blockly.PROCEDURE_CATEGORY_NAME.
2017-07-18 11:23:29 -07:00
marisaleung 85c73cd501 Merge pull request #1223 from marisaleung/develop_phpPythonProceduresWorkspace
Fix code generation for procedures (PHP, Python).
2017-07-17 15:13:04 -07:00
marisaleung 47872b4ada Fix code generation for procedures (PHP, Python). 2017-07-17 11:42:36 -07:00
Tony Lian 071798546c Modify the colour_rgb function to match other languages (#1210)
There are two tests fail before modifying the color_rgb function because it behaves differently in Dart and in other languages. In Dart, this function takes parameters ranging from 0 to 1.0 while in other languages such as Lua the counterpart function takes parameters ranging from 0 to 100. Now I have modified it to let it behave the same as other languages.
2017-07-11 16:43:50 -07:00
Andrew n marshall abcc9b82a1 Adding BlocklyDevTools.Analytics (#1217)
Adding BlocklyDevTools.Analytics, an interface for integrating an analytics
library to track basic usage, including:
 * navigation.
 * saving, importing, and exporting.
 * warnings and errors.
2017-07-11 15:39:35 -07:00
marisaleung ca2f0cacf4 Merge pull request #1213 from marisaleung/develop_modularizeRenameVariablePrompt
Modularize Rename Variable prompt and allow custom context menus for …
2017-07-07 15:16:24 -07:00
marisaleung 170b16706f Modularize Rename Variable prompt and allow custom context menus for flyout. 2017-07-06 15:41:18 -07:00
Tim Dawborn 389a41eedb Change the build process to be deterministic (#1154)
* Change the Blockly build process to be deterministic across machines.

* A couple more missed locations for deterministic ordering.
2017-07-05 14:21:10 -07:00
RoboErikG 1539941427 Fix issue with compression stripping dropdown options (#1207) 2017-06-30 15:48:20 -07:00
Sam El-Husseini 21b05d2a69 Only add a block in the flyout if it is not disabled (#1204)
* Only add a block in the flyout if it is not disabled

* PR feedback

* remove previous fix
2017-06-30 14:51:18 -07:00
Rachel Fenichel de67108b17 Fix #1160 (#1197)
Fix "Connection UI Effect not playing on block connect"
2017-06-29 15:13:56 -07:00
marisaleung 8d0d5d8b46 Merge pull request #1196 from marisaleung/develop_FireCorrectChangeEventsForVariableDropdowns
Fire field variable change event with variable value, not name.
2017-06-29 11:25:17 -07:00
marisaleung 15d47840b1 Fire field variable change event with variable value, not name. 2017-06-29 11:24:25 -07:00
marisaleung 3a1da94027 Merge pull request #1194 from marisaleung/develop_dropdownCreateVariable
Dropdown Create does not create a missing variable twice.
2017-06-29 10:22:12 -07:00
marisaleung 3c8e0ebbeb Dropdown Create does not create a missing variable twice. 2017-06-27 16:51:19 -07:00
marisaleung 63b8cee01e Merge pull request #1191 from marisaleung/develop
Fix python and php procedures.
2017-06-26 15:50:38 -07:00
marisaleung 693bdbb10e Fix python and php procedures. 2017-06-26 11:51:32 -07:00
marisaleung 997487568d Merge pull request #1193 from marisaleung/develop_fixDropdownBug
Fix flyout dropdown bug.
2017-06-26 11:17:56 -07:00
marisaleung fba60bf7a5 Fix flyout dropdown bug. 2017-06-26 10:41:17 -07:00
Rachel Fenichel 9053dbf9b4 Merge master to develop (#1189)
* Merge master into develop (#1063)

- pick up translation changes
- clean up trailing spaces

* Rebuild for translations

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Cherrypick a fix for #1069 and rebuild (#1075)

* Fix #1069 (#1073)

* rebuild

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Rebuild compressed files

* Add some translatewiki annotations back into msg/messages.js

* Rebuild msg files
2017-06-23 16:54:59 -07:00
marisaleung 59e87e5a5c Merge pull request #1187 from marisaleung/develop_moreTests
Revamping mostly gesture tests.
2017-06-23 10:40:20 -07:00
marisaleung 4a2c95fd1b Revamping mostly gesture tests. 2017-06-22 16:56:28 -07:00
CoryDCode ac5b322de5 Fixing the tree service so it doesn't treat unknown block deletion (#1182)
as an error, and turning off keypresses on the workspace when the
variable modals are open.
2017-06-22 14:44:02 -07:00
marisaleung 11167ca28c Merge pull request #1181 from marisaleung/develop_fixCheckBoxBug
Fix checkbox delete bug.
2017-06-22 14:41:48 -07:00
marisaleung 5d91051ec7 Merge pull request #1186 from marisaleung/develop_flyoutCLickBug
Bug fix: always open flyout fields are not editable.
2017-06-22 11:24:45 -07:00
marisaleung 95594bd070 Always open flyout fields are editable. 2017-06-22 10:59:12 -07:00
CoryDCode d4bf02a451 Fixing variable dropdowns so they select the correct option. (#1184) 2017-06-21 19:05:36 -07:00
marisaleung 7c71b9e7fc Fix checkbox delete bug. 2017-06-20 15:13:10 -07:00
marisaleung 912712cbf5 Merge pull request #1180 from marisaleung/develop_removeTodos
Remove out of date todo comments.
2017-06-20 15:08:59 -07:00
marisaleung 6869b68ba0 Merge pull request #1178 from marisaleung/refreshToolbox
Followup RefreshToolbox.
2017-06-20 13:55:43 -07:00
marisaleung d937920afc Remove out of date todo comments. 2017-06-20 13:15:28 -07:00
CoryDCode 668d220faa Fixing an error with block messages and ordering. (#1171) 2017-06-20 12:55:27 -07:00
marisaleung 7b664d90e9 Followup RefreshToolbox. 2017-06-20 10:40:20 -07:00
marisaleung d7e05a6f5c Merge pull request #1177 from marisaleung/develop_fixworkspaceSvg
createVariable in workspaceSvg takes in id and type.
2017-06-19 17:11:37 -07:00
marisaleung 803681efa1 createVariable in workspaceSvg takes in id and type. 2017-06-19 17:06:07 -07:00
CoryDCode 1ee550aa24 Fixing the accessible variable stuff so it interacts correctly with (#1170)
variableMap.
2017-06-13 09:47:27 -07:00
RoboErikG df84c71c66 Convert more text blocks to JSON format (#1163)
* Convert more text blocks to JSON format

Converts text_charAt, text_indexOf, text_isEmpty, and text_length
to JSON.

Includes a rebuild to pick up message changes.
2017-06-12 10:36:36 -07:00
marisaleung 884703b057 Merge pull request #1166 from marisaleung/develop_FireVarEvents
Test Delete Variable Twice.
2017-06-09 16:54:34 -07:00
marisaleung 5b1a0ae284 Test Delete Variable Twice. 2017-06-09 16:32:16 -07:00
marisaleung d8eb2fb4f4 Merge pull request #1165 from marisaleung/develop_checkForDeleteVariable
In DropdownCreate check for Msg.DELETE_VARIABLE.
2017-06-09 16:26:03 -07:00
marisaleung d852a2b116 In DropdownCreate check for Msg.DELETE_VARIABLE. 2017-06-09 16:22:25 -07:00
marisaleung bb3ebfa93e Merge pull request #1164 from marisaleung/develop_checkVariableValuesUtilityFunction
Create utility function for checking variable values.
2017-06-09 15:36:48 -07:00
marisaleung c58553b7ba Create utility function for checking variable values. 2017-06-09 15:24:31 -07:00
marisaleung e6c70df6f8 Merge pull request #1162 from marisaleung/develop_FireVarEvents
Fire VAR events and test.
2017-06-09 15:21:51 -07:00
marisaleung 833c2d0c40 Fire VAR events and test. 2017-06-09 14:42:14 -07:00
marisaleung 7b29eca8e7 Merge pull request #1150 from marisaleung/develop_varEvents
Add VAR events.
2017-06-08 15:54:29 -07:00
marisaleung 5bd966b46a Merge pull request #1151 from marisaleung/addBlockandVarEvents
Correctly named block events called.
2017-06-08 15:46:00 -07:00
marisaleung aff9c4867c Correctly named block events called. 2017-06-08 15:44:43 -07:00
marisaleung e9a480c51f Add VAR events. 2017-06-08 15:15:17 -07:00
Rachel Fenichel 2d532225d3 Split flyout into flyout_base, flyout_horizontal, and flyou_vertical
* Rename flyout to flyout_base

* flyout_base minus horizontal and vertical code

* Add flyout_vertical and flyout_horizontal

* review fixes + toolbox and workspace use

* Fix hat block case

* rebuild uncompressed

* Fix travis problem

* Fix build problem
2017-06-08 13:36:11 -07:00
CoryDCode f9e8d0fff2 Changing the build file to allow forced rebuilding of the msg files. (#1158) 2017-06-07 11:38:34 -07:00
marisaleung d6ea50b8b8 Merge pull request #1157 from marisaleung/wrapInTryCatch
Wrap Error in Try Finally Block.
2017-06-07 10:26:31 -07:00
marisaleung 7f873b12c4 Wrap Error in Try Finally Block. 2017-06-07 10:00:04 -07:00
marisaleung 7e18ddd218 Merge pull request #1156 from marisaleung/develop_accessiblebugfix
Field Variable setValue() looks up variable.
2017-06-06 15:31:05 -07:00
marisaleung 0040ff636c Field Variable setValue() looks up variable. 2017-06-06 15:29:28 -07:00
CoryDCode f48a68a9ef Adding automatically-generated variable lists to the accessible toolbox. (#1149)
Fixing a bug with the core-only uncompressed file not finding its own directory.
2017-06-05 13:42:51 -07:00
CoryDCode 7336d03538 Fixing modals so they're announced on focus, and changing variables t… (#1030)
* Fixing modals so they're announced on focus, and changing variables to only react to enter, not onChange.

* Adding a temp index.

* Whoops - added it in the wrong spot.
2017-06-01 17:30:50 -07:00
marisaleung 1a6ac798eb Merge pull request #1148 from marisaleung/addBlockandVarEvents
Add block events.
2017-06-01 15:35:45 -07:00
RoboErikG fa72e02dc5 Convert more text blocks to json (#1147)
* Convert more text blocks to json

Converts the mutator blocks for text_join and the text_append block
to JSON format.
2017-06-01 14:21:16 -07:00
marisaleung ccf3ad1473 Add BLOCK events and constructor tests. 2017-06-01 12:38:38 -07:00
marisaleung 231f650cbe Merge pull request #1146 from marisaleung/createTestUtilitiesFile
Clean up and create test utilities file.
2017-06-01 11:03:42 -07:00
marisaleung cd0487e816 Clean up and create test utilities file. 2017-06-01 10:36:01 -07:00
marisaleung 47cc79eb19 Merge pull request #1137 from marisaleung/develop_bugfix
Fix #1109
2017-05-31 15:50:11 -07:00
RoboErikG 039f536cf3 Convert text_join block to JSON + mutator format (#1140) 2017-05-31 10:22:44 -07:00
RoboErikG 12b7db6207 Fix for travis-ci testing failure (#1141)
It looks like the default configuration for Travis might have changed. Adding a manual step to install webdriverio.
2017-05-30 17:25:32 -07:00
CoryDCode 50c1eb995f Adding command-line options to the buildscript. (#1136) 2017-05-30 15:26:23 -07:00
marisaleung ca6fc462c4 Fix #1109 2017-05-26 17:25:23 -07:00
marisaleung 4c4cc7bb3b Merge pull request #1135 from marisaleung/develop_deserialization_variables_at_top
Deserialization variables at top.
2017-05-26 14:52:20 -07:00
marisaleung 5415c899d7 Merge pull request #1124 from marisaleung/develop_appease_lint
Appease eslint: semicolons and such.
2017-05-26 14:30:40 -07:00
CoryDCode e2ee3aa9c9 Setting up accessible Blockly for Closure compilation. (#1134)
Moving closure compilation into the build file.

Fixing up goog.requires for accessible blockly. Adding accessible to the build script.
2017-05-26 10:43:02 -07:00
marisaleung f7f664063b Deserialization variables at top. 2017-05-25 14:03:37 -07:00
marisaleung a6f85cec53 Appease eslint: semicolons and such. 2017-05-25 13:44:06 -07:00
Rachel Fenichel c0b906f4b6 Create grid object (#1131)
* Create grid object

* Doc

* Units!  Thanks @RoboErikG
2017-05-24 16:14:24 -07:00
marisaleung bebbf8fd39 Merge pull request #1130 from marisaleung/develop_deserialization_variables_at_top
Develop deserialization variables at top
2017-05-24 15:35:30 -07:00
marisaleung dc06872a37 Deserialization variables at top. 2017-05-24 15:29:46 -07:00
Rachel Fenichel 71066faff6 Move blockSvg.getHeightWidth to block_render_svg.js (#1118) 2017-05-24 13:38:46 -07:00
marisaleung 3d1420ba5f Merge pull request #1127 from marisaleung/develop_top_variables_dom
Include Variables at the top of dom.
2017-05-24 11:03:09 -07:00
marisaleung ce4a84b3cf Include variables at top of serialization. 2017-05-24 10:53:44 -07:00
marisaleung b5ad5c6185 Merge pull request #1125 from marisaleung/develop_toolbox_uses_targetworkspace_variableMap
Make flyout get variables from target workspace's variableMap.
2017-05-24 10:39:50 -07:00
Rachel Fenichel 3f58abfd8e Update contributing.md (#1126) 2017-05-23 14:59:39 -07:00
marisaleung c246e41bec Merge pull request #1123 from marisaleung/develop_fix_require
Require VariableModel in field_variable.js.
2017-05-23 14:00:19 -07:00
marisaleung 7603f9aa69 Require VariableModel in field_variable.js. 2017-05-23 13:36:24 -07:00
marisaleung 7d57143f75 Make flyout get variables from target workspace's variableMap. 2017-05-23 11:59:13 -07:00
marisaleung e84121690b Merge pull request #1113 from marisaleung/SmallFix
Small fix
2017-05-23 11:40:39 -07:00
marisaleung 67906939cd Merge pull request #1117 from marisaleung/develop_updateVariablesXml
Add variable info to xml generated in variables.js
2017-05-23 11:40:02 -07:00
Rachel Fenichel 620a210106 Move audio code to a new file (#1122)
* move audio code to a new file

* dispose

* null check
2017-05-22 16:58:31 -07:00
Andrew n marshall 10ad450176 Forgot update code demo (#1121) 2017-05-22 12:34:22 -07:00
Andrew n marshall 3840b3804f Replacing latest prettifier hosted in repo with latest version at rawgit CDN. (#1120) 2017-05-22 12:09:23 -07:00
RoboErikG c53605fef4 Add missing CLAs info to the contributing file (#1119)
* Add missing CLAs info to the contributing file

* Added larger changes paragraph
2017-05-22 11:33:30 -07:00
marisaleung 3b34038445 Add variable info to xml generated in variables.js 2017-05-19 17:07:12 -07:00
Rachel Fenichel 65908e8874 Spelling. Spelling is hard. 2017-05-19 16:31:12 -07:00
Rachel Fenichel 104958e25f Cleanup: semicolons, spacing, etc. (#1116) 2017-05-19 16:27:48 -07:00
Derek Brown 63fe91180b Add image_onclick option (#1080) 2017-05-19 16:18:54 -07:00
Rachel Fenichel d4694b49d3 Click events on shadow blocks have the correct id (#1089) 2017-05-19 16:16:01 -07:00
Rachel Fenichel 7395c24bf0 Fix apostrophe in tooltips and helpurls (#1111) 2017-05-19 16:14:53 -07:00
marisaleung 90eeaae8bf Merge pull request #1112 from marisaleung/Add_type_and_id_info_to_xml
Add type and id info to xml
2017-05-19 15:49:02 -07:00
marisaleung 1cd8e1fcc1 Add type, id, and info to the generated xml.
Add xml tests for fieldToDom.
Update workspace tests to pass with new changes.
2017-05-19 15:47:39 -07:00
marisaleung cd5bb0d888 Changed parameter name in workspace for clarity. 2017-05-18 14:14:17 -07:00
marisaleung eeaf92fe85 Merge pull request #1108 from marisaleung/FixRequire
Fix require
2017-05-17 15:01:09 -07:00
marisaleung 5fddba7545 Add VariableMap requirement to workspace. 2017-05-17 14:51:29 -07:00
marisaleung 78e4977bdc Merge pull request #1107 from marisaleung/develop
Edit generators to read in Variable Models.
2017-05-16 17:28:52 -07:00
marisaleung dc97909abd Edit generators to read in Variable Models. 2017-05-15 12:19:04 -07:00
RoboErikG 14e4f1905e Change registration link to a static one (#1106)
This lets us redirect to a different form if we change it in the future.
2017-05-15 11:31:17 -07:00
marisaleung 8d5edb3e55 Merge pull request #1096 from marisaleung/develop
Assign variable UUID to field_variable dropdown.
2017-05-15 10:56:56 -07:00
marisaleung 6492f2988a Assign variable UUID to field_variable dropdown. 2017-05-15 10:43:53 -07:00
picklesrus 48a5270072 Fix #1077 by adding a rule to cover the toolbox labels too. (#1099) 2017-05-12 11:29:52 -07:00
shirletan 7a2c01e1cf Work around timing issue with travis osx issue (#1092)
* add more wait time for test setup

* increase selenium wait time

* add more wait
2017-05-09 16:39:39 -07:00
shirletan 65e374e309 add more wait time for test setup (#1091) 2017-05-09 10:50:27 -07:00
marisaleung cc93e1c6e8 Merge pull request #1085 from marisaleung/develop
Names are correctly fetched from VariableModels.
2017-05-09 10:03:37 -07:00
marisaleung 2bb258165a Names are correctly fetched from VariableModels! 2017-05-09 08:50:11 -07:00
shirletan f8fa9748e9 add osx travis test run job (#1074) 2017-05-05 13:12:53 -07:00
Rachel Fenichel 920422196f Tidy up context menu code. (#1081) 2017-05-05 12:57:40 -07:00
Rachel Fenichel 98914fcf6b Dragging changes, rebased on develop (#1078)
* Add block drag surface translateSurfaceBy

* Add dragged connection manager

* Add gesture.js

* Add GestureHandler

* Implemented gesture skeleton

* Most basic workspace dragging

* Add dragged connection manager

* cleanup

* doc

* more cleanup

* Add gesture handler

* Add translateSurfaceInternal

* core/block_dragger.js

* cleanup

* Pull in changes to dragged connection manager

* Pull in changes to dragged connection manager

* comments

* more annotations

* Add workspace dragger

* Add coordinate annotations

* Start on block dragging

* Limit number of concurrent gestures

* Add some TODOs

* start using dragged connection manager

* Set origin correctly for dragging blocks

* Connect or delete at the end of a block drag.

* cleanup

* handle field clicks and block + workspace right-clicks

* move code into BlockDragger class, but still reach into Gesture internals a lot

* Clean up block dragger

* Call blockDragger constructor with correct arguments

* Enable block dragging in a mutator workspace

* Add workspace dragger

* click todos

* Drag flyout with background

* more dragging from flyout

* nit

* fix dragging from flyouts

* Remove unused code and rename gestureHandler to gestureDB

* Rename gesture handler

* Added some jsdoc in gesture.js

* Update some docs

* Move some code to block_svg and clean up code

* Lots of coordinate annotations

* Fix block dragging when zoomed.

* Remove built files from branch

* More dragging work (#1026)

-- Drag bubbles while dragging blocks
-- Use bindEventWithChecks to work in touch on Android. Not tested anywhere else yet.
-- Handle dragging blocks while zoomed
-- Handle dragging blocks in mutators
-- Handle right-clicks (I hope)
-- Removed lots of unused code

* More dragging work (#1048)

- Removed gestureDB
- Removing uses of terminateDrag
- Cleaned up disposal code

* Dragging bugfixes (#1058)

- Get rid of flyout.dragMode_ and blockly.dragMode_
- Make drags from the flyout start from the top block in the group
- Block tooltips from being scheduled or shown during gestures
- Don't resize mutator bubbles mid-drag

* Fix events in new dragging (#1060)

* rebuild for testing

* unbuild

* Fix events

* rebuild

* Fix up cursors

* Use language files from develop

* Remove handled TODOS

* attempt to fix IE rerendering bug, and recalculate workspace positions on scroll

* Rebuild all the things

* Comment cleanup; annotations; delete unused variables.
2017-05-05 12:42:53 -07:00
Andrew n marshall 8c46d7c798 Improve errors when validating JSON block definitions. (#1086)
goog.asserts to not run from blockly_compressed.js. User data validation should always run.
2017-05-05 11:14:54 -07:00
Rachel Fenichel 55096aeb16 Fix #1051 (#1084) 2017-05-05 09:36:24 -07:00
marisaleung ee58eb4791 Merge pull request #1079 from marisaleung/develop
Created separate file for VariableMap
2017-05-04 12:41:36 -07:00
marisaleung 6d882cb2e1 Merge branch 'develop' into develop 2017-05-04 12:40:49 -07:00
marisaleung 0e0b1a3ebd Merge branch 'develop' into develop 2017-05-04 12:38:15 -07:00
marisaleung 12ea998155 Created separate file for VariableMap 2017-05-03 17:04:15 -07:00
marisaleung 2b1fe1c4e3 Merge pull request #1071 from marisaleung/develop
VariableMap and functions added.
2017-05-03 13:20:05 -07:00
marisaleung d903b5e86b VariableMap and functions added. 2017-05-03 10:51:24 -07:00
Rachel Fenichel 507c13ed33 Fix #1069 (#1073) 2017-05-02 17:06:03 -07:00
marisaleung b159c7c3b5 Create separate file for VariableMap and its functions. 2017-05-02 15:06:12 -07:00
marisaleung 2994805e6a VariableMap and functions added. 2017-05-02 10:07:46 -07:00
Andrew n marshall fb020f1c06 New jsinterpreter demo includes wait block. Both demos have improved UI for clarity. (#1001)
Refactor of interpreter demo
 * Renamed demos/interpreter/index.html as demos/interpreter/step-execution.html (including redirect), and added demos/interpreter/async-execution.html.
 * Refactored code to automatically generate/parse the blocks, eliminating the need for a "Parse JavaScript" button. Code is still shown in alert upon stepping to the first statement. Print statements now write to output <textarea> instead of modal dialogs.
2017-05-01 17:28:34 -07:00
Rachel Fenichel db26c4c541 use goog.string.startswith instead of string.startswith (#1065) 2017-04-25 14:12:21 -07:00
Rachel Fenichel 74adf30355 Merge master into develop (#1063)
- pick up translation changes
- clean up trailing spaces
2017-04-24 16:08:21 -07:00
picklesrus 28572dadac Rebuild blockly_uncompressed to pick up a testing change to make travis happy. Fix a build warning from a multi-line string in the process. (#1059) 2017-04-21 16:20:13 -07:00
picklesrus 52f76013b5 Change how blockly handles cursors. The old way was quite slow becau… (#1057)
* Change how blockly handles cursors.  The old way was quite slow because it changed the stylesheet directly.  See issue #981 for more details on implementation and tradeoffs.  This changes makes the following high level changes: deprecate Blockly.Css.setCursor, use built in open and closed hand cursor instead of custom .cur files, add css to draggable objects to set the open and closed hand cursors.
2017-04-21 14:57:54 -07:00
marisaleung 54178c65ae Merge pull request #1055 from marisaleung/develop
Create a variable model with name, id, and type.
2017-04-20 13:31:22 -07:00
marisaleung 9577e5ddc4 Created a variable model with name, id, and type.
Created a jsunit test file for variable model.
2017-04-20 13:19:53 -07:00
Andrew n marshall 1c45724e37 Correcting #1054 (#1056)
single quotes. better logic.
2017-04-19 14:38:22 -07:00
Andrew n marshall 25788a016e Enforcing non-empty names on value inputs and statement inputs. (#1054) 2017-04-19 11:03:16 -07:00
marisaleung 2588ff8353 Merge pull request #1049 from marisaleung/develop
bumpNeighbours_ function moved to block_svg.
2017-04-19 10:25:18 -07:00
appr 25555b5c20 Fix French translation of "colour with rgb" block (#1053)
"colorier", which is currently used, is a verb and proposed "couleur" is
a noun: the block in question does not change colour of anything, it
creates new colour instead, thus noun is more applicable.

Also, noun is used in French translation of "random colour" block:
"couleur aléatoire".
2017-04-19 06:17:50 -07:00
Carlos b1aadd109b Update RegEx in js-to-json to match windowi eol (#1050)
The current regex only works with the "\n" line endings as it expects no characters after the optional ";" at the end of the line. In windows, if it adds the "\r" it counts as a characters and is not part of the line terminator so it doesn't match.
2017-04-18 13:54:12 -07:00
marisaleung 21bf339e5d bumpNeighbours_ function moved to block_svg.
Fixed #1009
2017-04-17 15:22:53 -07:00
marisaleung bae3493980 Merge pull request #1044 from marisaleung/develop
field_angle renders degree symbol consistently.
2017-04-17 09:59:21 -07:00
marisaleung bc1ca547a4 field_angle renders degree symbol consistently.
Fixes #973
2017-04-14 12:17:09 -07:00
shirletan 23e5728ff4 use pretest instead of preinstall in package.json (#1043)
* cherry pick for pretest fix

* put pretest target to test_setup.sh

* fix conflict

* cherry pick for get_chromedriver.sh

* add some sleep to wait download to finish

* use node.js stable

* use npm test target
2017-04-13 15:23:48 -07:00
marisaleung 606adccf38 Merge pull request #1039 from marisaleung/develop
Ensure useDragSurface is a boolean.
2017-04-11 15:42:53 -07:00
marisaleung e4edc3899a Ensure useDragSurface is a boolean.
Fixed #988
2017-04-11 15:33:51 -07:00
marisaleung 50831a953a Merge pull request #1038 from marisaleung/develop
Renames Blockly.workspaceDragSurface to Blockly.WorkspaceDragSurface.
2017-04-11 15:30:43 -07:00
marisaleung f19b2c87aa Renames Blockly.workspaceDragSurface to Blockly.WorkspaceDragSurface.
Fixes #880.
2017-04-11 14:01:25 -07:00
RoboErikG a8da16036e Enable google/blockly with continuous build on travis ci (#1023) (#1035)
* create .travis for ci job

* initial checkin for blocky-web travis ci job

* rename file to .travis.yaml for typo

* remove after_script

* added cache

* rename .travis.yaml to .travis.yml

* Update .travis.yml

* include build script

* fix yaml file format issue

* debug install part

* debug build issue

* Update .travis.yml

* remove cache for now

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* more debug info

* Update .travis.yml

* Update .travis.yml

* fix typo

* installing chrome browser

* remove chrome setting config

* run build.py as part of npm install

* Update .travis.yml

* update karma dependency

* use karma as test runner

* fix typo

* remove karma test for now

* Update .travis.yml

* Update package.json

* add npm test target

* add browserstack-runner depdendency

* update browser support

* fix typo for test target

* fix chrome typo

* added closure dependency

* add google-closure-library

* include blockly_uncompressed.js and core.js dependency

* uncomment out core/*.js files

* add kama job as part of install

* remove browserstack add on for now

* fix karma config typo

* add karma-closure

* add os support

* remove typo config

* include more closure files

* change os back to linux

* use closure-library from node_modules

* change log level back to INFO

* change npm test target to use open browser command instead of karma

* change travis test target to use open command instead of karma

* list current directory

* find what's in current dir

* typo command

* Update .travis.yml

* typo again

* open right index.html

* use right path for index.html

* xdg-open to open default browser on travis

* exit browser after 5s wait

* change timeout to 1 min

* exit after opening up browser

* use browser only

* use karma

* remove un-needed dependency

* clean up script section

* fix typo

* update build status on readme

* initial commit for selenium integration tests

* update selenium jar path

* fix test_runner.js typo

* add more debug info

* check java version

* add && instead of 9288

* fix java path

* add logic to check if selenium is running or not

* add some deugging info

* initial commit to get chromedriver

* add chromedriver flag

* add get_chromedriver.sh to package.json and .travel

* change browser to chrome for now

* fix path issue

* update chromdriver path

* fix path issue again

* more debugging

* add debug msg

* fix typo

* minor fix for getting chromedriver

* install latest chrome browser

* clean up pakcage.json

* use npm target for test run

* remove removing trailing comma

* fix another trailing comma

* updated travis test target

* clean up scripts

* not sure nmp run preinstall

* redirect selenium log to tmp file

* revert writing console log to file

* update test summary

* more clean up

* minor clean up before pull request

* resolved closure-library conflict

1. add closure-library to dependencies instead of devDependencies.
2. add lint back in scripts block

* fix typo (adding comma) in script section
2017-04-11 13:38:11 -07:00
Andrew n marshall a21d04b804 Making text_count use a text color (like text_length, which also returns a number). (#1027) 2017-04-10 16:26:30 -07:00
vicng ae2aaa2159 - Allows use of Blockly's messaging format for category name, colour,… (#1028)
...in toolbox XML.
- Updated code editor demo to use this message format
- Re-built blockly_compressed.js
2017-04-07 15:32:50 -07:00
vicng dfc50ba843 - Changed error message referencing 'procedure' instead of 'function' (#1019)
- Added iOS specific UI messages
- Fixed bug with js_to_json.py script where it didn't recognize ' character
2017-04-06 12:41:09 -07:00
CoryDCode 33355415df Adding the common modal class. (#1017)
Centralizes accessible modal behavior.
2017-04-05 16:06:45 -07:00
Rachel Fenichel 4b44635867 Merge pull request #1025 from rachel-fenichel/cleanup/misc
Miscellaneous comment cleanup
2017-04-05 16:06:25 -07:00
Rachel Fenichel f2f522d77a Miscellaneous comment cleanup 2017-04-05 15:52:49 -07:00
Rachel Fenichel 352719235d Merge pull request #1018 from rachel-fenichel/bugfix/block_context_menu
Block browser context menu in the toolbox and flyout
2017-04-05 10:42:27 -07:00
RoboErikG ba37894b51 Merge pull request #1021 from google/RoboErikG-readme-dev
Add links to the dev registration form and contributor guidelines
2017-04-04 16:47:39 -07:00
RoboErikG eea01e3e06 Add links to the dev registration form and contributor guidelines 2017-04-04 16:40:40 -07:00
Rachel Fenichel 077547ec90 Block browser context menu in the toolbox and flyout 2017-04-04 14:36:01 -07:00
CoryDCode 8e199ec04b Adding an add variable modal to accessible Blockly. (#1015)
* Adding the remove variable modal and functionality to accessible Blockly.

* Adding the add variable modal for accessible Blockly.
2017-04-03 16:16:50 -07:00
Rachel Fenichel 1ad3359730 Merge pull request #965 from K-ran/feature/minimap
Adding new minimap demo.
2017-04-03 11:30:49 -07:00
Karan Purohit 619237c05d Minimap position bug fix for browsers other than chrome. Added touch support. 2017-04-02 13:31:46 +05:30
CoryDCode 537bf17b7d Adding the remove variable modal and functionality to accessible Blockly. (#1011) 2017-03-30 11:31:31 -07:00
Karan Purohit ac18e207a2 Registering mousemove and mouseup listener in mousedown event. Mousemove and Mouseup events are now listening over document. 2017-03-14 08:58:03 +05:30
Karan Purohit 4e9055a343 Adding horizontal scrolling. Changed scroll change callbacks from onScroll_ to setHandlePosition. onScroll_ is not challed when workspace is dragged. 2017-03-12 15:29:05 +05:30
Karan Purohit f77325f289 Basic code style changes. Adding a few more comments. Return early if disableScrollChange in onScrollChange listener. 2017-03-08 10:17:38 +05:30
Karan Purohit 738d5d6669 Adding new minimap demo 2017-03-08 00:12:22 +05:30
303 arquivos alterados com 56841 adições e 32320 exclusões
+1 -1
Ver Arquivo
@@ -5,4 +5,4 @@ npm-debug.log
.project
*.pyc
*.komodoproject
/nbproject/private/
/nbproject/private/
+30
Ver Arquivo
@@ -0,0 +1,30 @@
language: node_js
matrix:
include:
- os: linux
dist: trusty
node_js: stable
sudo: required
addons:
apt:
packages:
- google-chrome-stable
- os: osx
node_js: stable
osx_image: xcode8.3
before_install:
- npm install google-closure-library
- npm install webdriverio
# Symlink closure library
- ln -s $(npm root)/google-closure-library ../closure-library
before_script:
- export DISPLAY=:99.0
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then ( scripts/setup_linux_env.sh ) fi
- if [ "${TRAVIS_OS_NAME}" == "osx" ]; then ( scripts/setup_osx_env.sh ) fi
- sleep 2
script:
- set -x
- npm test
+37 -2
Ver Arquivo
@@ -1,5 +1,40 @@
# Contributing to Blockly
Please make pull requests against develop, not master. If your patch needs to go into master immediately, include a note in your PR.
Want to contribute? Great!
- First, read this page (including the small print at the end).
- Second, please make pull requests against develop, not master. If your patch
needs to go into master immediately, include a note in your PR.
For more information, head over to the [Blockly Developers site](https://developers.google.com/blockly/guides/modify/contributing).
For more information on style guide and other details, head over to the [Blockly Developers site](https://developers.google.com/blockly/guides/modify/contributing).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
### Larger changes
Before you start working on a larger contribution, you should get in touch with
us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
### Code reviews
All submissions, including submissions by project members, require review. We
use Github pull requests for this purpose.
### Browser compatibility
We care strongly about making Blockly work on all browsers. As of 2017 we
support IE 10 and 11, Edge, Chrome, Safari, and Firefox. We will not accept
changes that only work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/)
for compatibility information.
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the
[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
+8 -1
Ver Arquivo
@@ -1,4 +1,5 @@
# Blockly
# Blockly [![Build Status]( https://travis-ci.org/google/blockly.svg?branch=master)](https://travis-ci.org/google/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.
@@ -8,3 +9,9 @@ blocks together to build programs. All code is free and open source.
![](https://developers.google.com/blockly/images/sample.png)
Blockly has an active [developer forum](https://groups.google.com/forum/#!forum/blockly). Please drop by and say hello. Show us your prototypes early; collectively we have a lot of experience and can offer hints which will save you time.
Help us focus our development efforts by telling us [what you are doing with
Blockly](https://developers.google.com/blockly/registration). The questionnaire only takes
a few minutes and will help us better support the Blockly community.
Want to contribute? Great! First, read [our guidelines for contributors](https://developers.google.com/blockly/guides/modify/contributing).
+30 -3
Ver Arquivo
@@ -22,6 +22,29 @@
* @author madeeha@google.com (Madeeha Ghori)
*/
goog.provide('blocklyApp.AppComponent');
goog.require('Blockly');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.BlockConnectionService');
goog.require('blocklyApp.BlockOptionsModalComponent');
goog.require('blocklyApp.BlockOptionsModalService');
goog.require('blocklyApp.KeyboardInputService');
goog.require('blocklyApp.NotificationsService');
goog.require('blocklyApp.SidebarComponent');
goog.require('blocklyApp.ToolboxModalComponent');
goog.require('blocklyApp.ToolboxModalService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.TreeService');
goog.require('blocklyApp.UtilsService');
goog.require('blocklyApp.VariableAddModalComponent');
goog.require('blocklyApp.VariableModalService');
goog.require('blocklyApp.VariableRenameModalComponent');
goog.require('blocklyApp.VariableRemoveModalComponent');
goog.require('blocklyApp.WorkspaceComponent');
blocklyApp.workspace = new Blockly.Workspace();
blocklyApp.AppComponent = ng.core.Component({
@@ -36,9 +59,11 @@ blocklyApp.AppComponent = ng.core.Component({
<span aria-live="polite" role="status">{{getAriaLiveReadout()}}</span>
</div>
<blockly-block-options-modal></blockly-block-options-modal>
<blockly-add-variable-modal></blockly-add-variable-modal>
<blockly-rename-variable-modal></blockly-rename-variable-modal>
<blockly-remove-variable-modal></blockly-remove-variable-modal>
<blockly-toolbox-modal></blockly-toolbox-modal>
<blockly-variable-modal></blockly-variable-modal>
<blockly-block-options-modal></blockly-block-options-modal>
<label id="blockly-translate-button" aria-hidden="true" hidden>
{{'BUTTON'|translate}}
@@ -51,7 +76,9 @@ blocklyApp.AppComponent = ng.core.Component({
blocklyApp.BlockOptionsModalComponent,
blocklyApp.SidebarComponent,
blocklyApp.ToolboxModalComponent,
blocklyApp.VariableModalComponent,
blocklyApp.VariableAddModalComponent,
blocklyApp.VariableRenameModalComponent,
blocklyApp.VariableRemoveModalComponent,
blocklyApp.WorkspaceComponent
],
pipes: [blocklyApp.TranslatePipe],
+5
Ver Arquivo
@@ -22,6 +22,11 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.AudioService');
goog.require('blocklyApp.NotificationsService');
blocklyApp.AudioService = ng.core.Class({
constructor: [
blocklyApp.NotificationsService, function(notificationsService) {
+6
Ver Arquivo
@@ -23,6 +23,12 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.BlockConnectionService');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.NotificationsService');
blocklyApp.BlockConnectionService = ng.core.Class({
constructor: [
blocklyApp.NotificationsService, blocklyApp.AudioService,
+24 -51
Ver Arquivo
@@ -23,6 +23,16 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.BlockOptionsModalComponent');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.BlockOptionsModalService');
goog.require('blocklyApp.KeyboardInputService');
goog.require('blocklyApp.TranslatePipe');
goog.require('Blockly.CommonModal');
blocklyApp.BlockOptionsModalComponent = ng.core.Component({
selector: 'blockly-block-options-modal',
template: `
@@ -39,7 +49,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
*ngFor="#buttonInfo of actionButtonsInfo; #buttonIndex=index">
<button [id]="getOptionId(buttonIndex)"
(click)="buttonInfo.action(); hideModal();"
[ngClass]="{activeButton: activeActionButtonIndex == buttonIndex}">
[ngClass]="{activeButton: activeButtonIndex == buttonIndex}">
{{buttonInfo.translationIdForText|translate}}
</button>
</div>
@@ -48,7 +58,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
<div class="blocklyModalButtonContainer">
<button [id]="getCancelOptionId()"
(click)="dismissModal()"
[ngClass]="{activeButton: activeActionButtonIndex == actionButtonsInfo.length}">
[ngClass]="{activeButton: activeButtonIndex == actionButtonsInfo.length}">
{{'CANCEL'|translate}}
</button>
</div>
@@ -68,7 +78,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
this.modalIsVisible = false;
this.actionButtonsInfo = [];
this.activeActionButtonIndex = -1;
this.activeButtonIndex = -1;
this.onDismissCallback = null;
var that = this;
@@ -79,67 +89,26 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
that.activeActionButtonIndex = -1;
that.onDismissCallback = onDismissCallback;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
Blockly.CommonModal.setupKeyboardOverrides(that);
that.keyboardInputService.addOverride('13', function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeActionButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeActionButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeActionButtonIndex ==
that.actionButtonsInfo.length) {
that.audioService.playOopsSound();
} else {
that.activeActionButtonIndex++;
}
}
that.focusOnOption(that.activeActionButtonIndex);
},
// Enter key: selects an action, performs it, and closes the modal.
'13': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (that.activeActionButtonIndex == -1) {
if (that.activeButtonIndex == -1) {
return;
}
var button = document.getElementById(
that.getOptionId(that.activeActionButtonIndex));
if (that.activeActionButtonIndex <
that.getOptionId(that.activeButtonIndex));
if (that.activeButtonIndex <
that.actionButtonsInfo.length) {
that.actionButtonsInfo[that.activeActionButtonIndex].action();
that.actionButtonsInfo[that.activeButtonIndex].action();
} else {
that.dismissModal();
}
that.hideModal();
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
// Prevent the page from scrolling.
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
// Prevent the page from scrolling.
evt.preventDefault();
}
});
});
setTimeout(function() {
document.getElementById('blockOptionsModal').focus();
@@ -152,6 +121,10 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
var button = document.getElementById(this.getOptionId(index));
button.focus();
},
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
return this.actionButtonsInfo.length + 1;
},
// Returns the ID for the corresponding option button.
getOptionId: function(index) {
return 'block-options-modal-option-' + index;
+3
Ver Arquivo
@@ -23,6 +23,9 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.BlockOptionsModalService');
blocklyApp.BlockOptionsModalService = ng.core.Class({
constructor: [function() {
this.actionButtonsInfo = [];
+77
Ver Arquivo
@@ -0,0 +1,77 @@
goog.provide('Blockly.CommonModal');
Blockly.CommonModal = function() {};
Blockly.CommonModal.setupKeyboardOverrides = function(component) {
component.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (component.activeButtonIndex <= 0) {
component.activeActionButtonIndex = 0;
component.audioService.playOopsSound();
} else {
component.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (component.activeButtonIndex == component.numInteractiveElements(component) - 1) {
component.audioService.playOopsSound();
} else {
component.activeButtonIndex++;
}
}
component.focusOnOption(component.activeButtonIndex, component);
},
// Escape key: closes the modal.
'27': function() {
component.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
}
Blockly.CommonModal.getInteractiveElements = function(component) {
return Array.prototype.filter.call(
component.getInteractiveContainer().elements, function(element) {
if (element.type === 'hidden') {
return false;
}
if (element.disabled) {
return false;
}
if (element.tabIndex < 0) {
return false;
}
return true;
});
};
Blockly.CommonModal.numInteractiveElements = function(component) {
var elements = this.getInteractiveElements(component);
return elements.length;
};
Blockly.CommonModal.focusOnOption = function(index, component) {
var elements = this.getInteractiveElements(component);
var button = elements[index];
button.focus();
};
Blockly.CommonModal.hideModal = function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
};
+39 -10
Ver Arquivo
@@ -24,6 +24,13 @@
* @author madeeha@google.com (Madeeha Ghori)
*/
goog.provide('blocklyApp.FieldSegmentComponent');
goog.require('blocklyApp.NotificationsService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.VariableModalService');
blocklyApp.FieldSegmentComponent = ng.core.Component({
selector: 'blockly-field-segment',
template: `
@@ -51,11 +58,10 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
<template [ngIf]="isDropdown()">
{{getPrefixText()}}
<select [id]="mainFieldId" [name]="mainFieldId"
[ngModel]="mainField.getValue()" (ngModelChange)="setDropdownValue($event)"
[ngModel]="selectedOption" (ngModelChange)="setDropdownValue($event)"
(keydown.enter)="selectOption()"
tabindex="-1">
<option *ngFor="#option of dropdownOptions" value="{{option.value}}"
[selected]="mainField.getValue() == option.value">
<option *ngFor="#option of dropdownOptions" value="{{option.value}}">
{{option.text}}
</option>
</select>
@@ -75,10 +81,17 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
this.dropdownOptions = [];
this.rawOptions = [];
}],
// Angular2 hook - called after initialization.
ngAfterContentInit: function() {
if (this.mainField) {
this.mainField.initModel();
}
},
// Angular2 hook - called to check if the cached component needs an update.
ngDoCheck: function() {
if (this.isDropdown() && this.shouldBreakCache()) {
this.optionValue = this.mainField.getValue();
this.fieldValue = this.mainField.getValue();
this.rawOptions = this.mainField.getOptions();
this.dropdownOptions = this.rawOptions.map(function(valueAndText) {
return {
@@ -86,6 +99,13 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
value: valueAndText[1]
};
});
// Set the currently selected value to the variable on the field.
for (var i = 0; i < this.dropdownOptions.length; i++) {
if (this.dropdownOptions[i].text === this.fieldValue) {
this.selectedOption = this.dropdownOptions[i].value;
}
}
}
},
// Returns whether the mutable, cached information needs to be refreshed.
@@ -97,11 +117,15 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
for (var i = 0; i < this.rawOptions.length; i++) {
// Compare the value of the cached options with the values in the field.
if (newOptions[i][1] != this.rawOptions[i][1]) {
if (newOptions[i][0] != this.rawOptions[i][0]) {
return true;
}
}
if (this.fieldValue != this.mainField.getValue()) {
return true;
}
return false;
},
// Gets the prefix text, to be printed before a field.
@@ -143,8 +167,17 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
},
// Confirm a selection for dropdown fields.
selectOption: function() {
if (this.optionValue == Blockly.Msg.RENAME_VARIABLE) {
this.variableModalService.showModal_(this.mainField.getValue());
if (this.optionValue != Blockly.RENAME_VARIABLE_ID && this.optionValue !=
Blockly.DELETE_VARIABLE_ID) {
this.mainField.setValue(this.optionValue);
}
if (this.optionValue == Blockly.RENAME_VARIABLE_ID) {
this.variableModalService.showRenameModal_(this.mainField.getValue());
}
if (this.optionValue == Blockly.DELETE_VARIABLE_ID) {
this.variableModalService.showRemoveModal_(this.mainField.getValue());
}
},
// Sets the value on a dropdown input.
@@ -154,10 +187,6 @@ blocklyApp.FieldSegmentComponent = ng.core.Component({
return;
}
if (this.optionValue != Blockly.Msg.RENAME_VARIABLE) {
this.mainField.setValue(this.optionValue);
}
var optionText = undefined;
for (var i = 0; i < this.dropdownOptions.length; i++) {
if (this.dropdownOptions[i].value == optionValue) {
+6
Ver Arquivo
@@ -23,6 +23,9 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.KeyboardInputService');
blocklyApp.KeyboardInputService = ng.core.Class({
constructor: [function() {
// Default custom actions for global keystrokes. The keys of this object
@@ -46,6 +49,9 @@ blocklyApp.KeyboardInputService = ng.core.Class({
setOverride: function(newKeysToActions) {
this.keysToActionsOverride = newKeysToActions;
},
addOverride: function(keyCode, action) {
this.keysToActionsOverride[keyCode] = action;
},
clearOverride: function() {
this.keysToActionsOverride = null;
}
+3
Ver Arquivo
@@ -23,6 +23,9 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.NotificationsService');
blocklyApp.NotificationsService = ng.core.Class({
constructor: [function() {
this.currentMessage = '';
+25 -1
Ver Arquivo
@@ -24,6 +24,17 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.SidebarComponent');
goog.require('blocklyApp.UtilsService');
goog.require('blocklyApp.BlockConnectionService');
goog.require('blocklyApp.ToolboxModalService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.TreeService');
goog.require('blocklyApp.VariableModalService');
blocklyApp.SidebarComponent = ng.core.Component({
selector: 'blockly-sidebar',
template: `
@@ -52,6 +63,11 @@ blocklyApp.SidebarComponent = ng.core.Component({
class="blocklySidebarButton">
{{'ERASE_WORKSPACE'|translate}}
</button>
<button *ngIf="hasVariableCategory()" id="add-variable"
(click)="showAddVariableModal()"
class="blocklySidebarButton">
Add Variable
</button>
</div>
`,
pipes: [blocklyApp.TranslatePipe]
@@ -62,9 +78,10 @@ blocklyApp.SidebarComponent = ng.core.Component({
blocklyApp.ToolboxModalService,
blocklyApp.TreeService,
blocklyApp.UtilsService,
blocklyApp.VariableModalService,
function(
blockConnectionService, toolboxModalService, treeService,
utilsService) {
utilsService, variableService) {
// ACCESSIBLE_GLOBALS is a global variable defined by the containing
// page. It should contain a key, customSidebarButtons, describing
// additional buttons that should be displayed after the default ones.
@@ -77,6 +94,7 @@ blocklyApp.SidebarComponent = ng.core.Component({
this.toolboxModalService = toolboxModalService;
this.treeService = treeService;
this.utilsService = utilsService;
this.variableModalService = variableService;
this.ID_FOR_ATTACH_TO_LINK_BUTTON = 'blocklyAttachToLinkBtn';
this.ID_FOR_CREATE_NEW_GROUP_BUTTON = 'blocklyCreateNewGroupBtn';
@@ -88,6 +106,9 @@ blocklyApp.SidebarComponent = ng.core.Component({
isWorkspaceEmpty: function() {
return this.utilsService.isWorkspaceEmpty();
},
hasVariableCategory: function() {
return this.toolboxModalService.toolboxHasVariableCategory();
},
clearWorkspace: function() {
blocklyApp.workspace.clear();
this.treeService.clearAllActiveDescs();
@@ -104,5 +125,8 @@ blocklyApp.SidebarComponent = ng.core.Component({
showToolboxModalForCreateNewGroup: function() {
this.toolboxModalService.showToolboxModalForCreateNewGroup(
this.ID_FOR_CREATE_NEW_GROUP_BUTTON);
},
showAddVariableModal: function() {
this.variableModalService.showAddModal_("item");
}
});
+23 -51
Ver Arquivo
@@ -23,6 +23,17 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.ToolboxModalComponent');
goog.require('Blockly.CommonModal');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.KeyboardInputService');
goog.require('blocklyApp.ToolboxModalService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.TreeService');
goog.require('blocklyApp.UtilsService');
blocklyApp.ToolboxModalComponent = ng.core.Component({
selector: 'blockly-toolbox-modal',
template: `
@@ -103,34 +114,8 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
that.firstBlockIndexes.push(cumulativeIndex);
that.totalNumBlocks = cumulativeIndex;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeButtonIndex == that.totalNumBlocks) {
that.audioService.playOopsSound();
} else {
that.activeButtonIndex++;
}
}
that.focusOnOption(that.activeButtonIndex);
},
// Enter key: selects a block (or the 'Cancel' button), and closes
// the modal.
'13': function(evt) {
Blockly.CommonModal.setupKeyboardOverrides(that);
that.keyboardInputService.addOverride('13', function(evt) {
evt.preventDefault();
evt.stopPropagation();
@@ -154,20 +139,7 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
// The 'Cancel' button has been pressed.
that.dismissModal();
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
});
setTimeout(function() {
document.getElementById('toolboxModal').focus();
@@ -177,10 +149,15 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
}
],
// Closes the modal (on both success and failure).
hideModal_: function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
this.toolboxModalService.hideModal();
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var button = document.getElementById(this.getOptionId(index));
button.focus();
},
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
return this.totalNumBlocks + 1;
},
getOverallIndex: function(categoryIndex, blockIndex) {
return this.firstBlockIndexes[categoryIndex] + blockIndex;
@@ -191,11 +168,6 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
getBlockDescription: function(block) {
return this.utilsService.getBlockDescription(block);
},
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var button = document.getElementById(this.getOptionId(index));
button.focus();
},
// Returns the ID for the corresponding option button.
getOptionId: function(index) {
return 'toolbox-modal-option-' + index;
+76 -37
Ver Arquivo
@@ -23,6 +23,15 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.ToolboxModalService');
goog.require('blocklyApp.UtilsService');
goog.require('blocklyApp.BlockConnectionService');
goog.require('blocklyApp.NotificationsService');
goog.require('blocklyApp.TreeService');
blocklyApp.ToolboxModalService = ng.core.Class({
constructor: [
blocklyApp.BlockConnectionService,
@@ -42,6 +51,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
this.selectedToolboxCategories = null;
this.onSelectBlockCallback = null;
this.onDismissCallback = null;
this.hasVariableCategory = null;
// The aim of the pre-show hook is to populate the modal component with
// the information it needs to display the modal (e.g., which categories
// and blocks to display).
@@ -50,45 +60,52 @@ blocklyApp.ToolboxModalService = ng.core.Class({
'A pre-show hook must be defined for the toolbox modal before it ' +
'can be shown.');
};
// Populate the toolbox categories.
this.allToolboxCategories = [];
var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
if (toolboxCategoryElts.length) {
this.allToolboxCategories = Array.from(toolboxCategoryElts).map(
function(categoryElt) {
var tmpWorkspace = new Blockly.Workspace();
Blockly.Xml.domToWorkspace(categoryElt, tmpWorkspace);
return {
categoryName: categoryElt.attributes.name.value,
blocks: tmpWorkspace.topBlocks_
};
}
);
this.computeCategoriesForCreateNewGroupModal_();
} else {
// A timeout seems to be needed in order for the .children accessor to
// work correctly.
var that = this;
setTimeout(function() {
// If there are no top-level categories, we create a single category
// containing all the top-level blocks.
var tmpWorkspace = new Blockly.Workspace();
Array.from(toolboxXmlElt.children).forEach(function(topLevelNode) {
Blockly.Xml.domToBlock(tmpWorkspace, topLevelNode);
});
that.allToolboxCategories = [{
categoryName: '',
blocks: tmpWorkspace.topBlocks_
}];
that.computeCategoriesForCreateNewGroupModal_();
});
}
}
],
populateToolbox_: function() {
// Populate the toolbox categories.
this.allToolboxCategories = [];
var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
if (toolboxCategoryElts.length) {
this.allToolboxCategories = Array.from(toolboxCategoryElts).map(
function(categoryElt) {
var tmpWorkspace = new Blockly.Workspace();
var custom = categoryElt.attributes.custom
// TODO (corydiers): Implement custom flyouts once #1153 is solved.
if (custom && custom.value == Blockly.VARIABLE_CATEGORY_NAME) {
var varBlocks =
Blockly.Variables.flyoutCategoryBlocks(blocklyApp.workspace);
varBlocks.forEach(function(block) {
Blockly.Xml.domToBlock(block, tmpWorkspace);
});
} else {
Blockly.Xml.domToWorkspace(categoryElt, tmpWorkspace);
}
return {
categoryName: categoryElt.attributes.name.value,
blocks: tmpWorkspace.topBlocks_
};
}
);
this.computeCategoriesForCreateNewGroupModal_();
} else {
var that = this;
// If there are no top-level categories, we create a single category
// containing all the top-level blocks.
var tmpWorkspace = new Blockly.Workspace();
Array.from(toolboxXmlElt.children).forEach(function(topLevelNode) {
Blockly.Xml.domToBlock(tmpWorkspace, topLevelNode);
});
that.allToolboxCategories = [{
categoryName: '',
blocks: tmpWorkspace.topBlocks_
}];
that.computeCategoriesForCreateNewGroupModal_();
}
},
computeCategoriesForCreateNewGroupModal_: function() {
// Precompute toolbox categories for blocks that have no output
// connection (and that can therefore be used as the base block of a
@@ -119,6 +136,26 @@ blocklyApp.ToolboxModalService = ng.core.Class({
isModalShown: function() {
return this.modalIsShown;
},
toolboxHasVariableCategory: function() {
if (this.hasVariableCategory === null) {
var toolboxXmlElt = document.getElementById('blockly-toolbox-xml');
var toolboxCategoryElts = toolboxXmlElt.getElementsByTagName('category');
var that = this;
Array.from(toolboxCategoryElts).forEach(
function(categoryElt) {
var custom = categoryElt.attributes.custom;
if (custom && custom.value == Blockly.VARIABLE_CATEGORY_NAME) {
that.hasVariableCategory = true;
}
});
if (this.hasVariableCategory === null) {
this.hasVariableCategory = false;
}
}
return this.hasVariableCategory;
},
showModal_: function(
selectedToolboxCategories, onSelectBlockCallback, onDismissCallback) {
this.selectedToolboxCategories = selectedToolboxCategories;
@@ -135,6 +172,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
var that = this;
var selectedToolboxCategories = [];
this.populateToolbox_();
this.allToolboxCategories.forEach(function(toolboxCategory) {
var selectedBlocks = toolboxCategory.blocks.filter(function(block) {
return that.blockConnectionService.canBeAttachedToMarkedConnection(
@@ -172,6 +210,7 @@ blocklyApp.ToolboxModalService = ng.core.Class({
},
showToolboxModalForCreateNewGroup: function(sourceButtonId) {
var that = this;
this.populateToolbox_();
this.showModal_(this.toolboxCategoriesForNewGroup, function(block) {
var blockDescription = that.utilsService.getBlockDescription(block);
var xml = Blockly.Xml.blockToDom(block);
+3
Ver Arquivo
@@ -22,6 +22,9 @@
* @author sll@google.com (Sean Lip)
*/
goog.provide('blocklyApp.TranslatePipe');
blocklyApp.TranslatePipe = ng.core.Pipe({
name: 'translate'
})
+21 -7
Ver Arquivo
@@ -25,6 +25,17 @@
* @author madeeha@google.com (Madeeha Ghori)
*/
goog.provide('blocklyApp.TreeService');
goog.require('blocklyApp.UtilsService');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.BlockConnectionService');
goog.require('blocklyApp.BlockOptionsModalService');
goog.require('blocklyApp.NotificationsService');
goog.require('blocklyApp.VariableModalService');
blocklyApp.TreeService = ng.core.Class({
constructor: [
blocklyApp.AudioService,
@@ -32,14 +43,16 @@ blocklyApp.TreeService = ng.core.Class({
blocklyApp.BlockOptionsModalService,
blocklyApp.NotificationsService,
blocklyApp.UtilsService,
blocklyApp.VariableModalService,
function(
audioService, blockConnectionService, blockOptionsModalService,
notificationsService, utilsService) {
notificationsService, utilsService, variableModalService) {
this.audioService = audioService;
this.blockConnectionService = blockConnectionService;
this.blockOptionsModalService = blockOptionsModalService;
this.notificationsService = notificationsService;
this.utilsService = utilsService;
this.variableModalService = variableModalService;
// The suffix used for all IDs of block root elements.
this.BLOCK_ROOT_ID_SUFFIX_ = blocklyApp.BLOCK_ROOT_ID_SUFFIX;
@@ -207,11 +220,10 @@ blocklyApp.TreeService = ng.core.Class({
var activeDesc = document.getElementById(this.getActiveDescId(treeId));
if (activeDesc) {
activeDesc.classList.remove('blocklyActiveDescendant');
}
if (this.activeDescendantIds_[treeId]) {
delete this.activeDescendantIds_[treeId];
} else {
throw Error(
'The active desc element for the tree with ID ' + treeId +
' is invalid.');
}
},
clearAllActiveDescs: function() {
@@ -460,14 +472,16 @@ blocklyApp.TreeService = ng.core.Class({
onKeypress: function(e, tree) {
// TODO(sll): Instead of this, have a common ActiveContextService which
// returns true if at least one modal is shown, and false otherwise.
if (this.blockOptionsModalService.isModalShown()) {
if (this.blockOptionsModalService.isModalShown() ||
this.variableModalService.isModalShown()) {
return;
}
var treeId = tree.id;
var activeDesc = document.getElementById(this.getActiveDescId(treeId));
if (!activeDesc) {
console.error('ERROR: no active descendant for current tree.');
// The underlying Blockly instance may have decided blocks needed to
// be deleted. This is not necessarily an error, but needs to be repaired.
this.initActiveDesc(treeId);
activeDesc = document.getElementById(this.getActiveDescId(treeId));
}
+3 -1
Ver Arquivo
@@ -25,7 +25,9 @@
* @author madeeha@google.com (Madeeha Ghori)
*/
var blocklyApp = {};
goog.provide('blocklyApp.UtilsService');
blocklyApp.ID_FOR_EMPTY_WORKSPACE_BTN = 'blocklyEmptyWorkspaceBtn';
blocklyApp.BLOCK_ROOT_ID_SUFFIX = '-blockRoot';
+118
Ver Arquivo
@@ -0,0 +1,118 @@
/**
* AccessibleBlockly
*
* Copyright 2017 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 Component representing the variable rename modal.
*
* @author corydiers@google.com (Cory Diers)
*/
goog.provide('blocklyApp.VariableAddModalComponent');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.KeyboardInputService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.VariableModalService');
goog.require('Blockly.CommonModal');
blocklyApp.VariableAddModalComponent = ng.core.Component({
selector: 'blockly-add-variable-modal',
template: `
<div *ngIf="modalIsVisible"class="blocklyModalCurtain"
(click)="dismissModal()">
<!-- $event.stopPropagation() prevents the modal from closing when its
interior is clicked. -->
<div id="varModal" class="blocklyModal" role="alertdialog"
(click)="$event.stopPropagation()" tabindex="0"
aria-labelledby="variableModalHeading">
<h3 id="variableModalHeading">Add a variable...</h3>
<form id="varForm">
<p id="inputLabel">New Variable Name:
<input id="mainFieldId" type="text" [ngModel]="VALUE"
(ngModelChange)="setTextValue($event)" tabindex="0"
aria-labelledby="inputLabel" />
</p>
<hr>
<button type="button" id="submitButton" (click)="submit()">
SUBMIT
</button>
<button type="button" id="cancelButton" (click)="dismissModal()">
CANCEL
</button>
</form>
</div>
</div>
`,
pipes: [blocklyApp.TranslatePipe]
})
.Class({
constructor: [
blocklyApp.AudioService, blocklyApp.KeyboardInputService, blocklyApp.VariableModalService,
function(audioService, keyboardService, variableService) {
this.workspace = blocklyApp.workspace;
this.variableModalService = variableService;
this.audioService = audioService;
this.keyboardInputService = keyboardService
this.modalIsVisible = false;
this.activeButtonIndex = -1;
var that = this;
this.variableModalService.registerPreAddShowHook(
function() {
that.modalIsVisible = true;
Blockly.CommonModal.setupKeyboardOverrides(that);
setTimeout(function() {
document.getElementById('varModal').focus();
}, 150);
}
);
}
],
// Caches the current text variable as the user types.
setTextValue: function(newValue) {
this.variableName = newValue;
},
// Closes the modal (on both success and failure).
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: Blockly.CommonModal.focusOnOption,
// Counts the number of interactive elements for the modal.
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
// Gets all the interactive elements for the modal.
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
// Gets the container with interactive elements.
getInteractiveContainer: function() {
return document.getElementById("varForm");
},
// Submits the name change for the variable.
submit: function() {
this.workspace.createVariable(this.variableName);
this.dismissModal();
},
// Dismisses and closes the modal.
dismissModal: function() {
this.variableModalService.hideModal();
this.hideModal_();
}
})
+46 -6
Ver Arquivo
@@ -23,27 +23,67 @@
* @author corydiers@google.com (Cory Diers)
*/
goog.provide('blocklyApp.VariableModalService');
blocklyApp.VariableModalService = ng.core.Class({
constructor: [
function() {
this.modalIsShown = false;
}
],
// Registers a hook to be called before the modal is shown.
registerPreShowHook: function(preShowHook) {
this.preShowHook = function(oldName) {
// Registers a hook to be called before the add modal is shown.
registerPreAddShowHook: function(preShowHook) {
this.preAddShowHook = function() {
preShowHook();
};
},
// Registers a hook to be called before the rename modal is shown.
registerPreRenameShowHook: function(preShowHook) {
this.preRenameShowHook = function(oldName) {
preShowHook(oldName);
};
},
// Registers a hook to be called before the remove modal is shown.
registerPreRemoveShowHook: function(preShowHook) {
this.preRemoveShowHook = function(oldName, count) {
preShowHook(oldName, count);
};
},
// Returns true if the variable modal is shown.
isModalShown: function() {
return this.modalIsShown;
},
// Show the variable modal.
showModal_: function(oldName) {
this.preShowHook(oldName);
// Show the add variable modal.
showAddModal_: function() {
this.preAddShowHook();
this.modalIsShown = true;
},
// Show the rename variable modal.
showRenameModal_: function(oldName) {
this.preRenameShowHook(oldName);
this.modalIsShown = true;
},
// Show the remove variable modal.
showRemoveModal_: function(oldName) {
var count = this.getNumVariables(oldName);
this.modalIsShown = true;
if (count > 1) {
this.preRemoveShowHook(oldName, count);
} else {
var variable = blocklyApp.workspace.getVariable(oldName);
blocklyApp.workspace.deleteVariableInternal_(variable);
// Allow the execution loop to finish before "closing" the modal. While
// the modal never opens, its being "open" should prevent other keypresses
// anyway.
var that = this;
setTimeout(function() {
that.modalIsShown = false;
});
}
},
getNumVariables: function(oldName) {
return blocklyApp.workspace.getVariableUses(oldName).length;
},
// Hide the variable modal.
hideModal: function() {
this.modalIsShown = false;
+125
Ver Arquivo
@@ -0,0 +1,125 @@
/**
* AccessibleBlockly
*
* Copyright 2017 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 Component representing the variable remove modal.
*
* @author corydiers@google.com (Cory Diers)
*/
goog.provide('blocklyApp.VariableRemoveModalComponent');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.KeyboardInputService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.TreeService');
goog.require('blocklyApp.VariableModalService');
goog.require('Blockly.CommonModal');
blocklyApp.VariableRemoveModalComponent = ng.core.Component({
selector: 'blockly-remove-variable-modal',
template: `
<div *ngIf="modalIsVisible"class="blocklyModalCurtain"
(click)="dismissModal()">
<!-- $event.stopPropagation() prevents the modal from closing when its
interior is clicked. -->
<div id="varModal" class="blocklyModal" role="alertdialog"
(click)="$event.stopPropagation()" tabindex="0"
aria-labelledby="variableModalHeading">
<h3 id="variableModalHeading">
Delete {{getNumVariables()}} uses of the "{{currentVariableName}}"
variable?
</h3>
<form id="varForm">
<hr>
<button type="button" id="yesButton" (click)="submit()">
YES
</button>
<button type="button" id="noButton" (click)="dismissModal()">
NO
</button>
</form>
</div>
</div>
`,
pipes: [blocklyApp.TranslatePipe]
})
.Class({
constructor: [
blocklyApp.AudioService,
blocklyApp.KeyboardInputService,
blocklyApp.TreeService,
blocklyApp.VariableModalService,
function(audioService, keyboardService, treeService, variableService) {
this.workspace = blocklyApp.workspace;
this.treeService = treeService;
this.variableModalService = variableService;
this.audioService = audioService;
this.keyboardInputService = keyboardService
this.modalIsVisible = false;
this.activeButtonIndex = -1;
this.currentVariableName = "";
this.count = 0;
var that = this;
this.variableModalService.registerPreRemoveShowHook(
function(name, count) {
that.currentVariableName = name;
that.count = count
that.modalIsVisible = true;
Blockly.CommonModal.setupKeyboardOverrides(that);
setTimeout(function() {
document.getElementById('varModal').focus();
}, 150);
}
);
}
],
// Closes the modal (on both success and failure).
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: Blockly.CommonModal.focusOnOption,
// Counts the number of interactive elements for the modal.
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
// Gets all the interactive elements for the modal.
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
// Gets the container with interactive elements.
getInteractiveContainer: function() {
return document.getElementById("varForm");
},
getNumVariables: function() {
return this.variableModalService.getNumVariables(this.currentVariableName);
},
// Submits the name change for the variable.
submit: function() {
var variable = blocklyApp.workspace.getVariable(this.currentVariableName);
blocklyApp.workspace.deleteVariableInternal_(variable);
this.dismissModal();
},
// Dismisses and closes the modal.
dismissModal: function() {
this.variableModalService.hideModal();
this.hideModal_();
}
})
@@ -18,13 +18,22 @@
*/
/**
* @fileoverview Component representing the variable modal.
* @fileoverview Component representing the variable rename modal.
*
* @author corydiers@google.com (Cory Diers)
*/
blocklyApp.VariableModalComponent = ng.core.Component({
selector: 'blockly-variable-modal',
goog.provide('blocklyApp.VariableRenameModalComponent');
goog.require('Blockly.CommonModal');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.KeyboardInputService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.VariableModalService');
blocklyApp.VariableRenameModalComponent = ng.core.Component({
selector: 'blockly-rename-variable-modal',
template: `
<div *ngIf="modalIsVisible"class="blocklyModalCurtain"
(click)="dismissModal()">
@@ -33,6 +42,10 @@ blocklyApp.VariableModalComponent = ng.core.Component({
<div id="varModal" class="blocklyModal" role="alertdialog"
(click)="$event.stopPropagation()" tabindex="0"
aria-labelledby="variableModalHeading">
<h3 id="variableModalHeading">
Rename the "{{currentVariableName}}" variable...
</h3>
<form id="varForm">
<p id="inputLabel">New Variable Name:
<input id="mainFieldId" type="text" [ngModel]="VALUE"
@@ -40,10 +53,10 @@ blocklyApp.VariableModalComponent = ng.core.Component({
aria-labelledby="inputLabel" />
</p>
<hr>
<button id="submitButton" (click)="submit()">
<button type="button" id="submitButton" (click)="submit()">
SUBMIT
</button>
<button id="cancelButton" (click)="dismissModal()">
<button type="button" id="cancelButton" (click)="dismissModal()">
CANCEL
</button>
</form>
@@ -65,52 +78,15 @@ blocklyApp.VariableModalComponent = ng.core.Component({
this.currentVariableName = "";
var that = this;
this.variableModalService.registerPreShowHook(
this.variableModalService.registerPreRenameShowHook(
function(oldName) {
that.currentVariableName = oldName;
that.modalIsVisible = true;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeButtonIndex == that.numInteractiveElements() - 1) {
that.audioService.playOopsSound();
} else {
that.activeButtonIndex++;
}
}
that.focusOnOption(that.activeButtonIndex);
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
Blockly.CommonModal.setupKeyboardOverrides(that);
setTimeout(function() {
document.getElementById('mainFieldId').focus();
document.getElementById('varModal').focus();
}, 150);
}
);
@@ -121,44 +97,25 @@ blocklyApp.VariableModalComponent = ng.core.Component({
this.variableName = newValue;
},
// Closes the modal (on both success and failure).
hideModal_: function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
},
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var elements = this.getInteractiveElements();
var button = elements[index];
button.focus();
},
focusOnOption: Blockly.CommonModal.focusOnOption,
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
var elements = this.getInteractiveElements();
return elements.length;
},
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
// Gets all the interactive elements for the modal.
getInteractiveElements: function() {
return Array.prototype.filter.call(
document.getElementById("varForm").elements, function(element) {
if (element.type === 'hidden') {
return false;
}
if (element.disabled) {
return false;
}
if (element.tabIndex < 0) {
return false;
}
return true;
});
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
// Gets the container with interactive elements.
getInteractiveContainer: function() {
return document.getElementById("varForm");
},
// Submits the name change for the variable.
submit: function() {
this.workspace.renameVariable(this.currentVariableName, this.variableName);
this.hideModal_();
this.dismissModal();
},
// Dismisses and closes the modal.
dismissModal: function() {
this.variableModalService.hideModal();
this.hideModal_();
}
})
+11
Ver Arquivo
@@ -23,6 +23,17 @@
* @author madeeha@google.com (Madeeha Ghori)
*/
goog.provide('blocklyApp.WorkspaceBlockComponent');
goog.require('blocklyApp.UtilsService');
goog.require('blocklyApp.AudioService');
goog.require('blocklyApp.BlockConnectionService');
goog.require('blocklyApp.FieldSegmentComponent');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.TreeService');
blocklyApp.WorkspaceBlockComponent = ng.core.Component({
selector: 'blockly-workspace-block',
template: `
+10
Ver Arquivo
@@ -24,6 +24,16 @@
* @author madeeha@google.com (Madeeha Ghori)
*/
goog.provide('blocklyApp.WorkspaceComponent');
goog.require('blocklyApp.NotificationsService');
goog.require('blocklyApp.ToolboxModalService');
goog.require('blocklyApp.TranslatePipe');
goog.require('blocklyApp.TreeService');
goog.require('blocklyApp.WorkspaceBlockComponent');
blocklyApp.WorkspaceComponent = ng.core.Component({
selector: 'blockly-workspace',
template: `
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+757 -605
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+320 -297
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1
Ver Arquivo
@@ -33,6 +33,7 @@ goog.provide('Blockly.Blocks.colour'); // Deprecated
goog.provide('Blockly.Constants.Colour');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
+1
Ver Arquivo
@@ -33,6 +33,7 @@ goog.provide('Blockly.Blocks.lists'); // Deprecated
goog.provide('Blockly.Constants.Lists');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
+1
Ver Arquivo
@@ -33,6 +33,7 @@ goog.provide('Blockly.Blocks.logic'); // Deprecated
goog.provide('Blockly.Constants.Logic');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
+1
Ver Arquivo
@@ -33,6 +33,7 @@ goog.provide('Blockly.Blocks.loops'); // Deprecated
goog.provide('Blockly.Constants.Loops');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
+1
Ver Arquivo
@@ -33,6 +33,7 @@ goog.provide('Blockly.Blocks.math'); // Deprecated
goog.provide('Blockly.Constants.Math');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
+11 -3
Ver Arquivo
@@ -27,6 +27,7 @@
goog.provide('Blockly.Blocks.procedures');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
@@ -443,7 +444,14 @@ Blockly.Blocks['procedures_mutatorarg'] = {
var source = this.sourceBlock_;
if (source && source.workspace && source.workspace.options &&
source.workspace.options.parentWorkspace) {
source.workspace.options.parentWorkspace.createVariable(newText);
var workspace = source.workspace.options.parentWorkspace;
var variable = workspace.getVariable(newText);
// If there is a case change, rename the variable.
if (variable && variable.name !== newText) {
workspace.renameVariableById(variable.getId(), newText);
} else {
workspace.createVariable(newText);
}
}
}
};
@@ -687,7 +695,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
// Block is deleted or is in a flyout.
return;
}
if (event.type == Blockly.Events.CREATE &&
if (event.type == Blockly.Events.BLOCK_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
@@ -730,7 +738,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
Blockly.Xml.domToWorkspace(xml, this.workspace);
Blockly.Events.setGroup(false);
}
} else if (event.type == Blockly.Events.DELETE) {
} else if (event.type == Blockly.Events.BLOCK_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.
+411 -369
Ver Arquivo
@@ -28,6 +28,7 @@ goog.provide('Blockly.Blocks.texts'); // Deprecated
goog.provide('Blockly.Constants.Text');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
@@ -57,377 +58,148 @@ Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT
"text_quotes",
"parent_tooltip_when_inline"
]
},
{
"type": "text_join",
"message0": "",
"output": "String",
"colour": "%{BKY_TEXTS_HUE}",
"helpUrl": "%{BKY_TEXT_JOIN_HELPURL}",
"tooltip": "%{BKY_TEXT_JOIN_TOOLTIP}",
"mutator": "text_join_mutator"
},
{
"type": "text_create_join_container",
"message0": "%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",
"args0": [{
"type": "input_dummy"
},
{
"type": "input_statement",
"name": "STACK"
}],
"colour": "%{BKY_TEXTS_HUE}",
"tooltip": "%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",
"enableContextMenu": false
},
{
"type": "text_create_join_item",
"message0": "%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_TEXTS_HUE}",
"tooltip": "{%BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",
"enableContextMenu": false
},
{
"type": "text_append",
"message0": "%{BKY_TEXT_APPEND_TITLE}",
"args0": [{
"type": "field_variable",
"name": "VAR",
"variable": "%{BKY_TEXT_APPEND_VARIABLE}"
},
{
"type": "input_value",
"name": "TEXT"
}],
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_TEXTS_HUE}",
"extensions": [
"text_append_tooltip"
]
},
{
"type": "text_length",
"message0": "%{BKY_TEXT_LENGTH_TITLE}",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": ['String', 'Array']
}
],
"output": 'Number',
"colour": "%{BKY_TEXTS_HUE}",
"tooltip": "%{BKY_TEXT_LENGTH_TOOLTIP}",
"helpUrl": "%{BKY_TEXT_LENGTH_HELPURL}"
},
{
"type": "text_isEmpty",
"message0": "%{BKY_TEXT_ISEMPTY_TITLE}",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": ['String', 'Array']
}
],
"output": 'Boolean',
"colour": "%{BKY_TEXTS_HUE}",
"tooltip": "%{BKY_TEXT_ISEMPTY_TOOLTIP}",
"helpUrl": "%{BKY_TEXT_ISEMPTY_HELPURL}"
},
{
"type": "text_indexOf",
"message0": "%{BKY_TEXT_INDEXOF_TITLE}",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "String"
},
{
"type": "field_dropdown",
"name": "END",
"options": [
[
"%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}",
"FIRST"
],
[
"%{BKY_TEXT_INDEXOF_OPERATOR_LAST}",
"LAST"
]
]
},
{
"type": "input_value",
"name": "FIND",
"check": "String"
}
],
"output": "Number",
"colour": "%{BKY_TEXTS_HUE}",
"helpUrl": "%{BKY_TEXT_INDEXOF_HELPURL}",
"inputsInline": true,
"extensions": [
"text_indexOf_tooltip"
]
},
{
"type": "text_charAt",
"message0": "%{BKY_TEXT_CHARAT_TITLE}", // "in text %1 %2"
"args0": [
{
"type":"input_value",
"name": "VALUE",
"check": "String"
},
{
"type": "input_dummy",
"name": "AT"
}
],
"output": "String",
"colour": "%{BKY_TEXTS_HUE}",
"helpUrl": "%{BKY_TEXT_CHARAT_HELPURL}",
"inputsInline": true,
"mutator": "text_charAt_mutator"
}
]); // END JSON EXTRACT (Do not delete this comment.)
/** Wraps TEXT field with images of double quote characters. */
Blockly.Constants.Text.textQuotesExtension = function() {
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
this.quoteField_('TEXT');
};
Blockly.Extensions.register('text_quotes',
Blockly.Constants.Text.textQuotesExtension);
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.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
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 = workspace.newBlock('text_create_join_container');
containerBlock.initSvg();
var connection = containerBlock.getInput('STACK').connection;
for (var i = 0; i < this.itemCount_; i++) {
var itemBlock = workspace.newBlock('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 = [];
while (itemBlock) {
connections.push(itemBlock.valueConnection_);
itemBlock = itemBlock.nextConnection &&
itemBlock.nextConnection.targetBlock();
}
// Disconnect any children that don't belong.
for (var i = 0; i < this.itemCount_; i++) {
var connection = this.getInput('ADD' + i).connection.targetConnection;
if (connection && connections.indexOf(connection) == -1) {
connection.disconnect();
}
}
this.itemCount_ = connections.length;
this.updateShape_();
// Reconnect any child blocks.
for (var i = 0; i < this.itemCount_; i++) {
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + 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() {
if (this.itemCount_ && this.getInput('EMPTY')) {
this.removeInput('EMPTY');
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
this.appendDummyInput('EMPTY')
.appendField(this.newQuote_(true))
.appendField(this.newQuote_(false));
}
// Add new inputs.
for (var i = 0; i < this.itemCount_; i++) {
if (!this.getInput('ADD' + i)) {
var input = this.appendValueInput('ADD' + i);
if (i == 0) {
input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH);
}
}
}
// Remove deleted inputs.
while (this.getInput('ADD' + i)) {
this.removeInput('ADD' + i);
i++;
}
}
};
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'));
});
}
};
Blockly.Blocks['text_length'] = {
/**
* Block for string length.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.TEXT_LENGTH_TITLE,
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": ['String', 'Array']
}
],
"output": 'Number',
"colour": Blockly.Blocks.texts.HUE,
"tooltip": Blockly.Msg.TEXT_LENGTH_TOOLTIP,
"helpUrl": Blockly.Msg.TEXT_LENGTH_HELPURL
});
}
};
Blockly.Blocks['text_isEmpty'] = {
/**
* Block for is the string null?
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.TEXT_ISEMPTY_TITLE,
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": ['String', 'Array']
}
],
"output": 'Boolean',
"colour": Blockly.Blocks.texts.HUE,
"tooltip": Blockly.Msg.TEXT_ISEMPTY_TOOLTIP,
"helpUrl": Blockly.Msg.TEXT_ISEMPTY_HELPURL
});
}
};
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);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace('%1',
thisBlock.workspace.options.oneBasedIndex ? '0' : '-1');
});
}
};
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);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var where = thisBlock.getFieldValue('WHERE');
var tooltip = Blockly.Msg.TEXT_CHARAT_TOOLTIP;
if (where == 'FROM_START' || where == 'FROM_END') {
var msg = (where == 'FROM_START') ?
Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP :
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP;
tooltip += ' ' + msg.replace('%1',
thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
}
return 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.
@@ -717,7 +489,7 @@ Blockly.Blocks['text_count'] = {
],
"output": "Number",
"inputsInline": true,
"colour": Blockly.Blocks.math.HUE,
"colour": Blockly.Blocks.texts.HUE,
"tooltip": Blockly.Msg.TEXT_COUNT_TOOLTIP,
"helpUrl": Blockly.Msg.TEXT_COUNT_HELPURL
});
@@ -849,3 +621,273 @@ Blockly.Constants.Text.QUOTE_IMAGE_MIXIN = {
}
};
/** Wraps TEXT field with images of double quote characters. */
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION = function() {
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
this.quoteField_('TEXT');
};
/**
* Mixin for mutator functions in the 'text_join_mutator' extension.
* @mixin
* @augments Blockly.Block
* @package
*/
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN = {
/**
* 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 = workspace.newBlock('text_create_join_container');
containerBlock.initSvg();
var connection = containerBlock.getInput('STACK').connection;
for (var i = 0; i < this.itemCount_; i++) {
var itemBlock = workspace.newBlock('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 = [];
while (itemBlock) {
connections.push(itemBlock.valueConnection_);
itemBlock = itemBlock.nextConnection &&
itemBlock.nextConnection.targetBlock();
}
// Disconnect any children that don't belong.
for (var i = 0; i < this.itemCount_; i++) {
var connection = this.getInput('ADD' + i).connection.targetConnection;
if (connection && connections.indexOf(connection) == -1) {
connection.disconnect();
}
}
this.itemCount_ = connections.length;
this.updateShape_();
// Reconnect any child blocks.
for (var i = 0; i < this.itemCount_; i++) {
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + 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() {
if (this.itemCount_ && this.getInput('EMPTY')) {
this.removeInput('EMPTY');
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
this.appendDummyInput('EMPTY')
.appendField(this.newQuote_(true))
.appendField(this.newQuote_(false));
}
// Add new inputs.
for (var i = 0; i < this.itemCount_; i++) {
if (!this.getInput('ADD' + i)) {
var input = this.appendValueInput('ADD' + i);
if (i == 0) {
input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH);
}
}
}
// Remove deleted inputs.
while (this.getInput('ADD' + i)) {
this.removeInput('ADD' + i);
i++;
}
}
};
// Performs final setup of a text_join block.
Blockly.Constants.Text.TEXT_JOIN_EXTENSION = function() {
// Add the quote mixin for the itemCount_ = 0 case.
this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);
// initialize the mutator values
this.itemCount_ = 2;
this.updateShape_();
// Configure the mutator ui
this.setMutator(new Blockly.Mutator(['text_create_join_item']));
};
Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION = function() {
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
if (Blockly.Msg.TEXT_APPEND_TOOLTIP) {
return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace('%1',
thisBlock.getFieldValue('VAR'));
}
return '';
});
};
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION = function() {
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace('%1',
thisBlock.workspace.options.oneBasedIndex ? '0' : '-1');
});
};
/**
* Mixin for mutator functions in the 'text_charAt_mutator' extension.
* @mixin
* @augments Blockly.Block
* @package
*/
Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN = {
/**
* 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');
}
};
// Does the initial mutator update of text_charAt and adds the tooltip
Blockly.Constants.Text.TEXT_CHARAT_EXTENSION = 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.updateAt_(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var where = thisBlock.getFieldValue('WHERE');
var tooltip = Blockly.Msg.TEXT_CHARAT_TOOLTIP;
if (where == 'FROM_START' || where == 'FROM_END') {
var msg = (where == 'FROM_START') ?
Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP :
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP;
if (msg) {
tooltip += ' ' + msg.replace('%1',
thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
}
}
return tooltip;
});
};
Blockly.Extensions.register('text_indexOf_tooltip',
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION);
Blockly.Extensions.register('text_quotes',
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION);
Blockly.Extensions.register('text_append_tooltip',
Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION);
Blockly.Extensions.registerMutator('text_join_mutator',
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN,
Blockly.Constants.Text.TEXT_JOIN_EXTENSION);
Blockly.Extensions.registerMutator('text_charAt_mutator',
Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN,
Blockly.Constants.Text.TEXT_CHARAT_EXTENSION);
+1
Ver Arquivo
@@ -33,6 +33,7 @@ goog.provide('Blockly.Blocks.variables'); // Deprecated.
goog.provide('Blockly.Constants.Variables');
goog.require('Blockly.Blocks');
goog.require('Blockly');
/**
+46 -29
Ver Arquivo
@@ -1,8 +1,25 @@
// Do not edit this file; automatically generated by build.py.
'use strict';
/*
// Copyright 2012 Google Inc. Apache License 2.0
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.
*/
Blockly.Blocks.colour={};Blockly.Constants={};Blockly.Constants.Colour={};Blockly.Constants.Colour.HUE=20;Blockly.Blocks.colour.HUE=Blockly.Constants.Colour.HUE;
Blockly.defineBlocksWithJsonArray([{type:"colour_picker",message0:"%1",args0:[{type:"field_colour",name:"COLOUR",colour:"#ff0000"}],output:"Colour",colour:"%{BKY_COLOUR_HUE}",helpUrl:"%{BKY_COLOUR_PICKER_HELPURL}",tooltip:"%{BKY_COLOUR_PICKER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"colour_random",message0:"%{BKY_COLOUR_RANDOM_TITLE}",output:"Colour",colour:"%{BKY_COLOUR_HUE}",helpUrl:"%{BKY_COLOUR_RANDOM_HELPURL}",tooltip:"%{BKY_COLOUR_RANDOM_TOOLTIP}"},{type:"colour_rgb",message0:"%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3",
args0:[{type:"input_value",name:"RED",check:"Number",align:"RIGHT"},{type:"input_value",name:"GREEN",check:"Number",align:"RIGHT"},{type:"input_value",name:"BLUE",check:"Number",align:"RIGHT"}],output:"Colour",colour:"%{BKY_COLOUR_HUE}",helpUrl:"%{BKY_COLOUR_RGB_HELPURL}",tooltip:"%{BKY_COLOUR_RGB_TOOLTIP}"},{type:"colour_blend",message0:"%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} %1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3",args0:[{type:"input_value",name:"COLOUR1",
@@ -34,7 +51,7 @@ this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number")
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 e="FROM_START"==c||"FROM_END"==c;if(e!=b){var d=this.sourceBlock_;d.updateAt_(a,e);d.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)}};
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_sort={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_SORT_TITLE,args0:[{type:"field_dropdown",name:"TYPE",options:[[Blockly.Msg.LISTS_SORT_TYPE_NUMERIC,"NUMERIC"],[Blockly.Msg.LISTS_SORT_TYPE_TEXT,"TEXT"],[Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE,"IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[[Blockly.Msg.LISTS_SORT_ORDER_ASCENDING,"1"],[Blockly.Msg.LISTS_SORT_ORDER_DESCENDING,"-1"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Array",colour:Blockly.Blocks.lists.HUE,
tooltip:Blockly.Msg.LISTS_SORT_TOOLTIP,helpUrl:Blockly.Msg.LISTS_SORT_HELPURL})}};
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);
@@ -95,7 +112,7 @@ mutationToDom:function(a){var b=document.createElement("mutation");a&&b.setAttri
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();this.getInput("RETURN")?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++)c=
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};var 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++)c=
goog.dom.createDom("arg"),c.setAttribute("name",this.arguments_[e]),d.appendChild(c);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)},callType_:"procedures_callnoreturn"};
Blockly.Blocks.procedures_defreturn={init:function(){var a=new Blockly.FieldTextInput("",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.workspace.options.comments||this.workspace.options.parentWorkspace&&
@@ -105,40 +122,29 @@ callType_:"procedures_callreturn"};Blockly.Blocks.procedures_mutatorcontainer={i
Blockly.Blocks.procedures_mutatorarg={init:function(){var a=new Blockly.FieldTextInput("x",this.validator_);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1;a.onFinishEditing_=this.createNewVar_;a.onFinishEditing_("x")},validator_:function(a){return(a=a.replace(/[\s\xa0]+/g,
" ").replace(/^ | $/g,""))||null},createNewVar_:function(a){var b=this.sourceBlock_;b&&b.workspace&&b.workspace.options&&b.workspace.options.parentWorkspace&&b.workspace.options.parentWorkspace.createVariable(a)}};
Blockly.Blocks.procedures_callnoreturn={init:function(){this.appendDummyInput("TOPROW").appendField(this.id,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null},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,
"NAME"),this.setTooltip((this.outputConnection?Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP:Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c,d=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace),e=d&&d.mutator&&d.mutator.isVisible();e||(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_=[]);d=this.rendered;this.rendered=!1;for(var f=0;f<this.arguments_.length;f++)if(c=this.getInput("ARG"+f))c=c.connection.targetConnection,this.quarkConnections_[this.quarkIds_[f]]=c,e&&c&&-1==b.indexOf(this.quarkIds_[f])&&(c.disconnect(),c.getSourceBlock().bumpNeighbours_());this.arguments_=[].concat(a);this.updateShape_();if(this.quarkIds_=b)for(f=0;f<this.arguments_.length;f++)e=
this.quarkIds_[f],e in this.quarkConnections_&&(c=this.quarkConnections_[e],Blockly.Mutator.reconnect(c,this,"ARG"+f)||delete this.quarkConnections_[e]);(this.rendered=d)&&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)))},
!this.workspace.isFlyout)if(a.type==Blockly.Events.BLOCK_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.BLOCK_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);a&&a.select()};a.push(b)},defType_:"procedures_defnoreturn"};
Blockly.Blocks.procedures_callreturn={init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null},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:Blockly.Blocks.procedures_callnoreturn.customContextMenu,defType_:"procedures_defreturn"};
Blockly.Blocks.procedures_ifreturn={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},
mutationToDom:function(){var a=document.createElement("mutation");a.setAttribute("value",Number(this.hasReturnValue_));return a},domToMutation:function(a){this.hasReturnValue_=1==a.getAttribute("value");this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN))},onchange:function(){if(this.workspace.isDragging&&!this.workspace.isDragging()){var a=!1,b=this;do{if(-1!=this.FUNCTION_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);
a?("procedures_defnoreturn"==b.type&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!1):"procedures_defreturn"!=b.type||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!0),this.setWarningText(null),this.isInFlyout||this.setDisabled(!1)):(this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING),
this.isInFlyout||this.getInheritedDisabled()||this.setDisabled(!0))}},FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};Blockly.Blocks.texts={};Blockly.Constants.Text={};Blockly.Constants.Text.HUE=160;Blockly.Blocks.texts.HUE=Blockly.Constants.Text.HUE;Blockly.defineBlocksWithJsonArray([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]}]);
Blockly.Constants.Text.textQuotesExtension=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")};Blockly.Extensions.register("text_quotes",Blockly.Constants.Text.textQuotesExtension);
Blockly.Blocks.text_join={init:function(){this.setHelpUrl(Blockly.Msg.TEXT_JOIN_HELPURL);this.setColour(Blockly.Blocks.texts.HUE);this.itemCount_=2;this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);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=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=
this.getInput("ADD"+b).connection.targetConnection;c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},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(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):
this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(;this.getInput("ADD"+a);)this.removeInput("ADD"+a),a++}};
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"))})}};
Blockly.Blocks.text_length={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_LENGTH_TITLE,args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_LENGTH_TOOLTIP,helpUrl:Blockly.Msg.TEXT_LENGTH_HELPURL})}};
Blockly.Blocks.text_isEmpty={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_ISEMPTY_TITLE,args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_ISEMPTY_TOOLTIP,helpUrl:Blockly.Msg.TEXT_ISEMPTY_HELPURL})}};
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);var b=this;this.setTooltip(function(){return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"0":"-1")})}};
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);var a=this;this.setTooltip(function(){var b=a.getFieldValue("WHERE"),c=Blockly.Msg.TEXT_CHARAT_TOOLTIP;if("FROM_START"==b||"FROM_END"==b)c+=" "+("FROM_START"==b?Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP:Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",a.workspace.options.oneBasedIndex?"#1":"#0");return c})},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")}};
this.isInFlyout||this.getInheritedDisabled()||this.setDisabled(!0))}},FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};Blockly.Blocks.texts={};Blockly.Constants.Text={};Blockly.Constants.Text.HUE=160;Blockly.Blocks.texts.HUE=Blockly.Constants.Text.HUE;
Blockly.defineBlocksWithJsonArray([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]},{type:"text_join",message0:"",output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_JOIN_HELPURL}",tooltip:"%{BKY_TEXT_JOIN_TOOLTIP}",mutator:"text_join_mutator"},{type:"text_create_join_container",message0:"%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",
args0:[{type:"input_dummy"},{type:"input_statement",name:"STACK"}],colour:"%{BKY_TEXTS_HUE}",tooltip:"%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",enableContextMenu:!1},{type:"text_create_join_item",message0:"%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",previousStatement:null,nextStatement:null,colour:"%{BKY_TEXTS_HUE}",tooltip:"{%BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",enableContextMenu:!1},{type:"text_append",message0:"%{BKY_TEXT_APPEND_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_TEXT_APPEND_VARIABLE}"},
{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,colour:"%{BKY_TEXTS_HUE}",extensions:["text_append_tooltip"]},{type:"text_length",message0:"%{BKY_TEXT_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",colour:"%{BKY_TEXTS_HUE}",tooltip:"%{BKY_TEXT_LENGTH_TOOLTIP}",helpUrl:"%{BKY_TEXT_LENGTH_HELPURL}"},{type:"text_isEmpty",message0:"%{BKY_TEXT_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],
output:"Boolean",colour:"%{BKY_TEXTS_HUE}",tooltip:"%{BKY_TEXT_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_TEXT_ISEMPTY_HELPURL}"},{type:"text_indexOf",message0:"%{BKY_TEXT_INDEXOF_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"END",options:[["%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}","FIRST"],["%{BKY_TEXT_INDEXOF_OPERATOR_LAST}","LAST"]]},{type:"input_value",name:"FIND",check:"String"}],output:"Number",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_INDEXOF_HELPURL}",inputsInline:!0,
extensions:["text_indexOf_tooltip"]},{type:"text_charAt",message0:"%{BKY_TEXT_CHARAT_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"input_dummy",name:"AT"}],output:"String",colour:"%{BKY_TEXTS_HUE}",helpUrl:"%{BKY_TEXT_CHARAT_HELPURL}",inputsInline:!0,mutator:"text_charAt_mutator"}]);
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 e="FROM_START"==c||"FROM_END"==c;if(e!=b){var d=this.sourceBlock_;d.updateAt_(a,e);d.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&this.moveInputBefore("AT1","AT2")}};
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.jsonInit({message0:Blockly.Msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_PRINT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_PRINT_HELPURL})}};
@@ -146,12 +152,23 @@ Blockly.Blocks.text_prompt_ext={init:function(){var a=[[Blockly.Msg.TEXT_PROMPT_
updateType_:function(a){this.outputConnection.setCheck("NUMBER"==a?"Number":"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(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);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");this.setTooltip(function(){return"TEXT"==b.getFieldValue("TYPE")?Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT:Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER})},updateType_:Blockly.Blocks.text_prompt_ext.updateType_,mutationToDom:Blockly.Blocks.text_prompt_ext.mutationToDom,domToMutation:Blockly.Blocks.text_prompt_ext.domToMutation};
Blockly.Blocks.text_count={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,colour:Blockly.Blocks.math.HUE,tooltip:Blockly.Msg.TEXT_COUNT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_COUNT_HELPURL})}};
Blockly.Blocks.text_count={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_COUNT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_COUNT_HELPURL})}};
Blockly.Blocks.text_replace={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_REPLACE_TOOLTIP,helpUrl:Blockly.Msg.TEXT_REPLACE_HELPURL})}};
Blockly.Blocks.text_reverse={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,colour:Blockly.Blocks.texts.HUE,tooltip:Blockly.Msg.TEXT_REVERSE_TOOLTIP,helpUrl:Blockly.Msg.TEXT_REVERSE_HELPURL})}};
Blockly.Constants.Text.QUOTE_IMAGE_MIXIN={QUOTE_IMAGE_LEFT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",QUOTE_IMAGE_RIGHT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==",
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(a==e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new Blockly.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,a?"\u201c":"\u201d")}};Blockly.Blocks.variables={};Blockly.Constants.Variables={};Blockly.Constants.Variables.HUE=330;Blockly.Blocks.variables.HUE=Blockly.Constants.Variables.HUE;
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(a==e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new Blockly.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,a?"\u201c":"\u201d")}};
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")};
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN={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=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=
e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},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(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(;this.getInput("ADD"+
a);)this.removeInput("ADD"+a),a++}};Blockly.Constants.Text.TEXT_JOIN_EXTENSION=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.itemCount_=2;this.updateShape_();this.setMutator(new Blockly.Mutator(["text_create_join_item"]))};Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION=function(){var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_APPEND_TOOLTIP?Blockly.Msg.TEXT_APPEND_TOOLTIP.replace("%1",a.getFieldValue("VAR")):""})};
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION=function(){var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",a.workspace.options.oneBasedIndex?"0":"-1")})};
Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN={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.Constants.Text.TEXT_CHARAT_EXTENSION=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.updateAt_(!0);var a=this;this.setTooltip(function(){var b=a.getFieldValue("WHERE"),c=Blockly.Msg.TEXT_CHARAT_TOOLTIP;("FROM_START"==b||"FROM_END"==b)&&(b="FROM_START"==b?Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP:
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP)&&(c+=" "+b.replace("%1",a.workspace.options.oneBasedIndex?"#1":"#0"));return c})};Blockly.Extensions.register("text_indexOf_tooltip",Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION);Blockly.Extensions.register("text_quotes",Blockly.Constants.Text.TEXT_QUOTES_EXTENSION);Blockly.Extensions.register("text_append_tooltip",Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION);
Blockly.Extensions.registerMutator("text_join_mutator",Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN,Blockly.Constants.Text.TEXT_JOIN_EXTENSION);Blockly.Extensions.registerMutator("text_charAt_mutator",Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN,Blockly.Constants.Text.TEXT_CHARAT_EXTENSION);Blockly.Blocks.variables={};Blockly.Constants.Variables={};Blockly.Constants.Variables.HUE=330;Blockly.Blocks.variables.HUE=Blockly.Constants.Variables.HUE;
Blockly.defineBlocksWithJsonArray([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,colour:"%{BKY_VARIABLES_HUE}",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,
nextStatement:null,colour:"%{BKY_VARIABLES_HUE}",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]);
Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if("variables_get"==this.type)var b="variables_set",c=Blockly.Msg.VARIABLES_GET_CREATE_SET;else b="variables_get",c=Blockly.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getFieldValue("VAR");d.text=c.replace("%1",e);c=goog.dom.createDom("field",null,e);c.setAttribute("name","VAR");c=goog.dom.createDom("block",null,c);c.setAttribute("type",b);d.callback=
Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if("variables_get"==this.type){var b="variables_set";var c=Blockly.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get",c=Blockly.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getFieldValue("VAR");d.text=c.replace("%1",e);c=goog.dom.createDom("field",null,e);c.setAttribute("name","VAR");c=goog.dom.createDom("block",null,c);c.setAttribute("type",b);d.callback=
Blockly.ContextMenu.callbackFactory(this,c);a.push(d)}};Blockly.Extensions.registerMixin("contextMenu_variableSetterGetter",Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
+187 -53
Ver Arquivo
@@ -16,7 +16,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# This script generates two versions of Blockly's core files:
# Usage: build.py <0 or more of accessible, core, generators, langfiles>
# build.py with no parameters builds all files.
# core builds blockly_compressed, blockly_uncompressed, and blocks_compressed.
# accessible builds blockly_accessible_compressed,
# blockly_accessible_uncompressed, and blocks_compressed.
# generators builds every <language>_compressed.js.
# langfiles builds every msg/js/<LANG>.js file.
# This script generates four versions of Blockly's core files. The first pair
# are:
# blockly_compressed.js
# blockly_uncompressed.js
# The compressed file is a concatenation of all of Blockly's core files which
@@ -28,6 +37,12 @@
# been renamed. The uncompressed file also allows for a faster developement
# cycle since there is no need to rebuild or recompile, just reload.
#
# The second pair are:
# blockly_accessible_compressed.js
# blockly_accessible_uncompressed.js
# These files are analogous to blockly_compressed and blockly_uncompressed,
# but also include the visually-impaired module for Blockly.
#
# This script also generates:
# blocks_compressed.js: The compressed Blockly language blocks.
# javascript_compressed.js: The compressed Javascript generator.
@@ -41,6 +56,15 @@ if sys.version_info[0] != 2:
raise Exception("Blockly build only compatible with Python 2.x.\n"
"You are using: " + sys.version)
for arg in sys.argv[1:len(sys.argv)]:
if (arg != 'core' and
arg != 'accessible' and
arg != 'generators' and
arg != 'langfiles' and
arg != 'demo'):
raise Exception("Invalid argument: \"" + arg + "\". Usage: build.py <0 or more of accessible," +
" core, generators, langfiles, demo>")
import errno, glob, httplib, json, os, re, subprocess, threading, urllib
@@ -71,13 +95,13 @@ 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):
def __init__(self, search_paths, target_filename):
threading.Thread.__init__(self)
self.search_paths = search_paths
self.target_filename = target_filename
def run(self):
target_filename = 'blockly_uncompressed.js'
f = open(target_filename, 'w')
f = open(self.target_filename, 'w')
f.write(HEADER)
f.write("""
var isNodeJS = !!(typeof module !== 'undefined' && module.exports &&
@@ -92,7 +116,7 @@ window.BLOCKLY_DIR = (function() {
if (!isNodeJS) {
// Find name of current directory.
var scripts = document.getElementsByTagName('script');
var re = new RegExp('(.+)[\/]blockly_uncompressed\.js$');
var re = new RegExp('(.+)[\/]blockly_(.*)uncompressed\.js$');
for (var i = 0, script; script = scripts[i]; i++) {
var match = re.exec(script.src);
if (match) {
@@ -122,20 +146,21 @@ window.BLOCKLY_BOOT = function() {
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.sort() # Deterministic build.
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)
'([\\/](core|accessible)[\\/])', '\\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()
provides.sort() # Deterministic build.
f.write('\n')
f.write('// Load Blockly.\n')
for provide in provides:
@@ -159,7 +184,7 @@ if (isNodeJS) {
}
""")
f.close()
print("SUCCESS: " + target_filename)
print("SUCCESS: " + self.target_filename)
class Gen_compressed(threading.Thread):
@@ -168,18 +193,68 @@ class Gen_compressed(threading.Thread):
Uses the Closure Compiler's online API.
Runs in a separate thread.
"""
def __init__(self, search_paths):
def __init__(self, search_paths, bundles):
threading.Thread.__init__(self)
self.search_paths = search_paths
self.bundles = bundles
def run(self):
self.gen_core()
self.gen_blocks()
self.gen_generator("javascript")
self.gen_generator("python")
self.gen_generator("php")
self.gen_generator("dart")
self.gen_generator("lua")
if ('core' in self.bundles):
self.gen_core()
if ('accessible' in self.bundles):
self.gen_accessible()
if ('core' in self.bundles or 'accessible' in self.bundles):
self.gen_blocks()
if ('generators' in self.bundles):
self.gen_generator("javascript")
self.gen_generator("python")
self.gen_generator("php")
self.gen_generator("dart")
self.gen_generator("lua")
if ('demo' in self.bundles):
self.gen_together()
def gen_together(self):
target_filename = os.path.join("demos", "fixed-advanced", "main_compressed.js")
# Define the parameters for the POST request.
params = [
("compilation_level", "ADVANCED_OPTIMIZATIONS"),
("use_closure_library", "true"),
("generate_exports", "true"),
("output_format", "json"),
("output_info", "compiled_code"),
("output_info", "warnings"),
("output_info", "errors"),
("output_info", "statistics"),
# debug options (to make the uglified code readable)
# ("formatting", "pretty_print"),
# ("formatting", "print_input_delimiter"),
# ("debug", "true"),
]
# Read in all the source files.
filenames = calcdeps.CalculateDependencies(self.search_paths,
[os.path.join("demos", "fixed-advanced", "main.js")])
filenames.sort() # Deterministic build.
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()
externs = [os.path.join("externs", "svg-externs.js")]
for filename in externs:
f = open(filename)
params.append(("js_externs", "".join(f.readlines())))
f.close()
self.do_compile(params, target_filename, filenames, "")
def gen_core(self):
target_filename = "blockly_compressed.js"
@@ -197,6 +272,62 @@ class Gen_compressed(threading.Thread):
# Read in all the source files.
filenames = calcdeps.CalculateDependencies(self.search_paths,
[os.path.join("core", "blockly.js")])
filenames.sort() # Deterministic build.
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_accessible(self):
target_filename = "blockly_accessible_compressed.js"
# Define the parameters for the POST request.
params = [
("compilation_level", "SIMPLE_OPTIMIZATIONS"),
("use_closure_library", "true"),
("language_out", "ES5"),
("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("accessible", "app.component.js")])
filenames.sort() # Deterministic build.
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_accessible(self):
target_filename = "blockly_accessible_compressed.js"
# Define the parameters for the POST request.
params = [
("compilation_level", "SIMPLE_OPTIMIZATIONS"),
("use_closure_library", "true"),
("language_out", "ES5"),
("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("accessible", "app.component.js")])
for filename in filenames:
# Filter out the Closure files (the compiler will add them).
if filename.startswith(os.pardir + os.sep): # '../'
@@ -221,8 +352,9 @@ class Gen_compressed(threading.Thread):
# Read in all the source files.
# Add Blockly.Blocks to be compatible with the compiler.
params.append(("js_code", "goog.provide('Blockly.Blocks');"))
params.append(("js_code", "goog.provide('Blockly');goog.provide('Blockly.Blocks');"))
filenames = glob.glob(os.path.join("blocks", "*.js"))
filenames.sort() # Deterministic build.
for filename in filenames:
f = open(filename)
params.append(("js_code", "".join(f.readlines())))
@@ -249,6 +381,7 @@ class Gen_compressed(threading.Thread):
params.append(("js_code", "goog.provide('Blockly.Generator');"))
filenames = glob.glob(
os.path.join("generators", language, "*.js"))
filenames.sort() # Deterministic build.
filenames.insert(0, os.path.join("generators", language + ".js"))
for filename in filenames:
f = open(filename)
@@ -313,31 +446,6 @@ class Gen_compressed(threading.Thread):
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"]
@@ -362,8 +470,9 @@ class Gen_langfiles(threading.Thread):
Runs in a separate thread.
"""
def __init__(self):
def __init__(self, force_gen):
threading.Thread.__init__(self)
self.force_gen = force_gen
def _rebuild(self, srcs, dests):
# Determine whether any of the files in srcs is newer than any in dests.
@@ -385,9 +494,10 @@ class Gen_langfiles(threading.Thread):
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"]]):
if (self.force_gen or
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",
@@ -456,14 +566,38 @@ if __name__ == "__main__":
developers.google.com/blockly/guides/modify/web/closure""")
sys.exit(1)
search_paths = calcdeps.ExpandDirectories(
core_search_paths = calcdeps.ExpandDirectories(
["core", os.path.join(os.path.pardir, "closure-library")])
core_search_paths.sort() # Deterministic build.
full_search_paths = calcdeps.ExpandDirectories(
["accessible", "core", os.path.join(os.path.pardir, "closure-library")])
full_search_paths.sort() # Deterministic build.
# Run both tasks in parallel threads.
if (len(sys.argv) == 1):
args = ['core', 'accessible', 'generators', 'defaultlangfiles']
else:
args = sys.argv
# Uncompressed and compressed are run 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()
if ('core' in args):
Gen_uncompressed(core_search_paths, 'blockly_uncompressed.js').start()
# This is run locally in a separate thread.
Gen_langfiles().start()
if ('accessible' in args):
Gen_uncompressed(full_search_paths, 'blockly_accessible_uncompressed.js').start()
if ('demo' in args):
all_search_paths = calcdeps.ExpandDirectories(
["accessible", "core", "blocks", os.path.join("demos", "fixed-advanced"), os.path.join("msg", "js"), os.path.join(os.path.pardir, "closure-library")])
all_search_paths.sort() # Deterministic build.
Gen_compressed(all_search_paths, args).start()
else:
# Compressed is limited by network and server speed.
Gen_compressed(full_search_paths, args).start()
# This is run locally in a separate thread
# defaultlangfiles checks for changes in the msg files, while manually asking
# to build langfiles will force the messages to be rebuilt.
if ('langfiles' in args or 'defaultlangfiles' in args):
Gen_langfiles('langfiles' in args).start()
+48 -55
Ver Arquivo
@@ -119,6 +119,8 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
this.comment = null;
/**
* The block's position in workspace units. (0, 0) is at the workspace's
* origin; scale does not change this value.
* @type {!goog.math.Coordinate}
* @private
*/
@@ -154,7 +156,7 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
/** @type {boolean|undefined} */
this.inputsInlineDefault = this.inputsInline;
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Create(this));
Blockly.Events.fire(new Blockly.Events.BlockCreate(this));
}
// Bind an onchange function, if it exists.
if (goog.isFunction(this.onchange)) {
@@ -190,6 +192,13 @@ Blockly.Block.prototype.data = null;
*/
Blockly.Block.prototype.colour_ = '#000000';
/**
* Colour of the block as HSV hue value (0-360)
* @type {?number}
* @private
*/
Blockly.Block.prototype.hue_ = null;
/**
* Dispose of this block.
* @param {boolean} healStack If true, then try to heal any gap by connecting
@@ -207,7 +216,7 @@ Blockly.Block.prototype.dispose = function(healStack) {
}
this.unplug(healStack);
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Delete(this));
Blockly.Events.fire(new Blockly.Events.BlockDelete(this));
}
Blockly.Events.disable();
@@ -309,7 +318,7 @@ Blockly.Block.prototype.getConnections_ = function() {
/**
* 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
* @package
*/
Blockly.Block.prototype.lastConnectionInStack_ = function() {
var nextConnection = this.nextConnection;
@@ -330,43 +339,9 @@ Blockly.Block.prototype.lastConnectionInStack_ = function() {
* connected should not coincidentally line up on screen.
* @private
*/
// TODO: Refactor to return early in headless mode.
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 through 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);
}
}
}
}
}
console.warn('Not expected to reach this bumpNeighbours_ function. The ' +
'BlockSvg function for bumpNeighbours_ was expected to be called instead.');
};
/**
@@ -627,16 +602,27 @@ Blockly.Block.prototype.getColour = function() {
return this.colour_;
};
/**
* Get the HSV hue value of a block. Null if hue not set.
* @return {?number} Hue value (0-360)
*/
Blockly.Block.prototype.getHue = function() {
return this.hue_;
}
/**
* Change the colour of a block.
* @param {number|string} colour HSV hue value, or #RRGGBB string.
*/
Blockly.Block.prototype.setColour = function(colour) {
var hue = Number(colour);
if (!isNaN(hue)) {
if (!isNaN(hue) && 0 <= hue && hue <= 360) {
this.hue_ = hue;
this.colour_ = Blockly.hueToRgb(hue);
} else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) {
this.colour_ = colour;
// Only store hue if colour is set as a hue
this.hue_ = null;
} else {
throw 'Invalid colour: ' + colour;
}
@@ -825,7 +811,7 @@ Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) {
*/
Blockly.Block.prototype.setInputsInline = function(newBoolean) {
if (this.inputsInline != newBoolean) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this, 'inline', null, this.inputsInline, newBoolean));
this.inputsInline = newBoolean;
}
@@ -864,7 +850,7 @@ Blockly.Block.prototype.getInputsInline = function() {
*/
Blockly.Block.prototype.setDisabled = function(disabled) {
if (this.disabled != disabled) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this, 'disabled', null, this.disabled, disabled));
this.disabled = disabled;
}
@@ -901,7 +887,7 @@ Blockly.Block.prototype.isCollapsed = function() {
*/
Blockly.Block.prototype.setCollapsed = function(collapsed) {
if (this.collapsed_ != collapsed) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this, 'collapsed', null, this.collapsed_, collapsed));
this.collapsed_ = collapsed;
}
@@ -1099,10 +1085,14 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
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);
if (token <= 0 || token > args.length) {
throw new Error('Block \"' + this.type + '\": ' +
'Message index %' + token + ' out of range.');
}
if (indexDup[token]) {
throw new Error('Block \"' + this.type + '\": ' +
'Message index %' + token + ' duplicated.');
}
indexDup[token] = true;
indexCount++;
elements.push(args[token - 1]);
@@ -1113,8 +1103,10 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
}
}
}
goog.asserts.assert(indexCount == args.length,
'block "%s": Message does not reference all %s arg(s).', this.type, args.length);
if(indexCount != args.length) {
throw new Error('Block \"' + this.type + '\": ' +
'Message does not reference all ' + args.length + ' arg(s).');
}
// Add last dummy input if needed.
if (elements.length && (typeof elements[elements.length - 1] == 'string' ||
goog.string.startsWith(elements[elements.length - 1]['type'],
@@ -1271,7 +1263,8 @@ Blockly.Block.newFieldTextInputFromJson_ = function(options) {
*/
Blockly.Block.newFieldVariableFromJson_ = function(options) {
var varname = Blockly.utils.replaceMessageReferences(options['variable']);
return new Blockly.FieldVariable(varname);
var variableTypes = options['variableTypes'];
return new Blockly.FieldVariable(varname, null, variableTypes);
};
@@ -1421,7 +1414,7 @@ Blockly.Block.prototype.getCommentText = function() {
*/
Blockly.Block.prototype.setCommentText = function(text) {
if (this.comment != text) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this, 'comment', null, this.comment, text || ''));
this.comment = text;
}
@@ -1445,7 +1438,7 @@ Blockly.Block.prototype.setMutator = function(/* mutator */) {
/**
* Return the coordinates of the top-left corner of this block relative to the
* drawing surface's origin (0,0).
* drawing surface's origin (0,0), in workspace units.
* @return {!goog.math.Coordinate} Object with .x and .y properties.
*/
Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
@@ -1454,12 +1447,12 @@ Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
/**
* Move a block by a relative offset.
* @param {number} dx Horizontal offset.
* @param {number} dy Vertical offset.
* @param {number} dx Horizontal offset, in workspace units.
* @param {number} dy Vertical offset, in workspace units.
*/
Blockly.Block.prototype.moveBy = function(dx, dy) {
goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
var event = new Blockly.Events.Move(this);
var event = new Blockly.Events.BlockMove(this);
this.xy_.translate(dx, dy);
event.recordNew();
Blockly.Events.fire(event);
+46 -17
Ver Arquivo
@@ -80,6 +80,15 @@ Blockly.BlockDragSurfaceSvg.prototype.container_ = null;
*/
Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1;
/**
* Cached value for the translation of the drag surface.
* This translation is in pixel units, because the scale is applied to the
* drag group rather than the top-level SVG.
* @type {goog.math.Coordinate}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null;
/**
* Create the drag surface and inject it into the container.
*/
@@ -109,13 +118,14 @@ Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
// appendChild removes the blocks from the previous parent
this.dragGroup_.appendChild(blocks);
this.SVG_.style.display = 'block';
this.surfaceXY_ = new goog.math.Coordinate(0, 0);
};
/**
* Translate and scale the entire drag surface group to keep in sync with the
* workspace.
* @param {number} x X translation
* @param {number} y Y translation
* Translate and scale the entire drag surface group to the given position, to
* keep in sync with the workspace.
* @param {number} x X translation in workspace coordinates.
* @param {number} y Y translation in workspace coordinates.
* @param {number} scale Scale of the group.
*/
Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
@@ -128,6 +138,23 @@ Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, sc
' scale(' + scale + ')');
};
/**
* Translate the drag surface's SVG based on its internal state.
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() {
var x = this.surfaceXY_.x;
var y = this.surfaceXY_.y;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
x = x.toFixed(0);
y = y.toFixed(0);
this.SVG_.style.display = 'block';
Blockly.utils.setCssTransform(this.SVG_,
'translate3d(' + x + 'px, ' + y + 'px, 0px)');
};
/**
* Translate the entire drag surface during a drag.
* We translate the drag surface instead of the blocks inside the surface
@@ -137,15 +164,8 @@ Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, sc
* @param {number} y Y translation for the entire surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
x *= this.scale_;
y *= this.scale_;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
x = x.toFixed(0);
y = y.toFixed(0);
this.SVG_.style.display = 'block';
Blockly.utils.setCssTransform(this.SVG_,
'translate3d(' + x + 'px, ' + y + 'px, 0px)');
this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_);
this.translateSurfaceInternal_();
};
/**
@@ -180,12 +200,21 @@ Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
/**
* Clear the group and hide the surface; move the blocks off onto the provided
* element.
* @param {!Element} newSurface Surface the dragging blocks should be moved to.
* If the block is being deleted it doesn't need to go back to the original
* surface, since it would be removed immediately during dispose.
* @param {Element} opt_newSurface Surface the dragging blocks should be moved
* to, or null if the blocks should be removed from this surface without
* being moved to a different surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
// appendChild removes the node from this.dragGroup_
newSurface.appendChild(this.getCurrentBlock());
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) {
if (opt_newSurface) {
// appendChild removes the node from this.dragGroup_
opt_newSurface.appendChild(this.getCurrentBlock());
} else {
this.dragGroup_.removeChild(this.getCurrentBlock());
}
this.SVG_.style.display = 'none';
goog.asserts.assert(this.dragGroup_.childNodes.length == 0,
'Drag group was not cleared.');
this.surfaceXY_ = null;
};
+324
Ver Arquivo
@@ -0,0 +1,324 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 dragging a block visually.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.BlockDragger');
goog.require('Blockly.DraggedConnectionManager');
goog.require('goog.math.Coordinate');
goog.require('goog.asserts');
/**
* Class for a block dragger. It moves blocks around the workspace when they
* are being dragged by a mouse or touch.
* @param {!Blockly.Block} block The block to drag.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
* @constructor
*/
Blockly.BlockDragger = function(block, workspace) {
/**
* The top block in the stack that is being dragged.
* @type {!Blockly.BlockSvg}
* @private
*/
this.draggingBlock_ = block;
/**
* The workspace on which the block is being dragged.
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* Object that keeps track of connections on dragged blocks.
* @type {!Blockly.DraggedConnectionManager}
* @private
*/
this.draggedConnectionManager_ = new Blockly.DraggedConnectionManager(
this.draggingBlock_);
/**
* Which delete area the mouse pointer is over, if any.
* One of {@link Blockly.DELETE_AREA_TRASH},
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
* @type {?number}
* @private
*/
this.deleteArea_ = null;
/**
* Whether the block would be deleted if dropped immediately.
* @type {boolean}
* @private
*/
this.wouldDeleteBlock_ = false;
/**
* The location of the top left corner of the dragging block at the beginning
* of the drag in workspace coordinates.
* @type {!goog.math.Coordinate}
* @private
*/
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
/**
* A list of all of the icons (comment, warning, and mutator) that are
* on this block and its descendants. Moving an icon moves the bubble that
* extends from it if that bubble is open.
* @type {Array.<!Object>}
* @private
*/
this.dragIconData_ = Blockly.BlockDragger.initIconData_(block);
};
/**
* Sever all links from this object.
* @package
*/
Blockly.BlockDragger.prototype.dispose = function() {
this.draggingBlock_ = null;
this.workspace_ = null;
this.startWorkspace_ = null;
this.dragIconData_.length = 0;
if (this.draggedConnectionManager_) {
this.draggedConnectionManager_.dispose();
this.draggedConnectionManager_ = null;
}
};
/**
* Make a list of all of the icons (comment, warning, and mutator) that are
* on this block and its descendants. Moving an icon moves the bubble that
* extends from it if that bubble is open.
* @param {!Blockly.BlockSvg} block The root block that is being dragged.
* @return {!Array.<!Object>} The list of all icons and their locations.
* @private
*/
Blockly.BlockDragger.initIconData_ = function(block) {
// Build a list of icons that need to be moved and where they started.
var dragIconData = [];
var descendants = block.getDescendants();
for (var i = 0, descendant; descendant = descendants[i]; i++) {
var icons = descendant.getIcons();
for (var j = 0; j < icons.length; j++) {
var data = {
// goog.math.Coordinate with x and y properties (workspace coordinates).
location: icons[j].getIconLocation(),
// Blockly.Icon
icon: icons[j]
};
dragIconData.push(data);
}
}
return dragIconData;
};
/**
* Start dragging a block. This includes moving it to the drag surface.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @package
*/
Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) {
if (!Blockly.Events.getGroup()) {
Blockly.Events.setGroup(true);
}
this.workspace_.setResizesEnabled(false);
Blockly.BlockSvg.disconnectUiStop_();
if (this.draggingBlock_.getParent()) {
this.draggingBlock_.unplug();
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.translate(newLoc.x, newLoc.y);
this.draggingBlock_.disconnectUiEffect();
}
this.draggingBlock_.setDragging(true);
// For future consideration: we may be able to put moveToDragSurface inside
// the block dragger, which would also let the block not track the block drag
// surface.
this.draggingBlock_.moveToDragSurface_();
if (this.workspace_.toolbox_) {
this.workspace_.toolbox_.addDeleteStyle();
}
};
/**
* Execute a step of block dragging, based on the given event. Update the
* display accordingly.
* @param {!Event} e The most recent move event.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) {
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.moveDuringDrag(newLoc);
this.dragIcons_(delta);
this.deleteArea_ = this.workspace_.isDeleteArea(e);
this.draggedConnectionManager_.update(delta, this.deleteArea_);
this.updateCursorDuringBlockDrag_();
};
/**
* Finish a block drag and put the block back on the workspace.
* @param {!Event} e The mouseup/touchend event.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.dragBlock(e, currentDragDeltaXY);
this.dragIconData_ = [];
Blockly.BlockSvg.disconnectUiStop_();
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.moveOffDragSurface_(newLoc);
var deleted = this.maybeDeleteBlock_();
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBlock_.moveConnections_(delta.x, delta.y);
this.draggingBlock_.setDragging(false);
this.draggedConnectionManager_.applyConnections();
this.draggingBlock_.render();
this.fireMoveEvent_();
this.draggingBlock_.scheduleSnapAndBump();
}
this.workspace_.setResizesEnabled(true);
if (this.workspace_.toolbox_) {
this.workspace_.toolbox_.removeDeleteStyle();
}
Blockly.Events.setGroup(false);
};
/**
* Fire a move event at the end of a block drag.
* @private
*/
Blockly.BlockDragger.prototype.fireMoveEvent_ = function() {
var event = new Blockly.Events.BlockMove(this.draggingBlock_);
event.oldCoordinate = this.startXY_;
event.recordNew();
Blockly.Events.fire(event);
};
/**
* Shut the trash can and, if necessary, delete the dragging block.
* Should be called at the end of a block drag.
* @return {boolean} whether the block was deleted.
* @private
*/
Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() {
var trashcan = this.workspace_.trashcan;
if (this.wouldDeleteBlock_) {
if (trashcan) {
goog.Timer.callOnce(trashcan.close, 100, trashcan);
}
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.draggingBlock_.dispose(false, true);
} else if (trashcan) {
// Make sure the trash can is closed.
trashcan.close();
}
return this.wouldDeleteBlock_;
};
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging block would be deleted if released immediately.
* @private
*/
Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() {
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
var trashcan = this.workspace_.trashcan;
if (this.wouldDeleteBlock_) {
this.draggingBlock_.setDeleteStyle(true);
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
trashcan.setOpen_(true);
}
} else {
this.draggingBlock_.setDeleteStyle(false);
if (trashcan) {
trashcan.setOpen_(false);
}
}
};
/**
* Convert a coordinate object from pixels to workspace units, including a
* correction for mutator workspaces.
* This function does not consider differing origins. It simply scales the
* input's x and y values.
* @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values
* in css pixel units.
* @return {!goog.math.Coordinate} The input coordinate divided by the workspace
* scale.
* @private
*/
Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same as
// the scale on the parent workspace.
// Fix that for dragging.
var mainScale = this.workspace_.options.parentWorkspace.scale;
result = result.scale(1 / mainScale);
}
return result;
};
/**
* Move all of the icons connected to this drag.
* @param {!goog.math.Coordinate} dxy How far to move the icons from their
* original positions, in workspace units.
* @private
*/
Blockly.BlockDragger.prototype.dragIcons_ = function(dxy) {
// Moving icons moves their associated bubbles.
for (var i = 0; i < this.dragIconData_.length; i++) {
var data = this.dragIconData_[i];
data.icon.setIconLocation(goog.math.Coordinate.sum(data.location, dxy));
}
};
+22
Ver Arquivo
@@ -255,6 +255,28 @@ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR =
Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
(Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5);
/**
* 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 in workspace units.
*/
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};
};
/**
* Render the block.
* Lays out and reflows a block based on its contents and settings.
+195 -415
Ver Arquivo
@@ -28,6 +28,7 @@ goog.provide('Blockly.BlockSvg');
goog.require('Blockly.Block');
goog.require('Blockly.ContextMenu');
goog.require('Blockly.Grid');
goog.require('Blockly.RenderedConnection');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
@@ -56,6 +57,7 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
* @private
*/
this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, null);
this.svgGroup_.translate_ = '';
/**
* @type {SVGElement}
@@ -89,7 +91,7 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
* @type {boolean}
* @private
*/
this.useDragSurface_ = Blockly.utils.is3dSupported() && workspace.blockDragSurface_;
this.useDragSurface_ = Blockly.utils.is3dSupported() && !!workspace.blockDragSurface_;
Blockly.Tooltip.bindMouseEvents(this.svgPath_);
Blockly.BlockSvg.superClass_.constructor.call(this,
@@ -99,10 +101,12 @@ goog.inherits(Blockly.BlockSvg, Blockly.Block);
/**
* Height of this block, not including any statement blocks above or below.
* Height is in workspace units.
*/
Blockly.BlockSvg.prototype.height = 0;
/**
* Width of this block, including any connected value blocks.
* Width is in workspace units.
*/
Blockly.BlockSvg.prototype.width = 0;
@@ -138,9 +142,6 @@ Blockly.BlockSvg.prototype.initSvg = function() {
if (!this.workspace.options.readOnly && !this.eventsInit_) {
Blockly.bindEventWithChecks_(this.getSvgRoot(), 'mousedown', this,
this.onMouseDown_);
var thisBlock = this;
Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
function(e) {Blockly.longStart_(e, thisBlock);});
}
this.eventsInit_ = true;
@@ -229,70 +230,6 @@ Blockly.BlockSvg.prototype.getIcons = function() {
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.moveOffDragSurface_();
selected.render();
selected.workspace.setResizesEnabled(true);
// Ensure that any snap 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);
}
}
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.
@@ -324,8 +261,12 @@ Blockly.BlockSvg.prototype.setParent = function(newParent) {
/**
* 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.
* drawing surface's origin (0,0), in workspace units.
* If the block is on the workspace, (0, 0) is the origin of the workspace
* coordinate system.
* This does not change with workspace scale.
* @return {!goog.math.Coordinate} Object with .x and .y properties in
* workspace coordinates.
*/
Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
var x = 0;
@@ -358,12 +299,12 @@ Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
/**
* Move a block by a relative offset.
* @param {number} dx Horizontal offset.
* @param {number} dy Vertical offset.
* @param {number} dx Horizontal offset in workspace units.
* @param {number} dy Vertical offset in workspace units.
*/
Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
var event = new Blockly.Events.Move(this);
var event = new Blockly.Events.BlockMove(this);
var xy = this.getRelativeToSurfaceXY();
this.translate(xy.x + dx, xy.y + dy);
this.moveConnections_(dx, dy);
@@ -375,8 +316,8 @@ Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
/**
* Transforms a block by setting the translation on the transform attribute
* of the block's SVG.
* @param {number} x The x coordinate of the translation.
* @param {number} y The y coordinate of the translation.
* @param {number} x The x coordinate of the translation in workspace units.
* @param {number} y The y coordinate of the translation in workspace units.
*/
Blockly.BlockSvg.prototype.translate = function(x, y) {
this.getSvgRoot().setAttribute('transform',
@@ -396,6 +337,7 @@ Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
// The translation for drag surface blocks,
// is equal to the current relative-to-surface position,
// to keep the position in sync as it move on/off the surface.
// This is in workspace coordinates.
var xy = this.getRelativeToSurfaceXY();
this.clearTransformAttributes_();
this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y);
@@ -407,19 +349,37 @@ Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
* Move this block back to the workspace block canvas.
* Generally should be called at the same time as setDragging_(false).
* Does nothing if useDragSurface_ is false.
* @param {!goog.math.Coordinate} newXY The position the block should take on
* on the workspace canvas, in workspace coordinates.
* @private
*/
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function() {
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function(newXY) {
if (!this.useDragSurface_) {
return;
}
// Translate to current position, turning off 3d.
var xy = this.getRelativeToSurfaceXY();
this.clearTransformAttributes_();
this.translate(xy.x, xy.y);
this.translate(newXY.x, newXY.y);
this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas());
};
/**
* Move this block during a drag, taking into account whether we are using a
* drag surface to translate blocks.
* This block must be a top-level block.
* @param {!goog.math.Coordinate} newLoc The location to translate to, in
* workspace coordinates.
* @package
*/
Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) {
if (this.useDragSurface_) {
this.workspace.blockDragSurface_.translateSurface(newLoc.x, newLoc.y);
} else {
this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
this.svgGroup_.setAttribute('transform',
this.svgGroup_.translate_ + this.svgGroup_.skew_);
}
};
/**
* Clear the block of transform="..." attributes.
* Used when the block is switching from 3d to 2d transform or vice versa.
@@ -436,7 +396,7 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
if (!this.workspace) {
return; // Deleted block.
}
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
if (this.workspace.isDragging()) {
return; // Don't bump blocks during a drag.
}
if (this.getParent()) {
@@ -445,11 +405,11 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
if (this.isInFlyout) {
return; // Don't move blocks around in a flyout.
}
if (!this.workspace.options.gridOptions ||
!this.workspace.options.gridOptions['snap']) {
var grid = this.workspace.getGrid();
if (!grid || !grid.shouldSnap()) {
return; // Config says no snapping.
}
var spacing = this.workspace.options.gridOptions['spacing'];
var spacing = grid.getSpacing();
var half = spacing / 2;
var xy = this.getRelativeToSurfaceXY();
var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
@@ -461,31 +421,10 @@ Blockly.BlockSvg.prototype.snapToGrid = function() {
}
};
/**
* 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.
* Coordinate system: workspace coordinates.
* @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}}
* Object with top left and bottom right coordinates of the bounding box.
*/
@@ -564,23 +503,7 @@ Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
* @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 list = this.createTabList_();
var i = list.indexOf(start);
if (i == -1) {
// No start location, start at the beginning or end.
@@ -600,132 +523,40 @@ Blockly.BlockSvg.prototype.tab = function(start, forward) {
}
};
/**
* Create an ordered list of all text fields and connected inputs.
* @return {!Array<!Blockly.FieldTextInput|!Blockly.Input>} The ordered list.
* @private
*/
Blockly.BlockSvg.prototype.createTabList_ = function() {
// This function need not be efficient since it runs once on a keypress.
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(# 1276): Also support dropdown fields.
list.push(field);
}
}
if (input.connection) {
var block = input.connection.targetBlock();
if (block) {
list.push(block);
}
}
}
return list;
};
/**
* Handle a mouse-down on an SVG block.
* @param {!Event} e Mouse down event or touch start event.
* @private
*/
Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
if (this.workspace.options.readOnly) {
return;
}
if (this.isInFlyout) {
// longStart's simulation of right-clicks for longpresses on touch devices
// calls the onMouseDown_ function defined on the prototype of the object
// the was longpressed (in this case, a Blockly.BlockSvg). In this case
// that behaviour is wrong, because Blockly.Flyout.prototype.blockMouseDown
// should be called for a mousedown on a block in the flyout, which blocks
// execution of the block's onMouseDown_ function.
if (e.type == 'touchstart' && Blockly.utils.isRightButton(e)) {
Blockly.Flyout.blockRightClick_(e, this);
e.stopPropagation();
e.preventDefault();
}
return;
}
if (this.isInMutator) {
// Mutator's coordinate system could be out of date because the bubble was
// dragged, the block was moved, the parent workspace zoomed, etc.
this.workspace.resize();
}
this.workspace.updateScreenCalculationsIfScrolled();
this.workspace.markFocused();
Blockly.terminateDrag_();
this.select();
Blockly.hideChaff();
if (Blockly.utils.isRightButton(e)) {
// Right-click.
this.showContextMenu_(e);
// Click, not drag, so stop waiting for other touches from this identifier.
Blockly.Touch.clearTouchIdentifier();
} 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.bindEventWithChecks_(document,
'mouseup', this, this.onMouseUp_);
Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(
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) {
Blockly.Touch.clearTouchIdentifier();
if (Blockly.dragMode_ != Blockly.DRAG_FREE &&
!Blockly.WidgetDiv.isVisible()) {
Blockly.Events.fire(
new Blockly.Events.Ui(this, 'click', undefined, undefined));
}
Blockly.terminateDrag_();
var deleteArea = this.workspace.isDeleteArea(e);
// Connect to a nearby block, but not if it's over the toolbox.
if (Blockly.selected && Blockly.highlightedConnection_ &&
deleteArea != Blockly.DELETE_AREA_TOOLBOX) {
// 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 (deleteArea && !this.getParent() && Blockly.selected.isDeletable()) {
// We didn't connect the block, and it was over the trash can or the
// toolbox. Delete it.
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;
}
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
if (!Blockly.WidgetDiv.isVisible()) {
Blockly.Events.setGroup(false);
var gesture = this.workspace.getGesture(e);
if (gesture) {
gesture.handleBlockStart(e, this);
}
};
@@ -868,7 +699,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
menuOptions.push(helpOption);
// Allow the block to add or modify menuOptions.
if (this.customContextMenu && !block.isInFlyout) {
if (this.customContextMenu) {
this.customContextMenu(menuOptions);
}
@@ -879,8 +710,10 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
/**
* 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.
* @param {number} dx Horizontal offset from current location, in workspace
* units.
* @param {number} dy Vertical offset from current location, in workspace
* units.
* @private
*/
Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
@@ -907,9 +740,9 @@ Blockly.BlockSvg.prototype.moveConnections_ = function(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
* @package
*/
Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
Blockly.BlockSvg.prototype.setDragging = function(adding) {
if (adding) {
var group = this.getSvgRoot();
group.translate_ = '';
@@ -925,149 +758,7 @@ Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
}
// 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_();
this.workspace.setResizesEnabled(false);
var disconnectEffect = !!this.parentBlock_;
// If in a stack, either split the stack, or pull out single block.
var healStack = !Blockly.DRAG_STACK;
if (e.altKey || e.ctrlKey || e.metaKey) {
healStack = !healStack;
}
// Push this block to the very top of the stack.
this.unplug(healStack);
if (disconnectEffect) {
var group = this.getSvgRoot();
group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
this.disconnectUiEffect();
}
this.setDragging_(true);
this.moveToDragSurface_();
}
}
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
// Unrestricted dragging.
var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_);
var group = this.getSvgRoot();
if (this.useDragSurface_) {
this.workspace.blockDragSurface_.translateSurface(newXY.x, newXY.y);
} else {
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 localConnection = null;
var radiusConnection = Blockly.SNAP_RADIUS;
for (var i = 0; i < myConnections.length; i++) {
var myConnection = myConnections[i];
var neighbour = myConnection.closest(radiusConnection, dxy);
if (neighbour.connection) {
closestConnection = 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;
}
var wouldDeleteBlock = this.updateCursor_(e, closestConnection);
// Add connection highlighting if needed.
if (!wouldDeleteBlock && closestConnection &&
closestConnection != Blockly.highlightedConnection_) {
closestConnection.highlight();
Blockly.highlightedConnection_ = closestConnection;
Blockly.localConnection_ = localConnection;
}
}
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
e.preventDefault();
};
/**
* Provide visual indication of whether the block will be deleted if
* dropped here.
* Prefer connecting over dropping into the trash can, but prefer dragging to
* the toolbox over connecting to other blocks.
* @param {!Event} e Mouse move event.
* @param {Blockly.Connection} closestConnection The connection this block would
* potentially connect to if dropped here, or null.
* @return {boolean} True if the block would be deleted if dropped here,
* otherwise false.
* @private
*/
Blockly.BlockSvg.prototype.updateCursor_ = function(e, closestConnection) {
var deleteArea = this.workspace.isDeleteArea(e);
var wouldConnect = Blockly.selected && closestConnection &&
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
var wouldDelete = deleteArea && !this.getParent() &&
Blockly.selected.isDeletable();
var showDeleteCursor = wouldDelete && !wouldConnect;
if (showDeleteCursor) {
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
if (deleteArea == Blockly.DELETE_AREA_TRASH && this.workspace.trashcan) {
this.workspace.trashcan.setOpen_(true);
}
return true;
} else {
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
if (this.workspace.trashcan) {
this.workspace.trashcan.setOpen_(false);
}
return false;
this.childBlocks_[i].setDragging(adding);
}
};
@@ -1142,7 +833,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
// If this block is being dragged, unlink the mouse events.
if (Blockly.selected == this) {
this.unselect();
Blockly.terminateDrag_();
this.workspace.cancelCurrentGesture();
}
// If this block has a context menu open, close it.
if (Blockly.ContextMenu.currentBlock == this) {
@@ -1181,7 +872,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
* Play some UI effects (sound, animation) when disposing of a block.
*/
Blockly.BlockSvg.prototype.disposeUiEffect = function() {
this.workspace.playAudio('delete');
this.workspace.getAudioManager().play('delete');
var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_));
// Deeply clone the current block.
@@ -1219,10 +910,8 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
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);
setTimeout(Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start,
workspaceScale);
}
};
@@ -1230,7 +919,7 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
* Play some UI effects (sound, ripple) after a connection has been established.
*/
Blockly.BlockSvg.prototype.connectionUiEffect = function() {
this.workspace.playAudio('click');
this.workspace.getAudioManager().play('click');
if (this.workspace.scale < 1) {
return; // Too small to care about visual effects.
}
@@ -1267,10 +956,8 @@ Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
} 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);
Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(
Blockly.BlockSvg.connectionUiStep_, 10, ripple, start, workspaceScale);
}
};
@@ -1278,7 +965,7 @@ Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
* Play some UI effects (sound, animation) when disconnecting a block.
*/
Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
this.workspace.playAudio('disconnect');
this.workspace.getAudioManager().play('disconnect');
if (this.workspace.scale < 1) {
return; // Too small to care about visual effects.
}
@@ -1314,11 +1001,10 @@ Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) {
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);
Blockly.BlockSvg.disconnectUiStop_.pid =
setTimeout(Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude,
start);
}
group.setAttribute('transform', group.translate_ + group.skew_);
};
@@ -1470,7 +1156,7 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
clearTimeout(this.setWarningText.pid_[id]);
delete this.setWarningText.pid_[id];
}
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
if (this.workspace.isDragging()) {
// Don't change the warning text during a drag.
// Wait until the drag finishes.
var thisBlock = this;
@@ -1581,13 +1267,6 @@ Blockly.BlockSvg.prototype.setHighlighted = function(highlighted) {
Blockly.BlockSvg.prototype.addSelect = function() {
Blockly.utils.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);
};
/**
@@ -1598,6 +1277,22 @@ Blockly.BlockSvg.prototype.removeSelect = function() {
'blocklySelected');
};
/**
* Update the cursor over this block by adding or removing a class.
* @param {boolean} enable True if the delete cursor should be shown, false
* otherwise.
* @package
*/
Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) {
if (enable) {
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
'blocklyDraggingDelete');
} else {
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
'blocklyDraggingDelete');
}
};
// Overrides of functions on Blockly.Block that take into account whether the
// block has been rendered.
@@ -1613,6 +1308,22 @@ Blockly.BlockSvg.prototype.setColour = function(colour) {
}
};
/**
* Move this block to the front of the visible workspace.
* <g> tags do not respect z-index so svg renders them in the
* order that they are in the dom. By placing this block first within the
* block group's <g>, it will render on top of any other blocks.
* @package
*/
Blockly.BlockSvg.prototype.bringToFront = function() {
var block = this;
do {
var root = block.getSvgRoot();
root.parentNode.appendChild(root);
block = block.getParent();
} while (block);
};
/**
* Set whether this block can chain onto the bottom of another block.
* @param {boolean} newBoolean True if there can be a previous statement.
@@ -1736,7 +1447,7 @@ Blockly.BlockSvg.prototype.appendInput_ = function(type, name) {
* 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
* @package
*/
Blockly.BlockSvg.prototype.getConnections_ = function(all) {
var myConnections = [];
@@ -1770,3 +1481,72 @@ Blockly.BlockSvg.prototype.getConnections_ = function(all) {
Blockly.BlockSvg.prototype.makeConnection_ = function(type) {
return new Blockly.RenderedConnection(this, type);
};
/**
* Bump unconnected blocks out of alignment. Two blocks which aren't actually
* connected should not coincidentally line up on screen.
* @private
*/
Blockly.BlockSvg.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 through 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);
}
}
}
}
}
};
/**
* Schedule snapping to grid and bumping neighbours to occur after a brief
* delay.
* @package
*/
Blockly.BlockSvg.prototype.scheduleSnapAndBump = function() {
var block = this;
// Ensure that any snap and bump are part of this move's event group.
var group = Blockly.Events.getGroup();
setTimeout(function() {
Blockly.Events.setGroup(group);
block.snapToGrid();
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY / 2);
setTimeout(function() {
Blockly.Events.setGroup(group);
block.bumpNeighbours_();
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY);
};
+11 -41
Ver Arquivo
@@ -73,20 +73,6 @@ Blockly.mainWorkspace = null;
*/
Blockly.selected = null;
/**
* Currently highlighted connection (during a drag).
* @type {Blockly.Connection}
* @private
*/
Blockly.highlightedConnection_ = 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>}
@@ -108,15 +94,6 @@ Blockly.clipboardXml_ = null;
*/
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;
/**
* Cached value for whether 3D is supported.
* @type {!boolean}
@@ -206,10 +183,18 @@ Blockly.onKeyDown_ = function(e) {
// Do this first to prevent an error in the delete code from resulting in
// data loss.
e.preventDefault();
// Don't delete while dragging. Jeez.
if (Blockly.mainWorkspace.isDragging()) {
return;
}
if (Blockly.selected && Blockly.selected.isDeletable()) {
deleteBlock = true;
}
} else if (e.altKey || e.ctrlKey || e.metaKey) {
// Don't use meta keys during drags.
if (Blockly.mainWorkspace.isDragging()) {
return;
}
if (Blockly.selected &&
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
if (e.keyCode == 67) {
@@ -239,25 +224,11 @@ Blockly.onKeyDown_ = function(e) {
// 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.selected.dispose(/* heal */ true, true);
Blockly.Events.setGroup(false);
}
};
/**
* Stop binding to the global mouseup and mousemove events.
* @private
*/
Blockly.terminateDrag_ = function() {
Blockly.BlockSvg.terminateDrag();
Blockly.Flyout.terminateDrag_();
};
/**
* Copy a block onto the local clipboard.
* @param {!Blockly.Block} block Block to be copied.
@@ -265,9 +236,8 @@ Blockly.terminateDrag_ = function() {
*/
Blockly.copy_ = function(block) {
var xmlBlock = Blockly.Xml.blockToDom(block);
if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
Blockly.Xml.deleteNext(xmlBlock);
}
// Copy only the selected block and internal blocks.
Blockly.Xml.deleteNext(xmlBlock);
// Encode start position in XML.
var xy = block.getRelativeToSurfaceXY();
xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
-5
Ver Arquivo
@@ -152,7 +152,6 @@ Blockly.Bubble.unbindDragEvents_ = function() {
*/
Blockly.Bubble.bubbleMouseUp_ = function(/*e*/) {
Blockly.Touch.clearTouchIdentifier();
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
Blockly.Bubble.unbindDragEvents_();
};
@@ -280,8 +279,6 @@ Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
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_));
@@ -323,8 +320,6 @@ Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
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_));
+2 -2
Ver Arquivo
@@ -119,7 +119,7 @@ Blockly.Comment.prototype.createEditor_ = function() {
});
Blockly.bindEventWithChecks_(textarea, 'change', this, function(e) {
if (this.text_ != textarea.value) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.block_, 'comment', null, this.text_, textarea.value));
this.text_ = textarea.value;
}
@@ -257,7 +257,7 @@ Blockly.Comment.prototype.getText = function() {
*/
Blockly.Comment.prototype.setText = function(text) {
if (this.text_ != text) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.block_, 'comment', null, this.text_, text));
this.text_ = text;
}
+2 -8
Ver Arquivo
@@ -219,7 +219,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(childBlock);
event = new Blockly.Events.BlockMove(childBlock);
}
// Establish the connections.
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
@@ -241,12 +241,6 @@ Blockly.Connection.prototype.dispose = function() {
if (this.inDB_) {
this.db_.removeConnection_(this);
}
if (Blockly.highlightedConnection_ == this) {
Blockly.highlightedConnection_ = null;
}
if (Blockly.localConnection_ == this) {
Blockly.localConnection_ = null;
}
this.db_ = null;
this.dbOpposite_ = null;
};
@@ -508,7 +502,7 @@ Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
childBlock) {
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(childBlock);
event = new Blockly.Events.BlockMove(childBlock);
}
var otherConnection = this.targetConnection;
otherConnection.targetConnection = null;
+29
Ver Arquivo
@@ -32,6 +32,13 @@ goog.provide('Blockly.constants');
*/
Blockly.DRAG_RADIUS = 5;
/**
* Number of pixels the mouse must move before a drag/scroll starts from the
* flyout. 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_DRAG_RADIUS = 10;
/**
* Maximum misalignment between connections for them to snap together.
*/
@@ -207,6 +214,12 @@ Blockly.TOOLBOX_AT_LEFT = 2;
*/
Blockly.TOOLBOX_AT_RIGHT = 3;
/**
* ENUM representing that an event is not in any delete areas.
* Null for backwards compatibility reasons.
* @const
*/
Blockly.DELETE_AREA_NONE = null;
/**
* ENUM representing that an event is in the delete area of the trash can.
@@ -236,3 +249,19 @@ Blockly.VARIABLE_CATEGORY_NAME = 'VARIABLE';
* @const {string}
*/
Blockly.PROCEDURE_CATEGORY_NAME = 'PROCEDURE';
/**
* String for use in the dropdown created in field_variable.
* This string indicates that this option in the dropdown is 'Rename
* variable...' and if selected, should trigger the prompt to rename a variable.
* @const {string}
*/
Blockly.RENAME_VARIABLE_ID = 'RENAME_VARIABLE_ID';
/**
* String for use in the dropdown created in field_variable.
* This string indicates that this option in the dropdown is 'Delete the "%1"
* variable' and if selected, should trigger the prompt to delete a variable.
* @const {string}
*/
Blockly.DELETE_VARIABLE_ID = 'DELETE_VARIABLE_ID';
+46 -11
Ver Arquivo
@@ -43,6 +43,13 @@ goog.require('goog.ui.MenuItem');
*/
Blockly.ContextMenu.currentBlock = null;
/**
* Opaque data that can be passed to unbindEvent_.
* @type {Array.<!Array>}
* @private
*/
Blockly.ContextMenu.eventWrapper_ = null;
/**
* Construct the menu based on the list of options and show the menu.
* @param {!Event} e Mouse event.
@@ -55,12 +62,33 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
Blockly.ContextMenu.hide();
return;
}
var menu = Blockly.ContextMenu.populate_(options, rtl);
goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
Blockly.ContextMenu.hide);
Blockly.ContextMenu.position_(menu, e, rtl);
// 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() {menu.getElement().focus();}, 1);
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
};
/**
* Create the context menu object and populate it with the given options.
* @param {!Array.<!Object>} options Array of menu options.
* @param {boolean} rtl True if RTL, false if LTR.
* @return {!goog.ui.Menu} The menu that will be shown on right click.
* @private
*/
Blockly.ContextMenu.populate_ = function(options, rtl) {
/* Here's what one option object looks like:
{text: 'Make It So',
enabled: true,
callback: Blockly.MakeItSo}
*/
var menu = new goog.ui.Menu();
menu.setAllowAutoFocus(true);
menu.setRightToLeft(rtl);
for (var i = 0, option; option = options[i]; i++) {
var menuItem = new goog.ui.MenuItem(option.text);
@@ -70,15 +98,25 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
if (option.enabled) {
goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION,
option.callback);
menuItem.handleContextMenu = function(e) {
menuItem.handleContextMenu = function(/* e */) {
// Right-clicking on menu option should count as a click.
goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION);
};
}
}
goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
Blockly.ContextMenu.hide);
// Record windowSize and scrollOffset before adding menu.
return menu;
};
/**
* Add the menu to the page and position it correctly.
* @param {!goog.ui.Menu} menu The menu to add and position.
* @param {!Event} e Mouse event for the right click that is making the context
* menu appear.
* @param {boolean} rtl True if RTL, false if LTR.
* @private
*/
Blockly.ContextMenu.position_ = function(menu, e, rtl) {
// Record windowSize and scrollOffset before adding menu.
var windowSize = goog.dom.getViewportSize();
var scrollOffset = goog.style.getViewportPageOffset(document);
var div = Blockly.WidgetDiv.DIV;
@@ -109,12 +147,6 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
}
}
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.
};
/**
@@ -123,6 +155,9 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
Blockly.ContextMenu.hide = function() {
Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
Blockly.ContextMenu.currentBlock = null;
if (Blockly.ContextMenu.eventWrapper_) {
Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_);
}
};
/**
@@ -150,7 +185,7 @@ Blockly.ContextMenu.callbackFactory = function(block, xml) {
Blockly.Events.enable();
}
if (Blockly.Events.isEnabled() && !newBlock.isShadow()) {
Blockly.Events.fire(new Blockly.Events.Create(newBlock));
Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock));
}
newBlock.select();
};
+62 -29
Ver Arquivo
@@ -95,41 +95,17 @@ Blockly.Css.inject = function(hasCss, pathToMedia) {
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.
* See See https://github.com/google/blockly/issues/981 for context.
* @param {Blockly.Css.Cursor} cursor Enum.
* @deprecated April 2017.
*/
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;
}
console.warn('Deprecated call to Blockly.Css.setCursor.' +
'See https://github.com/google/blockly/issues/981 for context');
};
/**
@@ -154,6 +130,7 @@ Blockly.Css.CONTENT = [
'height: 100%;',
'position: relative;',
'overflow: hidden;', /* So blocks in drag surface disappear at edges */
'touch-action: none',
'}',
'.blocklyNonSelectable {',
@@ -166,10 +143,15 @@ Blockly.Css.CONTENT = [
'.blocklyWsDragSurface {',
'display: none;',
'position: absolute;',
'overflow: visible;',
'top: 0;',
'left: 0;',
'}',
/* Added as a separate rule with multiple classes to make it more specific
than a bootstrap rule that selects svg:root. See issue #1275 for context.
*/
'.blocklyWsDragSurface.blocklyOverflowVisible {',
'overflow: visible;',
'}',
'.blocklyBlockDragSurface {',
'display: none;',
@@ -232,6 +214,49 @@ Blockly.Css.CONTENT = [
'display: none;',
'}',
'.blocklyDraggable {',
/* backup for browsers (e.g. IE11) that don't support grab */
'cursor: url("<<<PATH>>>/handopen.cur"), auto;',
'cursor: grab;',
'cursor: -webkit-grab;',
'cursor: -moz-grab;',
'}',
'.blocklyDragging {',
/* backup for browsers (e.g. IE11) that don't support grabbing */
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
'cursor: grabbing;',
'cursor: -webkit-grabbing;',
'cursor: -moz-grabbing;',
'}',
/* Changes cursor on mouse down. Not effective in Firefox because of
https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */
'.blocklyDraggable:active {',
/* backup for browsers (e.g. IE11) that don't support grabbing */
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
'cursor: grabbing;',
'cursor: -webkit-grabbing;',
'cursor: -moz-grabbing;',
'}',
/* Change the cursor on the whole drag surface in case the mouse gets
ahead of block during a drag. This way the cursor is still a closed hand.
*/
'.blocklyBlockDragSurface .blocklyDraggable {',
/* backup for browsers (e.g. IE11) that don't support grabbing */
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
'cursor: grabbing;',
'cursor: -webkit-grabbing;',
'cursor: -moz-grabbing;',
'}',
'.blocklyDragging.blocklyDraggingDelete {',
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
'}',
'.blocklyToolboxDelete {',
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
'}',
'.blocklyDragging>.blocklyPath,',
'.blocklyDragging>.blocklyPathLight {',
'fill-opacity: .8;',
@@ -390,6 +415,10 @@ Blockly.Css.CONTENT = [
'fill-opacity: .8;',
'}',
'.blocklyTransparentBackground {',
'opacity: 0;',
'}',
'.blocklyMainWorkspaceScrollbar {',
'z-index: 20;',
'}',
@@ -582,6 +611,10 @@ Blockly.Css.CONTENT = [
'vertical-align: middle;',
'}',
'.blocklyToolboxDelete .blocklyTreeLabel {',
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
'}',
'.blocklyTreeSelected .blocklyTreeLabel {',
'color: #fff;',
'}',
+240
Ver Arquivo
@@ -0,0 +1,240 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 Class that controls updates to connections during drags.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.DraggedConnectionManager');
goog.require('Blockly.RenderedConnection');
goog.require('goog.math.Coordinate');
/**
* Class that controls updates to connections during drags. It is primarily
* responsible for finding the closest eligible connection and highlighting or
* unhiglighting it as needed during a drag.
* @param {!Blockly.BlockSvg} block The top block in the stack being dragged.
* @constructor
*/
Blockly.DraggedConnectionManager = function(block) {
Blockly.selected = block;
/**
* The top block in the stack being dragged.
* Does not change during a drag.
* @type {!Blockly.Block}
* @private
*/
this.topBlock_ = block;
/**
* The workspace on which these connections are being dragged.
* Does not change during a drag.
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = block.workspace;
/**
* The connections on the dragging blocks that are available to connect to
* other blocks. This includes all open connections on the top block, as well
* as the last connection on the block stack.
* Does not change during a drag.
* @type {!Array.<!Blockly.RenderedConnection>}
* @private
*/
this.availableConnections_ = this.initAvailableConnections_();
/**
* The connection that this block would connect to if released immediately.
* Updated on every mouse move.
* @type {Blockly.RenderedConnection}
* @private
*/
this.closestConnection_ = null;
/**
* The connection that would connect to this.closestConnection_ if this block
* were released immediately.
* Updated on every mouse move.
* @type {Blockly.RenderedConnection}
* @private
*/
this.localConnection_ = null;
/**
* The distance between this.closestConnection_ and this.localConnection_,
* in workspace units.
* Updated on every mouse move.
* @type {number}
* @private
*/
this.radiusConnection_ = 0;
/**
* Whether the block would be deleted if it were dropped immediately.
* Updated on every mouse move.
* @type {boolean}
* @private
*/
this.wouldDeleteBlock_ = false;
};
/**
* Sever all links from this object.
* @package
*/
Blockly.DraggedConnectionManager.prototype.dispose = function() {
this.topBlock_ = null;
this.workspace_ = null;
this.availableConnections_.length = 0;
this.closestConnection_ = null;
this.localConnection_ = null;
};
/**
* Return whether the block would be deleted if dropped immediately, based on
* information from the most recent move event.
* @return {boolean} true if the block would be deleted if dropped immediately.
* @package
*/
Blockly.DraggedConnectionManager.prototype.wouldDeleteBlock = function() {
return this.wouldDeleteBlock_;
};
/**
* Connect to the closest connection and render the results.
* This should be called at the end of a drag.
* @package
*/
Blockly.DraggedConnectionManager.prototype.applyConnections = function() {
if (this.closestConnection_) {
// Connect two blocks together.
this.localConnection_.connect(this.closestConnection_);
if (this.topBlock_.rendered) {
// Trigger a connection animation.
// Determine which connection is inferior (lower in the source stack).
var inferiorConnection = this.localConnection_.isSuperior() ?
this.closestConnection_ : this.localConnection_;
inferiorConnection.getSourceBlock().connectionUiEffect();
// Bring the just-edited stack to the front.
var rootBlock = this.topBlock_.getRootBlock();
rootBlock.bringToFront();
}
this.removeHighlighting_();
}
};
/**
* Update highlighted connections based on the most recent move location.
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
* in workspace units.
* @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH},
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
* @package
*/
Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea) {
var oldClosestConnection = this.closestConnection_;
var closestConnectionChanged = this.updateClosest_(dxy);
if (closestConnectionChanged && oldClosestConnection) {
oldClosestConnection.unhighlight();
}
// Prefer connecting over dropping into the trash can, but prefer dragging to
// the toolbox over connecting to other blocks.
var wouldConnect = !!this.closestConnection_ &&
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
var wouldDelete = !!deleteArea && !this.topBlock_.getParent() &&
this.topBlock_.isDeletable();
this.wouldDeleteBlock_ = wouldDelete && !wouldConnect;
if (!this.wouldDeleteBlock_ && closestConnectionChanged &&
this.closestConnection_) {
this.addHighlighting_();
}
};
/**
* Remove highlighting from the currently highlighted connection, if it exists.
* @private
*/
Blockly.DraggedConnectionManager.prototype.removeHighlighting_ = function() {
if (this.closestConnection_) {
this.closestConnection_.unhighlight();
}
};
/**
* Add highlighting to the closest connection, if it exists.
* @private
*/
Blockly.DraggedConnectionManager.prototype.addHighlighting_ = function() {
if (this.closestConnection_) {
this.closestConnection_.highlight();
}
};
/**
* Populate the list of available connections on this block stack. This should
* only be called once, at the beginning of a drag.
* @return {!Array.<!Blockly.RenderedConnection>} a list of available
* connections.
* @private
*/
Blockly.DraggedConnectionManager.prototype.initAvailableConnections_ = function() {
var available = this.topBlock_.getConnections_(false);
// Also check the last connection on this stack
var lastOnStack = this.topBlock_.lastConnectionInStack_();
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
available.push(lastOnStack);
}
return available;
};
/**
* Find the new closest connection, and update internal state in response.
* @param {!goog.math.Coordinate} dxy Position relative to the drag start,
* in workspace units.
* @return {boolean} Whether the closest connection has changed.
* @private
*/
Blockly.DraggedConnectionManager.prototype.updateClosest_ = function(dxy) {
var oldClosestConnection = this.closestConnection_;
this.closestConnection_ = null;
this.localConnection_ = null;
this.radiusConnection_ = Blockly.SNAP_RADIUS;
for (var i = 0; i < this.availableConnections_.length; i++) {
var myConnection = this.availableConnections_[i];
var neighbour = myConnection.closest(this.radiusConnection_, dxy);
if (neighbour.connection) {
this.closestConnection_ = neighbour.connection;
this.localConnection_ = myConnection;
this.radiusConnection_ = neighbour.radius;
}
}
return oldClosestConnection != this.closestConnection_;
};
+325 -50
Ver Arquivo
@@ -55,29 +55,71 @@ Blockly.Events.recordUndo = true;
Blockly.Events.disabled_ = 0;
/**
* Name of event that creates a block.
* Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
* @const
*/
Blockly.Events.CREATE = 'create';
/**
* Name of event that deletes a block.
* Name of event that creates a block.
* @const
*/
Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE;
/**
* Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
* @const
*/
Blockly.Events.DELETE = 'delete';
/**
* Name of event that changes a block.
* Name of event that deletes a block.
* @const
*/
Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE;
/**
* Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
* @const
*/
Blockly.Events.CHANGE = 'change';
/**
* Name of event that moves a block.
* Name of event that changes a block.
* @const
*/
Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE;
/**
* Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
* @const
*/
Blockly.Events.MOVE = 'move';
/**
* Name of event that moves a block.
* @const
*/
Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE;
/**
* Name of event that creates a variable.
* @const
*/
Blockly.Events.VAR_CREATE = 'var_create';
/**
* Name of event that deletes a variable.
* @const
*/
Blockly.Events.VAR_DELETE = 'var_delete';
/**
* Name of event that renames a variable.
* @const
*/
Blockly.Events.VAR_RENAME = 'var_rename';
/**
* Name of event that records a UI change.
* @const
@@ -132,45 +174,37 @@ Blockly.Events.filter = function(queueIn, 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--;
}
var mergedQueue = [];
var hash = Object.create(null);
// Merge duplicates.
for (var i = 0, event; event = queue[i]; i++) {
if (!event.isNull()) {
var key = [event.type, event.blockId, event.workspaceId].join(' ');
var lastEvent = hash[key];
if (!lastEvent) {
hash[key] = event;
mergedQueue.push(event);
} else if (event.type == Blockly.Events.MOVE) {
// Merge move events.
lastEvent.newParentId = event.newParentId;
lastEvent.newInputName = event.newInputName;
lastEvent.newCoordinate = event.newCoordinate;
} else if (event.type == Blockly.Events.CHANGE &&
event.element == lastEvent.element &&
event.name == lastEvent.name) {
// Merge change events.
lastEvent.newValue = event.newValue;
} else if (event.type == Blockly.Events.UI &&
event.element == 'click' &&
(lastEvent.element == 'commentOpen' ||
lastEvent.element == 'mutatorOpen' ||
lastEvent.element == 'warningOpen')) {
// Merge click events.
lastEvent.newValue = event.newValue;
}
}
}
// Remove null events.
for (var i = queue.length - 1; i >= 0; i--) {
if (queue[i].isNull()) {
queue.splice(i, 1);
}
}
queue = mergedQueue;
if (!forward) {
// Restore undo order.
queue.reverse();
@@ -276,6 +310,15 @@ Blockly.Events.fromJson = function(json, workspace) {
case Blockly.Events.MOVE:
event = new Blockly.Events.Move(null);
break;
case Blockly.Events.VAR_CREATE:
event = new Blockly.Events.VarCreate(null);
break;
case Blockly.Events.VAR_DELETE:
event = new Blockly.Events.VarDelete(null);
break;
case Blockly.Events.VAR_RENAME:
event = new Blockly.Events.VarRename(null);
break;
case Blockly.Events.UI:
event = new Blockly.Events.Ui(null);
break;
@@ -289,13 +332,17 @@ Blockly.Events.fromJson = function(json, workspace) {
/**
* Abstract class for an event.
* @param {Blockly.Block} block The block.
* @param {Blockly.Block|Blockly.VariableModel} elem The block or variable.
* @constructor
*/
Blockly.Events.Abstract = function(block) {
if (block) {
this.blockId = block.id;
this.workspaceId = block.workspace.id;
Blockly.Events.Abstract = function(elem) {
if (elem instanceof Blockly.Block) {
this.blockId = elem.id;
this.workspaceId = elem.workspace.id;
}
else if (elem instanceof Blockly.VariableModel){
this.workspaceId = elem.workspace.id;
this.varId = elem.getId();
}
this.group = Blockly.Events.group_;
this.recordUndo = Blockly.Events.recordUndo;
@@ -312,6 +359,9 @@ Blockly.Events.Abstract.prototype.toJson = function() {
if (this.blockId) {
json['blockId'] = this.blockId;
}
if (this.varId) {
json['varId'] = this.varId;
}
if (this.group) {
json['group'] = this.group;
}
@@ -324,6 +374,7 @@ Blockly.Events.Abstract.prototype.toJson = function() {
*/
Blockly.Events.Abstract.prototype.fromJson = function(json) {
this.blockId = json['blockId'];
this.varId = json['varId'];
this.group = json['group'];
};
@@ -343,6 +394,21 @@ Blockly.Events.Abstract.prototype.run = function(forward) {
// Defined by subclasses.
};
/**
* Get workspace the event belongs to.
* @return {Blockly.Workspace} The workspace the event belongs to.
* @throws {Error} if workspace is null.
* @private
*/
Blockly.Events.Abstract.prototype.getEventWorkspace_ = function() {
var workspace = Blockly.Workspace.getById(this.workspaceId);
if (!workspace) {
throw Error('Workspace is null. Event must have been generated from real' +
' Blockly events.');
}
return workspace;
};
/**
* Class for a block creation event.
* @param {Blockly.Block} block The created block. Null for a blank event.
@@ -364,6 +430,14 @@ Blockly.Events.Create = function(block) {
};
goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract);
/**
* 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.BlockCreate = Blockly.Events.Create;
/**
* Type of this event.
* @type {string}
@@ -396,7 +470,7 @@ Blockly.Events.Create.prototype.fromJson = function(json) {
* @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);
var workspace = this.getEventWorkspace_();
if (forward) {
var xml = goog.dom.createDom('xml');
xml.appendChild(this.xml);
@@ -438,6 +512,14 @@ Blockly.Events.Delete = function(block) {
};
goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract);
/**
* 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.BlockDelete = Blockly.Events.Delete;
/**
* Type of this event.
* @type {string}
@@ -468,7 +550,7 @@ Blockly.Events.Delete.prototype.fromJson = function(json) {
* @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);
var workspace = this.getEventWorkspace_();
if (forward) {
for (var i = 0, id; id = this.ids[i]; i++) {
var block = workspace.getBlockById(id);
@@ -508,6 +590,18 @@ Blockly.Events.Change = function(block, element, name, oldValue, newValue) {
};
goog.inherits(Blockly.Events.Change, Blockly.Events.Abstract);
/**
* 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.BlockChange = Blockly.Events.Change;
/**
* Type of this event.
* @type {string}
@@ -552,7 +646,7 @@ Blockly.Events.Change.prototype.isNull = function() {
* @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 workspace = this.getEventWorkspace_();
var block = workspace.getBlockById(this.blockId);
if (!block) {
console.warn("Can't change non-existant block: " + this.blockId);
@@ -624,6 +718,15 @@ Blockly.Events.Move = function(block) {
};
goog.inherits(Blockly.Events.Move, Blockly.Events.Abstract);
/**
* 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.BlockMove = Blockly.Events.Move;
/**
* Type of this event.
* @type {string}
@@ -712,7 +815,7 @@ Blockly.Events.Move.prototype.isNull = function() {
* @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 workspace = this.getEventWorkspace_();
var block = workspace.getBlockById(this.blockId);
if (!block) {
console.warn("Can't move non-existant block: " + this.blockId);
@@ -801,6 +904,178 @@ Blockly.Events.Ui.prototype.fromJson = function(json) {
this.newValue = json['newValue'];
};
/**
* Class for a variable creation event.
* @param {Blockly.VariableModel} variable The created variable.
* Null for a blank event.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.VarCreate = function(variable) {
if (!variable) {
return; // Blank event to be populated by fromJson.
}
Blockly.Events.VarCreate.superClass_.constructor.call(this, variable);
this.varType = variable.type;
this.varName = variable.name;
};
goog.inherits(Blockly.Events.VarCreate, Blockly.Events.Abstract);
/**
* Type of this event.
* @type {string}
*/
Blockly.Events.VarCreate.prototype.type = Blockly.Events.VAR_CREATE;
/**
* Encode the event as JSON.
* @return {!Object} JSON representation.
*/
Blockly.Events.VarCreate.prototype.toJson = function() {
var json = Blockly.Events.VarCreate.superClass_.toJson.call(this);
json['varType'] = this.varType;
json['varName'] = this.varName;
return json;
};
/**
* Decode the JSON event.
* @param {!Object} json JSON representation.
*/
Blockly.Events.VarCreate.prototype.fromJson = function(json) {
Blockly.Events.VarCreate.superClass_.fromJson.call(this, json);
this.varType = json['varType'];
this.varName = json['varName'];
};
/**
* Run a variable creation event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.VarCreate.prototype.run = function(forward) {
var workspace = this.getEventWorkspace_();
if (forward) {
workspace.createVariable(this.varName, this.varType, this.varId);
} else {
workspace.deleteVariableById(this.varId);
}
};
/**
* Class for a variable deletion event.
* @param {Blockly.VariableModel} variable The deleted variable.
* Null for a blank event.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.VarDelete = function(variable) {
if (!variable) {
return; // Blank event to be populated by fromJson.
}
Blockly.Events.VarDelete.superClass_.constructor.call(this, variable);
this.varType = variable.type;
this.varName = variable.name;
};
goog.inherits(Blockly.Events.VarDelete, Blockly.Events.Abstract);
/**
* Type of this event.
* @type {string}
*/
Blockly.Events.VarDelete.prototype.type = Blockly.Events.VAR_DELETE;
/**
* Encode the event as JSON.
* @return {!Object} JSON representation.
*/
Blockly.Events.VarDelete.prototype.toJson = function() {
var json = Blockly.Events.VarDelete.superClass_.toJson.call(this);
json['varType'] = this.varType;
json['varName'] = this.varName;
return json;
};
/**
* Decode the JSON event.
* @param {!Object} json JSON representation.
*/
Blockly.Events.VarDelete.prototype.fromJson = function(json) {
Blockly.Events.VarDelete.superClass_.fromJson.call(this, json);
this.varType = json['varType'];
this.varName = json['varName'];
};
/**
* Run a variable deletion event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.VarDelete.prototype.run = function(forward) {
var workspace = this.getEventWorkspace_();
if (forward) {
workspace.deleteVariableById(this.varId);
} else {
workspace.createVariable(this.varName, this.varType, this.varId);
}
};
/**
* Class for a variable rename event.
* @param {Blockly.VariableModel} variable The renamed variable.
* Null for a blank event.
* @param {string} newName The new name the variable will be changed to.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.VarRename = function(variable, newName) {
if (!variable) {
return; // Blank event to be populated by fromJson.
}
Blockly.Events.VarRename.superClass_.constructor.call(this, variable);
this.oldName = variable.name;
this.newName = newName;
};
goog.inherits(Blockly.Events.VarRename, Blockly.Events.Abstract);
/**
* Type of this event.
* @type {string}
*/
Blockly.Events.VarRename.prototype.type = Blockly.Events.VAR_RENAME;
/**
* Encode the event as JSON.
* @return {!Object} JSON representation.
*/
Blockly.Events.VarRename.prototype.toJson = function() {
var json = Blockly.Events.VarRename.superClass_.toJson.call(this);
json['oldName'] = this.oldName;
json['newName'] = this.newName;
return json;
};
/**
* Decode the JSON event.
* @param {!Object} json JSON representation.
*/
Blockly.Events.VarRename.prototype.fromJson = function(json) {
Blockly.Events.VarRename.superClass_.fromJson.call(this, json);
this.oldName = json['oldName'];
this.newName = json['newName'];
};
/**
* Run a variable rename event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.VarRename.prototype.run = function(forward) {
var workspace = this.getEventWorkspace_();
if (forward) {
workspace.renameVariableById(this.varId, this.newName);
} else {
workspace.renameVariableById(this.varId, this.oldName);
}
};
/**
* 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.
@@ -821,7 +1096,7 @@ Blockly.Events.disableOrphans = function(event) {
child.setDisabled(false);
}
} else if ((block.outputConnection || block.previousConnection) &&
Blockly.dragMode_ == Blockly.DRAG_NONE) {
!workspace.isDragging()) {
do {
block.setDisabled(true);
block = block.getNextBlock();
+38 -39
Ver Arquivo
@@ -40,22 +40,13 @@ goog.provide('Blockly.Extensions');
*/
Blockly.Extensions.ALL_ = {};
/**
* The set of properties on a block that may only be set by a mutator.
* @type {!Array.<string>}
* @private
* @constant
*/
Blockly.Extensions.MUTATOR_PROPERTIES_ =
['domToMutation', 'mutationToDom', 'compose', 'decompose'];
/**
* Registers a new extension function. Extensions are functions that help
* initialize blocks, usually adding dynamic behavior such as onchange
* handlers and mutators. These are applied using Block.applyExtension(), or
* the JSON "extensions" array attribute.
* @param {string} name The name of this extension.
* @param {function} initFn The function to initialize an extended block.
* @param {Function} initFn The function to initialize an extended block.
* @throws {Error} if the extension name is empty, the extension is already
* registered, or extensionFn is not a function.
*/
@@ -92,7 +83,7 @@ Blockly.Extensions.registerMixin = function(name, mixinObj) {
* decompose are defined on the mixin.
* @param {string} name The name of this mutator extension.
* @param {!Object} mixinObj The values to mix in.
* @param {function()=} opt_helperFn An optional function to apply after mixing
* @param {(function())=} opt_helperFn An optional function to apply after mixing
* in the object.
* @param {Array.<string>=} opt_blockList A list of blocks to appear in the
* flyout of the mutator dialog.
@@ -103,8 +94,10 @@ Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn,
var errorPrefix = 'Error when registering mutator "' + name + '": ';
// Sanity check the mixin object before registering it.
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj, 'domToMutation');
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj, 'mutationToDom');
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj.domToMutation,
'domToMutation');
Blockly.Extensions.checkHasFunction_(errorPrefix, mixinObj.mutationToDom,
'mutationToDom');
var hasMutatorDialog = Blockly.Extensions.checkMutatorDialog_(mixinObj,
errorPrefix);
@@ -152,7 +145,7 @@ Blockly.Extensions.apply = function(name, block, isMutator) {
if (isMutator) {
var errorPrefix = 'Error after applying mutator "' + name + '": ';
Blockly.Extensions.checkBlockHasMutatorProperties_(name, block, errorPrefix);
Blockly.Extensions.checkBlockHasMutatorProperties_(errorPrefix, block);
} else {
if (!Blockly.Extensions.mutatorPropertiesMatch_(mutatorProperties, block)) {
throw new Error('Error when applying extension "' + name +
@@ -162,20 +155,19 @@ Blockly.Extensions.apply = function(name, block, isMutator) {
};
/**
* Check that the given object has a property with the given name, and that the
* property is a function.
* Check that the given value is a function.
* @param {string} errorPrefix The string to prepend to any error message.
* @param {!Object} object The object to check.
* @param {*} func Function to check.
* @param {string} propertyName Which property to check.
* @throws {Error} if the property does not exist or is not a function.
* @private
*/
Blockly.Extensions.checkHasFunction_ = function(errorPrefix, object,
Blockly.Extensions.checkHasFunction_ = function(errorPrefix, func,
propertyName) {
if (!object.hasOwnProperty(propertyName)) {
if (!func) {
throw new Error(errorPrefix +
'missing required property "' + propertyName + '"');
} else if (typeof object[propertyName] !== "function") {
} else if (typeof func != 'function') {
throw new Error(errorPrefix +
'" required property "' + propertyName + '" must be a function');
}
@@ -192,13 +184,11 @@ Blockly.Extensions.checkHasFunction_ = function(errorPrefix, object,
* @private
*/
Blockly.Extensions.checkNoMutatorProperties_ = function(mutationName, block) {
for (var i = 0; i < Blockly.Extensions.MUTATOR_PROPERTIES_.length; i++) {
var propertyName = Blockly.Extensions.MUTATOR_PROPERTIES_[i];
if (block.hasOwnProperty(propertyName)) {
throw new Error('Error: tried to apply mutation "' + mutationName +
'" to a block that already has a "' + propertyName +
'" function. Block id: ' + block.id);
}
var properties = Blockly.Extensions.getMutatorProperties_(block);
if (properties.length) {
throw new Error('Error: tried to apply mutation "' + mutationName +
'" to a block that already has mutator functions.' +
' Block id: ' + block.id);
}
};
@@ -215,13 +205,13 @@ Blockly.Extensions.checkNoMutatorProperties_ = function(mutationName, block) {
* @private
*/
Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) {
var hasCompose = object.hasOwnProperty('compose');
var hasDecompose = object.hasOwnProperty('decompose');
var hasCompose = object.compose !== undefined;
var hasDecompose = object.decompose !== undefined;
if (hasCompose && hasDecompose) {
if (typeof object['compose'] !== "function") {
if (typeof object.compose !== 'function') {
throw new Error(errorPrefix + 'compose must be a function.');
} else if (typeof object['decompose'] !== "function") {
} else if (typeof object.decompose !== 'function') {
throw new Error(errorPrefix + 'decompose must be a function.');
}
return true;
@@ -242,10 +232,10 @@ Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) {
*/
Blockly.Extensions.checkBlockHasMutatorProperties_ = function(errorPrefix,
block) {
if (!block.hasOwnProperty('domToMutation')) {
if (typeof block.domToMutation !== 'function') {
throw new Error(errorPrefix + 'Applying a mutator didn\'t add "domToMutation"');
}
if (!block.hasOwnProperty('mutationToDom')) {
if (typeof block.mutationToDom !== 'function') {
throw new Error(errorPrefix + 'Applying a mutator didn\'t add "mutationToDom"');
}
@@ -257,14 +247,25 @@ Blockly.Extensions.checkBlockHasMutatorProperties_ = function(errorPrefix,
/**
* Get a list of values of mutator properties on the given block.
* @param {!Blockly.Block} block The block to inspect.
* @return {!Array.<Object>} a list with all of the properties, which should be
* functions or undefined, but are not guaranteed to be.
* @return {!Array.<Object>} a list with all of the defined properties, which
* should be functions, but may be anything other than undefined.
* @private
*/
Blockly.Extensions.getMutatorProperties_ = function(block) {
var result = [];
for (var i = 0; i < Blockly.Extensions.MUTATOR_PROPERTIES_.length; i++) {
result.push(block[Blockly.Extensions.MUTATOR_PROPERTIES_[i]]);
// List each function explicitly by reference to allow for renaming
// during compilation.
if (block.domToMutation !== undefined) {
result.push(block.domToMutation);
}
if (block.mutationToDom !== undefined) {
result.push(block.mutationToDom);
}
if (block.compose !== undefined) {
result.push(block.compose);
}
if (block.decompose !== undefined) {
result.push(block.decompose);
}
return result;
};
@@ -442,5 +443,3 @@ Blockly.Extensions.extensionParentTooltip_ = function() {
};
Blockly.Extensions.register('parent_tooltip_when_inline',
Blockly.Extensions.extensionParentTooltip_);
+25 -27
Ver Arquivo
@@ -28,6 +28,8 @@
goog.provide('Blockly.Field');
goog.require('Blockly.Gesture');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.math.Size');
@@ -152,20 +154,27 @@ Blockly.Field.prototype.init = function() {
this.updateEditable();
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
this.mouseUpWrapper_ =
Blockly.bindEventWithChecks_(this.fieldGroup_, 'mouseup', this,
this.onMouseUp_);
this.mouseDownWrapper_ =
Blockly.bindEventWithChecks_(this.fieldGroup_, 'mousedown', this,
this.onMouseDown_);
// Force a render.
this.render_();
};
/**
* Initializes the model of the field after it has been installed on a block.
* No-op by default.
*/
Blockly.Field.prototype.initModel = function() {
};
/**
* 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;
if (this.mouseDownWrapper_) {
Blockly.unbindEvent_(this.mouseDownWrapper_);
this.mouseDownWrapper_ = null;
}
this.sourceBlock_ = null;
goog.dom.removeNode(this.fieldGroup_);
@@ -486,39 +495,28 @@ Blockly.Field.prototype.setValue = function(newValue) {
return;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, oldValue, newValue));
}
this.setText(newValue);
};
/**
* Handle a mouse up event on an editable field.
* @param {!Event} e Mouse up event.
* Handle a mouse down event on a field.
* @param {!Event} e Mouse down 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.
Blockly.Field.prototype.onMouseDown_ = function(e) {
if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
return;
} else if (Blockly.utils.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_();
// The field is handling the touch, but we also want the blockSvg onMouseUp
// handler to fire, so we will leave the touch identifier as it is.
// The next onMouseUp is responsible for nulling it out.
}
var gesture = this.sourceBlock_.workspace.getGesture(e);
if (gesture) {
gesture.setStartField(this);
}
};
/**
* Change the tooltip text for this field.
* @param {string|!Element} newTip Text for tooltip or a parent element to
+24 -8
Ver Arquivo
@@ -93,13 +93,35 @@ Blockly.FieldAngle.OFFSET = 0;
*/
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;
/**
* Adds degree symbol and recalculates width.
* Saves the computed width in a property.
* @private
*/
Blockly.FieldAngle.prototype.render_ = function() {
if (!this.visible_) {
this.size_.width = 0;
return;
}
// Update textElement.
this.textElement_.textContent = this.getDisplayText_();
// Insert degree symbol.
if (this.sourceBlock_.RTL) {
this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild);
} else {
this.textElement_.appendChild(this.symbol_);
}
this.updateWidth();
};
/**
* Clean up this FieldAngle, as well as the inherited FieldTextInput.
* @return {!Function} Closure to call on destruction of the WidgetDiv.
@@ -233,12 +255,6 @@ Blockly.FieldAngle.prototype.setText = function(text) {
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;
};
@@ -301,4 +317,4 @@ Blockly.FieldAngle.prototype.classValidator = function(text) {
n -= 360;
}
return String(n);
};
};
+1 -1
Ver Arquivo
@@ -93,7 +93,7 @@ Blockly.FieldCheckbox.prototype.setValue = function(newBool) {
(newBool.toUpperCase() == 'TRUE') : !!newBool;
if (this.state_ !== newState) {
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.state_, newState));
}
this.state_ = newState;
+1 -1
Ver Arquivo
@@ -101,7 +101,7 @@ Blockly.FieldColour.prototype.getValue = function() {
Blockly.FieldColour.prototype.setValue = function(colour) {
if (this.sourceBlock_ && Blockly.Events.isEnabled() &&
this.colour_ != colour) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.colour_, colour));
}
this.colour_ = colour;
+21 -1
Ver Arquivo
@@ -328,7 +328,7 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) {
return; // No change if null.
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.value_, newValue));
}
this.value_ = newValue;
@@ -413,6 +413,26 @@ Blockly.FieldDropdown.prototype.render_ = function() {
this.size_.width + Blockly.BlockSvg.SEP_SPACE_X);
};
/**
* Updates the width of the field. Overrides field.prototype.updateWidth to
* deal with image selections on IE and Edge. If the selected item is not an
* image, or if the browser is not IE / Edge, this simply calls the parent
* implementation.
*/
Blockly.FieldDropdown.prototype.updateWidth = function() {
if (this.imageJson_ && (goog.userAgent.IE || goog.userAgent.EDGE)) {
// Recalculate the full width.
var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_);
var width = Number(this.imageJson_.width) + arrowWidth + Blockly.BlockSvg.SEP_SPACE_X;
if (this.borderRect_) {
this.borderRect_.setAttribute('width', width);
}
this.size_.width = width;
} else {
Blockly.Field.prototype.updateWidth.call(this);
}
};
/**
* Close the dropdown menu if this input is being deleted.
*/
+17 -1
Ver Arquivo
@@ -38,10 +38,11 @@ goog.require('goog.userAgent');
* @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.
* @param {Function=} opt_onClick Optional function to be called when image is clicked
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldImage = function(src, width, height, opt_alt) {
Blockly.FieldImage = function(src, width, height, opt_alt, opt_onClick) {
this.sourceBlock_ = null;
// Ensure height and width are numbers. Strings are bad at math.
@@ -51,6 +52,10 @@ Blockly.FieldImage = function(src, width, height, opt_alt) {
this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y);
this.text_ = opt_alt || '';
this.setValue(src);
if (typeof opt_onClick === "function") {
this.clickHandler_ = opt_onClick;
}
};
goog.inherits(Blockly.FieldImage, Blockly.Field);
@@ -153,6 +158,7 @@ Blockly.FieldImage.prototype.setText = function(alt) {
Blockly.FieldImage.prototype.render_ = function() {
// NOP
};
/**
* Images are fixed width, no need to update.
* @private
@@ -160,3 +166,13 @@ Blockly.FieldImage.prototype.render_ = function() {
Blockly.FieldImage.prototype.updateWidth = function() {
// NOP
};
/**
* If field click is called, and click handler defined,
* call the handler.
*/
Blockly.FieldImage.prototype.showEditor = function() {
if (this.clickHandler_){
this.clickHandler_(this);
}
};
+85 -33
Ver Arquivo
@@ -55,6 +55,14 @@ goog.inherits(Blockly.FieldTextInput, Blockly.Field);
*/
Blockly.FieldTextInput.FONTSIZE = 11;
/**
* The HTML input element for the user to type, or null if no FieldTextInput
* editor is currently open.
* @type {HTMLInputElement}
* @private
*/
Blockly.FieldTextInput.htmlInput_ = null;
/**
* Mouse cursor style when over the hotspot that initiates the editor.
*/
@@ -109,7 +117,7 @@ Blockly.FieldTextInput.prototype.setText = function(newText) {
return;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.text_, newText));
}
Blockly.Field.prototype.setText.call(this, newText);
@@ -134,18 +142,35 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
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 fieldText = this;
Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_,
function(newValue) {
if (fieldText.sourceBlock_) {
newValue = fieldText.callValidator(newValue);
}
fieldText.setValue(newValue);
});
return;
this.showPromptEditor_();
} else {
this.showInlineEditor_(quietInput);
}
};
/**
* Create and show a text input editor that is a prompt (usually a popup).
* Mobile browsers have issues with in-line textareas (focus and keyboards).
* @private
*/
Blockly.FieldTextInput.showPromptEditor_ = function() {
var fieldText = this;
Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_,
function(newValue) {
if (fieldText.sourceBlock_) {
newValue = fieldText.callValidator(newValue);
}
fieldText.setValue(newValue);
});
};
/**
* Create and show a text input editor that sits directly over the text input.
* @param {boolean} quietInput True if editor should be created without
* focus.
* @private
*/
Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
var div = Blockly.WidgetDiv.DIV;
// Create the input.
@@ -156,7 +181,7 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
(Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
div.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
/** @type {!HTMLInputElement} */
Blockly.FieldTextInput.htmlInput_ = htmlInput;
div.appendChild(htmlInput);
@@ -169,6 +194,16 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
htmlInput.select();
}
this.bindEvents_(htmlInput);
};
/**
* Bind handlers for user input on this field and size changes on the workspace.
* @param {!HTMLInputElement} htmlInput The htmlInput created in showEditor, to
* which event handlers will be bound.
* @private
*/
Blockly.FieldTextInput.prototype.bindEvents_ = function(htmlInput) {
// Bind to keydown -- trap Enter without IME and Esc to hide.
htmlInput.onKeyDownWrapper_ =
Blockly.bindEventWithChecks_(htmlInput, 'keydown', this,
@@ -185,6 +220,19 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_);
};
/**
* Unbind handlers for user input and workspace size changes.
* @param {!HTMLInputElement} htmlInput The html for this text input.
* @private
*/
Blockly.FieldTextInput.prototype.unbindEvents_ = function(htmlInput) {
Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
this.workspace_.removeChangeListener(
htmlInput.onWorkspaceChangeWrapper_);
};
/**
* Handle key down to the editor.
* @param {!Event} e Keyboard event.
@@ -289,29 +337,12 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
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;
if (thisField.onFinishEditing_) {
thisField.onFinishEditing_(text);
}
}
}
thisField.setText(text);
thisField.sourceBlock_.rendered && thisField.sourceBlock_.render();
Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
thisField.workspace_.removeChangeListener(
htmlInput.onWorkspaceChangeWrapper_);
thisField.maybeSaveEdit_();
thisField.unbindEvents_(htmlInput);
Blockly.FieldTextInput.htmlInput_ = null;
Blockly.Events.setGroup(false);
// Delete style properties.
var style = Blockly.WidgetDiv.DIV.style;
style.width = 'auto';
@@ -320,6 +351,27 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
};
};
Blockly.FieldTextInput.prototype.maybeSaveEdit_ = function() {
var htmlInput = Blockly.FieldTextInput.htmlInput_;
// Save the edit (if it validates).
var text = htmlInput.value;
if (this.sourceBlock_) {
var text1 = this.callValidator(text);
if (text1 === null) {
// Invalid edit.
text = htmlInput.defaultValue;
} else {
// Validation function has changed the text.
text = text1;
if (this.onFinishEditing_) {
this.onFinishEditing_(text);
}
}
}
this.setText(text);
this.sourceBlock_.rendered && this.sourceBlock_.render();
};
/**
* Ensure that only a number may be entered.
* @param {string} text The user's text.
+107 -55
Ver Arquivo
@@ -28,7 +28,9 @@ goog.provide('Blockly.FieldVariable');
goog.require('Blockly.FieldDropdown');
goog.require('Blockly.Msg');
goog.require('Blockly.VariableModel');
goog.require('Blockly.Variables');
goog.require('Blockly.VariableModel');
goog.require('goog.asserts');
goog.require('goog.string');
@@ -39,31 +41,19 @@ goog.require('goog.string');
* 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.
* @param {Array.<string>} opt_variableTypes A list of the types of variables to
* include in the dropdown.
* @extends {Blockly.FieldDropdown}
* @constructor
*/
Blockly.FieldVariable = function(varname, opt_validator) {
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
Blockly.FieldVariable.superClass_.constructor.call(this,
Blockly.FieldVariable.dropdownCreate, opt_validator);
this.setValue(varname || '');
this.variableTypes = opt_variableTypes;
};
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);
/**
* The menu item index for the rename variable option.
* @type {number}
* @private
*/
Blockly.FieldVariable.prototype.renameVarItemIndex_ = -1;
/**
* The menu item index for the delete variable option.
* @type {number}
* @private
*/
Blockly.FieldVariable.prototype.deleteVarItemIndex_ = -1;
/**
* Install this dropdown on a block.
*/
@@ -73,6 +63,12 @@ Blockly.FieldVariable.prototype.init = function() {
return;
}
Blockly.FieldVariable.superClass_.init.call(this);
// TODO (1010): Change from init/initModel to initView/initModel
this.initModel();
};
Blockly.FieldVariable.prototype.initModel = function() {
if (!this.getValue()) {
// Variables without names get uniquely named for this workspace.
var workspace =
@@ -110,15 +106,54 @@ Blockly.FieldVariable.prototype.getValue = function() {
/**
* Set the variable name.
* @param {string} newValue New text.
* @param {string} value 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));
Blockly.FieldVariable.prototype.setValue = function(value) {
var newValue = value;
var newText = value;
if (this.sourceBlock_) {
var variable = this.sourceBlock_.workspace.getVariableById(value);
if (variable) {
newText = variable.name;
}
// TODO(marisaleung): Remove name lookup after converting all Field Variable
// instances to use id instead of name.
else if (variable = this.sourceBlock_.workspace.getVariable(value)) {
newValue = variable.getId();
}
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.value_, newValue));
}
}
this.value_ = newValue;
this.setText(newValue);
this.setText(newText);
};
/**
* Return a list of variable types to include in the dropdown.
* @return {!Array.<string>} Array of variable types.
* @throws {Error} if variableTypes is an empty array.
* @private
*/
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
var variableTypes = this.variableTypes;
if (variableTypes === null) {
// If variableTypes is null, return all variable types.
if (this.sourceBlock_) {
var workspace = this.sourceBlock_.workspace;
return workspace.getVariableTypes();
}
}
variableTypes = variableTypes || [''];
if (variableTypes.length == 0) {
// Throw an error if variableTypes is an empty list.
var name = this.getText();
throw new Error('\'variableTypes\' of field variable ' +
name + ' was an empty list');
}
return variableTypes;
};
/**
@@ -128,30 +163,47 @@ Blockly.FieldVariable.prototype.setValue = function(newValue) {
* @this {Blockly.FieldVariable}
*/
Blockly.FieldVariable.dropdownCreate = function() {
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
var variableModelList = [];
var name = this.getText();
// Don't create a new variable if there is nothing selected.
var createSelectedVariable = name ? true : false;
var workspace = null;
if (this.sourceBlock_) {
workspace = this.sourceBlock_.workspace;
}
if (workspace) {
var variableTypes = this.getVariableTypes_();
var variableModelList = [];
// Get a copy of the list, so that adding rename and new variable options
// doesn't modify the workspace's list.
var variableList = this.sourceBlock_.workspace.variableList.slice(0);
} else {
var variableList = [];
for (var i = 0; i < variableTypes.length; i++) {
var variableType = variableTypes[i];
var variables = workspace.getVariablesOfType(variableType);
variableModelList = variableModelList.concat(variables);
}
for (var i = 0; i < variableModelList.length; i++){
if (createSelectedVariable &&
goog.string.caseInsensitiveEquals(variableModelList[i].name, name)) {
createSelectedVariable = false;
break;
}
}
}
// Ensure that the currently selected variable is an option.
var name = this.getText();
if (name && variableList.indexOf(name) == -1) {
variableList.push(name);
if (createSelectedVariable && workspace) {
var newVar = workspace.createVariable(name);
variableModelList.push(newVar);
}
variableList.sort(goog.string.caseInsensitiveCompare);
this.renameVarItemIndex_ = variableList.length;
variableList.push(Blockly.Msg.RENAME_VARIABLE);
this.deleteVarItemIndex_ = variableList.length;
variableList.push(Blockly.Msg.DELETE_VARIABLE.replace('%1', name));
// Variables are not language-specific, use the name as both the user-facing
// text and the internal representation.
variableModelList.sort(Blockly.VariableModel.compareByName);
var options = [];
for (var i = 0; i < variableList.length; i++) {
options[i] = [variableList[i], variableList[i]];
for (var i = 0; i < variableModelList.length; i++) {
// Set the uuid as the internal representation of the variable.
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
}
options.push([Blockly.Msg.RENAME_VARIABLE, Blockly.RENAME_VARIABLE_ID]);
if (Blockly.Msg.DELETE_VARIABLE) {
options.push([Blockly.Msg.DELETE_VARIABLE.replace('%1', name),
Blockly.DELETE_VARIABLE_ID]);
}
return options;
};
@@ -164,24 +216,24 @@ Blockly.FieldVariable.dropdownCreate = function() {
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
*/
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
var itemText = menuItem.getValue();
if (this.sourceBlock_) {
var id = menuItem.getValue();
// TODO(marisaleung): change setValue() to take in an id as the parameter.
// Then remove itemText.
var itemText;
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
var workspace = this.sourceBlock_.workspace;
if (this.renameVarItemIndex_ >= 0 &&
menu.getChildAt(this.renameVarItemIndex_) === menuItem) {
var variable = workspace.getVariableById(id);
// If the item selected is a variable, set itemText to the variable name.
if (variable) {
itemText = variable.name;
}
else if (id == Blockly.RENAME_VARIABLE_ID) {
// Rename variable.
var oldName = this.getText();
Blockly.hideChaff();
Blockly.Variables.promptName(
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldName), oldName,
function(newName) {
if (newName) {
workspace.renameVariable(oldName, newName);
}
});
var currentName = this.getText();
variable = workspace.getVariable(currentName);
Blockly.Variables.renameVariable(workspace, variable);
return;
} else if (this.deleteVarItemIndex_ >= 0 &&
menu.getChildAt(this.deleteVarItemIndex_) === menuItem) {
} else if (id == Blockly.DELETE_VARIABLE_ID) {
// Delete variable.
workspace.deleteVariable(this.getText());
return;
-1486
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+814
Ver Arquivo
@@ -0,0 +1,814 @@
/**
* @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.Events');
goog.require('Blockly.FlyoutButton');
goog.require('Blockly.Gesture');
goog.require('Blockly.Touch');
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;
/**
* 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.backgroundButtons_ = [];
/**
* List of visible buttons.
* @type {!Array.<!Blockly.FlyoutButton>}
* @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;
/**
* Whether the flyout is visible.
* @type {boolean}
* @private
*/
Blockly.Flyout.prototype.isVisible_ = false;
/**
* Whether the workspace containing this flyout is visible.
* @type {boolean}
* @private
*/
Blockly.Flyout.prototype.containerVisible_ = true;
/**
* Corner radius of the flyout background.
* @type {number}
* @const
*/
Blockly.Flyout.prototype.CORNER_RADIUS = 8;
/**
* Margin around the edges of the blocks in the flyout.
* @type {number}
* @const
*/
Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS;
// TODO: Move GAP_X and GAP_Y to their appropriate files.
/**
* Gap between items in horizontal flyouts. Can be overridden with the "sep"
* element.
* @const {number}
*/
Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3;
/**
* Gap between items in vertical flyouts. Can be overridden with the "sep"
* element.
* @const {number}
*/
Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3;
/**
* 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;
/**
* 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_ = 70;
/**
* Creates the flyout's DOM. Only needs to be called once. The flyout can
* either exist as its own svg element or be a g element nested inside a
* separate svg element.
* @param {string} tagName The type of tag to put the flyout in. This
* should be <svg> or <g>.
* @return {!Element} The flyout's SVG group.
*/
Blockly.Flyout.prototype.createDom = function(tagName) {
/*
<svg | g>
<path class="blocklyFlyoutBackground"/>
<g class="blocklyFlyout"></g>
</ svg | g>
*/
// Setting style to display:none to start. The toolbox and flyout
// hide/show code will set up proper visibility and size later.
this.svgGroup_ = Blockly.utils.createSvgElement(tagName,
{'class': 'blocklyFlyout', 'style': 'display: none'}, null);
this.svgBackground_ = Blockly.utils.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, 'blocklyFlyoutScrollbar');
this.hide();
Array.prototype.push.apply(this.eventWrappers_,
Blockly.bindEventWithChecks_(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.bindEventWithChecks_(this.svgBackground_, 'mousedown', this,
this.onMouseDown_));
// A flyout connected to a workspace doesn't have its own current gesture.
this.workspace_.getGesture =
this.targetWorkspace_.getGesture.bind(this.targetWorkspace_);
// Get variables from the main workspace rather than the target workspace.
this.workspace_.getVariable =
this.targetWorkspace_.getVariable.bind(this.targetWorkspace_);
this.workspace_.getVariableById =
this.targetWorkspace_.getVariableById.bind(this.targetWorkspace_);
this.workspace_.getVariablesOfType =
this.targetWorkspace_.getVariablesOfType.bind(this.targetWorkspace_);
this.workspace_.deleteVariable =
this.targetWorkspace_.deleteVariable.bind(this.targetWorkspace_);
this.workspace_.deleteVariableById =
this.targetWorkspace_.deleteVariableById.bind(this.targetWorkspace_);
this.workspace_.renameVariable =
this.targetWorkspace_.renameVariable.bind(this.targetWorkspace_);
this.workspace_.renameVariableById =
this.targetWorkspace_.renameVariableById.bind(this.targetWorkspace_);
};
/**
* 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_;
};
/**
* Get the workspace inside the flyout.
* @return {!Blockly.WorkspaceSvg} The workspace inside the flyout.
* @package
*/
Blockly.Flyout.prototype.getWorkspace = function() {
return this.workspace_;
};
/**
* Is the flyout visible?
* @return {boolean} True if visible.
*/
Blockly.Flyout.prototype.isVisible = function() {
return this.isVisible_;
};
/**
* Set whether the flyout is visible. A value of true does not necessarily mean
* that the flyout is shown. It could be hidden because its container is hidden.
* @param {boolean} visible True if visible.
*/
Blockly.Flyout.prototype.setVisible = function(visible) {
var visibilityChanged = (visible != this.isVisible());
this.isVisible_ = visible;
if (visibilityChanged) {
this.updateDisplay_();
}
};
/**
* Set whether this flyout's container is visible.
* @param {boolean} visible Whether the container is visible.
*/
Blockly.Flyout.prototype.setContainerVisible = function(visible) {
var visibilityChanged = (visible != this.containerVisible_);
this.containerVisible_ = visible;
if (visibilityChanged) {
this.updateDisplay_();
}
};
/**
* Update the display property of the flyout based whether it thinks it should
* be visible and whether its containing workspace is visible.
* @private
*/
Blockly.Flyout.prototype.updateDisplay_ = function() {
var show = true;
if (!this.containerVisible_) {
show = false;
} else {
show = this.isVisible();
}
this.svgGroup_.style.display = show ? 'block' : 'none';
// Update the scrollbar's visiblity too since it should mimic the
// flyout's visibility.
this.scrollbar_.setContainerVisible(show);
};
/**
* Update the view based on coordinates calculated in position().
* @param {number} width The computed width of the flyout's SVG group
* @param {number} height The computed height of the flyout's SVG group.
* @param {number} x The computed x origin of the flyout's SVG group.
* @param {number} y The computed y origin of the flyout's SVG group.
* @private
*/
Blockly.Flyout.prototype.positionAt_ = function(width, height, x, y) {
this.svgGroup_.setAttribute("width", width);
this.svgGroup_.setAttribute("height", height);
var transform = 'translate(' + x + 'px,' + y + 'px)';
Blockly.utils.setCssTransform(this.svgGroup_, transform);
// Update the scrollbar (if one exists).
if (this.scrollbar_) {
// Set the scrollbars origin to be the top left of the flyout.
this.scrollbar_.setOrigin(x, y);
this.scrollbar_.resize();
}
};
/**
* Hide and empty the flyout.
*/
Blockly.Flyout.prototype.hide = function() {
if (!this.isVisible()) {
return;
}
this.setVisible(false);
// 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.workspace_.setResizesEnabled(false);
this.hide();
this.clearOldBlocks_();
// Handle dynamic categories, represented by a name instead of a list of XML.
// Look up the correct category generation function and call that to get a
// valid XML list.
if (typeof xmlList == 'string') {
var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback(
xmlList);
goog.asserts.assert(goog.isFunction(fnToApply),
'Couldn\'t find a callback function when opening a toolbox category.');
xmlList = fnToApply(this.workspace_.targetWorkspace);
goog.asserts.assert(goog.isArray(xmlList),
'The result of a toolbox category callback must be an array.');
}
this.setVisible(true);
// Create the blocks to be shown in this flyout.
var contents = [];
var gaps = [];
this.permanentlyDisabled_.length = 0;
for (var i = 0, xml; xml = xmlList[i]; i++) {
if (xml.tagName) {
var tagName = xml.tagName.toUpperCase();
var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y;
if (tagName == '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);
}
contents.push({type: 'block', block: curBlock});
var gap = parseInt(xml.getAttribute('gap'), 10);
gaps.push(isNaN(gap) ? default_gap : gap);
} else if (xml.tagName.toUpperCase() == 'SEP') {
// Change the gap between two blocks.
// <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller.
// This overwrites the gap attribute on the previous block.
// Note that a deprecated method is to add a gap to a block.
// <block type="math_arithmetic" gap="8"></block>
var newGap = parseInt(xml.getAttribute('gap'), 10);
// Ignore gaps before the first block.
if (!isNaN(newGap) && gaps.length > 0) {
gaps[gaps.length - 1] = newGap;
} else {
gaps.push(default_gap);
}
} else if (tagName == 'BUTTON' || tagName == 'LABEL') {
// Labels behave the same as buttons, but are styled differently.
var isLabel = tagName == 'LABEL';
var curButton = new Blockly.FlyoutButton(this.workspace_,
this.targetWorkspace_, xml, isLabel);
contents.push({type: 'button', button: curButton});
gaps.push(default_gap);
}
}
}
this.layout_(contents, 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.bindEventWithChecks_(this.svgBackground_,
'mouseover', this, deselectAll));
if (this.horizontalLayout_) {
this.height_ = 0;
} else {
this.width_ = 0;
}
this.workspace_.setResizesEnabled(true);
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_);
};
/**
* 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.backgroundButtons_[j]; j++) {
goog.dom.removeNode(rect);
}
this.backgroundButtons_.length = 0;
for (var i = 0, button; button = this.buttons_[i]; i++) {
button.dispose();
}
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.bindEventWithChecks_(root, 'mousedown', null,
this.blockMouseDown_(block)));
this.listeners_.push(Blockly.bindEventWithChecks_(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) {
var gesture = flyout.targetWorkspace_.getGesture(e);
if (gesture) {
gesture.setStartBlock(block);
gesture.handleFlyoutStart(e, flyout);
}
};
};
/**
* Mouse down on the flyout background. Start a vertical scroll drag.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.Flyout.prototype.onMouseDown_ = function(e) {
var gesture = this.targetWorkspace_.getGesture(e);
if (gesture) {
gesture.handleFlyoutStart(e, this);
}
};
/**
* Create a copy of this block on the workspace.
* @param {!Blockly.BlockSvg} originalBlock The block to copy from the flyout.
* @return {Blockly.BlockSvg} The newly created block, or null if something
* went wrong with deserialization.
* @package
*/
Blockly.Flyout.prototype.createBlock = function(originalBlock) {
var newBlock = null;
Blockly.Events.disable();
this.targetWorkspace_.setResizesEnabled(false);
try {
newBlock = this.placeNewBlock_(originalBlock);
//Force a render on IE and Edge to get around the issue described in
//Blockly.Field.getCachedWidth
if (goog.userAgent.IE || goog.userAgent.EDGE) {
var blocks = newBlock.getDescendants();
for (var i = blocks.length - 1; i >= 0; i--) {
blocks[i].render(false);
}
}
// Close the flyout.
Blockly.hideChaff();
} finally {
Blockly.Events.enable();
}
if (Blockly.Events.isEnabled()) {
Blockly.Events.setGroup(true);
Blockly.Events.fire(new Blockly.Events.Create(newBlock));
}
if (this.autoClose) {
this.hide();
} else {
this.filterForCapacity_();
}
return newBlock;
};
/**
* Initialize the given button: move it to the correct location,
* add listeners, etc.
* @param {!Blockly.FlyoutButton} button The button to initialize and place.
* @param {number} x The x position of the cursor during this layout pass.
* @param {number} y The y position of the cursor during this layout pass.
* @private
*/
Blockly.Flyout.prototype.initFlyoutButton_ = function(button, x, y) {
var buttonSvg = button.createDom();
button.moveTo(x, y);
button.show();
// Clicking on a flyout button or label is a lot like clicking on the
// flyout background.
this.listeners_.push(Blockly.bindEventWithChecks_(buttonSvg, 'mousedown',
this, this.onMouseDown_));
this.buttons_.push(button);
};
/**
* Create and place a rectangle corresponding to the given block.
* @param {!Blockly.Block} block The block to associate the rect to.
* @param {number} x The x position of the cursor during this layout pass.
* @param {number} y The y position of the cursor during this layout pass.
* @param {!{height: number, width: number}} blockHW The height and width of the
* block.
* @param {number} index The index into the background buttons list where this
* rect should be placed.
* @return {!SVGElement} Newly created SVG element for the rectangle behind the
* block.
* @private
*/
Blockly.Flyout.prototype.createRect_ = function(block, x, y, blockHW, index) {
// 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.utils.createSvgElement('rect',
{
'fill-opacity': 0,
'x': x,
'y': y,
'height': blockHW.height,
'width': blockHW.width
}, 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.backgroundButtons_[index] = rect;
return rect;
};
/**
* Move a rectangle to sit exactly behind a block, taking into account tabs,
* hats, and any other protrusions we invent.
* @param {!SVGElement} rect The rectangle to move directly behind the block.
* @param {!Blockly.BlockSvg} block The block the rectangle should be behind.
* @private
*/
Blockly.Flyout.prototype.moveRectToBlock_ = function(rect, block) {
var blockHW = block.getHeightWidth();
rect.setAttribute('width', blockHW.width);
rect.setAttribute('height', blockHW.height);
// 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);
}
// Blocks with output tabs are shifted a bit.
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
var blockXY = block.getRelativeToSurfaceXY();
rect.setAttribute('y', blockXY.y);
rect.setAttribute('x',
this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab);
};
/**
* Filter the blocks on the flyout to disable the ones that are above the
* capacity limit. For instance, if the user may only place two more blocks on
* the workspace, an "a + b" block that has two shadow blocks would be disabled.
* @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);
}
}
};
/**
* Reflow blocks and their buttons.
*/
Blockly.Flyout.prototype.reflow = function() {
if (this.reflowWrapper_) {
this.workspace_.removeChangeListener(this.reflowWrapper_);
}
var blocks = this.workspace_.getTopBlocks(false);
this.reflowInternal_(blocks);
if (this.reflowWrapper_) {
this.workspace_.addChangeListener(this.reflowWrapper_);
}
};
/**
* @return {boolean} True if this flyout may be scrolled with a scrollbar or by
* dragging.
* @package
*/
Blockly.Flyout.prototype.isScrollable = function() {
return this.scrollbar_ ? this.scrollbar_.isVisible() : false;
};
/**
* Copy a block from the flyout to the workspace and position it correctly.
* @param {!Blockly.Block} oldBlock The flyout block to copy.
* @return {!Blockly.Block} The new block in the main workspace.
* @private
*/
Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
var targetWorkspace = this.targetWorkspace_;
var svgRootOld = oldBlock.getSvgRoot();
if (!svgRootOld) {
throw 'oldBlock is not rendered.';
}
// Create the new block by cloning the block in the flyout (via XML).
var xml = Blockly.Xml.blockToDom(oldBlock);
// The target workspace would normally resize during domToBlock, which will
// lead to weird jumps. Save it for terminateDrag.
targetWorkspace.setResizesEnabled(false);
// Using domToBlock instead of domToWorkspace means that the new block will be
// placed at position (0, 0) in main workspace units.
var block = Blockly.Xml.domToBlock(xml, targetWorkspace);
var svgRootNew = block.getSvgRoot();
if (!svgRootNew) {
throw 'block is not rendered.';
}
// The offset in pixels between the main workspace's origin and the upper left
// corner of the injection div.
var mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels();
// The offset in pixels between the flyout workspace's origin and the upper
// left corner of the injection div.
var flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels();
// The position of the old block in flyout workspace coordinates.
var oldBlockPosWs = oldBlock.getRelativeToSurfaceXY();
// The position of the old block in pixels relative to the flyout
// workspace's origin.
var oldBlockPosPixels = oldBlockPosWs.scale(this.workspace_.scale);
// The position of the old block in pixels relative to the upper left corner
// of the injection div.
var oldBlockOffsetPixels = goog.math.Coordinate.sum(flyoutOffsetPixels,
oldBlockPosPixels);
// The position of the old block in pixels relative to the origin of the
// main workspace.
var finalOffsetPixels = goog.math.Coordinate.difference(oldBlockOffsetPixels,
mainOffsetPixels);
// The position of the old block in main workspace coordinates.
var finalOffsetMainWs = finalOffsetPixels.scale(1 / targetWorkspace.scale);
block.moveBy(finalOffsetMainWs.x, finalOffsetMainWs.y);
return block;
};
+19 -8
Ver Arquivo
@@ -115,6 +115,13 @@ Blockly.FlyoutButton.prototype.width = 0;
*/
Blockly.FlyoutButton.prototype.height = 0;
/**
* Opaque data that can be passed to Blockly.unbindEvent_.
* @type {Array.<!Array>}
* @private
*/
Blockly.FlyoutButton.prototype.onMouseUpWrapper_ = null;
/**
* Create the button elements.
* @return {!Element} The button's SVG group.
@@ -163,6 +170,9 @@ Blockly.FlyoutButton.prototype.createDom = function() {
svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN);
this.updateTransform_();
this.mouseUpWrapper_ = Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup',
this, this.onMouseUp_);
return this.svgGroup_;
};
@@ -207,6 +217,9 @@ Blockly.FlyoutButton.prototype.getTargetWorkspace = function() {
* Dispose of this button.
*/
Blockly.FlyoutButton.prototype.dispose = function() {
if (this.onMouseUpWrapper_) {
Blockly.unbindEvent_(this.onMouseUpWrapper_);
}
if (this.svgGroup_) {
goog.dom.removeNode(this.svgGroup_);
this.svgGroup_ = null;
@@ -218,15 +231,13 @@ Blockly.FlyoutButton.prototype.dispose = function() {
/**
* Do something when the button is clicked.
* @param {!Event} e Mouse up event.
* @private
*/
Blockly.FlyoutButton.prototype.onMouseUp = function(e) {
// Don't scroll the page.
e.preventDefault();
// Don't propagate mousewheel event (zooming).
e.stopPropagation();
// Stop binding to mouseup and mousemove events--flyout mouseup would normally
// do this, but we're skipping that.
Blockly.Flyout.terminateDrag_();
Blockly.FlyoutButton.prototype.onMouseUp_ = function(e) {
var gesture = this.targetWorkspace_.getGesture(e);
if (gesture) {
gesture.cancel();
}
// Call the callback registered to this button.
if (this.callback_) {
+83
Ver Arquivo
@@ -0,0 +1,83 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 dragging a flyout visually.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.FlyoutDragger');
goog.require('Blockly.WorkspaceDragger');
goog.require('goog.asserts');
goog.require('goog.math.Coordinate');
/**
* Class for a flyout dragger. It moves a flyout workspace around when it is
* being dragged by a mouse or touch.
* Note that the workspace itself manages whether or not it has a drag surface
* and how to do translations based on that. This simply passes the right
* commands based on events.
* @param {!Blockly.Flyout} flyout The flyout to drag.
* @constructor
*/
Blockly.FlyoutDragger = function(flyout) {
Blockly.FlyoutDragger.superClass_.constructor.call(this,
flyout.getWorkspace());
/**
* The scrollbar to update to move the flyout.
* Unlike the main workspace, the flyout has only one scrollbar, in either the
* horizontal or the vertical direction.
* @type {!Blockly.Scrollbar}
* @private
*/
this.scrollbar_ = flyout.scrollbar_;
/**
* Whether the flyout scrolls horizontally. If false, the flyout scrolls
* vertically.
* @type {boolean}
* @private
*/
this.horizontalLayout_ = flyout.horizontalLayout_;
};
goog.inherits(Blockly.FlyoutDragger, Blockly.WorkspaceDragger);
/**
* Move the appropriate scrollbar to drag the flyout.
* Since flyouts only scroll in one direction at a time, this will discard one
* of the calculated values.
* x and y are in pixels.
* @param {number} x The new x position to move the scrollbar to.
* @param {number} y The new y position to move the scrollbar to.
* @private
*/
Blockly.FlyoutDragger.prototype.updateScroll_ = function(x, y) {
// Move the scrollbar and the flyout will scroll automatically.
if (this.horizontalLayout_) {
this.scrollbar_.set(x);
} else {
this.scrollbar_.set(y);
}
};
+380
Ver Arquivo
@@ -0,0 +1,380 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 Horizontal flyout tray containing blocks which may be created.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.HorizontalFlyout');
goog.require('Blockly.Block');
goog.require('Blockly.Events');
goog.require('Blockly.FlyoutButton');
goog.require('Blockly.Flyout');
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.
* @extends {Blockly.Flyout}
* @constructor
*/
Blockly.HorizontalFlyout = function(workspaceOptions) {
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
Blockly.HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions);
/**
* Flyout should be laid out horizontally.
* @type {boolean}
* @private
*/
this.horizontalLayout_ = true;
};
goog.inherits(Blockly.HorizontalFlyout, Blockly.Flyout);
/**
* 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.HorizontalFlyout.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.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
absoluteTop = 0;
}
var viewHeight = this.height_;
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
viewHeight -= this.SCROLLBAR_PADDING;
}
var viewWidth = this.width_ - 2 * 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.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) {
var metrics = this.getMetrics_();
// This is a fix to an apparent race condition.
if (!metrics) {
return;
}
if (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.HorizontalFlyout.prototype.position = function() {
if (!this.isVisible()) {
return;
}
var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
if (!targetWorkspaceMetrics) {
// Hidden components will return null.
return;
}
// Record the width for Blockly.Flyout.getMetrics_.
this.width_ = targetWorkspaceMetrics.viewWidth;
var edgeWidth = targetWorkspaceMetrics.viewWidth - 2 * this.CORNER_RADIUS;
var edgeHeight = this.height_ - this.CORNER_RADIUS;
this.setBackgroundPath_(edgeWidth, edgeHeight);
var x = targetWorkspaceMetrics.absoluteLeft;
var y = targetWorkspaceMetrics.absoluteTop;
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) {
y += (targetWorkspaceMetrics.viewHeight - this.height_);
}
this.positionAt_(this.width_, this.height_, x, y);
};
/**
* 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.HorizontalFlyout.prototype.setBackgroundPath_ = 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 + 2 * 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);
// 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);
// Right.
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
this.CORNER_RADIUS, this.CORNER_RADIUS);
path.push('v', height);
// Bottom.
path.push('h', -width - 2 * this.CORNER_RADIUS);
// Left.
path.push('z');
}
this.svgBackground_.setAttribute('d', path.join(' '));
};
/**
* Scroll the flyout to the top.
*/
Blockly.HorizontalFlyout.prototype.scrollToStart = function() {
this.scrollbar_.set(this.RTL ? Infinity : 0);
};
/**
* Scroll the flyout.
* @param {!Event} e Mouse wheel scroll event.
* @private
*/
Blockly.HorizontalFlyout.prototype.wheel_ = function(e) {
var delta = e.deltaX;
if (delta) {
if (goog.userAgent.GECKO) {
// Firefox's deltas are a tenth that of Chrome/Safari.
delta *= 10;
}
// TODO: #1093
var metrics = this.getMetrics_();
var pos = metrics.viewLeft + delta;
var limit = metrics.contentWidth - metrics.viewWidth;
pos = Math.min(pos, limit);
pos = Math.max(pos, 0);
this.scrollbar_.set(pos);
// When the flyout moves from a wheel event, hide WidgetDiv.
Blockly.WidgetDiv.hide();
}
// Don't scroll the page.
e.preventDefault();
// Don't propagate mousewheel event (zooming).
e.stopPropagation();
};
/**
* Lay out the blocks in the flyout.
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
* @param {!Array.<number>} gaps The visible gaps between blocks.
* @private
*/
Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) {
this.workspace_.scale = this.targetWorkspace_.scale;
var margin = this.MARGIN;
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
var cursorY = margin;
if (this.RTL) {
contents = contents.reverse();
}
for (var i = 0, item; item = contents[i]; i++) {
if (item.type == 'block') {
var block = item.block;
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();
// Figure out where to place the block.
var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
if (this.RTL) {
var moveX = cursorX + blockHW.width;
} else {
var moveX = cursorX + tab;
}
block.moveBy(moveX, cursorY);
var rect = this.createRect_(block, moveX, cursorY, blockHW, i);
cursorX += (blockHW.width + gaps[i]);
this.addBlockListeners_(root, block, rect);
} else if (item.type == 'button') {
this.initFlyoutButton_(item.button, cursorX, cursorY);
cursorX += (item.button.width + gaps[i]);
}
}
};
/**
* 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 {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @return {boolean} true if the drag is toward the workspace.
* @package
*/
Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace = function(
currentDragDeltaXY) {
var dx = currentDragDeltaXY.x;
var dy = currentDragDeltaXY.y;
// 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 range = this.dragAngleRange_;
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
// Horizontal at top.
if (dragDirection < 90 + range && dragDirection > 90 - range) {
return true;
}
} else {
// Horizontal at bottom.
if (dragDirection > -90 - range && dragDirection < -90 + range) {
return true;
}
}
return false;
};
/**
* Return the deletion rectangle for this flyout in viewport coordinates.
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.HorizontalFlyout.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 y = flyoutRect.top;
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);
}
// TODO: Else throw error (should never happen).
};
/**
* 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.
* @private
*/
Blockly.HorizontalFlyout.prototype.reflowInternal_ = 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++) {
if (block.flyoutRect_) {
this.moveRectToBlock_(block.flyoutRect_, block);
}
}
// 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();
}
};
+370
Ver Arquivo
@@ -0,0 +1,370 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 Layout code for a vertical variant of the flyout.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.VerticalFlyout');
goog.require('Blockly.Block');
goog.require('Blockly.Events');
goog.require('Blockly.Flyout');
goog.require('Blockly.FlyoutButton');
goog.require('Blockly.utils');
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.
* @extends {Blockly.Flyout}
* @constructor
*/
Blockly.VerticalFlyout = function(workspaceOptions) {
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
Blockly.VerticalFlyout.superClass_.constructor.call(this, workspaceOptions);
/**
* Flyout should be laid out vertically.
* @type {boolean}
* @private
*/
this.horizontalLayout_ = false;
};
goog.inherits(Blockly.VerticalFlyout, Blockly.Flyout);
/**
* 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.VerticalFlyout.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};
}
// Padding for the end of the scrollbar.
var absoluteTop = this.SCROLLBAR_PADDING;
var 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 * this.workspace_.scale + 2 * this.MARGIN,
contentWidth: optionBox.width * this.workspace_.scale + 2 * this.MARGIN,
viewTop: -this.workspace_.scrollY + optionBox.y,
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.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) {
var metrics = this.getMetrics_();
// This is a fix to an apparent race condition.
if (!metrics) {
return;
}
if (goog.isNumber(xyRatio.y)) {
this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y;
}
this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft,
this.workspace_.scrollY + metrics.absoluteTop);
};
/**
* Move the flyout to the edge of the workspace.
*/
Blockly.VerticalFlyout.prototype.position = function() {
if (!this.isVisible()) {
return;
}
var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
if (!targetWorkspaceMetrics) {
// Hidden components will return null.
return;
}
// Record the height for Blockly.Flyout.getMetrics_
this.height_ = targetWorkspaceMetrics.viewHeight;
var edgeWidth = this.width_ - this.CORNER_RADIUS;
var edgeHeight = targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS;
this.setBackgroundPath_(edgeWidth, edgeHeight);
var y = targetWorkspaceMetrics.absoluteTop;
var x = targetWorkspaceMetrics.absoluteLeft;
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
x += (targetWorkspaceMetrics.viewWidth - this.width_);
}
this.positionAt_(this.width_, this.height_, x, y);
};
/**
* 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.VerticalFlyout.prototype.setBackgroundPath_ = function(width, height) {
var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT;
var totalWidth = width + this.CORNER_RADIUS;
// Decide whether to start on the left or right.
var path = ['M ' + (atRight ? totalWidth : 0) + ',0'];
// Top.
path.push('h', atRight ? -width : 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));
// 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', atRight ? width : -width);
path.push('z');
this.svgBackground_.setAttribute('d', path.join(' '));
};
/**
* Scroll the flyout to the top.
*/
Blockly.VerticalFlyout.prototype.scrollToStart = function() {
this.scrollbar_.set(0);
};
/**
* Scroll the flyout.
* @param {!Event} e Mouse wheel scroll event.
* @private
*/
Blockly.VerticalFlyout.prototype.wheel_ = function(e) {
var delta = 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 = metrics.viewTop + delta;
var limit = metrics.contentHeight - metrics.viewHeight;
pos = Math.min(pos, limit);
pos = Math.max(pos, 0);
this.scrollbar_.set(pos);
// When the flyout moves from a wheel event, hide WidgetDiv.
Blockly.WidgetDiv.hide();
}
// Don't scroll the page.
e.preventDefault();
// Don't propagate mousewheel event (zooming).
e.stopPropagation();
};
/**
* Lay out the blocks in the flyout.
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
* @param {!Array.<number>} gaps The visible gaps between blocks.
* @private
*/
Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) {
this.workspace_.scale = this.targetWorkspace_.scale;
var margin = this.MARGIN;
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
var cursorY = margin;
for (var i = 0, item; item = contents[i]; i++) {
if (item.type == 'block') {
var block = item.block;
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();
block.moveBy(cursorX, cursorY);
var rect = this.createRect_(block,
this.RTL ? cursorX - blockHW.width : cursorX, cursorY, blockHW, i);
this.addBlockListeners_(root, block, rect);
cursorY += blockHW.height + gaps[i];
} else if (item.type == 'button') {
this.initFlyoutButton_(item.button, cursorX, cursorY);
cursorY += item.button.height + gaps[i];
}
}
};
/**
* 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 {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @return {boolean} true if the drag is toward the workspace.
* @package
*/
Blockly.VerticalFlyout.prototype.isDragTowardWorkspace = function(
currentDragDeltaXY) {
var dx = currentDragDeltaXY.x;
var dy = currentDragDeltaXY.y;
// 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 range = this.dragAngleRange_;
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
// Vertical at left.
if (dragDirection < range && dragDirection > -range) {
return true;
}
} else {
// Vertical at right.
if (dragDirection < -180 + range || dragDirection > 180 - range) {
return true;
}
}
return false;
};
/**
* Return the deletion rectangle for this flyout in viewport coordinates.
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.VerticalFlyout.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 width = flyoutRect.width;
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);
}
};
/**
* 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.
* @private
*/
Blockly.VerticalFlyout.prototype.reflowInternal_ = 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);
}
for (var i = 0, button; button = this.buttons_[i]; i++) {
flyoutWidth = Math.max(flyoutWidth, button.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++) {
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_) {
this.moveRectToBlock_(block.flyoutRect_, block);
}
}
// 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();
}
};
+784
Ver Arquivo
@@ -0,0 +1,784 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 an in-progress gesture, usually a drag
* or a tap.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.Gesture');
goog.require('Blockly.BlockDragger');
goog.require('Blockly.constants');
goog.require('Blockly.FlyoutDragger');
goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
goog.require('Blockly.WorkspaceDragger');
goog.require('goog.asserts');
goog.require('goog.math.Coordinate');
/*
* Note: In this file "start" refers to touchstart, mousedown, and pointerstart
* events. "End" refers to touchend, mouseup, and pointerend events.
*/
// TODO: Consider touchcancel/pointercancel.
/**
* Class for one gesture.
* @param {!Event} e The event that kicked off this gesture.
* @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created
* this gesture and has a reference to it.
* @constructor
*/
Blockly.Gesture = function(e, creatorWorkspace) {
/**
* The position of the mouse when the gesture started. Units are css pixels,
* with (0, 0) at the top left of the browser window (mouseEvent clientX/Y).
* @type {goog.math.Coordinate}
*/
this.mouseDownXY_ = null;
/**
* How far the mouse has moved during this drag, in pixel units.
* (0, 0) is at this.mouseDownXY_.
* @type {goog.math.Coordinate}
* private
*/
this.currentDragDeltaXY_ = 0;
/**
* The field that the gesture started on, or null if it did not start on a
* field.
* @type {Blockly.Field}
* @private
*/
this.startField_ = null;
/**
* The block that the gesture started on, or null if it did not start on a
* block.
* @type {Blockly.BlockSvg}
* @private
*/
this.startBlock_ = null;
/**
* The block that this gesture targets. If the gesture started on a
* shadow block, this is the first non-shadow parent of the block. If the
* gesture started in the flyout, this is the root block of the block group
* that was clicked or dragged.
* @type {Blockly.BlockSvg}
* @private
*/
this.targetBlock_ = null;
/**
* The workspace that the gesture started on. There may be multiple
* workspaces on a page; this is more accurate than using
* Blockly.getMainWorkspace().
* @type {Blockly.WorkspaceSvg}
* @private
*/
this.startWorkspace_ = null;
/**
* The workspace that created this gesture. This workspace keeps a reference
* to the gesture, which will need to be cleared at deletion.
* This may be different from the start workspace. For instance, a flyout is
* a workspace, but its parent workspace manages gestures for it.
* @type {Blockly.WorkspaceSvg}
* @private
*/
this.creatorWorkspace_ = creatorWorkspace;
/**
* Whether the pointer has at any point moved out of the drag radius.
* A gesture that exceeds the drag radius is a drag even if it ends exactly at
* its start point.
* @type {boolean}
* @private
*/
this.hasExceededDragRadius_ = false;
/**
* Whether the workspace is currently being dragged.
* @type {boolean}
* @private
*/
this.isDraggingWorkspace_ = false;
/**
* Whether the block is currently being dragged.
* @type {boolean}
* @private
*/
this.isDraggingBlock_ = false;
/**
* The event that most recently updated this gesture.
* @type {!Event}
* @private
*/
this.mostRecentEvent_ = e;
/**
* A handle to use to unbind a mouse move listener at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
* @type {Array.<!Array>}
* @private
*/
this.onMoveWrapper_ = null;
/**
* A handle to use to unbind a mouse up listener at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
* @type {Array.<!Array>}
* @private
*/
this.onUpWrapper_ = null;
/**
* The object tracking a block drag, or null if none is in progress.
* @type {Blockly.BlockDragger}
* @private
*/
this.blockDragger_ = null;
/**
* The object tracking a workspace or flyout workspace drag, or null if none
* is in progress.
* @type {Blockly.WorkspaceDragger}
* @private
*/
this.workspaceDragger_ = null;
/**
* The flyout a gesture started in, if any.
* @type {Blockly.Flyout}
* @private
*/
this.flyout_ = null;
/**
* Boolean for sanity-checking that some code is only called once.
* @type {boolean}
* @private
*/
this.calledUpdateIsDragging_ = false;
/**
* Boolean for sanity-checking that some code is only called once.
* @type {boolean}
* @private
*/
this.hasStarted_ = false;
/**
* Boolean used internally to break a cycle in disposal.
* @type {boolean}
* @private
*/
this.isEnding_ = false;
};
/**
* Sever all links from this object.
* @package
*/
Blockly.Gesture.prototype.dispose = function() {
Blockly.Touch.clearTouchIdentifier();
Blockly.Tooltip.unblock();
// Clear the owner's reference to this gesture.
this.creatorWorkspace_.clearGesture();
if (this.onMoveWrapper_) {
Blockly.unbindEvent_(this.onMoveWrapper_);
}
if (this.onUpWrapper_) {
Blockly.unbindEvent_(this.onUpWrapper_);
}
this.startField_ = null;
this.startBlock_ = null;
this.targetBlock_ = null;
this.startWorkspace_ = null;
this.flyout_ = null;
if (this.blockDragger_) {
this.blockDragger_.dispose();
this.blockDragger_ = null;
}
if (this.workspaceDragger_) {
this.workspaceDragger_.dispose();
this.workspaceDragger_ = null;
}
};
/**
* Update internal state based on an event.
* @param {!Event} e The most recent mouse or touch event.
* @private
*/
Blockly.Gesture.prototype.updateFromEvent_ = function(e) {
var currentXY = new goog.math.Coordinate(e.clientX, e.clientY);
var changed = this.updateDragDelta_(currentXY);
// Exceeded the drag radius for the first time.
if (changed){
this.updateIsDragging_();
Blockly.longStop_();
}
this.mostRecentEvent_ = e;
};
/**
* DO MATH to set currentDragDeltaXY_ based on the most recent mouse position.
* @param {!goog.math.Coordinate} currentXY The most recent mouse/pointer
* position, in pixel units, with (0, 0) at the window's top left corner.
* @return {boolean} True if the drag just exceeded the drag radius for the
* first time.
* @private
*/
Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) {
this.currentDragDeltaXY_ = goog.math.Coordinate.difference(currentXY,
this.mouseDownXY_);
if (!this.hasExceededDragRadius_) {
var currentDragDelta = goog.math.Coordinate.magnitude(
this.currentDragDeltaXY_);
// The flyout has a different drag radius from the rest of Blockly.
var limitRadius = this.flyout_ ? Blockly.FLYOUT_DRAG_RADIUS :
Blockly.DRAG_RADIUS;
this.hasExceededDragRadius_ = currentDragDelta > limitRadius;
return this.hasExceededDragRadius_;
}
return false;
};
/**
* Update this gesture to record whether a block is being dragged from the
* flyout.
* This function should be called on a mouse/touch move event the first time the
* drag radius is exceeded. It should be called no more than once per gesture.
* If a block should be dragged from the flyout this function creates the new
* block on the main workspace and updates targetBlock_ and startWorkspace_.
* @return {boolean} True if a block is being dragged from the flyout.
* @private
*/
Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() {
// Disabled blocks may not be dragged from the flyout.
if (this.targetBlock_.disabled) {
return false;
}
if (!this.flyout_.isScrollable() ||
this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) {
this.startWorkspace_ = this.flyout_.targetWorkspace_;
this.startWorkspace_.updateScreenCalculationsIfScrolled();
// Start the event group now, so that the same event group is used for block
// creation and block dragging.
if (!Blockly.Events.getGroup()) {
Blockly.Events.setGroup(true);
}
// The start block is no longer relevant, because this is a drag.
this.startBlock_ = null;
this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_);
this.targetBlock_.select();
return true;
}
return false;
};
/**
* Update this gesture to record whether a block is being dragged.
* This function should be called on a mouse/touch move event the first time the
* drag radius is exceeded. It should be called no more than once per gesture.
* If a block should be dragged, either from the flyout or in the workspace,
* this function creates the necessary BlockDragger and starts the drag.
* @return {boolean} true if a block is being dragged.
* @private
*/
Blockly.Gesture.prototype.updateIsDraggingBlock_ = function() {
if (!this.targetBlock_) {
return false;
}
if (this.flyout_) {
this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_();
} else if (this.targetBlock_.isMovable()){
this.isDraggingBlock_ = true;
}
if (this.isDraggingBlock_) {
this.startDraggingBlock_();
return true;
}
return false;
};
/**
* Update this gesture to record whether a workspace is being dragged.
* This function should be called on a mouse/touch move event the first time the
* drag radius is exceeded. It should be called no more than once per gesture.
* If a workspace is being dragged this function creates the necessary
* WorkspaceDragger or FlyoutDragger and starts the drag.
* @private
*/
Blockly.Gesture.prototype.updateIsDraggingWorkspace_ = function() {
var wsMovable = this.flyout_ ? this.flyout_.isScrollable() :
this.startWorkspace_ && this.startWorkspace_.isDraggable();
if (!wsMovable) {
return;
}
if (this.flyout_) {
this.workspaceDragger_ = new Blockly.FlyoutDragger(this.flyout_);
} else {
this.workspaceDragger_ = new Blockly.WorkspaceDragger(this.startWorkspace_);
}
this.isDraggingWorkspace_ = true;
this.workspaceDragger_.startDrag();
};
/**
* Update this gesture to record whether anything is being dragged.
* This function should be called on a mouse/touch move event the first time the
* drag radius is exceeded. It should be called no more than once per gesture.
* @private
*/
Blockly.Gesture.prototype.updateIsDragging_ = function() {
// Sanity check.
goog.asserts.assert(!this.calledUpdateIsDragging_,
'updateIsDragging_ should only be called once per gesture.');
this.calledUpdateIsDragging_ = true;
// First check if it was a block drag.
if (this.updateIsDraggingBlock_()) {
return;
}
// Then check if it's a workspace drag.
this.updateIsDraggingWorkspace_();
};
/**
* Create a block dragger and start dragging the selected block.
* @private
*/
Blockly.Gesture.prototype.startDraggingBlock_ = function() {
this.blockDragger_ = new Blockly.BlockDragger(this.targetBlock_,
this.startWorkspace_);
this.blockDragger_.startBlockDrag(this.currentDragDeltaXY_);
this.blockDragger_.dragBlock(this.mostRecentEvent_,
this.currentDragDeltaXY_);
};
/**
* Start a gesture: update the workspace to indicate that a gesture is in
* progress and bind mousemove and mouseup handlers.
* @param {!Event} e A mouse down or touch start event.
* @package
*/
Blockly.Gesture.prototype.doStart = function(e) {
if (Blockly.utils.isTargetInput(e)) {
this.cancel();
return;
}
this.hasStarted_ = true;
Blockly.BlockSvg.disconnectUiStop_();
this.startWorkspace_.updateScreenCalculationsIfScrolled();
if (this.startWorkspace_.isMutator) {
// Mutator's coordinate system could be out of date because the bubble was
// dragged, the block was moved, the parent workspace zoomed, etc.
this.startWorkspace_.resize();
}
this.startWorkspace_.markFocused();
this.mostRecentEvent_ = e;
// Hide chaff also hides the flyout, so don't do it if the click is in a flyout.
Blockly.hideChaff(!!this.flyout_);
Blockly.Tooltip.block();
if (this.targetBlock_) {
this.targetBlock_.select();
}
if (Blockly.utils.isRightButton(e)) {
this.handleRightClick(e);
return;
}
if (goog.string.caseInsensitiveEquals(e.type, 'touchstart')) {
Blockly.longStart_(e, this);
}
this.mouseDownXY_ = new goog.math.Coordinate(e.clientX, e.clientY);
this.onMoveWrapper_ = Blockly.bindEventWithChecks_(
document, 'mousemove', null, this.handleMove.bind(this));
this.onUpWrapper_ = Blockly.bindEventWithChecks_(
document, 'mouseup', null, this.handleUp.bind(this));
e.preventDefault();
e.stopPropagation();
};
/**
* Handle a mouse move or touch move event.
* @param {!Event} e A mouse move or touch move event.
* @package
*/
Blockly.Gesture.prototype.handleMove = function(e) {
this.updateFromEvent_(e);
if (this.isDraggingWorkspace_) {
this.workspaceDragger_.drag(this.currentDragDeltaXY_);
} else if (this.isDraggingBlock_) {
this.blockDragger_.dragBlock(this.mostRecentEvent_,
this.currentDragDeltaXY_);
}
e.preventDefault();
e.stopPropagation();
};
/**
* Handle a mouse up or touch end event.
* @param {!Event} e A mouse up or touch end event.
* @package
*/
Blockly.Gesture.prototype.handleUp = function(e) {
this.updateFromEvent_(e);
Blockly.longStop_();
if (this.isEnding_) {
console.log('Trying to end a gesture recursively.');
return;
}
this.isEnding_ = true;
// The ordering of these checks is important: drags have higher priority than
// clicks. Fields have higher priority than blocks; blocks have higher
// priority than workspaces.
if (this.isDraggingBlock_) {
this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_);
} else if (this.isDraggingWorkspace_) {
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
} else if (this.isFieldClick_()) {
this.doFieldClick_();
} else if (this.isBlockClick_()) {
this.doBlockClick_();
} else if (this.isWorkspaceClick_()) {
this.doWorkspaceClick_();
}
e.preventDefault();
e.stopPropagation();
this.dispose();
};
/**
* Cancel an in-progress gesture. If a workspace or block drag is in progress,
* end the drag at the most recent location.
* @package
*/
Blockly.Gesture.prototype.cancel = function() {
// Disposing of a block cancels in-progress drags, but dragging to a delete
// area disposes of a block and leads to recursive disposal. Break that cycle.
if (this.isEnding_) {
return;
}
Blockly.longStop_();
if (this.isDraggingBlock_) {
this.blockDragger_.endBlockDrag(this.mostRecentEvent_,
this.currentDragDeltaXY_);
} else if (this.isDraggingWorkspace_) {
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
}
this.dispose();
};
/**
* Handle a real or faked right-click event by showing a context menu.
* @param {!Event} e A mouse move or touch move event.
* @package
*/
Blockly.Gesture.prototype.handleRightClick = function(e) {
if (this.targetBlock_) {
this.bringBlockToFront_();
Blockly.hideChaff(this.flyout_);
this.targetBlock_.showContextMenu_(e);
} else if (this.startWorkspace_ && !this.flyout_) {
Blockly.hideChaff();
this.startWorkspace_.showContextMenu_(e);
}
e.preventDefault();
e.stopPropagation();
this.dispose();
};
/**
* Handle a mousedown/touchstart event on a workspace.
* @param {!Event} e A mouse down or touch start event.
* @param {!Blockly.Workspace} ws The workspace the event hit.
* @package
*/
Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
goog.asserts.assert(!this.hasStarted_,
'Tried to call gesture.handleWsStart, but the gesture had already been ' +
'started.');
this.setStartWorkspace_(ws);
this.mostRecentEvent_ = e;
this.doStart(e);
};
/**
* Handle a mousedown/touchstart event on a flyout.
* @param {!Event} e A mouse down or touch start event.
* @param {!Blockly.Flyout} flyout The flyout the event hit.
* @package
*/
Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) {
goog.asserts.assert(!this.hasStarted_,
'Tried to call gesture.handleFlyoutStart, but the gesture had already been ' +
'started.');
this.setStartFlyout_(flyout);
this.handleWsStart(e, flyout.getWorkspace());
};
/**
* Handle a mousedown/touchstart event on a block.
* @param {!Event} e A mouse down or touch start event.
* @param {!Blockly.BlockSvg} block The block the event hit.
* @package
*/
Blockly.Gesture.prototype.handleBlockStart = function(e, block) {
goog.asserts.assert(!this.hasStarted_,
'Tried to call gesture.handleBlockStart, but the gesture had already been ' +
'started.');
this.setStartBlock(block);
this.mostRecentEvent_ = e;
};
/* Begin functions defining what actions to take to execute clicks on each type
* of target. Any developer wanting to add behaviour on clicks should modify
* only this code. */
/**
* Execute a field click.
* @private
*/
Blockly.Gesture.prototype.doFieldClick_ = function() {
this.startField_.showEditor_();
this.bringBlockToFront_();
};
/**
* Execute a block click.
* @private
*/
Blockly.Gesture.prototype.doBlockClick_ = function() {
// Block click in an autoclosing flyout.
if (this.flyout_ && this.flyout_.autoClose) {
if (!this.targetBlock_.disabled) {
if (!Blockly.Events.getGroup()) {
Blockly.Events.setGroup(true);
}
var newBlock = this.flyout_.createBlock(this.targetBlock_);
newBlock.scheduleSnapAndBump();
}
} else {
// Clicks events are on the start block, even if it was a shadow.
Blockly.Events.fire(
new Blockly.Events.Ui(this.startBlock_, 'click', undefined, undefined));
}
this.bringBlockToFront_();
Blockly.Events.setGroup(false);
};
/**
* Execute a workspace click.
* @private
*/
Blockly.Gesture.prototype.doWorkspaceClick_ = function() {
if (Blockly.selected) {
Blockly.selected.unselect();
}
};
/* End functions defining what actions to take to execute clicks on each type
* of target. */
/**
* Move the dragged/clicked block to the front of the workspace so that it is
* not occluded by other blocks.
* @private
*/
Blockly.Gesture.prototype.bringBlockToFront_ = function() {
// Blocks in the flyout don't overlap, so skip the work.
if (this.targetBlock_ && !this.flyout_) {
this.targetBlock_.bringToFront();
}
};
/* Begin functions for populating a gesture at mouse down. */
/**
* Record the field that a gesture started on.
* @param {Blockly.Field} field The field the gesture started on.
* @package
*/
Blockly.Gesture.prototype.setStartField = function(field) {
goog.asserts.assert(!this.hasStarted_,
'Tried to call gesture.setStartField, but the gesture had already been ' +
'started.');
if (!this.startField_) {
this.startField_ = field;
}
};
/**
* Record the block that a gesture started on, and set the target block
* appropriately.
* @param {Blockly.BlockSvg} block The block the gesture started on.
* @package
*/
Blockly.Gesture.prototype.setStartBlock = function(block) {
if (!this.startBlock_) {
this.startBlock_ = block;
if (block.isInFlyout && block != block.getRootBlock()) {
this.setTargetBlock_(block.getRootBlock());
} else {
this.setTargetBlock_(block);
}
}
};
/**
* Record the block that a gesture targets, meaning the block that will be
* dragged if this turns into a drag. If this block is a shadow, that will be
* its first non-shadow parent.
* @param {Blockly.BlockSvg} block The block the gesture targets.
* @private
*/
Blockly.Gesture.prototype.setTargetBlock_ = function(block) {
if (block.isShadow()) {
this.setTargetBlock_(block.getParent());
} else {
this.targetBlock_ = block;
}
};
/**
* Record the workspace that a gesture started on.
* @param {Blockly.WorkspaceSvg} ws The workspace the gesture started on.
* @private
*/
Blockly.Gesture.prototype.setStartWorkspace_ = function(ws) {
if (!this.startWorkspace_) {
this.startWorkspace_ = ws;
}
};
/**
* Record the flyout that a gesture started on.
* @param {Blockly.Flyout} flyout The flyout the gesture started on.
* @private
*/
Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) {
if (!this.flyout_) {
this.flyout_ = flyout;
}
};
/* End functions for populating a gesture at mouse down. */
/* Begin helper functions defining types of clicks. Any developer wanting
* to change the definition of a click should modify only this code. */
/**
* Whether this gesture is a click on a block. This should only be called when
* ending a gesture (mouse up, touch end).
* @return {boolean} whether this gesture was a click on a block.
* @private
*/
Blockly.Gesture.prototype.isBlockClick_ = function() {
// A block click starts on a block, never escapes the drag radius, and is not
// a field click.
var hasStartBlock = !!this.startBlock_;
return hasStartBlock && !this.hasExceededDragRadius_ && !this.isFieldClick_();
};
/**
* Whether this gesture is a click on a field. This should only be called when
* ending a gesture (mouse up, touch end).
* @return {boolean} whether this gesture was a click on a field.
* @private
*/
Blockly.Gesture.prototype.isFieldClick_ = function() {
var fieldEditable = this.startField_ ?
this.startField_.isCurrentlyEditable() : false;
return fieldEditable && !this.hasExceededDragRadius_ && (!this.flyout_ ||
!this.flyout_.autoClose);
};
/**
* Whether this gesture is a click on a workspace. This should only be called
* when ending a gesture (mouse up, touch end).
* @return {boolean} whether this gesture was a click on a workspace.
* @private
*/
Blockly.Gesture.prototype.isWorkspaceClick_ = function() {
var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_;
return onlyTouchedWorkspace && !this.hasExceededDragRadius_;
};
/* End helper functions defining types of clicks. */
/**
* Whether this gesture is a drag of either a workspace or block.
* This function is called externally to block actions that cannot be taken
* mid-drag (e.g. using the keyboard to delete the selected blocks).
* @return {boolean} true if this gesture is a drag of a workspace or block.
* @package
*/
Blockly.Gesture.prototype.isDragging = function() {
return this.isDraggingWorkspace_ || this.isDraggingBlock_;
};
/**
* Whether this gesture has already been started. In theory every mouse down
* has a corresponding mouse up, but in reality it is possible to lose a
* mouse up, leaving an in-process gesture hanging.
* @return {boolean} whether this gesture was a click on a workspace.
* @package
*/
Blockly.Gesture.prototype.hasStarted = function() {
return this.hasStarted_;
};
+222
Ver Arquivo
@@ -0,0 +1,222 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 for configuring and updating a workspace grid in
* Blockly.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.Grid');
goog.require('Blockly.utils');
goog.require('goog.userAgent');
/**
* Class for a workspace's grid.
* @param {!SVGElement} pattern The grid's SVG pattern, created during injection.
* @param {!Object} options A dictionary of normalized options for the grid.
* See grid documentation:
* https://developers.google.com/blockly/guides/configure/web/grid
* @constructor
*/
Blockly.Grid = function(pattern, options) {
/**
* The grid's SVG pattern, created during injection.
* @type {!SVGElement}
* @private
*/
this.gridPattern_ = pattern;
/**
* The spacing of the grid lines (in px).
* @type {number}
* @private
*/
this.spacing_ = options['spacing'];
/**
* How long the grid lines should be (in px).
* @type {number}
* @private
*/
this.length_ = options['length'];
/**
* The horizontal grid line, if it exists.
* @type {SVGElement}
* @private
*/
this.line1_ = pattern.firstChild;
/**
* The vertical grid line, if it exists.
* @type {SVGElement}
* @private
*/
this.line2_ = this.line1_ && this.line1_.nextSibling;
/**
* Whether blocks should snap to the grid.
* @type {boolean}
* @private
*/
this.snapToGrid_ = options['snap'];
};
/**
* The scale of the grid, used to set stroke width on grid lines.
* This should always be the same as the workspace scale.
* @type {number}
* @private
*/
Blockly.Grid.prototype.scale_ = 1;
/**
* Dispose of this grid and unlink from the DOM.
* @package
*/
Blockly.Grid.prototype.dispose = function() {
this.gridPattern_ = null;
};
/**
* Whether blocks should snap to the grid, based on the initial configuration.
* @return {boolean} True if blocks should snap, false otherwise.
* @package
*/
Blockly.Grid.prototype.shouldSnap = function() {
return this.snapToGrid_;
};
/**
* Get the spacing of the grid points (in px).
* @return {number} The spacing of the grid points.
* @package
*/
Blockly.Grid.prototype.getSpacing = function() {
return this.spacing_;
};
/**
* Get the id of the pattern element, which should be randomized to avoid
* conflicts with other Blockly instances on the page.
* @return {string} The pattern id.
* @package
*/
Blockly.Grid.prototype.getPatternId = function() {
return this.gridPattern_.id;
};
/**
* Update the grid with a new scale.
* @param {number} scale The new workspace scale.
* @package
*/
Blockly.Grid.prototype.update = function(scale) {
this.scale_ = scale;
// MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100.
var safeSpacing = (this.spacing_ * scale) || 100;
this.gridPattern_.setAttribute('width', safeSpacing);
this.gridPattern_.setAttribute('height', safeSpacing);
var half = Math.floor(this.spacing_ / 2) + 0.5;
var start = half - this.length_ / 2;
var end = half + this.length_ / 2;
half *= scale;
start *= scale;
end *= scale;
this.setLineAttributes_(this.line1_, scale, start, end, half, half);
this.setLineAttributes_(this.line2_, scale, half, half, start, end);
};
/**
* Set the attributes on one of the lines in the grid. Use this to update the
* length and stroke width of the grid lines.
* @param {!SVGElement} line Which line to update.
* @param {number} width The new stroke size (in px).
* @param {number} x1 The new x start position of the line (in px).
* @param {number} x2 The new x end position of the line (in px).
* @param {number} y1 The new y start position of the line (in px).
* @param {number} y2 The new y end position of the line (in px).
* @private
*/
Blockly.Grid.prototype.setLineAttributes_ = function(line, width, x1, x2, y1, y2) {
if (line) {
line.setAttribute('stroke-width', width);
line.setAttribute('x1', x1);
line.setAttribute('y1', y1);
line.setAttribute('x2', x2);
line.setAttribute('y2', y2);
}
};
/**
* Move the grid to a new x and y position, and make sure that change is visible.
* @param {number} x The new x position of the grid (in px).
* @param {number} y The new y position ofthe grid (in px).
* @package
*/
Blockly.Grid.prototype.moveTo = function(x, y) {
this.gridPattern_.setAttribute('x', x);
this.gridPattern_.setAttribute('y', y);
if (goog.userAgent.IE || goog.userAgent.EDGE) {
// IE/Edge doesn't notice that the x/y offsets have changed.
// Force an update.
this.update(this.scale_);
}
};
/**
* Create the DOM for the grid described by options.
* @param {string} rnd A random ID to append to the pattern's ID.
* @param {!Object} gridOptions The object containing grid configuration.
* @param {!SVGElement} defs The root SVG element for this workspace's defs.
* @return {!SVGElement} The SVG element for the grid pattern.
* @package
*/
Blockly.Grid.createDom = function(rnd, gridOptions, defs) {
/*
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
<rect stroke="#888" />
<rect stroke="#888" />
</pattern>
*/
var gridPattern = Blockly.utils.createSvgElement('pattern',
{'id': 'blocklyGridPattern' + rnd,
'patternUnits': 'userSpaceOnUse'}, defs);
if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) {
Blockly.utils.createSvgElement('line',
{'stroke': gridOptions['colour']}, gridPattern);
if (gridOptions['length'] > 1) {
Blockly.utils.createSvgElement('line',
{'stroke': gridOptions['colour']}, gridPattern);
}
// x1, y1, x1, x2 properties will be set later in update.
}
return gridPattern;
};
+3 -2
Ver Arquivo
@@ -170,7 +170,7 @@ Blockly.Icon.prototype.renderIcon = function(cursorX) {
/**
* Notification that the icon has moved. Update the arrow accordingly.
* @param {!goog.math.Coordinate} xy Absolute location.
* @param {!goog.math.Coordinate} xy Absolute location in workspace coordinates.
*/
Blockly.Icon.prototype.setIconLocation = function(xy) {
this.iconXY_ = xy;
@@ -197,7 +197,8 @@ Blockly.Icon.prototype.computeIconLocation = function() {
/**
* Returns the center of the block's icon relative to the surface.
* @return {!goog.math.Coordinate} Object with x and y properties.
* @return {!goog.math.Coordinate} Object with x and y properties in workspace
* coordinates.
*/
Blockly.Icon.prototype.getIconLocation = function() {
return this.iconXY_;
+11 -32
Ver Arquivo
@@ -28,6 +28,7 @@ goog.provide('Blockly.inject');
goog.require('Blockly.BlockDragSurfaceSvg');
goog.require('Blockly.Css');
goog.require('Blockly.Grid');
goog.require('Blockly.Options');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.WorkspaceDragSurfaceSvg');
@@ -60,7 +61,7 @@ Blockly.inject = function(container, opt_options) {
// Create surfaces for dragging things. These are optimizations
// so that the broowser does not repaint during the drag.
var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer);
var workspaceDragSurface = new Blockly.workspaceDragSurfaceSvg(subContainer);
var workspaceDragSurface = new Blockly.WorkspaceDragSurfaceSvg(subContainer);
var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface,
workspaceDragSurface);
@@ -164,27 +165,8 @@ Blockly.createDom_ = function(container, options) {
Blockly.utils.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.utils.createSvgElement('pattern',
{'id': 'blocklyGridPattern' + rnd,
'patternUnits': 'userSpaceOnUse'}, defs);
if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
Blockly.utils.createSvgElement('line',
{'stroke': options.gridOptions['colour']},
gridPattern);
if (options.gridOptions['length'] > 1) {
Blockly.utils.createSvgElement('line',
{'stroke': options.gridOptions['colour']},
gridPattern);
}
// x1, y1, x1, x2 properties will be set later in updateGridPattern_.
}
options.gridPattern = gridPattern;
options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs);
return svg;
};
@@ -217,7 +199,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspac
if (!options.readOnly && !options.hasScrollbars) {
var workspaceChanged = function() {
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
if (!mainWorkspace.isDragging()) {
var metrics = mainWorkspace.getMetrics();
var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
var edgeTop = metrics.viewTop + metrics.absoluteTop;
@@ -280,7 +262,7 @@ Blockly.init_ = function(mainWorkspace) {
var svg = mainWorkspace.getParentSvg();
// Suppress the browser's context menu.
Blockly.bindEventWithChecks_(svg, 'contextmenu', null,
Blockly.bindEventWithChecks_(svg.parentNode, 'contextmenu', null,
function(e) {
if (!Blockly.utils.isTargetInput(e)) {
e.preventDefault();
@@ -343,10 +325,6 @@ Blockly.inject.bindDocumentEvents_ = function() {
// should run regardless of what other touch event handlers have run.
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 squelch 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.bindEventWithChecks_(window, 'orientationchange', document,
@@ -366,15 +344,16 @@ Blockly.inject.bindDocumentEvents_ = function() {
* @private
*/
Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
workspace.loadAudio_(
var audioMgr = workspace.getAudioManager();
audioMgr.load(
[pathToMedia + 'click.mp3',
pathToMedia + 'click.wav',
pathToMedia + 'click.ogg'], 'click');
workspace.loadAudio_(
audioMgr.load(
[pathToMedia + 'disconnect.wav',
pathToMedia + 'disconnect.mp3',
pathToMedia + 'disconnect.ogg'], 'disconnect');
workspace.loadAudio_(
audioMgr.load(
[pathToMedia + 'delete.mp3',
pathToMedia + 'delete.ogg',
pathToMedia + 'delete.wav'], 'delete');
@@ -385,7 +364,7 @@ Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
while (soundBinds.length) {
Blockly.unbindEvent_(soundBinds.pop());
}
workspace.preloadAudio_();
audioMgr.preload();
};
// These are bound on mouse/touch events with Blockly.bindEventWithChecks_, so
+3
Ver Arquivo
@@ -41,6 +41,9 @@ goog.require('goog.asserts');
* @constructor
*/
Blockly.Input = function(type, name, block, connection) {
if (type != Blockly.DUMMY_INPUT && !name) {
throw 'Value inputs and statement inputs must have non-empty name.';
}
/** @type {number} */
this.type = type;
/** @type {string} */
+12 -4
Ver Arquivo
@@ -137,7 +137,11 @@ Blockly.Mutator.prototype.createEditor_ = function() {
// To fix this, scale needs to be applied at a different level in the dom.
var flyoutSvg = this.workspace_.addFlyout_('g');
var background = this.workspace_.createDom('blocklyMutatorBackground');
background.appendChild(flyoutSvg);
// Insert the flyout after the <rect> but before the block canvas so that
// the flyout is underneath in z-order. This makes blocks layering during
// dragging work properly.
background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_);
this.svgDialog_.appendChild(background);
return this.svgDialog_;
@@ -285,7 +289,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
* @private
*/
Blockly.Mutator.prototype.workspaceChanged_ = function() {
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
if (!this.workspace_.isDragging()) {
var blocks = this.workspace_.getTopBlocks(false);
var MARGIN = 20;
for (var b = 0, block; block = blocks[b]; b++) {
@@ -316,7 +320,7 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
var newMutationDom = block.mutationToDom();
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
if (oldMutation != newMutation) {
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
block, 'mutation', null, oldMutation, newMutation));
// Ensure that any bump is part of this mutation's event group.
var group = Blockly.Events.getGroup();
@@ -329,7 +333,11 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
if (block.rendered) {
block.render();
}
this.resizeBubble_();
// Don't update the bubble until the drag has ended, to avoid moving blocks
// under the cursor.
if (!this.workspace_.isDragging()) {
this.resizeBubble_();
}
Blockly.Events.setGroup(false);
}
};
+22 -3
Ver Arquivo
@@ -37,6 +37,13 @@ goog.require('Blockly.Names');
goog.require('Blockly.Workspace');
/**
* Constant to separate procedure names from variables and generated functions
* when running generators.
* @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME
*/
Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME;
/**
* Find all user-created procedure definitions in a workspace.
* @param {!Blockly.Workspace} root Root workspace.
@@ -112,6 +119,18 @@ Blockly.Procedures.findLegalName = function(name, block) {
* @private
*/
Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude);
};
/**
* Return if the given name is already a procedure name.
* @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 used, otherwise return false.
*/
Blockly.Procedures.isNameUsed = 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++) {
@@ -121,11 +140,11 @@ Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
if (blocks[i].getProcedureDef) {
var procName = blocks[i].getProcedureDef();
if (Blockly.Names.equals(procName[0], name)) {
return false;
return true;
}
}
}
return true;
return false;
};
/**
@@ -271,7 +290,7 @@ Blockly.Procedures.mutateCallers = function(defBlock) {
// undo action since it is deterministically tied to the procedure's
// definition mutation.
Blockly.Events.recordUndo = false;
Blockly.Events.fire(new Blockly.Events.Change(
Blockly.Events.fire(new Blockly.Events.BlockChange(
caller, 'mutation', null, oldMutation, newMutation));
Blockly.Events.recordUndo = oldRecordUndo;
}
+25 -14
Ver Arquivo
@@ -38,15 +38,22 @@ goog.require('Blockly.Connection');
*/
Blockly.RenderedConnection = function(source, type) {
Blockly.RenderedConnection.superClass_.constructor.call(this, source, type);
/**
* Workspace units, (0, 0) is top left of block.
* @type {!goog.math.Coordinate}
* @private
*/
this.offsetInBlock_ = new goog.math.Coordinate(0, 0);
};
goog.inherits(Blockly.RenderedConnection, Blockly.Connection);
/**
* Returns the distance between this connection and another connection.
* Returns the distance between this connection and another connection in
* workspace units.
* @param {!Blockly.Connection} otherConnection The other connection to measure
* the distance to.
* @return {number} The distance between connections.
* @return {number} The distance between connections, in workspace units.
*/
Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
var xDiff = this.x_ - otherConnection.x_;
@@ -62,7 +69,7 @@ Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
* @private
*/
Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) {
if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
if (this.sourceBlock_.workspace.isDragging()) {
// Don't move blocks around while the user is doing the same.
return;
}
@@ -102,8 +109,8 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
/**
* Change the connection's coordinates.
* @param {number} x New absolute x coordinate.
* @param {number} y New absolute y coordinate.
* @param {number} x New absolute x coordinate, in workspace coordinates.
* @param {number} y New absolute y coordinate, in workspace coordinates.
*/
Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
// Remove it from its old location in the database (if already present)
@@ -120,8 +127,8 @@ Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
/**
* Change the connection's coordinates.
* @param {number} dx Change to x coordinate.
* @param {number} dy Change to y coordinate.
* @param {number} dx Change to x coordinate, in workspace units.
* @param {number} dy Change to y coordinate, in workspace units.
*/
Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) {
this.moveTo(this.x_ + dx, this.y_ + dy);
@@ -129,9 +136,9 @@ Blockly.RenderedConnection.prototype.moveBy = function(dx, 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.
* the location of the block's top left corner.
* @param {!goog.math.Coordinate} blockTL The location of the top left corner
* of the block, in workspace coordinates.
*/
Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) {
this.moveTo(blockTL.x + this.offsetInBlock_.x,
@@ -140,8 +147,8 @@ Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) {
/**
* 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.
* @param {number} x The new relative x, in workspace units.
* @param {number} y The new relative y, in workspace units.
*/
Blockly.RenderedConnection.prototype.setOffsetInBlock = function(x, y) {
this.offsetInBlock_.x = x;
@@ -161,6 +168,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
if (!svgRoot) {
throw 'block is not rendered.';
}
// Workspace coordinates.
var xy = Blockly.utils.getRelativeXY(svgRoot);
block.getSvgRoot().setAttribute('transform',
'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
@@ -170,6 +178,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
/**
* Find the closest compatible connection to this connection.
* All parameters are in workspace units.
* @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).
@@ -294,7 +303,8 @@ Blockly.RenderedConnection.prototype.hideAll = function() {
/**
* 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.
* @param {number} maxRadius The maximum radius allowed for connections, in
* workspace units.
* @return {boolean} True if the connection is allowed, false otherwise.
*/
Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
@@ -353,7 +363,8 @@ Blockly.RenderedConnection.prototype.respawnShadow_ = function() {
/**
* 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.
* @param {number} maxLimit The maximum radius to another connection, in
* workspace units.
* @return {!Array.<!Blockly.Connection>} List of connections.
* @private
*/
+58 -32
Ver Arquivo
@@ -30,6 +30,10 @@ goog.provide('Blockly.ScrollbarPair');
goog.require('goog.dom');
goog.require('goog.events');
/**
* A note on units: most of the numbers that are in CSS pixels are scaled if the
* scrollbar is in a mutator.
*/
/**
* Class for a pair of scrollbars. Horizontal and vertical.
@@ -133,7 +137,8 @@ Blockly.ScrollbarPair.prototype.resize = function() {
};
/**
* Set the sliders of both scrollbars to be at a certain position.
* Set the handles of both scrollbars to be at a certain position in CSS pixels
* relative to their parents.
* @param {number} x Horizontal scroll value.
* @param {number} y Vertical scroll value.
*/
@@ -196,7 +201,9 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) {
this.createDom_(opt_class);
/**
* The upper left corner of the scrollbar's svg group.
* The upper left corner of the scrollbar's svg group in CSS pixels relative
* to the scrollbar's origin. This is usually relative to the injection div
* origin.
* @type {goog.math.Coordinate}
* @private
*/
@@ -231,29 +238,44 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) {
this.onMouseDownHandleWrapper_ = Blockly.bindEventWithChecks_(this.svgHandle_,
'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
};
/**
* The coordinate of the upper left corner of the scrollbar SVG.
* @type {goog.math.Coordinate}
* @private
*/
* The location of the origin of the workspace that the scrollbar is in,
* measured in CSS pixels relative to the injection div origin. This is usually
* (0, 0). When the scrollbar is in a flyout it may have a different origin.
* @type {goog.math.Coordinate}
* @private
*/
Blockly.Scrollbar.prototype.origin_ = new goog.math.Coordinate(0, 0);
/**
* The size of the area within which the scrollbar handle can move.
* The position of the mouse along this scrollbar's major axis at the start of
* the most recent drag.
* Units are CSS pixels, with (0, 0) at the top left of the browser window.
* For a horizontal scrollbar this is the x coordinate of the mouse down event;
* for a vertical scrollbar it's the y coordinate of the mouse down event.
* @type {goog.math.Coordinate}
*/
Blockly.Scrollbar.prototype.startDragMouse_ = 0;
/**
* The size of the area within which the scrollbar handle can move, in CSS
* pixels.
* @type {number}
* @private
*/
Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
/**
* The length of the scrollbar handle.
* The length of the scrollbar handle in CSS pixels.
* @type {number}
* @private
*/
Blockly.Scrollbar.prototype.handleLength_ = 0;
/**
* The offset of the start of the handle from the start of the scrollbar range.
* The offset of the start of the handle from the scrollbar position, in CSS
* pixels.
* @type {number}
* @private
*/
@@ -274,9 +296,8 @@ Blockly.Scrollbar.prototype.isVisible_ = true;
Blockly.Scrollbar.prototype.containerVisible_ = 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).
* Width of vertical scrollbar or height of horizontal scrollbar in CSS pixels.
* Scrollbars should be larger on touch devices.
*/
Blockly.Scrollbar.scrollbarThickness = 15;
if (goog.events.BrowserFeature.TOUCH_ENABLED) {
@@ -334,7 +355,7 @@ Blockly.Scrollbar.prototype.dispose = function() {
/**
* Set the length of the scrollbar's handle and change the SVG attribute
* accordingly.
* @param {number} newLength The new scrollbar handle length.
* @param {number} newLength The new scrollbar handle length in CSS pixels.
*/
Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) {
this.handleLength_ = newLength;
@@ -342,9 +363,9 @@ Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) {
};
/**
* Set the offset of the scrollbar's handle and change the SVG attribute
* accordingly.
* @param {number} newPosition The new scrollbar handle offset.
* Set the offset of the scrollbar's handle from the scrollbar's position, and
* change the SVG attribute accordingly.
* @param {number} newPosition The new scrollbar handle offset in CSS pixels.
*/
Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
this.handlePosition_ = newPosition;
@@ -354,7 +375,7 @@ Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
/**
* Set the size of the scrollbar's background and change the SVG attribute
* accordingly.
* @param {number} newSize The new scrollbar background length.
* @param {number} newSize The new scrollbar background length in CSS pixels.
* @private
*/
Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) {
@@ -373,11 +394,13 @@ Blockly.ScrollbarPair.prototype.setContainerVisible = function(visible) {
};
/**
* Set the position of the scrollbar's svg group.
* Set the position of the scrollbar's svg group in CSS pixels relative to the
* scrollbar's origin. This sets the scrollbar's location within the workspace.
* @param {number} x The new x coordinate.
* @param {number} y The new y coordinate.
* @private
*/
Blockly.Scrollbar.prototype.setPosition = function(x, y) {
Blockly.Scrollbar.prototype.setPosition_ = function(x, y) {
this.position_.x = x;
this.position_.y = y;
@@ -465,7 +488,7 @@ Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
// 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);
this.setPosition_(xCoordinate, yCoordinate);
// If the view has been resized, a content resize will also be necessary. The
// reverse is not true.
@@ -532,7 +555,7 @@ Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
Blockly.Scrollbar.scrollbarThickness - 1;
}
var yCoordinate = hostMetrics.absoluteTop + 0.5;
this.setPosition(xCoordinate, yCoordinate);
this.setPosition_(xCoordinate, yCoordinate);
// If the view has been resized, a content resize will also be necessary. The
// reverse is not true.
@@ -725,7 +748,7 @@ Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
this.workspace_.setupDragSurface();
// Record the current mouse position.
this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY;
this.startDragMouse_ = this.horizontal_ ? e.clientX : e.clientY;
Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
'mouseup', this, this.onMouseUpHandle_);
Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document,
@@ -741,7 +764,7 @@ Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
*/
Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
var currentMouse = this.horizontal_ ? e.clientX : e.clientY;
var mouseDelta = currentMouse - this.startDragMouse;
var mouseDelta = currentMouse - this.startDragMouse_;
var handlePosition = this.startDragHandle + mouseDelta;
// Position the bar.
this.setHandlePosition(this.constrainHandle_(handlePosition));
@@ -779,8 +802,8 @@ Blockly.Scrollbar.prototype.cleanUp_ = function() {
/**
* 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.
* @param {number} value Value that is potentially out of bounds, in CSS pixels.
* @return {number} Constrained value, in CSS pixels.
* @private
*/
Blockly.Scrollbar.prototype.constrainHandle_ = function(value) {
@@ -811,8 +834,10 @@ Blockly.Scrollbar.prototype.onScroll_ = function() {
};
/**
* Set the scrollbar slider's position.
* @param {number} value The distance from the top/left end of the bar.
* Set the scrollbar handle's position.
* @param {number} value The distance from the top/left end of the bar, in CSS
* pixels. It may be larger than the maximum allowable position of the
* scrollbar handle.
*/
Blockly.Scrollbar.prototype.set = function(value) {
this.setHandlePosition(this.constrainHandle_(value * this.ratio_));
@@ -820,11 +845,12 @@ Blockly.Scrollbar.prototype.set = function(value) {
};
/**
* Set the origin of the upper left of the scrollbar. This if for times
* when the scrollbar is used in an object whose origin isn't the same
* as the main workspace (e.g. in a flyout.)
* @param {number} x The x coordinate of the scrollbar's origin.
* @param {number} y The y coordinate of the scrollbar's origin.
* Record the origin of the workspace that the scrollbar is in, in pixels
* relative to the injection div origin. This is for times when the scrollbar is
* used in an object whose origin isn't the same as the main workspace
* (e.g. in a flyout.)
* @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels.
* @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels.
*/
Blockly.Scrollbar.prototype.setOrigin = function(x, y) {
this.origin_ = new goog.math.Coordinate(x, y);
+35 -3
Ver Arquivo
@@ -27,7 +27,9 @@
goog.provide('Blockly.Toolbox');
goog.require('Blockly.Flyout');
goog.require('Blockly.HorizontalFlyout');
goog.require('Blockly.Touch');
goog.require('Blockly.VerticalFlyout');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events');
@@ -181,7 +183,12 @@ Blockly.Toolbox.prototype.init = function() {
* @type {!Blockly.Flyout}
* @private
*/
this.flyout_ = new Blockly.Flyout(workspaceOptions);
this.flyout_ = null;
if (workspace.horizontalLayout) {
this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions);
} else {
this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
}
goog.dom.insertSiblingAfter(this.flyout_.createDom('svg'),
this.workspace_.getParentSvg());
this.flyout_.init(workspace);
@@ -304,7 +311,11 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
}
switch (childIn.tagName.toUpperCase()) {
case 'CATEGORY':
var childOut = this.tree_.createNode(childIn.getAttribute('name'));
// Decode the category name for any potential message references
// (eg. `%{BKY_CATEGORY_NAME_LOGIC}`).
var categoryName = Blockly.utils.replaceMessageReferences(
childIn.getAttribute('name'));
var childOut = this.tree_.createNode(categoryName);
childOut.blocks = [];
treeOut.add(childOut);
var custom = childIn.getAttribute('custom');
@@ -317,7 +328,10 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
openNode = newOpenNode;
}
}
var colour = childIn.getAttribute('colour');
// Decode the colour for any potential message references
// (eg. `%{BKY_MATH_HUE}`).
var colour = Blockly.utils.replaceMessageReferences(
childIn.getAttribute('colour'));
if (goog.isString(colour)) {
if (colour.match(/^#[0-9a-fA-F]{6}$/)) {
childOut.hexColour = colour;
@@ -406,6 +420,24 @@ Blockly.Toolbox.prototype.clearSelection = function() {
this.tree_.setSelectedItem(null);
};
/**
* Adds styles on the toolbox indicating blocks will be deleted.
* @package
*/
Blockly.Toolbox.prototype.addDeleteStyle = function() {
Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv),
'blocklyToolboxDelete');
};
/**
* Remove styles from the toolbox that indicate blocks will be deleted.
* @package
*/
Blockly.Toolbox.prototype.removeDeleteStyle = function() {
Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv),
'blocklyToolboxDelete');
};
/**
* Return the deletion rectangle for this toolbox.
* @return {goog.math.Rect} Rectangle in which to delete.
+42 -3
Ver Arquivo
@@ -44,6 +44,13 @@ goog.require('goog.dom.TagName');
*/
Blockly.Tooltip.visible = false;
/**
* Is someone else blocking the tooltip from being shown?
* @type {boolean}
* @private
*/
Blockly.Tooltip.blocked_ = false;
/**
* Maximum width (in characters) of a tooltip.
*/
@@ -153,6 +160,10 @@ Blockly.Tooltip.bindMouseEvents = function(element) {
* @private
*/
Blockly.Tooltip.onMouseOver_ = function(e) {
if (Blockly.Tooltip.blocked_) {
// Someone doesn't want us to show tooltips.
return;
}
// If the tooltip is an object, treat it as a pointer to the next object in
// the chain to look at. Terminate when a string or function is found.
var element = e.target;
@@ -174,6 +185,10 @@ Blockly.Tooltip.onMouseOver_ = function(e) {
* @private
*/
Blockly.Tooltip.onMouseOut_ = function(e) {
if (Blockly.Tooltip.blocked_) {
// Someone doesn't want us to show tooltips.
return;
}
// Moving from one element to another (overlapping or with no gap) generates
// a mouseOut followed instantly by a mouseOver. Fork off the mouseOut
// event and kill it if a mouseOver is received immediately.
@@ -196,12 +211,13 @@ Blockly.Tooltip.onMouseMove_ = function(e) {
if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) {
// No tooltip here to show.
return;
} else if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
// Don't display a tooltip during a drag.
return;
} else if (Blockly.WidgetDiv.isVisible()) {
// Don't display a tooltip if a widget is open (tooltip would be under it).
return;
} else if (Blockly.Tooltip.blocked_) {
// Someone doesn't want us to show tooltips. We are probably handling a
// user gesture, such as a click or drag.
return;
}
if (Blockly.Tooltip.visible) {
// Compute the distance between the mouse position when the tooltip was
@@ -235,11 +251,34 @@ Blockly.Tooltip.hide = function() {
clearTimeout(Blockly.Tooltip.showPid_);
};
/**
* Hide any in-progress tooltips and block showing new tooltips until the next
* call to unblock().
* @package
*/
Blockly.Tooltip.block = function() {
Blockly.Tooltip.hide();
Blockly.Tooltip.blocked_ = true;
};
/**
* Unblock tooltips: allow them to be scheduled and shown according to their own
* logic.
* @package
*/
Blockly.Tooltip.unblock = function() {
Blockly.Tooltip.blocked_ = false;
};
/**
* Create the tooltip and show it.
* @private
*/
Blockly.Tooltip.show_ = function() {
if (Blockly.Tooltip.blocked_) {
// Someone doesn't want us to show tooltips.
return;
}
Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_;
if (!Blockly.Tooltip.DIV) {
return;
+23 -76
Ver Arquivo
@@ -41,13 +41,6 @@ goog.require('goog.string');
*/
Blockly.Touch.touchIdentifier_ = null;
/**
* Wrapper function called when a touch mouseUp occurs during a drag operation.
* @type {Array.<!Array>}
* @private
*/
Blockly.Touch.onTouchUpWrapper_ = null;
/**
* The TOUCH_MAP lookup dictionary specifies additional touch events to fire,
* in conjunction with mouse events.
@@ -75,11 +68,10 @@ Blockly.longPid_ = 0;
* 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.
* @param {Blockly.Gesture} gesture The gesture that triggered this longStart.
* @private
*/
Blockly.longStart_ = function(e, uiObject) {
Blockly.longStart_ = function(e, gesture) {
Blockly.longStop_();
// Punt on multitouch events.
if (e.changedTouches.length != 1) {
@@ -90,7 +82,12 @@ Blockly.longStart_ = function(e, uiObject) {
// e was a touch event. It needs to pretend to be a mouse event.
e.clientX = e.changedTouches[0].clientX;
e.clientY = e.changedTouches[0].clientY;
uiObject.onMouseDown_(e);
// Let the gesture route the right-click correctly.
if (gesture) {
gesture.handleRightClick(e);
}
}, Blockly.LONGPRESS);
};
@@ -106,67 +103,6 @@ Blockly.longStop_ = function() {
}
};
/**
* Handle a mouse-up anywhere on the page.
* @param {!Event} e Mouse up event.
* @private
*/
Blockly.onMouseUp_ = function(e) {
var workspace = Blockly.getMainWorkspace();
if (workspace.dragMode_ == Blockly.DRAG_NONE) {
return;
}
Blockly.Touch.clearTouchIdentifier();
// TODO(#781): Check whether this needs to be called for all drag modes.
workspace.resetDragSurface();
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
workspace.dragMode_ = Blockly.DRAG_NONE;
// Unbind the touch event if it exists.
if (Blockly.Touch.onTouchUpWrapper_) {
Blockly.unbindEvent_(Blockly.Touch.onTouchUpWrapper_);
Blockly.Touch.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) {
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();
}
};
/**
* Clear the touch identifier that tracks which touch stream to pay attention
* to. This ends the current drag/gesture and allows other pointers to be
@@ -189,6 +125,20 @@ Blockly.Touch.shouldHandleEvent = function(e) {
Blockly.Touch.checkTouchIdentifier(e);
};
/**
* Get the touch identifier from the given event. If it was a mouse event, the
* identifier is the string 'mouse'.
* @param {!Event} e Mouse event or touch event.
* @return {string} The touch identifier from the first changed touch, if
* defined. Otherwise 'mouse'.
*/
Blockly.Touch.getTouchIdentifierFromEvent = function(e) {
return (e.changedTouches && e.changedTouches[0] &&
e.changedTouches[0].identifier != undefined &&
e.changedTouches[0].identifier != null) ?
e.changedTouches[0].identifier : 'mouse';
};
/**
* Check whether the touch identifier on the event matches the current saved
* identifier. If there is no identifier, that means it's a mouse event and
@@ -202,10 +152,7 @@ Blockly.Touch.shouldHandleEvent = function(e) {
* saved identifier.
*/
Blockly.Touch.checkTouchIdentifier = function(e) {
var identifier = (e.changedTouches && e.changedTouches[0] &&
e.changedTouches[0].identifier != undefined &&
e.changedTouches[0].identifier != null) ?
e.changedTouches[0].identifier : 'mouse';
var identifier = Blockly.Touch.getTouchIdentifierFromEvent(e);
// if (Blockly.touchIdentifier_ )is insufficient because android touch
// identifiers may be zero.
+3 -2
Ver Arquivo
@@ -166,8 +166,9 @@ Blockly.Trashcan.prototype.createDom = function() {
*/
this.svgGroup_ = Blockly.utils.createSvgElement('g',
{'class': 'blocklyTrash'}, null);
var clip;
var rnd = String(Math.random()).substring(2);
var clip = Blockly.utils.createSvgElement('clipPath',
clip = Blockly.utils.createSvgElement('clipPath',
{'id': 'blocklyTrashBodyClipPath' + rnd},
this.svgGroup_);
Blockly.utils.createSvgElement('rect',
@@ -182,7 +183,7 @@ Blockly.Trashcan.prototype.createDom = function() {
body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
this.workspace_.options.pathToMedia + Blockly.SPRITE.url);
var clip = Blockly.utils.createSvgElement('clipPath',
clip = Blockly.utils.createSvgElement('clipPath',
{'id': 'blocklyTrashLidClipPath' + rnd},
this.svgGroup_);
Blockly.utils.createSvgElement('rect',
+18 -4
Ver Arquivo
@@ -38,6 +38,20 @@ goog.require('goog.events.BrowserFeature');
goog.require('goog.math.Coordinate');
goog.require('goog.userAgent');
/**
* To allow ADVANCED_OPTIMIZATIONS, combining variable.name and variable['name']
* is not possible. To access the exported Blockly.Msg.Something it needs to be
* accessed through the exact name that was exported. Note, that all the exports
* are happening as the last thing in the generated js files, so they won't be
* accessible before javascript loads!
* @return {!Object<string, string>}
* @private
*/
Blockly.utils.getMessageArray_ = function() {
return goog.global['Blockly']['Msg'];
};
/**
* Remove an attribute from a element even if it's in IE 10.
* Similar to Element.removeAttribute() but it works on SVG elements in IE 10.
@@ -197,7 +211,7 @@ Blockly.utils.getInjectionDivXY_ = function(element) {
var scale = 1;
while (element) {
var xy = Blockly.utils.getRelativeXY(element);
var scale = Blockly.utils.getScale_(element);
scale = Blockly.utils.getScale_(element);
x = (x * scale) + xy.x;
y = (y * scale) + xy.y;
var classes = element.getAttribute('class') || '';
@@ -457,7 +471,7 @@ Blockly.utils.checkMessageReferences = function(message) {
var match = regex.exec(message);
while (match != null) {
var msgKey = match[1];
if (Blockly.Msg[msgKey] == null) {
if (Blockly.utils.getMessageArray_()[msgKey] == null) {
console.log('WARNING: No message string for %{BKY_' + msgKey + '}.');
isValid = false;
}
@@ -550,8 +564,8 @@ Blockly.utils.tokenizeInterpolation_ = function(message, parseInterpolationToken
// are defined in ../msgs/ files.
var bklyKey = goog.string.startsWith(keyUpper, 'BKY_') ?
keyUpper.substring(4) : null;
if (bklyKey && bklyKey in Blockly.Msg) {
var rawValue = Blockly.Msg[bklyKey];
if (bklyKey && bklyKey in Blockly.utils.getMessageArray_()) {
var rawValue = Blockly.utils.getMessageArray_()[bklyKey];
if (goog.isString(rawValue)) {
// Attempt to dereference substrings, too, appending to the end.
Array.prototype.push.apply(tokens,
+238
Ver Arquivo
@@ -0,0 +1,238 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 map of variables and their types.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.provide('Blockly.VariableMap');
/**
* Class for a variable map. This contains a dictionary data structure with
* variable types as keys and lists of variables as values. The list of
* variables are the type indicated by the key.
* @param {!Blockly.Workspace} workspace The workspace this map belongs to.
* @constructor
*/
Blockly.VariableMap = function(workspace) {
/**
* A map from variable type to list of variable names. The lists contain all
* of the named variables in the workspace, including variables
* that are not currently in use.
* @type {!Object<string, !Array.<Blockly.VariableModel>>}
* @private
*/
this.variableMap_ = {};
/**
* The workspace this map belongs to.
* @type {!Blockly.Workspace}
*/
this.workspace = workspace;
};
/**
* Clear the variable map.
*/
Blockly.VariableMap.prototype.clear = function() {
this.variableMap_ = new Object(null);
};
/**
* Rename the given variable by updating its name in the variable map.
* @param {?Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.VariableMap.prototype.renameVariable = function(variable, newName) {
var newVariable = this.getVariable(newName);
var variableIndex = -1;
var newVariableIndex = -1;
var type = '';
if (variable || newVariable) {
type = (variable || newVariable).type;
}
var variableList = this.getVariablesOfType(type);
if (variable) {
variableIndex = variableList.indexOf(variable);
}
if (newVariable){ // see if I can get rid of newVariable dependency
newVariableIndex = variableList.indexOf(newVariable);
}
if (variableIndex == -1 && newVariableIndex == -1) {
this.createVariable(newName, '');
console.log('Tried to rename an non-existent variable.');
} else if (variableIndex == newVariableIndex ||
variableIndex != -1 && newVariableIndex == -1) {
// Only changing case, or renaming to a completely novel name.
var variableToRename = this.variableMap_[type][variableIndex];
Blockly.Events.fire(new Blockly.Events.VarRename(variableToRename,
newName));
variableToRename.name = newName;
} else if (variableIndex != -1 && newVariableIndex != -1) {
// Renaming one existing variable to another existing variable.
// The case might have changed, so we update the destination ID.
var variableToRename = this.variableMap_[type][newVariableIndex];
Blockly.Events.fire(new Blockly.Events.VarRename(variableToRename,
newName));
var variableToDelete = this.variableMap_[type][variableIndex];
Blockly.Events.fire(new Blockly.Events.VarDelete(variableToDelete));
variableToRename.name = newName;
this.variableMap_[type].splice(variableIndex, 1);
}
};
/**
* Create a variable with a given name, optional type, and optional id.
* @param {!string} name The name of the variable. This must be unique across
* variables and procedures.
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
* Does not need to be unique. Field_variable can filter variables based on
* their type. This will default to '' which is a specific type.
* @param {?string} opt_id The unique id of the variable. This will default to
* a UUID.
* @return {?Blockly.VariableModel} The newly created variable.
*/
Blockly.VariableMap.prototype.createVariable = function(name, opt_type, opt_id) {
var variable = this.getVariable(name);
if (variable) {
if (opt_type && variable.type != opt_type) {
throw Error('Variable "' + name + '" is already in use and its type is "'
+ variable.type + '" which conflicts with the passed in ' +
'type, "' + opt_type + '".');
}
if (opt_id && variable.getId() != opt_id) {
throw Error('Variable "' + name + '" is already in use and its id is "'
+ variable.getId() + '" which conflicts with the passed in ' +
'id, "' + opt_id + '".');
}
// The variable already exists and has the same id and type.
return variable;
}
if (opt_id && this.getVariableById(opt_id)) {
throw Error('Variable id, "' + opt_id + '", is already in use.');
}
opt_id = opt_id || Blockly.utils.genUid();
opt_type = opt_type || '';
variable = new Blockly.VariableModel(this.workspace, name, opt_type, opt_id);
// If opt_type is not a key, create a new list.
if (!this.variableMap_[opt_type]) {
this.variableMap_[opt_type] = [variable];
} else {
// Else append the variable to the preexisting list.
this.variableMap_[opt_type].push(variable);
}
return variable;
};
/**
* Delete a variable.
* @param {Blockly.VariableModel} variable Variable to delete.
*/
Blockly.VariableMap.prototype.deleteVariable = function(variable) {
var variableList = this.variableMap_[variable.type];
for (var i = 0, tempVar; tempVar = variableList[i]; i++) {
if (tempVar.getId() == variable.getId()) {
variableList.splice(i, 1);
Blockly.Events.fire(new Blockly.Events.VarDelete(variable));
return;
}
}
};
/**
* Find the variable by the given name and return it. Return null if it is not
* found.
* @param {!string} name The name to check for.
* @return {?Blockly.VariableModel} The variable with the given name, or null if
* it was not found.
*/
Blockly.VariableMap.prototype.getVariable = function(name) {
var keys = Object.keys(this.variableMap_);
for (var i = 0; i < keys.length; i++ ) {
var key = keys[i];
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
if (Blockly.Names.equals(variable.name, name)) {
return variable;
}
}
}
return null;
};
/**
* Find the variable by the given id and return it. Return null if it is not
* found.
* @param {!string} id The id to check for.
* @return {?Blockly.VariableModel} The variable with the given id.
*/
Blockly.VariableMap.prototype.getVariableById = function(id) {
var keys = Object.keys(this.variableMap_);
for (var i = 0; i < keys.length; i++ ) {
var key = keys[i];
for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) {
if (variable.getId() == id) {
return variable;
}
}
}
return null;
};
/**
* Get a list containing all of the variables of a specified type. If type is
* null, return list of variables with empty string type.
* @param {?string} type Type of the variables to find.
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
* passed in type. An empty array if none are found.
*/
Blockly.VariableMap.prototype.getVariablesOfType = function(type) {
type = type || '';
var variable_list = this.variableMap_[type];
if (variable_list) {
return variable_list.slice();
}
return [];
};
/**
* Return all variable types.
* @return {!Array.<string>} List of variable types.
*/
Blockly.VariableMap.prototype.getVariableTypes = function() {
return Object.keys(this.variableMap_);
};
/**
* Return all variables of all types.
* @return {!Array.<Blockly.VariableModel>} List of variable models.
*/
Blockly.VariableMap.prototype.getAllVariables = function() {
var all_variables = [];
var keys = Object.keys(this.variableMap_);
for (var i = 0; i < keys.length; i++ ) {
all_variables = all_variables.concat(this.variableMap_[keys[i]]);
}
return all_variables;
};
+99
Ver Arquivo
@@ -0,0 +1,99 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 the variable model.
* @author marisaleung@google.com (Marisa Leung)
*/
'use strict';
goog.provide('Blockly.VariableModel');
goog.require('goog.string');
/**
* Class for a variable model.
* Holds information for the variable including name, id, and type.
* @param {!Blockly.Workspace} workspace The variable's workspace.
* @param {!string} name The name of the variable. This must be unique across
* variables and procedures.
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
* Does not need to be unique. Field_variable can filter variables based on
* their type. This will default to '' which is a specific type.
* @param {?string} opt_id The unique id of the variable. This will default to
* a UUID.
* @see {Blockly.FieldVariable}
* @constructor
*/
Blockly.VariableModel = function(workspace, name, opt_type, opt_id) {
/**
* The workspace the variable is in.
* @type {!Blockly.Workspace}
*/
this.workspace = workspace;
/**
* The name of the variable, typically defined by the user. It must be
* unique across all names used for procedures and variables. It may be
* changed by the user.
* @type {string}
*/
this.name = name;
/**
* The type of the variable, such as 'int' or 'sound_effect'. This may be
* used to build a list of variables of a specific type. By default this is
* the empty string '', which is a specific type.
* @see {Blockly.FieldVariable}
* @type {string}
*/
this.type = opt_type || '';
/**
* A unique id for the variable. This should be defined at creation and
* not change, even if the name changes. In most cases this should be a
* UUID.
* @type {string}
* @private
*/
this.id_ = opt_id || Blockly.utils.genUid();
Blockly.Events.fire(new Blockly.Events.VarCreate(this));
};
/**
* @return {!string} The id for the variable.
*/
Blockly.VariableModel.prototype.getId = function() {
return this.id_;
};
/**
* A custom compare function for the VariableModel objects.
* @param {Blockly.VariableModel} var1 First variable to compare.
* @param {Blockly.VariableModel} var2 Second variable to compare.
* @return {number} -1 if name of var1 is less than name of var2, 0 if equal,
* and 1 if greater.
* @package
*/
Blockly.VariableModel.compareByName = function(var1, var2) {
return goog.string.caseInsensitiveCompare(var1.name, var2.name);
};
+121 -18
Ver Arquivo
@@ -32,10 +32,18 @@ goog.provide('Blockly.Variables');
goog.require('Blockly.Blocks');
goog.require('Blockly.constants');
goog.require('Blockly.VariableModel');
goog.require('Blockly.Workspace');
goog.require('goog.string');
/**
* Constant to separate variable names from procedures and generated functions
* when running generators.
* @deprecated Use Blockly.VARIABLE_CATEGORY_NAME
*/
Blockly.Variables.NAME_TYPE = Blockly.VARIABLE_CATEGORY_NAME;
/**
* Find all user-created variables that are in use in the workspace.
* For use by generators.
@@ -79,7 +87,7 @@ Blockly.Variables.allUsedVariables = function(root) {
* Find all variables that the user has created through the workspace or
* toolbox. For use by generators.
* @param {!Blockly.Workspace} root The workspace to inspect.
* @return {!Array.<string>} Array of variable names.
* @return {!Array.<Blockly.VariableModel>} Array of variable models.
*/
Blockly.Variables.allVariables = function(root) {
if (root instanceof Blockly.Block) {
@@ -87,19 +95,18 @@ Blockly.Variables.allVariables = function(root) {
console.warn('Deprecated call to Blockly.Variables.allVariables ' +
'with a block instead of a workspace. You may want ' +
'Blockly.Variables.allUsedVariables');
return {};
}
return root.variableList;
return root.getAllVariables();
};
/**
* Construct the blocks required by the flyout for the variable category.
* Construct the elements (blocks and button) required by the flyout for the
* variable category.
* @param {!Blockly.Workspace} workspace The workspace contianing variables.
* @return {!Array.<!Element>} Array of XML block elements.
* @return {!Array.<!Element>} Array of XML elements.
*/
Blockly.Variables.flyoutCategory = function(workspace) {
var variableList = workspace.variableList;
variableList.sort(goog.string.caseInsensitiveCompare);
var xmlList = [];
var button = goog.dom.createDom('button');
button.setAttribute('text', Blockly.Msg.NEW_VARIABLE);
@@ -111,12 +118,28 @@ Blockly.Variables.flyoutCategory = function(workspace) {
xmlList.push(button);
if (variableList.length > 0) {
var blockList = Blockly.Variables.flyoutCategoryBlocks(workspace);
xmlList = xmlList.concat(blockList);
return xmlList;
};
/**
* Construct the blocks required by the flyout for the variable category.
* @param {!Blockly.Workspace} workspace The workspace contianing variables.
* @return {!Array.<!Element>} Array of XML block elements.
*/
Blockly.Variables.flyoutCategoryBlocks = function(workspace) {
var variableModelList = workspace.getVariablesOfType('');
variableModelList.sort(Blockly.VariableModel.compareByName);
var xmlList = [];
if (variableModelList.length > 0) {
var firstVariable = variableModelList[0];
if (Blockly.Blocks['variables_set']) {
var gap = Blockly.Blocks['math_change'] ? 8 : 24;
var blockText = '<xml>' +
'<block type="variables_set" gap="' + gap + '">' +
'<field name="VAR">' + variableList[0] + '</field>' +
Blockly.Variables.generateVariableFieldXml_(firstVariable) +
'</block>' +
'</xml>';
var block = Blockly.Xml.textToDom(blockText).firstChild;
@@ -126,7 +149,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
var gap = Blockly.Blocks['variables_get'] ? 20 : 8;
var blockText = '<xml>' +
'<block type="math_change" gap="' + gap + '">' +
'<field name="VAR">' + variableList[0] + '</field>' +
Blockly.Variables.generateVariableFieldXml_(firstVariable) +
'<value name="DELTA">' +
'<shadow type="math_number">' +
'<field name="NUM">1</field>' +
@@ -138,11 +161,11 @@ Blockly.Variables.flyoutCategory = function(workspace) {
xmlList.push(block);
}
for (var i = 0; i < variableList.length; i++) {
for (var i = 0, variable; variable = variableModelList[i]; i++) {
if (Blockly.Blocks['variables_get']) {
var blockText = '<xml>' +
'<block type="variables_get" gap="8">' +
'<field name="VAR">' + variableList[i] + '</field>' +
Blockly.Variables.generateVariableFieldXml_(variable) +
'</block>' +
'</xml>';
var block = Blockly.Xml.textToDom(blockText).firstChild;
@@ -162,7 +185,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
* @return {string} New variable name.
*/
Blockly.Variables.generateUniqueName = function(workspace) {
var variableList = workspace.variableList;
var variableList = workspace.getAllVariables();
var newName = '';
if (variableList.length) {
var nameSuffix = 1;
@@ -172,7 +195,7 @@ Blockly.Variables.generateUniqueName = function(workspace) {
while (!newName) {
var inUse = false;
for (var i = 0; i < variableList.length; i++) {
if (variableList[i].toLowerCase() == potName) {
if (variableList[i].name.toLowerCase() == potName) {
// This potential name is already used.
inUse = true;
break;
@@ -209,20 +232,31 @@ Blockly.Variables.generateUniqueName = function(workspace) {
* @param {function(?string=)=} opt_callback A callback. It will
* be passed an acceptable new variable name, or null if change is to be
* aborted (cancel button), or undefined if an existing variable was chosen.
* @param {?string} opt_type The type of the variable like 'int', 'string', or
* ''. This will default to '', which is a specific type.
*/
Blockly.Variables.createVariable = function(workspace, opt_callback) {
Blockly.Variables.createVariable = function(workspace, opt_callback, opt_type) {
// This function needs to be named so it can be called recursively.
var promptAndCheckWithAlert = function(defaultName) {
Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName,
function(text) {
if (text) {
if (workspace.variableIndexOf(text) != -1) {
if (workspace.getVariable(text)) {
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()),
function() {
promptAndCheckWithAlert(text); // Recurse
});
} else {
workspace.createVariable(text);
}
else if (Blockly.Procedures.isNameUsed(text, workspace)) {
Blockly.alert(Blockly.Msg.PROCEDURE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()),
function() {
promptAndCheckWithAlert(text); // Recurse
});
}
else {
workspace.createVariable(text, opt_type);
if (opt_callback) {
opt_callback(text);
}
@@ -238,6 +272,55 @@ Blockly.Variables.createVariable = function(workspace, opt_callback) {
promptAndCheckWithAlert('');
};
/**
* Rename a variable with the given workspace, variableType, and oldName.
* @param {!Blockly.Workspace} workspace The workspace on which to rename the
* variable.
* @param {?Blockly.VariableModel} variable Variable to rename.
* @param {function(?string=)=} opt_callback A callback. It will
* be passed an acceptable new variable name, or null if change is to be
* aborted (cancel button), or undefined if an existing variable was chosen.
*/
Blockly.Variables.renameVariable = function(workspace, variable,
opt_callback) {
// This function needs to be named so it can be called recursively.
var promptAndCheckWithAlert = function(defaultName) {
Blockly.Variables.promptName(
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name), defaultName,
function(newName) {
if (newName) {
var newVariable = workspace.getVariable(newName);
if (newVariable && newVariable.type != variable.type) {
Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE.replace('%1',
newName.toLowerCase()).replace('%2', newVariable.type),
function() {
promptAndCheckWithAlert(newName); // Recurse
});
}
else if (Blockly.Procedures.isNameUsed(newName, workspace)) {
Blockly.alert(Blockly.Msg.PROCEDURE_ALREADY_EXISTS.replace('%1',
newName.toLowerCase()),
function() {
promptAndCheckWithAlert(newName); // Recurse
});
}
else {
workspace.renameVariable(variable.name, newName);
if (opt_callback) {
opt_callback(newName);
}
}
} else {
// User canceled prompt without a value.
if (opt_callback) {
opt_callback(null);
}
}
});
};
promptAndCheckWithAlert('');
};
/**
* Prompt the user for a new variable name.
* @param {string} promptText The string of the prompt.
@@ -260,3 +343,23 @@ Blockly.Variables.promptName = function(promptText, defaultText, callback) {
callback(newVar);
});
};
/**
* Generate XML string for variable field.
* @param {!Blockly.VariableModel} variableModel The variable model to generate
* an XML string from.
* @return {string} The generated XML.
* @private
*/
Blockly.Variables.generateVariableFieldXml_ = function(variableModel) {
// The variable name may be user input, so it may contain characters that need
// to be escaped to create valid XML.
var element = goog.dom.createDom('field');
element.setAttribute('name', 'VAR');
element.setAttribute('variableType', variableModel.type);
element.setAttribute('id', variableModel.getId());
element.textContent = variableModel.name;
var xmlString = Blockly.Xml.domToText(element);
return xmlString;
};
+192 -86
Ver Arquivo
@@ -26,6 +26,7 @@
goog.provide('Blockly.Workspace');
goog.require('Blockly.VariableMap');
goog.require('goog.array');
goog.require('goog.math');
@@ -74,12 +75,15 @@ Blockly.Workspace = function(opt_options) {
* @private
*/
this.blockDB_ = Object.create(null);
/*
* @type {!Array.<string>}
* A list of all of the named variables in the workspace, including variables
/**
* @type {!Blockly.VariableMap}
* A map from variable type to list of variable names. The lists contain all
* of the named variables in the workspace, including variables
* that are not currently in use.
* @private
*/
this.variableList = [];
this.variableMap_ = new Blockly.VariableMap(this);
};
/**
@@ -115,19 +119,20 @@ Blockly.Workspace.SCAN_ANGLE = 3;
/**
* Add a block to the list of top blocks.
* @param {!Blockly.Block} block Block to remove.
* @param {!Blockly.Block} block Block to add.
*/
Blockly.Workspace.prototype.addTopBlock = function(block) {
this.topBlocks_.push(block);
if (this.isFlyout) {
// This is for the (unlikely) case where you have a variable in a block in
// an always-open flyout. It needs to be possible to edit the block in the
// flyout, so the contents of the dropdown need to be correct.
var variables = Blockly.Variables.allUsedVariables(block);
for (var i = 0; i < variables.length; i++) {
if (this.variableList.indexOf(variables[i]) == -1) {
this.variableList.push(variables[i]);
}
if (!this.isFlyout) {
return;
}
// This is for the (unlikely) case where you have a variable in a block in
// an always-open flyout. It needs to be possible to edit the block in the
// flyout, so the contents of the dropdown need to be correct.
var variableNames = Blockly.Variables.allUsedVariables(block);
for (var i = 0, name; name = variableNames[i]; i++) {
if (!this.getVariable(name)) {
this.createVariable(name);
}
}
};
@@ -191,85 +196,117 @@ Blockly.Workspace.prototype.clear = function() {
if (!existingGroup) {
Blockly.Events.setGroup(false);
}
this.variableList.length = 0;
this.variableMap_.clear();
};
/**
* Walk the workspace and update the list of variables to only contain ones in
* Walk the workspace and update the map of variables to only contain ones in
* use on the workspace. Use when loading new workspaces from disk.
* @param {boolean} clearList True if the old variable list should be cleared.
* @param {boolean} clear True if the old variable map should be cleared.
*/
Blockly.Workspace.prototype.updateVariableList = function(clearList) {
Blockly.Workspace.prototype.updateVariableStore = function(clear) {
// TODO: Sort
if (!this.isFlyout) {
// Update the list in place so that the flyout's references stay correct.
if (clearList) {
this.variableList.length = 0;
if (this.isFlyout) {
return;
}
var variableNames = Blockly.Variables.allUsedVariables(this);
var varList = [];
for (var i = 0, name; name = variableNames[i]; i++) {
// Get variable model with the used variable name.
var tempVar = this.getVariable(name);
if (tempVar) {
varList.push({'name': tempVar.name, 'type': tempVar.type,
'id': tempVar.getId()});
}
var allVariables = Blockly.Variables.allUsedVariables(this);
for (var i = 0; i < allVariables.length; i++) {
this.createVariable(allVariables[i]);
else {
varList.push({'name': name, 'type': null, 'id': null});
// TODO(marisaleung): Use variable.type and variable.getId() once variable
// instances are storing more than just name.
}
}
if (clear) {
this.variableMap_.clear();
}
// Update the list in place so that the flyout's references stay correct.
for (var i = 0, varDict; varDict = varList[i]; i++) {
if (!this.getVariable(varDict.name)) {
this.createVariable(varDict.name, varDict.type, varDict.id);
}
}
};
/**
* Rename a variable by updating its name in the variable list.
* TODO: #468
* @param {string} oldName Variable to rename.
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given variable.
* @param {?Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
// Find the old name in the list.
var variableIndex = this.variableIndexOf(oldName);
var newVariableIndex = this.variableIndexOf(newName);
Blockly.Workspace.prototype.renameVariableInternal_ = function(variable, newName) {
var newVariable = this.getVariable(newName);
var oldCase;
// We might be renaming to an existing name but with different case. If so,
// we will also update all of the blocks using the new name to have the
// correct case.
if (newVariableIndex != -1 &&
this.variableList[newVariableIndex] != newName) {
var oldCase = this.variableList[newVariableIndex];
// If they are different types, throw an error.
if (variable && newVariable && variable.type != newVariable.type) {
throw Error('Variable "' + variable.name + '" is type "' + variable.type +
'" and variable "' + newName + '" is type "' + newVariable.type +
'". Both must be the same type.');
}
// Find if newVariable case is different.
if (newVariable && newVariable.name != newName) {
oldCase = newVariable.name;
}
Blockly.Events.setGroup(true);
var blocks = this.getAllBlocks();
// Iterate through every block.
// Iterate through every block and update name.
for (var i = 0; i < blocks.length; i++) {
blocks[i].renameVar(oldName, newName);
blocks[i].renameVar(variable.name, newName);
if (oldCase) {
blocks[i].renameVar(oldCase, newName);
}
}
this.variableMap_.renameVariable(variable, newName);
Blockly.Events.setGroup(false);
};
if (variableIndex == newVariableIndex ||
variableIndex != -1 && newVariableIndex == -1) {
// Only changing case, or renaming to a completely novel name.
this.variableList[variableIndex] = newName;
} else if (variableIndex != -1 && newVariableIndex != -1) {
// Renaming one existing variable to another existing variable.
// The case might have changed, so we update the destination ID.
this.variableList[newVariableIndex] = newName;
this.variableList.splice(variableIndex, 1);
} else {
this.variableList.push(newName);
console.log('Tried to rename an non-existent variable.');
}
/**
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given name.
* @param {string} oldName Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
// Warning: Prefer to use renameVariableById.
var variable = this.getVariable(oldName);
this.renameVariableInternal_(variable, newName);
};
/**
* Create a variable with the given name.
* TODO: #468
* @param {string} name The new variable's name.
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given id.
* @param {string} id Id of the variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.createVariable = function(name) {
var index = this.variableIndexOf(name);
if (index == -1) {
this.variableList.push(name);
}
Blockly.Workspace.prototype.renameVariableById = function(id, newName) {
var variable = this.getVariableById(id);
this.renameVariableInternal_(variable, newName);
};
/**
* Create a variable with a given name, optional type, and optional id.
* @param {!string} name The name of the variable. This must be unique across
* variables and procedures.
* @param {?string} opt_type The type of the variable like 'int' or 'string'.
* Does not need to be unique. Field_variable can filter variables based on
* their type. This will default to '' which is a specific type.
* @param {?string} opt_id The unique id of the variable. This will default to
* a UUID.
* @return {?Blockly.VariableModel} The newly created variable.
*/
Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) {
return this.variableMap_.createVariable(name, opt_type, opt_id);
};
/**
@@ -287,7 +324,7 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
for (var j = 0; j < blockVariables.length; j++) {
var varName = blockVariables[j];
// Variable name may be null if the block is only half-built.
if (varName && Blockly.Names.equals(varName, name)) {
if (varName && name && Blockly.Names.equals(varName, name)) {
uses.push(blocks[i]);
}
}
@@ -297,14 +334,11 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
};
/**
* Delete a variables and all of its uses from this workspace.
* Delete a variable by the passed in name and all of its uses from this
* workspace. May prompt the user for confirmation.
* @param {string} name Name of variable to delete.
*/
Blockly.Workspace.prototype.deleteVariable = function(name) {
var variableIndex = this.variableIndexOf(name);
if (variableIndex == -1) {
return;
}
// Check whether this variable is a function parameter before deleting.
var uses = this.getVariableUses(name);
for (var i = 0, block; block = uses[i]; i++) {
@@ -320,14 +354,7 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
}
var workspace = this;
function doDeletion() {
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
}
Blockly.Events.setGroup(false);
workspace.variableList.splice(variableIndex, 1);
}
var variable = workspace.getVariable(name);
if (uses.length > 1) {
// Confirm before deleting multiple blocks.
Blockly.confirm(
@@ -335,31 +362,79 @@ Blockly.Workspace.prototype.deleteVariable = function(name) {
replace('%2', name),
function(ok) {
if (ok) {
doDeletion();
workspace.deleteVariableInternal_(variable);
}
});
} else {
// No confirmation necessary for a single block.
doDeletion();
this.deleteVariableInternal_(variable);
}
};
/**
* Delete a variables by the passed in id and all of its uses from this
* workspace. May prompt the user for confirmation.
* @param {string} id Id of variable to delete.
*/
Blockly.Workspace.prototype.deleteVariableById = function(id) {
var variable = this.getVariableById(id);
if (variable) {
this.deleteVariableInternal_(variable);
} else {
console.warn("Can't delete non-existant variable: " + id);
}
};
/**
* Deletes a variable and all of its uses from this workspace without asking the
* user for confirmation.
* @param {Blockly.VariableModel} variable Variable to delete.
* @private
*/
Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable) {
var uses = this.getVariableUses(variable.name);
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
}
this.variableMap_.deleteVariable(variable);
Blockly.Events.setGroup(false);
};
/**
* Check whether a variable exists with the given name. The check is
* case-insensitive.
* @param {string} name The name to check for.
* @return {number} The index of the name in the variable list, or -1 if it is
* not present.
* @deprecated April 2017
*/
Blockly.Workspace.prototype.variableIndexOf = function(name) {
for (var i = 0, varname; varname = this.variableList[i]; i++) {
if (Blockly.Names.equals(varname, name)) {
return i;
}
}
console.warn(
'Deprecated call to Blockly.Workspace.prototype.variableIndexOf');
return -1;
};
/**
* Find the variable by the given name and return it. Return null if it is not
* found.
* @param {!string} name The name to check for.
* @return {?Blockly.VariableModel} the variable with the given name.
*/
Blockly.Workspace.prototype.getVariable = function(name) {
return this.variableMap_.getVariable(name);
};
/**
* Find the variable by the given id and return it. Return null if it is not
* found.
* @param {!string} id The id to check for.
* @return {?Blockly.VariableModel} The variable with the given id.
*/
Blockly.Workspace.prototype.getVariableById = function(id) {
return this.variableMap_.getVariableById(id);
};
/**
* Returns the horizontal offset of the workspace.
* Intended for LTR/RTL compatibility in XML.
@@ -417,10 +492,14 @@ Blockly.Workspace.prototype.undo = function(redo) {
}
events = Blockly.Events.filter(events, redo);
Blockly.Events.recordUndo = false;
for (var i = 0, event; event = events[i]; i++) {
event.run(redo);
try {
for (var i = 0, event; event = events[i]; i++) {
event.run(redo);
}
}
finally {
Blockly.Events.recordUndo = true;
}
Blockly.Events.recordUndo = true;
};
/**
@@ -495,6 +574,33 @@ Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled
return true;
};
/**
* Find the variable with the specified type. If type is null, return list of
* variables with empty string type.
* @param {?string} type Type of the variables to find.
* @return {Array.<Blockly.VariableModel>} The sought after variables of the
* passed in type. An empty array if none are found.
*/
Blockly.Workspace.prototype.getVariablesOfType = function(type) {
return this.variableMap_.getVariablesOfType(type);
};
/**
* Return all variable types.
* @return {!Array.<string>} List of variable types.
*/
Blockly.Workspace.prototype.getVariableTypes = function() {
return this.variableMap_.getVariableTypes();
};
/**
* Return all variables of all types.
* @return {!Array.<Blockly.VariableModel>} List of variable models.
*/
Blockly.Workspace.prototype.getAllVariables = function() {
return this.variableMap_.getAllVariables();
};
/**
* Database of all workspaces.
* @private
+154
Ver Arquivo
@@ -0,0 +1,154 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 in charge of loading, storing, and playing audio for a
* workspace.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.WorkspaceAudio');
/**
* Class for loading, storing, and playing audio for a workspace.
* @param {Blockly.WorkspaceSvg} parentWorkspace The parent of the workspace
* this audio object belongs to, or null.
*/
Blockly.WorkspaceAudio = function(parentWorkspace) {
/**
* The parent of the workspace this object belongs to, or null. May be
* checked for sounds that this object can't find.
* @type {Blockly.WorkspaceSvg}
* @private
*/
this.parentWorkspace_ = parentWorkspace;
/**
* Database of pre-loaded sounds.
* @private
* @const
*/
this.SOUNDS_ = Object.create(null);
};
/**
* Time that the last sound was played.
* @type {Date}
* @private
*/
Blockly.WorkspaceAudio.prototype.lastSound_ = null;
/**
* Dispose of this audio manager.
* @package
*/
Blockly.WorkspaceAudio.prototype.dispose = function() {
this.parentWorkspace_ = null;
this.SOUNDS_ = null;
};
/**
* Load an audio file. Cache it, ready for instantaneous playing.
* @param {!Array.<string>} filenames List of file types in decreasing order of
* preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav']
* Filenames include path from Blockly's root. File extensions matter.
* @param {string} name Name of sound.
* @package
*/
Blockly.WorkspaceAudio.prototype.load = function(filenames, name) {
if (!filenames.length) {
return;
}
try {
var audioTest = new window['Audio']();
} catch (e) {
// No browser support for Audio.
// IE can throw an error even if the Audio object exists.
return;
}
var sound;
for (var i = 0; i < filenames.length; i++) {
var filename = filenames[i];
var ext = filename.match(/\.(\w+)$/);
if (ext && audioTest.canPlayType('audio/' + ext[1])) {
// Found an audio format we can play.
sound = new window['Audio'](filename);
break;
}
}
if (sound && sound.play) {
this.SOUNDS_[name] = sound;
}
};
/**
* Preload all the audio files so that they play quickly when asked for.
* @package
*/
Blockly.WorkspaceAudio.prototype.preload = function() {
for (var name in this.SOUNDS_) {
var sound = this.SOUNDS_[name];
sound.volume = .01;
sound.play();
sound.pause();
// iOS can only process one sound at a time. Trying to load more than one
// corrupts the earlier ones. Just load one and leave the others uncached.
if (goog.userAgent.IPAD || goog.userAgent.IPHONE) {
break;
}
}
};
/**
* Play a named sound at specified volume. If volume is not specified,
* use full volume (1).
* @param {string} name Name of sound.
* @param {number=} opt_volume Volume of sound (0-1).
*/
Blockly.WorkspaceAudio.prototype.play = function(name, opt_volume) {
var sound = this.SOUNDS_[name];
if (sound) {
// Don't play one sound on top of another.
var now = new Date;
if (this.lastSound_ != null &&
now - this.lastSound_ < Blockly.SOUND_LIMIT) {
return;
}
this.lastSound_ = now;
var mySound;
var ie9 = goog.userAgent.DOCUMENT_MODE &&
goog.userAgent.DOCUMENT_MODE === 9;
if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) {
// Creating a new audio node causes lag in IE9, Android and iPad. Android
// and IE9 refetch the file from the server, iPad uses a singleton audio
// node which must be deleted and recreated for each new audio tag.
mySound = sound;
} else {
mySound = sound.cloneNode();
}
mySound.volume = (opt_volume === undefined ? 1 : opt_volume);
mySound.play();
} else if (this.parentWorkspace_) {
// Maybe a workspace on a lower level knows about this sound.
this.parentWorkspace_.getAudioManager().play(name, opt_volume);
}
};
+11 -11
Ver Arquivo
@@ -43,17 +43,17 @@ goog.require('goog.math.Coordinate');
* @param {!Element} container Containing element.
* @constructor
*/
Blockly.workspaceDragSurfaceSvg = function(container) {
Blockly.WorkspaceDragSurfaceSvg = function(container) {
this.container_ = container;
this.createDom();
};
/**
* The SVG drag surface. Set once by Blockly.workspaceDragSurfaceSvg.createDom.
* The SVG drag surface. Set once by Blockly.WorkspaceDragSurfaceSvg.createDom.
* @type {Element}
* @private
*/
Blockly.workspaceDragSurfaceSvg.prototype.SVG_ = null;
Blockly.WorkspaceDragSurfaceSvg.prototype.SVG_ = null;
/**
* SVG group inside the drag surface that holds blocks while a drag is in
@@ -63,19 +63,19 @@ Blockly.workspaceDragSurfaceSvg.prototype.SVG_ = null;
* @type {Element}
* @private
*/
Blockly.workspaceDragSurfaceSvg.prototype.dragGroup_ = null;
Blockly.WorkspaceDragSurfaceSvg.prototype.dragGroup_ = null;
/**
* Containing HTML element; parent of the workspace and the drag surface.
* @type {Element}
* @private
*/
Blockly.workspaceDragSurfaceSvg.prototype.container_ = null;
Blockly.WorkspaceDragSurfaceSvg.prototype.container_ = null;
/**
* Create the drag surface and inject it into the container.
*/
Blockly.workspaceDragSurfaceSvg.prototype.createDom = function() {
Blockly.WorkspaceDragSurfaceSvg.prototype.createDom = function() {
if (this.SVG_) {
return; // Already created.
}
@@ -93,7 +93,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.createDom = function() {
'xmlns:html': Blockly.HTML_NS,
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
'version': '1.1',
'class': 'blocklyWsDragSurface'
'class': 'blocklyWsDragSurface blocklyOverflowVisible',
}, null);
this.container_.appendChild(this.SVG_);
};
@@ -107,7 +107,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.createDom = function() {
* @param {number} y Y translation for the entire surface
* @package
*/
Blockly.workspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
Blockly.WorkspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being moved on the drag surface.
x = x.toFixed(0);
@@ -124,7 +124,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
* @return {!goog.math.Coordinate} Current translation of the surface
* @package
*/
Blockly.workspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
Blockly.WorkspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
return Blockly.utils.getRelativeXY(this.SVG_);
};
@@ -135,7 +135,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
* into.
* @package
*/
Blockly.workspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
var blockCanvas = this.SVG_.childNodes[0];
var bubbleCanvas = this.SVG_.childNodes[1];
if (!blockCanvas || !bubbleCanvas ||
@@ -174,7 +174,7 @@ Blockly.workspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
* @param {number} scale The scale of the workspace being dragged.
* @package
*/
Blockly.workspaceDragSurfaceSvg.prototype.setContentsAndShow = function(
Blockly.WorkspaceDragSurfaceSvg.prototype.setContentsAndShow = function(
blockCanvas, bubbleCanvas, previousSibling, width, height, scale) {
goog.asserts.assert(this.SVG_.childNodes.length == 0,
'Already dragging a block.');
+132
Ver Arquivo
@@ -0,0 +1,132 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2017 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 dragging a workspace visually.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.WorkspaceDragger');
goog.require('goog.math.Coordinate');
goog.require('goog.asserts');
/**
* Class for a workspace dragger. It moves the workspace around when it is
* being dragged by a mouse or touch.
* Note that the workspace itself manages whether or not it has a drag surface
* and how to do translations based on that. This simply passes the right
* commands based on events.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag.
* @constructor
*/
Blockly.WorkspaceDragger = function(workspace) {
/**
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* The workspace's metrics object at the beginning of the drag. Contains size
* and position metrics of a workspace.
* Coordinate system: pixel coordinates.
* @type {!Object}
* @private
*/
this.startDragMetrics_ = workspace.getMetrics();
/**
* The scroll position of the workspace at the beginning of the drag.
* Coordinate system: pixel coordinates.
* @type {!goog.math.Coordinate}
* @private
*/
this.startScrollXY_ = new goog.math.Coordinate(workspace.scrollX,
workspace.scrollY);
};
/**
* Sever all links from this object.
* @package
*/
Blockly.WorkspaceDragger.prototype.dispose = function() {
this.workspace_ = null;
};
/**
* Start dragging the workspace.
* @package
*/
Blockly.WorkspaceDragger.prototype.startDrag = function() {
if (Blockly.selected) {
Blockly.selected.unselect();
}
this.workspace_.setupDragSurface();
};
/**
* Finish dragging the workspace and put everything back where it belongs.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel coordinates.
* @package
*/
Blockly.WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) {
// Make sure everything is up to date.
this.drag(currentDragDeltaXY);
this.workspace_.resetDragSurface();
};
/**
* Move the workspace based on the most recent mouse movements.
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel coordinates.
* @package
*/
Blockly.WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) {
var metrics = this.startDragMetrics_;
var newXY = goog.math.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY);
// Bound the new XY based on workspace bounds.
var x = Math.min(newXY.x, -metrics.contentLeft);
var y = Math.min(newXY.y, -metrics.contentTop);
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
metrics.contentWidth);
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
metrics.contentHeight);
x = -x - metrics.contentLeft;
y = -y - metrics.contentTop;
this.updateScroll_(x, y);
};
/**
* Move the scrollbars to drag the workspace.
* x and y are in pixels.
* @param {number} x The new x position to move the scrollbar to.
* @param {number} y The new y position to move the scrollbar to.
* @private
*/
Blockly.WorkspaceDragger.prototype.updateScroll_ = function(x, y) {
this.workspace_.scrollbar.set(x, y);
};
+381 -272
Ver Arquivo
@@ -30,11 +30,14 @@ goog.provide('Blockly.WorkspaceSvg');
//goog.require('Blockly.BlockSvg');
goog.require('Blockly.ConnectionDB');
goog.require('Blockly.constants');
goog.require('Blockly.Gesture');
goog.require('Blockly.Grid');
goog.require('Blockly.Options');
goog.require('Blockly.ScrollbarPair');
goog.require('Blockly.Touch');
goog.require('Blockly.Trashcan');
goog.require('Blockly.Workspace');
goog.require('Blockly.WorkspaceAudio');
goog.require('Blockly.WorkspaceDragSurfaceSvg');
goog.require('Blockly.Xml');
goog.require('Blockly.ZoomControls');
@@ -51,7 +54,7 @@ goog.require('goog.userAgent');
* @param {!Blockly.Options} options Dictionary of options.
* @param {Blockly.BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for
* blocks.
* @param {Blockly.workspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
* @param {Blockly.WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
* the workspace.
* @extends {Blockly.Workspace}
* @constructor
@@ -76,12 +79,6 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface
this.useWorkspaceDragSurface_ =
this.workspaceDragSurface_ && Blockly.utils.is3dSupported();
/**
* Database of pre-loaded sounds.
* @private
* @const
*/
this.SOUNDS_ = Object.create(null);
/**
* List of currently highlighted blocks. Block highlighting is often used to
* visually mark blocks currently being executed.
@@ -90,6 +87,21 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface
*/
this.highlightedBlocks_ = [];
/**
* Object in charge of loading, storing, and playing audio for a workspace.
* @type {Blockly.WorkspaceAudio}
* @private
*/
this.audioManager_ = new Blockly.WorkspaceAudio(options.parentWorkspace);
/**
* This workspace's grid object or null.
* @type {Blockly.Grid}
* @private
*/
this.grid_ = this.options.gridPattern ?
new Blockly.Grid(options.gridPattern, options.gridOptions) : null;
this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME,
Blockly.Variables.flyoutCategory);
this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME,
@@ -125,15 +137,6 @@ Blockly.WorkspaceSvg.prototype.isFlyout = false;
*/
Blockly.WorkspaceSvg.prototype.isMutator = false;
/**
* Is this workspace currently being dragged around?
* DRAG_NONE - No drag operation.
* DRAG_BEGIN - Still inside the initial DRAG_RADIUS.
* DRAG_FREE - Workspace has been dragged further than DRAG_RADIUS.
* @private
*/
Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE;
/**
* Whether this workspace has resizes enabled.
* Disable during batch operations for a performance improvement.
@@ -143,25 +146,25 @@ Blockly.WorkspaceSvg.prototype.dragMode_ = Blockly.DRAG_NONE;
Blockly.WorkspaceSvg.prototype.resizesEnabled_ = true;
/**
* Current horizontal scrolling offset.
* Current horizontal scrolling offset in pixel units.
* @type {number}
*/
Blockly.WorkspaceSvg.prototype.scrollX = 0;
/**
* Current vertical scrolling offset.
* Current vertical scrolling offset in pixel units.
* @type {number}
*/
Blockly.WorkspaceSvg.prototype.scrollY = 0;
/**
* Horizontal scroll value when scrolling started.
* Horizontal scroll value when scrolling started in pixel units.
* @type {number}
*/
Blockly.WorkspaceSvg.prototype.startScrollX = 0;
/**
* Vertical scroll value when scrolling started.
* Vertical scroll value when scrolling started in pixel units.
* @type {number}
*/
Blockly.WorkspaceSvg.prototype.startScrollY = 0;
@@ -191,6 +194,13 @@ Blockly.WorkspaceSvg.prototype.trashcan = null;
*/
Blockly.WorkspaceSvg.prototype.scrollbar = null;
/**
* The current gesture in progress on this workspace, if any.
* @type {Blockly.Gesture}
* @private
*/
Blockly.WorkspaceSvg.prototype.currentGesture_ = null;
/**
* This workspace's surface for dragging blocks, if it exists.
* @type {Blockly.BlockDragSurfaceSvg}
@@ -223,13 +233,6 @@ Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false;
*/
Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false;
/**
* Time that the last sound was played.
* @type {Date}
* @private
*/
Blockly.WorkspaceSvg.prototype.lastSound_ = null;
/**
* Last known position of the page scroll.
* This is used to determine whether we have recalculated screen coordinate
@@ -312,6 +315,18 @@ Blockly.WorkspaceSvg.prototype.getSvgXY = function(element) {
return new goog.math.Coordinate(x, y);
};
/**
* Return the position of the workspace origin relative to the injection div
* origin in pixels.
* The workspace origin is where a block would render at position (0, 0).
* It is not the upper left corner of the workspace SVG.
* @return {!goog.math.Coordinate} Offset in pixels.
* @package
*/
Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() {
return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_);
};
/**
* Save resize handler data so we can delete it later in dispose.
* @param {!Array.<!Array>} handler Data that can be passed to unbindEvent_.
@@ -338,14 +353,19 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
*/
this.svgGroup_ = Blockly.utils.createSvgElement('g',
{'class': 'blocklyWorkspace'}, null);
// Note that a <g> alone does not receive mouse events--it must have a
// valid target inside it. If no background class is specified, as in the
// flyout, the workspace will not receive mouse events.
if (opt_backgroundClass) {
/** @type {SVGElement} */
this.svgBackground_ = Blockly.utils.createSvgElement('rect',
{'height': '100%', 'width': '100%', 'class': opt_backgroundClass},
this.svgGroup_);
if (opt_backgroundClass == 'blocklyMainBackground') {
if (opt_backgroundClass == 'blocklyMainBackground' && this.grid_) {
this.svgBackground_.style.fill =
'url(#' + this.options.gridPattern.id + ')';
'url(#' + this.grid_.getPatternId() + ')';
}
}
/** @type {SVGElement} */
@@ -365,9 +385,6 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
if (!this.isFlyout) {
Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this,
this.onMouseDown_);
var thisWorkspace = this;
Blockly.bindEvent_(this.svgGroup_, 'touchstart', null,
function(e) {Blockly.longStart_(e, thisWorkspace);});
if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
// Mouse-wheel.
Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this,
@@ -384,7 +401,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
*/
this.toolbox_ = new Blockly.Toolbox(this);
}
this.updateGridPattern_();
if (this.grid_) {
this.grid_.update(this.scale);
}
this.recordDeleteAreas();
return this.svgGroup_;
};
@@ -396,6 +415,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
Blockly.WorkspaceSvg.prototype.dispose = function() {
// Stop rerendering.
this.rendered = false;
if (this.currentGesture_) {
this.currentGesture_.cancel();
}
Blockly.WorkspaceSvg.superClass_.dispose.call(this);
if (this.svgGroup_) {
goog.dom.removeNode(this.svgGroup_);
@@ -424,6 +446,16 @@ Blockly.WorkspaceSvg.prototype.dispose = function() {
this.zoomControls_ = null;
}
if (this.audioManager_) {
this.audioManager_.dispose();
this.audioManager_ = null;
}
if (this.grid_) {
this.grid_.dispose();
this.grid_ = null;
}
if (this.toolboxCategoryCallbacks_) {
this.toolboxCategoryCallbacks_ = null;
}
@@ -496,8 +528,16 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) {
horizontalLayout: this.horizontalLayout,
toolboxPosition: this.options.toolboxPosition
};
/** @type {Blockly.Flyout} */
this.flyout_ = new Blockly.Flyout(workspaceOptions);
/**
* @type {!Blockly.Flyout}
* @private
*/
this.flyout_ = null;
if (this.horizontalLayout) {
this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions);
} else {
this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
}
this.flyout_.autoClose = false;
// Return the element so that callers can place it in their desired
@@ -811,10 +851,20 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
this.remainingCapacity()) {
return;
}
Blockly.terminateDrag_(); // Dragging while pasting? No.
if (this.currentGesture_) {
this.currentGesture_.cancel(); // Dragging while pasting? No.
}
Blockly.Events.disable();
try {
var block = Blockly.Xml.domToBlock(xmlBlock, this);
// Rerender to get around problem with IE and Edge not measuring text
// correctly when it is hidden.
if (goog.userAgent.IE || goog.userAgent.EDGE) {
var blocks = block.getDescendants();
for (var i = blocks.length - 1; i >= 0; i--) {
blocks[i].render(false);
}
}
// Move the duplicate to original position.
var blockX = parseInt(xmlBlock.getAttribute('x'), 10);
var blockY = parseInt(xmlBlock.getAttribute('y'), 10);
@@ -862,25 +912,85 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
Blockly.Events.enable();
}
if (Blockly.Events.isEnabled() && !block.isShadow()) {
Blockly.Events.fire(new Blockly.Events.Create(block));
Blockly.Events.fire(new Blockly.Events.BlockCreate(block));
}
block.select();
};
/**
* Create a new variable with the given name. Update the flyout to show the new
* variable immediately.
* TODO: #468
* @param {string} name The new variable's name.
* Refresh the toolbox unless there's a drag in progress.
* @private
*/
Blockly.WorkspaceSvg.prototype.createVariable = function(name) {
Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name);
// Don't refresh the toolbox if there's a drag in progress.
if (this.toolbox_ && this.toolbox_.flyout_ && !Blockly.Flyout.startFlyout_) {
Blockly.WorkspaceSvg.prototype.refreshToolboxSelection_ = function() {
if (this.toolbox_ && this.toolbox_.flyout_ && !this.currentGesture_) {
this.toolbox_.refreshSelection();
}
};
/**
* Rename a variable by updating its name in the variable list.
* @param {string} oldName Variable to rename.
* @param {string} newName New variable name.
* @package
*/
Blockly.WorkspaceSvg.prototype.renameVariable = function(oldName, newName) {
Blockly.WorkspaceSvg.superClass_.renameVariable.call(this, oldName, newName);
this.refreshToolboxSelection_();
};
/**
* Rename a variable by updating its name in the variable map. Update the
* flyout to show the renamed variable immediately.
* @param {string} id Id of the variable to rename.
* @param {string} newName New variable name.
* @package
*/
Blockly.WorkspaceSvg.prototype.renameVariableById = function(id, newName) {
Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this, id, newName);
this.refreshToolboxSelection_();
};
/**
* Delete a variable by the passed in name. Update the flyout to show
* immediately that the variable is deleted.
* @param {string} name Name of variable to delete.
* @package
*/
Blockly.WorkspaceSvg.prototype.deleteVariable = function(name) {
Blockly.WorkspaceSvg.superClass_.deleteVariable.call(this, name);
this.refreshToolboxSelection_();
};
/**
* Delete a variable by the passed in id. Update the flyout to show
* immediately that the variable is deleted.
* @param {string} id Id of variable to delete.
* @package
*/
Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) {
Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this, id);
this.refreshToolboxSelection_();
};
/**
* Create a new variable with the given name. Update the flyout to show the new
* variable immediately.
* @param {string} name The new variable's name.
* @param {string=} opt_type The type of the variable like 'int' or 'string'.
* Does not need to be unique. Field_variable can filter variables based on
* their type. This will default to '' which is a specific type.
* @param {string=} opt_id The unique id of the variable. This will default to
* a UUID.
* @return {?Blockly.VariableModel} The newly created variable.
* @package
*/
Blockly.WorkspaceSvg.prototype.createVariable = function(name, opt_type, opt_id) {
var newVar = Blockly.WorkspaceSvg.superClass_.createVariable.call(this, name,
opt_type, opt_id);
this.refreshToolboxSelection_();
return newVar;
};
/**
* Make a list of all the delete areas for this workspace.
*/
@@ -901,7 +1011,6 @@ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() {
/**
* Is the mouse event over a delete area (toolbox or non-closing flyout)?
* Opens or closes the trashcan and sets the cursor as a side effect.
* @param {!Event} e Mouse move event.
* @return {?number} Null if not over a delete area, or an enum representing
* which delete area the event is over.
@@ -914,7 +1023,7 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) {
return Blockly.DELETE_AREA_TOOLBOX;
}
return null;
return Blockly.DELETE_AREA_NONE;
};
/**
@@ -923,60 +1032,10 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
* @private
*/
Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
this.markFocused();
if (Blockly.utils.isTargetInput(e)) {
Blockly.Touch.clearTouchIdentifier();
return;
var gesture = this.getGesture(e);
if (gesture) {
gesture.handleWsStart(e, this);
}
Blockly.terminateDrag_(); // In case mouse-up event was lost.
Blockly.hideChaff();
var isTargetWorkspace = e.target && e.target.nodeName &&
(e.target.nodeName.toLowerCase() == 'svg' ||
e.target == this.svgBackground_);
if (isTargetWorkspace && Blockly.selected && !this.options.readOnly) {
// Clicking on the document clears the selection.
Blockly.selected.unselect();
}
if (Blockly.utils.isRightButton(e)) {
// Right-click.
this.showContextMenu_(e);
// This is to handle the case where the event is pretending to be a right
// click event but it was really a long press. In that case, we want to make
// sure any in progress drags are stopped.
Blockly.onMouseUp_(e);
// Since this was a click, not a drag, end the gesture immediately.
Blockly.Touch.clearTouchIdentifier();
} else if (this.scrollbar) {
this.dragMode_ = Blockly.DRAG_BEGIN;
// Record the current mouse position.
this.startDragMouseX = e.clientX;
this.startDragMouseY = e.clientY;
this.startDragMetrics = this.getMetrics();
this.startScrollX = this.scrollX;
this.startScrollY = this.scrollY;
this.setupDragSurface();
// If this is a touch event then bind to the mouseup so workspace drag mode
// is turned off and double move events are not performed on a block.
// See comment in inject.js Blockly.init_ as to why mouseup events are
// bound to the document instead of the SVG's surface.
if ('mouseup' in Blockly.Touch.TOUCH_MAP) {
Blockly.Touch.onTouchUpWrapper_ = Blockly.Touch.onTouchUpWrapper_ || [];
Blockly.Touch.onTouchUpWrapper_ = Blockly.Touch.onTouchUpWrapper_.concat(
Blockly.bindEventWithChecks_(document, 'mouseup', null,
Blockly.onMouseUp_));
}
Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_ || [];
Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_.concat(
Blockly.bindEventWithChecks_(document, 'mousemove', null,
Blockly.onMouseMove_));
} else {
// It was a click, but the workspace isn't draggable.
Blockly.Touch.clearTouchIdentifier();
}
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
e.preventDefault();
};
/**
@@ -1013,10 +1072,7 @@ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
* @return {boolean} True if currently dragging or scrolling.
*/
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;
return this.currentGesture_ && this.currentGesture_.isDragging();
};
/**
@@ -1033,8 +1089,11 @@ Blockly.WorkspaceSvg.prototype.isDraggable = function() {
* @private
*/
Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
// TODO: Remove terminateDrag and compensate for coordinate skew during zoom.
Blockly.terminateDrag_();
// TODO: Remove gesture cancellation and compensate for coordinate skew during
// zoom.
if (this.currentGesture_) {
this.currentGesture_.cancel();
}
// The vertical scroll distance that corresponds to a click of a zoom button.
var PIXELS_PER_ZOOM_STEP = 50;
var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP;
@@ -1046,6 +1105,7 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
/**
* Calculate the bounding box for the blocks on the workspace.
* Coordinate system: workspace coordinates.
*
* @return {Object} Contains the position and size of the bounding box
* containing the blocks on the workspace.
@@ -1115,6 +1175,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
var menuOptions = [];
var topBlocks = this.getTopBlocks(true);
var eventGroup = Blockly.utils.genUid();
var ws = this;
// Options to undo/redo previous action.
var undoOption = {};
@@ -1224,6 +1285,9 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)),
enabled: deleteList.length > 0,
callback: function() {
if (ws.currentGesture_) {
ws.currentGesture_.cancel();
}
if (deleteList.length < 2 ) {
deleteNext();
} else {
@@ -1242,92 +1306,6 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
Blockly.ContextMenu.show(e, menuOptions, this.RTL);
};
/**
* Load an audio file. Cache it, ready for instantaneous playing.
* @param {!Array.<string>} filenames List of file types in decreasing order of
* preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav']
* Filenames include path from Blockly's root. File extensions matter.
* @param {string} name Name of sound.
* @private
*/
Blockly.WorkspaceSvg.prototype.loadAudio_ = function(filenames, name) {
if (!filenames.length) {
return;
}
try {
var audioTest = new window['Audio']();
} catch (e) {
// No browser support for Audio.
// IE can throw an error even if the Audio object exists.
return;
}
var sound;
for (var i = 0; i < filenames.length; i++) {
var filename = filenames[i];
var ext = filename.match(/\.(\w+)$/);
if (ext && audioTest.canPlayType('audio/' + ext[1])) {
// Found an audio format we can play.
sound = new window['Audio'](filename);
break;
}
}
if (sound && sound.play) {
this.SOUNDS_[name] = sound;
}
};
/**
* Preload all the audio files so that they play quickly when asked for.
* @private
*/
Blockly.WorkspaceSvg.prototype.preloadAudio_ = function() {
for (var name in this.SOUNDS_) {
var sound = this.SOUNDS_[name];
sound.volume = .01;
sound.play();
sound.pause();
// iOS can only process one sound at a time. Trying to load more than one
// corrupts the earlier ones. Just load one and leave the others uncached.
if (goog.userAgent.IPAD || goog.userAgent.IPHONE) {
break;
}
}
};
/**
* Play a named sound at specified volume. If volume is not specified,
* use full volume (1).
* @param {string} name Name of sound.
* @param {number=} opt_volume Volume of sound (0-1).
*/
Blockly.WorkspaceSvg.prototype.playAudio = function(name, opt_volume) {
var sound = this.SOUNDS_[name];
if (sound) {
// Don't play one sound on top of another.
var now = new Date;
if (now - this.lastSound_ < Blockly.SOUND_LIMIT) {
return;
}
this.lastSound_ = now;
var mySound;
var ie9 = goog.userAgent.DOCUMENT_MODE &&
goog.userAgent.DOCUMENT_MODE === 9;
if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) {
// Creating a new audio node causes lag in IE9, Android and iPad. Android
// and IE9 refetch the file from the server, iPad uses a singleton audio
// node which must be deleted and recreated for each new audio tag.
mySound = sound;
} else {
mySound = sound.cloneNode();
}
mySound.volume = (opt_volume === undefined ? 1 : opt_volume);
mySound.play();
} else if (this.options.parentWorkspace) {
// Maybe a workspace on a lower level knows about this sound.
this.options.parentWorkspace.playAudio(name, opt_volume);
}
};
/**
* Modify the block tree on the existing toolbox.
* @param {Node|string} tree DOM tree of blocks, or text representation of same.
@@ -1437,6 +1415,7 @@ Blockly.WorkspaceSvg.prototype.zoom = function(x, y, amount) {
.translate(x * (1 - scaleChange), y * (1 - scaleChange))
.scale(scaleChange);
// newScale and matrix.a should be identical (within a rounding error).
// ScrollX and scrollY are in pixels.
this.scrollX = matrix.e - metrics.absoluteLeft;
this.scrollY = matrix.f - metrics.absoluteTop;
}
@@ -1511,7 +1490,9 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) {
newScale = this.options.zoomOptions.minScale;
}
this.scale = newScale;
this.updateGridPattern_();
if (this.grid_) {
this.grid_.update(this.scale);
}
if (this.scrollbar) {
this.scrollbar.resize();
} else {
@@ -1525,45 +1506,121 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) {
};
/**
* Updates the grid pattern.
* Get the dimensions of the given workspace component, in pixels.
* @param {Blockly.Toolbox|Blockly.Flyout} elem The element to get the
* dimensions of, or null. It should be a toolbox or flyout, and should
* implement getWidth() and getHeight().
* @return {!Object} An object containing width and height attributes, which
* will both be zero if elem did not exist.
* @private
*/
Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() {
if (!this.options.gridPattern) {
return; // No grid.
Blockly.WorkspaceSvg.getDimensionsPx_ = function(elem) {
var width = 0;
var height = 0;
if (elem) {
width = elem.getWidth();
height = elem.getHeight();
}
// MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100.
var safeSpacing = (this.options.gridOptions['spacing'] * this.scale) || 100;
this.options.gridPattern.setAttribute('width', safeSpacing);
this.options.gridPattern.setAttribute('height', safeSpacing);
var half = Math.floor(this.options.gridOptions['spacing'] / 2) + 0.5;
var start = half - this.options.gridOptions['length'] / 2;
var end = half + this.options.gridOptions['length'] / 2;
var line1 = this.options.gridPattern.firstChild;
var line2 = line1 && line1.nextSibling;
half *= this.scale;
start *= this.scale;
end *= this.scale;
if (line1) {
line1.setAttribute('stroke-width', this.scale);
line1.setAttribute('x1', start);
line1.setAttribute('y1', half);
line1.setAttribute('x2', end);
line1.setAttribute('y2', half);
}
if (line2) {
line2.setAttribute('stroke-width', this.scale);
line2.setAttribute('x1', half);
line2.setAttribute('y1', start);
line2.setAttribute('x2', half);
line2.setAttribute('y2', end);
return {
width: width,
height: height
};
};
/**
* Get the content dimensions of the given workspace, taking into account
* whether or not it is scrollable and what size the workspace div is on screen.
* @param {!Blockly.WorkspaceSvg} ws The workspace to measure.
* @param {!Object} svgSize An object containing height and width attributes in
* CSS pixels. Together they specify the size of the visible workspace, not
* including areas covered up by the toolbox.
* @return {!Object} The dimensions of the contents of the given workspace, as
* an object containing at least
* - height and width in pixels
* - left and top in pixels relative to the workspace origin.
* @private
*/
Blockly.WorkspaceSvg.getContentDimensions_ = function(ws, svgSize) {
if (ws.scrollbar) {
return Blockly.WorkspaceSvg.getContentDimensionsBounded_(ws, svgSize);
} else {
return Blockly.WorkspaceSvg.getContentDimensionsExact_(ws);
}
};
/**
* Get the bounding box for all workspace contents, in pixels.
* @param {!Blockly.WorkspaceSvg} ws The workspace to inspect.
* @return {!Object} The dimensions of the contents of the given workspace, as
* an object containing
* - height and width in pixels
* - left, right, top and bottom in pixels relative to the workspace origin.
* @private
*/
Blockly.WorkspaceSvg.getContentDimensionsExact_ = function(ws) {
// Block bounding box is in workspace coordinates.
var blockBox = ws.getBlocksBoundingBox();
var scale = ws.scale;
// Convert to pixels.
var width = blockBox.width * scale;
var height = blockBox.height * scale;
var left = blockBox.x * scale;
var top = blockBox.y * scale;
return {
left: left,
top: top,
right: left + width,
bottom: top + height,
width: width,
height: height
};
};
/**
* Calculate the size of a scrollable workspace, which should include room for a
* half screen border around the workspace contents.
* @param {!Blockly.WorkspaceSvg} ws The workspace to measure.
* @param {!Object} svgSize An object containing height and width attributes in
* CSS pixels. Together they specify the size of the visible workspace, not
* including areas covered up by the toolbox.
* @return {!Object} The dimensions of the contents of the given workspace, as
* an object containing
* - height and width in pixels
* - left and top in pixels relative to the workspace origin.
* @private
*/
Blockly.WorkspaceSvg.getContentDimensionsBounded_ = function(ws, svgSize) {
var content = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws);
// View height and width are both in pixels, and are the same as the svg size.
var viewWidth = svgSize.width;
var viewHeight = svgSize.height;
var halfWidth = viewWidth / 2;
var halfHeight = viewHeight / 2;
// 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 left = Math.min(content.left - halfWidth, content.right - viewWidth);
var right = Math.max(content.right + halfWidth, content.left + viewWidth);
var top = Math.min(content.top - halfHeight, content.bottom - viewHeight);
var bottom = Math.max(content.bottom + halfHeight, content.top + viewHeight);
var dimensions = {
left: left,
top: top,
height: bottom - top,
width: right - left
};
return dimensions;
};
/**
* Return an object with all the metrics required to size scrollbars for a
* top level workspace. The following properties are computed:
* Coordinate system: pixel coordinates.
* .viewHeight: Height of the visible rectangle,
* .viewWidth: Width of the visible rectangle,
* .contentHeight: Height of the contents,
@@ -1585,69 +1642,59 @@ Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() {
* @this Blockly.WorkspaceSvg
*/
Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() {
var toolboxDimensions =
Blockly.WorkspaceSvg.getDimensionsPx_(this.toolbox_);
var flyoutDimensions =
Blockly.WorkspaceSvg.getDimensionsPx_(this.flyout_);
// Contains height and width in CSS pixels.
// svgSize is equivalent to the size of the injectionDiv at this point.
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();
svgSize.height -= toolboxDimensions.height;
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
svgSize.width -= this.toolbox_.getWidth();
svgSize.width -= toolboxDimensions.width;
}
}
// 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;
}
// svgSize is now the space taken up by the Blockly workspace, not including
// the toolbox.
var contentDimensions =
Blockly.WorkspaceSvg.getContentDimensions_(this, svgSize);
var absoluteLeft = 0;
if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
absoluteLeft = this.toolbox_.getWidth();
absoluteLeft = toolboxDimensions.width;
}
var absoluteTop = 0;
if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
absoluteTop = this.toolbox_.getHeight();
absoluteTop = toolboxDimensions.height;
}
var metrics = {
contentHeight: contentDimensions.height,
contentWidth: contentDimensions.width,
contentTop: contentDimensions.top,
contentLeft: contentDimensions.left,
viewHeight: svgSize.height,
viewWidth: svgSize.width,
contentHeight: bottomEdge - topEdge,
contentWidth: rightEdge - leftEdge,
viewTop: -this.scrollY,
viewLeft: -this.scrollX,
contentTop: topEdge,
contentLeft: leftEdge,
viewTop: -this.scrollY, // Must be in pixels, somehow.
viewLeft: -this.scrollX, // Must be in pixels, somehow.
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,
toolboxWidth: toolboxDimensions.width,
toolboxHeight: toolboxDimensions.height,
flyoutWidth: flyoutDimensions.width,
flyoutHeight: flyoutDimensions.height,
toolboxPosition: this.toolboxPosition
};
return metrics;
@@ -1674,14 +1721,8 @@ Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) {
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 || goog.userAgent.EDGE) {
// IE/Edge doesn't notice that the x/y offsets have changed.
// Force an update.
this.updateGridPattern_();
}
if (this.grid_) {
this.grid_.moveTo(x, y);
}
};
@@ -1783,6 +1824,74 @@ Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) {
this.toolboxCategoryCallbacks_[key] = null;
};
/**
* Look up the gesture that is tracking this touch stream on this workspace.
* May create a new gesture.
* @param {!Event} e Mouse event or touch event
* @return {Blockly.Gesture} The gesture that is tracking this touch stream,
* or null if no valid gesture exists.
* @package
*/
Blockly.WorkspaceSvg.prototype.getGesture = function(e) {
var isStart = (e.type == 'mousedown' || e.type == 'touchstart');
var gesture = this.currentGesture_;
if (gesture) {
if (isStart && gesture.hasStarted()) {
console.warn('tried to start the same gesture twice');
// That's funny. We must have missed a mouse up.
// Cancel it, rather than try to retrieve all of the state we need.
gesture.cancel();
return null;
}
return gesture;
}
// No gesture existed on this workspace, but this looks like the start of a
// new gesture.
if (isStart) {
this.currentGesture_ = new Blockly.Gesture(e, this);
return this.currentGesture_;
}
// No gesture existed and this event couldn't be the start of a new gesture.
return null;
};
/**
* Clear the reference to the current gesture.
* @package
*/
Blockly.WorkspaceSvg.prototype.clearGesture = function() {
this.currentGesture_ = null;
};
/**
* Cancel the current gesture, if one exists.
* @package
*/
Blockly.WorkspaceSvg.prototype.cancelCurrentGesture = function() {
if (this.currentGesture_) {
this.currentGesture_.cancel();
}
};
/**
* Get the audio manager for this workspace.
* @return {Blockly.WorkspaceAudio} The audio manager for this workspace.
*/
Blockly.WorkspaceSvg.prototype.getAudioManager = function() {
return this.audioManager_;
};
/**
* Get the grid object for this workspace, or null if there is none.
* @return {Blockly.Grid} The grid object for this workspace.
* @package
*/
Blockly.WorkspaceSvg.prototype.getGrid = function() {
return this.grid_;
};
// Export symbols that would otherwise be renamed by Closure compiler.
Blockly.WorkspaceSvg.prototype['setVisible'] =
Blockly.WorkspaceSvg.prototype.setVisible;
+104 -24
Ver Arquivo
@@ -42,6 +42,7 @@ goog.require('goog.dom');
*/
Blockly.Xml.workspaceToDom = function(workspace, opt_noId) {
var xml = goog.dom.createDom('xml');
xml.appendChild(Blockly.Xml.variablesToDom(workspace.getAllVariables()));
var blocks = workspace.getTopBlocks(true);
for (var i = 0, block; block = blocks[i]; i++) {
xml.appendChild(Blockly.Xml.blockToDomWithXY(block, opt_noId));
@@ -49,6 +50,23 @@ Blockly.Xml.workspaceToDom = function(workspace, opt_noId) {
return xml;
};
/**
* Encode a list of variables as XML.
* @param {!Array.<!Blockly.VariableModel>} variableList List of all variable
* models.
* @return {!Element} List of XML elements.
*/
Blockly.Xml.variablesToDom = function(variableList) {
var variables = goog.dom.createDom('variables');
for (var i = 0, variable; variable = variableList[i]; i++) {
var element = goog.dom.createDom('variable', null, variable.name);
element.setAttribute('type', variable.type);
element.setAttribute('id', variable.getId());
variables.appendChild(element);
}
return variables;
};
/**
* Encode a block subtree as XML with XY coordinates.
* @param {!Blockly.Block} block The root block to encode.
@@ -91,6 +109,13 @@ Blockly.Xml.blockToDom = function(block, opt_noId) {
if (field.name && field.EDITABLE) {
var container = goog.dom.createDom('field', null, field.getValue());
container.setAttribute('name', field.name);
if (field instanceof Blockly.FieldVariable) {
var variable = block.workspace.getVariable(field.getValue());
if (variable) {
container.setAttribute('id', variable.getId());
container.setAttribute('variableType', variable.type);
}
}
element.appendChild(container);
}
}
@@ -307,31 +332,47 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
if (workspace.setResizesEnabled) {
workspace.setResizesEnabled(false);
}
for (var i = 0; i < childCount; i++) {
var xmlChild = xml.childNodes[i];
var name = xmlChild.nodeName.toLowerCase();
if (name == 'block' ||
(name == 'shadow' && !Blockly.Events.recordUndo)) {
// Allow top-level shadow blocks if recordUndo is disabled since
// that means an undo is in progress. Such a block is expected
// to be moved to a nested destination in the next operation.
var block = Blockly.Xml.domToBlock(xmlChild, workspace);
newBlockIds.push(block.id);
var blockX = parseInt(xmlChild.getAttribute('x'), 10);
var blockY = parseInt(xmlChild.getAttribute('y'), 10);
if (!isNaN(blockX) && !isNaN(blockY)) {
block.moveBy(workspace.RTL ? width - blockX : blockX, blockY);
var variablesFirst = true;
try {
for (var i = 0; i < childCount; i++) {
var xmlChild = xml.childNodes[i];
var name = xmlChild.nodeName.toLowerCase();
if (name == 'block' ||
(name == 'shadow' && !Blockly.Events.recordUndo)) {
// Allow top-level shadow blocks if recordUndo is disabled since
// that means an undo is in progress. Such a block is expected
// to be moved to a nested destination in the next operation.
var block = Blockly.Xml.domToBlock(xmlChild, workspace);
newBlockIds.push(block.id);
var blockX = parseInt(xmlChild.getAttribute('x'), 10);
var blockY = parseInt(xmlChild.getAttribute('y'), 10);
if (!isNaN(blockX) && !isNaN(blockY)) {
block.moveBy(workspace.RTL ? width - blockX : blockX, blockY);
}
variablesFirst = false;
} else if (name == 'shadow') {
goog.asserts.fail('Shadow block cannot be a top-level block.');
variablesFirst = false;
} else if (name == 'variables') {
if (variablesFirst) {
Blockly.Xml.domToVariables(xmlChild, workspace);
}
else {
throw Error('\'variables\' tag must exist once before block and ' +
'shadow tag elements in the workspace XML, but it was found in ' +
'another location.');
}
variablesFirst = false;
}
} else if (name == 'shadow') {
goog.asserts.fail('Shadow block cannot be a top-level block.');
}
}
if (!existingGroup) {
Blockly.Events.setGroup(false);
finally {
if (!existingGroup) {
Blockly.Events.setGroup(false);
}
Blockly.Field.stopCache();
}
Blockly.Field.stopCache();
workspace.updateVariableList(false);
workspace.updateVariableStore(false);
// Re-enable workspace resizing.
if (workspace.setResizesEnabled) {
workspace.setResizesEnabled(true);
@@ -354,7 +395,7 @@ Blockly.Xml.appendDomToWorkspace = function(xml, workspace) {
var savetab = Blockly.BlockSvg.TAB_WIDTH;
try {
Blockly.BlockSvg.TAB_WIDTH = 0;
var bbox = workspace.getBlocksBoundingBox();
bbox = workspace.getBlocksBoundingBox();
} finally {
Blockly.BlockSvg.TAB_WIDTH = savetab;
}
@@ -440,11 +481,30 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
Blockly.Events.enable();
}
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Create(topBlock));
Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock));
}
return topBlock;
};
/**
* Decode an XML list of variables and add the variables to the workspace.
* @param {!Element} xmlVariables List of XML variable elements.
* @param {!Blockly.Workspace} workspace The workspace to which the variable
* should be added.
*/
Blockly.Xml.domToVariables = function(xmlVariables, workspace) {
for (var i = 0, xmlChild; xmlChild = xmlVariables.children[i]; i++) {
var type = xmlChild.getAttribute('type');
var id = xmlChild.getAttribute('id');
var name = xmlChild.textContent;
if (typeof(type) === undefined || type === null) {
throw Error('Variable with id, ' + id + ' is without a type');
}
workspace.createVariable(name, type, id);
}
};
/**
* Decode an XML block tag and create a block (and possibly sub blocks) on the
* workspace.
@@ -526,12 +586,32 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
// Fall through.
case 'field':
var field = block.getField(name);
var text = xmlChild.textContent;
if (field instanceof Blockly.FieldVariable) {
// TODO (marisaleung): When we change setValue and getValue to
// interact with id's instead of names, update this so that we get
// the variable based on id instead of textContent.
var type = xmlChild.getAttribute('variableType') || '';
var variable = workspace.getVariable(text);
if (!variable) {
variable = workspace.createVariable(text, type,
xmlChild.getAttribute(id));
}
if (typeof(type) !== undefined && type !== null) {
if (type !== variable.type) {
throw Error('Serialized variable type with id \'' +
variable.getId() + '\' had type ' + variable.type + ', and ' +
'does not match variable field that references it: ' +
Blockly.Xml.domToText(xmlChild) + '.');
}
}
}
if (!field) {
console.warn('Ignoring non-existent field ' + name + ' in block ' +
prototypeName);
break;
}
field.setValue(xmlChild.textContent);
field.setValue(text);
break;
case 'value':
case 'statement':
+4 -3
Ver Arquivo
@@ -115,9 +115,10 @@ Blockly.ZoomControls.prototype.createDom = function() {
*/
this.svgGroup_ = Blockly.utils.createSvgElement('g',
{'class': 'blocklyZoom'}, null);
var clip;
var rnd = String(Math.random()).substring(2);
var clip = Blockly.utils.createSvgElement('clipPath',
clip = Blockly.utils.createSvgElement('clipPath',
{'id': 'blocklyZoomoutClipPath' + rnd},
this.svgGroup_);
Blockly.utils.createSvgElement('rect',
@@ -132,7 +133,7 @@ Blockly.ZoomControls.prototype.createDom = function() {
zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
workspace.options.pathToMedia + Blockly.SPRITE.url);
var clip = Blockly.utils.createSvgElement('clipPath',
clip = Blockly.utils.createSvgElement('clipPath',
{'id': 'blocklyZoominClipPath' + rnd},
this.svgGroup_);
Blockly.utils.createSvgElement('rect',
@@ -148,7 +149,7 @@ Blockly.ZoomControls.prototype.createDom = function() {
zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
workspace.options.pathToMedia + Blockly.SPRITE.url);
var clip = Blockly.utils.createSvgElement('clipPath',
clip = Blockly.utils.createSvgElement('clipPath',
{'id': 'blocklyZoomresetClipPath' + rnd},
this.svgGroup_);
Blockly.utils.createSvgElement('rect',
+32 -15
Ver Arquivo
@@ -1,12 +1,29 @@
// Do not edit this file; automatically generated by build.py.
'use strict';
/*
// Copyright 2014 Google Inc. Apache License 2.0
Visual Blocks Language
Copyright 2014 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.
*/
Blockly.Dart=new Blockly.Generator("Dart");Blockly.Dart.addReservedWords("assert,break,case,catch,class,const,continue,default,do,else,enum,extends,false,final,finally,for,if,in,is,new,null,rethrow,return,super,switch,this,throw,true,try,var,void,while,with,print,identityHashCode,identical,BidirectionalIterator,Comparable,double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,ArgumentError,AssertionError,CastError,ConcurrentModificationError,CyclicInitializationError,Error,Exception,FallThroughError,FormatException,IntegerDivisionByZeroException,NoSuchMethodError,NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,StateError,TypeError,UnimplementedError,UnsupportedError");
Blockly.Dart.ORDER_ATOMIC=0;Blockly.Dart.ORDER_UNARY_POSTFIX=1;Blockly.Dart.ORDER_UNARY_PREFIX=2;Blockly.Dart.ORDER_MULTIPLICATIVE=3;Blockly.Dart.ORDER_ADDITIVE=4;Blockly.Dart.ORDER_SHIFT=5;Blockly.Dart.ORDER_BITWISE_AND=6;Blockly.Dart.ORDER_BITWISE_XOR=7;Blockly.Dart.ORDER_BITWISE_OR=8;Blockly.Dart.ORDER_RELATIONAL=9;Blockly.Dart.ORDER_EQUALITY=10;Blockly.Dart.ORDER_LOGICAL_AND=11;Blockly.Dart.ORDER_LOGICAL_OR=12;Blockly.Dart.ORDER_IF_NULL=13;Blockly.Dart.ORDER_CONDITIONAL=14;
Blockly.Dart.ORDER_CASCADE=15;Blockly.Dart.ORDER_ASSIGNMENT=16;Blockly.Dart.ORDER_NONE=99;
Blockly.Dart.init=function(a){Blockly.Dart.definitions_=Object.create(null);Blockly.Dart.functionNames_=Object.create(null);Blockly.Dart.variableDB_?Blockly.Dart.variableDB_.reset():Blockly.Dart.variableDB_=new Blockly.Names(Blockly.Dart.RESERVED_WORDS_);var b=[];a=a.variableList;if(a.length){for(var c=0;c<a.length;c++)b[c]=Blockly.Dart.variableDB_.getName(a[c],Blockly.Variables.NAME_TYPE);Blockly.Dart.definitions_.variables="var "+b.join(", ")+";"}};
Blockly.Dart.init=function(a){Blockly.Dart.definitions_=Object.create(null);Blockly.Dart.functionNames_=Object.create(null);Blockly.Dart.variableDB_?Blockly.Dart.variableDB_.reset():Blockly.Dart.variableDB_=new Blockly.Names(Blockly.Dart.RESERVED_WORDS_);var b=[];a=a.getAllVariables();if(a.length){for(var c=0;c<a.length;c++)b[c]=Blockly.Dart.variableDB_.getName(a[c].name,Blockly.Variables.NAME_TYPE);Blockly.Dart.definitions_.variables="var "+b.join(", ")+";"}};
Blockly.Dart.finish=function(a){a&&(a=Blockly.Dart.prefixLines(a,Blockly.Dart.INDENT));a="main() {\n"+a+"}";var b=[],c=[],d;for(d in Blockly.Dart.definitions_){var e=Blockly.Dart.definitions_[d];e.match(/^import\s/)?b.push(e):c.push(e)}delete Blockly.Dart.definitions_;delete Blockly.Dart.functionNames_;Blockly.Dart.variableDB_.reset();return(b.join("\n")+"\n\n"+c.join("\n\n")).replace(/\n\n+/g,"\n\n").replace(/\n*$/,"\n\n\n")+a};Blockly.Dart.scrubNakedValue=function(a){return a+";\n"};
Blockly.Dart.quote_=function(a){a=a.replace(/\\/g,"\\\\").replace(/\n/g,"\\\n").replace(/\$/g,"\\$").replace(/'/g,"\\'");return"'"+a+"'"};
Blockly.Dart.scrub_=function(a,b){var c="";if(!a.outputConnection||!a.outputConnection.targetConnection){var d=a.getCommentText();(d=Blockly.utils.wrap(d,Blockly.Dart.COMMENT_WRAP-3))&&(c=a.getProcedureDef?c+Blockly.Dart.prefixLines(d+"\n","/// "):c+Blockly.Dart.prefixLines(d+"\n","// "));for(var e=0;e<a.inputList.length;e++)a.inputList[e].type==Blockly.INPUT_VALUE&&(d=a.inputList[e].connection.targetBlock())&&(d=Blockly.Dart.allNestedComments(d))&&(c+=Blockly.Dart.prefixLines(d,"// "))}e=a.nextConnection&&
@@ -14,15 +31,15 @@ a.nextConnection.targetBlock();e=Blockly.Dart.blockToCode(e);return c+b+e};
Blockly.Dart.getAdjusted=function(a,b,c,d,e){c=c||0;e=e||Blockly.Dart.ORDER_NONE;a.workspace.options.oneBasedIndex&&c--;var f=a.workspace.options.oneBasedIndex?"1":"0";a=c?Blockly.Dart.valueToCode(a,b,Blockly.Dart.ORDER_ADDITIVE)||f:d?Blockly.Dart.valueToCode(a,b,Blockly.Dart.ORDER_UNARY_PREFIX)||f:Blockly.Dart.valueToCode(a,b,e)||f;if(Blockly.isNumber(a))a=parseInt(a,10)+c,d&&(a=-a);else{if(0<c){a=a+" + "+c;var g=Blockly.Dart.ORDER_ADDITIVE}else 0>c&&(a=a+" - "+-c,g=Blockly.Dart.ORDER_ADDITIVE);
d&&(a=c?"-("+a+")":"-"+a,g=Blockly.Dart.ORDER_UNARY_PREFIX);g=Math.floor(g);e=Math.floor(e);g&&e>=g&&(a="("+a+")")}return a};Blockly.Dart.colour={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.colour_picker=function(a){return["'"+a.getFieldValue("COLOUR")+"'",Blockly.Dart.ORDER_ATOMIC]};
Blockly.Dart.colour_random=function(a){Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_random",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"() {"," String hex = '0123456789abcdef';"," var rnd = new Math.Random();"," return '#${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}'"," '${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}'"," '${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}';","}"])+"()",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.colour_rgb=function(a){var b=Blockly.Dart.valueToCode(a,"RED",Blockly.Dart.ORDER_NONE)||0,c=Blockly.Dart.valueToCode(a,"GREEN",Blockly.Dart.ORDER_NONE)||0;a=Blockly.Dart.valueToCode(a,"BLUE",Blockly.Dart.ORDER_NONE)||0;Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_rgb",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(num r, num g, num b) {"," num rn = (Math.max(Math.min(r, 1), 0) * 255).round();"," String rs = rn.toInt().toRadixString(16);",
" rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," num gn = (Math.max(Math.min(g, 1), 0) * 255).round();"," String gs = gn.toInt().toRadixString(16);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," num bn = (Math.max(Math.min(b, 1), 0) * 255).round();"," String bs = bn.toInt().toRadixString(16);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.colour_rgb=function(a){var b=Blockly.Dart.valueToCode(a,"RED",Blockly.Dart.ORDER_NONE)||0,c=Blockly.Dart.valueToCode(a,"GREEN",Blockly.Dart.ORDER_NONE)||0;a=Blockly.Dart.valueToCode(a,"BLUE",Blockly.Dart.ORDER_NONE)||0;Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_rgb",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(num r, num g, num b) {"," num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round();"," String rs = rn.toInt().toRadixString(16);",
" rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," num gn = (Math.max(Math.min(g, 100), 0) * 2.55).round();"," String gs = gn.toInt().toRadixString(16);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," num bn = (Math.max(Math.min(b, 100), 0) * 2.55).round();"," String bs = bn.toInt().toRadixString(16);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.colour_blend=function(a){var b=Blockly.Dart.valueToCode(a,"COLOUR1",Blockly.Dart.ORDER_NONE)||"'#000000'",c=Blockly.Dart.valueToCode(a,"COLOUR2",Blockly.Dart.ORDER_NONE)||"'#000000'";a=Blockly.Dart.valueToCode(a,"RATIO",Blockly.Dart.ORDER_NONE)||.5;Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";return[Blockly.Dart.provideFunction_("colour_blend",["String "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(String c1, String c2, num ratio) {"," ratio = Math.max(Math.min(ratio, 1), 0);",
" int r1 = int.parse('0x${c1.substring(1, 3)}');"," int g1 = int.parse('0x${c1.substring(3, 5)}');"," int b1 = int.parse('0x${c1.substring(5, 7)}');"," int r2 = int.parse('0x${c2.substring(1, 3)}');"," int g2 = int.parse('0x${c2.substring(3, 5)}');"," int b2 = int.parse('0x${c2.substring(5, 7)}');"," num rn = (r1 * (1 - ratio) + r2 * ratio).round();"," String rs = rn.toInt().toRadixString(16);"," num gn = (g1 * (1 - ratio) + g2 * ratio).round();"," String gs = gn.toInt().toRadixString(16);",
" num bn = (b1 * (1 - ratio) + b2 * ratio).round();"," String bs = bn.toInt().toRadixString(16);"," rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.lists={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.lists_create_empty=function(a){return["[]",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.lists_create_with=function(a){for(var b=Array(a.itemCount_),c=0;c<a.itemCount_;c++)b[c]=Blockly.Dart.valueToCode(a,"ADD"+c,Blockly.Dart.ORDER_NONE)||"null";return["["+b.join(", ")+"]",Blockly.Dart.ORDER_ATOMIC]};
Blockly.Dart.lists_repeat=function(a){var b=Blockly.Dart.valueToCode(a,"ITEM",Blockly.Dart.ORDER_NONE)||"null";return["new List.filled("+(Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_NONE)||"0")+", "+b+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.lists_length=function(a){return[(Blockly.Dart.valueToCode(a,"VALUE",Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]")+".length",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.lists_isEmpty=function(a){return[(Blockly.Dart.valueToCode(a,"VALUE",Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]")+".isEmpty",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.lists_indexOf=function(a){var b="FIRST"==a.getFieldValue("END")?"indexOf":"lastIndexOf",c=Blockly.Dart.valueToCode(a,"FIND",Blockly.Dart.ORDER_NONE)||"''",b=(Blockly.Dart.valueToCode(a,"VALUE",Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]")+"."+b+"("+c+")";return a.workspace.options.oneBasedIndex?[b+" + 1",Blockly.Dart.ORDER_ADDITIVE]:[b,Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.lists_getIndex=function(a){function b(){var a=Blockly.Dart.variableDB_.getDistinctName("tmp_list",Blockly.Variables.NAME_TYPE),b="List "+a+" = "+e+";\n";e=a;return b}var c=a.getFieldValue("MODE")||"GET",d=a.getFieldValue("WHERE")||"FROM_START",e=Blockly.Dart.valueToCode(a,"VALUE","RANDOM"==d||"FROM_END"==d?Blockly.Dart.ORDER_NONE:Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]";if(("RANDOM"!=d||"REMOVE"!=c)&&"FROM_END"!=d||e.match(/^\w+$/))switch(d){case "FIRST":if("GET"==c)return[e+".first",
Blockly.Dart.lists_getIndex=function(a){function b(){var a=Blockly.Dart.variableDB_.getDistinctName("tmp_list",Blockly.Variables.NAME_TYPE),b="List "+a+" = "+e+";\n";e=a;return b}var c=a.getFieldValue("MODE")||"GET";var d=a.getFieldValue("WHERE")||"FROM_START";var e=Blockly.Dart.valueToCode(a,"VALUE","RANDOM"==d||"FROM_END"==d?Blockly.Dart.ORDER_NONE:Blockly.Dart.ORDER_UNARY_POSTFIX)||"[]";if(("RANDOM"!=d||"REMOVE"!=c)&&"FROM_END"!=d||e.match(/^\w+$/))switch(d){case "FIRST":if("GET"==c)return[e+".first",
Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return[e+".removeAt(0)",Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return e+".removeAt(0);\n";break;case "LAST":if("GET"==c)return[e+".last",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return[e+".removeLast()",Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return e+".removeLast();\n";break;case "FROM_START":d=Blockly.Dart.getAdjusted(a,"AT");if("GET"==c)return[e+"["+d+"]",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return[e+
".removeAt("+d+")",Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return e+".removeAt("+d+");\n";break;case "FROM_END":d=Blockly.Dart.getAdjusted(a,"AT",1,!1,Blockly.Dart.ORDER_ADDITIVE);if("GET"==c)return[e+"["+e+".length - "+d+"]",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c||"REMOVE"==c){a=e+".removeAt("+e+".length - "+d+")";if("GET_REMOVE"==c)return[a,Blockly.Dart.ORDER_UNARY_POSTFIX];if("REMOVE"==c)return a+";\n"}break;case "RANDOM":Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";
if("REMOVE"==c)return c=Blockly.Dart.variableDB_.getDistinctName("tmp_x",Blockly.Variables.NAME_TYPE),"int "+c+" = new Math.Random().nextInt("+e+".length);\n"+(e+".removeAt("+c+");\n");if("GET"==c)return c=Blockly.Dart.provideFunction_("lists_get_random_item",["dynamic "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List my_list) {"," int x = new Math.Random().nextInt(my_list.length);"," return my_list[x];","}"]),[c+"("+e+")",Blockly.Dart.ORDER_UNARY_POSTFIX];if("GET_REMOVE"==c)return c=Blockly.Dart.provideFunction_("lists_remove_random_item",
@@ -38,27 +55,27 @@ Blockly.Dart.getAdjusted(a,"AT2",1);break;case "FROM_END":f=Blockly.Dart.getAdju
Blockly.Dart.lists_sort=function(a){var b=Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]",c="1"===a.getFieldValue("DIRECTION")?1:-1;a=a.getFieldValue("TYPE");return[Blockly.Dart.provideFunction_("lists_sort",["List "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(list, type, direction) {"," var compareFuncs = {",' "NUMERIC": (a, b) => direction * a.compareTo(b),',' "TEXT": (a, b) => direction * a.toString().compareTo(b.toString()),',' "IGNORE_CASE": '," (a, b) => direction * ",
" a.toString().toLowerCase().compareTo(b.toString().toLowerCase())"," };"," list = new List.from(list);"," var compare = compareFuncs[type];"," list.sort(compare);"," return list;","}"])+"("+b+', "'+a+'", '+c+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.lists_split=function(a){var b=Blockly.Dart.valueToCode(a,"INPUT",Blockly.Dart.ORDER_UNARY_POSTFIX),c=Blockly.Dart.valueToCode(a,"DELIM",Blockly.Dart.ORDER_NONE)||"''";a=a.getFieldValue("MODE");if("SPLIT"==a)b||(b="''"),a="split";else if("JOIN"==a)b||(b="[]"),a="join";else throw"Unknown mode: "+a;return[b+"."+a+"("+c+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};
Blockly.Dart.lists_reverse=function(a){return["new List.from("+(Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]")+".reversed)",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.logic={};Blockly.Dart.controls_if=function(a){var b=0,c="",d,e;do e=Blockly.Dart.valueToCode(a,"IF"+b,Blockly.Dart.ORDER_NONE)||"false",d=Blockly.Dart.statementToCode(a,"DO"+b),c+=(0<b?"else ":"")+"if ("+e+") {\n"+d+"}",++b;while(a.getInput("IF"+b));a.getInput("ELSE")&&(d=Blockly.Dart.statementToCode(a,"ELSE"),c+=" else {\n"+d+"}");return c+"\n"};Blockly.Dart.controls_ifelse=Blockly.Dart.controls_if;
Blockly.Dart.lists_reverse=function(a){return["new List.from("+(Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]")+".reversed)",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.logic={};Blockly.Dart.controls_if=function(a){var b=0,c="";do{var d=Blockly.Dart.valueToCode(a,"IF"+b,Blockly.Dart.ORDER_NONE)||"false";var e=Blockly.Dart.statementToCode(a,"DO"+b);c+=(0<b?"else ":"")+"if ("+d+") {\n"+e+"}";++b}while(a.getInput("IF"+b));a.getInput("ELSE")&&(e=Blockly.Dart.statementToCode(a,"ELSE"),c+=" else {\n"+e+"}");return c+"\n"};Blockly.Dart.controls_ifelse=Blockly.Dart.controls_if;
Blockly.Dart.logic_compare=function(a){var b={EQ:"==",NEQ:"!=",LT:"<",LTE:"<=",GT:">",GTE:">="}[a.getFieldValue("OP")],c="=="==b||"!="==b?Blockly.Dart.ORDER_EQUALITY:Blockly.Dart.ORDER_RELATIONAL,d=Blockly.Dart.valueToCode(a,"A",c)||"0";a=Blockly.Dart.valueToCode(a,"B",c)||"0";return[d+" "+b+" "+a,c]};
Blockly.Dart.logic_operation=function(a){var b="AND"==a.getFieldValue("OP")?"&&":"||",c="&&"==b?Blockly.Dart.ORDER_LOGICAL_AND:Blockly.Dart.ORDER_LOGICAL_OR,d=Blockly.Dart.valueToCode(a,"A",c);a=Blockly.Dart.valueToCode(a,"B",c);if(d||a){var e="&&"==b?"true":"false";d||(d=e);a||(a=e)}else a=d="false";return[d+" "+b+" "+a,c]};Blockly.Dart.logic_negate=function(a){var b=Blockly.Dart.ORDER_UNARY_PREFIX;return["!"+(Blockly.Dart.valueToCode(a,"BOOL",b)||"true"),b]};
Blockly.Dart.logic_boolean=function(a){return["TRUE"==a.getFieldValue("BOOL")?"true":"false",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.logic_null=function(a){return["null",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.logic_ternary=function(a){var b=Blockly.Dart.valueToCode(a,"IF",Blockly.Dart.ORDER_CONDITIONAL)||"false",c=Blockly.Dart.valueToCode(a,"THEN",Blockly.Dart.ORDER_CONDITIONAL)||"null";a=Blockly.Dart.valueToCode(a,"ELSE",Blockly.Dart.ORDER_CONDITIONAL)||"null";return[b+" ? "+c+" : "+a,Blockly.Dart.ORDER_CONDITIONAL]};Blockly.Dart.loops={};
Blockly.Dart.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):Blockly.Dart.valueToCode(a,"TIMES",Blockly.Dart.ORDER_ASSIGNMENT)||"0",c=Blockly.Dart.statementToCode(a,"DO"),c=Blockly.Dart.addLoopTrap(c,a.id);a="";var d=Blockly.Dart.variableDB_.getDistinctName("count",Blockly.Variables.NAME_TYPE),e=b;b.match(/^\w+$/)||Blockly.isNumber(b)||(e=Blockly.Dart.variableDB_.getDistinctName("repeat_end",Blockly.Variables.NAME_TYPE),a+="var "+e+" = "+b+";\n");
return a+("for (int "+d+" = 0; "+d+" < "+e+"; "+d+"++) {\n"+c+"}\n")};Blockly.Dart.controls_repeat=Blockly.Dart.controls_repeat_ext;Blockly.Dart.controls_whileUntil=function(a){var b="UNTIL"==a.getFieldValue("MODE"),c=Blockly.Dart.valueToCode(a,"BOOL",b?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_NONE)||"false",d=Blockly.Dart.statementToCode(a,"DO"),d=Blockly.Dart.addLoopTrap(d,a.id);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"};
Blockly.Dart.controls_for=function(a){var b=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.Dart.valueToCode(a,"FROM",Blockly.Dart.ORDER_ASSIGNMENT)||"0",d=Blockly.Dart.valueToCode(a,"TO",Blockly.Dart.ORDER_ASSIGNMENT)||"0",e=Blockly.Dart.valueToCode(a,"BY",Blockly.Dart.ORDER_ASSIGNMENT)||"1",f=Blockly.Dart.statementToCode(a,"DO"),f=Blockly.Dart.addLoopTrap(f,a.id);if(Blockly.isNumber(c)&&Blockly.isNumber(d)&&Blockly.isNumber(e)){var g=parseFloat(c)<=
Blockly.Dart.controls_repeat_ext=function(a){var b=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):Blockly.Dart.valueToCode(a,"TIMES",Blockly.Dart.ORDER_ASSIGNMENT)||"0";var c=Blockly.Dart.statementToCode(a,"DO"),c=Blockly.Dart.addLoopTrap(c,a.id),d="",e=Blockly.Dart.variableDB_.getDistinctName("count",Blockly.Variables.NAME_TYPE);a=b;b.match(/^\w+$/)||Blockly.isNumber(b)||(a=Blockly.Dart.variableDB_.getDistinctName("repeat_end",Blockly.Variables.NAME_TYPE),d+="var "+a+" = "+b+";\n");
return d+("for (int "+e+" = 0; "+e+" < "+a+"; "+e+"++) {\n"+c+"}\n")};Blockly.Dart.controls_repeat=Blockly.Dart.controls_repeat_ext;Blockly.Dart.controls_whileUntil=function(a){var b="UNTIL"==a.getFieldValue("MODE"),c=Blockly.Dart.valueToCode(a,"BOOL",b?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_NONE)||"false",d=Blockly.Dart.statementToCode(a,"DO"),d=Blockly.Dart.addLoopTrap(d,a.id);b&&(c="!"+c);return"while ("+c+") {\n"+d+"}\n"};
Blockly.Dart.controls_for=function(a){var b=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE);var c=Blockly.Dart.valueToCode(a,"FROM",Blockly.Dart.ORDER_ASSIGNMENT)||"0";var d=Blockly.Dart.valueToCode(a,"TO",Blockly.Dart.ORDER_ASSIGNMENT)||"0",e=Blockly.Dart.valueToCode(a,"BY",Blockly.Dart.ORDER_ASSIGNMENT)||"1",f=Blockly.Dart.statementToCode(a,"DO"),f=Blockly.Dart.addLoopTrap(f,a.id);if(Blockly.isNumber(c)&&Blockly.isNumber(d)&&Blockly.isNumber(e)){var g=parseFloat(c)<=
parseFloat(d);a="for ("+b+" = "+c+"; "+b+(g?" <= ":" >= ")+d+"; "+b;b=Math.abs(parseFloat(e));a=(1==b?a+(g?"++":"--"):a+((g?" += ":" -= ")+b))+(") {\n"+f+"}\n")}else a="",g=c,c.match(/^\w+$/)||Blockly.isNumber(c)||(g=Blockly.Dart.variableDB_.getDistinctName(b+"_start",Blockly.Variables.NAME_TYPE),a+="var "+g+" = "+c+";\n"),c=d,d.match(/^\w+$/)||Blockly.isNumber(d)||(c=Blockly.Dart.variableDB_.getDistinctName(b+"_end",Blockly.Variables.NAME_TYPE),a+="var "+c+" = "+d+";\n"),d=Blockly.Dart.variableDB_.getDistinctName(b+
"_inc",Blockly.Variables.NAME_TYPE),a+="num "+d+" = ",a=Blockly.isNumber(e)?a+(Math.abs(e)+";\n"):a+("("+e+").abs();\n"),a=a+("if ("+g+" > "+c+") {\n")+(Blockly.Dart.INDENT+d+" = -"+d+";\n"),a+="}\n",a+="for ("+b+" = "+g+"; "+d+" >= 0 ? "+b+" <= "+c+" : "+b+" >= "+c+"; "+b+" += "+d+") {\n"+f+"}\n";return a};
Blockly.Dart.controls_forEach=function(a){var b=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE),c=Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_ASSIGNMENT)||"[]",d=Blockly.Dart.statementToCode(a,"DO"),d=Blockly.Dart.addLoopTrap(d,a.id);return"for (var "+b+" in "+c+") {\n"+d+"}\n"};
Blockly.Dart.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.Dart.math={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));var b;Infinity==a?(a="double.INFINITY",b=Blockly.Dart.ORDER_UNARY_POSTFIX):-Infinity==a?(a="-double.INFINITY",b=Blockly.Dart.ORDER_UNARY_PREFIX):b=0>a?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_ATOMIC;return[a,b]};
Blockly.Dart.controls_flow_statements=function(a){switch(a.getFieldValue("FLOW")){case "BREAK":return"break;\n";case "CONTINUE":return"continue;\n"}throw"Unknown flow statement.";};Blockly.Dart.math={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.math_number=function(a){a=parseFloat(a.getFieldValue("NUM"));if(Infinity==a){a="double.INFINITY";var b=Blockly.Dart.ORDER_UNARY_POSTFIX}else-Infinity==a?(a="-double.INFINITY",b=Blockly.Dart.ORDER_UNARY_PREFIX):b=0>a?Blockly.Dart.ORDER_UNARY_PREFIX:Blockly.Dart.ORDER_ATOMIC;return[a,b]};
Blockly.Dart.math_arithmetic=function(a){var b={ADD:[" + ",Blockly.Dart.ORDER_ADDITIVE],MINUS:[" - ",Blockly.Dart.ORDER_ADDITIVE],MULTIPLY:[" * ",Blockly.Dart.ORDER_MULTIPLICATIVE],DIVIDE:[" / ",Blockly.Dart.ORDER_MULTIPLICATIVE],POWER:[null,Blockly.Dart.ORDER_NONE]}[a.getFieldValue("OP")],c=b[0],b=b[1],d=Blockly.Dart.valueToCode(a,"A",b)||"0";a=Blockly.Dart.valueToCode(a,"B",b)||"0";return c?[d+c+a,b]:(Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;",["Math.pow("+d+", "+a+
")",Blockly.Dart.ORDER_UNARY_POSTFIX])};
Blockly.Dart.math_single=function(a){var b=a.getFieldValue("OP"),c;if("NEG"==b)return a=Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_PREFIX)||"0","-"==a[0]&&(a=" "+a),["-"+a,Blockly.Dart.ORDER_UNARY_PREFIX];Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";a="ABS"==b||"ROUND"==b.substring(0,5)?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_POSTFIX)||"0":"SIN"==b||"COS"==b||"TAN"==b?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_MULTIPLICATIVE)||
"0":Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_NONE)||"0";switch(b){case "ABS":c=a+".abs()";break;case "ROOT":c="Math.sqrt("+a+")";break;case "LN":c="Math.log("+a+")";break;case "EXP":c="Math.exp("+a+")";break;case "POW10":c="Math.pow(10,"+a+")";break;case "ROUND":c=a+".round()";break;case "ROUNDUP":c=a+".ceil()";break;case "ROUNDDOWN":c=a+".floor()";break;case "SIN":c="Math.sin("+a+" / 180 * Math.PI)";break;case "COS":c="Math.cos("+a+" / 180 * Math.PI)";break;case "TAN":c="Math.tan("+a+
Blockly.Dart.math_single=function(a){var b=a.getFieldValue("OP");if("NEG"==b)return a=Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_PREFIX)||"0","-"==a[0]&&(a=" "+a),["-"+a,Blockly.Dart.ORDER_UNARY_PREFIX];Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";a="ABS"==b||"ROUND"==b.substring(0,5)?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_UNARY_POSTFIX)||"0":"SIN"==b||"COS"==b||"TAN"==b?Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_MULTIPLICATIVE)||"0":
Blockly.Dart.valueToCode(a,"NUM",Blockly.Dart.ORDER_NONE)||"0";switch(b){case "ABS":var c=a+".abs()";break;case "ROOT":c="Math.sqrt("+a+")";break;case "LN":c="Math.log("+a+")";break;case "EXP":c="Math.exp("+a+")";break;case "POW10":c="Math.pow(10,"+a+")";break;case "ROUND":c=a+".round()";break;case "ROUNDUP":c=a+".ceil()";break;case "ROUNDDOWN":c=a+".floor()";break;case "SIN":c="Math.sin("+a+" / 180 * Math.PI)";break;case "COS":c="Math.cos("+a+" / 180 * Math.PI)";break;case "TAN":c="Math.tan("+a+
" / 180 * Math.PI)"}if(c)return[c,Blockly.Dart.ORDER_UNARY_POSTFIX];switch(b){case "LOG10":c="Math.log("+a+") / Math.log(10)";break;case "ASIN":c="Math.asin("+a+") / Math.PI * 180";break;case "ACOS":c="Math.acos("+a+") / Math.PI * 180";break;case "ATAN":c="Math.atan("+a+") / Math.PI * 180";break;default:throw"Unknown math operator: "+b;}return[c,Blockly.Dart.ORDER_MULTIPLICATIVE]};
Blockly.Dart.math_constant=function(a){var b={PI:["Math.PI",Blockly.Dart.ORDER_UNARY_POSTFIX],E:["Math.E",Blockly.Dart.ORDER_UNARY_POSTFIX],GOLDEN_RATIO:["(1 + Math.sqrt(5)) / 2",Blockly.Dart.ORDER_MULTIPLICATIVE],SQRT2:["Math.SQRT2",Blockly.Dart.ORDER_UNARY_POSTFIX],SQRT1_2:["Math.SQRT1_2",Blockly.Dart.ORDER_UNARY_POSTFIX],INFINITY:["double.INFINITY",Blockly.Dart.ORDER_ATOMIC]};a=a.getFieldValue("CONSTANT");"INFINITY"!=a&&(Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;");
return b[a]};
Blockly.Dart.math_number_property=function(a){var b=Blockly.Dart.valueToCode(a,"NUMBER_TO_CHECK",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!b)return["false",Blockly.Python.ORDER_ATOMIC];var c=a.getFieldValue("PROPERTY"),d;if("PRIME"==c)return Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;",[Blockly.Dart.provideFunction_("math_isPrime",["bool "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(n) {"," // https://en.wikipedia.org/wiki/Primality_test#Naive_methods"," if (n == 2 || n == 3) {"," return true;",
" }"," // False if n is null, negative, is 1, or not whole."," // And false if n is divisible by 2 or 3."," if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 || n % 3 == 0) {"," return false;"," }"," // Check all the numbers of form 6k +/- 1, up to sqrt(n)."," for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {"," if (n % (x - 1) == 0 || n % (x + 1) == 0) {"," return false;"," }"," }"," return true;","}"])+"("+b+")",Blockly.Dart.ORDER_UNARY_POSTFIX];switch(c){case "EVEN":d=b+
" % 2 == 0";break;case "ODD":d=b+" % 2 == 1";break;case "WHOLE":d=b+" % 1 == 0";break;case "POSITIVE":d=b+" > 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=Blockly.Dart.valueToCode(a,"DIVISOR",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!a)return["false",Blockly.Python.ORDER_ATOMIC];d=b+" % "+a+" == 0"}return[d,Blockly.Dart.ORDER_EQUALITY]};
Blockly.Dart.math_number_property=function(a){var b=Blockly.Dart.valueToCode(a,"NUMBER_TO_CHECK",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!b)return["false",Blockly.Python.ORDER_ATOMIC];var c=a.getFieldValue("PROPERTY");if("PRIME"==c)return Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;",[Blockly.Dart.provideFunction_("math_isPrime",["bool "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(n) {"," // https://en.wikipedia.org/wiki/Primality_test#Naive_methods"," if (n == 2 || n == 3) {"," return true;",
" }"," // False if n is null, negative, is 1, or not whole."," // And false if n is divisible by 2 or 3."," if (n == null || n <= 1 || n % 1 != 0 || n % 2 == 0 || n % 3 == 0) {"," return false;"," }"," // Check all the numbers of form 6k +/- 1, up to sqrt(n)."," for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {"," if (n % (x - 1) == 0 || n % (x + 1) == 0) {"," return false;"," }"," }"," return true;","}"])+"("+b+")",Blockly.Dart.ORDER_UNARY_POSTFIX];switch(c){case "EVEN":var d=
b+" % 2 == 0";break;case "ODD":d=b+" % 2 == 1";break;case "WHOLE":d=b+" % 1 == 0";break;case "POSITIVE":d=b+" > 0";break;case "NEGATIVE":d=b+" < 0";break;case "DIVISIBLE_BY":a=Blockly.Dart.valueToCode(a,"DIVISOR",Blockly.Dart.ORDER_MULTIPLICATIVE);if(!a)return["false",Blockly.Python.ORDER_ATOMIC];d=b+" % "+a+" == 0"}return[d,Blockly.Dart.ORDER_EQUALITY]};
Blockly.Dart.math_change=function(a){var b=Blockly.Dart.valueToCode(a,"DELTA",Blockly.Dart.ORDER_ADDITIVE)||"0";a=Blockly.Dart.variableDB_.getName(a.getFieldValue("VAR"),Blockly.Variables.NAME_TYPE);return a+" = ("+a+" is num ? "+a+" : 0) + "+b+";\n"};Blockly.Dart.math_round=Blockly.Dart.math_single;Blockly.Dart.math_trig=Blockly.Dart.math_single;
Blockly.Dart.math_on_list=function(a){var b=a.getFieldValue("OP");a=Blockly.Dart.valueToCode(a,"LIST",Blockly.Dart.ORDER_NONE)||"[]";switch(b){case "SUM":b=Blockly.Dart.provideFunction_("math_sum",["num "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List myList) {"," num sumVal = 0;"," myList.forEach((num entry) {sumVal += entry;});"," return sumVal;","}"]);b=b+"("+a+")";break;case "MIN":Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";b=Blockly.Dart.provideFunction_("math_min",
["num "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List myList) {"," if (myList.isEmpty) return null;"," num minVal = myList[0];"," myList.forEach((num entry) {minVal = Math.min(minVal, entry);});"," return minVal;","}"]);b=b+"("+a+")";break;case "MAX":Blockly.Dart.definitions_.import_dart_math="import 'dart:math' as Math;";b=Blockly.Dart.provideFunction_("math_max",["num "+Blockly.Dart.FUNCTION_NAME_PLACEHOLDER_+"(List myList) {"," if (myList.isEmpty) return null;"," num maxVal = myList[0];",
+7 -34
Ver Arquivo
@@ -4,39 +4,18 @@
<meta charset="utf-8">
<title>Accessible Blockly Demo</title>
<!-- Load Blockly -->
<script src="../../blockly_compressed.js"></script>
<script src="../../blocks_compressed.js"></script>
<script src="../../javascript_compressed.js"></script>
<script src="../../msg/js/en.js"></script>
<script src="../../msg/messages.js"></script>
<script src="../../accessible/messages.js"></script>
<!-- Load accessibleBlockly -->
<script src="../../accessible/libs/es6-shim.min.js"></script>
<script src="../../accessible/libs/angular2-polyfills.min.js"></script>
<script src="../../accessible/libs/Rx.umd.min.js"></script>
<script src="../../accessible/libs/angular2-all.umd.min.js"></script>
<script src="../../accessible/utils.service.js"></script>
<script src="../../accessible/notifications.service.js"></script>
<script src="../../accessible/audio.service.js"></script>
<script src="../../accessible/block-connection.service.js"></script>
<script src="../../accessible/block-options-modal.service.js"></script>
<script src="../../accessible/keyboard-input.service.js"></script>
<script src="../../accessible/tree.service.js"></script>
<script src="../../accessible/toolbox-modal.service.js"></script>
<script src="../../accessible/translate.pipe.js"></script>
<script src="../../accessible/variable-modal.service.js"></script>
<!-- Load accessibleBlockly -->
<script src="../../blockly_accessible_compressed.js"></script>
<script src="../../accessible/field-segment.component.js"></script>
<script src="../../accessible/block-options-modal.component.js"></script>
<script src="../../accessible/toolbox-modal.component.js"></script>
<script src="../../accessible/variable-modal.component.js"></script>
<script src="../../accessible/sidebar.component.js"></script>
<script src="../../accessible/workspace-block.component.js"></script>
<script src="../../accessible/workspace.component.js"></script>
<script src="../../accessible/app.component.js"></script>
<!-- Load Blockly -->
<script src="../../msg/js/en.js"></script>
<script src="../../accessible/messages.js"></script>
<script src="../../blocks_compressed.js"></script>
<link rel="stylesheet" href="../../accessible/media/accessible.css">
<style>
@@ -165,13 +144,6 @@
</block>
</value>
</block>
<block type="math_change">
<value name="DELTA">
<block type="math_number">
<field name="NUM">1</field>
</block>
</value>
</block>
<block type="math_round">
<value name="NUM">
<block type="math_number">
@@ -350,6 +322,7 @@
</value>
</block>
</category>
<category name="Variables" colour="330" custom="VARIABLE"></category>
</xml>
</body>
+210
Ver Arquivo
@@ -0,0 +1,210 @@
/**
* @license
* Blockly Demos: Block Factory
*
* Copyright 2017 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 Stubbed interface functions for analytics integration.
*/
goog.provide('BlocklyDevTools.Analytics');
/**
* Whether these stub methods should log analytics calls to the console.
* @private
* @const
*/
BlocklyDevTools.Analytics.LOG_TO_CONSOLE_ = false;
/**
* An import/export type id for a library of BlockFactory's original block
* save files (each a serialized workspace of block definition blocks).
* @package
* @const
*/
BlocklyDevTools.Analytics.BLOCK_FACTORY_LIBRARY = "Block Factory library";
/**
* An import/export type id for a standard Blockly library of block
* definitions.
* @package
* @const
*/
BlocklyDevTools.Analytics.BLOCK_DEFINITIONS = "Block definitions";
/**
* An import/export type id for a code generation function, or a
* boilerplate stub of the same.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.GENERATOR = "Generator";
/**
* An import/export type id for a Blockly Toolbox.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.TOOLBOX = "Toolbox";
/**
* An import/export type id for the serialized contents of a workspace.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.WORKSPACE_CONTENTS = "Workspace contents";
/**
* Format id for imported/exported JavaScript resources.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.FORMAT_JS = "JavaScript";
/**
* Format id for imported/exported JSON resources.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.FORMAT_JSON = "JSON";
/**
* Format id for imported/exported XML resources.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.FORMAT_XML = "XML";
/**
* Platform id for resources exported for use in Android projects.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.PLATFORM_ANDROID = "Android";
/**
* Platform id for resources exported for use in iOS projects.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.PLATFORM_IOS = "iOS";
/**
* Platform id for resources exported for use in web projects.
*
* @package
* @const
*/
BlocklyDevTools.Analytics.PLATFORM_WEB = "web";
/**
* Initializes the analytics framework, including noting that the page/app was
* opened.
* @package
*/
BlocklyDevTools.Analytics.init = function() {
// stub
this.LOG_TO_CONSOLE_ && console.log('Analytics.init');
};
/**
* Event noting the user navigated to a specific view.
*
* @package
* @param viewId {string} An identifier for the view state.
*/
BlocklyDevTools.Analytics.onNavigateTo = function(viewId) {
// stub
this.LOG_TO_CONSOLE_ &&
console.log('Analytics.onNavigateTo(' + viewId + ')');
};
/**
* Event noting a project resource was saved. In the web Block Factory, this
* means saved to localStorage.
*
* @package
* @param typeId {string} An identifying string for the saved type.
*/
BlocklyDevTools.Analytics.onSave = function(typeId) {
// stub
this.LOG_TO_CONSOLE_ && console.log('Analytics.onSave(' + typeId + ')');
};
/**
* Event noting the user attempted to import a resource file.
*
* @package
* @param typeId {string} An identifying string for the imported type.
* @param optMetadata {Object} Metadata about the import, such as format and
* platform.
*/
BlocklyDevTools.Analytics.onImport = function(typeId, optMetadata) {
// stub
this.LOG_TO_CONSOLE_ && console.log('Analytics.onImport(' + typeId +
(optMetadata ? '): ' + JSON.stringify(optMetadata) : ')'));
};
/**
* Event noting a project resource was saved. In the web Block Factory, this
* means downloaded to the user's system.
*
* @package
* @param typeId {string} An identifying string for the exported object type.
* @param optMetadata {Object} Metadata about the import, such as format and
* platform.
*/
BlocklyDevTools.Analytics.onExport = function(typeId, optMetadata) {
// stub
this.LOG_TO_CONSOLE_ && console.log('Analytics.onExport(' + typeId +
(optMetadata ? '): ' + JSON.stringify(optMetadata) : ')'));
};
/**
* Event noting the system encountered an error. It should attempt to send
* immediately.
*
* @package
* @param e {!Object} A value representing or describing the error.
*/
BlocklyDevTools.Analytics.onError = function(e) {
// stub
this.LOG_TO_CONSOLE_ &&
console.log('Analytics.onError("' + e.toString() + '")');
};
/**
* Event noting the user was notified with a warning.
*
* @package
* @param msg {string} The warning message, or a description thereof.
*/
BlocklyDevTools.Analytics.onWarning = function(msg) {
// stub
this.LOG_TO_CONSOLE_ && console.log('Analytics.onWarning("' + msg + '")');
};
/**
* Request the analytics framework to send any queued events to the server.
* @package
*/
BlocklyDevTools.Analytics.sendQueued = function() {
// stub
this.LOG_TO_CONSOLE_ && console.log('Analytics.sendQueued');
};
+35 -12
Ver Arquivo
@@ -28,6 +28,7 @@
goog.provide('AppController');
goog.require('BlockFactory');
goog.require('BlocklyDevTools.Analytics');
goog.require('FactoryUtils');
goog.require('BlockLibraryController');
goog.require('BlockExporterController');
@@ -85,6 +86,10 @@ AppController.prototype.importBlockLibraryFromFile = function() {
var files = document.getElementById('files');
// If the file list is empty, the user likely canceled in the dialog.
if (files.files.length > 0) {
BlocklyDevTools.Analytics.onImport(
BlocklyDevTools.Analytics.BLOCK_FACTORY_LIBRARY,
{ format: BlocklyDevTools.Analytics.FORMAT_XML });
// The input tag doesn't have the "multiple" attribute
// so the user can only choose 1 file.
var file = files.files[0];
@@ -137,9 +142,14 @@ AppController.prototype.exportBlockLibraryToFile = function() {
// Download file if all necessary parameters are provided.
if (filename) {
FactoryUtils.createAndDownloadFile(blockLibText, filename, 'xml');
BlocklyDevTools.Analytics.onExport(
BlocklyDevTools.Analytics.BLOCK_FACTORY_LIBRARY,
{ format: BlocklyDevTools.Analytics.FORMAT_XML });
} else {
alert('Could not export Block Library without file name under which to ' +
'save library.');
var msg = 'Could not export Block Library without file name under which ' +
'to save library.';
BlocklyDevTools.Analytics.onWarning(msg);
alert(msg);
}
};
@@ -201,7 +211,7 @@ AppController.prototype.formatBlockLibraryForImport_ = function(xmlText) {
var blockType = this.getBlockTypeFromXml_(xmlText).toLowerCase();
// Some names are invalid so fix them up.
blockType = FactoryUtils.cleanBlockType(blockType);
blockXmlTextMap[blockType] = xmlText;
}
@@ -285,13 +295,17 @@ AppController.prototype.onTab = function() {
var hasUnsavedChanges =
!FactoryUtils.savedBlockChanges(this.blockLibraryController);
if (hasUnsavedChanges &&
!confirm('You have unsaved changes in Block Factory.')) {
// If the user doesn't want to switch tabs with unsaved changes,
// stay on Block Factory Tab.
this.setSelected_(AppController.BLOCK_FACTORY);
this.lastSelectedTab = AppController.BLOCK_FACTORY;
return;
if (hasUnsavedChanges) {
var msg = 'You have unsaved changes in Block Factory.';
var continueAnyway = confirm(msg);
BlocklyDevTools.Analytics.onWarning(msg);
if (!continueAnyway) {
// If the user doesn't want to switch tabs with unsaved changes,
// stay on Block Factory Tab.
this.setSelected_(AppController.BLOCK_FACTORY);
this.lastSelectedTab = AppController.BLOCK_FACTORY;
return;
}
}
}
@@ -304,6 +318,8 @@ AppController.prototype.onTab = function() {
this.styleTabs_();
if (this.selectedTab == AppController.EXPORTER) {
BlocklyDevTools.Analytics.onNavigateTo('Exporter');
// Hide other tabs.
FactoryUtils.hide('workspaceFactoryContent');
FactoryUtils.hide('blockFactoryContent');
@@ -325,6 +341,8 @@ AppController.prototype.onTab = function() {
this.exporter.updatePreview();
} else if (this.selectedTab == AppController.BLOCK_FACTORY) {
BlocklyDevTools.Analytics.onNavigateTo('BlockFactory');
// Hide other tabs.
FactoryUtils.hide('blockLibraryExporter');
FactoryUtils.hide('workspaceFactoryContent');
@@ -332,6 +350,9 @@ AppController.prototype.onTab = function() {
FactoryUtils.show('blockFactoryContent');
} else if (this.selectedTab == AppController.WORKSPACE_FACTORY) {
// TODO: differentiate Workspace and Toolbox editor, based on the other tab state.
BlocklyDevTools.Analytics.onNavigateTo('WorkspaceFactory');
// Hide other tabs.
FactoryUtils.hide('blockLibraryExporter');
FactoryUtils.hide('blockFactoryContent');
@@ -563,9 +584,9 @@ AppController.prototype.addBlockFactoryEventListeners = function() {
document.getElementById('direction')
.addEventListener('change', BlockFactory.updatePreview);
document.getElementById('languageTA')
.addEventListener('change', BlockFactory.updatePreview);
.addEventListener('change', BlockFactory.manualEdit);
document.getElementById('languageTA')
.addEventListener('keyup', BlockFactory.updatePreview);
.addEventListener('keyup', BlockFactory.manualEdit);
document.getElementById('format')
.addEventListener('change', BlockFactory.formatChange);
document.getElementById('language')
@@ -624,12 +645,14 @@ AppController.prototype.onresize = function(event) {
* @param {!Event} e beforeunload event.
*/
AppController.prototype.confirmLeavePage = function(e) {
BlocklyDevTools.Analytics.sendQueued();
if ((!BlockFactory.isStarterBlock() &&
!FactoryUtils.savedBlockChanges(blocklyFactory.blockLibraryController)) ||
blocklyFactory.workspaceFactoryController.hasUnsavedChanges()) {
var confirmationMessage = 'You will lose any unsaved changes. ' +
'Are you sure you want to exit this page?';
BlocklyDevTools.Analytics.onWarning(confirmationMessage);
e.returnValue = confirmationMessage;
return confirmationMessage;
}
@@ -0,0 +1,749 @@
/**
* Copyright 2017 Juan Carlos Orozco Arena
* Apache License Version 2.0
*/
/**
* @fileoverview
* The BlockDefinitionExtractor is a class that generates a workspace DOM
* suitable for the BlockFactory's block editor, derived from an example
* Blockly.Block.
*
* <code>
* var workspaceDom = new BlockDefinitionExtractor()
* .buildBlockFactoryWorkspace(exampleBlocklyBlock);
* Blockly.Xml.domToWorkspace(workspaceDom, BlockFactory.mainWorkspace);
* </code>
*
* The <code>exampleBlocklyBlock</code> is usually the block loaded into the
* preview workspace after manually entering the block definition.
*
* @author JC-Orozco (Juan Carlos Orozco), AnmAtAnm (Andrew n marshall)
*/
'use strict';
/**
* Namespace for BlockDefinitionExtractor.
*/
goog.provide('BlockDefinitionExtractor');
/**
* Class to contain all functions needed to extract block definition from
* the block preview data structure.
* @namespace
*/
BlockDefinitionExtractor = BlockDefinitionExtractor || Object.create(null);
/**
* Builds a BlockFactory workspace that reflects the block structure of the
* exmaple block.
*
* @param {!Blockly.Block} block The reference block from which the definition
* will be extracted.
* @return {!Element} Returns the root workspace DOM <xml> for the block editor
* workspace.
*/
BlockDefinitionExtractor.buildBlockFactoryWorkspace = function(block) {
var workspaceXml = goog.dom.createDom('xml');
workspaceXml.append(
BlockDefinitionExtractor.factoryBase_(block, block.type));
return workspaceXml;
};
/**
* Helper function to create a new Element with the provided attributes and
* inner text.
*
* @param {string} name New element tag name.
* @param {Map<String,String>} opt_attrs Optional list of attributes.
* @param {string?} opt_text Optional inner text.
* @return {!Element} The newly created element.
* @private
*/
BlockDefinitionExtractor.newDomElement_ = function(name, opt_attrs, opt_text) {
// Avoid createDom(..)'s attributes argument for being too HTML specific.
var elem = goog.dom.createDom(name);
if (opt_attrs) {
for (var key in opt_attrs) {
elem.setAttribute(key, opt_attrs[key]);
}
}
if (opt_text) {
elem.append(opt_text);
}
return elem;
};
/**
* Creates an connection type constraint <block> Element representing the
* requested type.
*
* @param {string} type Type name of desired connection constraint.
* @return {!Element} The <block> representing the the constraint type.
* @private
*/
BlockDefinitionExtractor.buildBlockForType_ = function(type) {
switch (type) {
case 'Null':
return BlockDefinitionExtractor.typeNull_();
case 'Boolean':
return BlockDefinitionExtractor.typeBoolean_();
case 'Number':
return BlockDefinitionExtractor.typeNumber_();
case 'String':
return BlockDefinitionExtractor.typeString_();
case 'Array':
return BlockDefinitionExtractor.typeList_();
default:
return BlockDefinitionExtractor.typeOther_(type);
}
};
/**
* Constructs a <block> element representing the type constraints of the
* provided connection.
*
* @param {!Blockly.Connection} connection The connection with desired
* connection constraints.
* @return {!Element} The root <block> element of the constraint definition.
* @private
*/
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_ =
function(connection)
{
var typeBlock;
if (connection.check_) {
if (connection.check_.length < 1) {
typeBlock = BlockDefinitionExtractor.typeNullShadow_();
} else if (connection.check_.length === 1) {
typeBlock = BlockDefinitionExtractor.buildBlockForType_(
connection.check_[0]);
} else if (connection.check_.length > 1 ) {
typeBlock = BlockDefinitionExtractor.typeGroup_(connection.check_);
}
} else {
typeBlock = BlockDefinitionExtractor.typeNullShadow_();
}
return typeBlock;
};
/**
* Creates the root "factory_base" <block> element for the block definition.
*
* @param {!Blockly.Block} block The example block from which to extract the
* definition.
* @param {string} name Block name.
* @return {!Element} The factory_base block element.
* @private
*/
BlockDefinitionExtractor.factoryBase_ = function(block, name) {
BlockDefinitionExtractor.src = {root: block, current: block};
var factoryBaseEl =
BlockDefinitionExtractor.newDomElement_('block', {type: 'factory_base'});
factoryBaseEl.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'NAME'}, name));
factoryBaseEl.append(BlockDefinitionExtractor.buildInlineField_(block));
BlockDefinitionExtractor.buildConnections_(block, factoryBaseEl);
var inputsStatement = BlockDefinitionExtractor.newDomElement_(
'statement', {name: 'INPUTS'});
inputsStatement.append(BlockDefinitionExtractor.parseInputs_(block));
factoryBaseEl.append(inputsStatement);
var tooltipValue =
BlockDefinitionExtractor.newDomElement_('value', {name: 'TOOLTIP'});
tooltipValue.append(BlockDefinitionExtractor.text_(block.tooltip));
factoryBaseEl.append(tooltipValue);
var helpUrlValue =
BlockDefinitionExtractor.newDomElement_('value', {name: 'HELPURL'});
helpUrlValue.append(BlockDefinitionExtractor.text_(block.helpUrl));
factoryBaseEl.append(helpUrlValue);
// Convert colour_ to hue value 0-360 degrees
// TODO(#1247): Solve off-by-one errors.
// TODO: Deal with colors that don't map to standard hues. (Needs improved
// block definitions.)
var colour_hue = Math.floor(
goog.color.hexToHsv(block.colour_)[0]); // Off by one... sometimes
var colourBlock = BlockDefinitionExtractor.colourBlockFromHue_(colour_hue);
var colourInputValue =
BlockDefinitionExtractor.newDomElement_('value', {name: 'COLOUR'});
colourInputValue.append(colourBlock);
factoryBaseEl.append(colourInputValue);
return factoryBaseEl;
};
/**
* Generates the appropriate <field> element for the block definition's
* CONNECTIONS field, which determines the next, previous, and output
* connections.
*
* @param {!Blockly.Block} block The example block from which to extract the
* definition.
* @param {!Element} factoryBaseEl The root of the block definition.
* @private
*/
BlockDefinitionExtractor.buildConnections_ = function(block, factoryBaseEl) {
var connections = 'NONE';
if (block.outputConnection) {
connections = 'LEFT';
} else {
if (block.previousConnection) {
if (block.nextConnection) {
connections = 'BOTH';
} else {
connections = 'TOP';
}
} else if (block.nextConnection) {
connections = 'BOTTOM';
}
}
factoryBaseEl.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'CONNECTIONS'}, connections));
if (connections === 'LEFT') {
var inputValue =
BlockDefinitionExtractor.newDomElement_('value', {name: 'OUTPUTTYPE'});
inputValue.append(
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
block.outputConnection));
factoryBaseEl.append(inputValue);
} else {
if (connections === 'UP' || connections === 'BOTH') {
var inputValue =
BlockDefinitionExtractor.newDomElement_('value', {name: 'TOPTYPE'});
inputValue.append(
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
block.previousConnection));
factoryBaseEl.append(inputValue);
}
if (connections === 'DOWN' || connections === 'BOTH') {
var inputValue = BlockDefinitionExtractor.newDomElement_(
'value', {name: 'BOTTOMTYPE'});
inputValue.append(
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
block.nextConnection));
factoryBaseEl.append(inputValue);
}
}
};
/**
* Generates the appropriate <field> element for the block definition's INLINE
* field.
*
* @param {!Blockly.Block} block The example block from which to extract the
* definition.
* @return {Element} The INLINE <field> with value 'AUTO', 'INT' (internal) or
* 'EXT' (external).
* @private
*/
BlockDefinitionExtractor.buildInlineField_ = function(block) {
var inline = 'AUTO'; // When block.inputsInlineDefault === undefined
if (block.inputsInlineDefault === true) {
inline = 'INT';
} else if (block.inputsInlineDefault === false) {
inline = 'EXT';
}
return BlockDefinitionExtractor.newDomElement_(
'field', {name: 'INLINE'}, inline);
};
/**
* Constructs a sequence of <block> elements that represent the inputs of the
* provided block.
*
* @param {!Blockly.Block} block The source block to copy the inputs of.
* @return {Element} The fist <block> element of the sequence
* (and the root of the constructed DOM).
* @private
*/
BlockDefinitionExtractor.parseInputs_ = function(block) {
var firstInputDefElement = null;
var lastInputDefElement = null;
for (var i = 0; i < block.inputList.length; i++) {
var input = block.inputList[i];
var align = 'LEFT'; // Left alignment is the default.
if (input.align === Blockly.ALIGN_CENTRE) {
align = 'CENTRE';
} else if (input.align === Blockly.ALIGN_RIGHT) {
align = 'RIGHT';
}
var inputDefElement = BlockDefinitionExtractor.input_(input, align);
if (lastInputDefElement) {
var next = BlockDefinitionExtractor.newDomElement_('next');
next.append(inputDefElement);
lastInputDefElement.append(next);
} else {
firstInputDefElement = inputDefElement;
}
lastInputDefElement = inputDefElement;
}
return firstInputDefElement;
};
/**
* Creates a <block> element representing a block input.
*
* @param {!Blockly.Input} input The input object.
* @param {string} align Can be left, right or centre.
* @return {!Element} The <block> element that defines the input.
* @private
*/
BlockDefinitionExtractor.input_ = function(input, align) {
var isDummy = (input.type === Blockly.DUMMY_INPUT);
var inputTypeAttr =
isDummy ? 'input_dummy' :
(input.type === Blockly.INPUT_VALUE) ? 'input_value' : 'input_statement';
var inputDefBlock =
BlockDefinitionExtractor.newDomElement_('block', {type: inputTypeAttr});
if (!isDummy) {
inputDefBlock.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'INPUTNAME'}, input.name));
}
inputDefBlock.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'ALIGN'}, align));
var fieldsDef = BlockDefinitionExtractor.newDomElement_(
'statement', {name: 'FIELDS'});
var fieldsXml = BlockDefinitionExtractor.buildFields_(input.fieldRow);
fieldsDef.append(fieldsXml);
inputDefBlock.append(fieldsDef);
if (!isDummy) {
var typeValue = BlockDefinitionExtractor.newDomElement_(
'value', {name: 'TYPE'});
typeValue.append(
BlockDefinitionExtractor.buildTypeConstraintBlockForConnection_(
input.connection));
inputDefBlock.append(typeValue);
}
return inputDefBlock;
};
/**
* Constructs a sequence <block> elements representing the field definition.
* @param {Array<Blockly.Field>} fieldRow A list of fields in a Blockly.Input.
* @return {Element} The fist <block> element of the sequence
* (and the root of the constructed DOM).
* @private
*/
BlockDefinitionExtractor.buildFields_ = function(fieldRow) {
var firstFieldDefElement = null;
var lastFieldDefElement = null;
for (var i = 0; i < fieldRow.length; i++) {
var field = fieldRow[i];
var fieldDefElement = BlockDefinitionExtractor.buildFieldElement_(field);
if (lastFieldDefElement) {
var next = BlockDefinitionExtractor.newDomElement_('next');
next.append(fieldDefElement);
lastFieldDefElement.append(next);
} else {
firstFieldDefElement = fieldDefElement;
}
lastFieldDefElement = fieldDefElement;
}
return firstFieldDefElement;
};
/**
* Constructs a <field> element that describes the provided Blockly.Field.
* @param {!Blockly.Field} field The field from which the definition is copied.
* @param {!Element} A <field> for the Field definition.
* @private
*/
BlockDefinitionExtractor.buildFieldElement_ = function(field) {
if (field instanceof Blockly.FieldLabel) {
return BlockDefinitionExtractor.buildFieldLabel_(field.text_);
} else if (field instanceof Blockly.FieldTextInput) {
return BlockDefinitionExtractor.buildFieldInput_(field.name, field.text_);
} else if (field instanceof Blockly.FieldNumber) {
return BlockDefinitionExtractor.buildFieldNumber_(
field.name, field.text_, field.min_, field.max_, field.presicion_);
} else if (field instanceof Blockly.FieldAngle) {
return BlockDefinitionExtractor.buildFieldAngle_(field.name, field.text_);
} else if (field instanceof Blockly.FieldCheckbox) {
return BlockDefinitionExtractor.buildFieldCheckbox_(field.name, field.state_);
} else if (field instanceof Blockly.FieldColour) {
return BlockDefinitionExtractor.buildFieldColour_(field.name, field.colour_);
} else if (field instanceof Blockly.FieldImage) {
return BlockDefinitionExtractor.buildFieldImage_(
field.src_, field.width_, field.height_, field.text_);
} else if (field instanceof Blockly.FieldVariable) {
// FieldVariable must be before FieldDropdown, because FieldVariable is a
// subclass.
return BlockDefinitionExtractor.buildFieldVariable_(field.name, field.text_);
} else if (field instanceof Blockly.FieldDropdown) {
return BlockDefinitionExtractor.buildFieldDropdown_(field);
}
throw Error('Unrecognized field class: ' + field.constructor.name);
};
/**
* Creates a <block> element representing a FieldLabel definition.
* @param {string} text
* @return {Element} The XML for FieldLabel definition.
* @private
*/
BlockDefinitionExtractor.buildFieldLabel_ = function(text) {
var fieldBlock =
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_static'});
fieldBlock.append(
BlockDefinitionExtractor.newDomElement_('field', {name: 'TEXT'}, text));
return fieldBlock;
};
/**
* Creates a <block> element representing a FieldInput (text input) definition.
*
* @param {string} fieldName The identifying name of the field.
* @param {string} text The default text string.
* @return {Element} The XML for FieldInput definition.
* @private
*/
BlockDefinitionExtractor.buildFieldInput_ = function(fieldName, text) {
var fieldInput =
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_input'});
fieldInput.append(
BlockDefinitionExtractor.newDomElement_('field', {name: 'TEXT'}, text));
fieldInput.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, fieldName));
return fieldInput;
};
/**
* Creates a <block> element representing a FieldNumber definition.
*
* @param {string} fieldName The identifying name of the field.
* @param {number} value The field's default value.
* @param {number} min The minimum allowed value, or negative infinity.
* @param {number} max The maximum allowed value, or positive infinity.
* @param {number} precision The precision allowed for the number.
* @return {Element} The XML for FieldNumber definition.
* @private
*/
BlockDefinitionExtractor.buildFieldNumber_ =
function(fieldName, value, min, max, precision)
{
var fieldNumber =
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_number'});
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'VALUE'}, value));
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, fieldName));
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'MIN'}, min));
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'MAX'}, max));
fieldNumber.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'PRECISION'}, precision));
return fieldNumber;
};
/**
* Creates a <block> element representing a FieldAngle definition.
*
* @param {string} fieldName The identifying name of the field.
* @param {number} angle The field's default value.
* @return {Element} The XML for FieldAngle definition.
* @private
*/
BlockDefinitionExtractor.buildFieldAngle_ = function(angle, fieldName) {
var fieldAngle =
BlockDefinitionExtractor.newDomElement_('block', {type: 'field_angle'});
fieldAngle.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'ANGLE'}, angle));
fieldAngle.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, fieldName));
return fieldAngle;
};
/**
* Creates a <block> element representing a FieldDropdown definition.
*
* @param {Blockly.FieldDropdown} dropdown
* @return {Element} The <block> element representing a similar FieldDropdown.
* @private
*/
BlockDefinitionExtractor.buildFieldDropdown_ = function(dropdown) {
var menuGenerator = dropdown.menuGenerator_;
if (typeof menuGenerator === 'function') {
var options = menuGenerator();
} else if (goog.isArray(menuGenerator)) {
var options = menuGenerator;
} else {
throw new Error('Unrecognized type of menuGenerator: ' + menuGenerator);
}
var fieldDropdown = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'field_dropdown'});
var optionsStr = '[';
var mutation = BlockDefinitionExtractor.newDomElement_('mutation');
fieldDropdown.append(mutation);
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, dropdown.name));
for (var i=0; i<options.length; i++) {
var option = options[i];
if (typeof option[0] === "string") {
optionsStr += '"text",'
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'USER'+i}, option[0]));
} else {
optionsStr += '"image",';
fieldDropdown.append(
BlockDefinitionExtractor.newDomElement_(
'field', {name: 'SRC'+i}, option[0].src));
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'WIDTH'+i}, option[0].width));
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'HEIGHT'+i}, option[0].height));
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'ALT'+i}, option[0].alt));
}
fieldDropdown.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'CPU'+i}, option[1]));
}
optionsStr = optionsStr.slice(0,-1); // Drop last comma
optionsStr += ']';
mutation.setAttribute('options', optionsStr);
return fieldDropdown;
};
/**
* Creates a <block> element representing a FieldCheckbox definition.
*
* @param {string} fieldName The identifying name of the field.
* @param {string} checked The field's default value, true or false.
* @return {Element} The XML for FieldCheckbox definition.
* @private
*/
BlockDefinitionExtractor.buildFieldCheckbox_ =
function(fieldName, checked)
{
var fieldCheckbox = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'field_checkbox'});
fieldCheckbox.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'CHECKED'}, checked));
fieldCheckbox.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, fieldName));
return fieldCheckbox;
};
/**
* Creates a <block> element representing a FieldColour definition.
*
* @param {string} fieldName The identifying name of the field.
* @param {string} colour The field's default value as a string.
* @return {Element} The XML for FieldColour definition.
* @private
*/
BlockDefinitionExtractor.buildFieldColour_ =
function(fieldName, colour)
{
var fieldColour = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'field_colour'});
fieldColour.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'COLOUR'}, colour));
fieldColour.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, fieldName));
return fieldColour;
};
/**
* Creates a <block> element representing a FieldVaraible definition.
*
* @param {string} fieldName The identifying name of the field.
* @param {string} varName The variables
* @return {Element} The <block> element representing the FieldVariable.
* @private
*/
BlockDefinitionExtractor.buildFieldVariable_ = function(fieldName, varName) {
var fieldVar = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'field_variable'});
fieldVar.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'FIELDNAME'}, fieldName));
fieldVar.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'TEXT'}, varName));
return fieldVar;
};
/**
* Creates a <block> element representing a FieldImage definition.
*
* @param {string} src The URL of the field image.
* @param {number} width The pixel width of the source image
* @param {number} height The pixel height of the source image.
* @param {string} alt Alterante text to describe image.
* @private
*/
BlockDefinitionExtractor.buildFieldImage_ =
function(src, width, height, alt)
{
var block1 = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'field_image'});
block1.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'SRC'}, src));
block1.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'WIDTH'}, width));
block1.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'HEIGHT'}, height));
block1.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'ALT'}, alt));
};
/**
* Creates a <block> element a group of allowed connection constraint types.
*
* @param {Array<string>} types List of type names in this group.
* @return {Element} The <block> element representing the group, with child
* types attached.
* @private
*/
BlockDefinitionExtractor.typeGroup_ = function(types) {
var typeGroupBlock = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'type_group'});
typeGroupBlock.append(BlockDefinitionExtractor.newDomElement_(
'mutation', {types:types.length}));
for (var i=0; i<types.length; i++) {
var typeBlock = BlockDefinitionExtractor.buildBlockForType_(types[i]);
var valueBlock = BlockDefinitionExtractor.newDomElement_(
'value', {name:'TYPE'+i});
valueBlock.append(typeBlock);
typeGroupBlock.append(valueBlock);
}
return typeGroupBlock;
};
/**
* Creates a <shadow> block element representing the default null connection
* constraint.
* @return {Element} The <block> element representing the "null" type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeNullShadow_ = function() {
return BlockDefinitionExtractor.newDomElement_(
'shadow', {type: 'type_null'});
};
/**
* Creates a <block> element representing null in a connection constraint.
* @return {Element} The <block> element representing the "null" type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeNull_ = function() {
return BlockDefinitionExtractor.newDomElement_('block', {type: 'type_null'});
};
/**
* Creates a <block> element representing the a boolean in a connection
* constraint.
* @return {Element} The <block> element representing the "boolean" type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeBoolean_ = function() {
return BlockDefinitionExtractor.newDomElement_(
'block', {type: 'type_boolean'});
};
/**
* Creates a <block> element representing the a number in a connection
* constraint.
* @return {Element} The <block> element representing the "number" type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeNumber_ = function() {
return BlockDefinitionExtractor.newDomElement_(
'block', {type: 'type_number'});
};
/**
* Creates a <block> element representing the a string in a connection
* constraint.
* @return {Element} The <block> element representing the "string" type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeString_ = function() {
return BlockDefinitionExtractor.newDomElement_(
'block', {type: 'type_string'});
};
/**
* Creates a <block> element representing the a list in a connection
* constraint.
* @return {Element} The <block> element representing the "list" type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeList_ = function() {
return BlockDefinitionExtractor.newDomElement_('block', {type: 'type_list'});
};
/**
* Creates a <block> element representing the given custom connection
* constraint type name.
*
* @param {string} type The connection constratin type name.
* @return {Element} The <block> element representing a custom input type
* constraint.
* @private
*/
BlockDefinitionExtractor.typeOther_ = function(type) {
var block = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'type_other'});
block.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'TYPE'}, type));
return block;
};
/**
* Creates a block Element for the color_hue block, with the given hue.
* @param hue {number} The hue value, from 0 to 360.
* @return {Element} The <block> Element representing a colour_hue block
* with the given hue.
* @private
*/
BlockDefinitionExtractor.colourBlockFromHue_ = function(hue) {
var colourBlock = BlockDefinitionExtractor.newDomElement_(
'block', {type: 'colour_hue'});
colourBlock.append(BlockDefinitionExtractor.newDomElement_('mutation', {
colour: Blockly.hueToRgb(hue)
}));
colourBlock.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'HUE'}, hue.toString()));
return colourBlock;
};
/**
* Creates a block Element for a text block with the given text.
*
* @param text {string} The text value of the block.
* @return {Element} The <block> element representing a "text" block.
* @private
*/
BlockDefinitionExtractor.text_ = function(text) {
var textBlock =
BlockDefinitionExtractor.newDomElement_('block', {type: 'text'});
if (text) {
textBlock.append(BlockDefinitionExtractor.newDomElement_(
'field', {name: 'TEXT'}, text));
} // Else, use empty string default.
return textBlock;
};
+18 -2
Ver Arquivo
@@ -31,6 +31,7 @@
goog.provide('BlockExporterController');
goog.require('BlocklyDevTools.Analytics');
goog.require('FactoryUtils');
goog.require('StandardCategories');
goog.require('BlockExporterView');
@@ -103,7 +104,9 @@ BlockExporterController.prototype.export = function() {
// User wants to export selected blocks' definitions.
if (!blockDef_filename) {
// User needs to enter filename.
alert('Please enter a filename for your block definition(s) download.');
var msg = 'Please enter a filename for your block definition(s) download.';
BlocklyDevTools.Analytics.onWarning(msg);
alert(msg);
} else {
// Get block definition code in the selected format for the blocks.
var blockDefs = this.tools.getBlockDefinitions(blockXmlMap,
@@ -111,6 +114,13 @@ BlockExporterController.prototype.export = function() {
// Download the file, using .js file ending for JSON or Javascript.
FactoryUtils.createAndDownloadFile(
blockDefs, blockDef_filename, 'javascript');
BlocklyDevTools.Analytics.onExport(
BlocklyDevTools.Analytics.BLOCK_DEFINITIONS,
{
format: (definitionFormat == 'JSON' ?
BlocklyDevTools.Analytics.FORMAT_JSON :
BlocklyDevTools.Analytics.FORMAT_JS)
});
}
}
@@ -118,7 +128,9 @@ BlockExporterController.prototype.export = function() {
// User wants to export selected blocks' generator stubs.
if (!generatorStub_filename) {
// User needs to enter filename.
alert('Please enter a filename for your generator stub(s) download.');
var msg = 'Please enter a filename for your generator stub(s) download.';
BlocklyDevTools.Analytics.onWarning(msg);
alert(msg);
} else {
// Get generator stub code in the selected language for the blocks.
var genStubs = this.tools.getGeneratorCode(blockXmlMap,
@@ -128,6 +140,10 @@ BlockExporterController.prototype.export = function() {
// Download the file.
FactoryUtils.createAndDownloadFile(
genStubs, generatorStub_filename, fileType);
BlocklyDevTools.Analytics.onExport(
BlocklyDevTools.Analytics.GENERATOR,
(fileType == 'javascript' ?
{ format: BlocklyDevTools.Analytics.FORMAT_JS } : undefined));
}
}
+10 -5
Ver Arquivo
@@ -34,6 +34,7 @@
goog.provide('BlockLibraryController');
goog.require('BlocklyDevTools.Analytics');
goog.require('BlockLibraryStorage');
goog.require('BlockLibraryView');
goog.require('BlockFactory');
@@ -110,8 +111,9 @@ BlockLibraryController.prototype.getSelectedBlockType = function() {
* updating the dropdown and displaying the starter block (factory_base).
*/
BlockLibraryController.prototype.clearBlockLibrary = function() {
var check = confirm('Delete all blocks from library?');
if (check) {
var msg = 'Delete all blocks from library?';
BlocklyDevTools.Analytics.onWarning(msg);
if (confirm(msg)) {
// Clear Block Library Storage.
this.storage.clear();
this.storage.saveToLocalStorage();
@@ -133,9 +135,11 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() {
// If user has not changed the name of the starter block.
if (blockType == 'block_type') {
// Do not save block if it has the default type, 'block_type'.
alert('You cannot save a block under the name "block_type". Try changing ' +
'the name before saving. Then, click on the "Block Library" button ' +
'to view your saved blocks.');
var msg = 'You cannot save a block under the name "block_type". Try ' +
'changing the name before saving. Then, click on the "Block Library"' +
' button to view your saved blocks.';
alert(msg);
BlocklyDevTools.Analytics.onWarning(msg);
return;
}
@@ -159,6 +163,7 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() {
// Add select handler to the new option.
this.addOptionSelectHandler(blockType);
BlocklyDevTools.Analytics.onSave('Block');
};
/**
+3 -3
Ver Arquivo
@@ -126,9 +126,9 @@ BlockOption.prototype.showPreviewBlock = function() {
var blockOptPreviewID = this.dom.id + '_workspace';
// Inject preview block.
var workspace = Blockly.inject(blockOptPreviewID, {readOnly:true});
Blockly.Xml.domToWorkspace(this.previewBlockXml, workspace);
this.previewWorkspace = workspace;
var demoWorkspace = Blockly.inject(blockOptPreviewID, {readOnly:true});
Blockly.Xml.domToWorkspace(this.previewBlockXml, demoWorkspace);
this.previewWorkspace = demoWorkspace;
// Center the preview block in the workspace.
this.centerBlock();

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais