Skip to content

Commit d3e621e

Browse files
committed
✨ hydrate the app
1 parent 7a09708 commit d3e621e

File tree

11 files changed

+187
-138
lines changed

11 files changed

+187
-138
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
- run: bunx vite build --base yAR-htzee
2121
env:
2222
VITE_XR8_API_KEY: ${{ secrets.VITE_XR8_API_KEY }}
23+
- run: bun build-index-html.ts
2324
- run: cp doc/* ./dist/
2425
- uses: actions/upload-pages-artifact@v3
2526
with:

build-index-html.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as fs from "fs";
2+
import * as React from "react";
3+
import { renderToString } from "react-dom/server";
4+
import { App } from "./src/App/App";
5+
6+
console.log("📸 rendering loading screen to string...");
7+
8+
const appContent = renderToString(React.createElement(App, { loading: true }));
9+
10+
console.log("🖊 replacing content in index.html...");
11+
12+
let indexContent = fs.readFileSync(__dirname + "/dist/index.html").toString();
13+
14+
indexContent = indexContent.replace(
15+
`<div id="overlay"></div>`,
16+
`<div id="overlay">${appContent}</div>`
17+
);
18+
19+
fs.writeFileSync(__dirname + "/dist/index.html", indexContent);
20+
21+
console.log("✅ done");

index.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,17 @@
2222
left: 0;
2323
/* pointer-events: none; */
2424
}
25+
#canvas-container {
26+
position: fixed;
27+
width: 100vw;
28+
height: 100vh;
29+
top: 0;
30+
left: 0;
31+
}
2532
</style>
2633
</head>
2734
<body>
28-
<div id="root"></div>
35+
<div id="canvas-container"></div>
2936
<div id="overlay"></div>
3037
<script type="module" src="/src/index.tsx"></script>
3138
</body>

src/App/App.tsx

Lines changed: 106 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import * as React from "react";
22
import * as THREE from "three";
33
import { GithubLogo } from "./Ui/GithubLogo";
4-
import { Canvas, useFrame, useThree } from "@react-three/fiber";
4+
import { Canvas } from "@react-three/fiber";
55
import { XR8Controls } from "../XR8Canvas/XR8Controls";
6-
import { useXR8 } from "../XR8Canvas/useXR8";
7-
import { getXR8, loadXR8, xr8Hosted } from "../XR8Canvas/getXR8";
6+
import { loadXR8, xr8Hosted } from "../XR8Canvas/getXR8";
87
import { Game } from "./Game";
98
import { Dice } from "./Scene/Dice";
10-
// @ts-ignore
11-
import { Visualizer } from "react-touch-visualizer";
129
import tunnel from "tunnel-rat";
1310
import { Ground } from "./Scene/Ground";
1411
import { WebXRControls } from "../WebXRCanvas/WebXRControls";
@@ -19,15 +16,17 @@ import { TrackingHint } from "./Ui/Hints/TrackingHint";
1916
import { useProgress } from "@react-three/drei";
2017
import { useIsWebXRSupported } from "../WebXRCanvas/useWebXRSession";
2118
import { useDelay } from "./Ui/useDelay";
22-
import { PageRules } from "./Ui/PageRules";
2319
import { LoadingScreen } from "./Ui/LoadingScreen";
20+
// @ts-ignore
21+
import { Visualizer } from "react-touch-visualizer";
2422

2523
// @ts-ignore
2624
const xr8ApiKey: string | undefined = import.meta.env.VITE_XR8_API_KEY;
27-
const touchSupported = "ontouchend" in document;
25+
const touchSupported =
26+
typeof document !== "undefined" && "ontouchend" in document;
2827

29-
export const App = () => {
30-
const [state, setState] = React.useState<
28+
export const App = ({ loading = false }: { loading?: boolean }) => {
29+
let [state, setState] = React.useState<
3130
| { type: "loading" }
3231
| { type: "waiting-user-input" }
3332
| {
@@ -45,6 +44,9 @@ export const App = () => {
4544
| { type: "flat" }
4645
>({ type: "waiting-user-input" });
4746

47+
// force the state to loading
48+
if (loading) state = { type: "loading" };
49+
4850
const uiTunnel = React.useMemo(tunnel, []);
4951

5052
const [error, setError] = React.useState<Error>();
@@ -96,107 +98,109 @@ export const App = () => {
9698

9799
const hint = useDelay(readyForRender && !readyForGame && "tracking", 2500);
98100

99-
if (webXRSupported === "loading") return null;
100-
101-
if (state.type === "loading") return null;
102-
103101
return (
104102
<>
105-
<Canvas
106-
camera={{ position: new THREE.Vector3(0, 6, 6), near: 0.1, far: 1000 }}
107-
shadows
108-
style={{
109-
position: "fixed",
110-
top: 0,
111-
left: 0,
112-
right: 0,
113-
bottom: 0,
114-
touchAction: "none",
115-
opacity: readyForRender ? 1 : 0,
116-
}}
117-
>
118-
{state.type === "xr8" && state.xr8 && (
119-
<XR8Controls
120-
xr8={state.xr8}
121-
onPoseFound={() => setState((s) => ({ ...s, poseFound: true }))}
122-
onCameraFeedDisplayed={() =>
123-
setState((s) => ({ ...s, cameraFeedDisplayed: true }))
124-
}
125-
/>
126-
)}
127-
128-
{state.type === "webXR" && state.webXRSession && (
129-
<WebXRControls
130-
worldSize={8}
131-
webXRSession={state.webXRSession}
132-
onPoseFound={() => setState((s) => ({ ...s, poseFound: true }))}
133-
onCameraFeedDisplayed={() =>
134-
setState((s) => ({ ...s, cameraFeedDisplayed: true }))
103+
<CanvasContainerPortal>
104+
<Canvas
105+
camera={{
106+
position: new THREE.Vector3(0, 6, 6),
107+
near: 0.1,
108+
far: 1000,
109+
}}
110+
shadows
111+
style={{
112+
position: "fixed",
113+
top: 0,
114+
left: 0,
115+
right: 0,
116+
bottom: 0,
117+
touchAction: "none",
118+
opacity: readyForRender ? 1 : 0,
119+
}}
120+
>
121+
{state.type === "xr8" && state.xr8 && (
122+
<XR8Controls
123+
xr8={state.xr8}
124+
onPoseFound={() => setState((s) => ({ ...s, poseFound: true }))}
125+
onCameraFeedDisplayed={() =>
126+
setState((s) => ({ ...s, cameraFeedDisplayed: true }))
127+
}
128+
/>
129+
)}
130+
131+
{state.type === "webXR" && state.webXRSession && (
132+
<WebXRControls
133+
worldSize={8}
134+
webXRSession={state.webXRSession}
135+
onPoseFound={() => setState((s) => ({ ...s, poseFound: true }))}
136+
onCameraFeedDisplayed={() =>
137+
setState((s) => ({ ...s, cameraFeedDisplayed: true }))
138+
}
139+
/>
140+
)}
141+
142+
<React.Suspense fallback={null}>
143+
<Environment />
144+
145+
{
146+
/* preload the dice model */
147+
!readyForGame && (
148+
<Dice
149+
position={[999, 999, 9999]}
150+
scale={[0.0001, 0.0001, 0.0001]}
151+
/>
152+
)
135153
}
154+
155+
{readyForGame && <Game UiPortal={uiTunnel.In} />}
156+
</React.Suspense>
157+
158+
<directionalLight position={[10, 8, 6]} intensity={0} castShadow />
159+
160+
<Ground />
161+
</Canvas>
162+
</CanvasContainerPortal>
163+
164+
{false && <Visualizer />}
165+
166+
<a href="https://github.com/platane/yAR-htzee" title="github repository">
167+
<button
168+
style={{
169+
position: "absolute",
170+
width: "40px",
171+
height: "40px",
172+
bottom: "10px",
173+
right: "10px",
174+
pointerEvents: "auto",
175+
zIndex: 1,
176+
}}
177+
>
178+
<GithubLogo />
179+
</button>
180+
</a>
181+
182+
{React.createElement(uiTunnel.Out)}
183+
184+
{hint === "tracking" && <TrackingHint />}
185+
186+
{!readyForRender && (
187+
<Over>
188+
<LoadingScreen
189+
loading={state.type !== "waiting-user-input"}
190+
onStartFlat={startFlat}
191+
onStartWebXR={(webXRSupported === true && startWebXR) || undefined}
192+
onStartXR8={(xr8Supported && startXR8) || undefined}
136193
/>
137-
)}
138-
139-
<React.Suspense fallback={null}>
140-
<Environment />
141-
142-
{
143-
/* preload the dice model */
144-
!readyForGame && (
145-
<Dice
146-
position={[999, 999, 9999]}
147-
scale={[0.0001, 0.0001, 0.0001]}
148-
/>
149-
)
150-
}
151-
152-
{readyForGame && <Game UiPortal={uiTunnel.In} />}
153-
</React.Suspense>
154-
155-
<directionalLight position={[10, 8, 6]} intensity={0} castShadow />
156-
157-
<Ground />
158-
</Canvas>
159-
160-
<OverlayPortal>
161-
{false && <Visualizer />}
162-
163-
<a href="https://github.com/platane/yAR-htzee" title="github">
164-
<button
165-
style={{
166-
position: "absolute",
167-
width: "40px",
168-
height: "40px",
169-
bottom: "10px",
170-
right: "10px",
171-
pointerEvents: "auto",
172-
zIndex: 1,
173-
}}
174-
>
175-
<GithubLogo />
176-
</button>
177-
</a>
178-
179-
{React.createElement(uiTunnel.Out)}
180-
181-
{hint === "tracking" && <TrackingHint />}
182-
183-
{!readyForRender && (
184-
<Over>
185-
<LoadingScreen
186-
loading={state.type !== "waiting-user-input"}
187-
onStartFlat={startFlat}
188-
onStartWebXR={webXRSupported && startWebXR}
189-
onStartXR8={xr8Supported && startXR8}
190-
/>
191-
</Over>
192-
)}
193-
</OverlayPortal>
194+
</Over>
195+
)}
194196
</>
195197
);
196198
};
197199

198-
const OverlayPortal = ({ children }: { children?: any }) =>
199-
createPortal(children, document.getElementById("overlay")!);
200+
const CanvasContainerPortal = ({ children }: { children?: any }) => {
201+
if (typeof document === "undefined") return null;
202+
return createPortal(children, document.getElementById("canvas-container")!);
203+
};
200204

201205
const Over = ({ children }: { children?: any }) => (
202206
<div

src/App/Scene/SelectedDiceHint.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { path as refreshIconPath } from "../Ui/RefreshIcon";
66

77
const springParams = { tension: 120, friction: 8 };
88

9-
const createTexture = async () => {
9+
const createCanvas = async () => {
1010
const width = 256;
1111
const height = 256;
1212

@@ -16,7 +16,7 @@ const createTexture = async () => {
1616

1717
const img = new Image();
1818
const svg = `<svg viewBox="0 0 100 100" width="100" height="100" xmlns="http://www.w3.org/2000/svg"><path d="${refreshIconPath}" fill="#888"/></svg>`;
19-
const src = `data:image/svg+xml;base64,${btoa(svg)}`;
19+
const src = "data:image/svg+xml," + encodeURIComponent(svg);
2020

2121
await new Promise((resolve, reject) => {
2222
img.addEventListener("load", resolve);
@@ -38,10 +38,10 @@ const createTexture = async () => {
3838
return canvas;
3939
};
4040

41-
let texture: THREE.Texture;
42-
createTexture().then((t) => {
43-
texture = new THREE.CanvasTexture(
44-
t,
41+
let texturePromise: Promise<THREE.Texture>;
42+
const createTexture = async () => {
43+
const texture = new THREE.CanvasTexture(
44+
await createCanvas(),
4545
THREE.UVMapping,
4646
THREE.ClampToEdgeWrapping,
4747
THREE.ClampToEdgeWrapping,
@@ -50,9 +50,19 @@ createTexture().then((t) => {
5050
THREE.RGBAFormat
5151
);
5252
texture.generateMipmaps = true;
53-
});
53+
return texture;
54+
};
55+
56+
const useTexture = () => {
57+
const [texture, setTexture] = React.useState<THREE.Texture>();
58+
React.useEffect(() => {
59+
(texturePromise = texturePromise || createTexture()).then(setTexture);
60+
}, []);
61+
return texture;
62+
};
5463

5564
export const SelectedDiceHint = ({ selected }: any) => {
65+
const texture = useTexture();
5666
const spring = React.useRef({ x: 0, v: 0, target: 0 });
5767
spring.current.target = selected ? 1 : 0;
5868

src/App/Ui/Dice.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ export const Dice = ({
1515
lineColor = "#333",
1616
...props
1717
}: Props & {}) => (
18-
<svg width={28} height={28} {...props} viewBox="-50 -50 200 200">
18+
<svg
19+
width={28}
20+
height={28}
21+
{...props}
22+
viewBox="-50 -50 200 200"
23+
xmlns="http://www.w3.org/2000/svg"
24+
>
1925
<rect
2026
x={-40}
2127
y={-40}

0 commit comments

Comments
 (0)