Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add websqrl @70db1495b43e0d8f1ed6fc88c0ccb7d538169925 #11

Merged
merged 2 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add websqrl @70db1495b43e0d8f1ed6fc88c0ccb7d538169925
  • Loading branch information
qix committed Nov 22, 2022
commit 0961ce12389139b01434acf178f09a6c53e5a117
35 changes: 35 additions & 0 deletions websqrl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build
/public/*.js

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
30 changes: 30 additions & 0 deletions websqrl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
253 changes: 253 additions & 0 deletions websqrl/components/StreamPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import Head from "next/head";
import { useEffect, useState, useRef } from "react";
import { sprintf } from "sprintf-js";
import Split from "react-split";
import { invariant } from "../src/invariant";
import {
addMinutes,
subMilliseconds,
startOfMinute,
differenceInMilliseconds,
} from "date-fns";
import { Request, Response, WikiEvent, Result } from "../src/types";
import { MonacoEditor } from "../src/MonacoEditor";

let LOG_ID = 0;
const DELAY_MS = 60_000 * 3;

export function StreamPage(props: {
urlPrefix: string,
extractDate: (payload: any) => Date,
sampleCode: string
}) {
const { extractDate, urlPrefix } = props;
const worker = useRef<Worker>();
const lastSource = useRef<string>(null);
const logs = useRef<JSX.Element[]>([]);

const [, setLogId] = useState(LOG_ID);
const [source, setSource] = useState(props.sampleCode);

function recompile() {
clearLog("Compiling...");
lastSource.current = source;
req({ type: "compile", source });
}

if (lastSource.current !== source && worker.current) {
recompile();
}

function req(request: Request) {
worker.current.postMessage(request);
}

function clearLog(message: string) {
logs.current = [<div key={LOG_ID++}>{message}</div>];
setLogId(LOG_ID);
}

function append(entry: JSX.Element) {
// @todo: This is an extremely hacky way to do logs
if (logs.current.length > 150) {
logs.current.shift();
}
logs.current.push(<div key={LOG_ID++}>{entry}</div>);
setLogId(LOG_ID);
}

function buildEntry(res: Result) {
const messages = [];
for (const msg of res.logs) {
messages.push(sprintf(msg.format, ...msg.args));
}
return <div style={{
backgroundColor: '#555',
marginTop: '2px'
}}>{messages}</div>;
}
useEffect(() => {
const scripts: HTMLScriptElement[] = [];

function startDownload(date: Date) {
const filename = date
.toISOString()
.substring(0, "0000-00-00T00:00".length);
const script = document.createElement("script");
script.src = `${urlPrefix}${filename}.js`;
script.async = true;
document.body.appendChild(script);
scripts.push(script);
}

let events: WikiEvent[] = [];
let eventIndex = 0;
let nextEventTimeout: NodeJS.Timeout;
let downloadDate = startOfMinute(subMilliseconds(new Date(), DELAY_MS));
let firstBatch = true;

function nextEvent() {
if (eventIndex < events.length) {
req({
type: "event",
event: events[eventIndex],
});
eventIndex++;
} else {
const nextDownload = addMinutes(downloadDate, 1);
if (delayedMsUntil(nextDownload) <= 0) {
downloadDate = nextDownload;
startDownload(downloadDate);
}
}
scheduleNextEvent();
}

function delayedMsUntil(nextEventDate: Date) {
const now = subMilliseconds(new Date(), DELAY_MS);
return differenceInMilliseconds(nextEventDate, now);
}

function scheduleNextEvent() {
let nextEventDate: Date;
if (eventIndex < events.length) {
nextEventDate = new Date(extractDate(events[eventIndex]));
} else {
nextEventDate = addMinutes(downloadDate, 1);
}
clearTimeout(nextEventTimeout);
nextEventTimeout = setTimeout(
nextEvent,
Math.max(10, delayedMsUntil(nextEventDate))
);
}

(window as any).sqrlInjectData = (data: {
version: string;
timestamp: string;
events: any[];
}) => {
events = data.events;
eventIndex = 0;
if (firstBatch) {
while (eventIndex < events.length) {
const eventDate = new Date(extractDate(events[eventIndex]));
if (delayedMsUntil(eventDate) > 0) {
break;
}
eventIndex++;
}
firstBatch = false;
}
console.log("Got", events.length, "in batch, starting from", eventIndex);
scheduleNextEvent();
};

startDownload(downloadDate);

return () => {
clearTimeout(nextEventTimeout);
scripts.forEach((script) => {
document.body.removeChild(script);
});
};
});

useEffect(() => {
invariant(!worker.current, "Worker created twice");
worker.current = new Worker(
new URL("../workers/compile.worker", import.meta.url)
);
worker.current.onmessage = (event) => {
const res = event.data as Response;
if (res.type === "compileOkay") {
if (res.source === lastSource.current) {
clearLog("Compiled! Running...");
}
} else if (res.type === "compileError") {
if (res.source === lastSource.current) {
clearLog(res.message);
}
} else if (res.type === "runtimeError") {
if (res.source === lastSource.current) {
clearLog("Error during execution\n\n" + res.message);
}
} else if (res.type === "result") {
try {
append(buildEntry(res));
} catch (err) {
append(
<div>
<h2>Error rendering result</h2>
<div>{err.stack}</div>
</div>
);
}
} else {
console.log("Unknown webworker message:", res);
}
};
recompile();

return () => {
worker.current.terminate();
worker.current = null;
};
}, []);

return (
<main
style={{
display: "flex",
flexDirection: "row",
width: "100%",
height: "100%",
}}
>
<Head>
<title>SQRL Wikipedia Demo</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Split
direction="horizontal"
elementStyle={(dimension, size, gutterSize) => ({
"flex-basis": `calc(${size}% - ${gutterSize}px)`,
})}
gutterStyle={(dimension, gutterSize) => ({
"flex-basis": `${gutterSize}px`,
cursor: "ew-resize",
})}
style={{ width: "100%", height: "100%", display: "flex" }}
>
<MonacoEditor
style={{ height: "100%", width: "100%" }}
value={source}
onChange={setSource}
theme="vs-dark"
/>

<div
style={{
paddingTop: "5px",
height: "100%",
overflow: "auto",
backgroundColor: "#111",
}}
>
<div
id="compileOutput"
style={{
whiteSpace: "pre-wrap",
wordWrap: "break-word",
padding: "5px",
color: "#fff",
height: "100%",
}}
>
{logs.current}
</div>
</div>
</Split>
</main>
);
}
5 changes: 5 additions & 0 deletions websqrl/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
52 changes: 52 additions & 0 deletions websqrl/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @ts-check

const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const assert = require("assert");

module.exports = {
reactStrictMode: true,
webpack: (
/** @type {import('webpack').Configuration} */
config,
{ isServer }
) => {
config.resolve = config.resolve || {};
config.resolve.fallback = {
// the require for this module is wrapped in a try/catch, so it can fail without causing issues
"sqrl-test-utils": false,
};

assert(config.module?.rules);
config.module.rules.push({
test: /\.sqrl$/,
type: "asset/source",
});

const oneOfRule = config.module.rules.find(
/** @returns {rule is import('webpack').RuleSetRule} */
(rule) => typeof rule === "object" && !!rule.oneOf
);
assert(oneOfRule?.oneOf);

oneOfRule.oneOf.forEach((r) => {
if (r.issuer?.and?.[0]?.toString().includes("_app")) {
r.issuer = [
{ and: r.issuer.and },
/[\\/]node_modules[\\/]monaco-editor[\\/]/,
];
}
});

assert(config.plugins, "no plugins array");
if (!isServer) {
config.plugins.push(
new MonacoWebpackPlugin({
languages: ["cpp"],
filename: "static/[name].worker.js",
publicPath: "_next",
})
);
}
return config;
},
};
Loading