Skip to content
Open
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
6 changes: 5 additions & 1 deletion core/blockly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
import {VerticalFlyout} from './flyout_vertical.js';
import {CodeGenerator} from './generator.js';
import {Gesture} from './gesture.js';
import {Grid} from './grid.js';
import {Grid, GridProvider} from './grid.js';
import * as icons from './icons.js';
import {inject} from './inject.js';
import * as inputs from './inputs.js';
Expand Down Expand Up @@ -132,6 +132,7 @@ import {
} from './interfaces/i_draggable.js';
import {IDragger} from './interfaces/i_dragger.js';
import {IFlyout} from './interfaces/i_flyout.js';
import {IGrid, IGridProvider} from './interfaces/i_grid.js';
import {IHasBubble, hasBubble} from './interfaces/i_has_bubble.js';
import {IIcon, isIcon} from './interfaces/i_icon.js';
import {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js';
Expand Down Expand Up @@ -508,6 +509,7 @@ export {
CodeGenerator as Generator,
Gesture,
Grid,
GridProvider,
HorizontalFlyout,
IASTNodeLocation,
IASTNodeLocationSvg,
Expand All @@ -529,6 +531,8 @@ export {
IDraggable,
IDragger,
IFlyout,
IGrid,
IGridProvider,
IHasBubble,
IIcon,
IKeyboardAccessible,
Expand Down
124 changes: 77 additions & 47 deletions core/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,89 @@
*/
// Former goog.module ID: Blockly.Grid

import {BlocklyOptions} from './blockly_options.js';
import {IGrid, IGridProvider} from './interfaces/i_grid.js';
import {GridOptions} from './options.js';
import * as registry from './registry.js';
import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';

export class GridProvider implements IGridProvider {
/**
* Create the DOM for the grid described by options.
*
* @param rnd A random ID to append to the pattern's ID.
* @param gridOptions The object containing grid configuration.
* @param defs The root SVG element for this workspace's defs.
* @returns The SVG element for the grid pattern.
* @internal
*/
createDom(
rnd: string,
gridOptions: GridOptions,
defs: SVGElement,
): SVGElement {
/*
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
<rect stroke="#888" />
<rect stroke="#888" />
</pattern>
*/
const gridPattern = dom.createSvgElement(
Svg.PATTERN,
{'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'},
defs,
);
// x1, y1, x1, x2 properties will be set later in update.
if ((gridOptions['length'] ?? 1) > 0 && (gridOptions['spacing'] ?? 0) > 0) {
dom.createSvgElement(
Svg.LINE,
{'stroke': gridOptions['colour']},
gridPattern,
);
if (gridOptions['length'] ?? 1 > 1) {
dom.createSvgElement(
Svg.LINE,
{'stroke': gridOptions['colour']},
gridPattern,
);
}
} else {
// Edge 16 doesn't handle empty patterns
dom.createSvgElement(Svg.LINE, {}, gridPattern);
}
return gridPattern;
}

/**
* Parse the user-specified grid options, using reasonable defaults where
* behaviour is unspecified. See grid documentation:
* https://developers.google.com/blockly/guides/configure/web/grid
*
* @param options Dictionary of options.
* @returns Normalized grid options.
*/
parseGridOptions(options: BlocklyOptions): GridOptions {
const grid = options['grid'] || {};
const gridOptions = {} as GridOptions;
gridOptions.spacing = Number(grid['spacing']) || 0;
gridOptions.colour = grid['colour'] || '#888';
gridOptions.length =
grid['length'] === undefined ? 1 : Number(grid['length']);
gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
return gridOptions;
}

createGrid(pattern: SVGElement, options: GridOptions): IGrid {
return new Grid(pattern, options);
}
}

/**
* Class for a workspace's grid.
*/
export class Grid {
export class Grid implements IGrid {
private spacing: number;
private length: number;
private scale: number = 1;
Expand Down Expand Up @@ -203,50 +277,6 @@ export class Grid {
}
return new Coordinate(x, y);
}

/**
* Create the DOM for the grid described by options.
*
* @param rnd A random ID to append to the pattern's ID.
* @param gridOptions The object containing grid configuration.
* @param defs The root SVG element for this workspace's defs.
* @returns The SVG element for the grid pattern.
* @internal
*/
static createDom(
rnd: string,
gridOptions: GridOptions,
defs: SVGElement,
): SVGElement {
/*
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
<rect stroke="#888" />
<rect stroke="#888" />
</pattern>
*/
const gridPattern = dom.createSvgElement(
Svg.PATTERN,
{'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'},
defs,
);
// x1, y1, x1, x2 properties will be set later in update.
if ((gridOptions['length'] ?? 1) > 0 && (gridOptions['spacing'] ?? 0) > 0) {
dom.createSvgElement(
Svg.LINE,
{'stroke': gridOptions['colour']},
gridPattern,
);
if (gridOptions['length'] ?? 1 > 1) {
dom.createSvgElement(
Svg.LINE,
{'stroke': gridOptions['colour']},
gridPattern,
);
}
} else {
// Edge 16 doesn't handle empty patterns
dom.createSvgElement(Svg.LINE, {}, gridPattern);
}
return gridPattern;
}
}

registry.register(registry.Type.GRID_PROVIDER, registry.DEFAULT, GridProvider);
8 changes: 6 additions & 2 deletions core/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import * as bumpObjects from './bump_objects.js';
import * as common from './common.js';
import * as Css from './css.js';
import * as dropDownDiv from './dropdowndiv.js';
import {Grid} from './grid.js';
import {Msg} from './msg.js';
import {Options} from './options.js';
import {ScrollbarPair} from './scrollbar_pair.js';
Expand Down Expand Up @@ -141,7 +140,12 @@ function createDom(container: Element, options: Options): SVGElement {
// https://neil.fraser.name/news/2015/11/01/
const rnd = String(Math.random()).substring(2);

options.gridPattern = Grid.createDom(rnd, options.gridOptions, defs);
options.gridPattern = options.gridProvider.createDom(
rnd,
options.gridOptions,
defs,
);

return svg;
}

Expand Down
116 changes: 116 additions & 0 deletions core/interfaces/i_grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {BlocklyOptions} from '../blockly_options.js';
import {GridOptions} from '../options.js';
import {Coordinate} from '../utils';
import type {IRegistrable} from './i_registrable.js';

export interface IGrid {
/**
* Sets the spacing between the centers of the grid lines.
*
* This does not trigger snapping to the newly spaced grid. If you want to
* snap blocks to the grid programmatically that needs to be triggered
* on individual top-level blocks. The next time a block is dragged and
* dropped it will snap to the grid if snapping to the grid is enabled.
*/
setSpacing(spacing: number): void;

/**
* Get the spacing of the grid points (in px).
*
* @returns The spacing of the grid points.
*/
getSpacing(): number;

/** Sets the length of the grid lines. */
setLength(length: number): void;

/** Get the length of the grid lines (in px). */
getLength(): number;

/**
* Sets whether blocks should snap to the grid or not.
*
* Setting this to true does not trigger snapping. If you want to snap blocks
* to the grid programmatically that needs to be triggered on individual
* top-level blocks. The next time a block is dragged and dropped it will
* snap to the grid.
*/
setSnapToGrid(snap: boolean): void;

/**
* Whether blocks should snap to the grid.
*
* @returns True if blocks should snap, false otherwise.
*/
shouldSnap(): boolean;

/**
* Get the ID of the pattern element, which should be randomized to avoid
* conflicts with other Blockly instances on the page.
*
* @returns The pattern ID.
* @internal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout, I don't think we want @internal annotations in the interface; if we're committing it to the interface, that makes it public API.

*/
getPatternId(): string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest removing this; it's quite low level, and its only caller in core is in WorkspaceSvg, which can be eliminated easily if we follow the approach I outlined in my top-level comment.


/**
* Update the grid with a new scale.
*
* @param scale The new workspace scale.
* @internal
*/
update(scale: number): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this setScale() and getScale() for consistency. The built-in Grid implementation can still call update() to adjust itself in response to changes in its configuration, but that doesn't need to be part of the interface (or require scale as an argument).


/**
* Move the grid to a new x and y position, and make sure that change is
* visible.
*
* @param x The new x position of the grid (in px).
* @param y The new y position of the grid (in px).
* @internal
*/
moveTo(x: number, y: number): void;

/**
* Given a coordinate, return the nearest coordinate aligned to the grid.
*
* @param xy A workspace coordinate.
* @returns Workspace coordinate of nearest grid point.
* If there's no change, return the same coordinate object.
*/
alignXY(xy: Coordinate): Coordinate;
}

export interface IGridProvider extends IRegistrable {
/**
* Create the DOM for the grid described by options.
*
* @param rnd A random ID to append to the pattern's ID.
* @param gridOptions The object containing grid configuration.
* @param defs The root SVG element for this workspace's defs.
* @returns The SVG element for the grid pattern.
*/
createDom(
rnd: string,
gridOptions: GridOptions,
defs: SVGElement,
): SVGElement;

/**
* Parse the user-specified grid options, using reasonable defaults where
* behaviour is unspecified. See grid documentation:
* https://developers.google.com/blockly/guides/configure/web/grid
*
* @param options Dictionary of options.
* @returns Normalized grid options.
*/
parseGridOptions(options: BlocklyOptions): GridOptions;

/**
* @param pattern The grid's SVG pattern, created during injection.
* @param options A dictionary of normalized options for the grid.
* See grid documentation:
* https://developers.google.com/blockly/guides/configure/web/grid
*/
createGrid(pattern: SVGElement, options: GridOptions): IGrid;
}
33 changes: 13 additions & 20 deletions core/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// Former goog.module ID: Blockly.Options

import type {BlocklyOptions} from './blockly_options.js';
import {IGridProvider} from './interfaces/i_grid.js';
import * as registry from './registry.js';
import {Theme} from './theme.js';
import {Classic} from './theme/classic.js';
Expand Down Expand Up @@ -60,6 +61,8 @@ export class Options {
parentWorkspace: WorkspaceSvg | null;
plugins: {[key: string]: (new (...p1: any[]) => any) | string};

gridProvider: IGridProvider;

/**
* If set, sets the translation of the workspace to match the scrollbars.
* A function that
Expand Down Expand Up @@ -175,7 +178,6 @@ export class Options {
this.hasCss = hasCss;
this.horizontalLayout = horizontalLayout;
this.languageTree = toolboxJsonDef;
this.gridOptions = Options.parseGridOptions(options);
this.zoomOptions = Options.parseZoomOptions(options);
this.toolboxPosition = toolboxPosition;
this.theme = Options.parseThemeOptions(options);
Expand All @@ -191,6 +193,16 @@ export class Options {

/** Map of plugin type to name of registered plugin or plugin class. */
this.plugins = plugins;

// This should be safe to call since this.plugins has been set
const GridProvider = registry.getClassFromOptions(
registry.Type.GRID_PROVIDER,
this,
true,
);

this.gridProvider = new GridProvider!();
this.gridOptions = this.gridProvider.parseGridOptions(options);
}

/**
Expand Down Expand Up @@ -301,25 +313,6 @@ export class Options {
return zoomOptions;
}

/**
* Parse the user-specified grid options, using reasonable defaults where
* behaviour is unspecified. See grid documentation:
* https://developers.google.com/blockly/guides/configure/web/grid
*
* @param options Dictionary of options.
* @returns Normalized grid options.
*/
private static parseGridOptions(options: BlocklyOptions): GridOptions {
const grid = options['grid'] || {};
const gridOptions = {} as GridOptions;
gridOptions.spacing = Number(grid['spacing']) || 0;
gridOptions.colour = grid['colour'] || '#888';
gridOptions.length =
grid['length'] === undefined ? 1 : Number(grid['length']);
gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
return gridOptions;
}

/**
* Parse the user-specified theme options, using the classic theme as a
* default. https://developers.google.com/blockly/guides/configure/web/themes
Expand Down
Loading
Loading