Skip to content

Commit

Permalink
fix: Add support for flyout labels with status indicators (#212)
Browse files Browse the repository at this point in the history
* chore: rename and move flyout_extension_category_header.js into src

* fix: add support for status indicators in flyout labels

* chore: improve docs for StatusIndicatorLabel
  • Loading branch information
gonfunko authored Oct 14, 2024
1 parent 0d78559 commit 665d196
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 9 deletions.
14 changes: 13 additions & 1 deletion src/checkable_continuous_flyout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as Blockly from "blockly/core";
import { ContinuousFlyout } from "@blockly/continuous-toolbox";
import { RecyclableBlockFlyoutInflater } from "./recyclable_block_flyout_inflater.js";
import { StatusIndicatorLabel } from "./status_indicator_label.js";

export class CheckableContinuousFlyout extends ContinuousFlyout {
/**
Expand Down Expand Up @@ -97,7 +98,7 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
const categoryLabels = this.getContents()
.filter(
(item) =>
item.type === "label" &&
(item.type === "label" || item.type === "status_indicator_label") &&
item.element.isLabel() &&
this.getParentToolbox_().getCategoryByName(
item.element.getButtonText()
Expand All @@ -123,4 +124,15 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
// updated for the new flyout API.
Blockly.VerticalFlyout.prototype.layout_.call(this, contents);
}

/**
* Updates the state of status indicators for hardware-based extensions.
*/
refreshStatusButtons() {
for (const item of this.contents) {
if (item.element instanceof StatusIndicatorLabel) {
item.element.refreshStatus();
}
}
}
}
9 changes: 8 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
import { CheckableContinuousFlyout } from "./checkable_continuous_flyout.js";
import { buildGlowFilter, glowStack } from "./glows.js";
import { ScratchContinuousToolbox } from "./scratch_continuous_toolbox.js";
import "./scratch_continuous_category.js";
import "./scratch_comment_icon.js";
import "./scratch_dragger.js";
import "./scratch_variable_map.js";
Expand Down Expand Up @@ -62,6 +61,8 @@ import { registerFieldVariableGetter } from "./fields/field_variable_getter.js";
import { registerFieldVariable } from "./fields/field_variable.js";
import { registerFieldVerticalSeparator } from "./fields/field_vertical_separator.js";
import { registerRecyclableBlockFlyoutInflater } from "./recyclable_block_flyout_inflater.js";
import { registerStatusIndicatorLabelFlyoutInflater } from "./status_indicator_label_flyout_inflater.js";
import { registerScratchContinuousCategory } from "./scratch_continuous_category.js";

export * from "blockly/core";
export * from "./block_reporting.js";
Expand All @@ -76,6 +77,10 @@ export { ScratchVariables };
export { contextMenuItems };
export { FieldColourSlider, FieldNote };
export { CheckboxBubble } from "./checkbox_bubble.js";
export {
StatusIndicatorLabel,
StatusButtonState,
} from "./status_indicator_label.js";

export function inject(container, options) {
registerFieldAngle();
Expand All @@ -89,6 +94,8 @@ export function inject(container, options) {
registerFieldVariable();
registerFieldVerticalSeparator();
registerRecyclableBlockFlyoutInflater();
registerStatusIndicatorLabelFlyoutInflater();
registerScratchContinuousCategory();

Object.assign(options, {
renderer: "scratch",
Expand Down
64 changes: 57 additions & 7 deletions src/scratch_continuous_category.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";
import { ContinuousCategory } from "@blockly/continuous-toolbox";

class ScratchContinuousCategory extends ContinuousCategory {
export class ScratchContinuousCategory extends ContinuousCategory {
/**
* Whether this toolbox category has a status indicator button on its label
* in the flyout, typically for extensions that interface with hardware
* devices.
* @type {boolean}
*/
showStatusButton = false;

/** Creates a new ScratchContinuousCategory.
*
* @param {!Blockly.toolbox.CategoryInfo} toolboxItemDef A toolbox item
* definition.
* @param {!Blockly.Toolbox} parentToolbox The toolbox this category is being
* added to.
* @param {?Blockly.ICollapsibleToolboxItem} opt_parent The parent toolbox
* category, if any.
*/
constructor(toolboxItemDef, parentToolbox, opt_parent) {
super(toolboxItemDef, parentToolbox, opt_parent);
this.showStatusButton = toolboxItemDef["showStatusButton"] === "true";
}

/**
* Creates a DOM element for this category's icon.
* @returns {!HTMLElement} A DOM element for this category's icon.
*/
createIconDom_() {
if (this.toolboxItemDef_.iconURI) {
const icon = document.createElement("img");
Expand All @@ -15,16 +47,34 @@ class ScratchContinuousCategory extends ContinuousCategory {
}
}

/**
* Sets whether or not this category is selected.
* @param {boolean} isSelected True if this category is selected.
*/
setSelected(isSelected) {
super.setSelected(isSelected);
// Prevent hardcoding the background color to grey.
this.rowDiv_.style.backgroundColor = "";
}

/**
* Returns whether or not this category's label in the flyout should display
* status indicators.
*/
shouldShowStatusButton() {
return this.showStatusButton;
}
}

Blockly.registry.register(
Blockly.registry.Type.TOOLBOX_ITEM,
Blockly.ToolboxCategory.registrationName,
ScratchContinuousCategory,
true
);
/** Registers this toolbox category and unregisters the default one. */
export function registerScratchContinuousCategory() {
Blockly.registry.unregister(
Blockly.registry.Type.TOOLBOX_ITEM,
ScratchContinuousCategory.registrationName
);
Blockly.registry.register(
Blockly.registry.Type.TOOLBOX_ITEM,
ScratchContinuousCategory.registrationName,
ScratchContinuousCategory
);
}
54 changes: 54 additions & 0 deletions src/scratch_continuous_toolbox.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";
import { ContinuousToolbox } from "@blockly/continuous-toolbox";
import { ScratchContinuousCategory } from "./scratch_continuous_category.js";

export class ScratchContinuousToolbox extends ContinuousToolbox {
postRenderCallbacks = [];
Expand All @@ -8,6 +15,49 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
// Intentionally a no-op, Scratch manually manages refreshing the toolbox via forceRerender().
}

/**
* Gets the contents that should be shown in the flyout.
* @returns {!Blockly.utils.toolbox.FlyoutItemInfoArray} Flyout contents.
*/
getInitialFlyoutContents_() {
// TODO(#211) Clean this up when the continuous toolbox plugin is updated.
/** @type {!Blockly.utils.toolbox.FlyoutItemInfoArray} */
let contents = [];
for (const toolboxItem of this.getToolboxItems()) {
if (toolboxItem instanceof ScratchContinuousCategory) {
if (toolboxItem.shouldShowStatusButton()) {
contents.push({
kind: "STATUS_INDICATOR_LABEL",
id: toolboxItem.getId(),
text: toolboxItem.getName(),
});
} else {
// Create a label node to go at the top of the category
contents.push({ kind: "LABEL", text: toolboxItem.getName() });
}
/**
* @type {string|Blockly.utils.toolbox.FlyoutItemInfoArray|
* Blockly.utils.toolbox.FlyoutItemInfo}
*/
let itemContents = toolboxItem.getContents();

// Handle custom categories (e.g. variables and functions)
if (typeof itemContents === "string") {
itemContents =
/** @type {!Blockly.utils.toolbox.DynamicCategoryInfo} */ ({
custom: itemContents,
kind: "CATEGORY",
});
}
contents = contents.concat(itemContents);
}
}
return contents;
}

/**
* Forcibly rerenders the toolbox, preserving selection when possible.
*/
forceRerender() {
const selectedCategoryName = this.selectedItem_?.getName();
super.refreshSelection();
Expand All @@ -18,6 +68,10 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
this.selectCategoryByName(selectedCategoryName);
}

/**
* Runs the specified callback after the next rerender.
* @param {!Function} A callback to run whenever the toolbox next rerenders.
*/
runAfterRerender(callback) {
this.postRenderCallbacks.push(callback);
}
Expand Down
Loading

0 comments on commit 665d196

Please # to comment.