Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement focus mode #1848

Merged
merged 7 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions plugins/workspace-minimap/src/focus_mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview A class that highlights the user's
* viewport on the minimap.
* @author [email protected] (Cesar Ades)
*/

import * as Blockly from 'blockly/core';

const blockEvents = new Set([
Blockly.Events.VIEWPORT_CHANGE,
Blockly.Events.BLOCK_CHANGE,
Blockly.Events.BLOCK_CREATE,
Blockly.Events.BLOCK_DELETE,
Blockly.Events.BLOCK_DRAG,
Blockly.Events.BLOCK_MOVE]);

const borderRadius = 6;

/**
* A class that highlights the user's viewport on the minimap.
*/
export class FocusRegion {
private onChangeWrapper: (e: Blockly.Events.Abstract) => void;
private svgGroup: SVGElement;
private rect: SVGElement;
private background: SVGElement;
private id: string;
private initialized = false;


/**
* Constructor for the focus region.
* @param primaryWorkspace The primary workspaceSvg.
* @param minimapWorkspace The minimap workspaceSvg.
*/
constructor(private primaryWorkspace: Blockly.WorkspaceSvg,
private minimapWorkspace: Blockly.WorkspaceSvg) {
this.id = String(Math.random()).substring(2);
}


/**
* Initializes focus region.
*/
init() {
// Make the svg group element.
this.svgGroup = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.G, {'class': 'focusRegion'}, null);

// Make the mask under the svg group.
const mask = Blockly.utils.dom.createSvgElement(
new Blockly.utils.Svg('mask'),
{'id': 'focusRegionMask' + this.id},
this.svgGroup);

// Make the background under the svg group.
this.background = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.RECT, {
'x': 0,
'y': 0,
'width': '100%',
'height': '100%',
'mask': 'url(#focusRegionMask' + this.id + ')',
}, this.svgGroup);

// Make the white layer under the svg mask.
Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.RECT, {
'x': 0,
'y': 0,
'width': '100%',
'height': '100%',
'fill': 'white',
}, mask);

// Make the black layer under the mask.
this.rect = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.RECT, {
'x': 0,
'y': 0,
'rx': borderRadius,
'ry': borderRadius,
'fill': 'black',
}, mask);

// Theme.
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
this.background.setAttribute('fill', '#e6e6e6');

// Add the svg group to the minimap.
const parentSvg = this.minimapWorkspace.getParentSvg();
if (parentSvg.firstChild) {
parentSvg.insertBefore(this.svgGroup, parentSvg.firstChild);
} else {
parentSvg.appendChild(this.svgGroup);
}

window.addEventListener('resize', () => void this.update());
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
this.onChangeWrapper = this.onChange.bind(this);
this.primaryWorkspace.addChangeListener(this.onChangeWrapper);

this.update();
this.initialized = true;
}


/**
* Disposes of the focus region.
* Unlinks from all DOM elements and remove all event listeners
* to prevent memory leaks.
*/
dispose() {
if (this.onChangeWrapper) {
this.primaryWorkspace.removeChangeListener(this.onChangeWrapper);
}
if (this.svgGroup) {
Blockly.utils.dom.removeNode(this.svgGroup);
}
this.svgGroup = null;
this.rect = null;
this.background = null;
this.initialized = false;
}


/**
* Handles events triggered on the primary workspace.
* @param event The event.
*/
private onChange(event: Blockly.Events.Abstract): void {
if (blockEvents.has(event.type)) {
this.update();
}
}


/**
* Positions and sizes the highlight on the minimap
* based on the primary workspace.
*/
private update(): void {
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
// Get the metrics.
const primaryMetrics = this.primaryWorkspace.getMetricsManager();
const minimapMetrics = this.minimapWorkspace.getMetricsManager();

const primaryView = primaryMetrics.getViewMetrics();
const primaryContent = primaryMetrics.getContentMetrics();
const minimapContent = minimapMetrics.getContentMetrics();
const minimapSvg = minimapMetrics.getSvgMetrics();

// Get the workscape to pixel scale on the minimap.
const scale = minimapContent.width /
minimapMetrics.getContentMetrics(true).width;

// Get the viewport size on a minimap scale.
const width = primaryView.width * scale;
const height = primaryView.height * scale;

// Get the viewport position in relation to the content.
let left = (primaryView.left - primaryContent.left) * scale;
let top = (primaryView.top - primaryContent.top) * scale;

// Account for the padding outside the content on the minimap.
left += (minimapSvg.width - minimapContent.width) / 2;
top += (minimapSvg.height - minimapContent.height) / 2;

// Set the svg attributes.
this.rect.setAttribute('transform', `translate(${left},${top})`);
this.rect.setAttribute('width', width.toString());
this.rect.setAttribute('height', height.toString());
}

/**
* Returns whether focus region is initialized or not.
* @returns True if focus region is initialized else false.
*/
isEnabled(): boolean {
return this.initialized;
}
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
}
21 changes: 21 additions & 0 deletions plugins/workspace-minimap/src/minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

import * as Blockly from 'blockly/core';
import {FocusRegion} from './focus_mode';

// Events that should be send over to the minimap from the primary workspace
const BlockEvents = new Set([
Expand All @@ -29,6 +30,7 @@ const BlockEvents = new Set([
export class Minimap {
protected primaryWorkspace: Blockly.WorkspaceSvg;
protected minimapWorkspace: Blockly.WorkspaceSvg;
protected focusRegion: FocusRegion;
private onMouseMoveWrapper: Blockly.browserEvents.Data;
/**
* Constructor for a minimap.
Expand Down Expand Up @@ -83,6 +85,11 @@ export class Minimap {
this.minimapWorkspace.svgGroup_, 'mousedown', this, this.onClickDown);
Blockly.browserEvents.bind(
primaryInjectParentDiv, 'mouseup', this, this.onClickUp);

// Initializes the focus region.
this.focusRegion = new FocusRegion(
this.primaryWorkspace, this.minimapWorkspace);
this.enableFocusRegion();
}


Expand Down Expand Up @@ -183,4 +190,18 @@ export class Minimap {
private onMouseMove(event: PointerEvent): void {
this.primaryScroll(event);
}

/**
* Enables the focus region; A highlight of the viewport in the minimap.
*/
enableFocusRegion(): void {
this.focusRegion.init();
}

/**
* Disables the focus region.
*/
disableFocusRegion(): void {
this.focusRegion.dispose();
}
}
1 change: 1 addition & 0 deletions plugins/workspace-minimap/src/positioned_minimap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class PositionedMinimap extends Minimap implements Blockly.IPositionable
protected width: number;
protected height: number;
id: string;

/**
* Constructor for a positionable minimap.
* @param workspace The workspace to mirror.
Expand Down
5 changes: 1 addition & 4 deletions plugins/workspace-minimap/test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
</head>

<body>
<div style="display: flex;">
<div id="positionedRoot" style="height: 98vh; width: 50vw;"></div>
<div id="unpositionedRoot" style="height: 98vh; width: 50vw;"></div>
</div>
<div id="root" style="height: 99vh; width: 100vw;"></div>
<script src="../build/test_bundle.js"></script>
</body>

Expand Down
9 changes: 1 addition & 8 deletions plugins/workspace-minimap/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,11 @@ import {toolboxCategories} from '@blockly/dev-tools';
import {Minimap, PositionedMinimap} from '../src/index';

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

// 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++) {
Expand Down
Loading