Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into dev/riknoll/tutoria…
Browse files Browse the repository at this point in the history
…l-bundle
  • Loading branch information
riknoll committed Oct 16, 2024
2 parents 3c09912 + cec05ce commit 2ab6330
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 61 deletions.
18 changes: 18 additions & 0 deletions common-docs/extensions/simulator-extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Simulator Extensions

A simulator extension is a static web application that complements a traditional MakeCode extension. When you add an extension containing a simulator extension to your MakeCode project, the simulator extension is loaded into a separate iframe within the MakeCode editor. This enables extensions to display custom UI when the project is running.

> [!IMPORTANT]
> Simulator extensions are only supported for [Approved Extensions](./approval.md), and they must go through an additional approval process. The MakeCode team reserves the right to approve or decline simulator extensions at their discretion. We strongly recommend consulting with the MakeCode team before investing time and resources into creating a simulator extension.
## Example simulator extensions

* [pxt-simx-sample](https://github.com/microsoft/pxt-simx-sample)
* [pxt-arcadeshield](https://github.com/microsoft/pxt-arcadeshield)
* [microbit-robot](https://github.com/microsoft/microbit-robot)
* [pxt-jacdac](https://github.com/microsoft/)

## Creating a simulator extension

Refer to the [pxt-simx-sample](https://github.com/microsoft/pxt-simx-sample) project for detailed instructions.

10 changes: 10 additions & 0 deletions docs/extensions/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ This guide describes a simple setup that requires nothing, but a web browser.
We have another guide, if you want to
[use command line tools](/extensions/getting-started/vscode).

### ~ hint

#### Making an extension

Watch this overview video on making your own MakeCode extension.

https://youtu.be/XSskpMEHYlo

### ~

## Step 1: Design blocks in the editor

It is easiest to tinker and design your blocks from the editor itself.
Expand Down
10 changes: 9 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,14 @@ function runUglify() {
return Promise.resolve();
}

async function inlineBlocklySourcemaps() {
if (process.env.PXT_ENV === 'production') {
return;
}

return exec("node ./scripts/inlineBlocklySourceMaps.js");
}



/********************************************************
Expand Down Expand Up @@ -733,7 +741,7 @@ function getMochaExecutable() {
const buildAll = gulp.series(
updatestrings,
maybeUpdateWebappStrings(),
gulp.parallel(copyTypescriptServices, copyBlocklyMedia),
gulp.parallel(copyTypescriptServices, copyBlocklyMedia, inlineBlocklySourcemaps),
gulp.parallel(pxtlib, pxtweb),
gulp.parallel(pxtcompiler, pxtsim, backendutils),
pxtpy,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pxt-core",
"version": "11.2.3",
"version": "11.2.10",
"description": "Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors",
"keywords": [
"TypeScript",
Expand Down
8 changes: 7 additions & 1 deletion pxtblocks/codecardRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,13 @@ export function renderCodeCard(card: pxt.CodeCard, options: CodeCardRenderOption
}
if (card.description) {
const descr = div(ct, 'ui description');
const shortenedDescription = card.description.split('.')[0] + '.';
const regex = /((?:\.{1,3})|[\!\?…])/;
const match = regex.exec(card.description);
let shortenedDescription = card.description + ".";
if (match) {
const punctuation = match[1];
shortenedDescription = card.description.split(punctuation)[0] + punctuation;
}

descr.appendChild(document.createTextNode(shortenedDescription));
}
Expand Down
12 changes: 6 additions & 6 deletions pxtblocks/contextMenu/contextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export function initContextMenu() {
}

export function setupWorkspaceContextMenu(workspace: Blockly.WorkspaceSvg) {
Blockly.ContextMenuItems.registerCommentOptions();
try {
Blockly.ContextMenuItems.registerCommentOptions();
}
catch (e) {
// will throw if already registered. ignore
}
workspace.configureContextMenu = (options, e) => {
if (workspace.options.comments && !workspace.options.readOnly) {
// options.unshift(Blockly.ContextMenu.workspaceCommentOption(workspace, e))

}

onWorkspaceContextMenu(workspace, options);
};
}
1 change: 1 addition & 0 deletions pxtblocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from "./diff";
export * from "./legacyMutations";
export * from "./blockDragger";
export * from "./workspaceSearch";
export * from "./monkeyPatches";

import * as contextMenu from "./contextMenu";
import * as external from "./external";
Expand Down
3 changes: 0 additions & 3 deletions pxtblocks/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { initVariables } from "./builtins/variables";
import { initOnStart } from "./builtins/misc";
import { initContextMenu } from "./contextMenu";
import { renderCodeCard } from "./codecardRenderer";
import { applyMonkeyPatches } from "./monkeyPatches";
import { FieldDropdown } from "./fields/field_dropdown";
import { setDraggableShadowBlocks, setDuplicateOnDragStrategy } from "./plugins/duplicateOnDrag";

Expand Down Expand Up @@ -588,8 +587,6 @@ function init(blockInfo: pxtc.BlocksInfo) {
if (blocklyInitialized) return;
blocklyInitialized = true;

applyMonkeyPatches();

initFieldEditors();
initContextMenu();
initOnStart();
Expand Down
73 changes: 73 additions & 0 deletions pxtblocks/monkeyPatches/grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as Blockly from "blockly";

interface ExtendedGridOptions extends Blockly.Options.GridOptions {
image?: {
path: string;
width: string;
height: string;
opacity: number;
}
}

export function monkeyPatchGrid() {
const options = pxt.appTarget.appTheme.blocklyOptions?.grid as ExtendedGridOptions;

if (!options?.image?.path) return;

const gridPatternIds: string[] = [];

Blockly.Grid.createDom = function (rnd: string, gridOptions: Blockly.Options.GridOptions, defs: SVGElement) {
const id = "blocklyGridPattern" + rnd;

const gridPattern = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.PATTERN,
{
id,
patternUnits: "userSpaceOnUse",
width: options.image.width,
height: options.image.height
},
defs,
);

gridPatternIds.push(id)

const image = Blockly.utils.dom.createSvgElement(
Blockly.utils.Svg.IMAGE,
{
width: options.image.width,
height: options.image.height,
opacity: options.image.opacity
},
gridPattern
);

image.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", options.image.path)

return gridPattern;
}

const oldGridUpdate = Blockly.Grid.prototype.update;

Blockly.Grid.prototype.update = function (this: Blockly.Grid, scale: number) {
oldGridUpdate.call(this, scale);

const patternsToRemove: string[] = [];
for (const patternId of gridPatternIds) {
const imagePattern = document.getElementById(patternId) as unknown as SVGPatternElement;

if (!imagePattern) {
patternsToRemove.push(patternId);
continue;
}

imagePattern.setAttribute("width", options.image.width);
imagePattern.setAttribute("height", options.image.height);
imagePattern.setAttribute('patternTransform', 'scale(' + scale + ')');
}

for (const patternId of patternsToRemove) {
gridPatternIds.splice(gridPatternIds.indexOf(patternId), 1);
}
}
}
2 changes: 2 additions & 0 deletions pxtblocks/monkeyPatches/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { monkeyPatchBlockSvg } from "./blockSvg";
import { monkeyPatchGrid } from "./grid";

export function applyMonkeyPatches() {
monkeyPatchBlockSvg();
monkeyPatchGrid();
}
48 changes: 34 additions & 14 deletions pxtblocks/plugins/logic/ifElse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,46 +64,39 @@ const IF_ELSE_MIXIN = {
*/
restoreConnections_: function (this: IfElseBlock) {
for (let i = 1; i <= this.elseifCount_; i++) {
this.getInput('IF' + i).connection.setShadowState({ 'type': 'logic_boolean', 'fields': { 'BOOL': 'FALSE' } });
this.valueConnections_[i]?.reconnect(this, 'IF' + i);
this.reconnectValueConnection_(i, this.valueConnections_);
this.statementConnections_[i]?.reconnect(this, 'DO' + i);
}
if (this.getInput('ELSE')) this.elseStatementConnection_?.reconnect(this, 'ELSE');
},
addElse_: function (this: IfElseBlock) {
this.storeConnections_();
const update = () => {
this.elseCount_++;
};
this.update_(update);
this.restoreConnections_();

},
removeElse_: function (this: IfElseBlock) {
this.storeConnections_();
const update = () => {
this.elseCount_--;
};
this.update_(update);
this.restoreConnections_();
},
addElseIf_: function (this: IfElseBlock) {
this.storeConnections_();
const update = () => {
this.elseifCount_++;
};
this.update_(update);
this.restoreConnections_();
},
removeElseIf_: function (this: IfElseBlock, arg: number) {
this.storeConnections_(arg);
const update = () => {
this.elseifCount_--;
};
this.update_(update);
this.restoreConnections_();
this.update_(update, arg);
},
update_: function (this: IfElseBlock, update: () => void) {
update_: function (this: IfElseBlock, update: () => void, arg?: number) {
Blockly.Events.setGroup(true);
this.storeConnections_(arg);
const block = this;
const oldMutationDom = block.mutationToDom();
const oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
Expand Down Expand Up @@ -131,6 +124,7 @@ const IF_ELSE_MIXIN = {
if (block.rendered && block instanceof Blockly.BlockSvg) {
block.render();
}
this.restoreConnections_();
Blockly.Events.setGroup(false);
},
/**
Expand Down Expand Up @@ -162,7 +156,8 @@ const IF_ELSE_MIXIN = {
}(i);
this.appendValueInput('IF' + i)
.setCheck('Boolean')
.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF)
.setShadowDom(createShadowDom());
this.appendDummyInput('IFTITLE' + i)
.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
this.appendDummyInput('IFBUTTONS' + i)
Expand Down Expand Up @@ -231,13 +226,38 @@ const IF_ELSE_MIXIN = {
reconnectChildBlocks_: function (this: IfElseBlock, valueConnections: Blockly.Connection[], statementConnections: Blockly.Connection[],
elseStatementConnection: Blockly.Connection) {
for (let i = 1; i <= this.elseifCount_; i++) {
valueConnections[i]?.reconnect(this, 'IF' + i);
this.reconnectValueConnection_(i, valueConnections);
statementConnections[i]?.reconnect(this, 'DO' + i);
}
elseStatementConnection?.reconnect(this, 'ELSE');
},

reconnectValueConnection_: function (this: IfElseBlock, i: number, valueConnections: Blockly.Connection[]) {
const shadow = this.getInput('IF' + i)?.connection.targetBlock();

if (valueConnections[i]) {
valueConnections[i].reconnect(this, 'IF' + i);
// Sometimes reconnect leaves behind orphaned shadow blocks behind. If
// that happens, clean it up
if (shadow && !shadow.getParent()) {
shadow.dispose();
}
}
}
};

function createShadowDom() {
const shadow = document.createElement("shadow");
shadow.setAttribute("type", "logic_boolean");

const field = document.createElement("field");
field.setAttribute("name", "BOOL");
field.textContent = "FALSE";

shadow.appendChild(field);
return shadow;
}

Blockly.Blocks["controls_if"] = {
...IF_ELSE_MIXIN,
init(this: IfElseBlock) {
Expand Down
20 changes: 14 additions & 6 deletions pxtsim/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ namespace pxsim {
return Promise.all(values.map(v => mapper(v)));
}

export function promiseMapAllSeries<T, V>(values: T[], mapper: (obj: T) => Promise<V>): Promise<V[]> {
export function promiseMapAllSeries<T, V>(values: T[], mapper: (obj: T) => Promise<V>): Promise<V[]> {
return promisePoolAsync(1, values, mapper);
}

Expand Down Expand Up @@ -193,7 +193,7 @@ namespace pxsim {
}, ms);
});

return Promise.race([ promise, timeoutPromise ])
return Promise.race([promise, timeoutPromise])
.then(output => {
// clear any dangling timeout
if (res) {
Expand Down Expand Up @@ -311,6 +311,14 @@ namespace pxsim {
})
return v;
}

export function sanitizeCssName(name: string): string {
let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, '_');
if (!/^[a-zA-Z_]/.test(sanitized)) {
sanitized = 'cls_' + sanitized;
}
return sanitized;
}
}

export interface Map<T> {
Expand Down Expand Up @@ -575,7 +583,7 @@ namespace pxsim {

class EventHandler {
private busy = 0;
constructor(public handler: RefAction, public flags: number) {}
constructor(public handler: RefAction, public flags: number) { }

async runAsync(eventValue: EventIDType, runtime: Runtime, valueToArgs?: EventValueToActionArgs) {
// The default behavior can technically be configured in codal, but we always set it to queue if busy
Expand All @@ -597,9 +605,9 @@ namespace pxsim {
}

private async runFiberAsync(eventValue: EventIDType, runtime: Runtime, valueToArgs?: EventValueToActionArgs) {
this.busy ++;
this.busy++;
await runtime.runFiberAsync(this.handler, ...(valueToArgs ? valueToArgs(eventValue) : [eventValue]));
this.busy --;
this.busy--;
}
}

Expand Down Expand Up @@ -680,7 +688,7 @@ namespace pxsim {
this._handlers = [new EventHandler(a, flags)];
}
else {
this._addRemoveLog.push({ act: a, log: LogType.UserSet, flags});
this._addRemoveLog.push({ act: a, log: LogType.UserSet, flags });
}
}

Expand Down
Loading

0 comments on commit 2ab6330

Please sign in to comment.