diff --git a/pxtblocks/builtins/functions.ts b/pxtblocks/builtins/functions.ts index d90a09cde2c1..2beaec4a8c49 100644 --- a/pxtblocks/builtins/functions.ts +++ b/pxtblocks/builtins/functions.ts @@ -11,7 +11,6 @@ import { domToWorkspaceNoEvents } from "../importer"; import { shouldDuplicateOnDrag } from "../plugins/duplicateOnDrag"; import { PathObject } from "../plugins/renderer/pathObject"; import { FieldImageNoText } from "../fields/field_imagenotext"; -import { getGlobalProgram } from "../external"; export function initFunctions() { const msg = Blockly.Msg; diff --git a/pxtblocks/builtins/variables.ts b/pxtblocks/builtins/variables.ts index a63da8f91b64..acbaee1c5141 100644 --- a/pxtblocks/builtins/variables.ts +++ b/pxtblocks/builtins/variables.ts @@ -26,6 +26,7 @@ export function initVariables() { mostRecentVariable = variableModelList[0]; } let currentFile: string; + const globeIcon = "\uf0ac"; // variables getters first for (let i = 0; i < variableModelList.length; i++) { @@ -34,7 +35,8 @@ export function initVariables() { if (symbol) { if (currentFile !== symbol.file) { currentFile = symbol.file; - const label = createFlyoutGroupLabel(currentFile); + const label = createFlyoutGroupLabel(currentFile, globeIcon); + label.setAttribute("web-icon-color", "#fff"); xmlList.push(label); } } diff --git a/pxtblocks/plugins/functions/blocks/functionCallBlocks.ts b/pxtblocks/plugins/functions/blocks/functionCallBlocks.ts index 66d529b28dea..d276f7feff2d 100644 --- a/pxtblocks/plugins/functions/blocks/functionCallBlocks.ts +++ b/pxtblocks/plugins/functions/blocks/functionCallBlocks.ts @@ -88,10 +88,15 @@ const FUNCTION_CALL_MIXIN: FunctionCallMixin = { }, addFunctionLabel_: function (this: FunctionCallBlock, text) { - this.appendDummyInput("function_name").appendField( - new Blockly.FieldLabel(text, "functionNameText"), - "function_name" - ); + const input = this.appendDummyInput("function_name"); + + if (this.imported_) { + const globe = new Blockly.FieldLabel("\uf0ac", "blocklyText semanticIcon"); + globe.setClass("blocklyText semanticIcon"); + input.appendField(globe); + } + + input.appendField(new Blockly.FieldLabel(text, "functionNameText"), "function_name"); }, updateFunctionLabel_: function (this: FunctionCallBlock, text: string) { diff --git a/pxtblocks/plugins/functions/commonFunctionMixin.ts b/pxtblocks/plugins/functions/commonFunctionMixin.ts index 4c0b6cdfccd5..919ca289da8a 100644 --- a/pxtblocks/plugins/functions/commonFunctionMixin.ts +++ b/pxtblocks/plugins/functions/commonFunctionMixin.ts @@ -39,18 +39,21 @@ export interface FunctionDefinitionExtraState { name: string; functionid: string; arguments: FunctionArgument[]; + imported?: boolean; } export const COMMON_FUNCTION_MIXIN = { name_: "", functionId_: "", arguments_: [] as FunctionArgument[], + imported_: false, mutationToDom: function (this: CommonFunctionBlock): Element | null { this.ensureIds_(); const container = Blockly.utils.xml.createElement("mutation"); container.setAttribute("name", this.name_); container.setAttribute("functionid", this.functionId_); + if (this.imported_) container.setAttribute("imported", "true"); this.arguments_.forEach(function (arg) { const argNode = Blockly.utils.xml.createElement("arg"); argNode.setAttribute("name", arg.name); @@ -80,6 +83,8 @@ export const COMMON_FUNCTION_MIXIN = { this.arguments_ = args; this.name_ = xmlElement.getAttribute("name")!; + this.imported_ = xmlElement.getAttribute("imported") === "true"; + this.restoreSavedFunctionId(xmlElement.getAttribute("functionid")!); }, @@ -88,12 +93,14 @@ export const COMMON_FUNCTION_MIXIN = { name: this.name_, functionid: this.functionId_, arguments: this.arguments_.slice(), + imported: this.imported_, }; }, loadExtraState: function (this: CommonFunctionBlock, state: FunctionDefinitionExtraState) { this.arguments_ = state.arguments.slice(); this.name_ = state.name; + this.imported_ = !!(state as any).imported; this.restoreSavedFunctionId(state.functionid); }, diff --git a/pxtblocks/plugins/functions/utils.ts b/pxtblocks/plugins/functions/utils.ts index 8ecd239de57f..3332d2c38ebe 100644 --- a/pxtblocks/plugins/functions/utils.ts +++ b/pxtblocks/plugins/functions/utils.ts @@ -438,7 +438,10 @@ export function flyoutCategory(workspace: Blockly.WorkspaceSvg) { // label.setAttribute("text", Blockly.Msg[MsgKey.FUNCTION_FLYOUT_LABEL]); // xmlList.push(label); - const addFunctionCallBlock = (name: string, args: { name: string; type: string; id: string }[]) => { + const addFunctionCallBlock = ( + name: string, + args: { name: string; type: string; id: string }[] + ): Element => { const block = Blockly.utils.xml.createElement("block"); block.setAttribute("type", "function_call"); block.setAttribute("gap", "16"); @@ -461,6 +464,7 @@ export function flyoutCategory(workspace: Blockly.WorkspaceSvg) { mutation.appendChild(argElement); } xmlList.push(block); + return block; }; // Populate function call blocks from the current workspace @@ -473,6 +477,7 @@ export function flyoutCategory(workspace: Blockly.WorkspaceSvg) { if (program) { program.refreshSymbols?.(); + const globeIcon = "\uf0ac"; const currentFile = program.getAllWorkspaces().find(w => w.workspace === workspace)?.fileName || pxt.MAIN_BLOCKS; const localNames = new Set(getAllFunctionDefinitionBlocks(workspace).map(f => f.getName().toLowerCase())); @@ -492,10 +497,14 @@ export function flyoutCategory(workspace: Blockly.WorkspaceSvg) { } if (importedFunctions.length) { - xmlList.push(createFlyoutGroupLabel(`${file} functions`)); + const label = createFlyoutGroupLabel(`${file} functions`, globeIcon); + label.setAttribute("web-icon-color", "#fff"); + xmlList.push(label); for (const func of importedFunctions) { - addFunctionCallBlock(func.name, func.arguments); + const block = addFunctionCallBlock(func.name, func.arguments); + const mutation = block.querySelector("mutation"); + if (mutation) mutation.setAttribute("imported", "true"); } } } diff --git a/tests/blocklycompiler-test/test.spec.ts b/tests/blocklycompiler-test/test.spec.ts index 34c6a67d5529..57a8a59c7eb0 100644 --- a/tests/blocklycompiler-test/test.spec.ts +++ b/tests/blocklycompiler-test/test.spec.ts @@ -271,14 +271,15 @@ async function blockTestAsync(name: string) { chai.expect(res).to.not.be.undefined; - const compiledTs = res.source.trim().replace(/\s+/g, " "); + const compiledSource = res.outfiles[pxt.MAIN_TS]; + const compiledTs = compiledSource.trim().replace(/\s+/g, " "); const baselineTs = tsFile.trim().replace(/\s+/g, " "); if (compiledTs !== baselineTs) { console.log(compiledTs); } - chai.expect(compiledTs).to.equal(baselineTs, "Compiled result did not match baseline: " + name + " " + res.source); + chai.expect(compiledTs).to.equal(baselineTs, "Compiled result did not match baseline: " + name + " " + compiledSource); } describe("blockly compiler", function () { diff --git a/theme/blockly-core.less b/theme/blockly-core.less index 57d97c3061bc..a33dc8c3b290 100644 --- a/theme/blockly-core.less +++ b/theme/blockly-core.less @@ -102,6 +102,12 @@ body.blocklyMinimalBody { font-family: "Icons"; font-size: 19px; } + // Fallback to ensure icon font applies even when labels are not wrapped in editable/noneditable text groups + text.semanticIcon { + font-family: "Icons"; + font-style: normal; + font-weight: normal; + } .blocklyEditableText>text.semanticIcon.inverted, .blocklyEditableField>text.semanticIcon.inverted { fill: #000; } diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 6b8bd56ba6f1..4d6c106b1784 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -1470,12 +1470,19 @@ export class ProjectView } updateFileAsync(name: string, content: string, open?: boolean): Promise { - const p = pkg.mainEditorPkg(); - return p.setContentAsync(name, content) + const mainPkg = pkg.mainEditorPkg(); + return mainPkg.setContentAsync(name, content) + .then(() => this.reloadHeaderAsync()) .then(() => { - if (open) this.setFile(p.lookupFile("this/" + name)); + if (open) { + const reloadedPkg = pkg.mainEditorPkg(); + const file = reloadedPkg.lookupFile("this/" + name); + if (file) { + if (pxteditor.isBlocks(file)) this.setSideFile(file); + else this.setFile(file); + } + } }) - .then(() => this.reloadHeaderAsync()) } isSideDocExpanded(): boolean {