mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40:08 +01:00
* chore(deps): Bump prettier from 2.8.8 to 3.0.0 Bumps [prettier](https://github.com/prettier/prettier) from 2.8.8 to 3.0.0. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.8.8...3.0.0) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore: Reformat using Prettier v3.0 defaults The main change is to add trailing commas to the last line of block-formatted function calls. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Christopher Allen <cpcallen+git@google.com>
327 lines
8.2 KiB
TypeScript
327 lines
8.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Class for a button in the flyout.
|
|
*
|
|
* @class
|
|
*/
|
|
import * as goog from '../closure/goog/goog.js';
|
|
goog.declareModuleId('Blockly.FlyoutButton');
|
|
|
|
import * as browserEvents from './browser_events.js';
|
|
import * as Css from './css.js';
|
|
import {Coordinate} from './utils/coordinate.js';
|
|
import * as dom from './utils/dom.js';
|
|
import * as parsing from './utils/parsing.js';
|
|
import * as style from './utils/style.js';
|
|
import {Svg} from './utils/svg.js';
|
|
import type * as toolbox from './utils/toolbox.js';
|
|
import type {WorkspaceSvg} from './workspace_svg.js';
|
|
|
|
/**
|
|
* Class for a button or label in the flyout.
|
|
*/
|
|
export class FlyoutButton {
|
|
/** The horizontal margin around the text in the button. */
|
|
static TEXT_MARGIN_X = 5;
|
|
|
|
/** The vertical margin around the text in the button. */
|
|
static TEXT_MARGIN_Y = 2;
|
|
|
|
/** The radius of the flyout button's borders. */
|
|
static BORDER_RADIUS = 4;
|
|
|
|
private readonly text: string;
|
|
private readonly position: Coordinate;
|
|
private readonly callbackKey: string;
|
|
private readonly cssClass: string | null;
|
|
|
|
/** Mouse up event data. */
|
|
private onMouseUpWrapper: browserEvents.Data | null = null;
|
|
info: toolbox.ButtonOrLabelInfo;
|
|
|
|
/** The width of the button's rect. */
|
|
width = 0;
|
|
|
|
/** The height of the button's rect. */
|
|
height = 0;
|
|
|
|
/** The root SVG group for the button or label. */
|
|
private svgGroup: SVGGElement | null = null;
|
|
|
|
/** The SVG element with the text of the label or button. */
|
|
private svgText: SVGTextElement | null = null;
|
|
|
|
/**
|
|
* @param workspace The workspace in which to place this button.
|
|
* @param targetWorkspace The flyout's target workspace.
|
|
* @param json The JSON specifying the label/button.
|
|
* @param isLabel_ Whether this button should be styled as a label.
|
|
* @internal
|
|
*/
|
|
constructor(
|
|
private readonly workspace: WorkspaceSvg,
|
|
private readonly targetWorkspace: WorkspaceSvg,
|
|
json: toolbox.ButtonOrLabelInfo,
|
|
private readonly isLabel_: boolean,
|
|
) {
|
|
this.text = json['text'];
|
|
|
|
this.position = new Coordinate(0, 0);
|
|
|
|
/** The key to the function called when this button is clicked. */
|
|
this.callbackKey =
|
|
(json as AnyDuringMigration)[
|
|
'callbackKey'
|
|
] /* Check the lower case version
|
|
too to satisfy IE */ ||
|
|
(json as AnyDuringMigration)['callbackkey'];
|
|
|
|
/** If specified, a CSS class to add to this button. */
|
|
this.cssClass = (json as AnyDuringMigration)['web-class'] || null;
|
|
|
|
/** The JSON specifying the label / button. */
|
|
this.info = json;
|
|
}
|
|
|
|
/**
|
|
* Create the button elements.
|
|
*
|
|
* @returns The button's SVG group.
|
|
*/
|
|
createDom(): SVGElement {
|
|
let cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton';
|
|
if (this.cssClass) {
|
|
cssClass += ' ' + this.cssClass;
|
|
}
|
|
|
|
this.svgGroup = dom.createSvgElement(
|
|
Svg.G,
|
|
{'class': cssClass},
|
|
this.workspace.getCanvas(),
|
|
);
|
|
|
|
let shadow;
|
|
if (!this.isLabel_) {
|
|
// Shadow rectangle (light source does not mirror in RTL).
|
|
shadow = dom.createSvgElement(
|
|
Svg.RECT,
|
|
{
|
|
'class': 'blocklyFlyoutButtonShadow',
|
|
'rx': FlyoutButton.BORDER_RADIUS,
|
|
'ry': FlyoutButton.BORDER_RADIUS,
|
|
'x': 1,
|
|
'y': 1,
|
|
},
|
|
this.svgGroup!,
|
|
);
|
|
}
|
|
// Background rectangle.
|
|
const rect = dom.createSvgElement(
|
|
Svg.RECT,
|
|
{
|
|
'class': this.isLabel_
|
|
? 'blocklyFlyoutLabelBackground'
|
|
: 'blocklyFlyoutButtonBackground',
|
|
'rx': FlyoutButton.BORDER_RADIUS,
|
|
'ry': FlyoutButton.BORDER_RADIUS,
|
|
},
|
|
this.svgGroup!,
|
|
);
|
|
|
|
const svgText = dom.createSvgElement(
|
|
Svg.TEXT,
|
|
{
|
|
'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText',
|
|
'x': 0,
|
|
'y': 0,
|
|
'text-anchor': 'middle',
|
|
},
|
|
this.svgGroup!,
|
|
);
|
|
let text = parsing.replaceMessageReferences(this.text);
|
|
if (this.workspace.RTL) {
|
|
// Force text to be RTL by adding an RLM.
|
|
text += '\u200F';
|
|
}
|
|
svgText.textContent = text;
|
|
if (this.isLabel_) {
|
|
this.svgText = svgText;
|
|
this.workspace
|
|
.getThemeManager()
|
|
.subscribe(this.svgText, 'flyoutForegroundColour', 'fill');
|
|
}
|
|
|
|
const fontSize = style.getComputedStyle(svgText, 'fontSize');
|
|
const fontWeight = style.getComputedStyle(svgText, 'fontWeight');
|
|
const fontFamily = style.getComputedStyle(svgText, 'fontFamily');
|
|
this.width = dom.getFastTextWidthWithSizeString(
|
|
svgText,
|
|
fontSize,
|
|
fontWeight,
|
|
fontFamily,
|
|
);
|
|
const fontMetrics = dom.measureFontMetrics(
|
|
text,
|
|
fontSize,
|
|
fontWeight,
|
|
fontFamily,
|
|
);
|
|
this.height = fontMetrics.height;
|
|
|
|
if (!this.isLabel_) {
|
|
this.width += 2 * FlyoutButton.TEXT_MARGIN_X;
|
|
this.height += 2 * FlyoutButton.TEXT_MARGIN_Y;
|
|
shadow?.setAttribute('width', String(this.width));
|
|
shadow?.setAttribute('height', String(this.height));
|
|
}
|
|
rect.setAttribute('width', String(this.width));
|
|
rect.setAttribute('height', String(this.height));
|
|
|
|
svgText.setAttribute('x', String(this.width / 2));
|
|
svgText.setAttribute(
|
|
'y',
|
|
String(this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline),
|
|
);
|
|
|
|
this.updateTransform();
|
|
|
|
// AnyDuringMigration because: Argument of type 'SVGGElement | null' is not
|
|
// assignable to parameter of type 'EventTarget'.
|
|
this.onMouseUpWrapper = browserEvents.conditionalBind(
|
|
this.svgGroup as AnyDuringMigration,
|
|
'pointerup',
|
|
this,
|
|
this.onMouseUp,
|
|
);
|
|
return this.svgGroup!;
|
|
}
|
|
|
|
/** Correctly position the flyout button and make it visible. */
|
|
show() {
|
|
this.updateTransform();
|
|
this.svgGroup!.setAttribute('display', 'block');
|
|
}
|
|
|
|
/** Update SVG attributes to match internal state. */
|
|
private updateTransform() {
|
|
this.svgGroup!.setAttribute(
|
|
'transform',
|
|
'translate(' + this.position.x + ',' + this.position.y + ')',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Move the button to the given x, y coordinates.
|
|
*
|
|
* @param x The new x coordinate.
|
|
* @param y The new y coordinate.
|
|
*/
|
|
moveTo(x: number, y: number) {
|
|
this.position.x = x;
|
|
this.position.y = y;
|
|
this.updateTransform();
|
|
}
|
|
|
|
/** @returns Whether or not the button is a label. */
|
|
isLabel(): boolean {
|
|
return this.isLabel_;
|
|
}
|
|
|
|
/**
|
|
* Location of the button.
|
|
*
|
|
* @returns x, y coordinates.
|
|
* @internal
|
|
*/
|
|
getPosition(): Coordinate {
|
|
return this.position;
|
|
}
|
|
|
|
/** @returns Text of the button. */
|
|
getButtonText(): string {
|
|
return this.text;
|
|
}
|
|
|
|
/**
|
|
* Get the button's target workspace.
|
|
*
|
|
* @returns The target workspace of the flyout where this button resides.
|
|
*/
|
|
getTargetWorkspace(): WorkspaceSvg {
|
|
return this.targetWorkspace;
|
|
}
|
|
|
|
/** Dispose of this button. */
|
|
dispose() {
|
|
if (this.onMouseUpWrapper) {
|
|
browserEvents.unbind(this.onMouseUpWrapper);
|
|
}
|
|
if (this.svgGroup) {
|
|
dom.removeNode(this.svgGroup);
|
|
}
|
|
if (this.svgText) {
|
|
this.workspace.getThemeManager().unsubscribe(this.svgText);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do something when the button is clicked.
|
|
*
|
|
* @param e Pointer up event.
|
|
*/
|
|
private onMouseUp(e: PointerEvent) {
|
|
const gesture = this.targetWorkspace.getGesture(e);
|
|
if (gesture) {
|
|
gesture.cancel();
|
|
}
|
|
|
|
if (this.isLabel_ && this.callbackKey) {
|
|
console.warn(
|
|
'Labels should not have callbacks. Label text: ' + this.text,
|
|
);
|
|
} else if (
|
|
!this.isLabel_ &&
|
|
!(
|
|
this.callbackKey &&
|
|
this.targetWorkspace.getButtonCallback(this.callbackKey)
|
|
)
|
|
) {
|
|
console.warn('Buttons should have callbacks. Button text: ' + this.text);
|
|
} else if (!this.isLabel_) {
|
|
const callback = this.targetWorkspace.getButtonCallback(this.callbackKey);
|
|
if (callback) {
|
|
callback(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** CSS for buttons and labels. See css.js for use. */
|
|
Css.register(`
|
|
.blocklyFlyoutButton {
|
|
fill: #888;
|
|
cursor: default;
|
|
}
|
|
|
|
.blocklyFlyoutButtonShadow {
|
|
fill: #666;
|
|
}
|
|
|
|
.blocklyFlyoutButton:hover {
|
|
fill: #aaa;
|
|
}
|
|
|
|
.blocklyFlyoutLabel {
|
|
cursor: default;
|
|
}
|
|
|
|
.blocklyFlyoutLabelBackground {
|
|
opacity: 0;
|
|
}
|
|
`);
|