Skip to content

Commit

Permalink
New playground UX (#266)
Browse files Browse the repository at this point in the history
Still a couple TODOs, but opening as a PR now so folks can start to
review and leave feedback as I finish up.

This is not replacing the main page yet, so open the /ux path when
testing.
  • Loading branch information
billti authored May 4, 2023
1 parent 28c2715 commit 079701c
Show file tree
Hide file tree
Showing 19 changed files with 1,566 additions and 727 deletions.
2 changes: 2 additions & 0 deletions npm/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export function getCompilerWorker(script: string): ICompilerWorker {
return createWorkerProxy(postMessage, setMsgHandler, onTerminate);
}

export type { ICompilerWorker }
export { log }
export { renderDump, exampleDump } from "./state-table.js"
export { type Dump, type ShotResult, type VSDiagnostic } from "./common.js";
export { getAllKatas, getKata, type Kata, type KataItem, type Exercise } from "./katas.js";
Expand Down
62 changes: 53 additions & 9 deletions npm/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface QscEventMap {
"Message": QscEvent<string>;
"DumpMachine": QscEvent<Dump>;
"Result": QscEvent<Result>;
"uiResultsRefresh": QscEvent<void>;
}

// Union of valid message names
Expand Down Expand Up @@ -47,7 +48,9 @@ function makeResultObj(): ShotResult {

export class QscEventTarget extends EventTarget implements IQscEventTarget {
private results: ShotResult[] = [];
private currentResult: ShotResult = makeResultObj();
private shotActive = false;
private animationFrameId = 0;
private supportsUiRefresh = false;

// Overrides for the base EventTarget methods to limit to expected event types
addEventListener<K extends keyof QscEventMap>(
Expand All @@ -70,6 +73,8 @@ export class QscEventTarget extends EventTarget implements IQscEventTarget {
*/
constructor(captureEvents: boolean) {
super();
this.supportsUiRefresh = typeof globalThis.requestAnimationFrame === 'function';

if (captureEvents) {
this.addEventListener('Message', (ev) => this.onMessage(ev.detail));
this.addEventListener('DumpMachine', (ev) => this.onDumpMachine(ev.detail));
Expand All @@ -78,21 +83,60 @@ export class QscEventTarget extends EventTarget implements IQscEventTarget {
}

private onMessage(msg: string) {
this.currentResult.events.push({ "type": "Message", "message": msg });
this.ensureActiveShot();

const shotIdx = this.results.length - 1;
this.results[shotIdx].events.push({ "type": "Message", "message": msg });

this.queueUiRefresh();
}

private onDumpMachine(dump: Dump) {
this.currentResult.events.push({ "type": "DumpMachine", "state": dump });
this.ensureActiveShot();

const shotIdx = this.results.length - 1;
this.results[shotIdx].events.push({ "type": "DumpMachine", "state": dump });

this.queueUiRefresh();
}

private onResult(result: Result) {
// Save result and move to the next
this.currentResult.success = result.success;
this.currentResult.result = result.value;
this.results.push(this.currentResult);
this.currentResult = makeResultObj();
this.ensureActiveShot();

const shotIdx = this.results.length - 1;

this.results[shotIdx].success = result.success;
this.results[shotIdx].result = result.value;
this.shotActive = false;

this.queueUiRefresh();
}

private ensureActiveShot() {
if (!this.shotActive) {
this.results.push(makeResultObj());
this.shotActive = true;
}
}

private queueUiRefresh() {
if (this.supportsUiRefresh && !this.animationFrameId) {
this.animationFrameId = requestAnimationFrame(() => {this.onUiRefresh()});
}
}

private onUiRefresh() {
this.animationFrameId = 0;
const uiRefreshEvent = makeEvent('uiResultsRefresh', undefined);
this.dispatchEvent(uiRefreshEvent);
}

getResults(): ShotResult[] { return this.results; }
clearResults(): void { this.results.length = 0; }

resultCount(): number {
// May be one less than length if the last is still in flight
return this.shotActive ? this.results.length - 1 : this.results.length;
}

clearResults(): void { this.results = []; }
}
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
"devDependencies": {
"@types/node": "^18.15.3",
"esbuild": "^0.17.12",
"github-markdown-css": "^5.2.0",
"marked": "^4.3.0",
"mathjax": "^3.2.2",
"monaco-editor": "^0.36.1",
"preact": "^10.13.1",
"marked": "^4.3.0",
"typescript": "^5.0.2"
}
}
6 changes: 5 additions & 1 deletion playground/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const outdir = join(thisDir, 'public/libs');

/** @type {import("esbuild").BuildOptions} */
const buildOptions = {
entryPoints: [join(thisDir, "src/main.ts"), join(thisDir, "src/worker.ts")],
entryPoints: [join(thisDir, "src/main.tsx"), join(thisDir, "src/worker.ts")],
outdir,
bundle: true,
target: ['es2020', 'chrome64', 'edge79', 'firefox62' ,'safari11.1'],
Expand All @@ -43,6 +43,10 @@ function copyLibs() {
mkdirSync(mathjaxDest, { recursive: true});
cpSync(mathjaxBase, mathjaxDest, {recursive: true});

let githubMarkdown = join(libsDir, "github-markdown-css/github-markdown-light.css");
let githubMarkdownDest = join(thisDir, 'public/libs/github-markdown.css');
copyFileSync(githubMarkdown, githubMarkdownDest);

let qsharpWasm = join(thisDir, "..", "npm/lib/web/qsc_wasm_bg.wasm");
let qsharpDest = join(thisDir, `public/libs/qsharp`);

Expand Down
40 changes: 6 additions & 34 deletions playground/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
<head>
<meta charset="utf-8">
<script>
<!-- If this is served under a directory it needs the trailing slash to load the relative paths-->
// If this is served under a directory it needs the trailing slash to load the relative paths
if (!window.location.pathname.endsWith("/")) window.location = window.location + "/";
window.qscBasePath = new URL("./", window.location).pathname; // e.g. "/qsharp/"
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:image/gif;base64,R0lGODlhEAAQAAAAACwAAAAAEAAQAAACDvAxdbn9YZSTVntx1qcAADs=">
<link rel="stylesheet" href="libs/github-markdown.css">
<link rel="stylesheet" href="style.css">
<title>Q# playground</title>
<script>
Expand All @@ -22,39 +24,9 @@
};
</script>
<script src="libs/mathjax/tex-chtml.js"></script>
</head>

<body>
<h1>Q# playground</h1>
<div id="container">
<div>
<div id="editor"></div>
<div id="button-row">
<div>
<input id="expr" value="" />
<input id="shot" type="number" value="100" max="1000" min="1" />
<button id="run">Run</button>
</div>
<div id="share-div">
<span id="share-confirmation">Copied to clipboard.</span>
<button id="share">Share</button>
</div>
</div>
</div>
<div id="output"></div>
</div>
<div id="errors"></div>

<div id="katas">
<h1>Katas</h1>
<div>
<select id="katas-list">
</select>
<div id="katas-canvas"></div>
</div>
</div>
<script src="libs/monaco/vs/loader.js"></script>
<script src="libs/main.js"></script>
</body>

</head>
<body>
</body>
</html>
Loading

0 comments on commit 079701c

Please sign in to comment.