From d4440d51657f5ffae232802b5b482172838b5296 Mon Sep 17 00:00:00 2001 From: Batalov Sergey Date: Thu, 25 Aug 2016 16:04:47 +0500 Subject: [PATCH 01/27] Fix reversed right/left in RTL --- core/toolbox.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/toolbox.js b/core/toolbox.js index 4af1b1dd9..77be45d4b 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -605,8 +605,10 @@ Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) { Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) { if (this.horizontalLayout_) { var map = {}; - map[goog.events.KeyCodes.RIGHT] = goog.events.KeyCodes.DOWN; - map[goog.events.KeyCodes.LEFT] = goog.events.KeyCodes.UP; + var next = goog.events.KeyCodes.DOWN + var prev = goog.events.KeyCodes.UP + map[goog.events.KeyCodes.RIGHT] = this.rightToLeft_ ? prev : next; + map[goog.events.KeyCodes.LEFT] = this.rightToLeft_ ? next : prev; map[goog.events.KeyCodes.UP] = goog.events.KeyCodes.LEFT; map[goog.events.KeyCodes.DOWN] = goog.events.KeyCodes.RIGHT; From ab101ee3b780001e2fa1292f01a7fff8f2a3db4a Mon Sep 17 00:00:00 2001 From: Batalov Sergey Date: Thu, 25 Aug 2016 16:07:36 +0500 Subject: [PATCH 02/27] TreeSeparator node did not know about horiz. layout. Fixed. --- core/toolbox.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/toolbox.js b/core/toolbox.js index 77be45d4b..d559b577d 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -542,7 +542,6 @@ Blockly.Toolbox.TreeControl.prototype.setSelectedItem = function(node) { Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) { goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper); if (toolbox) { - this.horizontalLayout_ = toolbox.horizontalLayout_; var resize = function() { // Even though the div hasn't changed size, the visible workspace // surface of the workspace has, so we may need to reposition everything. @@ -603,7 +602,7 @@ Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) { * @private */ Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) { - if (this.horizontalLayout_) { + if (this.tree.toolbox_.horizontalLayout_) { var map = {}; var next = goog.events.KeyCodes.DOWN var prev = goog.events.KeyCodes.UP From ad3541992edc7bc34ffb42d72c00d8121df4903b Mon Sep 17 00:00:00 2001 From: Troy McKinnon Date: Fri, 30 Sep 2016 16:26:44 -0500 Subject: [PATCH 03/27] add a few type definitions --- core/toolbox.js | 5 ++++- core/workspace_svg.js | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/toolbox.js b/core/toolbox.js index caa03c54b..e67936a4b 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -148,7 +148,10 @@ Blockly.Toolbox.prototype.init = function() { var workspace = this.workspace_; var svg = this.workspace_.getParentSvg(); - // Create an HTML container for the Toolbox menu. + /** + * HTML container for the Toolbox menu. + * @type {Element} + */ this.HtmlDiv = goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyToolboxDiv'); this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR'); diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 6c72c934e..4559decc8 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -254,6 +254,10 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { // Determine if there needs to be a category tree, or a simple list of // blocks. This cannot be changed later, since the UI is very different. if (this.options.hasCategories) { + /** + * @type {Blockly.Toolbox} + * @private + */ this.toolbox_ = new Blockly.Toolbox(this); } else if (this.options.languageTree) { this.addFlyout_(); From 832df810c1873bb47efe5099633285d3ed483cf5 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Sun, 9 Oct 2016 13:11:21 -0500 Subject: [PATCH 04/27] Stops duplicating on rename of old ID to old ID. --- core/workspace.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/workspace.js b/core/workspace.js index 89912a724..805790869 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -251,9 +251,9 @@ Blockly.Workspace.prototype.renameVariable = function(oldName, newName) { this.variableList[variableIndex] = newName; } else if (variableIndex != -1 && newVariableIndex != -1) { // Renaming one existing variable to another existing variable. - this.variableList.splice(variableIndex, 1); - // The case might have changed. + // 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.'); From 9fe29a9cc24b167e130913759c25fbc65693c591 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Mon, 17 Oct 2016 17:32:14 -0700 Subject: [PATCH 05/27] Block Factory: Add missing workspace options, fix reset button. --- demos/blockfactory/index.html | 71 +++++++++---------- .../workspacefactory/wfactory_controller.js | 52 ++++++++------ .../workspacefactory/wfactory_init.js | 13 ++-- .../workspacefactory/wfactory_view.js | 16 ++++- 4 files changed, 84 insertions(+), 68 deletions(-) diff --git a/demos/blockfactory/index.html b/demos/blockfactory/index.html index b76151b74..6dfea2e17 100644 --- a/demos/blockfactory/index.html +++ b/demos/blockfactory/index.html @@ -225,49 +225,42 @@
-
-
-
+
+
+ +
+
+
+
+ +
-
-
-
-
-
-
-
- -
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- +
+
+
+
diff --git a/demos/blockfactory/workspacefactory/wfactory_controller.js b/demos/blockfactory/workspacefactory/wfactory_controller.js index fe609564d..85b14d11b 100644 --- a/demos/blockfactory/workspacefactory/wfactory_controller.js +++ b/demos/blockfactory/workspacefactory/wfactory_controller.js @@ -1105,33 +1105,42 @@ WorkspaceFactoryController.prototype.readOptions_ = function() { // Add all standard options to the options object. // Use parse int to get numbers from value inputs. - optionsObj['collapse'] = - document.getElementById('option_collapse_checkbox').checked; - optionsObj['comments'] = - document.getElementById('option_comments_checkbox').checked; - optionsObj['css'] = document.getElementById('option_css_checkbox').checked; - optionsObj['disable'] = - document.getElementById('option_disable_checkbox').checked; - if (document.getElementById('option_infiniteBlocks_checkbox').checked) { - optionsObj['maxBlocks'] = Infinity; + var readonly = document.getElementById('option_readOnly_checkbox').checked; + if (readonly) { + optionsObj['readOnly'] = true; } else { - var maxBlocksValue = - document.getElementById('option_maxBlocks_number').value; - optionsObj['maxBlocks'] = typeof maxBlocksValue == 'string' ? - parseInt(maxBlocksValue) : maxBlocksValue; + optionsObj['collapse'] = + document.getElementById('option_collapse_checkbox').checked; + optionsObj['comments'] = + document.getElementById('option_comments_checkbox').checked; + optionsObj['disable'] = + document.getElementById('option_disable_checkbox').checked; + if (document.getElementById('option_infiniteBlocks_checkbox').checked) { + optionsObj['maxBlocks'] = Infinity; + } else { + var maxBlocksValue = + document.getElementById('option_maxBlocks_number').value; + optionsObj['maxBlocks'] = typeof maxBlocksValue == 'string' ? + parseInt(maxBlocksValue) : maxBlocksValue; + } + optionsObj['trashcan'] = + document.getElementById('option_trashcan_checkbox').checked; + optionsObj['horizontalLayout'] = + document.getElementById('option_horizontalLayout_checkbox').checked; + optionsObj['toolboxPosition'] = + document.getElementById('option_toolboxPosition_checkbox').checked ? + 'end' : 'start'; } + + optionsObj['css'] = document.getElementById('option_css_checkbox').checked; optionsObj['media'] = document.getElementById('option_media_text').value; - optionsObj['readOnly'] = - document.getElementById('option_readOnly_checkbox').checked; optionsObj['rtl'] = document.getElementById('option_rtl_checkbox').checked; optionsObj['scrollbars'] = document.getElementById('option_scrollbars_checkbox').checked; optionsObj['sounds'] = document.getElementById('option_sounds_checkbox').checked; - if (!optionsObj['readOnly']) { - optionsObj['trashcan'] = - document.getElementById('option_trashcan_checkbox').checked; - } + optionsObj['oneBasedIndex'] = + document.getElementById('option_oneBasedIndex_checkbox').checked; // If using a grid, add all grid options. if (document.getElementById('option_grid_checkbox').checked) { @@ -1144,7 +1153,10 @@ WorkspaceFactoryController.prototype.readOptions_ = function() { grid['length'] = typeof lengthValue == 'string' ? parseInt(lengthValue) : lengthValue; grid['colour'] = document.getElementById('gridOption_colour_text').value; - grid['snap'] = document.getElementById('gridOption_snap_checkbox').checked; + if (!readonly) { + grid['snap'] = + document.getElementById('gridOption_snap_checkbox').checked; + } optionsObj['grid'] = grid; } diff --git a/demos/blockfactory/workspacefactory/wfactory_init.js b/demos/blockfactory/workspacefactory/wfactory_init.js index 341af46fb..49a78fe94 100644 --- a/demos/blockfactory/workspacefactory/wfactory_init.js +++ b/demos/blockfactory/workspacefactory/wfactory_init.js @@ -513,7 +513,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryOptionsListeners_ = 'block' : 'none'; }); - // Checking the grid checkbox displays zoom options. + // Checking the zoom checkbox displays zoom options. document.getElementById('option_zoom_checkbox').addEventListener('change', function(e) { document.getElementById('zoom_options').style.display = @@ -521,12 +521,12 @@ WorkspaceFactoryInit.addWorkspaceFactoryOptionsListeners_ = 'block' : 'none'; }); + // Checking the readonly checkbox enables/disables other options. document.getElementById('option_readOnly_checkbox').addEventListener('change', function(e) { var checkbox = document.getElementById('option_readOnly_checkbox'); blocklyFactory.ifCheckedEnable(!checkbox.checked, - ['readonly1', 'readonly2', 'readonly3', 'readonly4', 'readonly5', - 'readonly6', 'readonly7']); + ['readonly1', 'readonly2']); }); document.getElementById('option_infiniteBlocks_checkbox').addEventListener('change', @@ -537,9 +537,10 @@ WorkspaceFactoryInit.addWorkspaceFactoryOptionsListeners_ = }); // Generate new options every time an options input is updated. - var optionsElements = document.getElementsByClassName('optionsInput'); - for (var i = 0; i < optionsElements.length; i++) { - optionsElements[i].addEventListener('change', function() { + var div = document.getElementById('workspace_options'); + var options = div.getElementsByTagName('input'); + for (var i = 0, option; option = options[i]; i++) { + option.addEventListener('change', function() { controller.generateNewOptions(); }); } diff --git a/demos/blockfactory/workspacefactory/wfactory_view.js b/demos/blockfactory/workspacefactory/wfactory_view.js index 0aebd2a21..677d2eda6 100644 --- a/demos/blockfactory/workspacefactory/wfactory_view.js +++ b/demos/blockfactory/workspacefactory/wfactory_view.js @@ -382,15 +382,25 @@ WorkspaceFactoryView.prototype.updateHelpText = function(mode) { * or a single flyout of blocks. Updates checkboxes and text fields. */ WorkspaceFactoryView.prototype.setBaseOptions = function() { + // Readonly mode. + document.getElementById('option_readOnly_checkbox').checked = false; + blocklyFactory.ifCheckedEnable(true, ['readonly1', 'readonly2']); + // Set basic options. document.getElementById('option_css_checkbox').checked = true; - document.getElementById('option_infiniteBlocks_checkbox').checked = true; document.getElementById('option_maxBlocks_number').value = 100; document.getElementById('option_media_text').value = 'https://blockly-demo.appspot.com/static/media/'; - document.getElementById('option_readOnly_checkbox').checked = false; document.getElementById('option_rtl_checkbox').checked = false; document.getElementById('option_sounds_checkbox').checked = true; + document.getElementById('option_oneBasedIndex_checkbox').checked = true; + document.getElementById('option_horizontalLayout_checkbox').checked = false; + document.getElementById('option_toolboxPosition_checkbox').checked = false; + + // Check infinite blocks and hide suboption. + document.getElementById('option_infiniteBlocks_checkbox').checked = true; + document.getElementById('maxBlockNumber_option').style.display = + 'none'; // Uncheck grid and zoom options and hide suboptions. document.getElementById('option_grid_checkbox').checked = false; @@ -399,7 +409,7 @@ WorkspaceFactoryView.prototype.setBaseOptions = function() { document.getElementById('zoom_options').style.display = 'none'; // Set grid options. - document.getElementById('gridOption_spacing_number').value = 0; + document.getElementById('gridOption_spacing_number').value = 20; document.getElementById('gridOption_length_number').value = 1; document.getElementById('gridOption_colour_text').value = '#888'; document.getElementById('gridOption_snap_checkbox').checked = false; From 122f69f92c440d01b88aabe106cf34ae67071fcf Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 19 Oct 2016 17:04:03 -0700 Subject: [PATCH 06/27] Disable break/return blocks in addition to warning. --- blocks/loops.js | 9 +++++++++ blocks/procedures.js | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/blocks/loops.js b/blocks/loops.js index b9bae1e80..3d72d5b56 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -257,6 +257,9 @@ Blockly.Blocks['controls_flow_statements'] = { * @this Blockly.Block */ onchange: function(e) { + if (this.workspace.isDragging()) { + return; // Don't change state at the start of a drag. + } var legal = false; // Is the block nested in a loop? var block = this; @@ -269,8 +272,14 @@ Blockly.Blocks['controls_flow_statements'] = { } while (block); if (legal) { this.setWarningText(null); + if (!this.isInFlyout) { + this.setDisabled(false); + } } else { this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING); + if (!this.isInFlyout && !this.getInheritedDisabled()) { + this.setDisabled(true); + } } }, /** diff --git a/blocks/procedures.js b/blocks/procedures.js index 3ada788cf..25661c217 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -843,6 +843,9 @@ Blockly.Blocks['procedures_ifreturn'] = { * @this Blockly.Block */ onchange: function(e) { + if (this.workspace.isDragging()) { + return; // Don't change state at the start of a drag. + } var legal = false; // Is the block nested in a procedure? var block = this; @@ -868,8 +871,14 @@ Blockly.Blocks['procedures_ifreturn'] = { this.hasReturnValue_ = true; } this.setWarningText(null); + if (!this.isInFlyout) { + this.setDisabled(false); + } } else { this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING); + if (!this.isInFlyout && !this.getInheritedDisabled()) { + this.setDisabled(true); + } } }, /** From c2bf01ab4879e489b2e996eea2459d4bdf74dde6 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 19 Oct 2016 17:15:57 -0700 Subject: [PATCH 07/27] Dragging blocks towards the toolbox should create blocks rather than do nothing. --- core/flyout.js | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/core/flyout.js b/core/flyout.js index 199a34bfb..c872f9252 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -1056,34 +1056,21 @@ Blockly.Flyout.prototype.isDragTowardWorkspace_ = function(dx, dy) { // Direction goes from -180 to 180, with 0 toward the right and 90 on top. var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; - var draggingTowardWorkspace = false; var range = this.dragAngleRange_; if (this.horizontalLayout_) { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - // Horizontal at top. - if (dragDirection < 90 + range && dragDirection > 90 - range) { - draggingTowardWorkspace = true; - } - } else { - // Horizontal at bottom. - if (dragDirection > -90 - range && dragDirection < -90 + range) { - draggingTowardWorkspace = true; - } + // Check for up or down dragging. + if ((dragDirection < 90 + range && dragDirection > 90 - range) || + (dragDirection > -90 - range && dragDirection < -90 + range)) { + return true; } } else { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - // Vertical at left. - if (dragDirection < range && dragDirection > -range) { - draggingTowardWorkspace = true; - } - } else { - // Vertical at right. - if (dragDirection < -180 + range || dragDirection > 180 - range) { - draggingTowardWorkspace = true; - } + // Check for left or right dragging. + if ((dragDirection < range && dragDirection > -range) || + (dragDirection < -180 + range || dragDirection > 180 - range)) { + return true; } } - return draggingTowardWorkspace; + return false; }; /** From 8f3b4bcb5ebea62e45b42fd3acf69638fe7100ba Mon Sep 17 00:00:00 2001 From: Andrew n marshall Date: Thu, 20 Oct 2016 09:30:45 -0700 Subject: [PATCH 08/27] Replaces calls to window.alert(), window.confirm(), and window.prompt() with Blockly.alert(), Blockly.confirm(), and Blockly.prompt(). These are designed to allow app developers to replace the dialogs with versions that match their own open app, possibly avoiding modal browser dialogs. They each take a callback, so the developer has the opportunity to implement non-modal behavior. --- core/blockly.js | 36 +++ core/field_angle.js | 2 +- core/field_textinput.js | 13 +- core/variables.js | 78 +++--- core/workspace.js | 29 ++- core/workspace_svg.js | 12 +- demos/custom-dialogs/custom-dialog.js | 101 ++++++++ demos/custom-dialogs/index.html | 352 ++++++++++++++++++++++++++ 8 files changed, 567 insertions(+), 56 deletions(-) create mode 100644 demos/custom-dialogs/custom-dialog.js create mode 100644 demos/custom-dialogs/index.html diff --git a/core/blockly.js b/core/blockly.js index 106957f52..da6adc834 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -338,6 +338,42 @@ Blockly.getMainWorkspace = function() { return Blockly.mainWorkspace; }; +/** + * Wrapper to window.alert() that app developers may override to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {function()=} opt_callback The callback when the alert is dismissed. + */ +Blockly.alert = function(message, opt_callback) { + window.alert(message); + if (opt_callback) { + opt_callback(); + } +}; + +/** + * Wrapper to window.confirm() that app developers may override to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {!function(boolean)} callback The callback for handling user response. + */ +Blockly.confirm = function(message, callback) { + callback(window.confirm(message)); +}; + +/** + * Wrapper to window.prompt() that app developers may override to provide + * alternatives to the modal browser window. Built-in browser prompts are + * often used for better text input experience on mobile device. We strongly + * recommend testing mobile when overriding this. + * @param {string} message The message to display to the user. + * @param {string} defaultValue The value to initialize the prompt with. + * @param {!function(string)} callback The callback for handling user reponse. + */ +Blockly.prompt = function(message, defaultValue, callback) { + callback(window.prompt(message, defaultValue)); +}; + // IE9 does not have a console. Create a stub to stop errors. if (!goog.global['console']) { goog.global['console'] = { diff --git a/core/field_angle.js b/core/field_angle.js index a294948e9..75d779f00 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -130,7 +130,7 @@ Blockly.FieldAngle.prototype.showEditor_ = function() { Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus); var div = Blockly.WidgetDiv.DIV; if (!div.firstChild) { - // Mobile interface uses window.prompt. + // Mobile interface uses Blockly.prompt. return; } // Build the SVG DOM. diff --git a/core/field_textinput.js b/core/field_textinput.js index 5af8bf9a7..6356481d3 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -114,11 +114,14 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID || goog.userAgent.IPAD)) { // Mobile browsers have issues with in-line textareas (focus & keyboards). - var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_); - if (this.sourceBlock_) { - newValue = this.callValidator(newValue); - } - this.setValue(newValue); + 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; } diff --git a/core/variables.js b/core/variables.js index 00c38ad21..35fb3139b 100644 --- a/core/variables.js +++ b/core/variables.js @@ -227,47 +227,57 @@ Blockly.Variables.generateUniqueName = function(workspace) { * Create a new variable on the given workspace. * @param {!Blockly.Workspace} workspace The workspace on which to create the * variable. - * @return {null|undefined|string} An acceptable new variable name, or null if - * change is to be aborted (cancel button), or undefined if an existing - * variable was chosen. + * @param {function(null|undefined|string)=} opt_callback A callback. It will + * return 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.createVariable = function(workspace) { - while (true) { - var text = Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, ''); - if (text) { - if (workspace.variableIndexOf(text) != -1) { - window.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1', - text.toLowerCase())); - } else { - workspace.createVariable(text); - break; - } - } else { - text = null; - break; - } - } - return text; +Blockly.Variables.createVariable = function(workspace, opt_callback) { + var promptAndCheckWithAlert = function(defaultName) { + Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName, + function(text) { + if (text) { + if (workspace.variableIndexOf(text) != -1) { + Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1', + text.toLowerCase()), + function() { + promptAndCheckWithAlert(text); // Recurse + }); + } else { + workspace.createVariable(text); + if (opt_callback) { + opt_callback(text); + } + } + } 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. * @param {string} defaultText The default value to show in the prompt's field. - * @return {?string} The new variable name, or null if the user picked - * something illegal. + * @param {function(?string)} callback A callback. It will return the new + * variable name, or null if the user picked something illegal. */ -Blockly.Variables.promptName = function(promptText, defaultText) { - var newVar = window.prompt(promptText, defaultText); - // Merge runs of whitespace. Strip leading and trailing whitespace. - // Beyond this, all names are legal. - if (newVar) { - newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); - if (newVar == Blockly.Msg.RENAME_VARIABLE || - newVar == Blockly.Msg.NEW_VARIABLE) { - // Ok, not ALL names are legal... - newVar = null; +Blockly.Variables.promptName = function(promptText, defaultText, callback) { + Blockly.prompt(promptText, defaultText, function(newVar) { + // Merge runs of whitespace. Strip leading and trailing whitespace. + // Beyond this, all names are legal. + if (newVar) { + newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); + if (newVar == Blockly.Msg.RENAME_VARIABLE || + newVar == Blockly.Msg.NEW_VARIABLE) { + // Ok, not ALL names are legal... + newVar = null; + } } - } - return newVar; + callback(newVar); + }); }; diff --git a/core/workspace.js b/core/workspace.js index a8e97c04b..81b1ad3a4 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -316,26 +316,29 @@ Blockly.Workspace.prototype.deleteVariable = function(name) { if (block.type == 'procedures_defnoreturn' || block.type == 'procedures_defreturn') { var procedureName = block.getFieldValue('NAME'); - window.alert( + Blockly.alert( Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.replace('%1', name). - replace('%2', procedureName)); + replace('%2', procedureName)); return; } } - var ok = window.confirm( + var workspace = this; + Blockly.confirm( Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length). - replace('%2', name)); - if (!ok) { - return; - } - } + replace('%2', name), + function(ok) { + if (!ok) { + return; + } - Blockly.Events.setGroup(true); - for (var i = 0; i < uses.length; i++) { - uses[i].dispose(true, false); + 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); + }); } - Blockly.Events.setGroup(false); - this.variableList.splice(variableIndex, 1); } }; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 6c72c934e..d7074e1d7 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -985,10 +985,16 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)), enabled: deleteList.length > 0, callback: function() { - if (deleteList.length < 2 || - window.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', - String(deleteList.length)))) { + if (deleteList.length < 2 ) { deleteNext(); + } else { + Blockly.confirm(Blockly.Msg.DELETE_ALL_BLOCKS. + replace('%1',String(deleteList.length)), + function(ok) { + if (ok) { + deleteNext(); + } + }); } } }; diff --git a/demos/custom-dialogs/custom-dialog.js b/demos/custom-dialogs/custom-dialog.js new file mode 100644 index 000000000..984448160 --- /dev/null +++ b/demos/custom-dialogs/custom-dialog.js @@ -0,0 +1,101 @@ +/** Override Blockly.alert() with custom implementation. */ +Blockly.alert = function(message, callback) { + showDialog('Alert', message, { + okay: callback + }); +} + +/** Override Blockly.confirm() with custom implementation. */ +Blockly.confirm = function(message, callback) { + showDialog('Confirm', message, { + showOkay: true, + okay: function() { + callback(true) + }, + showCancel: true, + cancel: function() { + callback(false) + } + }); +} + +/** Override Blockly.prompt() with custom implementation. */ +Blockly.prompt = function(message, defaultValue, callback) { + showDialog('Prompt', message, { + showInput: true, + showOkay: true, + okay: function() { + callback(dialogInput.value) + }, + showCancel: true, + cancel: function() { + callback(null) + } + }); + dialogInput.value = defaultValue +} + +// Implementation details below... +var backdropDiv, dialogDiv, dialogInput + +function hideDialog() { + backdropDiv.style.display = 'none' + dialogDiv.style.display = 'none' +} + +function showDialog(title, message, options) { + if (!backdropDiv) { + // Generate HTML + var body = document.getElementsByTagName('body')[0] + backdropDiv = document.createElement('div') + backdropDiv.setAttribute('id', 'backdrop') + body.appendChild(backdropDiv) + + dialogDiv = document.createElement('div') + dialogDiv.setAttribute('id', 'dialog') + backdropDiv.appendChild(dialogDiv) + dialogDiv.onclick = function(event) { + event.stopPropagation() + } + + } + backdropDiv.style.display = 'block' + dialogDiv.style.display = 'block' + + dialogDiv.innerHTML = '
' + title + '
' + + '

' + message + '

' + + (options.showInput? '
' : '') + + '
' + + (options.showCancel ? '': '') + + (options.showOkay ? '': '') + + '
' + dialogInput = document.getElementById('dialogInput') + + document.getElementById('dialogOkay').onclick = function(event) { + hideDialog() + if (options.okay) { + options.okay() + } + event.stopPropagation() + } + document.getElementById('dialogCancel').onclick = function(event) { + hideDialog() + if (options.cancel) { + options.cancel() + } + event.stopPropagation() + } + + backdropDiv.onclick = function(event) { + hideDialog() + if (options.cancel) { + options.cancel(); + } else if (options.okay) { + options.okay(); + } + event.stopPropagation() + } +} + + + diff --git a/demos/custom-dialogs/index.html b/demos/custom-dialogs/index.html new file mode 100644 index 000000000..0d203fc03 --- /dev/null +++ b/demos/custom-dialogs/index.html @@ -0,0 +1,352 @@ + + + + + Blockly Demo: Custom Dialog + + + + + + +

Blockly > + Demos > Custom Dialog

+ +

This is a simple demo of replacing modal browser dialogs with HTML.

+ +

Try creating new variables, creating variables with names already in + use, or deleting multiple blocks on the workspace. +

+ +
+ + + + + + + + From acb5569d3979fa0cfb2cfe924a38c8dad4ddc41a Mon Sep 17 00:00:00 2001 From: Andrew n marshall Date: Thu, 20 Oct 2016 14:15:15 -0700 Subject: [PATCH 09/27] Adding a demo for custom dialogs. --- demos/custom-dialogs/custom-dialog.js | 240 +++++++++++++-------- demos/custom-dialogs/index.html | 299 +------------------------- 2 files changed, 153 insertions(+), 386 deletions(-) diff --git a/demos/custom-dialogs/custom-dialog.js b/demos/custom-dialogs/custom-dialog.js index 984448160..77a79660a 100644 --- a/demos/custom-dialogs/custom-dialog.js +++ b/demos/custom-dialogs/custom-dialog.js @@ -1,101 +1,163 @@ +/** + * Blockly Demos: Custom Dialogs + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +CustomDialog = {} + /** Override Blockly.alert() with custom implementation. */ Blockly.alert = function(message, callback) { - showDialog('Alert', message, { - okay: callback - }); -} + console.log('Alert: ' + message); + CustomDialog.show('Alert', message, { + onCancel: callback + }); +}; /** Override Blockly.confirm() with custom implementation. */ Blockly.confirm = function(message, callback) { - showDialog('Confirm', message, { - showOkay: true, - okay: function() { - callback(true) - }, - showCancel: true, - cancel: function() { - callback(false) - } - }); -} + console.log('Confirm: ' + message); + CustomDialog.show('Confirm', message, { + showOkay: true, + onOkay: function() { + callback(true) + }, + showCancel: true, + onCancel: function() { + callback(false) + } + }); +}; /** Override Blockly.prompt() with custom implementation. */ Blockly.prompt = function(message, defaultValue, callback) { - showDialog('Prompt', message, { - showInput: true, - showOkay: true, - okay: function() { - callback(dialogInput.value) - }, - showCancel: true, - cancel: function() { - callback(null) - } - }); - dialogInput.value = defaultValue + console.log('Prompt: ' + message); + CustomDialog.show('Prompt', message, { + showInput: true, + showOkay: true, + onOkay: function() { + callback(CustomDialog.inputField.value) + }, + showCancel: true, + onCancel: function() { + callback(null) + } + }); + CustomDialog.inputField.value = defaultValue; +}; + + +CustomDialog.hide = function() { + if (CustomDialog.backdropDiv_) { + CustomDialog.backdropDiv_.style.display = 'none' + CustomDialog.dialogDiv_.style.display = 'none' + } } -// Implementation details below... -var backdropDiv, dialogDiv, dialogInput +/** + * Shows the dialog. + * Allowed options: + * - showOkay: Whether to show the OK button. + * - showCancel: Whether to show the Cancel button. + * - showInput: Whether to show the text input field. + * - onOkay: Callback to handle the okay button. + * - onCancel: Callback to handle the cancel button. + */ +CustomDialog.show = function(title, message, options) { + var backdropDiv = CustomDialog.backdropDiv_; + var dialogDiv = CustomDialog.dialogDiv_; + if (!dialogDiv) { + // Generate HTML + var body = document.getElementsByTagName('body')[0]; + backdropDiv = document.createElement('div'); + backdropDiv.setAttribute('id', 'backdrop'); + backdropDiv.style.cssText = + 'position: absolute;' + + 'top: 0px;' + + 'left: 0px;' + + 'right: 0px;' + + 'bottom: 0px;' + + 'background-color: rgba(0, 0, 0, 0.7);'; + body.appendChild(backdropDiv); -function hideDialog() { - backdropDiv.style.display = 'none' - dialogDiv.style.display = 'none' + dialogDiv = document.createElement('div'); + dialogDiv.setAttribute('id', 'dialog'); + dialogDiv.style.cssText = + 'background-color: white;' + + 'width: 400px;' + + 'margin: 20px auto 0;' + + 'padding: 10px;'; + backdropDiv.appendChild(dialogDiv); + + dialogDiv.onclick = function(event) { + event.stopPropagation(); + }; + + CustomDialog.backdropDiv_ = backdropDiv; + CustomDialog.dialogDiv_ = dialogDiv; + } + backdropDiv.style.display = 'block'; + dialogDiv.style.display = 'block'; + + dialogDiv.innerHTML = '
' + title + '
' + + '

' + message + '

' + + (options.showInput? '
' : '') + + '
' + + (options.showCancel ? '': '') + + (options.showOkay ? '': '') + + '
'; + + var onOkay = function(event) { + CustomDialog.hide(); + options.onOkay && options.onOkay(); + event && event.stopPropagation(); + }; + var onCancel = function(event) { + CustomDialog.hide(); + options.onCancel && options.onCancel(); + event && event.stopPropagation(); + }; + + var dialogInput = document.getElementById('dialogInput'); + CustomDialog.inputField = dialogInput; + if (dialogInput) { + dialogInput.focus(); + + dialogInput.onkeyup = function(event) { + if (event.keyCode == 13) { + // Process as OK when user hits enter. + onOkay(); + return false; + } else if (event.keyCode == 27) { + // Process as cancel when user hits esc. + onCancel(); + return false; + } + }; + } else { + var okay = document.getElementById('dialogOkay'); + okay && okay.focus(); + } + + if (options.showOkay) { + document.getElementById('dialogOkay').onclick = onOkay; + } + if (options.showCancel) { + document.getElementById('dialogCancel').onclick = onCancel; + } + + backdropDiv.onclick = onCancel; } - -function showDialog(title, message, options) { - if (!backdropDiv) { - // Generate HTML - var body = document.getElementsByTagName('body')[0] - backdropDiv = document.createElement('div') - backdropDiv.setAttribute('id', 'backdrop') - body.appendChild(backdropDiv) - - dialogDiv = document.createElement('div') - dialogDiv.setAttribute('id', 'dialog') - backdropDiv.appendChild(dialogDiv) - dialogDiv.onclick = function(event) { - event.stopPropagation() - } - - } - backdropDiv.style.display = 'block' - dialogDiv.style.display = 'block' - - dialogDiv.innerHTML = '
' + title + '
' - + '

' + message + '

' - + (options.showInput? '
' : '') - + '
' - + (options.showCancel ? '': '') - + (options.showOkay ? '': '') - + '
' - dialogInput = document.getElementById('dialogInput') - - document.getElementById('dialogOkay').onclick = function(event) { - hideDialog() - if (options.okay) { - options.okay() - } - event.stopPropagation() - } - document.getElementById('dialogCancel').onclick = function(event) { - hideDialog() - if (options.cancel) { - options.cancel() - } - event.stopPropagation() - } - - backdropDiv.onclick = function(event) { - hideDialog() - if (options.cancel) { - options.cancel(); - } else if (options.okay) { - options.okay(); - } - event.stopPropagation() - } -} - - - diff --git a/demos/custom-dialogs/index.html b/demos/custom-dialogs/index.html index 0d203fc03..42cba5477 100644 --- a/demos/custom-dialogs/index.html +++ b/demos/custom-dialogs/index.html @@ -15,20 +15,6 @@ font-weight: normal; font-size: 140%; } - #backdrop { - position: absolute; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; - background-color: rgba(0, 0, 0, 0.7); - } - #dialog { - background-color: white; - width: 400px; - margin: 20px auto 0; - padding: 10px; - } @@ -43,202 +29,10 @@
-