Skip to content
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
21 changes: 0 additions & 21 deletions plugins/workspace-backpack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,27 +120,6 @@ beneficial for performance if you expect blocks stacks to be very large.
Note: Currently the empty Backpack context menu is registered globally, while
the others are registered per workspace.

### Blockly Languages

We do not currently support translating the text in this plugin to different
languages. However, if you would like to support multiple languages the messages
can be translated by assigning the following properties of Blockly.Msg

- `EMPTY_BACKPACK` (Default: "Empty") context menu - Empty the backpack.
- `REMOVE_FROM_BACKPACK` (Default: "Remove from Backpack") context menu - Remove
the selected Block from the backpack.
- `COPY_TO_BACKPACK` (Default: "Copy to Backpack") context menu - Copy the
selected Block to the backpack.
- `COPY_ALL_TO_BACKPACK` (Default: "Copy All Blocks to Backpack") Context menu -
copy all Blocks on the workspace to the backpack.
- `PASTE_ALL_FROM_BACKPACK` (Default: "Paste All Blocks from Backpack") context
menu - Paste all Blocks from the backpack to the workspace.

```javascript
Blockly.Msg['EMPTY_BACKPACK'] = 'Opróżnij plecak'; // Polish
// Inject workspace, etc...
```

## API

- `init`: Initializes the backpack.
Expand Down
161 changes: 126 additions & 35 deletions plugins/workspace-backpack/src/backpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import {Backpackable, isBackpackable} from './backpackable';
*/
export class Backpack
extends Blockly.DragTarget
implements Blockly.IAutoHideable, Blockly.IPositionable
implements
Blockly.IComponent,
Blockly.IContextMenu,
Blockly.IAutoHideable,
Blockly.IPositionable,
Blockly.IFocusableNode,
Blockly.IContextMenu
{
/** The unique id for this component. */
id = 'backpack';
Expand Down Expand Up @@ -132,6 +138,7 @@ export class Backpack
Blockly.ComponentManager.Capability.AUTOHIDEABLE,
Blockly.ComponentManager.Capability.DRAG_TARGET,
Blockly.ComponentManager.Capability.POSITIONABLE,
Blockly.ComponentManager.Capability.FOCUSABLE,
],
});
this.initFlyout();
Expand Down Expand Up @@ -219,9 +226,35 @@ export class Backpack
protected createDom() {
this.svgGroup_ = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.G,
{},
{
id: Blockly.utils.idGenerator.getNextUniqueId(),
tabindex: '0',
class: 'blocklyBackpackContainer',
},
null,
);
Blockly.utils.aria.setState(
this.svgGroup_,
Blockly.utils.aria.State.LABEL,
Blockly.Msg['OPEN_BACKPACK'],
);
Blockly.utils.aria.setRole(this.svgGroup_, Blockly.utils.aria.Role.BUTTON);
const margin = 8;
const cornerRadius = 2;
Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.RECT,
{
width: this.WIDTH_ + margin,
height: this.HEIGHT_ + margin,
class: 'blocklyFocusRing',
x: -1 * (margin / 2),
y: -1 * (margin / 2),
rx: cornerRadius,
ry: cornerRadius,
fill: 'none',
},
this.svgGroup_,
);
const rnd = Blockly.utils.idGenerator.genUid();
const clip = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.CLIPPATH,
Expand Down Expand Up @@ -767,21 +800,24 @@ export class Backpack
* Opens the backpack flyout.
*/
open() {
if (!this.isOpenable()) {
if (!this.isOpenable() || !this.flyout_ || !this.svgGroup_) {
return;
}
Blockly.utils.aria.setState(
this.svgGroup_,
Blockly.utils.aria.State.LABEL,
Blockly.Msg['CLOSE_BACKPACK'],
);
const jsons = this.contents_.map((text) => JSON.parse(text));
this.flyout_?.show(jsons);
// TODO: We can remove the setVisible check when updating from ^10.0.0 to
// ^11.
/* eslint-disable @typescript-eslint/no-explicit-any */
if (
this.workspace_.scrollbar &&
(this.workspace_.scrollbar as any).setVisible
) {
(this.workspace_.scrollbar as any).setVisible(false);
this.flyout_.show(jsons);
this.workspace_.scrollbar?.setVisible(false);
if (Blockly.keyboardNavigationController.getIsActive()) {
const flyoutWorkspace = this.flyout_.getWorkspace();
const firstItem = flyoutWorkspace?.getNavigator().getFirstNode();
if (firstItem) {
Blockly.getFocusManager().focusNode(firstItem);
}
}
/* eslint-enable @typescript-eslint/no-explicit-any */
Blockly.Events.fire(new BackpackOpen(true, this.workspace_.id));
}

Expand All @@ -800,21 +836,18 @@ export class Backpack
* Closes the backpack flyout.
*/
close() {
if (!this.isOpen()) {
if (!this.isOpen() || !this.svgGroup_) {
return;
}
this.flyout_?.hide();
// TODO: We can remove the setVisible check when updating from ^10.0.0 to
// ^11.
/* eslint-disable @typescript-eslint/no-explicit-any */
if (
this.workspace_.scrollbar &&
(this.workspace_.scrollbar as any).setVisible
) {
(this.workspace_.scrollbar as any).setVisible(true);
}
/* eslint-enable @typescript-eslint/no-explicit-any */
this.workspace_.scrollbar?.setVisible(true);

Blockly.Events.fire(new BackpackOpen(false, this.workspace_.id));
Blockly.utils.aria.setState(
this.svgGroup_,
Blockly.utils.aria.State.LABEL,
Blockly.Msg['OPEN_BACKPACK'],
);
}

/**
Expand All @@ -836,11 +869,11 @@ export class Backpack
*
* @param e Mouse event.
*/
protected onClick(e: Event) {
protected onClick(e?: Event) {
if (e instanceof MouseEvent && Blockly.browserEvents.isRightButton(e)) {
return;
}
this.open();
this.isOpen() ? this.close() : this.open();
const uiEvent = new (Blockly.Events.get(Blockly.Events.CLICK))(
null,
this.workspace_.id,
Expand Down Expand Up @@ -923,18 +956,76 @@ export class Backpack
* @param e A mouse down event.
*/
protected blockMouseDownWhenOpenable(e: Event) {
if (
e instanceof MouseEvent &&
!Blockly.browserEvents.isRightButton(e) &&
this.isOpenable()
) {
e.stopPropagation(); // Don't start a workspace scroll.
if (e instanceof MouseEvent) {
if (Blockly.browserEvents.isRightButton(e)) {
this.showContextMenu(e);
e.stopPropagation();
return;
}

if (this.isOpenable()) {
e.stopPropagation(); // Don't start a workspace scroll.
}
}
}

getFocusableElement(): HTMLElement | SVGElement {
if (!this.svgGroup_) {
throw new Error('Attempted to focus an uninitialized backpack');
}

return this.svgGroup_;
}

getFocusableTree(): Blockly.IFocusableTree {
return this.workspace_;
}

onNodeFocus(): void {}

onNodeBlur(): void {}

canBeFocused(): boolean {
return true;
}

performAction(e?: Event): void {
this.onClick(e);
}

/**
* Show the context menu for the backpack.
*
* @param e Event that triggered the display of the context menu.
*/
showContextMenu(e: Event): void {
if (!this.options.contextMenu?.emptyBackpack) return;

Blockly.ContextMenu.show(
e,
[
{
text: Blockly.Msg['EMPTY_BACKPACK'],
enabled: !!this.getCount(),
callback: () => {
this.empty();
},
weight: 0,
id: 'empty_backpack',
},
],
this.workspace_.RTL,
this.workspace_,
new Blockly.utils.Coordinate(
e instanceof MouseEvent ? e.clientX : this.left_,
e instanceof MouseEvent ? e.clientY : this.top_,
),
);
}
}

/**
* Base64 encoded data uri for backpack icon.
* Base64 encoded data uri for backpack icon.
*/
const backpackSvgDataUri =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC' +
Expand All @@ -951,7 +1042,7 @@ const backpackSvgDataUri =
'YxNHYtMUg4di0xaDdoMVYxM3oiLz48L2c+PC9nPjwvc3ZnPg==';

/**
* Base64 encoded data uri for backpack icon when filled.
* Base64 encoded data uri for backpack icon when filled.
*/
const backpackFilledSvgDataUri =
'data:image/svg+xml;base64,PD94bWwgdmVyc2' +
Expand Down Expand Up @@ -1003,7 +1094,7 @@ Blockly.Css.register(`
.blocklyBackpack {
opacity: 0.4;
}
.blocklyBackpackDarken {
.blocklyBackpackContainer:focus .blocklyBackpack, .blocklyBackpackDarken {
opacity: 0.6;
}
.blocklyBackpack:active {
Expand Down
41 changes: 0 additions & 41 deletions plugins/workspace-backpack/src/backpack_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,11 @@
* @author kozbial@google.com (Monica Kozbial)
*/

import './msg';

import * as Blockly from 'blockly/core';

import {Backpack} from './backpack';
import {BackpackContextMenuOptions} from './options';

/**
* Registers a context menu option to empty the backpack when right-clicked.
*
* @param workspace The workspace to register the context menu option on.
*/
function registerEmptyBackpack(workspace: Blockly.WorkspaceSvg) {
const prevConfigureContextMenu = workspace.configureContextMenu;
workspace.configureContextMenu = (menuOptions, e: Event) => {
const backpack = workspace
.getComponentManager()
.getComponent('backpack') as Backpack;
const backpackClientRect = backpack && backpack.getClientRect();
if (e instanceof PointerEvent && backpackClientRect) {
if (!backpack || !backpackClientRect.contains(e.clientX, e.clientY)) {
prevConfigureContextMenu &&
prevConfigureContextMenu.call(null, menuOptions, e);
return;
}
}
menuOptions.length = 0;
const backpackOptions = {
text: Blockly.Msg['EMPTY_BACKPACK'],
enabled: !!backpack.getCount(),
callback: function () {
backpack.empty();
},
scope: {
workspace,
},
weight: 0,
id: 'empty_backpack',
};
menuOptions.push(backpackOptions);
};
}

/**
* Registers a context menu option to remove a block from a backpack flyout.
*/
Expand Down Expand Up @@ -240,9 +202,6 @@ export function registerContextMenus(
contextMenuOptions: BackpackContextMenuOptions,
workspace: Blockly.WorkspaceSvg,
) {
if (contextMenuOptions.emptyBackpack) {
registerEmptyBackpack(workspace);
}
if (contextMenuOptions.removeFromBackpack) {
registerRemoveFromBackpack();
}
Expand Down
23 changes: 0 additions & 23 deletions plugins/workspace-backpack/src/msg.ts

This file was deleted.

Loading