From 8c30855c09f86586a04139b58722fb6cc6dd0b2e Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 22 Apr 2024 11:33:44 -0700 Subject: [PATCH 1/7] Tutorial tool initial checkin (#9970) * init tutorial tool * More tutorial tool work and refactor iframe comms * use the same monaco editor as the main webapp * tutorial tool sign in * pr feedback --- .gitignore | 1 + cli/webapps-config.json | 6 + gulpfile.js | 8 +- pxtlib/shell.ts | 1 - pxtservices/backendRequests.ts | 64 + pxtservices/constants.ts | 6 + pxtservices/loggingService.ts | 49 + .../components/profile/SignInButton.tsx | 20 + .../components/profile/UserAvatarDropdown.tsx | 59 + react-common/styles/profile/SignInButton.less | 19 + .../styles/profile/UserAvatarDropdown.less | 25 + react-common/styles/profile/profile.less | 3 + teachertool/src/components/MakecodeFrame.tsx | 6 +- tutorialtool/.env | 3 + tutorialtool/.eslintrc.js | 6 + tutorialtool/.gitignore | 3 + tutorialtool/.prettierrc | 6 + tutorialtool/README.md | 30 + tutorialtool/config-overrides.js | 41 + tutorialtool/package-lock.json | 31609 ++++++++++++++++ tutorialtool/package.json | 46 + tutorialtool/public/index.html | 101 + tutorialtool/src/App.tsx | 48 + tutorialtool/src/components/ActionBar.tsx | 26 + tutorialtool/src/components/EditorPanel.tsx | 27 + tutorialtool/src/components/HeaderBar.tsx | 135 + tutorialtool/src/components/MakeCodeFrame.tsx | 35 + tutorialtool/src/components/MonacoEditor.tsx | 43 + tutorialtool/src/components/SignInModal.tsx | 28 + tutorialtool/src/components/SplitPane.tsx | 118 + .../components/styling/ActionBar.module.scss | 3 + .../styling/EditorPanel.module.scss | 3 + .../components/styling/HeaderBar.module.scss | 44 + .../styling/MakeCodeFrame.module.scss | 7 + .../styling/MonacoEditor.module.scss | 5 + .../components/styling/SplitPane.module.scss | 62 + tutorialtool/src/constants.ts | 13 + tutorialtool/src/global.scss | 166 + tutorialtool/src/hooks/usePromise.ts | 11 + tutorialtool/src/hooks/useResizeObserver.ts | 36 + tutorialtool/src/index.tsx | 56 + tutorialtool/src/react-app-env.d.ts | 1 + tutorialtool/src/services/authClient.ts | 92 + .../src/services/makecodeEditorService.ts | 43 + tutorialtool/src/state/actions.ts | 91 + tutorialtool/src/state/appStateContext.tsx | 62 + tutorialtool/src/state/index.ts | 3 + tutorialtool/src/state/reducer.ts | 45 + tutorialtool/src/state/state.ts | 12 + .../transforms/previewCurrentTutorialAsync.ts | 10 + .../setCurrentTutorialMarkdownAsync.ts | 8 + .../src/transforms/setUserProfileAsync.ts | 17 + tutorialtool/src/types/errorCode.ts | 6 + tutorialtool/src/types/modalOptions.ts | 5 + tutorialtool/src/utils/index.ts | 11 + tutorialtool/src/utils/monaco.ts | 71 + tutorialtool/tsconfig.json | 24 + tutorialtool/tsconfig.paths.json | 10 + 58 files changed, 33485 insertions(+), 4 deletions(-) create mode 100644 pxtservices/backendRequests.ts create mode 100644 pxtservices/constants.ts create mode 100644 pxtservices/loggingService.ts create mode 100644 react-common/components/profile/SignInButton.tsx create mode 100644 react-common/components/profile/UserAvatarDropdown.tsx create mode 100644 react-common/styles/profile/SignInButton.less create mode 100644 react-common/styles/profile/UserAvatarDropdown.less create mode 100644 tutorialtool/.env create mode 100644 tutorialtool/.eslintrc.js create mode 100644 tutorialtool/.gitignore create mode 100644 tutorialtool/.prettierrc create mode 100644 tutorialtool/README.md create mode 100644 tutorialtool/config-overrides.js create mode 100644 tutorialtool/package-lock.json create mode 100644 tutorialtool/package.json create mode 100644 tutorialtool/public/index.html create mode 100644 tutorialtool/src/App.tsx create mode 100644 tutorialtool/src/components/ActionBar.tsx create mode 100644 tutorialtool/src/components/EditorPanel.tsx create mode 100644 tutorialtool/src/components/HeaderBar.tsx create mode 100644 tutorialtool/src/components/MakeCodeFrame.tsx create mode 100644 tutorialtool/src/components/MonacoEditor.tsx create mode 100644 tutorialtool/src/components/SignInModal.tsx create mode 100644 tutorialtool/src/components/SplitPane.tsx create mode 100644 tutorialtool/src/components/styling/ActionBar.module.scss create mode 100644 tutorialtool/src/components/styling/EditorPanel.module.scss create mode 100644 tutorialtool/src/components/styling/HeaderBar.module.scss create mode 100644 tutorialtool/src/components/styling/MakeCodeFrame.module.scss create mode 100644 tutorialtool/src/components/styling/MonacoEditor.module.scss create mode 100644 tutorialtool/src/components/styling/SplitPane.module.scss create mode 100644 tutorialtool/src/constants.ts create mode 100644 tutorialtool/src/global.scss create mode 100644 tutorialtool/src/hooks/usePromise.ts create mode 100644 tutorialtool/src/hooks/useResizeObserver.ts create mode 100644 tutorialtool/src/index.tsx create mode 100644 tutorialtool/src/react-app-env.d.ts create mode 100644 tutorialtool/src/services/authClient.ts create mode 100644 tutorialtool/src/services/makecodeEditorService.ts create mode 100644 tutorialtool/src/state/actions.ts create mode 100644 tutorialtool/src/state/appStateContext.tsx create mode 100644 tutorialtool/src/state/index.ts create mode 100644 tutorialtool/src/state/reducer.ts create mode 100644 tutorialtool/src/state/state.ts create mode 100644 tutorialtool/src/transforms/previewCurrentTutorialAsync.ts create mode 100644 tutorialtool/src/transforms/setCurrentTutorialMarkdownAsync.ts create mode 100644 tutorialtool/src/transforms/setUserProfileAsync.ts create mode 100644 tutorialtool/src/types/errorCode.ts create mode 100644 tutorialtool/src/types/modalOptions.ts create mode 100644 tutorialtool/src/utils/index.ts create mode 100644 tutorialtool/src/utils/monaco.ts create mode 100644 tutorialtool/tsconfig.json create mode 100644 tutorialtool/tsconfig.paths.json diff --git a/.gitignore b/.gitignore index 0a75f3116eab..449b9390936d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ webapp/public/authcode.html webapp/public/multiplayer.html webapp/public/kiosk.html webapp/public/teachertool.html +webapp/public/tutorialtool.html localtypings/blockly.d.ts node_modules *.sw? diff --git a/cli/webapps-config.json b/cli/webapps-config.json index 048acb7f28e3..90d261777bfa 100644 --- a/cli/webapps-config.json +++ b/cli/webapps-config.json @@ -12,6 +12,12 @@ "localServeWebConfigUrl": false, "localServeEndpoint": "eval" }, + { + "name": "tutorialtool", + "buildCss": false, + "localServeWebConfigUrl": false, + "localServeEndpoint": "tt" + }, { "name": "skillmap", "buildCss": true, diff --git a/gulpfile.js b/gulpfile.js index 72b6902f2fd7..1abd2b1315cb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -590,6 +590,12 @@ const kiosk = createWebappTasks("kiosk"); const teacherTool = createWebappTasks("teachertool"); +/******************************************************** + Tutorial Tool +*********************************************************/ + +const tutorialTool = createWebappTasks("tutorialtool"); + /******************************************************** Webapp build wrappers *********************************************************/ @@ -604,7 +610,7 @@ const maybeUpdateWebappStrings = () => { const maybeBuildWebapps = () => { if (!shouldBuildWebapps()) return noop; - return gulp.parallel(skillmap, authcode, multiplayer, kiosk, teacherTool); + return gulp.parallel(skillmap, authcode, multiplayer, kiosk, teacherTool, tutorialTool); } /******************************************************** diff --git a/pxtlib/shell.ts b/pxtlib/shell.ts index ad9c09f86c27..4b48475255b7 100644 --- a/pxtlib/shell.ts +++ b/pxtlib/shell.ts @@ -95,5 +95,4 @@ namespace pxt.shell { export function setToolboxAnimation(): void { pxt.storage.setLocal("toolboxanimation", "1"); } - } \ No newline at end of file diff --git a/pxtservices/backendRequests.ts b/pxtservices/backendRequests.ts new file mode 100644 index 000000000000..325107933cea --- /dev/null +++ b/pxtservices/backendRequests.ts @@ -0,0 +1,64 @@ +import { ErrorCode } from "./constants"; +import { logError } from "./loggingService"; + +export async function fetchJsonDocAsync(url: string): Promise { + try { + const response = await fetch(url, { + cache: "no-cache", + }); + if (!response.ok) { + throw new Error("Unable to fetch the json file"); + } else { + const json = await response.json(); + return json; + } + } catch (e) { + logError(ErrorCode.fetchJsonDocAsync, e); + } + + return undefined; +} + +export async function getProjectTextAsync(projectId: string): Promise { + try { + const projectTextUrl = `${pxt.Cloud.apiRoot}/${projectId}/text`; + const response = await fetch(projectTextUrl); + if (!response.ok) { + throw new Error("Unable to fetch the project details"); + } else { + const projectText = await response.json(); + return projectText; + } + } catch (e) { + logError(ErrorCode.getProjectTextAsync, e); + } + + return undefined; +} + +export async function getProjectMetaAsync(projectId: string): Promise { + try { + const projectMetaUrl = `${pxt.Cloud.apiRoot}/${projectId}`; + const response = await fetch(projectMetaUrl); + if (!response.ok) { + throw new Error("Unable to fetch the project meta information"); + } else { + const projectMeta = await response.json(); + return projectMeta; + } + } catch (e) { + logError(ErrorCode.getProjectMetaAsync, e); + } + + return undefined; +} + +export async function downloadTargetConfigAsync(): Promise { + try { + return await pxt.targetConfigAsync(); + } catch (e) { + logError(ErrorCode.downloadTargetConfigAsync, e); + } + + return undefined; +} diff --git a/pxtservices/constants.ts b/pxtservices/constants.ts new file mode 100644 index 000000000000..5ee87bc658d7 --- /dev/null +++ b/pxtservices/constants.ts @@ -0,0 +1,6 @@ +export enum ErrorCode { + downloadTargetConfigAsync = "downloadTargetConfigAsync", + fetchJsonDocAsync = "fetchJsonDocAsync", + getProjectTextAsync = "getProjectTextAsync", + getProjectMetaAsync = "getProjectMetaAsync", +} \ No newline at end of file diff --git a/pxtservices/loggingService.ts b/pxtservices/loggingService.ts new file mode 100644 index 000000000000..0ff438ce4aeb --- /dev/null +++ b/pxtservices/loggingService.ts @@ -0,0 +1,49 @@ +let tickEvent = "pxt.error"; + +const timestamp = () => { + const time = new Date(); + const hours = padTime(time.getHours()); + const minutes = padTime(time.getMinutes()); + const seconds = padTime(time.getSeconds()); + + return `[${hours}:${minutes}:${seconds}]`; +}; + +const padTime = (time: number) => ("0" + time).slice(-2); + +export const logError = (errorCode: string, message?: any, data: pxt.Map = {}) => { + let dataObj = { ...data }; + if (message) { + if (typeof message === "object") { + dataObj = { ...dataObj, ...message }; + // Look for non-enumerable properties found on Error objects + ["message", "stack", "name"].forEach(key => { + if (message[key]) { + dataObj[key] = message[key]; + } + }); + } else { + dataObj.message = message; + } + } + pxt.tickEvent(tickEvent, { + ...dataObj, + errorCode, + }); + console.error(timestamp(), errorCode, dataObj); +}; + +export const logInfo = (message: any) => { + console.log(timestamp(), message); +}; + +export const logDebug = (message: any, data?: any) => { + if (pxt.BrowserUtils.isLocalHost() || pxt.options.debug) { + console.log(timestamp(), message, data); + } +}; + + +export const setTickEvent = (event: string) => { + tickEvent = event; +} \ No newline at end of file diff --git a/react-common/components/profile/SignInButton.tsx b/react-common/components/profile/SignInButton.tsx new file mode 100644 index 000000000000..655387fff869 --- /dev/null +++ b/react-common/components/profile/SignInButton.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; +import { Button } from "../controls/Button"; +import { classList } from "../util"; + +interface IProps { + onSignInClick: () => void; + className?: string; +} + +export const SignInButton: React.FC = ({ onSignInClick, className }) => { + return ( +