diff --git a/localtypings/pxtpackage.d.ts b/localtypings/pxtpackage.d.ts index 160a03e4b3d0..ddf78f775357 100644 --- a/localtypings/pxtpackage.d.ts +++ b/localtypings/pxtpackage.d.ts @@ -93,6 +93,10 @@ declare namespace pxt { theme?: string | pxt.Map; assetPack?: boolean; // if set to true, only the assets of this project will be imported when added as an extension (no code) assetPacks?: Map; // a map of dependency id to boolean that indicates which dependencies should be imported as asset packs + toolboxFilter?: { + namespaces: {[index: string]: "visible" | "hidden" | "disabled"}, + blocks: {[index: string]: "visible" | "hidden" | "disabled"}, + } } interface PackageExtension { diff --git a/webapp/src/package.ts b/webapp/src/package.ts index 4391f3bee654..f11638a2b048 100644 --- a/webapp/src/package.ts +++ b/webapp/src/package.ts @@ -1000,4 +1000,53 @@ export function getExtensionOfFileName(filename: string) { async function getHostCacheAsync(): Promise> { return getObjectStoreAsync(HOSTCACHE_TABLE) +} + +export function getProjectToolboxFilters() { + const filters: pxt.editor.ProjectFilters = {}; + + const deps = mainPkg.sortedDeps(); + // sort the dependencies by level and then id. lower level overrides + // higher level + deps.sort((a, b) => { + if (a.level === b.level) { + return pxt.U.strcmp(a.id, b.id); + } + return b.level - a.level; + }); + + const applyProjectFilter = (projectFilter: pxt.PackageConfig["toolboxFilter"], key: keyof pxt.PackageConfig["toolboxFilter"]) => { + if (!projectFilter[key]) { + return + } + + if (!filters[key]) { + filters[key] = {}; + } + + for (const entry of Object.keys(projectFilter[key])) { + const value = String(projectFilter[key][entry]).toLowerCase(); + if (value === "hidden") { + filters[key][entry] = pxt.editor.FilterState.Hidden; + } + else if (value === "visible") { + filters[key][entry] = pxt.editor.FilterState.Visible; + } + else if (value === "disabled") { + filters[key][entry] = pxt.editor.FilterState.Disabled; + } + } + } + + for (const dep of deps) { + const projectFilter = dep.config?.toolboxFilter; + if (!projectFilter) { + continue; + } + + applyProjectFilter(projectFilter, "blocks"); + applyProjectFilter(projectFilter, "namespaces"); + } + + return filters; } \ No newline at end of file diff --git a/webapp/src/toolboxeditor.tsx b/webapp/src/toolboxeditor.tsx index 144bdaa25fd2..55157e89276b 100644 --- a/webapp/src/toolboxeditor.tsx +++ b/webapp/src/toolboxeditor.tsx @@ -2,6 +2,7 @@ import * as srceditor from "./srceditor"; import * as toolbox from "./toolbox"; import * as compiler from "./compiler"; +import { getProjectToolboxFilters } from "./package"; export abstract class ToolboxEditor extends srceditor.Editor { @@ -17,11 +18,32 @@ export abstract class ToolboxEditor extends srceditor.Editor { abstract getBlocksForCategory(ns: string, subns?: string): toolbox.BlockDefinition[]; protected shouldShowBlock(blockId: string, ns: string, shadow?: boolean) { - const filters = this.parent.state.editorState && this.parent.state.editorState.filters; + let filters = this.parent.state.editorState && this.parent.state.editorState.filters; + + const projectFilter = getProjectToolboxFilters(); + + if (projectFilter) { + if (filters) { + // tutorial filters override project filters + pxt.U.jsonMergeFrom(projectFilter, filters); + } + + filters = projectFilter; + } + + if (filters) { - // block-level filters should not apply to shadow blocks (nested) - const blockFilter = filters.blocks && (filters.blocks[blockId] || (this.blockIdMap && this.blockIdMap[blockId]?.some(id => filters.blocks[id]))); + let blockFilter: pxt.editor.FilterState | boolean; + if (filters.blocks) { + if (filters.blocks[blockId] !== undefined) { + blockFilter = filters.blocks[blockId]; + } + else { + blockFilter = this.blockIdMap && this.blockIdMap[blockId]?.some(id => filters.blocks[id]); + } + } const categoryFilter = filters.namespaces && filters.namespaces[ns]; + // block-level filters should not apply to shadow blocks (nested) // First try block filters if (blockFilter != undefined && blockFilter == pxt.editor.FilterState.Hidden && !shadow) return false; if (blockFilter != undefined) return true;