preliminary stuff
This commit is contained in:
		@@ -1,33 +1,27 @@
 | 
			
		||||
import { css } from "@emotion/css";
 | 
			
		||||
import { Center, Cover, Stack } from "@firebox/components";
 | 
			
		||||
import { MathuscriptPlayer } from "./player/Player";
 | 
			
		||||
import { Katex } from "./player/MathText";
 | 
			
		||||
 | 
			
		||||
const script = String.raw`
 | 
			
		||||
 | 
			
		||||
bridget (happy) "Hi, friends!"
 | 
			
		||||
 | 
			
		||||
board "Given $f: \mathbb{Q} \to \mathbb{R}$ and $x \in \mathbb{Q}$, there is a unique $y \in \mathbb{N}$ such that $f(x)=y$"
 | 
			
		||||
 | 
			
		||||
axelle (happy) "Wow, did you know that $a^2+b^2=c^2$?"
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
const App = (props: { name: string }) => {
 | 
			
		||||
	const {name} = props;
 | 
			
		||||
	return (
 | 
			
		||||
		<Stack>
 | 
			
		||||
			<div className={css`background-color: floralwhite;`}>
 | 
			
		||||
				<Cover gap pad>
 | 
			
		||||
					<Center>
 | 
			
		||||
						<Stack gap={-1}>
 | 
			
		||||
							<h1>Hello, {name}!</h1>
 | 
			
		||||
							<p>Welcome to a website with a certain design philosophy. Tell me how it's working out! I want to see this text wrap a few times. Hopefully this sentence will help.</p>
 | 
			
		||||
						</Stack>
 | 
			
		||||
					</Center>
 | 
			
		||||
					<Cover.Footer>A page by Dylan Pizzo</Cover.Footer>
 | 
			
		||||
				</Cover>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div className={css`background-color: aliceblue;`}>
 | 
			
		||||
				<Cover gap pad>
 | 
			
		||||
					<Center>
 | 
			
		||||
						<Stack gap={-1}>
 | 
			
		||||
							<h1>Hello, {name}!</h1>
 | 
			
		||||
							<p>Welcome to a website with a certain design philosophy. Tell me how it's working out! I want to see this text wrap a few times. Hopefully this sentence will help.</p>
 | 
			
		||||
						</Stack>
 | 
			
		||||
					</Center>
 | 
			
		||||
					<Cover.Footer>A page by Dylan Pizzo</Cover.Footer>
 | 
			
		||||
				</Cover>
 | 
			
		||||
			</div>
 | 
			
		||||
		</Stack>
 | 
			
		||||
		<div className={css`
 | 
			
		||||
			margin: auto;
 | 
			
		||||
		`}>
 | 
			
		||||
			<h1>MathU</h1>
 | 
			
		||||
			<MathuscriptPlayer script={script} />
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								src/client/player/MathText.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/client/player/MathText.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import { css } from "@emotion/css";
 | 
			
		||||
import katex from "katex";
 | 
			
		||||
import { useLayoutEffect, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
export const Katex = (props: { tex: string }) => {
 | 
			
		||||
	const {tex} = props;
 | 
			
		||||
	const ref = useRef<HTMLDivElement>(null);
 | 
			
		||||
 | 
			
		||||
	useLayoutEffect(() => {
 | 
			
		||||
		const element = ref.current;
 | 
			
		||||
		if (!element) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		katex.render(tex, element);
 | 
			
		||||
	}, [tex])
 | 
			
		||||
	return (
 | 
			
		||||
		<span ref={ref} className={css`
 | 
			
		||||
			math {
 | 
			
		||||
				display: none;
 | 
			
		||||
			}
 | 
			
		||||
		`}></span>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const MathText = (props: { children: string }) => {
 | 
			
		||||
	const str = props.children;
 | 
			
		||||
	const segments = str.split("$").map((s, i) => (i % 2 === 0 ? {type: "text", value: s} : {type: "math", value: s}));
 | 
			
		||||
	const components = segments.map((segment, i) => {
 | 
			
		||||
		if (segment.type === "text") {
 | 
			
		||||
			return segment.value.split("\\\\").flatMap((s, j) => [<br key={j}/>, s]).slice(1);
 | 
			
		||||
		} else if (segment.type === "math") {
 | 
			
		||||
			return <Katex key={i} tex={segment.value}/>;
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<>{components}</>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										107
									
								
								src/client/player/Player.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/client/player/Player.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
import { css } from "@emotion/css";
 | 
			
		||||
import { Mathuscript, parseMathuscript } from "./parse";
 | 
			
		||||
import katex from "katex";
 | 
			
		||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
 | 
			
		||||
import { MathText } from "./MathText";
 | 
			
		||||
 | 
			
		||||
type VisualState = {
 | 
			
		||||
	characters: {name: string, emotion: string, x: number}[],
 | 
			
		||||
	board: {text: string},
 | 
			
		||||
	dialog: {name: string | null, text: string},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const afterStep = (state: VisualState, step: Mathuscript[number]): VisualState => {
 | 
			
		||||
	const newState = structuredClone(state);
 | 
			
		||||
	const {characters, board, dialog} = newState;
 | 
			
		||||
	if (step.name === "board") {
 | 
			
		||||
		board.text = step.text;
 | 
			
		||||
	} else {
 | 
			
		||||
		let char = characters.find(c => c.name === step.name);
 | 
			
		||||
		if (!char) {
 | 
			
		||||
			char = {name: step.name, emotion: "default", x: 0.5};
 | 
			
		||||
		}
 | 
			
		||||
		char.emotion = step.emotion ?? char.emotion;
 | 
			
		||||
		dialog.name = step.name;
 | 
			
		||||
		dialog.text = step.text;
 | 
			
		||||
	}
 | 
			
		||||
	console.log(newState);
 | 
			
		||||
	return newState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const MathuscriptPlayer = (props: { script: string }) => {
 | 
			
		||||
	const {script} = props;
 | 
			
		||||
	const parsedScript = useMemo(() => parseMathuscript(script), [script]);
 | 
			
		||||
	const [index, setIndex] = useState(0);
 | 
			
		||||
	const [visualState, setVisualState] = useState<VisualState>({characters: [], board: {text: ""}, dialog: {name: null, text: ""}});
 | 
			
		||||
 | 
			
		||||
	const doStep = useCallback(() => {
 | 
			
		||||
		const step = parsedScript[index];
 | 
			
		||||
		if (step) {
 | 
			
		||||
			console.log(step);
 | 
			
		||||
			setIndex(i => i+1);
 | 
			
		||||
			setVisualState(state => afterStep(state, step));
 | 
			
		||||
		} else {
 | 
			
		||||
			console.log("the end");
 | 
			
		||||
		}
 | 
			
		||||
	}, [index, parsedScript, setIndex, setVisualState]);
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			<div className={css`
 | 
			
		||||
				width: 800px;
 | 
			
		||||
				height: 480px;
 | 
			
		||||
				border: 1px solid black;
 | 
			
		||||
				font-size: 16px;
 | 
			
		||||
				margin: auto;
 | 
			
		||||
				position: relative;
 | 
			
		||||
				background-image: url("/assets/background.png");
 | 
			
		||||
				background-size: 118%;
 | 
			
		||||
				background-position-x: center;
 | 
			
		||||
				background-position-y: bottom;
 | 
			
		||||
			`}>
 | 
			
		||||
				<div className={css`
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					width: 58%;
 | 
			
		||||
					top: 7%;
 | 
			
		||||
					left: 21%;
 | 
			
		||||
					height: 64%;
 | 
			
		||||
					display: flex;
 | 
			
		||||
					align-items: center;
 | 
			
		||||
					justify-content: center;
 | 
			
		||||
					color: white;
 | 
			
		||||
					/* background-color: rgba(255, 0, 0, 0.3); */
 | 
			
		||||
					padding: 1em;
 | 
			
		||||
				`}>
 | 
			
		||||
					<div>
 | 
			
		||||
						<MathText>{visualState.board.text}</MathText>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className={css`
 | 
			
		||||
					padding: 1em;
 | 
			
		||||
					display: flex;
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					width: 100%;
 | 
			
		||||
					height: 25%;
 | 
			
		||||
					bottom: 0;
 | 
			
		||||
				`}>
 | 
			
		||||
					<div className={css`
 | 
			
		||||
						flex-basis: 0;
 | 
			
		||||
						flex-grow: 1;
 | 
			
		||||
						background-color: hsla(220, 50%, 40%, 0.85);
 | 
			
		||||
						border: 2px solid hsla(220, 30%, 60%, 0.85);
 | 
			
		||||
						border-radius: 0.25em;
 | 
			
		||||
						padding: 0.5em;
 | 
			
		||||
						color: white;
 | 
			
		||||
					`}>
 | 
			
		||||
						{
 | 
			
		||||
							visualState.dialog.name && <>
 | 
			
		||||
								<strong>{visualState.dialog.name}.</strong> <MathText>{visualState.dialog.text}</MathText>
 | 
			
		||||
							</>
 | 
			
		||||
						}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<button onClick={doStep}>Step</button>
 | 
			
		||||
		</>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										14
									
								
								src/client/player/parse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/client/player/parse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
 | 
			
		||||
const lineRegex = /^\s*(?<name>[a-z-]+)\s+(?:\((?<emotion>[a-z-]+)\))?\s*"(?<text>(?:[^"\\]|\\.)*)"\s*$/;
 | 
			
		||||
 | 
			
		||||
export const parseMathuscript = (script: string) => {
 | 
			
		||||
	return script.split("\n").filter(Boolean).map(line => {
 | 
			
		||||
		const match = line.match(lineRegex);
 | 
			
		||||
		if (!match) {
 | 
			
		||||
			throw Error(`Bad Line: ${JSON.stringify(line)}`);
 | 
			
		||||
		}
 | 
			
		||||
		return match.groups as Mathuscript[number];
 | 
			
		||||
	}).filter(Boolean);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Mathuscript = {name: string, emotion: string | undefined, text: string}[];
 | 
			
		||||
							
								
								
									
										3
									
								
								src/client/player/render.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/client/player/render.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
// export const render = (str: string) => {
 | 
			
		||||
// 	str.split("$").map((s, i) => (i % 2 === 0 ? s : katex.))
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										8
									
								
								src/client/player/script.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/client/player/script.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bridget (happy) "Hi, friends!"
 | 
			
		||||
 | 
			
		||||
board "Given $f: \mathbb{Q} \to \mathbb{R}$ and $x \in \mathbb{Q}$, there\\is a unique $y \in \mathbb{N}$ such that $f(x)=y$"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/server/public/assets/background.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/server/public/assets/background.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/server/public/assets/bridget.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/server/public/assets/bridget.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 976 KiB  | 
@@ -2,6 +2,7 @@
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<script src="dist/index.js" type="module"></script>
 | 
			
		||||
		<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
 | 
			
		||||
		<style>
 | 
			
		||||
			:root {
 | 
			
		||||
				--measure: 64ch;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user