Skip to content

Commit

Permalink
initial theming from target
Browse files Browse the repository at this point in the history
  • Loading branch information
eanders-ms committed Jan 25, 2024
1 parent 8bea667 commit e6149a0
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 110 deletions.
3 changes: 2 additions & 1 deletion teachertool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 0 additions & 2 deletions teachertool/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,5 @@
<script type="text/javascript" src="/blb/pxtblocks.js"></script>

<div id="root"></div>

<!-- @include footer.html -->
</body>
</html>
36 changes: 24 additions & 12 deletions teachertool/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? (
<div className="ui active dimmer">
<div className="ui large main loader msft"></div>
</div>
) : (
<div className="app-container">
<HeaderBar />
<div className="inner-app-container">
Expand Down
2 changes: 1 addition & 1 deletion teachertool/src/components/DebugInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const DebugInput: React.FC<IProps> = ({}) => {
rows={20}
onChange={setRubric} />
</div>
<Button id="evaluateSingleProjectButton" className="primary" onClick={evaluate} title={"Evaluate"} label={lf("Evaluate")} />
<Button id="evaluateSingleProjectButton" className="ui button primary" onClick={evaluate} title={"Evaluate"} label={lf("Evaluate")} />
</div>
)

Expand Down
139 changes: 92 additions & 47 deletions teachertool/src/components/HeaderBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HeaderBarProps> {
protected reportAbuseUrl = "https://github.com/contact/report-content";
export const HeaderBar: React.FC<HeaderBarProps> = () => {
const appTheme = pxt.appTarget?.appTheme;

protected getOrganizationLogo(targetTheme: pxt.AppTheme) {
const logoUrl = targetTheme.organizationWideLogo;
return <div className="header-logo">
{logoUrl
? <img src={logoUrl} alt={lf("{0} Logo", targetTheme.organization)}/>
: <span className="name">{targetTheme.organization}</span>}
</div>
}
const brandIconClick = () => {};

protected getTargetLogo(targetTheme: pxt.AppTheme) {
return <div className={`ui item logo brand noclick}`}>
{targetTheme.useTextLogo
? [<span className="name" key="org-name" onClick={this.onHomeClicked}>{targetTheme.organizationText}</span>,
<span className="name-short" key="org-name-short" onClick={this.onHomeClicked}>{targetTheme.organizationShortText || targetTheme.organizationText}</span>]
: (targetTheme.logo || targetTheme.portraitLogo
? <img className="logo" src={targetTheme.logo || targetTheme.portraitLogo} alt={lf("{0} Logo", targetTheme.boardName)}/>
: <span className="name"> {targetTheme.boardName}</span>)
}
</div>
}
const getOrganizationLogo = () => {
return (
<div className="ui item logo organization">
{appTheme.organizationWideLogo || appTheme.organizationLogo ? (
<img
className={`ui logo`}
src={
appTheme.organizationWideLogo ||
appTheme.organizationLogo
}
alt={lf("{0} Logo", appTheme.organization)}
/>
) : (
<span className="name">{appTheme.organization}</span>
)}
</div>
);
};

onHomeClicked = () => {
const getTargetLogo = () => {
return (
<div
aria-label={lf("{0} Logo", appTheme.boardName)}
role="menuitem"
className={`ui item logo brand mobile hide`}
onClick={brandIconClick}
>
{appTheme.useTextLogo ? (
[
<span className="name" key="org-name">
{appTheme.organizationText}
</span>,
<span className="name-short" key="org-name-short">
{appTheme.organizationShortText ||
appTheme.organizationText}
</span>,
]
) : appTheme.logo || appTheme.portraitLogo ? (
<img
className={`ui ${
appTheme.logoWide ? "small" : ""
} logo`}
src={appTheme.logo || appTheme.portraitLogo}
alt={lf("{0} Logo", appTheme.boardName)}
/>
) : (
<span className="name">{appTheme.boardName}</span>
)}
</div>
);
};

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 (
<header className="menubar" role="banner">
<MenuBar
className={`ui menu ${
appTheme?.invertedMenu ? `inverted` : ""
} header`}
ariaLabel={lf("Header")}
>
<div className="left menu">
{getOrganizationLogo()}
{getTargetLogo()}
</div>

const appTheme = pxt.appTarget?.appTheme;
<div className="spacer" />

return <MenuBar className="header" ariaLabel={lf("Header")}>
<div className="header-left">
{this.getOrganizationLogo(appTheme)}
{this.getTargetLogo(appTheme)}
</div>

<div className="spacer" />

<div className="header-right">
<Button className="menu-button" leftIcon="fas fa-home large" title={lf("Return to the editor homepage")} onClick={this.onHomeClicked}/>
</div>
</MenuBar>
}
}
<div className="header-right">
<Button
className="menu-button"
leftIcon="fas fa-home large"
title={lf("Return to the editor homepage")}
onClick={onHomeClicked}
/>
</div>
</MenuBar>
</header>
);
};

export default HeaderBar;
export default HeaderBar;
32 changes: 22 additions & 10 deletions teachertool/src/services/BackendRequests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const getProjectTextAsync = async (
import { logError } from "./loggingService";

export async function getProjectTextAsync(
projectId: string
): Promise<pxt.Cloud.JsonText | undefined> => {
): Promise<pxt.Cloud.JsonText | undefined> {
try {
const projectTextUrl = `${pxt.Cloud.apiRoot}/${projectId}/text`;
const response = await fetch(projectTextUrl);
Expand All @@ -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<pxt.Cloud.JsonScript | undefined> => {
): Promise<pxt.Cloud.JsonScript | undefined> {
try {
const projectMetaUrl = `${pxt.Cloud.apiRoot}/${projectId}`;
const response = await fetch(projectMetaUrl);
Expand All @@ -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());
}
};
}
38 changes: 23 additions & 15 deletions teachertool/src/services/loggingService.ts
Original file line number Diff line number Diff line change
@@ -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<string | number> = {}
) => {
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);
}
}
};
22 changes: 19 additions & 3 deletions teachertool/src/state/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -36,7 +41,8 @@ export type Action =
| PostNotification
| RemoveNotification
| SetProjectMetadata
| SetEvalResult;
| SetEvalResult
| SetTargetConfig;

/**
* Action creators
Expand All @@ -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,
};
Loading

0 comments on commit e6149a0

Please sign in to comment.