diff --git a/appengine/app.yaml b/appengine/app.yaml
index 9c93fb6873d..563af8b24d0 100644
--- a/appengine/app.yaml
+++ b/appengine/app.yaml
@@ -70,6 +70,8 @@ handlers:
# Blockly files.
- url: /static
static_dir: static
+ http_headers:
+ Access-Control-Allow-Origin: "*"
secure: always
# Storage API.
diff --git a/core/gesture.ts b/core/gesture.ts
index 4c65c1d3842..fa3d8a15138 100644
--- a/core/gesture.ts
+++ b/core/gesture.ts
@@ -467,6 +467,15 @@ export class Gesture {
/* opt_noCaptureIdentifier */ true,
),
);
+ this.boundEvents.push(
+ browserEvents.conditionalBind(
+ document,
+ 'pointercancel',
+ null,
+ this.handleUp.bind(this),
+ /* opt_noCaptureIdentifier */ true,
+ ),
+ );
e.preventDefault();
e.stopPropagation();
diff --git a/core/touch.ts b/core/touch.ts
index 9af3b1f9494..8fb2cd2298c 100644
--- a/core/touch.ts
+++ b/core/touch.ts
@@ -46,7 +46,6 @@ export const TOUCH_MAP: {[key: string]: string[]} = {
'mouseup': ['pointerup', 'pointercancel'],
'touchend': ['pointerup'],
'touchcancel': ['pointercancel'],
- 'pointerup': ['pointerup', 'pointercancel'],
};
/** PID of queued long-press task. */
diff --git a/core/variable_map.ts b/core/variable_map.ts
index ba36dcea600..3a6cf402681 100644
--- a/core/variable_map.ts
+++ b/core/variable_map.ts
@@ -112,7 +112,11 @@ export class VariableMap
const oldType = variable.getType();
if (oldType === newType) return variable;
- this.variableMap.get(variable.getType())?.delete(variable.getId());
+ const oldTypeVariables = this.variableMap.get(oldType);
+ oldTypeVariables?.delete(variable.getId());
+ if (oldTypeVariables?.size === 0) {
+ this.variableMap.delete(oldType);
+ }
variable.setType(newType);
const newTypeVariables =
this.variableMap.get(newType) ??
diff --git a/demos/minimap/icon.png b/demos/minimap/icon.png
deleted file mode 100644
index 870caa070d8..00000000000
Binary files a/demos/minimap/icon.png and /dev/null differ
diff --git a/demos/minimap/index.html b/demos/minimap/index.html
deleted file mode 100644
index 1a8fd357dea..00000000000
--- a/demos/minimap/index.html
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
- Blockly Demo: Minimap
-
-
-
-
-
-
-
-
-
- This is a simple demo showing how a minimap can be implemented.
-
-
-
-
-
-
-
-
- 123
-
-
-
-
- i
- j
- k
-
-
-
-
-
-
-
diff --git a/demos/minimap/minimap.js b/demos/minimap/minimap.js
deleted file mode 100644
index a4343899ad6..00000000000
--- a/demos/minimap/minimap.js
+++ /dev/null
@@ -1,302 +0,0 @@
-/**
-
- * Copyright 2017 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * @fileoverview JavaScript for Blockly's Minimap demo.
- */
-'use strict';
-
-/**
- * Creating a separate namespace for minimap.
- */
-var Minimap = {};
-
-/**
- * Initialize the workspace and minimap.
- * @param {!Workspace} workspace The main workspace of the user.
- * @param {!Workspace} minimap The workspace that will be used as a minimap.
- */
-Minimap.init = function(workspace, minimap) {
- this.workspace = workspace;
- this.minimap = minimap;
-
- // Adding scroll callback functionality to vScroll and hScroll just for this demo.
- // IMPORTANT: This should be changed when there is proper UI event handling
- // API available and should be handled by workspace's event listeners.
- this.workspace.scrollbar.vScroll.setHandlePosition = function(newPosition) {
- this.handlePosition_ = newPosition;
- this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
-
- // Code above is same as the original setHandlePosition function in core/scrollbar.js.
- // New code starts from here.
-
- // Get the absolutePosition.
- var absolutePosition = (this.handlePosition_ / this.ratio);
-
- // Firing the scroll change listener.
- Minimap.onScrollChange(absolutePosition, this.horizontal_);
- };
-
- // Adding call back for horizontal scroll.
- this.workspace.scrollbar.hScroll.setHandlePosition = function(newPosition) {
- this.handlePosition_ = newPosition;
- this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
-
- // Code above is same as the original setHandlePosition function in core/scrollbar.js.
- // New code starts from here.
-
- // Get the absolutePosition.
- var absolutePosition = (this.handlePosition_ / this.ratio);
-
- // Firing the scroll change listener.
- Minimap.onScrollChange(absolutePosition, this.horizontal_);
- };
-
-
- // Required to stop a positive feedback loop when user clicks minimap
- // and the scroll changes, which in turn may change minimap.
- this.disableScrollChange = false;
-
- // Listen to events on the main workspace.
- this.workspace.addChangeListener(Minimap.mirrorEvent);
-
- //Get rectangle bounding the minimap div.
- this.rect = document.getElementById('mapDiv').getBoundingClientRect();
-
- // Create a svg overlay on the top of mapDiv for the minimap.
- this.svg = Blockly.utils.dom.createSvgElement('svg', {
- 'xmlns': Blockly.utils.dom.SVG_NS,
- 'xmlns:html': Blockly.utils.dom.HTML_NS,
- 'xmlns:xlink': Blockly.utils.dom.XLINK_NS,
- 'version': '1.1',
- 'height': this.rect.bottom-this.rect.top,
- 'width': this.rect.right-this.rect.left,
- 'class': 'minimap',
- }, document.getElementById('mapDiv'));
- this.svg.style.top = this.rect.top + 'px';
- this.svg.style.left = this.rect.left + 'px';
-
- // Creating a rectangle in the minimap that represents current view.
- Blockly.utils.dom.createSvgElement('rect', {
- 'width': 100,
- 'height': 100,
- 'class': 'mapDragger'
- }, this.svg);
-
- // Rectangle in the minimap that represents current view.
- this.mapDragger = this.svg.childNodes[0];
-
- // Adding mouse events to the rectangle, to make it Draggable.
- // Using Blockly.browserEvents.bind to attach mouse/touch listeners.
- Blockly.browserEvents.bind(
- this.mapDragger, 'mousedown', null, Minimap.mousedown);
-
- //When the window change, we need to resize the minimap window.
- window.addEventListener('resize', Minimap.repositionMinimap);
-
- // Mouse up event for the minimap.
- this.svg.addEventListener('mouseup', Minimap.updateMapDragger);
-
- //Boolean to check whether I am dragging the surface or not.
- this.isDragging = false;
-};
-
-Minimap.mousedown = function(e) {
- // Using Blockly.browserEvents.bind to attach mouse/touch listeners.
- Minimap.mouseMoveBindData = Blockly.browserEvents.bind(
- document, 'mousemove', null, Minimap.mousemove);
- Minimap.mouseUpBindData =
- Blockly.browserEvents.bind(document, 'mouseup', null, Minimap.mouseup);
-
- Minimap.isDragging = true;
- e.stopPropagation();
-};
-
-Minimap.mouseup = function(e) {
- Minimap.isDragging = false;
- // Removing listeners.
- Blockly.browserEvents.unbind(Minimap.mouseUpBindData);
- Blockly.browserEvents.unbind(Minimap.mouseMoveBindData);
- Minimap.updateMapDragger(e);
- e.stopPropagation();
-};
-
-Minimap.mousemove = function(e) {
- if (Minimap.isDragging) {
- Minimap.updateMapDragger(e);
- e.stopPropagation();
- }
-};
-
-/**
- * Run non-UI events from the main workspace on the minimap.
- * @param {!Blockly.Events.Abstract} event Event that triggered in the main
- * workspace.
- */
-Minimap.mirrorEvent = function(event) {
- if (event.isUiEvent) {
- return; // Don't mirror UI events.
- }
- // Convert event to JSON. This could then be transmitted across the net.
- var json = event.toJson();
- // Convert JSON back into an event, then execute it.
- var minimapEvent = Blockly.Events.fromJson(json, Minimap.minimap);
- minimapEvent.run(true);
- Minimap.scaleMinimap();
- Minimap.setDraggerHeight();
- Minimap.setDraggerWidth();
-};
-
-/**
- * Called when window is resized. Repositions the minimap overlay.
- */
-Minimap.repositionMinimap = function() {
- Minimap.rect = document.getElementById('mapDiv').getBoundingClientRect();
- Minimap.svg.style.top = Minimap.rect.top + 'px';
- Minimap.svg.style.left = Minimap.rect.left + 'px';
-};
-
-/**
- * Updates the rectangle's height.
- */
-Minimap.setDraggerHeight = function() {
- var workspaceMetrics = Minimap.workspace.getMetrics();
- var draggerHeight = (workspaceMetrics.viewHeight / Minimap.workspace.scale) *
- Minimap.minimap.scale;
- // It's zero when first block is placed.
- if (draggerHeight === 0) {
- return;
- }
- Minimap.mapDragger.setAttribute('height', draggerHeight);
-};
-
-/**
- * Updates the rectangle's width.
- */
-Minimap.setDraggerWidth = function() {
- var workspaceMetrics = Minimap.workspace.getMetrics();
- var draggerWidth = (workspaceMetrics.viewWidth / Minimap.workspace.scale) *
- Minimap.minimap.scale;
- // It's zero when first block is placed.
- if (draggerWidth === 0) {
- return;
- }
- Minimap.mapDragger.setAttribute('width', draggerWidth);
-};
-
-
-/**
- * Updates the overall position of the viewport of the minimap by appropriately
- * using translate functions.
- */
-Minimap.scaleMinimap = function() {
- var minimapBoundingBox = Minimap.minimap.getBlocksBoundingBox();
- var workspaceBoundingBox = Minimap.workspace.getBlocksBoundingBox();
- var workspaceMetrics = Minimap.workspace.getMetrics();
- var minimapMetrics = Minimap.minimap.getMetrics();
-
- // Scaling the minimap such that all the blocks can be seen in the viewport.
- // This padding is default because this is how to scrollbar(in main workspace)
- // is implemented.
- var topPadding = (workspaceMetrics.viewHeight) * Minimap.minimap.scale /
- (2 * Minimap.workspace.scale);
- var sidePadding = (workspaceMetrics.viewWidth) * Minimap.minimap.scale /
- (2 * Minimap.workspace.scale);
-
- // If actual padding is more than half view ports height,
- // change it to actual padding.
- if ((workspaceBoundingBox.y * Minimap.workspace.scale -
- workspaceMetrics.contentTop) *
- Minimap.minimap.scale / Minimap.workspace.scale > topPadding) {
- topPadding = (workspaceBoundingBox.y * Minimap.workspace.scale -
- workspaceMetrics.contentTop) *
- Minimap.minimap.scale / Minimap.workspace.scale;
- }
-
- // If actual padding is more than half view ports height,
- // change it to actual padding.
- if ((workspaceBoundingBox.x * Minimap.workspace.scale -
- workspaceMetrics.contentLeft) *
- Minimap.minimap.scale / Minimap.workspace.scale > sidePadding) {
- sidePadding = (workspaceBoundingBox.x * Minimap.workspace.scale -
- workspaceMetrics.contentLeft) *
- Minimap.minimap.scale / Minimap.workspace.scale;
- }
-
- var scalex = (minimapMetrics.viewWidth - 2 * sidePadding) /
- minimapBoundingBox.width;
- var scaley = (minimapMetrics.viewHeight - 2 * topPadding) /
- minimapBoundingBox.height;
- Minimap.minimap.setScale(Math.min(scalex, scaley));
-
- // Translating the minimap.
- Minimap.minimap.translate(
- -minimapMetrics.contentLeft * Minimap.minimap.scale + sidePadding,
- -minimapMetrics.contentTop * Minimap.minimap.scale + topPadding);
-};
-
-/**
- * Handles the onclick event on the minimapBoundingBox.
- * Changes mapDraggers position.
- * @param {!Event} e Event from the mouse click.
- */
-Minimap.updateMapDragger = function(e) {
- var y = e.clientY;
- var x = e.clientX;
- var draggerHeight = Minimap.mapDragger.getAttribute('height');
- var draggerWidth = Minimap.mapDragger.getAttribute('width');
-
- var finalY = y - Minimap.rect.top - draggerHeight / 2;
- var finalX = x - Minimap.rect.left - draggerWidth / 2;
-
- var maxValidY = (Minimap.workspace.getMetrics().contentHeight -
- Minimap.workspace.getMetrics().viewHeight) * Minimap.minimap.scale;
- var maxValidX = (Minimap.workspace.getMetrics().contentWidth -
- Minimap.workspace.getMetrics().viewWidth) * Minimap.minimap.scale;
-
- if (y + draggerHeight / 2 > Minimap.rect.bottom) {
- finalY = Minimap.rect.bottom - Minimap.rect.top - draggerHeight;
- } else if (y < Minimap.rect.top + draggerHeight / 2) {
- finalY = 0;
- }
-
- if (x + draggerWidth / 2 > Minimap.rect.right) {
- finalX = Minimap.rect.right - Minimap.rect.left - draggerWidth;
- } else if (x < Minimap.rect.left + draggerWidth / 2) {
- finalX = 0;
- }
-
- // Do not go below lower bound of scrollbar.
- if (finalY > maxValidY) {
- finalY = maxValidY;
- }
- if (finalX > maxValidX) {
- finalX = maxValidX;
- }
- Minimap.mapDragger.setAttribute('y', finalY);
- Minimap.mapDragger.setAttribute('x', finalX);
- // Required, otherwise creates a feedback loop.
- Minimap.disableScrollChange = true;
- Minimap.workspace.scrollbar.vScroll.set((finalY * Minimap.workspace.scale) /
- Minimap.minimap.scale);
- Minimap.workspace.scrollbar.hScroll.set((finalX * Minimap.workspace.scale) /
- Minimap.minimap.scale);
- Minimap.disableScrollChange = false;
-};
-
-/**
- * Handles the onclick event on the minimapBoundingBox, parameters are passed by
- * the event handler.
- * @param {number} position This is the absolute position of the scrollbar.
- * @param {boolean} horizontal Informs if the change event if for
- * horizontal (true) or vertical (false) scrollbar.
- */
-Minimap.onScrollChange = function(position, horizontal) {
- if (!Minimap.disableScrollChange) {
- Minimap.mapDragger.setAttribute(horizontal ? 'x' : 'y',
- position * Minimap.minimap.scale / Minimap.workspace.scale);
- }
-};
diff --git a/demos/mobile/README.md b/demos/mobile/README.md
deleted file mode 100644
index 168690e55db..00000000000
--- a/demos/mobile/README.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# Blockly on Mobile Devices
-
-This directory contains three examples of running the Blockly library on mobile
-devices. The `html/` directory is a example of configuring a webpage for touch
-devices, with a Blockly workspace that fills the screen.
-
-The `mobile/html/` is also the basis for the Android and iOS demos. Each native
-app copies this demo into the app's local resources, and required Blockly
-library files, and hosts them in an embedded WebView.
-
-Thus, developers can quickly iterate within the `mobile/html/` directory, and
-see changes in both the Android and iOS native apps.
-
-## Running the Mobile HTML Demo
-
-Before running the mobile HTML demo, you need to create some symbolic links
-in your local file system. Run the `mobile/html/ln_resources.sh` file from
-the `mobile/html/` directory. This mimics the relative locations of the
-Blockly files seen when loading the page in a native app's embedded WebView.
-
-After doing this, opening `mobile/html/index.html` should open normally,
-filling the page with one large Blockly workspace.
-
-## The Android App
-
-### Build and Run
-
-Open the `demos/mobile/android/` directory in Android Studio. The project
-files in the directory should be ready to build and run the demo in an emulator
-or connected device.
-
-### Android Copy Tasks
-
-If you edit the `mobile/html/` demo to include new files, you will need to
-update the native app project files to also copy those files.
-
-In the Android project, two Gradle tasks are responsible for the copies.
-In `mobile/android/app/build.gradle`, the tasks `copyBlocklyHtmlFile` and
-`copyBlocklyMoreFiles` configure the copy actions.
-
-## The iOS App
-
-### Build and Run
-
-Open the `demos/mobile/iOS/` directory in XCode. The project files in the
-directory should be ready to build and run the demo in a simulator or connected
-device.
-
-### iOS Copy Script
-
-The XCode project call out to `mobile/ios/cp_resources.sh` to copy the required
-HTML and related files. If you've edited the `mobile/html/` demo to require new
-files, update this script to copy these files, too.
diff --git a/demos/mobile/android/.gitignore b/demos/mobile/android/.gitignore
deleted file mode 100644
index 3ba2b2b61bd..00000000000
--- a/demos/mobile/android/.gitignore
+++ /dev/null
@@ -1,27 +0,0 @@
-/build
-/captures
-/app/src/main/assets/blockly
-.settings
-.project
-
-# Local Settings
-local.properties
-
-# Project files
-*.komodoproject
-.gradle
-*.iml
-.idea
-
-# Build files
-*.pyc
-*.apk
-*.ap_
-*.class
-*.dex
-
-# OSX Files
-.DS_Store
-
-# Windows Files
-Thumb.db
diff --git a/demos/mobile/android/README.md b/demos/mobile/android/README.md
deleted file mode 100644
index 31f968feab4..00000000000
--- a/demos/mobile/android/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Blockly in an Android WebView
-
-This code demonstrates how to get Blockly running in an Android app by
-embedding it in a WebView.
-
-### BlocklyWebViewFragment
-
-Most of the work is done within the fragment class `BlocklyWebViewFragment`.
-This fragment instantiates the WebView, loads the HTML
-(`assets/blockly/webview.html`, copied from `demos/mobile/html/index.html`),
-and provides a few helper methods.
-
-### Copying web assets with gradle
-
-This android project copies the necessary files from the main Blockly
-repository (i.e., parent directory). In `app/build.gradle`, note the
-`copyBlocklyHtmlFile` and `copyBlocklyMoreFiles` tasks.
-
-In your own project, the HTML and related files can be placed directly in the
-`assets/blockly` directory without the copy step. However, using the copy tasks
-simplifies the synchronization with an iOS app using the same files.
-
-### Loading Block Definitions and Generator functions
-
-The `webview.html` loads the block definitions and generator functions directly
-into the page, without support or coordination with the Android classes. This
-assumes the app will always utilize the same blocks. This does not mean all
-blocks are visible to the user all the time; that is controlled by the toolbox
-and workspace files. This should accommodate almost all applications.
-
-This does mean loading your own block definitions and generators will involve
-editing the HTML, adding you own `
-
-
-
-
-
-
-
-
-
-