jbilcke-hf HF staff commited on
Commit
637dd5c
1 Parent(s): c32c4ae

improve UI + add caching

Browse files
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 'next/navigation'
 
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:", 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 something changed in the background
69
- if (type !== gameRef?.current) {
70
- console.log("agent type changed! reloading scene")
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 className="flex flex-col w-full pt-4">
 
 
 
 
 
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
- gameRef.current = value as GameType
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">This experimental demo uses shared ressources: each scene may take more than 45s to load.</p>
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
- <div>
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="dark fixed inset-0 flex flex-col items-center bg-stone-900 text-stone-10 overflow-y-scroll">
 
 
 
14
  <Main />
15
  </main>
16
- </div>
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
- let defaulResult: RenderedScene = {
11
- assetUrl: "",
12
- maskBase64: "",
13
- error: "",
14
- segments: []
15
- }
16
-
17
- try {
18
- console.log(`calling ${apiUrl}/render with prompt: ${prompt}`)
19
- const res = await fetch(`${apiUrl}/render`, {
20
- method: "POST",
21
- headers: {
22
- Accept: "application/json",
23
- "Content-Type": "application/json",
24
- // Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
25
- },
26
- body: JSON.stringify({
27
- prompt,
28
- // nbFrames: 8 and nbSteps: 15 --> ~10 sec generation
29
- nbFrames: 1, // when nbFrames is 1, we will only generate static images
30
- nbSteps: 20,
31
- actionnables,
32
- segmentation: "firstframe", // one day we will remove this param, to make it automatic
33
- }),
34
- cache: 'no-store',
35
- // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
36
- // next: { revalidate: 1 }
37
- })
38
-
39
- // console.log("res:", res)
40
- // The return value is *not* serialized
41
- // You can return Date, Map, Set, etc.
42
-
43
- // Recommendation: handle errors
44
- if (res.status !== 200) {
45
- // This will activate the closest `error.js` Error Boundary
46
- throw new Error('Failed to fetch data')
 
 
 
 
 
 
 
 
 
47
  }
48
-
49
- const response = (await res.json()) as RenderedScene
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 = false,
 
15
  }: {
16
  rendered: RenderedScene
17
  onUserAction: (actionnable: string) => void
18
  onUserHover: (actionnable: string) => void
19
- isLoading?: boolean
 
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 showLoaderRef = useRef(true)
 
28
 
29
  useEffect(() => {
30
  if (maskBase64) {
@@ -147,49 +151,30 @@ export const ImageRenderer = ({
147
  }
148
  };
149
 
150
- useEffect(() => {
151
- clearTimeout(timeoutRef.current)
152
- let progress = 0
153
- setProcessPercent(0)
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
- // console.log("still loading..")
166
-
167
- // console.log("updating progress")
168
- progress = progress + 1
169
- setProcessPercent(progress)
170
-
171
- timeoutRef.current = setTimeout(() => {
172
- computeProgress()
173
- }, 1000)
174
- }
175
 
176
- computeProgress()
177
- }, [assetUrl])
 
178
 
179
-
180
  useEffect(() => {
181
- showLoaderRef.current = isLoading || !assetUrl
182
- }, [isLoading, assetUrl])
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
- <img
234
- src={assetUrl}
235
- // src={"data:image/png;base64," + maskBase64}
236
- ref={imgRef}
237
- width="1024px"
238
- height="512px"
239
- className={
240
- [
241
- // "absolute top-0 left-0",
242
- actionnable && !isLoading ? "cursor-pointer" : ""
243
- ].join(" ")
244
- }
245
- onMouseDown={(event) => handleMouseEvent(event, true)}
246
- onMouseMove={handleMouseEvent}
 
 
 
 
 
 
 
 
 
 
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={8}
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.25,
34
 
35
  // Whether to use rounded or flat corners on the ends - can use 'butt' or 'round'
36
- strokeLinecap: 'butt',
37
 
38
  // Text size
39
- textSize: '16px',
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 },