Skip to content

Commit

Permalink
feat: Created a minimap that is positionable in the primary workspace (
Browse files Browse the repository at this point in the history
…#1824)

* feat: Created a minimap that is positionable in the primary workspace

* updated with feedback changes

* feedback updates
  • Loading branch information
cesarades authored Jul 25, 2023
1 parent 57b4719 commit 036e32a
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 50 deletions.
63 changes: 42 additions & 21 deletions plugins/workspace-minimap/src/minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,40 +31,61 @@ export class Minimap {
protected minimapWorkspace: Blockly.WorkspaceSvg;
private onMouseMoveWrapper: Blockly.browserEvents.Data;
/**
* Constructor for a minimap
* @param workspace The workspace to mirror
* Constructor for a minimap.
* @param workspace The workspace to mirror.
*/
constructor(workspace: Blockly.WorkspaceSvg) {
this.primaryWorkspace = workspace;
const options = {
readOnly: true,
move: {
scrollbars: {
vertical: true,
horizontal: true,
},
drag: false,
wheel: false,
},
};
this.minimapWorkspace = Blockly.inject('minimapDiv', options);
}

/**
* Initialize.
*/
init(): void {
// Create a wrapper div for the minimap injection.
const minimapWrapper = document.createElement('div');
minimapWrapper.id = 'minimapWrapper_' + this.primaryWorkspace.id;
minimapWrapper.className = 'minimapWrapper';

// Make the wrapper a sibling to the primary injection div.
const primaryInjectParentDiv =
this.primaryWorkspace.getInjectionDiv().parentNode;
primaryInjectParentDiv.appendChild(minimapWrapper);

// Inject the minimap workspace.
this.minimapWorkspace = Blockly.inject(minimapWrapper.id,
{
// Inherit the layout of the primary workspace.
rtl: this.primaryWorkspace.RTL,
// Include the scrollbars so that internal scrolling is enabled and
// remove direct interaction with the minimap workspace.
move: {
scrollbars: true,
drag: false,
wheel: false,
},
// Remove the scale bounds of the minimap so that it can
// correctly zoomToFit.
zoom: {
maxScale: null,
minScale: null,
},
readOnly: true,
});

this.minimapWorkspace.scrollbar.setContainerVisible(false);
this.primaryWorkspace.addChangeListener((e) => void this.mirror(e));
window.addEventListener('resize', () => {
this.minimapWorkspace.zoomToFit();
});

// The mouseup binds to the parent container div instead of the minimap
// because if a drag begins on the minimap and ends outside of it the
// mousemove should still unbind.
Blockly.browserEvents.bind(
this.minimapWorkspace.svgGroup_, 'mousedown', this, this.onClickDown);
Blockly.browserEvents.bind(
this.minimapWorkspace.svgGroup_, 'mouseup', this, this.onClickUp);
primaryInjectParentDiv, 'mouseup', this, this.onClickUp);
}


/**
* Creates the mirroring between workspaces. Passes on all desired events
* to the minimap from the primary workspace.
Expand Down Expand Up @@ -150,9 +171,9 @@ export class Minimap {
* Unbinds the minimap mousemove when the mouse is not clicked.
*/
private onClickUp(): void {
// TODO: If you start the click in the minimap and end it in the primary
// the then this function is never unbinded
Blockly.browserEvents.unbind(this.onMouseMoveWrapper);
if (this.onMouseMoveWrapper) {
Blockly.browserEvents.unbind(this.onMouseMoveWrapper);
}
}

/**
Expand Down
127 changes: 118 additions & 9 deletions plugins/workspace-minimap/src/positioned_minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,132 @@ import {Minimap} from './minimap';

/**
* A positionable version of minimap that implements IPositionable.
* @param {Minimap} minimap The minimap to position.
* @implements {Blockly.IPositionable}
*/
export class PositionedMinimap {
protected minimap: Minimap;
export class PositionedMinimap extends Minimap implements Blockly.IPositionable {
protected margin: number;
protected top: number;
protected left: number;
protected width: number;
protected height: number;
id: string;
/**
* Constructor for a minimap
* @param {Minimap} minimap The workspace to sit in.
* Constructor for a positionable minimap.
* @param workspace The workspace to mirror.
*/
constructor(minimap: Minimap) {
this.minimap = minimap;
constructor(workspace: Blockly.WorkspaceSvg) {
super(workspace);
this.id = 'minimap';
this.margin = 20;
this.top = 0;
this.left = 0;
this.width = 225; // TODO: Bumping size responsiveness to future branch.
this.height = 150;
}

/**
* Initialize.
*/
init(): void {
// this.work
super.init();
this.primaryWorkspace.getComponentManager().addComponent({
component: this,
weight: 3,
capabilities: [Blockly.ComponentManager.Capability.POSITIONABLE],
});
this.primaryWorkspace.resize();
}

/**
* Returns the bounding rectangle of the UI element in pixel units
* relative to the Blockly injection div.
* @returns The component’s bounding box.
*/
getBoundingRectangle(): Blockly.utils.Rect {
return new Blockly.utils.Rect(
this.top, this.top + this.height,
this.left, this.left + this.width);
}

/**
* Positions the minimap.
* @param metrics The workspace metrics.
* @param savedPositions List of rectangles already on the workspace.
*/
position(metrics: Blockly.MetricsManager.UiMetrics,
savedPositions: Blockly.utils.Rect[]): void {
// Aliases.
const workspace = this.primaryWorkspace;
const scrollbars = workspace.scrollbar;

const hasVerticalScrollbars = scrollbars &&
scrollbars.isVisible() && scrollbars.canScrollVertically();
const hasHorizontalScrollbars = scrollbars &&
scrollbars.isVisible() && scrollbars.canScrollHorizontally();

if (metrics.toolboxMetrics.position === Blockly.TOOLBOX_AT_LEFT ||
(workspace.horizontalLayout && !workspace.RTL)) {
// Right edge placement.
this.left = metrics.absoluteMetrics.left + metrics.viewMetrics.width -
this.width - this.margin;
if (hasVerticalScrollbars && !workspace.RTL) {
this.left -= Blockly.Scrollbar.scrollbarThickness;
}
} else {
// Left edge placement.
this.left = this.margin;
if (hasVerticalScrollbars && workspace.RTL) {
this.left += Blockly.Scrollbar.scrollbarThickness;
}
}

const startAtBottom =
metrics.toolboxMetrics.position === Blockly.TOOLBOX_AT_BOTTOM;
if (startAtBottom) {
// Bottom edge placement.
this.top = metrics.absoluteMetrics.top + metrics.viewMetrics.height -
this.height - this.margin;
if (hasHorizontalScrollbars) {
// The horizontal scrollbars are always positioned on the bottom.
this.top -= Blockly.Scrollbar.scrollbarThickness;
}
} else {
// Upper edge placement.
this.top = metrics.absoluteMetrics.top + this.margin;
}

// Check for collision and bump if needed.
let boundingRect = this.getBoundingRectangle();
for (let i = 0; i < savedPositions.length; i++) {
if (boundingRect.intersects(savedPositions[i])) {
if (startAtBottom) {
this.top = savedPositions[i].top - this.height - this.margin;
} else {
this.top = savedPositions[i].bottom + this.margin;
}
// Recheck other savedPositions.
boundingRect = this.getBoundingRectangle();
i = -1;
}
}

// Update the Css.
this.setMinimapCss();
}

/**
* Updates the CSS attribute for the minimap.
*/
private setMinimapCss(): void {
const injectDiv = this.minimapWorkspace.getInjectionDiv();
// TODO: Styling properties will be added later this is a placeholder.
injectDiv.parentElement.setAttribute('style',
`z-index: 2;
position: absolute;
width: ${this.width}px;
height: ${this.height}px;
top: ${this.top}px;
left: ${this.left}px;
box-shadow: 2px 2px 10px grey;`);
Blockly.svgResize(this.minimapWorkspace);
}
}
6 changes: 3 additions & 3 deletions plugins/workspace-minimap/test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
</head>

<body>
<div id="root" style="display: flex;height: 100vh;width: 100vw;">
<div id="primaryDiv" style="height: 95%; width: 50%;"></div>
<div id="minimapDiv" style="height: 95%; width: 50%;"></div>
<div style="display: flex;">
<div id="positionedRoot" style="height: 98vh; width: 50vw;"></div>
<div id="unpositionedRoot" style="height: 98vh; width: 50vw;"></div>
</div>
<script src="../build/test_bundle.js"></script>
</body>
Expand Down
44 changes: 27 additions & 17 deletions plugins/workspace-minimap/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,35 @@
*/

import * as Blockly from 'blockly';
import {toolboxCategories, createPlayground} from '@blockly/dev-tools';
import {toolboxCategories} from '@blockly/dev-tools';
import {Minimap, PositionedMinimap} from '../src/index';

// Creates the primary workspace and adds the minimap.
const workspace = Blockly.inject('primaryDiv', {toolbox: toolboxCategories});
const minimap = new Minimap(workspace);
minimap.init();
const positionedWorkspace = Blockly.inject('positionedRoot',
{toolbox: toolboxCategories});
const positionedMinimap = new PositionedMinimap(positionedWorkspace);
positionedMinimap.init();

// Creates 100 if blocks
for (let i = 0; i < 100; i++) {
const block = workspace.newBlock('controls_if');
block.initSvg();
block.render();
}
// Creates the primary workspace and adds the minimap.
const unpositionedWorkspace = Blockly.inject('unpositionedRoot',
{toolbox: toolboxCategories});
const unpositionedMinimap = new Minimap(unpositionedWorkspace);
unpositionedMinimap.init();


const seedTest = (workspace: Blockly.WorkspaceSvg): void => {
// Creates 100 if blocks
for (let i = 0; i < 100; i++) {
const block = workspace.newBlock('controls_if');
block.initSvg();
block.render();
}

// Move the blocks to random locations.
for (const block of workspace.getAllBlocks(true)) {
const cord = new Blockly.utils.Coordinate(
Math.round(Math.random() * 450 + 40),
Math.round(Math.random() * 600 + 40));
block.moveTo(cord);
}
// Move the blocks to random locations.
for (const block of workspace.getAllBlocks(true)) {
const cord = new Blockly.utils.Coordinate(
Math.round(Math.random() * 450 + 40),
Math.round(Math.random() * 600 + 40));
block.moveTo(cord);
}
};

0 comments on commit 036e32a

Please sign in to comment.