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$"
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user