From 10aa25edae012774340a487a0d9062e7509c7a92 Mon Sep 17 00:00:00 2001 From: Phoenix616 Date: Tue, 17 Sep 2024 17:40:01 +0100 Subject: [PATCH 1/3] Store selected kernel and toolbox in .jpblockly file --- packages/blockly/src/widget.ts | 55 +++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/blockly/src/widget.ts b/packages/blockly/src/widget.ts index b648951..77bbad8 100644 --- a/packages/blockly/src/widget.ts +++ b/packages/blockly/src/widget.ts @@ -5,7 +5,9 @@ import { } from '@jupyterlab/docregistry'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { runIcon } from '@jupyterlab/ui-components'; +import { showErrorMessage } from "@jupyterlab/apputils"; +import { PartialJSONObject } from "@lumino/coreutils"; import { SplitPanel } from '@lumino/widgets'; import { Signal } from '@lumino/signaling'; @@ -82,6 +84,7 @@ export namespace BlocklyEditor { */ export class BlocklyPanel extends SplitPanel { private _context: DocumentRegistry.IContext; + private _manager: BlocklyManager; private _rendermime: IRenderMimeRegistry; /** @@ -105,6 +108,7 @@ export class BlocklyPanel extends SplitPanel { }); this.addClass('jp-BlocklyPanel'); this._context = context; + this._manager = manager; this._rendermime = rendermime; // Load the content of the file when the context is ready @@ -139,9 +143,44 @@ export class BlocklyPanel extends SplitPanel { } private _load(): void { - // Loading the content of the document into the workspace - const content = this._context.model.toJSON() as any as Blockly.Workspace; - (this.layout as BlocklyLayout).workspace = content; + const fileContent = this._context.model.toJSON(); + const fileFormat = fileContent['format']; + // Check if format is set or if we have legacy content + if (fileFormat === undefined && fileContent['blocks']) { + // Load legacy content + (this.layout as BlocklyLayout).workspace = fileContent as any as Blockly.Workspace; + } else if (fileFormat === 2) { + // Load the content from the "workspace" key + (this.layout as BlocklyLayout).workspace = fileContent['workspace'] as any as Blockly.Workspace; + const metadata = fileContent['metadata']; + if (metadata) { + if (metadata['toolbox']) { + const toolbox = metadata['toolbox']; + if (this._manager.listToolboxes().find(value => value.value === toolbox)) { + this._manager.setToolbox(metadata['toolbox']); + } else { + // Unknown toolbox + showErrorMessage(`Unknown toolbox`, + `The toolbox '` + toolbox + `' is not available. Using default toolbox.` + ); + } + } + if (metadata['kernel'] && metadata['kernel'] !== 'No kernel') { + const kernel = metadata['kernel']; + if (this._manager.listKernels().find(value => value.value === kernel)) { + this._manager.selectKernel(metadata['kernel']); + } else { + // Unknown kernel + console.warn(`Unknown kernel in blockly file: ` + kernel); + } + } + } + } else { + // Unsupported format + showErrorMessage(`Unsupported file format`, + `The file format '` + fileFormat + `' is not supported by the Blockly editor.` + ); + } } private _onSave( @@ -150,7 +189,15 @@ export class BlocklyPanel extends SplitPanel { ): void { if (state === 'started') { const workspace = (this.layout as BlocklyLayout).workspace; - this._context.model.fromJSON(workspace as any); + const fileContent: PartialJSONObject = { + format: 2, + workspace: workspace as any, + metadata: { + toolbox: this._manager.getToolbox(), + kernel: this._manager.kernel + } + }; + this._context.model.fromJSON(fileContent); } } } From 44f2eb7a2d252590a6fbcb9112fae7644ca1b037 Mon Sep 17 00:00:00 2001 From: Phoenix616 Date: Thu, 19 Sep 2024 16:59:57 +0100 Subject: [PATCH 2/3] Add ability to specify allowed blocks in the jpblockly files --- packages/blockly/src/manager.ts | 79 ++++++++++++++++++++++++++++++++- packages/blockly/src/widget.ts | 4 ++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/blockly/src/manager.ts b/packages/blockly/src/manager.ts index b29a432..97a5ee0 100644 --- a/packages/blockly/src/manager.ts +++ b/packages/blockly/src/manager.ts @@ -8,7 +8,14 @@ import { ISignal, Signal } from '@lumino/signaling'; import * as Blockly from 'blockly'; import { BlocklyRegistry } from './registry'; -import { ToolboxDefinition } from 'blockly/core/utils/toolbox'; +import { + BlockInfo, + DynamicCategoryInfo, + StaticCategoryInfo, + ToolboxDefinition, + ToolboxInfo, + ToolboxItemInfo +} from 'blockly/core/utils/toolbox'; /** * BlocklyManager the manager for each document @@ -17,6 +24,7 @@ import { ToolboxDefinition } from 'blockly/core/utils/toolbox'; */ export class BlocklyManager { private _toolbox: string; + private _allowedBlocks: string[]; private _generator: Blockly.Generator; private _registry: BlocklyRegistry; private _selectedKernel: KernelSpec.ISpecModel; @@ -37,6 +45,7 @@ export class BlocklyManager { this._mimetypeService = mimetypeService; this._toolbox = 'default'; + this._filterToolbox(); this._generator = this._registry.generators.get('python'); this._changed = new Signal(this); @@ -112,6 +121,7 @@ export class BlocklyManager { if (this._toolbox !== name) { const toolbox = this._registry.toolboxes.get(name); this._toolbox = toolbox ? name : 'default'; + this._filterToolbox(); this._changed.emit('toolbox'); } } @@ -129,6 +139,73 @@ export class BlocklyManager { return list; } + /** + * Get the list of allowed blocks. If undefined, all blocks are allowed. + * + * @returns The list of allowed blocks. + */ + getAllowedBlocks() { + return this._allowedBlocks; + } + + /** + * Set the list of allowed blocks. If undefined, all blocks are allowed. + * + * @param allowedBlocks The list of allowed blocks. + */ + setAllowedBlocks(allowedBlocks: string[]) { + this._allowedBlocks = allowedBlocks; + this._filterToolbox(); + this._changed.emit('toolbox'); + } + + private _filterToolbox() { + const toolbox = this._registry.toolboxes.get(this._toolbox) as ToolboxInfo; + if (toolbox) { + this._filterContents(toolbox.contents); + } + } + + private _filterContents(contents: ToolboxItemInfo[]): number { + let visible = 0; + contents.forEach(itemInfo => { + if ("kind" in itemInfo) { + if (itemInfo.kind.toUpperCase() === "CATEGORY") { + if ("contents" in itemInfo) { + const categoryInfo = itemInfo as StaticCategoryInfo; + if (this._filterContents(categoryInfo.contents) > 0) { + visible++; + categoryInfo.hidden = "false"; + } else { + categoryInfo.hidden = "true"; + } + } else if ("custom" in itemInfo) { + const categoryInfo = itemInfo as DynamicCategoryInfo; + if (this._allowedBlocks === undefined || this._allowedBlocks.includes(categoryInfo.custom.toLowerCase())) { + categoryInfo.hidden = "false"; + visible++; + console.log("Category " + categoryInfo.custom + " is allowed"); + } else { + categoryInfo.hidden = "true"; + console.log("Category " + categoryInfo.custom + " is not allowed"); + } + } + } else if (itemInfo.kind.toUpperCase() === "BLOCK") { + const blockInfo = itemInfo as BlockInfo; + if (this._allowedBlocks === undefined || this._allowedBlocks.includes(blockInfo.type.toLowerCase())) { + blockInfo.disabled = false; + blockInfo.disabledReasons = []; + visible++; + } else { + blockInfo.disabled = true; + blockInfo.disabledReasons = ["This block is not allowed"]; + } + } + } + }); + return visible; + } + /** * Set the selected kernel. * diff --git a/packages/blockly/src/widget.ts b/packages/blockly/src/widget.ts index 77bbad8..be164b0 100644 --- a/packages/blockly/src/widget.ts +++ b/packages/blockly/src/widget.ts @@ -174,6 +174,9 @@ export class BlocklyPanel extends SplitPanel { console.warn(`Unknown kernel in blockly file: ` + kernel); } } + if (metadata['allowed_blocks']) { + this._manager.setAllowedBlocks(metadata['allowed_blocks']); + } } } else { // Unsupported format @@ -194,6 +197,7 @@ export class BlocklyPanel extends SplitPanel { workspace: workspace as any, metadata: { toolbox: this._manager.getToolbox(), + allowed_blocks: this._manager.getAllowedBlocks(), kernel: this._manager.kernel } }; From 3e79165a596377e786c853b90d4f090ae68b0f98 Mon Sep 17 00:00:00 2001 From: Phoenix616 Date: Mon, 7 Oct 2024 11:00:54 +0100 Subject: [PATCH 3/3] Fix code style issues --- packages/blockly/src/manager.ts | 34 +++++++++++++++++++-------------- packages/blockly/src/widget.ts | 30 ++++++++++++++++++----------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/packages/blockly/src/manager.ts b/packages/blockly/src/manager.ts index 97a5ee0..f97bc69 100644 --- a/packages/blockly/src/manager.ts +++ b/packages/blockly/src/manager.ts @@ -169,36 +169,42 @@ export class BlocklyManager { private _filterContents(contents: ToolboxItemInfo[]): number { let visible = 0; contents.forEach(itemInfo => { - if ("kind" in itemInfo) { - if (itemInfo.kind.toUpperCase() === "CATEGORY") { - if ("contents" in itemInfo) { + if ('kind' in itemInfo) { + if (itemInfo.kind.toUpperCase() === 'CATEGORY') { + if ('contents' in itemInfo) { const categoryInfo = itemInfo as StaticCategoryInfo; if (this._filterContents(categoryInfo.contents) > 0) { visible++; - categoryInfo.hidden = "false"; + categoryInfo.hidden = 'false'; } else { - categoryInfo.hidden = "true"; + categoryInfo.hidden = 'true'; } - } else if ("custom" in itemInfo) { + } else if ('custom' in itemInfo) { const categoryInfo = itemInfo as DynamicCategoryInfo; - if (this._allowedBlocks === undefined || this._allowedBlocks.includes(categoryInfo.custom.toLowerCase())) { - categoryInfo.hidden = "false"; + if ( + this._allowedBlocks === undefined || + this._allowedBlocks.includes(categoryInfo.custom.toLowerCase()) + ) { + categoryInfo.hidden = 'false'; visible++; - console.log("Category " + categoryInfo.custom + " is allowed"); + console.log(`Category ${categoryInfo.custom} is allowed`); } else { - categoryInfo.hidden = "true"; - console.log("Category " + categoryInfo.custom + " is not allowed"); + categoryInfo.hidden = 'true'; + console.log(`Category ${categoryInfo.custom} is not allowed`); } } - } else if (itemInfo.kind.toUpperCase() === "BLOCK") { + } else if (itemInfo.kind.toUpperCase() === 'BLOCK') { const blockInfo = itemInfo as BlockInfo; - if (this._allowedBlocks === undefined || this._allowedBlocks.includes(blockInfo.type.toLowerCase())) { + if ( + this._allowedBlocks === undefined || + this._allowedBlocks.includes(blockInfo.type.toLowerCase()) + ) { blockInfo.disabled = false; blockInfo.disabledReasons = []; visible++; } else { blockInfo.disabled = true; - blockInfo.disabledReasons = ["This block is not allowed"]; + blockInfo.disabledReasons = ['This block is not allowed']; } } } diff --git a/packages/blockly/src/widget.ts b/packages/blockly/src/widget.ts index be164b0..fa3f605 100644 --- a/packages/blockly/src/widget.ts +++ b/packages/blockly/src/widget.ts @@ -5,9 +5,9 @@ import { } from '@jupyterlab/docregistry'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { runIcon } from '@jupyterlab/ui-components'; -import { showErrorMessage } from "@jupyterlab/apputils"; +import { showErrorMessage } from '@jupyterlab/apputils'; -import { PartialJSONObject } from "@lumino/coreutils"; +import { PartialJSONObject } from '@lumino/coreutils'; import { SplitPanel } from '@lumino/widgets'; import { Signal } from '@lumino/signaling'; @@ -148,30 +148,37 @@ export class BlocklyPanel extends SplitPanel { // Check if format is set or if we have legacy content if (fileFormat === undefined && fileContent['blocks']) { // Load legacy content - (this.layout as BlocklyLayout).workspace = fileContent as any as Blockly.Workspace; + (this.layout as BlocklyLayout).workspace = + fileContent as any as Blockly.Workspace; } else if (fileFormat === 2) { // Load the content from the "workspace" key - (this.layout as BlocklyLayout).workspace = fileContent['workspace'] as any as Blockly.Workspace; + const workspace = fileContent['workspace'] as any as Blockly.Workspace; + (this.layout as BlocklyLayout).workspace = workspace; const metadata = fileContent['metadata']; if (metadata) { if (metadata['toolbox']) { const toolbox = metadata['toolbox']; - if (this._manager.listToolboxes().find(value => value.value === toolbox)) { + if ( + this._manager.listToolboxes().find(value => value.value === toolbox) + ) { this._manager.setToolbox(metadata['toolbox']); } else { // Unknown toolbox - showErrorMessage(`Unknown toolbox`, - `The toolbox '` + toolbox + `' is not available. Using default toolbox.` + showErrorMessage( + 'Unknown toolbox', + `The toolbox '${toolbox}' is not available. Using default toolbox.` ); } } if (metadata['kernel'] && metadata['kernel'] !== 'No kernel') { const kernel = metadata['kernel']; - if (this._manager.listKernels().find(value => value.value === kernel)) { + if ( + this._manager.listKernels().find(value => value.value === kernel) + ) { this._manager.selectKernel(metadata['kernel']); } else { // Unknown kernel - console.warn(`Unknown kernel in blockly file: ` + kernel); + console.warn(`Unknown kernel in blockly file: ${kernel}`); } } if (metadata['allowed_blocks']) { @@ -180,8 +187,9 @@ export class BlocklyPanel extends SplitPanel { } } else { // Unsupported format - showErrorMessage(`Unsupported file format`, - `The file format '` + fileFormat + `' is not supported by the Blockly editor.` + showErrorMessage( + 'Unsupported file format', + `The file format '${fileFormat}' is not supported by the Blockly editor.` ); } }