From 72522a4df74e2e499f30ff3a08c2b88a9bca281c Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 10:03:46 +0000 Subject: [PATCH 01/21] Use TypeDoc to generate docs, tweak README.md --- .gitignore | 1 + README.md | 13 +- docs/custom.css | 4 + package-lock.json | 152 ++++++++++++++++ package.json | 4 +- src/vanilla/index.ts | 32 ++++ src/vanilla/makecode-frame-driver.ts | 2 +- src/vanilla/pxt.ts | 252 +++++++++++---------------- typedoc.json | 12 ++ 9 files changed, 315 insertions(+), 157 deletions(-) create mode 100644 docs/custom.css create mode 100644 typedoc.json 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/README.md b/README.md index 84e5b6d..42a4aaa 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ -# 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/) -There isn't any documentation yet. You can find a StoryBook demo of the -components at https://microbit-foundation.github.io/makecode-embed/ +## Usage ## 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/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/vanilla/index.ts b/src/vanilla/index.ts index c1756f1..db17594 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,45 @@ 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 type { + MakeCodeRenderBlocksOptions, + 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/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..5a45514 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,12 @@ +{ + "name": "MakeCode Embed", + "navigationLinks": { + "micro:bit tech site": "https://tech.microbit.org", + "GitHub": "https://github.com/microbit-foundation/makecode-embed" + }, + "entryPoints": ["./src/react/index.ts", "./src/vanilla/index.ts"], + "out": "./docs/build", + "readme": "./README.md", + "headings": { "readme": false }, + "customCss": "./docs/custom.css" +} From 72ddb57d4660c2909a0574966f6a931381182634 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 10:04:01 +0000 Subject: [PATCH 02/21] Add build-docs workflow --- .github/workflows/build-docs.yml | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/build-docs.yml 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 From 421df26fda9f7531e8098dfbfa8c3323add962b0 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 10:11:37 +0000 Subject: [PATCH 03/21] Add TODO to LICENSE.md --- LICENSE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LICENSE.md b/LICENSE.md index 52209ef..200b8ae 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,7 @@ MIT License + + Copyright (c) YEAR AUTHOR Permission is hereby granted, free of charge, to any person obtaining a copy From 9c35ef2ae2f0706efc4e6251cf0791bc6b59f566 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 10:41:38 +0000 Subject: [PATCH 04/21] Add usage documentation for VanillaJS and React --- README.md | 3 +++ docs/react.md | 47 +++++++++++++++++++++++++++++++++ docs/vanilla.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ typedoc.json | 1 + 4 files changed, 121 insertions(+) create mode 100644 docs/react.md create mode 100644 docs/vanilla.md diff --git a/README.md b/README.md index 42a4aaa..f402d5c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ who embed MakeCode. ## Usage +- [React usage documentation](docs/react.md) +- [VanillaJS usage documentation](docs/vanilla.md) + ## License This software is under the MIT open source license. diff --git a/docs/react.md b/docs/react.md new file mode 100644 index 0000000..2017e50 --- /dev/null +++ b/docs/react.md @@ -0,0 +1,47 @@ +--- +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. You can see examples of projects used for the demo in [fixtures.ts](../src/stories/fixtures.ts). + +```js +import { + MakeCodeRenderBlocksProvider, + MakeCodeBlocksRendering, +} from '@microbit/makecode-embed/react'; + + + +; +``` + +## Embed MakeCode editor + +Use {@link react.MakeCodeFrame | MakeCodeFrame} component to embed MakeCode. + +```js +import { MakeCodeFrame } from '@microbit/makecode-embed/react'; + + 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)} +/> +``` diff --git a/docs/vanilla.md b/docs/vanilla.md new file mode 100644 index 0000000..a603bc9 --- /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/vanilla/examples.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: defaultMakeCodeProject }); + +document.querySelector("#app")!.innerHTML = ` +
+ ${result.svg} +
+`; +``` + +## Embed MakeCode editor + +Use {@link vanilla.MakeCodeFrameDriver | MakeCodeFrameDriver} class to create a driverRef for an iframe element. + +```js +import { + Project, + MakeCodeFrameDriver, + createMakeCodeURL, +} from "@microbit/makecode-embed/vanilla"; + +// Set up an iframe element. +let iframe = document.createElement("iframe"); +iframe.allow = "usb; autoplay; camera; microphone;"; +iframe.src = createMakeCodeURL( + "https://makecode.microbit.org", + undefined, + undefined, + 1, + undefined +); +iframe.width = "100%"; +iframe.height = "100%"; + +document.querySelector("#app")!.appendChild(iframe); + +// Create and initialise an instance of MakeCodeFrameDriver. +const driverRef = new MakeCodeFrameDriver( + { + initialProjects: async () => [defaultMakeCodeProject], + 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) => { + console.log(e.project!.header!.id, e.project); + }, + onTutorialEvent: (e) => console.log("tutorialEvent", e), + }, + () => iframe +); +driverRef.initialize(); +``` diff --git a/typedoc.json b/typedoc.json index 5a45514..07f761c 100644 --- a/typedoc.json +++ b/typedoc.json @@ -4,6 +4,7 @@ "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", From 9664fa32990ec1071e1bf3f344f29181ff3be0ac Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 15:04:57 +0000 Subject: [PATCH 05/21] Export createMakeCodeRenderBlocks (potentially breaking change) --- src/react/useMakeCodeRenderBlocks.tsx | 3 ++- src/vanilla/index.ts | 3 +++ src/vanilla/makecode-render-blocks.ts | 4 +--- 3 files changed, 6 insertions(+), 4 deletions(-) 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/vanilla/index.ts b/src/vanilla/index.ts index db17594..3a689ec 100644 --- a/src/vanilla/index.ts +++ b/src/vanilla/index.ts @@ -66,8 +66,11 @@ export type { 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-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; From 0016cea189febe4582f9b1704000726b315b69b5 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 15:09:37 +0000 Subject: [PATCH 06/21] Add links to React storybook sourcecode --- docs/react.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/react.md b/docs/react.md index 2017e50..0ccdafb 100644 --- a/docs/react.md +++ b/docs/react.md @@ -8,7 +8,7 @@ title: React Usage ## Blocks rendering -Use {@link react.MakeCodeRenderBlocksProvider | MakeCodeRenderBlocksProvider} and {@link react.MakeCodeBlocksRendering | MakeCodeBlocksRendering} React components to render MakeCode blocks for a MakeCode project. You can see examples of projects used for the demo in [fixtures.ts](../src/stories/fixtures.ts). +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 { @@ -21,6 +21,8 @@ import { ; ``` +For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/MakeCodeBlocksRendering.stories.tsx). + ## Embed MakeCode editor Use {@link react.MakeCodeFrame | MakeCodeFrame} component to embed MakeCode. @@ -45,3 +47,5 @@ import { MakeCodeFrame } from '@microbit/makecode-embed/react'; onTutorialEvent={(e) => console.log('tutorialEvent', e)} /> ``` + +For more examples, take a look at the [MakeCode frame demo source code](../src/stories/MakeCodeFrame.stories.tsx). From 88a7cd06b6d9639eacb41ad75ad3857c15974a12 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 18:13:25 +0000 Subject: [PATCH 07/21] Setup VanillaJS stories --- docs/vanilla.md | 4 +- src/stories/StoryWrapper.tsx | 3 +- .../createMakeCodeRenderBlocks.stories.tsx | 93 +++++++++++++++++ src/stories/makeCodeFrameDriver.stories.tsx | 99 +++++++++++++++++++ src/stories/utils.ts | 0 5 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/stories/createMakeCodeRenderBlocks.stories.tsx create mode 100644 src/stories/makeCodeFrameDriver.stories.tsx create mode 100644 src/stories/utils.ts diff --git a/docs/vanilla.md b/docs/vanilla.md index a603bc9..0be1d00 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -36,7 +36,7 @@ import { } from "@microbit/makecode-embed/vanilla"; // Set up an iframe element. -let iframe = document.createElement("iframe"); +const iframe = document.createElement("iframe"); iframe.allow = "usb; autoplay; camera; microphone;"; iframe.src = createMakeCodeURL( "https://makecode.microbit.org", @@ -53,7 +53,7 @@ document.querySelector("#app")!.appendChild(iframe); // Create and initialise an instance of MakeCodeFrameDriver. const driverRef = new MakeCodeFrameDriver( { - initialProjects: async () => [defaultMakeCodeProject], + initialProjects: async () => [makeCodeProject], onEditorContentLoaded: (e) => console.log("editorContentLoaded", e), onWorkspaceLoaded: (e) => console.log("workspaceLoaded", e), onWorkspaceSync: (e) => console.log("workspaceSync", e), diff --git a/src/stories/StoryWrapper.tsx b/src/stories/StoryWrapper.tsx index 489698e..720d230 100644 --- a/src/stories/StoryWrapper.tsx +++ b/src/stories/StoryWrapper.tsx @@ -1,7 +1,8 @@ import { ReactNode } from 'react'; -const StoryWrapper = (props: { children: ReactNode }) => ( +const StoryWrapper = (props: { id?: string; children?: ReactNode }) => (
= { + title: 'createMakeCodeRenderBlocks', +}; + +export default meta; + +type Story = StoryObj; + +const renderBlocks = (args: StoryArgs) => { + const elementId = 'story-wrapper'; + const renderer = createMakeCodeRenderBlocks(args.options ?? {}); + renderer.initialize(); + const waitForElementLoaded = () => { + const targetEl = document.getElementById(elementId); + if (!targetEl) { + window.setTimeout(waitForElementLoaded, 500); + return; + } + renderer.renderBlocks({ code: args.project }).then((r) => { + if (r.svg) { + targetEl.innerHTML = ` +
+ ${r.svg} +
+ `; + } + }); + }; + waitForElementLoaded(); + return
; +}; + +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/stories/makeCodeFrameDriver.stories.tsx b/src/stories/makeCodeFrameDriver.stories.tsx new file mode 100644 index 0000000..84154bc --- /dev/null +++ b/src/stories/makeCodeFrameDriver.stories.tsx @@ -0,0 +1,99 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { + createMakeCodeURL, + MakeCodeFrameDriver, + Options, +} from '../vanilla/makecode-frame-driver.js'; +import { Project } from '../vanilla/pxt.js'; +import StoryWrapper from './StoryWrapper.js'; +import { defaultMakeCodeProject } from '../vanilla/examples.js'; + +interface StoryArgs { + options?: { + version?: string; + lang?: string; + controller?: 1 | 2; + queryParams?: Record; + }; + project?: Project; + callbacks?: Partial; +} + +const meta: Meta = { + title: 'makeCodeFrameDriver', +}; + +export default meta; + +type Story = StoryObj; + +const renderEditor = (args: StoryArgs) => { + const elementId = 'story-wrapper'; + + // 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%'; + + // Create and initialise an instance of MakeCodeFrameDriver. + const driverRef = 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) => { + console.log(e.project!.header!.id, e.project); + }, + onTutorialEvent: (e) => console.log('tutorialEvent', e), + ...(args.callbacks ?? {}), + }, + () => iframe + ); + + const waitForElementLoaded = () => { + const targetEl = document.getElementById(elementId); + if (!targetEl) { + window.setTimeout(waitForElementLoaded, 500); + return; + } + targetEl.replaceChildren(iframe); + driverRef.initialize(); + }; + waitForElementLoaded(); + + 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/utils.ts b/src/stories/utils.ts new file mode 100644 index 0000000..e69de29 From f60a41941f3efe79354146e752909d7ca025ce38 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 31 Jan 2025 18:16:27 +0000 Subject: [PATCH 08/21] Add links to VanillaJS storybook sourcecode --- docs/vanilla.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/vanilla.md b/docs/vanilla.md index 0be1d00..3a17a24 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -24,6 +24,8 @@ document.querySelector("#app")!.innerHTML = ` `; ``` +For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/createMakeCodeRenderBlocks.stories.tsx). + ## Embed MakeCode editor Use {@link vanilla.MakeCodeFrameDriver | MakeCodeFrameDriver} class to create a driverRef for an iframe element. @@ -68,3 +70,5 @@ const driverRef = new MakeCodeFrameDriver( ); driverRef.initialize(); ``` + +For more examples, take a look at the [MakeCode frame demo source code](../src/stories/makeCodeFrameDriver.stories.tsx). From 1005d134af95ed321a648076b1e0522b240425c3 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 09:34:44 +0000 Subject: [PATCH 09/21] Add controls to makeCodeFrameDriver stories --- src/stories/StoryWrapper.tsx | 3 +- src/stories/makeCodeFrameDriver.stories.tsx | 208 +++++++++++++++++++- src/stories/utils.ts | 0 3 files changed, 206 insertions(+), 5 deletions(-) delete mode 100644 src/stories/utils.ts diff --git a/src/stories/StoryWrapper.tsx b/src/stories/StoryWrapper.tsx index 720d230..489698e 100644 --- a/src/stories/StoryWrapper.tsx +++ b/src/stories/StoryWrapper.tsx @@ -1,8 +1,7 @@ import { ReactNode } from 'react'; -const StoryWrapper = (props: { id?: string; children?: ReactNode }) => ( +const StoryWrapper = (props: { children: ReactNode }) => (
{ iframe.width = '100%'; iframe.height = '100%'; + const savedProjects: Map = new Map(); + const toolbarStyles = { + fontFamily: 'sans-serif', + display: 'flex', + flexWrap: 'wrap', + gap: '5px', + margin: '10px 0', + } as const; + // Create and initialise an instance of MakeCodeFrameDriver. const driverRef = new MakeCodeFrameDriver( { @@ -53,7 +61,9 @@ const renderEditor = (args: StoryArgs) => { onWorkspaceReset: (e) => console.log('workspaceReset', e), onWorkspaceEvent: (e) => console.log('workspaceEvent', e), onWorkspaceSave: (e) => { - console.log(e.project!.header!.id, e.project); + const headerId = e.project!.header!.id; + savedProjects.set(headerId, e.project); + console.log(savedProjects); }, onTutorialEvent: (e) => console.log('tutorialEvent', e), ...(args.callbacks ?? {}), @@ -72,7 +82,199 @@ const renderEditor = (args: StoryArgs) => { }; waitForElementLoaded(); - return ; + return ( + <> +
+
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + +
+
+ + + + +
+
+
+ + ); }; export const MakeCodeEditorWithControlsStory: Story = { diff --git a/src/stories/utils.ts b/src/stories/utils.ts deleted file mode 100644 index e69de29..0000000 From c34715489bfd926a5011663f9d2556d8dd2dd9c2 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 10:33:56 +0000 Subject: [PATCH 10/21] Remove stories GH pages deployment --- .github/workflows/build.yml | 16 ---------------- 1 file changed, 16 deletions(-) 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 From 8d36ab72a5511ff75c0208401c10c8dd2b9100b2 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 10:49:15 +0000 Subject: [PATCH 11/21] Tweak stories --- docs/vanilla.md | 2 +- src/stories/createMakeCodeRenderBlocks.stories.tsx | 8 ++++++-- src/stories/makeCodeFrameDriver.stories.tsx | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/vanilla.md b/docs/vanilla.md index 3a17a24..15cc66d 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -8,7 +8,7 @@ title: VanillaJS Usage ## 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/vanilla/examples.ts)). The function will return a {@link vanilla.RenderBlocksResponse | RenderBlocksResponse}. +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"; diff --git a/src/stories/createMakeCodeRenderBlocks.stories.tsx b/src/stories/createMakeCodeRenderBlocks.stories.tsx index d55ff57..4e76828 100644 --- a/src/stories/createMakeCodeRenderBlocks.stories.tsx +++ b/src/stories/createMakeCodeRenderBlocks.stories.tsx @@ -21,7 +21,7 @@ interface StoryArgs { } const meta: Meta = { - title: 'createMakeCodeRenderBlocks', + title: 'stories/createMakeCodeRenderBlocks', }; export default meta; @@ -49,7 +49,11 @@ const renderBlocks = (args: StoryArgs) => { }); }; waitForElementLoaded(); - return
; + return ( +
+

Loading...

+
+ ); }; export const Simple: Story = { diff --git a/src/stories/makeCodeFrameDriver.stories.tsx b/src/stories/makeCodeFrameDriver.stories.tsx index dd2a43c..040fb43 100644 --- a/src/stories/makeCodeFrameDriver.stories.tsx +++ b/src/stories/makeCodeFrameDriver.stories.tsx @@ -19,7 +19,7 @@ interface StoryArgs { } const meta: Meta = { - title: 'makeCodeFrameDriver', + title: 'stories/makeCodeFrameDriver', }; export default meta; @@ -272,7 +272,9 @@ const renderEditor = (args: StoryArgs) => { width: '100%', height: 700, }} - /> + > +

Loading...

+
); }; From 896c994e21007c8f4d6ae3576bacb9a92335707b Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 10:53:08 +0000 Subject: [PATCH 12/21] Organise stories sidebar --- src/stories/MakeCodeBlocksRendering.stories.tsx | 1 + src/stories/MakeCodeFrame.stories.tsx | 1 + src/stories/createMakeCodeRenderBlocks.stories.tsx | 2 +- src/stories/makeCodeFrameDriver.stories.tsx | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stories/MakeCodeBlocksRendering.stories.tsx b/src/stories/MakeCodeBlocksRendering.stories.tsx index 8e8e153..0d6c723 100644 --- a/src/stories/MakeCodeBlocksRendering.stories.tsx +++ b/src/stories/MakeCodeBlocksRendering.stories.tsx @@ -18,6 +18,7 @@ import { MakeCodeRenderBlocksOptions } from '../vanilla/makecode-render-blocks.j import { BlockLayout, Project } from '../vanilla/pxt.js'; const meta: Meta = { + title: 'stories/React/MakeCodeBlocksRendering', component: MakeCodeRenderBlocksProvider, argTypes: { options: { diff --git a/src/stories/MakeCodeFrame.stories.tsx b/src/stories/MakeCodeFrame.stories.tsx index 8ca6560..c1280ee 100644 --- a/src/stories/MakeCodeFrame.stories.tsx +++ b/src/stories/MakeCodeFrame.stories.tsx @@ -11,6 +11,7 @@ import { controllerId } from './config.js'; import StoryWrapper from './StoryWrapper.js'; const meta: Meta = { + title: 'stories/React/MakeCodeFrame', component: MakeCodeFrame, argTypes: { version: { diff --git a/src/stories/createMakeCodeRenderBlocks.stories.tsx b/src/stories/createMakeCodeRenderBlocks.stories.tsx index 4e76828..c96a807 100644 --- a/src/stories/createMakeCodeRenderBlocks.stories.tsx +++ b/src/stories/createMakeCodeRenderBlocks.stories.tsx @@ -21,7 +21,7 @@ interface StoryArgs { } const meta: Meta = { - title: 'stories/createMakeCodeRenderBlocks', + title: 'stories/VanillaJS/createMakeCodeRenderBlocks', }; export default meta; diff --git a/src/stories/makeCodeFrameDriver.stories.tsx b/src/stories/makeCodeFrameDriver.stories.tsx index 040fb43..ae9b68b 100644 --- a/src/stories/makeCodeFrameDriver.stories.tsx +++ b/src/stories/makeCodeFrameDriver.stories.tsx @@ -19,7 +19,7 @@ interface StoryArgs { } const meta: Meta = { - title: 'stories/makeCodeFrameDriver', + title: 'stories/VanillaJS/makeCodeFrameDriver', }; export default meta; From 15d1ae35d619da0c14441bf4f8713b8bf7aef681 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 10:54:45 +0000 Subject: [PATCH 13/21] Tweak license --- LICENSE.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 200b8ae..0965380 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,8 +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 From 71b9bf472d82652dffc54ad1b5c7e6f93a175892 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 11:00:48 +0000 Subject: [PATCH 14/21] Tweak vanilla embed MakeCode doc --- docs/vanilla.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/vanilla.md b/docs/vanilla.md index 15cc66d..f5d545d 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -15,7 +15,7 @@ import { createMakeCodeRenderBlocks } from "@microbit/makecode-embed/vanilla"; const renderer = createMakeCodeRenderBlocks({}); renderer.initialize(); -const result = await renderer.renderBlocks({ code: defaultMakeCodeProject }); +const result = await renderer.renderBlocks({ code: makeCodeProject }); document.querySelector("#app")!.innerHTML = `
@@ -28,7 +28,7 @@ For more examples, take a look at the [MakeCode blocks rendering demo source cod ## Embed MakeCode editor -Use {@link vanilla.MakeCodeFrameDriver | MakeCodeFrameDriver} class to create a driverRef for an iframe element. +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 { @@ -42,10 +42,10 @@ const iframe = document.createElement("iframe"); iframe.allow = "usb; autoplay; camera; microphone;"; iframe.src = createMakeCodeURL( "https://makecode.microbit.org", - undefined, - undefined, - 1, - undefined + undefined, // Version. + undefined, // Language. + 1, // Controller. + undefined // Query params. ); iframe.width = "100%"; iframe.height = "100%"; From 1e829c7a9783eb7fbcbaddc4b89481adc68ee4f4 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 11:38:47 +0000 Subject: [PATCH 15/21] Refactor --- src/stories/HtmlToReactWrapper.tsx | 31 +++++++++++++ src/stories/StoryWrapper.tsx | 3 +- .../createMakeCodeRenderBlocks.stories.tsx | 39 +++++++--------- src/stories/makeCodeFrameDriver.stories.tsx | 44 +++++-------------- 4 files changed, 61 insertions(+), 56 deletions(-) create mode 100644 src/stories/HtmlToReactWrapper.tsx diff --git a/src/stories/HtmlToReactWrapper.tsx b/src/stories/HtmlToReactWrapper.tsx new file mode 100644 index 0000000..4d6b171 --- /dev/null +++ b/src/stories/HtmlToReactWrapper.tsx @@ -0,0 +1,31 @@ +import { ReactNode } from 'react'; +import StoryWrapper from './StoryWrapper.js'; + +interface HtmlToReactWrapperProps { + htmlEl: HTMLElement; + children?: ReactNode; +} + +export const HtmlToReactWrapper = ({ + htmlEl, + children, +}: HtmlToReactWrapperProps) => { + const elementId = 'story-wrapper'; + const waitForElementLoaded = () => { + const targetEl = document.getElementById(elementId); + if (!targetEl) { + window.setTimeout(waitForElementLoaded, 500); + return; + } + targetEl.replaceChildren(htmlEl); + }; + waitForElementLoaded(); + return ( + <> + {children} + +

Loading...

+
+ + ); +}; diff --git a/src/stories/StoryWrapper.tsx b/src/stories/StoryWrapper.tsx index 489698e..728832b 100644 --- a/src/stories/StoryWrapper.tsx +++ b/src/stories/StoryWrapper.tsx @@ -1,7 +1,8 @@ import { ReactNode } from 'react'; -const StoryWrapper = (props: { children: ReactNode }) => ( +const StoryWrapper = (props: { children: ReactNode; id?: string }) => (
; -const renderBlocks = (args: StoryArgs) => { - const elementId = 'story-wrapper'; +const createMakeCodeBlockHTMLElement = (args: StoryArgs): HTMLElement => { const renderer = createMakeCodeRenderBlocks(args.options ?? {}); renderer.initialize(); - const waitForElementLoaded = () => { - const targetEl = document.getElementById(elementId); - if (!targetEl) { - window.setTimeout(waitForElementLoaded, 500); - return; + const div = document.createElement('div'); + renderer.renderBlocks({ code: args.project }).then((r) => { + if (r.svg) { + div.innerHTML = ` +
+ ${r.svg} +
+ `; } - renderer.renderBlocks({ code: args.project }).then((r) => { - if (r.svg) { - targetEl.innerHTML = ` -
- ${r.svg} -
- `; - } - }); - }; - waitForElementLoaded(); - return ( -
-

Loading...

-
- ); + }); + return div; }; +const renderBlocks = (args: StoryArgs) => ( + +); + export const Simple: Story = { render: renderBlocks, args: { project: project }, diff --git a/src/stories/makeCodeFrameDriver.stories.tsx b/src/stories/makeCodeFrameDriver.stories.tsx index ae9b68b..28a38da 100644 --- a/src/stories/makeCodeFrameDriver.stories.tsx +++ b/src/stories/makeCodeFrameDriver.stories.tsx @@ -6,6 +6,7 @@ import { } from '../vanilla/makecode-frame-driver.js'; import { Project } from '../vanilla/pxt.js'; import { defaultMakeCodeProject } from '../vanilla/examples.js'; +import { HtmlToReactWrapper } from './HtmlToReactWrapper.js'; interface StoryArgs { options?: { @@ -26,9 +27,15 @@ export default meta; type Story = StoryObj; -const renderEditor = (args: StoryArgs) => { - const elementId = 'story-wrapper'; +const toolbarStyles = { + fontFamily: 'sans-serif', + display: 'flex', + flexWrap: 'wrap', + gap: '5px', + margin: '10px 0', +} as const; +const renderEditor = (args: StoryArgs) => { // Create an iframe element. const iframe = document.createElement('iframe'); iframe.allow = 'usb; autoplay; camera; microphone;'; @@ -43,13 +50,6 @@ const renderEditor = (args: StoryArgs) => { iframe.height = '100%'; const savedProjects: Map = new Map(); - const toolbarStyles = { - fontFamily: 'sans-serif', - display: 'flex', - flexWrap: 'wrap', - gap: '5px', - margin: '10px 0', - } as const; // Create and initialise an instance of MakeCodeFrameDriver. const driverRef = new MakeCodeFrameDriver( @@ -71,19 +71,10 @@ const renderEditor = (args: StoryArgs) => { () => iframe ); - const waitForElementLoaded = () => { - const targetEl = document.getElementById(elementId); - if (!targetEl) { - window.setTimeout(waitForElementLoaded, 500); - return; - } - targetEl.replaceChildren(iframe); - driverRef.initialize(); - }; - waitForElementLoaded(); + driverRef.initialize(); return ( - <> +
-
-

Loading...

-
- +
); }; From a8cc1b9cffdba4c9869383520830ecd16e4d5d72 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 3 Feb 2025 11:45:33 +0000 Subject: [PATCH 16/21] Update React editor example --- docs/react.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/react.md b/docs/react.md index 0ccdafb..c3ced27 100644 --- a/docs/react.md +++ b/docs/react.md @@ -34,18 +34,12 @@ import { MakeCodeFrame } from '@microbit/makecode-embed/react'; ref={ref} controller={1} controllerId={controllerId} - initialProjects={initialProjects} - 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)} + initialProjects={[savedProject]} onWorkspaceSave={(e) => { - savedProjects.current?.set(e.project!.header!.id, e.project); - console.log(savedProjects.current); + // Set project as project changes in the editor. + setSavedProject(e.project); }} - onTutorialEvent={(e) => console.log('tutorialEvent', e)} -/> +/>; ``` For more examples, take a look at the [MakeCode frame demo source code](../src/stories/MakeCodeFrame.stories.tsx). From e70ca945cdce2b0b28e21b67e1615470c0577244 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Mon, 3 Feb 2025 13:12:03 +0000 Subject: [PATCH 17/21] Switch to refs, share toolbar --- src/stories/HtmlToReactWrapper.tsx | 31 -- src/stories/MakeCodeFrame.stories.tsx | 198 +------------ src/stories/MakeCodeToolbar.tsx | 213 ++++++++++++++ .../createMakeCodeRenderBlocks.stories.tsx | 40 +-- src/stories/makeCodeFrameDriver.stories.tsx | 264 ++++-------------- 5 files changed, 285 insertions(+), 461 deletions(-) delete mode 100644 src/stories/HtmlToReactWrapper.tsx create mode 100644 src/stories/MakeCodeToolbar.tsx diff --git a/src/stories/HtmlToReactWrapper.tsx b/src/stories/HtmlToReactWrapper.tsx deleted file mode 100644 index 4d6b171..0000000 --- a/src/stories/HtmlToReactWrapper.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { ReactNode } from 'react'; -import StoryWrapper from './StoryWrapper.js'; - -interface HtmlToReactWrapperProps { - htmlEl: HTMLElement; - children?: ReactNode; -} - -export const HtmlToReactWrapper = ({ - htmlEl, - children, -}: HtmlToReactWrapperProps) => { - const elementId = 'story-wrapper'; - const waitForElementLoaded = () => { - const targetEl = document.getElementById(elementId); - if (!targetEl) { - window.setTimeout(waitForElementLoaded, 500); - return; - } - targetEl.replaceChildren(htmlEl); - }; - waitForElementLoaded(); - return ( - <> - {children} - -

Loading...

-
- - ); -}; diff --git a/src/stories/MakeCodeFrame.stories.tsx b/src/stories/MakeCodeFrame.stories.tsx index c1280ee..44360b9 100644 --- a/src/stories/MakeCodeFrame.stories.tsx +++ b/src/stories/MakeCodeFrame.stories.tsx @@ -9,6 +9,7 @@ import { import { Project } from '../vanilla/pxt.js'; import { controllerId } from './config.js'; import StoryWrapper from './StoryWrapper.js'; +import MakeCodeToolbar from './MakeCodeToolbar.js'; const meta: Meta = { title: 'stories/React/MakeCodeFrame', @@ -26,16 +27,6 @@ const meta: Meta = { 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 ) => { @@ -50,192 +41,7 @@ const MakeCodeEditorWithControls = ( }, []); return ( <> -
-
- - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - -
-
- - - - -
-
+ ; + savedProjects: MutableRefObject>; +}) => { + return ( +
+
+ + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + +
+
+ + + + +
+
+ ); +}; + +export default MakeCodeToolbar; diff --git a/src/stories/createMakeCodeRenderBlocks.stories.tsx b/src/stories/createMakeCodeRenderBlocks.stories.tsx index 1217edd..513266e 100644 --- a/src/stories/createMakeCodeRenderBlocks.stories.tsx +++ b/src/stories/createMakeCodeRenderBlocks.stories.tsx @@ -14,7 +14,7 @@ import { projectWithTwoExtensions, } from './fixtures.js'; import { Project } from '../vanilla/pxt.js'; -import { HtmlToReactWrapper } from './HtmlToReactWrapper.js'; +import StoryWrapper from './StoryWrapper.js'; interface StoryArgs { options: MakeCodeRenderBlocksOptions | undefined; @@ -29,26 +29,30 @@ export default meta; type Story = StoryObj; -const createMakeCodeBlockHTMLElement = (args: StoryArgs): HTMLElement => { - const renderer = createMakeCodeRenderBlocks(args.options ?? {}); - renderer.initialize(); - const div = document.createElement('div'); - renderer.renderBlocks({ code: args.project }).then((r) => { - if (r.svg) { - div.innerHTML = ` -
- ${r.svg} -
- `; +const renderBlocks = (args: StoryArgs) => { + const cbRef = (e: HTMLElement | null) => { + if (!e) { + return; } - }); - return div; + const renderer = createMakeCodeRenderBlocks(args.options ?? {}); + renderer.initialize(); + renderer.renderBlocks({ code: args.project }).then((r) => { + if (r.svg) { + e.innerHTML = ` +
+ ${r.svg} +
+ `; + } + }); + }; + return ( + +
Loading...
+
+ ); }; -const renderBlocks = (args: StoryArgs) => ( - -); - export const Simple: Story = { render: renderBlocks, args: { project: project }, diff --git a/src/stories/makeCodeFrameDriver.stories.tsx b/src/stories/makeCodeFrameDriver.stories.tsx index 28a38da..fc070f8 100644 --- a/src/stories/makeCodeFrameDriver.stories.tsx +++ b/src/stories/makeCodeFrameDriver.stories.tsx @@ -1,12 +1,14 @@ 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 { defaultMakeCodeProject } from '../vanilla/examples.js'; -import { HtmlToReactWrapper } from './HtmlToReactWrapper.js'; +import MakeCodeToolbar from './MakeCodeToolbar.js'; +import StoryWrapper from './StoryWrapper.js'; interface StoryArgs { options?: { @@ -36,226 +38,56 @@ const toolbarStyles = { } as const; const renderEditor = (args: StoryArgs) => { - // 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%'; + 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(); + const savedProjects: Map = new Map(); - // Create and initialise an instance of MakeCodeFrameDriver. - const driverRef = 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); + // 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 ?? {}), }, - onTutorialEvent: (e) => console.log('tutorialEvent', e), - ...(args.callbacks ?? {}), - }, - () => iframe - ); + () => iframe + ); - driverRef.initialize(); + ref.current.initialize(); + }; return ( - -
-
- - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
- - -
-
- - - - -
-
-
+ + +
+ ); }; From 3e19e4654d3b1c00d9308fa37f6da80feef766a7 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Mon, 3 Feb 2025 13:22:41 +0000 Subject: [PATCH 18/21] Fix links, simplify examples --- docs/react.md | 7 ++++--- docs/vanilla.md | 12 ++++-------- .../MakeCodeBlocksRendering.stories.tsx | 10 +++++----- .../{ => react}/MakeCodeFrame.stories.tsx | 14 +++++++------- .../makecode-frame-driver.stories.tsx} | 18 +++++------------- .../makecode-render-blocks.stories.tsx} | 8 ++++---- 6 files changed, 29 insertions(+), 40 deletions(-) rename src/stories/{ => react}/MakeCodeBlocksRendering.stories.tsx (96%) rename src/stories/{ => react}/MakeCodeFrame.stories.tsx (88%) rename src/stories/{makeCodeFrameDriver.stories.tsx => vanilla/makecode-frame-driver.stories.tsx} (88%) rename src/stories/{createMakeCodeRenderBlocks.stories.tsx => vanilla/makecode-render-blocks.stories.tsx} (91%) diff --git a/docs/react.md b/docs/react.md index c3ced27..f9d5426 100644 --- a/docs/react.md +++ b/docs/react.md @@ -21,7 +21,7 @@ import { ; ``` -For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/MakeCodeBlocksRendering.stories.tsx). +For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/react/MakeCodeBlocksRendering.stories.tsx). ## Embed MakeCode editor @@ -33,8 +33,9 @@ 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); @@ -42,4 +43,4 @@ import { MakeCodeFrame } from '@microbit/makecode-embed/react'; />; ``` -For more examples, take a look at the [MakeCode frame demo source code](../src/stories/MakeCodeFrame.stories.tsx). +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 index f5d545d..9374c89 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -24,7 +24,7 @@ document.querySelector("#app")!.innerHTML = ` `; ``` -For more examples, take a look at the [MakeCode blocks rendering demo source code](../src/stories/createMakeCodeRenderBlocks.stories.tsx). +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 @@ -55,20 +55,16 @@ 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("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), + onEditorContentLoaded: (e) => console.log("MakeCode is now ready"), onWorkspaceSave: (e) => { console.log(e.project!.header!.id, e.project); }, - onTutorialEvent: (e) => console.log("tutorialEvent", e), }, () => iframe ); driverRef.initialize(); ``` -For more examples, take a look at the [MakeCode frame demo source code](../src/stories/makeCodeFrameDriver.stories.tsx). +For more examples, take a look at the [MakeCode frame demo source code](../src/stories/vanilla/makecode-frame-driver.stories.tsx). diff --git a/src/stories/MakeCodeBlocksRendering.stories.tsx b/src/stories/react/MakeCodeBlocksRendering.stories.tsx similarity index 96% rename from src/stories/MakeCodeBlocksRendering.stories.tsx rename to src/stories/react/MakeCodeBlocksRendering.stories.tsx index 0d6c723..069e5d4 100644 --- a/src/stories/MakeCodeBlocksRendering.stories.tsx +++ b/src/stories/react/MakeCodeBlocksRendering.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { ReactNode, useState } from 'react'; -import MakeCodeBlocksRendering from '../react/MakeCodeBlocksRendering.js'; +import MakeCodeBlocksRendering from '../../react/MakeCodeBlocksRendering.js'; import { initialProject, project, @@ -12,10 +12,10 @@ import { 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'; +import { MakeCodeRenderBlocksProvider } from '../../react/MakeCodeRenderBlocksProvider.js'; +import { MakeCodeRenderBlocksOptions } from '../../vanilla/makecode-render-blocks.js'; +import { BlockLayout, Project } from '../../vanilla/pxt.js'; const meta: Meta = { title: 'stories/React/MakeCodeBlocksRendering', diff --git a/src/stories/MakeCodeFrame.stories.tsx b/src/stories/react/MakeCodeFrame.stories.tsx similarity index 88% rename from src/stories/MakeCodeFrame.stories.tsx rename to src/stories/react/MakeCodeFrame.stories.tsx index 44360b9..627337c 100644 --- a/src/stories/MakeCodeFrame.stories.tsx +++ b/src/stories/react/MakeCodeFrame.stories.tsx @@ -1,15 +1,15 @@ 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 { 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'; -import MakeCodeToolbar from './MakeCodeToolbar.js'; +} from '../../react/MakeCodeFrame.js'; +import { Project } from '../../vanilla/pxt.js'; +import { controllerId } from '../config.js'; +import StoryWrapper from '../StoryWrapper.js'; +import MakeCodeToolbar from '../MakeCodeToolbar.js'; const meta: Meta = { title: 'stories/React/MakeCodeFrame', diff --git a/src/stories/makeCodeFrameDriver.stories.tsx b/src/stories/vanilla/makecode-frame-driver.stories.tsx similarity index 88% rename from src/stories/makeCodeFrameDriver.stories.tsx rename to src/stories/vanilla/makecode-frame-driver.stories.tsx index fc070f8..e1b4b69 100644 --- a/src/stories/makeCodeFrameDriver.stories.tsx +++ b/src/stories/vanilla/makecode-frame-driver.stories.tsx @@ -1,14 +1,14 @@ import { Meta, StoryObj } from '@storybook/react'; import { useRef } from 'react'; -import { defaultMakeCodeProject } from '../vanilla/examples.js'; +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'; +} 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?: { @@ -29,14 +29,6 @@ export default meta; type Story = StoryObj; -const toolbarStyles = { - fontFamily: 'sans-serif', - display: 'flex', - flexWrap: 'wrap', - gap: '5px', - margin: '10px 0', -} as const; - const renderEditor = (args: StoryArgs) => { const savedProjects = useRef>(new Map()); const ref = useRef(null); diff --git a/src/stories/createMakeCodeRenderBlocks.stories.tsx b/src/stories/vanilla/makecode-render-blocks.stories.tsx similarity index 91% rename from src/stories/createMakeCodeRenderBlocks.stories.tsx rename to src/stories/vanilla/makecode-render-blocks.stories.tsx index 513266e..ef0ba12 100644 --- a/src/stories/createMakeCodeRenderBlocks.stories.tsx +++ b/src/stories/vanilla/makecode-render-blocks.stories.tsx @@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { createMakeCodeRenderBlocks, MakeCodeRenderBlocksOptions, -} from '../vanilla/makecode-render-blocks.js'; +} from '../../vanilla/makecode-render-blocks.js'; import { initialProject, project, @@ -12,9 +12,9 @@ import { projectWithLayout, projectWithMelody, projectWithTwoExtensions, -} from './fixtures.js'; -import { Project } from '../vanilla/pxt.js'; -import StoryWrapper from './StoryWrapper.js'; +} from '../fixtures.js'; +import { Project } from '../../vanilla/pxt.js'; +import StoryWrapper from '../StoryWrapper.js'; interface StoryArgs { options: MakeCodeRenderBlocksOptions | undefined; From a90d06bc9760e058a8b2db45ffe8ca8f0592bb13 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Mon, 3 Feb 2025 13:45:44 +0000 Subject: [PATCH 19/21] Flatten options into props for render blocks React. --- docs/react.md | 4 +- src/react/MakeCodeRenderBlocksProvider.tsx | 23 +++- .../react/MakeCodeBlocksRendering.stories.tsx | 127 ++++++++---------- 3 files changed, 75 insertions(+), 79 deletions(-) diff --git a/docs/react.md b/docs/react.md index f9d5426..1705b11 100644 --- a/docs/react.md +++ b/docs/react.md @@ -16,11 +16,13 @@ import { 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 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/stories/react/MakeCodeBlocksRendering.stories.tsx b/src/stories/react/MakeCodeBlocksRendering.stories.tsx index 069e5d4..90c2a63 100644 --- a/src/stories/react/MakeCodeBlocksRendering.stories.tsx +++ b/src/stories/react/MakeCodeBlocksRendering.stories.tsx @@ -1,30 +1,28 @@ 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 { 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'; const meta: Meta = { title: 'stories/React/MakeCodeBlocksRendering', component: MakeCodeRenderBlocksProvider, argTypes: { - options: { + version: { options: ['default', 'beta'], defaultValue: 'default', - name: 'version', control: { type: 'radio' }, }, }, @@ -47,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 ( - - + + @@ -72,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 ( - - + + @@ -87,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 ( - - + + @@ -102,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 ( - - + + @@ -117,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 ( - - + + @@ -132,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 ( - - + + @@ -147,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 ( - - + + @@ -162,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 ( - - + + @@ -177,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 ( - - + + @@ -193,7 +175,7 @@ export const Error: Story = { render: () => { return ( - + @@ -204,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 ( - - + + @@ -219,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 ( - - + + @@ -234,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 ( - - + + @@ -249,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 ( - +
- +
@@ -280,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 ( - - + + Date: Mon, 3 Feb 2025 14:04:04 +0000 Subject: [PATCH 20/21] Remove not needed props/changes --- docs/vanilla.md | 2 +- src/stories/StoryWrapper.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/vanilla.md b/docs/vanilla.md index 9374c89..b1a24d8 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -13,7 +13,7 @@ Use {@link vanilla.createMakeCodeRenderBlocks | createMakeCodeRenderBlocks} to c ```js import { createMakeCodeRenderBlocks } from "@microbit/makecode-embed/vanilla"; -const renderer = createMakeCodeRenderBlocks({}); +const renderer = createMakeCodeRenderBlocks(); renderer.initialize(); const result = await renderer.renderBlocks({ code: makeCodeProject }); diff --git a/src/stories/StoryWrapper.tsx b/src/stories/StoryWrapper.tsx index 728832b..489698e 100644 --- a/src/stories/StoryWrapper.tsx +++ b/src/stories/StoryWrapper.tsx @@ -1,8 +1,7 @@ import { ReactNode } from 'react'; -const StoryWrapper = (props: { children: ReactNode; id?: string }) => ( +const StoryWrapper = (props: { children: ReactNode }) => (
Date: Mon, 3 Feb 2025 14:13:04 +0000 Subject: [PATCH 21/21] Tweak removal of arg --- docs/vanilla.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/vanilla.md b/docs/vanilla.md index b1a24d8..9374c89 100644 --- a/docs/vanilla.md +++ b/docs/vanilla.md @@ -13,7 +13,7 @@ Use {@link vanilla.createMakeCodeRenderBlocks | createMakeCodeRenderBlocks} to c ```js import { createMakeCodeRenderBlocks } from "@microbit/makecode-embed/vanilla"; -const renderer = createMakeCodeRenderBlocks(); +const renderer = createMakeCodeRenderBlocks({}); renderer.initialize(); const result = await renderer.renderBlocks({ code: makeCodeProject });