From 54b4a1a86610d2f7ec96d8ad96d51b3f4336a1b5 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 13 Sep 2024 16:03:16 +0100 Subject: [PATCH 1/4] Comment re odd behaviour --- src/vanilla/makecode-frame-driver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vanilla/makecode-frame-driver.ts b/src/vanilla/makecode-frame-driver.ts index ba317eb..72ae65e 100644 --- a/src/vanilla/makecode-frame-driver.ts +++ b/src/vanilla/makecode-frame-driver.ts @@ -297,6 +297,7 @@ export class MakeCodeFrameDriver { // Native app oriented event that doesn't have a 'type' field. switch (data.cmd) { case 'backtap': + // MakeCode seems to send this twice for a single click but there's little we can do here. return this.options.onBack?.(); case 'backpress': return this.options.onBackLongPress?.(); From 666bf1faceb653475020d98aebd00ca2291e6455 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:17:12 +0000 Subject: [PATCH 2/4] Add documentation and stories; update React API (#1) * Breaking: Flatten options into props for render blocks React * Use TypeDoc to generate docs, tweak README.md * Add usage documentation for VanillaJS and React * Export createMakeCodeRenderBlocks (potentially breaking change) --- .github/workflows/build-docs.yml | 39 +++ .github/workflows/build.yml | 16 - .gitignore | 1 + LICENSE.md | 2 +- README.md | 16 +- docs/custom.css | 4 + docs/react.md | 48 +++ docs/vanilla.md | 70 ++++ package-lock.json | 152 +++++++++ package.json | 4 +- src/react/MakeCodeRenderBlocksProvider.tsx | 23 +- src/react/useMakeCodeRenderBlocks.tsx | 3 +- src/stories/MakeCodeFrame.stories.tsx | 300 ------------------ src/stories/MakeCodeToolbar.tsx | 213 +++++++++++++ .../MakeCodeBlocksRendering.stories.tsx | 132 ++++---- src/stories/react/MakeCodeFrame.stories.tsx | 107 +++++++ .../vanilla/makecode-frame-driver.stories.tsx | 107 +++++++ .../makecode-render-blocks.stories.tsx | 94 ++++++ src/vanilla/index.ts | 35 ++ src/vanilla/makecode-frame-driver.ts | 2 +- src/vanilla/makecode-render-blocks.ts | 4 +- src/vanilla/pxt.ts | 252 ++++++--------- typedoc.json | 13 + 23 files changed, 1079 insertions(+), 558 deletions(-) create mode 100644 .github/workflows/build-docs.yml create mode 100644 docs/custom.css create mode 100644 docs/react.md create mode 100644 docs/vanilla.md delete mode 100644 src/stories/MakeCodeFrame.stories.tsx create mode 100644 src/stories/MakeCodeToolbar.tsx rename src/stories/{ => react}/MakeCodeBlocksRendering.stories.tsx (58%) create mode 100644 src/stories/react/MakeCodeFrame.stories.tsx create mode 100644 src/stories/vanilla/makecode-frame-driver.stories.tsx create mode 100644 src/stories/vanilla/makecode-render-blocks.stories.tsx create mode 100644 typedoc.json diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 0000000..ed0ea74 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,39 @@ +name: build-docs +on: + release: + types: [created] + push: + branches: + - '**' + tags: + - '**' +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - run: npm ci + - name: Build docs + run: npm run docs + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./docs/build + + deploy: + if: ${{ startsWith(github.ref, 'refs/tags/') }} + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95e1e54..d3b03ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,19 +31,3 @@ jobs: if: github.event_name == 'release' && github.event.action == 'created' env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - deploy: - # https://github.com/actions/deploy-pages - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - needs: build - # Deploy to the github-pages environment - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - if: github.ref == 'refs/heads/main' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index e571fed..4a30bf8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +docs/build # Editor directories and files .vscode/* diff --git a/LICENSE.md b/LICENSE.md index 52209ef..0965380 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) YEAR AUTHOR +Copyright (c) 2024-2025 Micro:bit Educational Foundation and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 84e5b6d..f402d5c 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ -# React/JavaScript library for embedding Microsoft MakeCode as an iframe +# MakeCode Embed -This project is a work in progress. +This documentation is best viewed on the documentation site rather than GitHub or NPM package site. + +This is a React/JavaScript library for embedding Microsoft MakeCode as an iframe. It is intended to be used by other Micro:bit Educational Foundation projects that need to embed MakeCode and, when the API stabilises, to be useful to others who embed MakeCode. -## Documentation +- [StoryBook demo of the components](https://makecode-embed.pages.dev/) + +## Usage -There isn't any documentation yet. You can find a StoryBook demo of the -components at https://microbit-foundation.github.io/makecode-embed/ +- [React usage documentation](docs/react.md) +- [VanillaJS usage documentation](docs/vanilla.md) ## License This software is under the MIT open source license. -[SPDX-License-Identifier: MIT](LICENSE) +[SPDX-License-Identifier: MIT](LICENSE.md) We use dependencies via the NPM registry as specified by the package.json file under common Open Source licenses. diff --git a/docs/custom.css b/docs/custom.css new file mode 100644 index 0000000..d327beb --- /dev/null +++ b/docs/custom.css @@ -0,0 +1,4 @@ +/* Styling for hiding elements in the generated TypeDoc site. */ +a.typedoc-ignore { + display: none; +} \ No newline at end of file diff --git a/docs/react.md b/docs/react.md new file mode 100644 index 0000000..1705b11 --- /dev/null +++ b/docs/react.md @@ -0,0 +1,48 @@ +--- +title: React Usage +--- + +# React Usage + +This documentation is best viewed on the documentation site rather than GitHub or NPM package site. + +## Blocks rendering + +Use {@link react.MakeCodeRenderBlocksProvider | MakeCodeRenderBlocksProvider} and {@link react.MakeCodeBlocksRendering | MakeCodeBlocksRendering} React components to render MakeCode blocks for a MakeCode project. Example MakeCode projects used for the demo are defined in [fixtures.ts](../src/stories/fixtures.ts). + +```js +import { + MakeCodeRenderBlocksProvider, + MakeCodeBlocksRendering, +} from '@microbit/makecode-embed/react'; + + + +; +``` + +The provider manages a hidden, embedded MakeCode. If you have more than one code embed then place the provider at a suitable location. You can use the `disabled` prop to avoid loading MakeCode if you know it's not needed. + +For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/react/MakeCodeBlocksRendering.stories.tsx). + +## Embed MakeCode editor + +Use {@link react.MakeCodeFrame | MakeCodeFrame} component to embed MakeCode. + +```js +import { MakeCodeFrame } from '@microbit/makecode-embed/react'; + + console.log("MakeCode is now ready")}, + onWorkspaceSave={(e) => { + // Set project as project changes in the editor. + setSavedProject(e.project); + }} +/>; +``` + +For more examples, take a look at the [MakeCode frame demo source code](../src/stories/react/MakeCodeFrame.stories.tsx). diff --git a/docs/vanilla.md b/docs/vanilla.md new file mode 100644 index 0000000..9374c89 --- /dev/null +++ b/docs/vanilla.md @@ -0,0 +1,70 @@ +--- +title: VanillaJS Usage +--- + +# VanillaJS Usage + +This documentation is best viewed on the documentation site rather than GitHub or NPM package site. + +## Blocks rendering + +Use {@link vanilla.createMakeCodeRenderBlocks | createMakeCodeRenderBlocks} to create a MakeCode block renderer. Initialise the renderer before calling `renderBlocks` with a {@link vanilla.RenderBlocksRequest | RenderBlocksRequest}, which includes a MakeCode project ([see examples](../src/stories/fixtures.ts)). The function will return a {@link vanilla.RenderBlocksResponse | RenderBlocksResponse}. + +```js +import { createMakeCodeRenderBlocks } from "@microbit/makecode-embed/vanilla"; + +const renderer = createMakeCodeRenderBlocks({}); +renderer.initialize(); +const result = await renderer.renderBlocks({ code: makeCodeProject }); + +document.querySelector("#app")!.innerHTML = ` +
+ ${result.svg} +
+`; +``` + +For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/vanilla/makecode-render-blocks.stories.tsx). + +## Embed MakeCode editor + +Use {@link vanilla.MakeCodeFrameDriver | MakeCodeFrameDriver} class to create a driverRef for an iframe element. The iframe element src URL can be generated using {@link vanilla.createMakeCodeURL | createMakeCodeURL}. + +```js +import { + Project, + MakeCodeFrameDriver, + createMakeCodeURL, +} from "@microbit/makecode-embed/vanilla"; + +// Set up an iframe element. +const iframe = document.createElement("iframe"); +iframe.allow = "usb; autoplay; camera; microphone;"; +iframe.src = createMakeCodeURL( + "https://makecode.microbit.org", + undefined, // Version. + undefined, // Language. + 1, // Controller. + undefined // Query params. +); +iframe.width = "100%"; +iframe.height = "100%"; + +document.querySelector("#app")!.appendChild(iframe); + +// Create and initialise an instance of MakeCodeFrameDriver. +const driverRef = new MakeCodeFrameDriver( + { + controllerId: "YOUR APP NAME HERE", + initialProjects: async () => [makeCodeProject], + onEditorContentLoaded: (e) => console.log("MakeCode is now ready"), + onWorkspaceSave: (e) => { + console.log(e.project!.header!.id, e.project); + }, + }, + () => iframe +); +driverRef.initialize(); +``` + +For more examples, take a look at the [MakeCode frame demo source code](../src/stories/vanilla/makecode-frame-driver.stories.tsx). diff --git a/package-lock.json b/package-lock.json index bb487de..27be0e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "prettier": "^3.2.5", "storybook": "^8.2.9", "tslib": "^2.6.2", + "typedoc": "^0.27.6", "typescript": "^5.2.2" }, "engines": { @@ -2527,6 +2528,17 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", + "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "dev": true, + "dependencies": { + "@shikijs/engine-oniguruma": "^1.27.2", + "@shikijs/types": "^1.27.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2979,6 +2991,32 @@ ], "peer": true }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", + "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "dev": true, + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@shikijs/types": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", + "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "dev": true, + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", + "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -5358,6 +5396,18 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", @@ -7146,6 +7196,15 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7276,6 +7335,12 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -7324,6 +7389,23 @@ "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", "dev": true }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/markdown-to-jsx": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.5.0.tgz", @@ -7336,6 +7418,12 @@ "react": ">= 0.14.0" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8344,6 +8432,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -9582,6 +9679,43 @@ "node": ">= 0.6" } }, + "node_modules/typedoc": { + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.6.tgz", + "integrity": "sha512-oBFRoh2Px6jFx366db0lLlihcalq/JzyCVp7Vaq1yphL/tbgx2e+bkpkCgJPunaPvPwoTOXSwasfklWHm7GfAw==", + "dev": true, + "dependencies": { + "@gerrit0/mini-shiki": "^1.24.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.6.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", @@ -9595,6 +9729,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", @@ -10003,6 +10143,18 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 8ae960d..48bad1a 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "ci": "npm run lint && npm run build && npm run build:storybook", "storybook": "storybook dev -p 6006", - "build:storybook": "storybook build" + "build:storybook": "storybook build", + "docs": "typedoc" }, "peerDependencies": { "react": ">=18.0.0", @@ -60,6 +61,7 @@ "prettier": "^3.2.5", "storybook": "^8.2.9", "tslib": "^2.6.2", + "typedoc": "^0.27.6", "typescript": "^5.2.2" } } diff --git a/src/react/MakeCodeRenderBlocksProvider.tsx b/src/react/MakeCodeRenderBlocksProvider.tsx index 805008f..30c4945 100644 --- a/src/react/MakeCodeRenderBlocksProvider.tsx +++ b/src/react/MakeCodeRenderBlocksProvider.tsx @@ -1,5 +1,4 @@ -import { ReactNode, createContext, useContext } from 'react'; -import { MakeCodeRenderBlocksOptions } from '../vanilla/makecode-render-blocks.js'; +import { ReactNode, createContext, useContext, useMemo } from 'react'; import useMakeCodeRenderBlocks, { UseMakeCodeRenderBlocksReturn, } from './useMakeCodeRenderBlocks.js'; @@ -12,12 +11,28 @@ const MakeCodeRenderBlocksContext = }); export const MakeCodeRenderBlocksProvider = ({ - options, + disabled, + version, + lang, children, }: { - options: MakeCodeRenderBlocksOptions; + /** + * This can be used to disable loading MakeCode in scenarios where it will be unused. + */ + disabled?: boolean; + /** + * MakeCode version. + */ + version?: string; + /** + * MakeCode language code. + */ + lang?: string; children: ReactNode; }) => { + const options = useMemo(() => { + return { disabled, version, lang }; + }, [disabled, lang, version]); const value = useMakeCodeRenderBlocks(options); return ( diff --git a/src/react/useMakeCodeRenderBlocks.tsx b/src/react/useMakeCodeRenderBlocks.tsx index e8fa678..a7ca02f 100644 --- a/src/react/useMakeCodeRenderBlocks.tsx +++ b/src/react/useMakeCodeRenderBlocks.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo } from 'react'; -import createMakeCodeRenderBlocks, { +import { + createMakeCodeRenderBlocks, MakeCodeRenderBlocksOptions, MakeCodeRenderBlocksReturn, RenderBlocksRequest, diff --git a/src/stories/MakeCodeFrame.stories.tsx b/src/stories/MakeCodeFrame.stories.tsx deleted file mode 100644 index 8ca6560..0000000 --- a/src/stories/MakeCodeFrame.stories.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { useCallback, useRef } from 'react'; -import { defaultMakeCodeProject } from '../vanilla/examples.js'; -import { MakeCodeFrameDriver } from '../vanilla/makecode-frame-driver.js'; -import { - default as MakeCodeFrame, - MakeCodeFrameProps, -} from '../react/MakeCodeFrame.js'; -import { Project } from '../vanilla/pxt.js'; -import { controllerId } from './config.js'; -import StoryWrapper from './StoryWrapper.js'; - -const meta: Meta = { - component: MakeCodeFrame, - argTypes: { - version: { - options: ['default', 'beta'], - defaultValue: undefined, - name: 'version', - control: { type: 'radio' }, - }, - }, -}; - -export default meta; -type Story = StoryObj; - -const EditorWithToolbarStyles = { - actions: { - fontFamily: 'sans-serif', - display: 'flex', - flexWrap: 'wrap', - gap: '5px', - margin: '10px 0', - } as const, -}; - -const MakeCodeEditorWithControls = ( - props: Omit -) => { - const savedProjects = useRef>(new Map()); - const ref = useRef(null); - const initialProjects = useCallback(async () => { - if (savedProjects.current.size === 0) { - // Maybe we can switch to using newProject instead? - return [defaultMakeCodeProject]; - } - return [...savedProjects.current.values()]; - }, []); - return ( - <> -
-
- - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - -
-
- - - - -
-
- console.log('editorContentLoaded', e)} - onWorkspaceLoaded={(e) => console.log('workspaceLoaded', e)} - onWorkspaceSync={(e) => console.log('workspaceSync', e)} - onWorkspaceReset={(e) => console.log('workspaceReset', e)} - onWorkspaceEvent={(e) => console.log('workspaceEvent', e)} - onWorkspaceSave={(e) => { - savedProjects.current?.set(e.project!.header!.id, e.project); - console.log(savedProjects.current); - }} - onTutorialEvent={(e) => console.log('tutorialEvent', e)} - {...props} - /> - - ); -}; - -export const MakeCodeEditorWithControlsStory: Story = { - name: 'MakeCode Editor with controls', - args: { - version: 'default', - }, - render: (args) => { - const { version } = args; - return ( - - - - ); - }, -}; - -export const MakeCodeEditorControllerAppModeStory: Story = { - name: 'MakeCode Editor with controller=2 mode', - args: { - version: 'default', - }, - render: (args) => { - const { version } = args; - return ( - - console.log('download', download)} - onSave={(save) => console.log('save', save)} - onBack={() => console.log('back')} - onBackLongPress={() => console.log('back long')} - /> - - ); - }, -}; diff --git a/src/stories/MakeCodeToolbar.tsx b/src/stories/MakeCodeToolbar.tsx new file mode 100644 index 0000000..421b3c9 --- /dev/null +++ b/src/stories/MakeCodeToolbar.tsx @@ -0,0 +1,213 @@ +import { MutableRefObject, RefObject } from 'react'; +import { MakeCodeFrameDriver } from '../vanilla/makecode-frame-driver.js'; +import { defaultMakeCodeProject } from '../vanilla/examples.js'; +import { Project } from '../vanilla/pxt.js'; + +const toolbarRowStyle = { + fontFamily: 'sans-serif', + display: 'flex', + flexWrap: 'wrap', + gap: '5px', + margin: '10px 0', +} as const; + +const MakeCodeToolbar = ({ + driver, + savedProjects, +}: { + driver: RefObject; + savedProjects: MutableRefObject>; +}) => { + return ( +
+
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + +
+
+ + + + +
+
+ ); +}; + +export default MakeCodeToolbar; diff --git a/src/stories/MakeCodeBlocksRendering.stories.tsx b/src/stories/react/MakeCodeBlocksRendering.stories.tsx similarity index 58% rename from src/stories/MakeCodeBlocksRendering.stories.tsx rename to src/stories/react/MakeCodeBlocksRendering.stories.tsx index 8e8e153..90c2a63 100644 --- a/src/stories/MakeCodeBlocksRendering.stories.tsx +++ b/src/stories/react/MakeCodeBlocksRendering.stories.tsx @@ -1,29 +1,28 @@ import { Meta, StoryObj } from '@storybook/react'; import { ReactNode, useState } from 'react'; -import MakeCodeBlocksRendering from '../react/MakeCodeBlocksRendering.js'; +import MakeCodeBlocksRendering from '../../react/MakeCodeBlocksRendering.js'; +import { MakeCodeRenderBlocksProvider } from '../../react/MakeCodeRenderBlocksProvider.js'; +import { BlockLayout, Project } from '../../vanilla/pxt.js'; import { initialProject, project, + projectWithCustomBlock, projectWithDatalogging, projectWithExtensionBlock, projectWithLayout, - projectWithCustomBlock, projectWithMelody, projectWithTwoExtensions, projectWithUserLayout, strawbeesExample, -} from './fixtures.js'; -import { MakeCodeRenderBlocksProvider } from '../react/MakeCodeRenderBlocksProvider.js'; -import { MakeCodeRenderBlocksOptions } from '../vanilla/makecode-render-blocks.js'; -import { BlockLayout, Project } from '../vanilla/pxt.js'; +} from '../fixtures.js'; const meta: Meta = { + title: 'stories/React/MakeCodeBlocksRendering', component: MakeCodeRenderBlocksProvider, argTypes: { - options: { + version: { options: ['default', 'beta'], defaultValue: 'default', - name: 'version', control: { type: 'radio' }, }, }, @@ -46,23 +45,17 @@ const StoryWrapper = (props: { children: ReactNode }) => ( ); -const getOptionsFromVersion = ( - version: string -): MakeCodeRenderBlocksOptions => { - const options: MakeCodeRenderBlocksOptions = {}; - if (version && version !== 'default') { - options['version'] = version; - } - return options; +const adaptStorybookVersion = ( + version: string | undefined +): string | undefined => { + return version && version !== 'default' ? version : undefined; }; export const Simple: Story = { - render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + render: ({ version }) => { return ( - - + + @@ -71,12 +64,10 @@ export const Simple: Story = { }; export const XML: Story = { - render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + render: ({ version }) => { return ( - - + + @@ -86,12 +77,10 @@ export const XML: Story = { export const Published: Story = { render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); - console.log(options); + const { version } = args; return ( - - + + @@ -101,11 +90,10 @@ export const Published: Story = { export const Melody: Story = { render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -116,11 +104,10 @@ export const Melody: Story = { export const ExtensionBlockSingle: Story = { name: 'Extension block (single)', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -131,11 +118,10 @@ export const ExtensionBlockSingle: Story = { export const ExtensionBlockTwo: Story = { name: 'Extension block (two different)', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -146,11 +132,10 @@ export const ExtensionBlockTwo: Story = { export const ExtensionBlockStrawbees: Story = { name: 'Extension block (Strawbees - spaces in name)', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -161,11 +146,10 @@ export const ExtensionBlockStrawbees: Story = { export const ExtensionBlockDatalogging: Story = { name: 'Extension block (Datalogging)', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -176,11 +160,10 @@ export const ExtensionBlockDatalogging: Story = { export const CustomBlock: Story = { name: 'Custom block', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -192,7 +175,7 @@ export const Error: Story = { render: () => { return ( - + @@ -203,11 +186,10 @@ export const Error: Story = { export const Robust: Story = { name: 'Robust against invalid/empty project', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -218,11 +200,10 @@ export const Robust: Story = { export const InitialBlankProject: Story = { name: 'Initial blank project', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -233,11 +214,10 @@ export const InitialBlankProject: Story = { export const EmptyString: Story = { name: 'Empty string', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + @@ -248,13 +228,14 @@ export const EmptyString: Story = { export const EmptyToBlocksTransition: Story = { name: 'Empty to blocks transition', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; const [project, setProject] = useState(initialProject); return ( - +
- +
@@ -279,11 +260,10 @@ export const EmptyToBlocksTransition: Story = { export const RespectUserLayout: Story = { name: 'Respect user layout', render: (args) => { - const { options: version } = args; - const options = getOptionsFromVersion(version as string); + const { version } = args; return ( - - + + = { + title: 'stories/React/MakeCodeFrame', + component: MakeCodeFrame, + argTypes: { + version: { + options: ['default', 'beta'], + defaultValue: undefined, + name: 'version', + control: { type: 'radio' }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +const MakeCodeEditorWithControls = ( + props: Omit +) => { + const savedProjects = useRef>(new Map()); + const ref = useRef(null); + const initialProjects = useCallback(async () => { + if (savedProjects.current.size === 0) { + // Maybe we can switch to using newProject instead? + return [defaultMakeCodeProject]; + } + return [...savedProjects.current.values()]; + }, []); + return ( + <> + + console.log('editorContentLoaded', e)} + onWorkspaceLoaded={(e) => console.log('workspaceLoaded', e)} + onWorkspaceSync={(e) => console.log('workspaceSync', e)} + onWorkspaceReset={(e) => console.log('workspaceReset', e)} + onWorkspaceEvent={(e) => console.log('workspaceEvent', e)} + onWorkspaceSave={(e) => { + savedProjects.current?.set(e.project!.header!.id, e.project); + console.log(savedProjects.current); + }} + onTutorialEvent={(e) => console.log('tutorialEvent', e)} + {...props} + /> + + ); +}; + +export const MakeCodeEditorWithControlsStory: Story = { + name: 'MakeCode Editor with controls', + args: { + version: 'default', + }, + render: (args) => { + const { version } = args; + return ( + + + + ); + }, +}; + +export const MakeCodeEditorControllerAppModeStory: Story = { + name: 'MakeCode Editor with controller=2 mode', + args: { + version: 'default', + }, + render: (args) => { + const { version } = args; + return ( + + console.log('download', download)} + onSave={(save) => console.log('save', save)} + onBack={() => console.log('back')} + onBackLongPress={() => console.log('back long')} + /> + + ); + }, +}; diff --git a/src/stories/vanilla/makecode-frame-driver.stories.tsx b/src/stories/vanilla/makecode-frame-driver.stories.tsx new file mode 100644 index 0000000..e1b4b69 --- /dev/null +++ b/src/stories/vanilla/makecode-frame-driver.stories.tsx @@ -0,0 +1,107 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { useRef } from 'react'; +import { defaultMakeCodeProject } from '../../vanilla/examples.js'; +import { + createMakeCodeURL, + MakeCodeFrameDriver, + Options, +} from '../../vanilla/makecode-frame-driver.js'; +import { Project } from '../../vanilla/pxt.js'; +import MakeCodeToolbar from '../MakeCodeToolbar.js'; +import StoryWrapper from '../StoryWrapper.js'; + +interface StoryArgs { + options?: { + version?: string; + lang?: string; + controller?: 1 | 2; + queryParams?: Record; + }; + project?: Project; + callbacks?: Partial; +} + +const meta: Meta = { + title: 'stories/VanillaJS/makeCodeFrameDriver', +}; + +export default meta; + +type Story = StoryObj; + +const renderEditor = (args: StoryArgs) => { + const savedProjects = useRef>(new Map()); + const ref = useRef(null); + const cbRef = (div: HTMLElement | null) => { + if (!div) { + return; + } + // Create an iframe element. + const iframe = document.createElement('iframe'); + iframe.allow = 'usb; autoplay; camera; microphone;'; + iframe.src = createMakeCodeURL( + 'https://makecode.microbit.org', + args.options?.version === 'default' ? undefined : args.options?.version, + args.options?.lang, + args.options?.controller ?? 1, + args.options?.queryParams + ); + iframe.width = '100%'; + iframe.height = '100%'; + div.appendChild(iframe); + + const savedProjects: Map = new Map(); + + // Create and initialise an instance of MakeCodeFrameDriver. + ref.current = new MakeCodeFrameDriver( + { + initialProjects: async () => (args.project ? [args.project] : []), + onEditorContentLoaded: (e) => console.log('editorContentLoaded', e), + onWorkspaceLoaded: (e) => console.log('workspaceLoaded', e), + onWorkspaceSync: (e) => console.log('workspaceSync', e), + onWorkspaceReset: (e) => console.log('workspaceReset', e), + onWorkspaceEvent: (e) => console.log('workspaceEvent', e), + onWorkspaceSave: (e) => { + const headerId = e.project!.header!.id; + savedProjects.set(headerId, e.project); + console.log(savedProjects); + }, + onTutorialEvent: (e) => console.log('tutorialEvent', e), + ...(args.callbacks ?? {}), + }, + () => iframe + ); + + ref.current.initialize(); + }; + + return ( + + +
+ + ); +}; + +export const MakeCodeEditorWithControlsStory: Story = { + name: 'MakeCode Editor with controls', + render: renderEditor, + args: { + options: { version: 'default', queryParams: { hideMenu: '' } }, + project: defaultMakeCodeProject, + }, +}; + +export const MakeCodeEditorControllerAppModeStory: Story = { + name: 'MakeCode Editor with controller=2 mode', + render: renderEditor, + args: { + options: { version: 'default', controller: 2 }, + callbacks: { + onDownload: (download) => console.log('download', download), + onSave: (save) => console.log('save', save), + onBack: () => console.log('back'), + onBackLongPress: () => console.log('back long'), + }, + }, +}; diff --git a/src/stories/vanilla/makecode-render-blocks.stories.tsx b/src/stories/vanilla/makecode-render-blocks.stories.tsx new file mode 100644 index 0000000..ef0ba12 --- /dev/null +++ b/src/stories/vanilla/makecode-render-blocks.stories.tsx @@ -0,0 +1,94 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { + createMakeCodeRenderBlocks, + MakeCodeRenderBlocksOptions, +} from '../../vanilla/makecode-render-blocks.js'; +import { + initialProject, + project, + projectWithCustomBlock, + projectWithDatalogging, + projectWithExtensionBlock, + projectWithLayout, + projectWithMelody, + projectWithTwoExtensions, +} from '../fixtures.js'; +import { Project } from '../../vanilla/pxt.js'; +import StoryWrapper from '../StoryWrapper.js'; + +interface StoryArgs { + options: MakeCodeRenderBlocksOptions | undefined; + project: Project; +} + +const meta: Meta = { + title: 'stories/VanillaJS/createMakeCodeRenderBlocks', +}; + +export default meta; + +type Story = StoryObj; + +const renderBlocks = (args: StoryArgs) => { + const cbRef = (e: HTMLElement | null) => { + if (!e) { + return; + } + const renderer = createMakeCodeRenderBlocks(args.options ?? {}); + renderer.initialize(); + renderer.renderBlocks({ code: args.project }).then((r) => { + if (r.svg) { + e.innerHTML = ` +
+ ${r.svg} +
+ `; + } + }); + }; + return ( + +
Loading...
+
+ ); +}; + +export const Simple: Story = { + render: renderBlocks, + args: { project: project }, +}; + +export const XML: Story = { + render: renderBlocks, + args: { project: projectWithLayout }, +}; + +export const Melody: Story = { + render: renderBlocks, + args: { project: projectWithMelody }, +}; + +export const ExtensionBlockSingle: Story = { + render: renderBlocks, + args: { project: projectWithExtensionBlock }, +}; + +export const ExtensionBlockTwo: Story = { + render: renderBlocks, + args: { project: projectWithTwoExtensions }, +}; + +export const ExtensionBlockDatalogging: Story = { + render: renderBlocks, + args: { project: projectWithDatalogging }, +}; + +export const CustomBlock: Story = { + render: renderBlocks, + args: { project: projectWithCustomBlock }, +}; + +export const InitialBlankProject: Story = { + render: renderBlocks, + args: { project: initialProject }, +}; diff --git a/src/vanilla/index.ts b/src/vanilla/index.ts index c1756f1..3a689ec 100644 --- a/src/vanilla/index.ts +++ b/src/vanilla/index.ts @@ -6,13 +6,20 @@ export { export { MakeCodeFrameDriver, createMakeCodeURL, + type EditorShareOptions, type Options, } from '../vanilla/makecode-frame-driver.js'; export { BlockLayout } from '../vanilla/pxt.js'; export type { + CodeCard, + CodeCardAction, + CodeCardEditorType, + CodeCardType, + CodeValidationConfig, CreateEvent, + DependencyMap, EditorContentLoadedRequest, EditorEvent, EditorMessageTutorialCompletedEventRequest, @@ -22,20 +29,48 @@ export type { EditorMessageTutorialProgressEventRequest, EditorWorkspaceSaveRequest, EditorWorkspaceSyncRequest, + FilterState, + Header, ImportExternalProjectOptions, ImportFileOptions, ImportProjectOptions, InfoMessage, LanguageRestriction, + PackageConfig, + PackageExtension, Project, ProjectCreationOptions, ProjectFilters, + ProjectTemplate, RenderBlocksOptions, RenderByBlockIdOptions, RenderXmlOptions, + ScriptText, ShareResult, + SnippetAnswerTypes, + SnippetConfig, + SnippetGoToOptions, + SnippetParameters, + SnippetQuestionInput, + SnippetQuestions, StartActivityOptions, + TargetVersions, ToolboxBlockDefinition, ToolboxCategoryDefinition, + TutorialActivityInfo, + TutorialBlockConfig, + TutorialMetadata, + TutorialOptions, + TutorialStepInfo, UIEvent, + YottaConfig, } from '../vanilla/pxt.js'; + +export { createMakeCodeRenderBlocks } from '../vanilla/makecode-render-blocks.js'; + +export type { + MakeCodeRenderBlocksOptions, + MakeCodeRenderBlocksReturn, + RenderBlocksResponse, + RenderBlocksRequest, +} from '../vanilla/makecode-render-blocks.js'; diff --git a/src/vanilla/makecode-frame-driver.ts b/src/vanilla/makecode-frame-driver.ts index 72ae65e..1358ffe 100644 --- a/src/vanilla/makecode-frame-driver.ts +++ b/src/vanilla/makecode-frame-driver.ts @@ -50,7 +50,7 @@ type EditorMessageRequestUnion = | EditorMessageNewProjectRequest | EditorMessageRequest; -interface EditorShareOptions { +export interface EditorShareOptions { headerId: string; projectName: string; } diff --git a/src/vanilla/makecode-render-blocks.ts b/src/vanilla/makecode-render-blocks.ts index 7fb09f1..de9fd83 100644 --- a/src/vanilla/makecode-render-blocks.ts +++ b/src/vanilla/makecode-render-blocks.ts @@ -80,7 +80,7 @@ interface RenderBlocksRequestResponse { type PendingRequests = { [id: string]: RenderBlocksRequestResponse }; -const createMakeCodeRenderBlocks = ( +export const createMakeCodeRenderBlocks = ( options: MakeCodeRenderBlocksOptions ): MakeCodeRenderBlocksReturn => { const defaultedOptions: MakeCodeRenderBlocksOptions = { @@ -311,5 +311,3 @@ function createIframe( document.body.appendChild(f); return f; } - -export default createMakeCodeRenderBlocks; diff --git a/src/vanilla/pxt.ts b/src/vanilla/pxt.ts index b441605..0f0183d 100644 --- a/src/vanilla/pxt.ts +++ b/src/vanilla/pxt.ts @@ -19,15 +19,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -export interface JsonScriptMeta { - blocksWidth?: number; - blocksHeight?: number; - versions?: TargetVersions; -} - export interface InstallHeader { name: string; // script name, should always be in sync with pxt.json name - meta: JsonScriptMeta; // script meta data + meta: { + // json script meta data + blocksWidth?: number; + blocksHeight?: number; + versions?: TargetVersions; + }; editor: string; // editor that we're in board?: string; // name of the package that contains the board.json info temporary?: boolean; // don't serialize project @@ -37,7 +36,10 @@ export interface InstallHeader { targetVersion: string; pubId: string; // for published scripts pubCurrent: boolean; // is this exactly pubId, or just based on it - pubVersions?: PublishVersion[]; + pubVersions?: { + id: string; + type: 'snapshot' | 'permalink'; + }[]; pubPermalink?: string; // permanent (persistent) share ID anonymousSharePreference?: boolean; // if true, default to sharing anonymously even when logged in githubId?: string; @@ -46,7 +48,12 @@ export interface InstallHeader { // in progress tutorial if any tutorial?: TutorialOptions; // completed tutorial info if any - tutorialCompleted?: TutorialCompletionInfo; + tutorialCompleted?: { + // id of the tutorial + id: string; + // number of steps completed + steps: number; + }; // workspace guid of the extension under test extensionUnderTest?: string; // id of cloud user who created this project @@ -77,11 +84,6 @@ export interface Header extends InstallHeader { _rev: string; // used for idb / pouchdb revision tracking } -interface PublishVersion { - id: string; - type: 'snapshot' | 'permalink'; -} - export type ScriptText = Record; export interface Project { @@ -603,7 +605,7 @@ export interface EditorMessageGetToolboxCategoriesResponse { categories: ToolboxCategoryDefinition[]; } -interface ProjectTemplate { +export interface ProjectTemplate { id: string; config: PackageConfig; files: Record; @@ -809,17 +811,7 @@ export type CloudStatus = | 'conflict' | 'localEdits'; -interface TargetVersions { - target: string; - targetId?: string; - targetWebsite?: string; - pxt?: string; - tag?: string; - branch?: string; - commits?: string; // URL -} - -type CodeCardType = +export type CodeCardType = | 'file' | 'example' | 'codeExample' @@ -832,13 +824,13 @@ type CodeCardType = | 'forumExample' | 'sharedExample' | 'link'; -type CodeCardEditorType = 'blocks' | 'js' | 'py'; +export type CodeCardEditorType = 'blocks' | 'js' | 'py'; -interface Map { +export interface DependencyMap { [index: string]: T; } -interface TargetVersions { +export interface TargetVersions { target: string; targetId?: string; targetWebsite?: string; @@ -848,12 +840,7 @@ interface TargetVersions { commits?: string; // URL } -interface Size { - width: number; - height: number; -} - -interface CodeCardAction { +export interface CodeCardAction { url: string; editor?: CodeCardEditorType; cardType?: CodeCardType; @@ -862,7 +849,7 @@ interface CodeCardAction { /** * The schema for the pxt.json package files */ -interface PackageConfig { +export interface PackageConfig { name: string; version?: string; // installedVersion?: string; moved to Package class @@ -872,13 +859,13 @@ interface PackageConfig { documentation?: string; // doc page to open when loading project, used by sidedocs targetVersions?: TargetVersions; // versions of the target/pxt the package was compiled against description?: string; - dependencies: Map; + dependencies: DependencyMap; license?: string; authors?: string[]; files: string[]; simFiles?: string[]; testFiles?: string[]; - fileDependencies?: Map; // exclude certain files if dependencies are not fulfilled + fileDependencies?: DependencyMap; // exclude certain files if dependencies are not fulfilled preferredEditor?: string; // tsprj, blocksprj, pyprj languageRestriction?: LanguageRestriction; // language restrictions that have been placed on the package testDependencies?: Record; @@ -886,14 +873,21 @@ interface PackageConfig { public?: boolean; partial?: boolean; // true if project is not compileable on its own (eg base) binaryonly?: boolean; - platformio?: PlatformIOConfig; + platformio?: { + dependencies?: DependencyMap; + }; compileServiceVariant?: string; palette?: string[]; paletteNames?: string[]; - screenSize?: Size; + screenSize?: { + width: number; + height: number; + }; yotta?: YottaConfig; - codal?: CodalConfig; - npmDependencies?: Map; + codal?: { + libraries?: string[]; + }; + npmDependencies?: DependencyMap; card?: CodeCard; additionalFilePath?: string; additionalFilePaths?: string[]; @@ -923,10 +917,10 @@ interface PackageConfig { disableTargetTemplateFiles?: boolean; // do not override target template files when commiting to github theme?: string | Record; 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 + assetPacks?: DependencyMap; // a map of dependency id to boolean that indicates which dependencies should be imported as asset packs } -interface PackageExtension { +export interface PackageExtension { // Namespace to add the button under, defaults to package name namespace?: string; // Group to place button in @@ -943,34 +937,24 @@ interface PackageExtension { localUrl?: string; } -interface PlatformIOConfig { - dependencies?: Map; -} - -interface CompilationConfig { - description: string; - config: any; -} - -interface CodalConfig { - libraries?: string[]; -} - -interface YottaConfig { - dependencies?: Map; +export interface YottaConfig { + dependencies?: DependencyMap; config?: any; /** * Overridable config flags */ optionalConfig?: any; - userConfigs?: CompilationConfig[]; + userConfigs?: { + description: string; + config: any; + }[]; /* deprecated */ configIsJustDefaults?: boolean; /* deprecated */ ignoreConflicts?: boolean; } -interface CodeCard { +export interface CodeCard { name?: string; shortName?: string; title?: string; @@ -1023,21 +1007,18 @@ interface CodeCard { className?: string; variant?: string; } - -type SnippetOutputType = 'blocks'; -type SnippetOutputBehavior = /*assumed default*/ 'merge' | 'replace'; -interface SnippetConfig { +export interface SnippetConfig { name: string; namespace: string; group?: string; label: string; - outputType: SnippetOutputType; - outputBehavior?: SnippetOutputBehavior; + outputType: 'blocks'; + outputBehavior?: /*assumed default*/ 'merge' | 'replace'; initialOutput?: string; questions: SnippetQuestions[]; } -type SnippetAnswerTypes = +export type SnippetAnswerTypes = | 'number' | 'text' | 'variableName' @@ -1046,70 +1027,56 @@ type SnippetAnswerTypes = | 'yesno' | string; // TODO(jb) Should include custom answer types for number, enums, string, image -interface SnippetGoToOptions { +export interface SnippetGoToOptions { question?: number; - validate?: SnippetValidate; + validate?: { + regex?: { + token: string; + regex: string; + match?: SnippetParameters; + noMatch?: SnippetParameters; + }; + }; parameters?: SnippetParameters[]; // Answer token with corresponding question } -interface SnippetParameters { +export interface SnippetParameters { token?: string; answer?: string; question: number; } -interface SnippetInputAnswerSingular { - answerToken: string; - defaultAnswer: SnippetAnswerTypes; -} - -interface SnippetInputAnswerPlural { - answerTokens: string[]; - defaultAnswers: SnippetAnswerTypes[]; -} - -interface SnippetInputOtherType { - type: string; -} - -interface SnippetInputNumberType { - type: 'number' | 'positionPicker'; - max?: number; - min?: number; -} - -interface SnippetInputDropdownType { - type: 'dropdown'; - options: Record; -} - -interface SnippetInputYesNoType { - type: 'yesno'; -} - -type SnippetQuestionInput = { label?: string } & ( - | SnippetInputAnswerSingular - | SnippetInputAnswerPlural +export type SnippetQuestionInput = { label?: string } & ( + | { + // Singular input answer. + answerToken: string; + defaultAnswer: SnippetAnswerTypes; + } + | { + // Plural input answer. + answerTokens: string[]; + defaultAnswers: SnippetAnswerTypes[]; + } ) & ( - | SnippetInputOtherType - | SnippetInputNumberType - | SnippetInputDropdownType - | SnippetInputYesNoType + | { + type: string; + } + | { + type: 'number' | 'positionPicker'; + max?: number; + min?: number; + } + | { + type: 'dropdown'; + options: Record; + } + | { + type: 'yesno'; + } ); -interface SnippetValidateRegex { - token: string; - regex: string; - match?: SnippetParameters; - noMatch?: SnippetParameters; -} - -interface SnippetValidate { - regex?: SnippetValidateRegex; -} - -interface SnippetQuestions { +export interface SnippetQuestions { title: string; output?: string; outputConditionalOnAnswer?: string; @@ -1119,7 +1086,7 @@ interface SnippetQuestions { hint?: string; } -interface TutorialOptions { +export interface TutorialOptions { tutorial?: string; // tutorial tutorialName?: string; // tutorial title tutorialReportId?: string; // if this tutorial was user generated, the report abuse id @@ -1147,7 +1114,7 @@ interface TutorialOptions { simTheme?: Partial; } -interface TutorialStepInfo { +export interface TutorialStepInfo { // Step metadata showHint?: boolean; // automatically displays hint showDialog?: boolean; // no coding, displays in modal @@ -1172,24 +1139,15 @@ interface TutorialStepInfo { localValidationConfig?: CodeValidationConfig; } -interface TutorialCompletionInfo { - // id of the tutorial - id: string; - // number of steps completed - steps: number; -} - -interface TutorialBlockConfigEntry { - blockId?: string; - xml?: string; -} - -interface TutorialBlockConfig { +export interface TutorialBlockConfig { md?: string; // `blockconfig` markdown fragment - blocks?: TutorialBlockConfigEntry[]; // markdown fragment can contain multiple block definitions + blocks?: { + blockId?: string; + xml?: string; + }[]; // markdown fragment can contain multiple block definitions } -interface TutorialMetadata { +export interface TutorialMetadata { activities?: boolean; // tutorial consists of activities, then steps. uses `###` for steps explicitHints?: boolean; // tutorial expects explicit hints in `#### ~ tutorialhint` format flyoutOnly?: boolean; // no categories, display all blocks in flyout @@ -1202,21 +1160,17 @@ interface TutorialMetadata { preferredEditor?: string; // preferred editor for opening the tutorial } -interface TutorialActivityInfo { +export interface TutorialActivityInfo { name: string; step: number; } -interface CodeValidatorBaseProperties { - enabled?: string; - markers?: string; -} - -interface CodeValidatorMetadata { - validatorType: string; - properties: CodeValidatorBaseProperties; -} - -interface CodeValidationConfig { - validatorsMetadata: CodeValidatorMetadata[]; +export interface CodeValidationConfig { + validatorsMetadata: { + validatorType: string; + properties: { + enabled?: string; + markers?: string; + }; + }[]; } diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..07f761c --- /dev/null +++ b/typedoc.json @@ -0,0 +1,13 @@ +{ + "name": "MakeCode Embed", + "navigationLinks": { + "micro:bit tech site": "https://tech.microbit.org", + "GitHub": "https://github.com/microbit-foundation/makecode-embed" + }, + "projectDocuments": ["docs/react.md", "docs/vanilla.md"], + "entryPoints": ["./src/react/index.ts", "./src/vanilla/index.ts"], + "out": "./docs/build", + "readme": "./README.md", + "headings": { "readme": false }, + "customCss": "./docs/custom.css" +} From d608899aec8e08c37c532496198e5e601d5dd950 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:44:44 +0000 Subject: [PATCH 3/4] Configure TypeDoc to exclude externals (#3) --- typedoc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typedoc.json b/typedoc.json index 07f761c..9308f46 100644 --- a/typedoc.json +++ b/typedoc.json @@ -9,5 +9,6 @@ "out": "./docs/build", "readme": "./README.md", "headings": { "readme": false }, - "customCss": "./docs/custom.css" + "customCss": "./docs/custom.css", + "excludeExternals": true } From 936d6a5cdaa43e7acb2dc5f046bb5673fdffc8ea Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Mon, 3 Feb 2025 14:44:55 +0000 Subject: [PATCH 4/4] Breaking: Rename Project -> MakeCodeProject (#2) * Breaking: Rename Project -> MakeCodeProject * Remove unused import that wasn't renamed --- docs/vanilla.md | 1 - src/react/MakeCodeBlocksRendering.tsx | 4 ++-- src/react/MakeCodeFrame.tsx | 4 ++-- src/react/index.ts | 2 +- src/stories/MakeCodeToolbar.tsx | 4 ++-- src/stories/fixtures.ts | 12 ++++++------ .../react/MakeCodeBlocksRendering.stories.tsx | 6 +++--- src/stories/react/MakeCodeFrame.stories.tsx | 4 ++-- .../vanilla/makecode-frame-driver.stories.tsx | 8 ++++---- .../vanilla/makecode-render-blocks.stories.tsx | 4 ++-- src/vanilla/examples.ts | 6 +++--- src/vanilla/index.ts | 2 +- src/vanilla/makecode-frame-driver.ts | 4 ++-- src/vanilla/makecode-render-blocks.ts | 8 ++++---- src/vanilla/pxt.ts | 10 +++++----- 15 files changed, 39 insertions(+), 40 deletions(-) diff --git a/docs/vanilla.md b/docs/vanilla.md index 9374c89..ac759b4 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -32,7 +32,6 @@ Use {@link vanilla.MakeCodeFrameDriver | MakeCodeFrameDriver} class to create a ```js import { - Project, MakeCodeFrameDriver, createMakeCodeURL, } from "@microbit/makecode-embed/vanilla"; diff --git a/src/react/MakeCodeBlocksRendering.tsx b/src/react/MakeCodeBlocksRendering.tsx index ec20c0c..02319f3 100644 --- a/src/react/MakeCodeBlocksRendering.tsx +++ b/src/react/MakeCodeBlocksRendering.tsx @@ -4,11 +4,11 @@ */ import React, { useState, useEffect, ReactNode } from 'react'; import { useMakeCodeRenderBlocksContext } from './MakeCodeRenderBlocksProvider.js'; -import { BlockLayout, Project } from '../vanilla/pxt.js'; +import { BlockLayout, MakeCodeProject } from '../vanilla/pxt.js'; export interface MakeCodeBlocksRenderingProps { className?: string; - code?: string | Project; + code?: string | MakeCodeProject; packageId?: string; package?: string; snippetMode?: boolean; diff --git a/src/react/MakeCodeFrame.tsx b/src/react/MakeCodeFrame.tsx index fab7c4d..9f52aed 100644 --- a/src/react/MakeCodeFrame.tsx +++ b/src/react/MakeCodeFrame.tsx @@ -16,7 +16,7 @@ import { EditorMessageTutorialEventRequest, EditorWorkspaceSaveRequest, EditorWorkspaceSyncRequest, - Project, + MakeCodeProject, ProjectFilters, } from '../vanilla/pxt.js'; @@ -38,7 +38,7 @@ export interface MakeCodeFrameProps // https://github.com/microsoft/pxt-microbit/blob/master/pxtarget.json#L605C6-L605C14 queryParams?: Record; - initialProjects: () => Promise; + initialProjects: () => Promise; controllerId?: string; filters?: ProjectFilters; diff --git a/src/react/index.ts b/src/react/index.ts index be05503..4581050 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -42,7 +42,7 @@ export type { ImportProjectOptions, InfoMessage, LanguageRestriction, - Project, + MakeCodeProject, ProjectCreationOptions, ProjectFilters, RenderBlocksOptions, diff --git a/src/stories/MakeCodeToolbar.tsx b/src/stories/MakeCodeToolbar.tsx index 421b3c9..703ca5c 100644 --- a/src/stories/MakeCodeToolbar.tsx +++ b/src/stories/MakeCodeToolbar.tsx @@ -1,7 +1,7 @@ import { MutableRefObject, RefObject } from 'react'; import { MakeCodeFrameDriver } from '../vanilla/makecode-frame-driver.js'; import { defaultMakeCodeProject } from '../vanilla/examples.js'; -import { Project } from '../vanilla/pxt.js'; +import { MakeCodeProject } from '../vanilla/pxt.js'; const toolbarRowStyle = { fontFamily: 'sans-serif', @@ -16,7 +16,7 @@ const MakeCodeToolbar = ({ savedProjects, }: { driver: RefObject; - savedProjects: MutableRefObject>; + savedProjects: MutableRefObject>; }) => { return (
diff --git a/src/stories/fixtures.ts b/src/stories/fixtures.ts index aea6897..8dd8816 100644 --- a/src/stories/fixtures.ts +++ b/src/stories/fixtures.ts @@ -1,4 +1,4 @@ -import { Project } from '../vanilla/pxt.js'; +import { MakeCodeProject } from '../vanilla/pxt.js'; export const project = { text: { @@ -9,7 +9,7 @@ export const project = { 'pxt.json': '{\n "name": "Untitled",\n "dependencies": {\n "core": "*"\n , "radio": "*"\n },\n "description": "",\n "files": [\n "main.blocks",\n "main.ts",\n "README.md"\n ]\n}', }, -} as Project; +} as MakeCodeProject; export const projectWithLayout = { text: { @@ -63,9 +63,9 @@ export const projectWithLayout = { `, }, -} as Project; +} as MakeCodeProject; -export const projectWithExtensionBlock: Project = { +export const projectWithExtensionBlock: MakeCodeProject = { text: { 'README.md': '', 'main.blocks': @@ -136,7 +136,7 @@ export const initialProject = { }, }; -export const projectWithCustomBlock: Project = { +export const projectWithCustomBlock: MakeCodeProject = { text: { 'README.md': '', 'custom.ts': @@ -149,7 +149,7 @@ export const projectWithCustomBlock: Project = { }, }; -export const projectWithUserLayout: Project = { +export const projectWithUserLayout: MakeCodeProject = { text: { 'README.md': '', 'main.blocks': diff --git a/src/stories/react/MakeCodeBlocksRendering.stories.tsx b/src/stories/react/MakeCodeBlocksRendering.stories.tsx index 90c2a63..a094459 100644 --- a/src/stories/react/MakeCodeBlocksRendering.stories.tsx +++ b/src/stories/react/MakeCodeBlocksRendering.stories.tsx @@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { ReactNode, useState } from 'react'; import MakeCodeBlocksRendering from '../../react/MakeCodeBlocksRendering.js'; import { MakeCodeRenderBlocksProvider } from '../../react/MakeCodeRenderBlocksProvider.js'; -import { BlockLayout, Project } from '../../vanilla/pxt.js'; +import { BlockLayout, MakeCodeProject } from '../../vanilla/pxt.js'; import { initialProject, project, @@ -190,7 +190,7 @@ export const Robust: Story = { return ( - + ); @@ -229,7 +229,7 @@ export const EmptyToBlocksTransition: Story = { name: 'Empty to blocks transition', render: (args) => { const { version } = args; - const [project, setProject] = useState(initialProject); + const [project, setProject] = useState(initialProject); return (
diff --git a/src/stories/react/MakeCodeFrame.stories.tsx b/src/stories/react/MakeCodeFrame.stories.tsx index 627337c..e41b196 100644 --- a/src/stories/react/MakeCodeFrame.stories.tsx +++ b/src/stories/react/MakeCodeFrame.stories.tsx @@ -6,7 +6,7 @@ import { default as MakeCodeFrame, MakeCodeFrameProps, } from '../../react/MakeCodeFrame.js'; -import { Project } from '../../vanilla/pxt.js'; +import { MakeCodeProject } from '../../vanilla/pxt.js'; import { controllerId } from '../config.js'; import StoryWrapper from '../StoryWrapper.js'; import MakeCodeToolbar from '../MakeCodeToolbar.js'; @@ -30,7 +30,7 @@ type Story = StoryObj; const MakeCodeEditorWithControls = ( props: Omit ) => { - const savedProjects = useRef>(new Map()); + const savedProjects = useRef>(new Map()); const ref = useRef(null); const initialProjects = useCallback(async () => { if (savedProjects.current.size === 0) { diff --git a/src/stories/vanilla/makecode-frame-driver.stories.tsx b/src/stories/vanilla/makecode-frame-driver.stories.tsx index e1b4b69..1dac623 100644 --- a/src/stories/vanilla/makecode-frame-driver.stories.tsx +++ b/src/stories/vanilla/makecode-frame-driver.stories.tsx @@ -6,7 +6,7 @@ import { MakeCodeFrameDriver, Options, } from '../../vanilla/makecode-frame-driver.js'; -import { Project } from '../../vanilla/pxt.js'; +import { MakeCodeProject } from '../../vanilla/pxt.js'; import MakeCodeToolbar from '../MakeCodeToolbar.js'; import StoryWrapper from '../StoryWrapper.js'; @@ -17,7 +17,7 @@ interface StoryArgs { controller?: 1 | 2; queryParams?: Record; }; - project?: Project; + project?: MakeCodeProject; callbacks?: Partial; } @@ -30,7 +30,7 @@ export default meta; type Story = StoryObj; const renderEditor = (args: StoryArgs) => { - const savedProjects = useRef>(new Map()); + const savedProjects = useRef>(new Map()); const ref = useRef(null); const cbRef = (div: HTMLElement | null) => { if (!div) { @@ -50,7 +50,7 @@ const renderEditor = (args: StoryArgs) => { iframe.height = '100%'; div.appendChild(iframe); - const savedProjects: Map = new Map(); + const savedProjects: Map = new Map(); // Create and initialise an instance of MakeCodeFrameDriver. ref.current = new MakeCodeFrameDriver( diff --git a/src/stories/vanilla/makecode-render-blocks.stories.tsx b/src/stories/vanilla/makecode-render-blocks.stories.tsx index ef0ba12..03bdd54 100644 --- a/src/stories/vanilla/makecode-render-blocks.stories.tsx +++ b/src/stories/vanilla/makecode-render-blocks.stories.tsx @@ -13,12 +13,12 @@ import { projectWithMelody, projectWithTwoExtensions, } from '../fixtures.js'; -import { Project } from '../../vanilla/pxt.js'; +import { MakeCodeProject } from '../../vanilla/pxt.js'; import StoryWrapper from '../StoryWrapper.js'; interface StoryArgs { options: MakeCodeRenderBlocksOptions | undefined; - project: Project; + project: MakeCodeProject; } const meta: Meta = { diff --git a/src/vanilla/examples.ts b/src/vanilla/examples.ts index 0bb4216..9f5bf1d 100644 --- a/src/vanilla/examples.ts +++ b/src/vanilla/examples.ts @@ -1,6 +1,6 @@ -import { Project } from './pxt.js'; +import { MakeCodeProject } from './pxt.js'; -export const defaultMakeCodeProject: Project = { +export const defaultMakeCodeProject: MakeCodeProject = { text: { 'main.blocks': '\n \n', @@ -11,7 +11,7 @@ export const defaultMakeCodeProject: Project = { }, }; -export const emptyMakeCodeProject: Project = { +export const emptyMakeCodeProject: MakeCodeProject = { text: { 'main.blocks': '\n \n', diff --git a/src/vanilla/index.ts b/src/vanilla/index.ts index 3a689ec..588d0bb 100644 --- a/src/vanilla/index.ts +++ b/src/vanilla/index.ts @@ -38,7 +38,7 @@ export type { LanguageRestriction, PackageConfig, PackageExtension, - Project, + MakeCodeProject, ProjectCreationOptions, ProjectFilters, ProjectTemplate, diff --git a/src/vanilla/makecode-frame-driver.ts b/src/vanilla/makecode-frame-driver.ts index 1358ffe..6fd7e57 100644 --- a/src/vanilla/makecode-frame-driver.ts +++ b/src/vanilla/makecode-frame-driver.ts @@ -29,7 +29,7 @@ import { ImportProjectOptions, InfoMessage, LanguageRestriction, - Project, + MakeCodeProject, ProjectCreationOptions, ProjectFilters, RenderBlocksOptions, @@ -64,7 +64,7 @@ export interface Options { * The projects will receive updates via `onWorkspaceSave` and should be stored keyed by header * id. */ - initialProjects: () => Promise; + initialProjects: () => Promise; /** * Set this to a value representing your app. diff --git a/src/vanilla/makecode-render-blocks.ts b/src/vanilla/makecode-render-blocks.ts index de9fd83..a868694 100644 --- a/src/vanilla/makecode-render-blocks.ts +++ b/src/vanilla/makecode-render-blocks.ts @@ -2,7 +2,7 @@ * MakeCode handling that does not depend on React. */ -import { BlockLayout, Project } from './pxt.js'; +import { BlockLayout, MakeCodeProject } from './pxt.js'; const disposedMessage = 'Disposed'; const makecodeFailedToLoadMessage = 'Failed to load MakeCode to render blocks.'; @@ -22,7 +22,7 @@ export interface MakeCodeRenderBlocksReturn { } export interface RenderBlocksRequest { - code: string | Project; + code: string | MakeCodeProject; options?: { packageId?: string; package?: string; @@ -68,7 +68,7 @@ interface RenderBlocksResponseMessage { type RequestInputType = 'text' | 'blocks'; interface RenderBlocksRequestResponse { - input: Project | string; + input: MakeCodeProject | string; sent: boolean; type: RequestInputType; req: RenderBlocksRequestMessage; @@ -129,7 +129,7 @@ export const createMakeCodeRenderBlocks = ( }; const findBestCode = ( - code: string | Project, + code: string | MakeCodeProject, ignoreBlocks?: boolean ): { type: RequestInputType; code: string } => { if (typeof code === 'string') { diff --git a/src/vanilla/pxt.ts b/src/vanilla/pxt.ts index 0f0183d..93953d2 100644 --- a/src/vanilla/pxt.ts +++ b/src/vanilla/pxt.ts @@ -86,7 +86,7 @@ export interface Header extends InstallHeader { export type ScriptText = Record; -export interface Project { +export interface MakeCodeProject { header?: Header; text?: ScriptText; } @@ -333,7 +333,7 @@ export interface EditorWorkspaceSyncResponse extends EditorMessageResponse { /* * Full list of project, required for init */ - projects: Project[]; + projects: MakeCodeProject[]; // (optional) filtering argument editor?: EditorSyncState; // (optional) controller id, used for determining what the parent controller is @@ -345,12 +345,12 @@ export interface EditorWorkspaceSaveRequest extends EditorMessageRequest { /* * Modified project */ - project: Project; + project: MakeCodeProject; } export interface ImportProjectOptions { // project to load - project: Project; + project: MakeCodeProject; // (optional) filtering argument filters?: ProjectFilters; searchBar?: boolean; @@ -364,7 +364,7 @@ export interface EditorMessageImportProjectRequest export interface ImportExternalProjectOptions { // project to load - project: Project; + project: MakeCodeProject; } export interface EditorMessageImportExternalProjectRequest