diff --git a/.vscode/launch.json b/.vscode/launch.json
index f2e435922ad5..38cc33fa1346 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -16,6 +16,21 @@
"localRoot": "${workspaceRoot}/../pxt-bedrock/codegen",
"console": "integratedTerminal"
},
+ {
+ "name": "codegen2 (pxt-bedrock)",
+ "type": "node",
+ "request": "launch",
+ "runtimeArgs": [
+ "run-script",
+ "vscode:debug"
+ ],
+ "runtimeExecutable": "npm",
+ "stopOnEntry": false,
+ "sourceMaps": true,
+ "outFiles": [],
+ "localRoot": "${workspaceRoot}/../pxt-bedrock/codegen2",
+ "console": "integratedTerminal"
+ },
{
"name": "pxt ci (pxt-core)",
"type": "node",
diff --git a/docs/static/icons/ts-logo-512.svg b/docs/static/icons/ts-logo-512.svg
new file mode 100644
index 000000000000..1437810ea3b0
--- /dev/null
+++ b/docs/static/icons/ts-logo-512.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pxtcompiler/emitter/backjs.ts b/pxtcompiler/emitter/backjs.ts
index ceb026bdefee..90a6d4d67234 100644
--- a/pxtcompiler/emitter/backjs.ts
+++ b/pxtcompiler/emitter/backjs.ts
@@ -426,6 +426,7 @@ function ${id}(s) {
else if (e.data === null) return "null"
else if (e.data === undefined) return "undefined"
else if (typeof e.data == "number") return e.data + ""
+ else if (typeof e.data == "string") return e.data
else throw oops("invalid data: " + typeof e.data);
case EK.PointerLiteral:
if (e.ptrlabel()) {
diff --git a/pxtcompiler/emitter/emitter.ts b/pxtcompiler/emitter/emitter.ts
index f3e215c5b863..7bd8d9b66dd1 100644
--- a/pxtcompiler/emitter/emitter.ts
+++ b/pxtcompiler/emitter/emitter.ts
@@ -3507,8 +3507,8 @@ ${lbl}: .short 0xffff
return ev;
if (/^0x[A-Fa-f\d]{2,8}$/.test(ev))
return ev;
- U.userError("enumval only support number literals")
- return "0"
+ //U.userError("enumval only support number literals")
+ return ev;
}
function emitFolded(f: Folded) {
@@ -3535,9 +3535,7 @@ ${lbl}: .short 0xffff
if (ev == null) {
info.constantFolded = constantFold(en.initializer)
} else {
- const v = parseInt(ev)
- if (!isNaN(v))
- info.constantFolded = { val: v }
+ info.constantFolded = { val: ev }
}
} else if (decl.kind == SK.PropertyDeclaration && isStatic(decl) && isReadOnly(decl)) {
const pd = decl as PropertyDeclaration
diff --git a/pxtcompiler/emitter/service.ts b/pxtcompiler/emitter/service.ts
index 28f101c16c3e..5aebaa346a1e 100644
--- a/pxtcompiler/emitter/service.ts
+++ b/pxtcompiler/emitter/service.ts
@@ -1258,6 +1258,7 @@ namespace ts.pxtc.service {
host.opts.fileSystem = prevFS
for (let k of Object.keys(newFS))
host.setFile(k, newFS[k]) // update version numbers
+ res.fileSystem = U.flatClone(newFS)
if (res.diagnostics.length == 0) {
host.opts.skipPxtModulesEmit = false
host.opts.skipPxtModulesTSC = false
@@ -1275,6 +1276,7 @@ namespace ts.pxtc.service {
let ts2asm = compile(host.opts, service)
res = {
sourceMap: res.sourceMap,
+ fileSystem: res.fileSystem,
...ts2asm,
}
if (res.needsFullRecompile || ((!res.success || res.diagnostics.length) && host.opts.clearIncrBuildAndRetryOnError)) {
diff --git a/pxtlib/service.ts b/pxtlib/service.ts
index 198b039c5b3c..7ee28eed1ee2 100644
--- a/pxtlib/service.ts
+++ b/pxtlib/service.ts
@@ -260,6 +260,7 @@ namespace ts.pxtc {
sourceMap?: SourceInterval[];
globalNames?: pxt.Map;
builtVariants?: string[];
+ fileSystem?: pxt.Map;
}
export interface Breakpoint extends LocationInfo {
diff --git a/theme/themes/pxt/globals/site.variables b/theme/themes/pxt/globals/site.variables
index c291bef67ff2..b84f9e84dd9a 100644
--- a/theme/themes/pxt/globals/site.variables
+++ b/theme/themes/pxt/globals/site.variables
@@ -139,6 +139,8 @@
@pyIcon: data-uri(@pyIconUrl);
@jsIconUrl: "../docs/static/icons/js.svg";
@jsIcon: data-uri(@jsIconUrl);
+@tsIconUrl: "../docs/static/icons/ts-logo-512.svg";
+@tsIcon: data-uri(@tsIconUrl);
@immersiveReaderUrl: "../docs/static/icons/immersive-reader.svg";
@immersiveReaderIcon: data-uri(@immersiveReaderUrl);
@immersiveReaderLightUrl: "../docs/static/icons/immersive-reader-light.svg";
diff --git a/webapp/src/container.tsx b/webapp/src/container.tsx
index b9805b72c87a..4c40ae0f0164 100644
--- a/webapp/src/container.tsx
+++ b/webapp/src/container.tsx
@@ -120,6 +120,7 @@ export interface SettingsMenuState {
greenScreen?: boolean;
accessibleBlocks?: boolean;
showShare?: boolean;
+ extDownloadMenuItems?: sui.MenuItem[];
}
export class SettingsMenu extends data.Component {
@@ -263,6 +264,12 @@ export class SettingsMenu extends data.Component {
+ // Ensure dropdown always has the editor's most up-to-date menu items
+ const extDownloadMenuItems = pxt.commands.getDownloadMenuItems?.() || [];
+ this.setState({ extDownloadMenuItems });
+ }
+
UNSAFE_componentWillReceiveProps(nextProps: SettingsMenuProps) {
const newState: SettingsMenuState = {};
if (nextProps.greenScreen !== undefined) {
@@ -282,6 +289,7 @@ export class SettingsMenu extends data.Component this.dropdown = ref}>
+ return this.dropdown = ref} onShow={this.onDropdownShow}>
{showHome && }
{showShare && }
{(showHome || showShare) && }
@@ -333,7 +340,7 @@ export class SettingsMenu extends data.Component : undefined}
{showDownloadMenuItems && <>
- {extMenuItems.map((props, index) => )}
+ {this.state.extDownloadMenuItems.map((props, index) => )}
>}
{targetTheme.selectLanguage ? : undefined}
diff --git a/webapp/src/sui.tsx b/webapp/src/sui.tsx
index 499e54442e32..6c4ffc1c1082 100644
--- a/webapp/src/sui.tsx
+++ b/webapp/src/sui.tsx
@@ -68,6 +68,8 @@ export interface DropdownProps extends UiProps {
id?: string;
onChange?: (v: string) => void;
onClick?: () => boolean; // Return 'true' to toggle open/close
+ onShow?: () => void;
+ onHide?: () => void;
titleContent?: React.ReactNode;
displayAbove?: boolean;
@@ -84,10 +86,12 @@ export interface DropdownState {
export class DropdownMenu extends UIElement {
show() {
+ this.props.onShow?.();
this.setState({ open: true, focus: true });
}
hide() {
+ this.props.onHide?.();
this.setState({ open: false });
}
@@ -167,32 +171,52 @@ export class DropdownMenu extends UIElement {
}
}
- componentDidMount() {
+ childFocus = (child: HTMLElement) => {
+ this.setActive(child);
+ }
+ childBlur = (child: HTMLElement) => {
+ this.blur(child);
+ }
+ childClick = (child: HTMLElement) => {
+ this.hide();
+ }
+ childKeyDown = (child: HTMLElement, e: KeyboardEvent, prev: HTMLElement, next: HTMLElement) => {
+ this.navigateToNextElement(e, prev, next);
+ }
+ lastChildKeyDown = (child: HTMLElement, e: KeyboardEvent) => {
+ const charCode = core.keyCodeFromEvent(e);
+ if (!e.shiftKey && charCode === core.TAB_KEY) {
+ this.hide();
+ }
+ }
+
+ UNSAFE_componentWillUpdate(nextProps: Readonly, nextState: Readonly, nextContext: any): void {
const children = this.getChildren();
for (let i = 0; i < children.length; i++) {
const prev = i > 0 ? children[i - 1] as HTMLElement : undefined;
const child = children[i] as HTMLElement;
const next = i < children.length ? children[i + 1] as HTMLElement : undefined;
+ child.removeEventListener('keydown', (e) => this.childKeyDown(child, e, prev, next));
+ child.removeEventListener('focus', () => this.childFocus(child));
+ child.removeEventListener('blur', () => this.childBlur(child));
+ child.removeEventListener('click', () => this.childClick(child));
+ child.removeEventListener('keydown', (e) => this.lastChildKeyDown(child, e));
+ }
+ }
- child.addEventListener('keydown', (e) => {
- this.navigateToNextElement(e, prev, next);
- })
-
- child.addEventListener('focus', (e: FocusEvent) => {
- this.setActive(child);
- })
- child.addEventListener('blur', (e: FocusEvent) => {
- this.blur(child);
- })
-
+ componentDidMount() {
+ const children = this.getChildren();
+ for (let i = 0; i < children.length; i++) {
+ const prev = i > 0 ? children[i - 1] as HTMLElement : undefined;
+ const child = children[i] as HTMLElement;
+ const next = i < children.length ? children[i + 1] as HTMLElement : undefined;
+ child.addEventListener('keydown', (e) => this.childKeyDown(child, e, prev, next));
+ child.addEventListener('focus', (e: FocusEvent) => this.childFocus(child));
+ child.addEventListener('blur', (e: FocusEvent) => this.childBlur(child));
+ child.addEventListener('click', (e) => this.childClick(child));
if (i == children.length - 1) {
// set tab on last child to clear focus
- child.addEventListener('keydown', (e) => {
- const charCode = core.keyCodeFromEvent(e);
- if (!e.shiftKey && charCode === core.TAB_KEY) {
- this.hide();
- }
- })
+ child.addEventListener('keydown', (e) => this.lastChildKeyDown(child, e));
}
}
}
@@ -204,6 +228,17 @@ export class DropdownMenu extends UIElement {
const child = children[i] as HTMLElement;
// On allow tabbing to valid child nodes (ie: no separators or mobile only items)
child.tabIndex = this.state.open ? 0 : -1;
+ // Set event handlers
+ const prev = i > 0 ? children[i - 1] as HTMLElement : undefined;
+ const next = i < children.length ? children[i + 1] as HTMLElement : undefined;
+ child.addEventListener('keydown', (e) => this.childKeyDown(child, e, prev, next));
+ child.addEventListener('focus', (e: FocusEvent) => this.childFocus(child));
+ child.addEventListener('blur', (e: FocusEvent) => this.childBlur(child));
+ child.addEventListener('click', (e) => this.childClick(child));
+ if (i == children.length - 1) {
+ // set tab on last child to clear focus
+ child.addEventListener('keydown', (e) => this.lastChildKeyDown(child, e));
+ }
}
// Check if dropdown width exceeds the bounds, add the left class to the menu