Spaces:
Runtime error
Runtime error
Commit
•
637dd5c
1
Parent(s):
c32c4ae
improve UI + add caching
Browse files- package-lock.json +6 -0
- package.json +1 -0
- src/app/games/city.ts +2 -0
- src/app/games/doom.ts +2 -0
- src/app/games/dungeon.ts +2 -0
- src/app/games/pirates.ts +2 -0
- src/app/games/types.ts +3 -0
- src/app/main.tsx +46 -25
- src/app/page.tsx +10 -3
- src/app/render.ts +53 -46
- src/components/business/image-renderer.tsx +52 -56
- src/components/misc/progress.tsx +8 -8
- src/fonts/Lugrasimo-Regular.woff2 +0 -0
- src/lib/fonts.ts +49 -0
- tailwind.config.js +8 -0
package-lock.json
CHANGED
@@ -8,6 +8,7 @@
|
|
8 |
"name": "video-quest",
|
9 |
"version": "0.0.0",
|
10 |
"dependencies": {
|
|
|
11 |
"@huggingface/agents": "^0.0.4",
|
12 |
"@huggingface/inference": "^2.6.1",
|
13 |
"@radix-ui/react-accordion": "^1.1.2",
|
@@ -189,6 +190,11 @@
|
|
189 |
"react-dom": ">=16.8.0"
|
190 |
}
|
191 |
},
|
|
|
|
|
|
|
|
|
|
|
192 |
"node_modules/@huggingface/agents": {
|
193 |
"version": "0.0.4",
|
194 |
"resolved": "https://registry.npmjs.org/@huggingface/agents/-/agents-0.0.4.tgz",
|
|
|
8 |
"name": "video-quest",
|
9 |
"version": "0.0.0",
|
10 |
"dependencies": {
|
11 |
+
"@gorgonjs/gorgon": "^1.4.1",
|
12 |
"@huggingface/agents": "^0.0.4",
|
13 |
"@huggingface/inference": "^2.6.1",
|
14 |
"@radix-ui/react-accordion": "^1.1.2",
|
|
|
190 |
"react-dom": ">=16.8.0"
|
191 |
}
|
192 |
},
|
193 |
+
"node_modules/@gorgonjs/gorgon": {
|
194 |
+
"version": "1.4.1",
|
195 |
+
"resolved": "https://registry.npmjs.org/@gorgonjs/gorgon/-/gorgon-1.4.1.tgz",
|
196 |
+
"integrity": "sha512-XUTvRODad+uD89CVoLQEi3aOaJC/x9+KqLBKil4a+hKlrDRc6TAoEofn/Kje/S4Q+ylwJRbhZnb98QgiSZxIqw=="
|
197 |
+
},
|
198 |
"node_modules/@huggingface/agents": {
|
199 |
"version": "0.0.4",
|
200 |
"resolved": "https://registry.npmjs.org/@huggingface/agents/-/agents-0.0.4.tgz",
|
package.json
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
"lint": "next lint"
|
10 |
},
|
11 |
"dependencies": {
|
|
|
12 |
"@huggingface/agents": "^0.0.4",
|
13 |
"@huggingface/inference": "^2.6.1",
|
14 |
"@radix-ui/react-accordion": "^1.1.2",
|
|
|
9 |
"lint": "next lint"
|
10 |
},
|
11 |
"dependencies": {
|
12 |
+
"@gorgonjs/gorgon": "^1.4.1",
|
13 |
"@huggingface/agents": "^0.0.4",
|
14 |
"@huggingface/inference": "^2.6.1",
|
15 |
"@radix-ui/react-accordion": "^1.1.2",
|
src/app/games/city.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import { Game } from "./types"
|
2 |
|
3 |
const actions = [
|
@@ -43,6 +44,7 @@ const initialActionnables = [
|
|
43 |
export const game: Game = {
|
44 |
title: "City",
|
45 |
type: "city",
|
|
|
46 |
initialSituation,
|
47 |
initialActionnables,
|
48 |
getScenePrompt: (situation?: string) => [
|
|
|
1 |
+
import { edu } from "@/lib/fonts"
|
2 |
import { Game } from "./types"
|
3 |
|
4 |
const actions = [
|
|
|
44 |
export const game: Game = {
|
45 |
title: "City",
|
46 |
type: "city",
|
47 |
+
className: edu.className,
|
48 |
initialSituation,
|
49 |
initialActionnables,
|
50 |
getScenePrompt: (situation?: string) => [
|
src/app/games/doom.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import { Game } from "./types"
|
2 |
|
3 |
const initialSituation = [
|
@@ -16,6 +17,7 @@ const initialActionnables = [
|
|
16 |
export const game: Game = {
|
17 |
title: "Doom",
|
18 |
type: "doom",
|
|
|
19 |
initialSituation,
|
20 |
initialActionnables,
|
21 |
getScenePrompt: (situation?: string) => [
|
|
|
1 |
+
import { orbitron } from "@/lib/fonts"
|
2 |
import { Game } from "./types"
|
3 |
|
4 |
const initialSituation = [
|
|
|
17 |
export const game: Game = {
|
18 |
title: "Doom",
|
19 |
type: "doom",
|
20 |
+
className: orbitron.className,
|
21 |
initialSituation,
|
22 |
initialActionnables,
|
23 |
getScenePrompt: (situation?: string) => [
|
src/app/games/dungeon.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import { Game, Scene } from "./types"
|
2 |
|
3 |
const actions = [
|
@@ -47,6 +48,7 @@ const initialActionnables = [
|
|
47 |
export const game: Game = {
|
48 |
title: "Dungeon",
|
49 |
type: "dungeon",
|
|
|
50 |
initialSituation,
|
51 |
initialActionnables,
|
52 |
getScenePrompt: (situation?: string) => [
|
|
|
1 |
+
import { amatic } from "@/lib/fonts"
|
2 |
import { Game, Scene } from "./types"
|
3 |
|
4 |
const actions = [
|
|
|
48 |
export const game: Game = {
|
49 |
title: "Dungeon",
|
50 |
type: "dungeon",
|
51 |
+
className: amatic.className,
|
52 |
initialSituation,
|
53 |
initialActionnables,
|
54 |
getScenePrompt: (situation?: string) => [
|
src/app/games/pirates.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import { Game } from "./types"
|
2 |
|
3 |
const actions = [
|
@@ -54,6 +55,7 @@ const initialSituation = [
|
|
54 |
export const game: Game = {
|
55 |
title: "Pirates",
|
56 |
type: "pirates",
|
|
|
57 |
initialSituation,
|
58 |
initialActionnables,
|
59 |
getScenePrompt: (situation?: string) => [
|
|
|
1 |
+
import { lugrasimo } from "@/lib/fonts"
|
2 |
import { Game } from "./types"
|
3 |
|
4 |
const actions = [
|
|
|
55 |
export const game: Game = {
|
56 |
title: "Pirates",
|
57 |
type: "pirates",
|
58 |
+
className: lugrasimo.className,
|
59 |
initialSituation,
|
60 |
initialActionnables,
|
61 |
getScenePrompt: (situation?: string) => [
|
src/app/games/types.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
export type GameType = 'pirates' | 'city' | 'dungeon' | 'doom'
|
2 |
|
3 |
export interface Scene {
|
@@ -8,6 +10,7 @@ export interface Scene {
|
|
8 |
export interface Game {
|
9 |
title: string
|
10 |
type: GameType
|
|
|
11 |
initialSituation: string
|
12 |
initialActionnables: string[]
|
13 |
getScenePrompt: (situation?: string) => string[]
|
|
|
1 |
+
import { FontName } from "@/lib/fonts"
|
2 |
+
|
3 |
export type GameType = 'pirates' | 'city' | 'dungeon' | 'doom'
|
4 |
|
5 |
export interface Scene {
|
|
|
10 |
export interface Game {
|
11 |
title: string
|
12 |
type: GameType
|
13 |
+
className: string
|
14 |
initialSituation: string
|
15 |
initialActionnables: string[]
|
16 |
getScenePrompt: (situation?: string) => string[]
|
src/app/main.tsx
CHANGED
@@ -1,7 +1,8 @@
|
|
1 |
"use client"
|
2 |
|
3 |
import { useEffect, useRef, useState, useTransition } from "react"
|
4 |
-
import { useSearchParams } from
|
|
|
5 |
|
6 |
import { ImageRenderer } from "@/components/business/image-renderer"
|
7 |
|
@@ -16,7 +17,7 @@ import {
|
|
16 |
import { render } from "./render"
|
17 |
|
18 |
import { RenderedScene } from "./types"
|
19 |
-
import { GameType } from "./games/types"
|
20 |
import { defaultGame, games, getGame } from "./games"
|
21 |
import { getBackground } from "@/app/queries/getBackground"
|
22 |
import { getDialogue } from "@/app/queries/getDialogue"
|
@@ -30,11 +31,15 @@ export default function Main() {
|
|
30 |
maskBase64: "",
|
31 |
segments:[]
|
32 |
})
|
|
|
|
|
33 |
const searchParams = useSearchParams()
|
34 |
|
35 |
const requestedGame = (searchParams.get('game') as GameType) || defaultGame
|
36 |
-
console.log("requestedGame:"
|
37 |
const gameRef = useRef<GameType>(requestedGame)
|
|
|
|
|
38 |
const [situation, setSituation] = useState("")
|
39 |
const [scene, setScene] = useState("")
|
40 |
const [dialogue, setDialogue] = useState("")
|
@@ -46,12 +51,7 @@ export default function Main() {
|
|
46 |
setLoading(true)
|
47 |
|
48 |
await startTransition(async () => {
|
49 |
-
|
50 |
-
// console.log(`getting agent..`)
|
51 |
-
// note: we use a ref so that it can be changed in the background
|
52 |
-
const type = gameRef?.current
|
53 |
-
console.log("type:", type)
|
54 |
-
const game = getGame(type)
|
55 |
|
56 |
// console.log(`rendering scene..`)
|
57 |
const newRendered = await render(
|
@@ -65,10 +65,9 @@ export default function Main() {
|
|
65 |
).slice(0, 6) // too many can slow us down it seems
|
66 |
)
|
67 |
|
68 |
-
// detect if
|
69 |
-
if (type !== gameRef?.current) {
|
70 |
-
console.log("
|
71 |
-
setTimeout(() => { loadNextScene() }, 0)
|
72 |
return
|
73 |
}
|
74 |
|
@@ -130,23 +129,44 @@ export default function Main() {
|
|
130 |
|
131 |
const clickables = Array.from(new Set(rendered.segments.map(s => s.label)).values())
|
132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
return (
|
134 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
135 |
<div className="flex flex-col space-y-3 px-2">
|
136 |
<div className="flex flex-row items-center space-x-3">
|
137 |
<label className="flex">Select a story:</label>
|
138 |
<Select
|
139 |
defaultValue={gameRef.current}
|
140 |
-
onValueChange={(value) => {
|
141 |
-
|
142 |
-
setRendered({
|
143 |
-
assetUrl: "",
|
144 |
-
error: "",
|
145 |
-
maskBase64: "",
|
146 |
-
segments:[]
|
147 |
-
})
|
148 |
-
}}>
|
149 |
-
<SelectTrigger className="w-[180px]">
|
150 |
<SelectValue placeholder="Type" />
|
151 |
</SelectTrigger>
|
152 |
<SelectContent>
|
@@ -156,7 +176,7 @@ export default function Main() {
|
|
156 |
</SelectContent>
|
157 |
</Select>
|
158 |
</div>
|
159 |
-
<p className="text-xl">
|
160 |
<div className="flex flex-row">
|
161 |
<div className="text-xl mr-2">🔎 Clickable items:</div>
|
162 |
{clickables.map((clickable, i) =>
|
@@ -172,6 +192,7 @@ export default function Main() {
|
|
172 |
onUserAction={handleUserAction}
|
173 |
onUserHover={setHoveredActionnable}
|
174 |
isLoading={isLoading}
|
|
|
175 |
/>
|
176 |
<p className="text-xl">{dialogue}</p>
|
177 |
</div>
|
|
|
1 |
"use client"
|
2 |
|
3 |
import { useEffect, useRef, useState, useTransition } from "react"
|
4 |
+
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
5 |
+
|
6 |
|
7 |
import { ImageRenderer } from "@/components/business/image-renderer"
|
8 |
|
|
|
17 |
import { render } from "./render"
|
18 |
|
19 |
import { RenderedScene } from "./types"
|
20 |
+
import { Game, GameType } from "./games/types"
|
21 |
import { defaultGame, games, getGame } from "./games"
|
22 |
import { getBackground } from "@/app/queries/getBackground"
|
23 |
import { getDialogue } from "@/app/queries/getDialogue"
|
|
|
31 |
maskBase64: "",
|
32 |
segments:[]
|
33 |
})
|
34 |
+
const router = useRouter()
|
35 |
+
const pathname = usePathname()
|
36 |
const searchParams = useSearchParams()
|
37 |
|
38 |
const requestedGame = (searchParams.get('game') as GameType) || defaultGame
|
39 |
+
console.log("requestedGame: " + requestedGame)
|
40 |
const gameRef = useRef<GameType>(requestedGame)
|
41 |
+
console.log("gameRef.current: " + gameRef.current)
|
42 |
+
const [game, setGame] = useState<Game>(getGame(gameRef.current))
|
43 |
const [situation, setSituation] = useState("")
|
44 |
const [scene, setScene] = useState("")
|
45 |
const [dialogue, setDialogue] = useState("")
|
|
|
51 |
setLoading(true)
|
52 |
|
53 |
await startTransition(async () => {
|
54 |
+
console.log("Rendering a scene for " + game.type)
|
|
|
|
|
|
|
|
|
|
|
55 |
|
56 |
// console.log(`rendering scene..`)
|
57 |
const newRendered = await render(
|
|
|
65 |
).slice(0, 6) // too many can slow us down it seems
|
66 |
)
|
67 |
|
68 |
+
// detect if type game type changed while we were busy
|
69 |
+
if (game.type !== gameRef?.current) {
|
70 |
+
console.log("game type changed! aborting..")
|
|
|
71 |
return
|
72 |
}
|
73 |
|
|
|
129 |
|
130 |
const clickables = Array.from(new Set(rendered.segments.map(s => s.label)).values())
|
131 |
|
132 |
+
const handleSelectGame = (newGameType: GameType) => {
|
133 |
+
gameRef.current = newGameType
|
134 |
+
setGame(getGame(newGameType))
|
135 |
+
setRendered({
|
136 |
+
assetUrl: "",
|
137 |
+
error: "",
|
138 |
+
maskBase64: "",
|
139 |
+
segments:[]
|
140 |
+
})
|
141 |
+
setLoading(true)
|
142 |
+
|
143 |
+
const current = new URLSearchParams(Array.from(searchParams.entries()))
|
144 |
+
current.set("game", newGameType)
|
145 |
+
const search = current.toString()
|
146 |
+
const query = search ? `?${search}` : ""
|
147 |
+
|
148 |
+
// for some reason, this doesn't work?!
|
149 |
+
router.replace(`${pathname}${query}`, { })
|
150 |
+
|
151 |
+
// workaround.. but it is strange that router.replace doesn't work..
|
152 |
+
let newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + search.toString()
|
153 |
+
window.history.pushState({path: newurl}, '', newurl)
|
154 |
+
}
|
155 |
+
|
156 |
return (
|
157 |
+
<div
|
158 |
+
className={[
|
159 |
+
"flex flex-col w-full pt-4",
|
160 |
+
getGame(gameRef.current).className // apply the game theme
|
161 |
+
].join(" ")}
|
162 |
+
>
|
163 |
<div className="flex flex-col space-y-3 px-2">
|
164 |
<div className="flex flex-row items-center space-x-3">
|
165 |
<label className="flex">Select a story:</label>
|
166 |
<Select
|
167 |
defaultValue={gameRef.current}
|
168 |
+
onValueChange={(value) => { handleSelectGame(value as GameType) }}>
|
169 |
+
<SelectTrigger className="text-xl w-[180px]">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
<SelectValue placeholder="Type" />
|
171 |
</SelectTrigger>
|
172 |
<SelectContent>
|
|
|
176 |
</SelectContent>
|
177 |
</Select>
|
178 |
</div>
|
179 |
+
<p className="text-xl">A stable diffusion exploration game. Click on an item to explore a new scene!</p>
|
180 |
<div className="flex flex-row">
|
181 |
<div className="text-xl mr-2">🔎 Clickable items:</div>
|
182 |
{clickables.map((clickable, i) =>
|
|
|
192 |
onUserAction={handleUserAction}
|
193 |
onUserHover={setHoveredActionnable}
|
194 |
isLoading={isLoading}
|
195 |
+
type={game.type}
|
196 |
/>
|
197 |
<p className="text-xl">{dialogue}</p>
|
198 |
</div>
|
src/app/page.tsx
CHANGED
@@ -4,15 +4,22 @@ import Head from "next/head"
|
|
4 |
|
5 |
import Main from "./main"
|
6 |
|
|
|
|
|
7 |
export default async function IndexPage({ params: { ownerId } }: { params: { ownerId: string }}) {
|
8 |
return (
|
9 |
-
|
10 |
<Head>
|
|
|
|
|
11 |
<meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
|
12 |
</Head>
|
13 |
-
<main className=
|
|
|
|
|
|
|
14 |
<Main />
|
15 |
</main>
|
16 |
-
|
17 |
)
|
18 |
}
|
|
|
4 |
|
5 |
import Main from "./main"
|
6 |
|
7 |
+
// https://nextjs.org/docs/pages/building-your-application/optimizing/fonts
|
8 |
+
|
9 |
export default async function IndexPage({ params: { ownerId } }: { params: { ownerId: string }}) {
|
10 |
return (
|
11 |
+
<>
|
12 |
<Head>
|
13 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
14 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" crossOrigin="anonymous" />
|
15 |
<meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
|
16 |
</Head>
|
17 |
+
<main className={
|
18 |
+
`dark fixed inset-0 flex flex-col items-center
|
19 |
+
bg-stone-900 text-stone-10 overflow-y-scroll
|
20 |
+
`}>
|
21 |
<Main />
|
22 |
</main>
|
23 |
+
</>
|
24 |
)
|
25 |
}
|
src/app/render.ts
CHANGED
@@ -1,56 +1,63 @@
|
|
1 |
"use server"
|
2 |
|
|
|
|
|
3 |
import { RenderedScene } from "./types"
|
4 |
|
5 |
// note: there is no / at the end in the variable
|
6 |
// so we have to add it ourselves if needed
|
7 |
const apiUrl = process.env.RENDERING_ENGINE_API
|
8 |
|
|
|
|
|
|
|
9 |
export async function render(prompt: string, actionnables: string[] = []) {
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
}
|
48 |
-
|
49 |
-
|
50 |
-
// console.log("response:", response)
|
51 |
-
return response
|
52 |
-
} catch (err) {
|
53 |
-
console.error(err)
|
54 |
-
return defaulResult
|
55 |
-
}
|
56 |
-
}
|
|
|
1 |
"use server"
|
2 |
|
3 |
+
import Gorgon from "@gorgonjs/gorgon"
|
4 |
+
|
5 |
import { RenderedScene } from "./types"
|
6 |
|
7 |
// note: there is no / at the end in the variable
|
8 |
// so we have to add it ourselves if needed
|
9 |
const apiUrl = process.env.RENDERING_ENGINE_API
|
10 |
|
11 |
+
|
12 |
+
const cacheDurationInSec = 30 * 60 // 30 minutes
|
13 |
+
|
14 |
export async function render(prompt: string, actionnables: string[] = []) {
|
15 |
+
return await Gorgon.get(`render/${JSON.stringify(prompt, actionnables)}`, async () => {
|
16 |
+
let defaulResult: RenderedScene = {
|
17 |
+
assetUrl: "",
|
18 |
+
maskBase64: "",
|
19 |
+
error: "",
|
20 |
+
segments: []
|
21 |
+
}
|
22 |
+
|
23 |
+
try {
|
24 |
+
console.log(`calling ${apiUrl}/render with prompt: ${prompt}`)
|
25 |
+
const res = await fetch(`${apiUrl}/render`, {
|
26 |
+
method: "POST",
|
27 |
+
headers: {
|
28 |
+
Accept: "application/json",
|
29 |
+
"Content-Type": "application/json",
|
30 |
+
// Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
|
31 |
+
},
|
32 |
+
body: JSON.stringify({
|
33 |
+
prompt,
|
34 |
+
// nbFrames: 8 and nbSteps: 15 --> ~10 sec generation
|
35 |
+
nbFrames: 1, // when nbFrames is 1, we will only generate static images
|
36 |
+
nbSteps: 20,
|
37 |
+
actionnables,
|
38 |
+
segmentation: "firstframe", // one day we will remove this param, to make it automatic
|
39 |
+
}),
|
40 |
+
cache: 'no-store',
|
41 |
+
// we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
|
42 |
+
// next: { revalidate: 1 }
|
43 |
+
})
|
44 |
+
|
45 |
+
// console.log("res:", res)
|
46 |
+
// The return value is *not* serialized
|
47 |
+
// You can return Date, Map, Set, etc.
|
48 |
+
|
49 |
+
// Recommendation: handle errors
|
50 |
+
if (res.status !== 200) {
|
51 |
+
// This will activate the closest `error.js` Error Boundary
|
52 |
+
throw new Error('Failed to fetch data')
|
53 |
+
}
|
54 |
+
|
55 |
+
const response = (await res.json()) as RenderedScene
|
56 |
+
// console.log("response:", response)
|
57 |
+
return response
|
58 |
+
} catch (err) {
|
59 |
+
console.error(err)
|
60 |
+
return defaulResult
|
61 |
}
|
62 |
+
}, cacheDurationInSec * 1000)
|
63 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/components/business/image-renderer.tsx
CHANGED
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react"
|
|
2 |
|
3 |
import { ImageSegment, RenderedScene } from "@/app/types"
|
4 |
import { ProgressBar } from "../misc/progress"
|
|
|
5 |
|
6 |
export const ImageRenderer = ({
|
7 |
rendered: {
|
@@ -11,12 +12,14 @@ export const ImageRenderer = ({
|
|
11 |
},
|
12 |
onUserAction,
|
13 |
onUserHover,
|
14 |
-
isLoading
|
|
|
15 |
}: {
|
16 |
rendered: RenderedScene
|
17 |
onUserAction: (actionnable: string) => void
|
18 |
onUserHover: (actionnable: string) => void
|
19 |
-
isLoading
|
|
|
20 |
}) => {
|
21 |
const timeoutRef = useRef<any>()
|
22 |
const imgRef = useRef<HTMLImageElement | null>(null)
|
@@ -24,7 +27,8 @@ export const ImageRenderer = ({
|
|
24 |
const contextRef = useRef<CanvasRenderingContext2D | null>(null)
|
25 |
const [actionnable, setActionnable] = useState<string>("")
|
26 |
const [progressPercent, setProcessPercent] = useState(0)
|
27 |
-
const
|
|
|
28 |
|
29 |
useEffect(() => {
|
30 |
if (maskBase64) {
|
@@ -147,49 +151,30 @@ export const ImageRenderer = ({
|
|
147 |
}
|
148 |
};
|
149 |
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
// note: when everything is fine, it takes about 45 seconds to render a new scene
|
155 |
-
|
156 |
-
const computeProgress = async () => {
|
157 |
-
if (!showLoaderRef.current) {
|
158 |
-
console.log("Asked to hide the loader")
|
159 |
-
progress = 100
|
160 |
-
setProcessPercent(100)
|
161 |
-
clearTimeout(timeoutRef.current)
|
162 |
-
return
|
163 |
-
}
|
164 |
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
setProcessPercent(progress)
|
170 |
-
|
171 |
-
timeoutRef.current = setTimeout(() => {
|
172 |
-
computeProgress()
|
173 |
-
}, 1000)
|
174 |
-
}
|
175 |
|
176 |
-
|
177 |
-
|
|
|
178 |
|
179 |
-
|
180 |
useEffect(() => {
|
181 |
-
|
182 |
-
|
183 |
-
|
|
|
|
|
|
|
|
|
|
|
184 |
|
185 |
-
if (!assetUrl) {
|
186 |
-
return <div className="flex w-full pt-8 items-center justify-center text-center">
|
187 |
-
<ProgressBar
|
188 |
-
text="⌛"
|
189 |
-
progressPercentage={progressPercent}
|
190 |
-
/>
|
191 |
-
</div>
|
192 |
-
}
|
193 |
|
194 |
/*
|
195 |
<img
|
@@ -226,26 +211,37 @@ export const ImageRenderer = ({
|
|
226 |
return (
|
227 |
<div className={[
|
228 |
"w-full py-8 px-2",
|
229 |
-
isLoading ? "animate-pulse" : ""
|
230 |
].join(" ")
|
231 |
}>
|
232 |
<div className="relative w-full">
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
/>
|
248 |
</div>
|
|
|
249 |
</div>
|
250 |
)
|
251 |
}
|
|
|
2 |
|
3 |
import { ImageSegment, RenderedScene } from "@/app/types"
|
4 |
import { ProgressBar } from "../misc/progress"
|
5 |
+
import { GameType } from "@/app/games/types"
|
6 |
|
7 |
export const ImageRenderer = ({
|
8 |
rendered: {
|
|
|
12 |
},
|
13 |
onUserAction,
|
14 |
onUserHover,
|
15 |
+
isLoading,
|
16 |
+
type,
|
17 |
}: {
|
18 |
rendered: RenderedScene
|
19 |
onUserAction: (actionnable: string) => void
|
20 |
onUserHover: (actionnable: string) => void
|
21 |
+
isLoading: boolean
|
22 |
+
type: GameType
|
23 |
}) => {
|
24 |
const timeoutRef = useRef<any>()
|
25 |
const imgRef = useRef<HTMLImageElement | null>(null)
|
|
|
27 |
const contextRef = useRef<CanvasRenderingContext2D | null>(null)
|
28 |
const [actionnable, setActionnable] = useState<string>("")
|
29 |
const [progressPercent, setProcessPercent] = useState(0)
|
30 |
+
const progressRef = useRef(0)
|
31 |
+
const isLoadingRef = useRef(isLoading)
|
32 |
|
33 |
useEffect(() => {
|
34 |
if (maskBase64) {
|
|
|
151 |
}
|
152 |
};
|
153 |
|
154 |
+
const updateProgressBar = () => {
|
155 |
+
const duration = 1000 // 1 sec
|
156 |
+
const frequency = 200 // 200ms
|
157 |
+
const nbUpdatesPerSec = duration / frequency // 5x per second
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
|
159 |
+
// normally it takes 45, and we will try to go below,
|
160 |
+
// but to be safe let's set the counter a 1 min
|
161 |
+
const nbSeconds = 45 // 1 min
|
162 |
+
const amountInPercent = 100 / (nbUpdatesPerSec * nbSeconds) // 0.333
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
|
164 |
+
progressRef.current = Math.min(100, progressRef.current + amountInPercent)
|
165 |
+
setProcessPercent(progressRef.current)
|
166 |
+
}
|
167 |
|
|
|
168 |
useEffect(() => {
|
169 |
+
clearInterval(timeoutRef.current)
|
170 |
+
isLoadingRef.current = isLoading
|
171 |
+
progressRef.current = 0
|
172 |
+
setProcessPercent(0)
|
173 |
+
if (isLoading) {
|
174 |
+
timeoutRef.current = setInterval(updateProgressBar, 200)
|
175 |
+
}
|
176 |
+
}, [isLoading, assetUrl, type])
|
177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
|
179 |
/*
|
180 |
<img
|
|
|
211 |
return (
|
212 |
<div className={[
|
213 |
"w-full py-8 px-2",
|
214 |
+
// isLoading ? "animate-pulse" : ""
|
215 |
].join(" ")
|
216 |
}>
|
217 |
<div className="relative w-full">
|
218 |
+
{assetUrl
|
219 |
+
? <img
|
220 |
+
src={assetUrl}
|
221 |
+
// src={"data:image/png;base64," + maskBase64}
|
222 |
+
ref={imgRef}
|
223 |
+
width="1024px"
|
224 |
+
height="512px"
|
225 |
+
className={
|
226 |
+
[
|
227 |
+
// "absolute top-0 left-0",
|
228 |
+
actionnable && !isLoading ? "cursor-pointer" : ""
|
229 |
+
].join(" ")
|
230 |
+
}
|
231 |
+
onMouseDown={(event) => handleMouseEvent(event, true)}
|
232 |
+
onMouseMove={handleMouseEvent}
|
233 |
+
/>
|
234 |
+
: null}
|
235 |
+
</div>
|
236 |
+
|
237 |
+
{isLoading
|
238 |
+
? <div className="fixed flex w-20 h-20 bottom-8 right-0 mr-8">
|
239 |
+
<ProgressBar
|
240 |
+
text="⌛"
|
241 |
+
progressPercentage={progressPercent}
|
242 |
/>
|
243 |
</div>
|
244 |
+
: null}
|
245 |
</div>
|
246 |
)
|
247 |
}
|
src/components/misc/progress.tsx
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
"use client"
|
2 |
|
3 |
-
import { CircularProgressbar } from "react-circular-progressbar"
|
4 |
import "react-circular-progressbar/dist/styles.css"
|
5 |
|
6 |
export function ProgressBar ({
|
@@ -23,20 +23,20 @@ export function ProgressBar ({
|
|
23 |
text={text || ""}
|
24 |
|
25 |
// Width of circular line relative to total width of component, a value from 0-100. Default: 8.
|
26 |
-
strokeWidth={
|
27 |
|
28 |
-
|
29 |
// As a convenience, you can use buildStyles to configure the most common style changes:
|
30 |
|
31 |
styles={buildStyles({
|
32 |
// Rotation of path and trail, in number of turns (0-1)
|
33 |
-
rotation: 0
|
34 |
|
35 |
// Whether to use rounded or flat corners on the ends - can use 'butt' or 'round'
|
36 |
-
strokeLinecap: '
|
37 |
|
38 |
// Text size
|
39 |
-
textSize: '
|
40 |
|
41 |
// How long animation takes to go from one percentage to another, in seconds
|
42 |
pathTransitionDuration: 0.5,
|
@@ -45,12 +45,12 @@ export function ProgressBar ({
|
|
45 |
// pathTransition: 'none',
|
46 |
|
47 |
// Colors
|
48 |
-
pathColor: `rgba(62, 152, 199, ${percentage / 100})`,
|
49 |
textColor: '#f88',
|
50 |
trailColor: '#d6d6d6',
|
51 |
backgroundColor: '#3e98c7',
|
52 |
})}
|
53 |
-
|
54 |
/>
|
55 |
</div>
|
56 |
)
|
|
|
1 |
"use client"
|
2 |
|
3 |
+
import { CircularProgressbar, buildStyles } from "react-circular-progressbar"
|
4 |
import "react-circular-progressbar/dist/styles.css"
|
5 |
|
6 |
export function ProgressBar ({
|
|
|
23 |
text={text || ""}
|
24 |
|
25 |
// Width of circular line relative to total width of component, a value from 0-100. Default: 8.
|
26 |
+
strokeWidth={12}
|
27 |
|
28 |
+
|
29 |
// As a convenience, you can use buildStyles to configure the most common style changes:
|
30 |
|
31 |
styles={buildStyles({
|
32 |
// Rotation of path and trail, in number of turns (0-1)
|
33 |
+
rotation: 0,
|
34 |
|
35 |
// Whether to use rounded or flat corners on the ends - can use 'butt' or 'round'
|
36 |
+
strokeLinecap: 'round',
|
37 |
|
38 |
// Text size
|
39 |
+
textSize: '40px',
|
40 |
|
41 |
// How long animation takes to go from one percentage to another, in seconds
|
42 |
pathTransitionDuration: 0.5,
|
|
|
45 |
// pathTransition: 'none',
|
46 |
|
47 |
// Colors
|
48 |
+
// pathColor: `rgba(62, 152, 199, ${percentage / 100})`,
|
49 |
textColor: '#f88',
|
50 |
trailColor: '#d6d6d6',
|
51 |
backgroundColor: '#3e98c7',
|
52 |
})}
|
53 |
+
|
54 |
/>
|
55 |
</div>
|
56 |
)
|
src/fonts/Lugrasimo-Regular.woff2
ADDED
Binary file (19.1 kB). View file
|
|
src/lib/fonts.ts
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Inter, Edu_SA_Beginner, Orbitron, Amatic_SC } from "next/font/google"
|
2 |
+
import localFont from "next/font/local"
|
3 |
+
|
4 |
+
export const inter = Inter({
|
5 |
+
subsets: ["latin"],
|
6 |
+
variable: "--font-inter",
|
7 |
+
})
|
8 |
+
|
9 |
+
export const edu = Edu_SA_Beginner({
|
10 |
+
subsets: ["latin"],
|
11 |
+
variable: "--font-edu",
|
12 |
+
})
|
13 |
+
|
14 |
+
export const orbitron = Orbitron({
|
15 |
+
subsets: ["latin"],
|
16 |
+
variable: "--font-orbitron",
|
17 |
+
})
|
18 |
+
|
19 |
+
export const amatic = Amatic_SC({
|
20 |
+
subsets: ["latin"],
|
21 |
+
weight: "400",
|
22 |
+
variable: "--font-amatic"
|
23 |
+
})
|
24 |
+
|
25 |
+
export const lugrasimo = localFont({
|
26 |
+
src: "../fonts/Lugrasimo-Regular.woff2",
|
27 |
+
variable: "--font-lugrasimo"
|
28 |
+
})
|
29 |
+
|
30 |
+
// https://fonts.google.com/specimen/Amatic+SC
|
31 |
+
// https://fonts.google.com/specimen/Orbitron
|
32 |
+
// https://fonts.google.com/specimen/Edu+SA+Beginner
|
33 |
+
// https://fonts.google.com/specimen/Tektur
|
34 |
+
|
35 |
+
// https://nextjs.org/docs/pages/building-your-application/optimizing/fonts
|
36 |
+
// If loading a variable font, you don"t need to specify the font weight
|
37 |
+
export const fontList = [
|
38 |
+
inter,
|
39 |
+
edu,
|
40 |
+
orbitron,
|
41 |
+
amatic,
|
42 |
+
lugrasimo,
|
43 |
+
]
|
44 |
+
|
45 |
+
export const classNames = fontList.map(font => font.className)
|
46 |
+
|
47 |
+
export const className = classNames.join(" ")
|
48 |
+
|
49 |
+
export type FontName = "font-inter" | "font-sans" | "font-edu" | "font-orbitron" | "font-amatic" | "font-lugrasimo"
|
tailwind.config.js
CHANGED
@@ -6,6 +6,7 @@ module.exports = {
|
|
6 |
'./components/**/*.{ts,tsx}',
|
7 |
'./app/**/*.{ts,tsx}',
|
8 |
'./src/**/*.{ts,tsx}',
|
|
|
9 |
],
|
10 |
theme: {
|
11 |
container: {
|
@@ -16,6 +17,13 @@ module.exports = {
|
|
16 |
},
|
17 |
},
|
18 |
extend: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
keyframes: {
|
20 |
"accordion-down": {
|
21 |
from: { height: 0 },
|
|
|
6 |
'./components/**/*.{ts,tsx}',
|
7 |
'./app/**/*.{ts,tsx}',
|
8 |
'./src/**/*.{ts,tsx}',
|
9 |
+
'./src/lib/fonts.ts'
|
10 |
],
|
11 |
theme: {
|
12 |
container: {
|
|
|
17 |
},
|
18 |
},
|
19 |
extend: {
|
20 |
+
fontFamily: {
|
21 |
+
sans: ['var(--font-inter)'],
|
22 |
+
edu: ['var(--font-edu)'],
|
23 |
+
orbitron: ['var(--font-orbitron)'],
|
24 |
+
amatic: ['var(--font-amatic)'],
|
25 |
+
lugrasimo: ['var(--font-lugrasimo)'],
|
26 |
+
},
|
27 |
keyframes: {
|
28 |
"accordion-down": {
|
29 |
from: { height: 0 },
|