From e6149a07409e8b9095fda5595d127733f0ddf2ab Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 24 Jan 2024 16:26:31 -0800 Subject: [PATCH] initial theming from target --- teachertool/README.md | 3 +- teachertool/public/index.html | 2 - teachertool/src/App.tsx | 36 +++-- teachertool/src/components/DebugInput.tsx | 2 +- teachertool/src/components/HeaderBar.tsx | 139 ++++++++++++------ teachertool/src/services/BackendRequests.ts | 32 ++-- teachertool/src/services/loggingService.ts | 38 +++-- teachertool/src/state/actions.ts | 22 ++- teachertool/src/teacherTool.css | 26 +--- .../transforms/loadProjectMetadataAsync.ts | 2 +- 10 files changed, 192 insertions(+), 110 deletions(-) diff --git a/teachertool/README.md b/teachertool/README.md index d5dd83224ded..215e0dcc27d4 100644 --- a/teachertool/README.md +++ b/teachertool/README.md @@ -6,7 +6,8 @@ To test the Teacher 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 `teachertool` folder and start the Teacher Tool dev server: `npm run start`. This will *not* open a browser window. -3. In another command shell, in the `pxt-arcade` repo, start the Arcade dev server: `pxt serve --rebundle`. This will open the Arcade webapp in a browser. +3. In another command shell, in the `pxt-arcade` repo, start the Arcade dev server: `pxt serve --rebundle --noauth`. This will open the Arcade 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 `/teachertool` endpoint will be routed to the Teacher Tool dev server. diff --git a/teachertool/public/index.html b/teachertool/public/index.html index de8c743e75d2..974a974f2c3d 100644 --- a/teachertool/public/index.html +++ b/teachertool/public/index.html @@ -38,7 +38,5 @@
- - diff --git a/teachertool/src/App.tsx b/teachertool/src/App.tsx index 2635ba2cc701..99f3e92135b0 100644 --- a/teachertool/src/App.tsx +++ b/teachertool/src/App.tsx @@ -11,28 +11,40 @@ import { makeNotification } from "./utils"; import DebugInput from "./components/DebugInput"; import { MakeCodeFrame } from "./components/MakecodeFrame"; import EvalResultDisplay from "./components/EvalResultDisplay"; - +import { downloadTargetConfigAsync } from "./services/backendRequests"; +import * as Actions from "./state/actions"; +import { logDebug } from "./services/loggingService"; function App() { const { state, dispatch } = useContext(AppStateContext); - const [didNotify, setDidNotify] = useState(false); + const [inited, setInited] = useState(false); const ready = usePromise(AppStateReady, false); useEffect(() => { - // Init subsystems. - NotificationService.initialize(); - }, [ready]); + if (ready && !inited) { + NotificationService.initialize(); + Promise.resolve().then(async () => { + const cfg = await downloadTargetConfigAsync(); + dispatch(Actions.setTargetConfig(cfg || {})); + pxt.BrowserUtils.initTheme(); + // TODO: Remove this. Delay app init to expose any startup race conditions. + setTimeout(() => { + // Test notification + postNotification(makeNotification("🎓", 2000)); + setInited(true); - // Test notification - useEffect(() => { - if (ready && !didNotify) { - postNotification(makeNotification("🎓", 2000)); - setDidNotify(true); + logDebug("App initialized"); + }, 10); + }); } - }, [ready]); + }, [ready, inited]); - return ( + return !inited ? ( +
+
+
+ ) : (
diff --git a/teachertool/src/components/DebugInput.tsx b/teachertool/src/components/DebugInput.tsx index 4d1dd2768bde..22d590730def 100644 --- a/teachertool/src/components/DebugInput.tsx +++ b/teachertool/src/components/DebugInput.tsx @@ -37,7 +37,7 @@ const DebugInput: React.FC = ({}) => { rows={20} onChange={setRubric} />
-
) diff --git a/teachertool/src/components/HeaderBar.tsx b/teachertool/src/components/HeaderBar.tsx index f8cb4c96ab15..56ab72ab686e 100644 --- a/teachertool/src/components/HeaderBar.tsx +++ b/teachertool/src/components/HeaderBar.tsx @@ -3,67 +3,112 @@ import * as React from "react"; import { Button } from "react-common/components/controls/Button"; import { MenuBar } from "react-common/components/controls/MenuBar"; -interface HeaderBarProps { -} +interface HeaderBarProps {} -export class HeaderBar extends React.Component { - protected reportAbuseUrl = "https://github.com/contact/report-content"; +export const HeaderBar: React.FC = () => { + const appTheme = pxt.appTarget?.appTheme; - protected getOrganizationLogo(targetTheme: pxt.AppTheme) { - const logoUrl = targetTheme.organizationWideLogo; - return
- {logoUrl - ? {lf("{0} - : {targetTheme.organization}} -
- } + const brandIconClick = () => {}; - protected getTargetLogo(targetTheme: pxt.AppTheme) { - return
- {targetTheme.useTextLogo - ? [{targetTheme.organizationText}, - {targetTheme.organizationShortText || targetTheme.organizationText}] - : (targetTheme.logo || targetTheme.portraitLogo - ? {lf("{0} - : {targetTheme.boardName}) - } -
- } + const getOrganizationLogo = () => { + return ( +
+ {appTheme.organizationWideLogo || appTheme.organizationLogo ? ( + {lf("{0} + ) : ( + {appTheme.organization} + )} +
+ ); + }; - onHomeClicked = () => { + const getTargetLogo = () => { + return ( +
+ {appTheme.useTextLogo ? ( + [ + + {appTheme.organizationText} + , + + {appTheme.organizationShortText || + appTheme.organizationText} + , + ] + ) : appTheme.logo || appTheme.portraitLogo ? ( + {lf("{0} + ) : ( + {appTheme.boardName} + )} +
+ ); + }; + + const onHomeClicked = () => { pxt.tickEvent("teacherTool.home"); // relprefix looks like "/beta---", need to chop off the hyphens and slash - let rel = pxt.webConfig?.relprefix.substr(0, pxt.webConfig.relprefix.length - 3); + let rel = pxt.webConfig?.relprefix.substr( + 0, + pxt.webConfig.relprefix.length - 3 + ); if (pxt.appTarget.appTheme.homeUrl && rel) { - if (pxt.appTarget.appTheme.homeUrl?.lastIndexOf("/") === pxt.appTarget.appTheme.homeUrl?.length - 1) { + if ( + pxt.appTarget.appTheme.homeUrl?.lastIndexOf("/") === + pxt.appTarget.appTheme.homeUrl?.length - 1 + ) { rel = rel.substr(1); } window.open(pxt.appTarget.appTheme.homeUrl + rel); - } - else { + } else { window.open(pxt.appTarget.appTheme.homeUrl); } - } + }; - render() { - const hasIdentity = pxt.auth.hasIdentity(); + return ( +
+ +
+ {getOrganizationLogo()} + {getTargetLogo()} +
- const appTheme = pxt.appTarget?.appTheme; +
- return -
- {this.getOrganizationLogo(appTheme)} - {this.getTargetLogo(appTheme)} -
- -
- -
-
- - } -} +
+
+ +
+ ); +}; -export default HeaderBar; \ No newline at end of file +export default HeaderBar; diff --git a/teachertool/src/services/BackendRequests.ts b/teachertool/src/services/BackendRequests.ts index 6cfc0390e18a..9c90f07b64ae 100644 --- a/teachertool/src/services/BackendRequests.ts +++ b/teachertool/src/services/BackendRequests.ts @@ -1,6 +1,8 @@ -export const getProjectTextAsync = async ( +import { logError } from "./loggingService"; + +export async function getProjectTextAsync( projectId: string -): Promise => { +): Promise { try { const projectTextUrl = `${pxt.Cloud.apiRoot}/${projectId}/text`; const response = await fetch(projectTextUrl); @@ -10,14 +12,14 @@ export const getProjectTextAsync = async ( const projectText = await response.json(); return projectText; } - } catch (error) { - console.error(error); + } catch (e) { + logError("getProjectTextAsync", e?.toString()); } -}; +} -export const getProjectMetaAsync = async ( +export async function getProjectMetaAsync( projectId: string -): Promise => { +): Promise { try { const projectMetaUrl = `${pxt.Cloud.apiRoot}/${projectId}`; const response = await fetch(projectMetaUrl); @@ -27,7 +29,17 @@ export const getProjectMetaAsync = async ( const projectMeta = await response.json(); return projectMeta; } - } catch (error) { - console.error(error); + } catch (e) { + logError("getProjectMetaAsync", e?.toString()); + } +} + +export async function downloadTargetConfigAsync(): Promise< + pxt.TargetConfig | undefined +> { + try { + return await pxt.targetConfigAsync(); + } catch (e) { + logError("downloadTargetConfigAsync", e?.toString()); } -}; \ No newline at end of file +} diff --git a/teachertool/src/services/loggingService.ts b/teachertool/src/services/loggingService.ts index deed920f6560..e48f3b22f486 100644 --- a/teachertool/src/services/loggingService.ts +++ b/teachertool/src/services/loggingService.ts @@ -1,24 +1,32 @@ -const formatMessageForConsole = (message: string) => { +const timestamp = () => { const time = new Date(); - return `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] ${message}`; -} + return `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}]`; +}; const formatName = (name: string) => { return name.toLowerCase().replace(/ /g, "_"); -} +}; -export const logError = (name: string, details: string) => { - pxt.tickEvent("teachertool.error", { name: formatName(name), message: details }); - console.error(formatMessageForConsole(`${name}: ${details}`)); -} +export const logError = ( + name: string, + message?: any, + data: pxt.Map = {} +) => { + name = formatName(name); + pxt.tickEvent("teachertool.error", { + ...data, + name, + message: JSON.stringify(message ?? ""), + }); + console.error(timestamp(), name, message, data); +}; -export const logInfo = (name: string, message: string) => { - pxt.tickEvent(`teachertool.${formatName(name)}`, { message: message }); - console.log(formatMessageForConsole(message)); -} +export const logInfo = (message: any) => { + console.log(timestamp(), message); +}; -export const logDebug = (message: string) => { +export const logDebug = (message: any) => { if (pxt.BrowserUtils.isLocalHost() || pxt.options.debug) { - console.log(formatMessageForConsole(message)); + console.log(timestamp(), message); } -} \ No newline at end of file +}; diff --git a/teachertool/src/state/actions.ts b/teachertool/src/state/actions.ts index d465357c8c54..8c175275de9e 100644 --- a/teachertool/src/state/actions.ts +++ b/teachertool/src/state/actions.ts @@ -28,6 +28,11 @@ type SetEvalResult = ActionBase & { result: pxt.blocks.EvaluationResult | undefined; }; +type SetTargetConfig = ActionBase & { + type: "SET_TARGET_CONFIG"; + config: pxt.TargetConfig; +}; + /** * Union of all actions */ @@ -36,7 +41,8 @@ export type Action = | PostNotification | RemoveNotification | SetProjectMetadata - | SetEvalResult; + | SetEvalResult + | SetTargetConfig; /** * Action creators @@ -53,19 +59,29 @@ const removeNotification = (notificationId: string): RemoveNotification => ({ notificationId, }); -const setProjectMetadata = (metadata: pxt.Cloud.JsonScript | undefined): SetProjectMetadata => ({ +const setProjectMetadata = ( + metadata: pxt.Cloud.JsonScript | undefined +): SetProjectMetadata => ({ type: "SET_PROJECT_METADATA", metadata, }); -const setEvalResult = (result: pxt.blocks.EvaluationResult | undefined): SetEvalResult => ({ +const setEvalResult = ( + result: pxt.blocks.EvaluationResult | undefined +): SetEvalResult => ({ type: "SET_EVAL_RESULT", result, }); +const setTargetConfig = (config: pxt.TargetConfig): SetTargetConfig => ({ + type: "SET_TARGET_CONFIG", + config, +}); + export { postNotification, removeNotification, setProjectMetadata, setEvalResult, + setTargetConfig, }; diff --git a/teachertool/src/teacherTool.css b/teachertool/src/teacherTool.css index e6adffeec87f..96336e45c7de 100644 --- a/teachertool/src/teacherTool.css +++ b/teachertool/src/teacherTool.css @@ -46,12 +46,8 @@ body { - position: relative; - margin: 0; - font-family: var(--body-font-family); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: var(--body-background-color); overflow: hidden; } @@ -59,15 +55,6 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } -#root { - width: 100%; - height: 100%; -} - -#root.editor { - height: 100%; -} - .app-container { display: flex; flex-direction: column; @@ -92,18 +79,22 @@ code { /***** HEADER BAR *****/ /*******************************/ -.header { - background-color: var(--tertiary-color); +.menubar { + position: relative; + .ui.menu .brand .ui.logo { + height: 1.1rem; + } +} +.ui.header { height: var(--header-height); max-height: var(--header-height); + border-radius: 0; display: flex; align-items: center; justify-content: space-between; - color: var(--inverted-text-color); flex-grow: 0; flex-shrink: 0; - z-index: var(--above-frame-zindex); } @@ -124,7 +115,6 @@ code { } .header .brand:before { - position: relative; height: 1.5rem; border-left: 2px solid #fff; content: " "; diff --git a/teachertool/src/transforms/loadProjectMetadataAsync.ts b/teachertool/src/transforms/loadProjectMetadataAsync.ts index 4255d2d0721d..c7c02f2985b9 100644 --- a/teachertool/src/transforms/loadProjectMetadataAsync.ts +++ b/teachertool/src/transforms/loadProjectMetadataAsync.ts @@ -1,6 +1,6 @@ import { stateAndDispatch } from "../state"; import * as Actions from "../state/actions"; -import { getProjectMetaAsync } from "../services/BackendRequests"; +import { getProjectMetaAsync } from "../services/backendRequests"; import { logDebug } from "../services/loggingService"; import { postNotification } from "./postNotification"; import { makeNotification } from "../utils";