Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/microsoft/pxt into thspar…
Browse files Browse the repository at this point in the history
…ks/teachertool/rework_catalog_modal
  • Loading branch information
thsparks committed Apr 24, 2024
2 parents 7981077 + 8c30855 commit e628527
Show file tree
Hide file tree
Showing 58 changed files with 33,485 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
6 changes: 6 additions & 0 deletions cli/webapps-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"localServeWebConfigUrl": false,
"localServeEndpoint": "eval"
},
{
"name": "tutorialtool",
"buildCss": false,
"localServeWebConfigUrl": false,
"localServeEndpoint": "tt"
},
{
"name": "skillmap",
"buildCss": true,
Expand Down
8 changes: 7 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,12 @@ const kiosk = createWebappTasks("kiosk");

const teacherTool = createWebappTasks("teachertool");

/********************************************************
Tutorial Tool
*********************************************************/

const tutorialTool = createWebappTasks("tutorialtool");

/********************************************************
Webapp build wrappers
*********************************************************/
Expand All @@ -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);
}

/********************************************************
Expand Down
1 change: 0 additions & 1 deletion pxtlib/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,4 @@ namespace pxt.shell {
export function setToolboxAnimation(): void {
pxt.storage.setLocal("toolboxanimation", "1");
}

}
64 changes: 64 additions & 0 deletions pxtservices/backendRequests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ErrorCode } from "./constants";
import { logError } from "./loggingService";

export async function fetchJsonDocAsync<T = any>(url: string): Promise<T | undefined> {
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<pxt.Cloud.JsonText | undefined> {
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<pxt.Cloud.JsonScript | undefined> {
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<pxt.TargetConfig | undefined> {
try {
return await pxt.targetConfigAsync();
} catch (e) {
logError(ErrorCode.downloadTargetConfigAsync, e);
}

return undefined;
}
6 changes: 6 additions & 0 deletions pxtservices/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum ErrorCode {
downloadTargetConfigAsync = "downloadTargetConfigAsync",
fetchJsonDocAsync = "fetchJsonDocAsync",
getProjectTextAsync = "getProjectTextAsync",
getProjectMetaAsync = "getProjectMetaAsync",
}
49 changes: 49 additions & 0 deletions pxtservices/loggingService.ts
Original file line number Diff line number Diff line change
@@ -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<string | number> = {}) => {
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;
}
20 changes: 20 additions & 0 deletions react-common/components/profile/SignInButton.tsx
Original file line number Diff line number Diff line change
@@ -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<IProps> = ({ onSignInClick, className }) => {
return (
<Button
className={classList(className, "sign-in-button")}
rightIcon="xicon cloud-user large"
title={lf("Sign In")}
label={lf("Sign In")}
onClick={onSignInClick}
/>
);
};
59 changes: 59 additions & 0 deletions react-common/components/profile/UserAvatarDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from "react";
import { MenuDropdown, MenuItem } from "../controls/MenuDropdown";
import { classList } from "../util";

interface IProps {
userProfile: pxt.auth.UserProfile;
title: string;

onSignOutClick?: () => void;
className?: string;
items?: MenuItem[];
}

export const UserAvatarDropdown: React.FC<IProps> = (props) => {
const { userProfile, title, items, onSignOutClick, className } = props;

const avatarUrl = userProfile?.idp?.pictureUrl ?? encodedAvatarPic(userProfile);

const avatarElem = <UserAvatar avatarPicUrl={avatarUrl} />;
const initialsElem = <UserInitials userProfile={userProfile} />;

const allItems = items ? items.slice() : [];

if (onSignOutClick) {
allItems.unshift({
id: "signout",
title: lf("Sign Out"),
label: lf("Sign Out"),
onClick: onSignOutClick
});
}

return (
<MenuDropdown
className={classList("user-avatar-dropdown", className)}
title={title}
label={avatarUrl ? avatarElem : initialsElem}
items={allItems}
/>
);
};

const UserInitials = (props: { userProfile: pxt.auth.UserProfile }) => (
<span>
<div className="user-avatar-initials" aria-hidden="true">{pxt.auth.userInitials(props.userProfile)}</div>
</span>
);

const UserAvatar = (props: { avatarPicUrl: string }) => (
<div className="user-avatar-image">
<img src={props.avatarPicUrl} alt={lf("Profile Image")} referrerPolicy="no-referrer" aria-hidden="true" />
</div>
);

function encodedAvatarPic(user: pxt.auth.UserProfile): string {
const type = user?.idp?.picture?.mimeType;
const encodedImg = user?.idp?.picture?.encoded;
return type && encodedImg ? `data:${type};base64,${encodedImg}` : "";
}
19 changes: 19 additions & 0 deletions react-common/styles/profile/SignInButton.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.common-button.sign-in-button {
padding-left: 0.75rem;
padding-right: 0.75rem;

.common-button-label {
font-family: Segoe UI, Tahoma, Geneva, Verdana;
font-weight: 500;
}
}

@media @tabletAndBelow {
.common-button.sign-in-button {
padding-left: 0.25rem;

.common-button-label {
display: none;
}
}
}
25 changes: 25 additions & 0 deletions react-common/styles/profile/UserAvatarDropdown.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.user-avatar-image {
display: flex;
align-items: center;
justify-content: center;
height: 100%;

img {
border: solid 2px @buttonMenuTextColor;
border-radius: 100%;
width: 2.5rem;
height: 2.5rem;
}
}

.user-avatar-initials {
display: flex;
align-items: center;
justify-content: center;
border: solid 2px @buttonMenuTextColor;
border-radius: 100%;
width: 2.5rem;
height: 2.5rem;

background-color: var(--pxt-headerbar-accent);
}
3 changes: 3 additions & 0 deletions react-common/styles/profile/profile.less
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@import "./SignInButton.less";
@import "./UserAvatarDropdown.less";

.user-profile {
display: flex;
flex-direction: row;
Expand Down
6 changes: 4 additions & 2 deletions teachertool/src/components/MakecodeFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference path="../../../localtypings/pxteditor.d.ts" />

import css from "./styling/MakeCodeFrame.module.scss";
import { useContext } from "react";
import { useContext, useState } from "react";
import { setEditorRef } from "../services/makecodeEditorService";
import { AppStateContext } from "../state/appStateContext";
import { getEditorUrl } from "../utils";
Expand All @@ -11,6 +11,8 @@ interface IProps {}

export const MakeCodeFrame: React.FC<IProps> = () => {
const { state: teacherTool } = useContext(AppStateContext);
const [ frameId ] = useState(pxt.Util.guidGen());


function createIFrameUrl(shareId: string): string {
const editorUrl: string = pxt.BrowserUtils.isLocalHost()
Expand All @@ -22,7 +24,7 @@ export const MakeCodeFrame: React.FC<IProps> = () => {
url = editorUrl.substr(0, editorUrl.length - 1);
}
const shareSection = shareId ? `#pub:${shareId}` : "";
url += `?controller=1&teachertool=1&readonly=1&ws=mem&nocookiebanner=1${shareSection}`;
url += `?controller=1&teachertool=1&readonly=1&ws=mem&nocookiebanner=1&frameid=${frameId}${shareSection}`;
return url;
}

Expand Down
3 changes: 3 additions & 0 deletions tutorialtool/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DISABLE_ESLINT_PLUGIN=true
GENERATE_SOURCEMAP=false
BROWSER=none
6 changes: 6 additions & 0 deletions tutorialtool/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
"parserOptions": {
"project": "tutorialtool/tsconfig.json",
},
"ignorePatterns": ["tests/**/*.spec.ts", "public/**/*", "build/**/*"]
}
3 changes: 3 additions & 0 deletions tutorialtool/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vscode
build
!package-lock.json
6 changes: 6 additions & 0 deletions tutorialtool/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"arrowParens": "avoid",
"semi": true,
"tabWidth": 4,
"printWidth":120
}
30 changes: 30 additions & 0 deletions tutorialtool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Tutorial tool

## Localhost testing

To test the Tutorial Tool locally:

1. Ensure your pxt repo is up to date and has been built recently.
2. In a command shell, in the `pxt` repo, cd into the `tutorialtool` folder and start the Tutorial Tool dev server: `npm run start`. This will *not* open a browser window.
3. In another command shell, in the target repo (e.g. `pxt-arcade` or `pxt-microbit`), start the pxt dev server: `pxt serve --rebundle --noauth`. This will open the editor webapp in a browser.
1. **Note the `--noauth` parameter.** It is important to include this option when running on localhost in order to download certain required startup files from the localhost pxt server.

Requests to the `/tt` endpoint will be routed to the Tutorial Tool dev server.

Debug and step through Tutorial Tool code using the browser dev tools (F12 to open).


## Test in staging environment

1. In the pxt repo, run `gulp` to ensure production Tutorial tool is built.
2. In a browser, go to `https://staging.pxt.io/oauth/gettoken`. This should return a url with an auth token embedded. Copy the entire url value to your clipboard.
- It should look something like `https://staging.pxt.io/?access_token=X.XXXXXXXX`
- If you get access denied, contact your manager to help you.
3. In a command shell, set environment variable `PXT_ACCESS_TOKEN` with the copied value.
4. In the same shell, in the pxt-arcade repo, run `pxt uploadtrg --rebundle`. This should return a url to your private build.
- It should look something like `https://arcade.staging.pxt.io/app/XXXXXX-XXXXX`
- Paste in a browser and append "--tutorialtool". This should take you to your Tutorial tool build in staging.

## Test in production environment

Follow the "Test in staging environment" instructions, but get your auth token from `https://makecode.com/oauth/gettoken`.
Loading

0 comments on commit e628527

Please sign in to comment.