This commit is contained in:
Michael Zhang 2024-10-01 20:00:02 -05:00
parent 84ebac763f
commit 069a35e959
11 changed files with 81 additions and 161 deletions

2
.gitignore vendored
View file

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

View file

@ -1,50 +1,3 @@
# React + TypeScript + Vite
# cubeviz
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
Cubical type theory visualizer.

BIN
bun.lockb

Binary file not shown.

View file

@ -13,6 +13,7 @@
"@react-three/drei": "^9.114.0",
"@react-three/fiber": "^8.17.8",
"@types/three": "^0.169.0",
"jotai": "^2.10.0",
"meshline": "^3.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",

View file

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View file

@ -1,63 +1,15 @@
import { ReactNode, useCallback, useRef, useState } from "react";
import { Canvas, extend, useFrame } from "@react-three/fiber";
import { OrbitControls, OrthographicCamera } from "@react-three/drei";
import { BoxGeometry, EdgesGeometry, LineBasicMaterial } from "three";
import { InlineMath, BlockMath } from "react-katex";
import { createPortal } from "react-dom";
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import "./App.css";
import styles from "./styles.module.scss";
import "katex/dist/katex.min.css";
import Point from "./components/Point";
import Path from "./components/Path";
import { SettingsBox } from "./SettingsContext";
// https://threejs.org/manual/#en/align-html-elements-to-3d
function createBox({ dimensions, ...props }) {
// This reference gives us direct access to the THREE.Mesh object
const ref = useRef();
// Hold state for hovered and clicked events
const [hovered, hover] = useState(false);
const [clicked, click] = useState(false);
const box = new BoxGeometry(1, 1, 1);
const edges = new EdgesGeometry(box);
const label = (
<div>
helloge {JSON.stringify(clicked)}
<InlineMath math="f(x)" />
</div>
);
// Return the view, these are regular Threejs elements expressed in JSX
const view = (
<mesh
{...props}
ref={ref}
onClick={(event) => click(!clicked)}
onPointerOver={(event) => hover(true)}
onPointerOut={(event) => hover(false)}
>
{/* <boxGeometry args={[1, 1, 1]} /> */}
{/* <edgesGeometry args={[box]} /> */}
<lineSegments
args={[edges, new LineBasicMaterial({ color: "darkgray" })]}
/>
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</mesh>
);
return [view, label];
}
function App() {
const [labelContainerEl, setLabelContainerEl] =
useState<HTMLDivElement | null>(null);
const labelContainerRef = useCallback((el) => {
if (el) setLabelContainerEl(el);
}, []);
let coords: [number, number, number][] = [
[0, 0, 0],
[0, 0, 1],
@ -84,7 +36,8 @@ function App() {
);
return (
<>
<div className={styles.container}>
<SettingsBox />
<Canvas className={styles.canvas}>
<OrbitControls />
<ambientLight intensity={Math.PI / 2} />
@ -105,7 +58,16 @@ function App() {
<Path key={JSON.stringify([start, end])} start={start} end={end} />
))}
</Canvas>
</>
<div className={styles.footer}>
<a
href="https://git.sr.ht/~mzhang/cubeviz"
target="_blank"
rel="noreferrer noopener"
>
Source
</a>
</div>
</div>
);
}

20
src/SettingsContext.tsx Normal file
View file

@ -0,0 +1,20 @@
import { atom, useAtom } from "jotai";
import styles from "./styles.module.scss";
export const showEmptyAtom = atom(true);
export function SettingsBox() {
const [showEmpty, setShowEmpty] = useAtom(showEmptyAtom);
return (
<div className={styles.header}>
<input
type="checkbox"
id="showEmptyCheckbox"
checked={showEmpty}
onChange={() => setShowEmpty((v) => !v)}
/>
<label htmlFor="showEmptyCheckbox">Show empty?</label>
</div>
);
}

View file

@ -1,20 +1,23 @@
import { useCallback, useState } from "react";
import { InlineMath } from "react-katex";
import { type FormEvent, useCallback, useState } from "react";
import { BlockMath } from "react-katex";
import styles from "./EditBox.module.scss";
import { showEmptyAtom } from "../SettingsContext";
import { useAtomValue } from "jotai";
export interface EditBoxProps {}
// export interface EditBoxProps {}
export default function EditBox({}: EditBoxProps) {
export default function EditBox() {
const [value, setValue] = useState("");
const [isEditing, setIsEditing] = useState(false);
const showEmpty = useAtomValue(showEmptyAtom);
const handleDblClick = useCallback(() => {
if (!isEditing) setIsEditing(true);
}, [isEditing]);
const done = useCallback(
(evt) => {
(evt: FormEvent) => {
evt.preventDefault();
if (isEditing) setIsEditing(false);
},
@ -26,15 +29,18 @@ export default function EditBox({}: EditBoxProps) {
{isEditing ? (
<form onSubmit={done}>
<input
// biome-ignore lint/a11y/noAutofocus: <explanation>
autoFocus={true}
onBlur={done}
value={value}
onChange={(evt) => setValue(evt.target.value)}
placeholder="Type latex code..."
/>
</form>
) : value === "" ? (
<span className={styles.empty}>(empty)</span>
<span className={styles.empty}>{showEmpty ? <>(empty)</> : ""}</span>
) : (
<InlineMath math={value} />
<BlockMath math={value} />
)}
</div>
);

View file

@ -1,6 +1,6 @@
import { extend, useFrame } from "@react-three/fiber";
import { extend } from "@react-three/fiber";
import { Html, Line } from "@react-three/drei";
import { MeshLineGeometry, MeshLineMaterial, raycast } from "meshline";
import { MeshLineGeometry, MeshLineMaterial } from "meshline";
import EditBox from "./EditBox";
extend({ MeshLineGeometry, MeshLineMaterial });

View file

@ -1,11 +1,28 @@
.canvas {
.container {
display: grid;
position: relative;
width: 100%;
height: 100%;
}
.labels {
position: absolute;
left: 0;
top: 0;
.canvas {
grid-area: 1 / 1;
position: relative;
width: 100%;
height: 100%;
}
.header {
grid-area: 1 / 1;
place-self: start center;
z-index: 50;
padding: 10px;
}
.footer {
grid-area: 1 / 1;
place-self: end end;
z-index: 50;
padding: 10px;
}

View file

@ -1,7 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
base: "/cubeviz",
plugins: [react()],
})
});