diff --git a/packages/blockly/src/manager.ts b/packages/blockly/src/manager.ts index b29a432..f97bc69 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,79 @@ 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 b648951..fa3f605 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,55 @@ 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 + 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) + ) { + 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}`); + } + } + if (metadata['allowed_blocks']) { + this._manager.setAllowedBlocks(metadata['allowed_blocks']); + } + } + } else { + // Unsupported format + showErrorMessage( + 'Unsupported file format', + `The file format '${fileFormat}' is not supported by the Blockly editor.` + ); + } } private _onSave( @@ -150,7 +200,16 @@ 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(), + allowed_blocks: this._manager.getAllowedBlocks(), + kernel: this._manager.kernel + } + }; + this._context.model.fromJSON(fileContent); } } }