mirror of
https://github.com/google/blockly.git
synced 2026-01-04 23:50:12 +01:00
Our files are up to a decade old, and have churned so much, that the initial author of the file no longer has much meaning. Furthermore, this will encourage developers to post to the developer group, rather than emailing Googlers (usually me) directly.
303 lines
10 KiB
JavaScript
303 lines
10 KiB
JavaScript
/**
|
|
|
|
* 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 mimimap 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);
|
|
}
|
|
};
|