create-pixi-vn 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/template-react-vite-muijoy/package-lock.json +35 -0
  3. package/template-react-vite-muijoy/package.json +1 -0
  4. package/template-react-vite-muijoy/src/components/menus/save-menu/save-slots.tsx +2 -1
  5. package/template-react-vite-muijoy/src/components/menus/settings/menus/history.tsx +15 -6
  6. package/template-react-vite-muijoy/src/components/{menus → scrrens}/narration/index.tsx +1 -1
  7. package/template-react-vite-muijoy/src/components/{menus → scrrens}/narration/narration-cards.tsx +3 -2
  8. package/template-react-vite-muijoy/src/components/ui/image.tsx +50 -0
  9. package/template-react-vite-muijoy/src/lib/hooks/image-hooks.ts +11 -0
  10. package/template-react-vite-muijoy/src/routes/game/narration.tsx +2 -2
  11. package/template-react-vite-muijoy/src/routes/game.tsx +1 -1
  12. package/template-react-vite-muijoy-ink/package-lock.json +35 -0
  13. package/template-react-vite-muijoy-ink/package.json +1 -0
  14. package/template-react-vite-muijoy-ink/src/components/menus/save-menu/save-slots.tsx +2 -1
  15. package/template-react-vite-muijoy-ink/src/components/menus/settings/menus/history.tsx +15 -6
  16. package/{template-react-vite-muijoy-ink-tauri/src/components/menus → template-react-vite-muijoy-ink/src/components/scrrens}/narration/index.tsx +1 -1
  17. package/template-react-vite-muijoy-ink/src/components/{menus → scrrens}/narration/narration-cards.tsx +3 -2
  18. package/template-react-vite-muijoy-ink/src/components/ui/image.tsx +50 -0
  19. package/template-react-vite-muijoy-ink/src/lib/hooks/image-hooks.ts +11 -0
  20. package/template-react-vite-muijoy-ink/src/routes/game/narration.tsx +2 -2
  21. package/template-react-vite-muijoy-ink/src/routes/game.tsx +1 -1
  22. package/template-react-vite-muijoy-ink-tauri/_gitignore +2 -0
  23. package/template-react-vite-muijoy-ink-tauri/package-lock.json +35 -0
  24. package/template-react-vite-muijoy-ink-tauri/package.json +1 -0
  25. package/template-react-vite-muijoy-ink-tauri/src/components/menus/save-menu/save-slots.tsx +2 -1
  26. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/menus/history.tsx +15 -6
  27. package/{template-react-vite-muijoy-ink/src/components/menus → template-react-vite-muijoy-ink-tauri/src/components/scrrens}/narration/index.tsx +1 -1
  28. package/{template-react-vite-muijoy-tauri/src/components/menus → template-react-vite-muijoy-ink-tauri/src/components/scrrens}/narration/narration-cards.tsx +3 -2
  29. package/template-react-vite-muijoy-ink-tauri/src/components/ui/image.tsx +50 -0
  30. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/image-hooks.ts +11 -0
  31. package/template-react-vite-muijoy-ink-tauri/src/routes/game/narration.tsx +2 -2
  32. package/template-react-vite-muijoy-ink-tauri/src/routes/game.tsx +1 -1
  33. package/template-react-vite-muijoy-tauri/_gitignore +2 -0
  34. package/template-react-vite-muijoy-tauri/package-lock.json +35 -0
  35. package/template-react-vite-muijoy-tauri/package.json +1 -0
  36. package/template-react-vite-muijoy-tauri/src/components/menus/save-menu/save-slots.tsx +2 -1
  37. package/template-react-vite-muijoy-tauri/src/components/menus/settings/menus/history.tsx +15 -6
  38. package/template-react-vite-muijoy-tauri/src/components/{menus → scrrens}/narration/index.tsx +1 -1
  39. package/{template-react-vite-muijoy-ink-tauri/src/components/menus → template-react-vite-muijoy-tauri/src/components/scrrens}/narration/narration-cards.tsx +3 -2
  40. package/template-react-vite-muijoy-tauri/src/components/ui/image.tsx +50 -0
  41. package/template-react-vite-muijoy-tauri/src/lib/hooks/image-hooks.ts +11 -0
  42. package/template-react-vite-muijoy-tauri/src/routes/game/narration.tsx +2 -2
  43. package/template-react-vite-muijoy-tauri/src/routes/game.tsx +1 -1
  44. /package/template-react-vite-muijoy/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  45. /package/template-react-vite-muijoy/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  46. /package/template-react-vite-muijoy/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
  47. /package/template-react-vite-muijoy-ink/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  48. /package/template-react-vite-muijoy-ink/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  49. /package/template-react-vite-muijoy-ink/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
  50. /package/template-react-vite-muijoy-ink-tauri/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  51. /package/template-react-vite-muijoy-ink-tauri/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  52. /package/template-react-vite-muijoy-ink-tauri/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
  53. /package/template-react-vite-muijoy-tauri/src/components/{menus → modals}/quick-actions-wheel.tsx +0 -0
  54. /package/template-react-vite-muijoy-tauri/src/components/{menus/quick-tools.tsx → quick-tools.tsx} +0 -0
  55. /package/template-react-vite-muijoy-tauri/src/components/{menus → scrrens}/narration/click-overlay.tsx +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-pixi-vn",
3
3
  "description": "Create a new Pixi’VN project",
4
- "version": "2.0.0",
4
+ "version": "2.0.2",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
7
7
  "author": "DRincs-Productions",
@@ -25,6 +25,7 @@
25
25
  "@tanstack/react-store": "latest",
26
26
  "@tanstack/router-plugin": "latest",
27
27
  "@tanstack/store": "latest",
28
+ "@unpic/react": "^1.0.2",
28
29
  "class-variance-authority": "^0.7.1",
29
30
  "clsx": "^2.1.1",
30
31
  "embla-carousel-react": "^8.6.0",
@@ -6902,6 +6903,34 @@
6902
6903
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
6903
6904
  "license": "ISC"
6904
6905
  },
6906
+ "node_modules/@unpic/core": {
6907
+ "version": "1.0.3",
6908
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
6909
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
6910
+ "license": "MIT",
6911
+ "dependencies": {
6912
+ "unpic": "^4.2.2"
6913
+ }
6914
+ },
6915
+ "node_modules/@unpic/react": {
6916
+ "version": "1.0.2",
6917
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
6918
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
6919
+ "license": "MIT",
6920
+ "dependencies": {
6921
+ "@unpic/core": "^1.0.3"
6922
+ },
6923
+ "peerDependencies": {
6924
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
6925
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
6926
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
6927
+ },
6928
+ "peerDependenciesMeta": {
6929
+ "next": {
6930
+ "optional": true
6931
+ }
6932
+ }
6933
+ },
6905
6934
  "node_modules/@vite-pwa/assets-generator": {
6906
6935
  "version": "1.0.2",
6907
6936
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -16982,6 +17011,12 @@
16982
17011
  "node": ">= 10.0.0"
16983
17012
  }
16984
17013
  },
17014
+ "node_modules/unpic": {
17015
+ "version": "4.2.2",
17016
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17017
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17018
+ "license": "MIT"
17019
+ },
16985
17020
  "node_modules/unpipe": {
16986
17021
  "version": "1.0.0",
16987
17022
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -34,6 +34,7 @@
34
34
  "@tanstack/react-store": "latest",
35
35
  "@tanstack/router-plugin": "latest",
36
36
  "@tanstack/store": "latest",
37
+ "@unpic/react": "^1.0.2",
37
38
  "class-variance-authority": "^0.7.1",
38
39
  "clsx": "^2.1.1",
39
40
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";
@@ -26,6 +26,7 @@
26
26
  "@tanstack/react-store": "latest",
27
27
  "@tanstack/router-plugin": "latest",
28
28
  "@tanstack/store": "latest",
29
+ "@unpic/react": "^1.0.2",
29
30
  "class-variance-authority": "^0.7.1",
30
31
  "clsx": "^2.1.1",
31
32
  "embla-carousel-react": "^8.6.0",
@@ -7119,6 +7120,34 @@
7119
7120
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
7120
7121
  "license": "ISC"
7121
7122
  },
7123
+ "node_modules/@unpic/core": {
7124
+ "version": "1.0.3",
7125
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
7126
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
7127
+ "license": "MIT",
7128
+ "dependencies": {
7129
+ "unpic": "^4.2.2"
7130
+ }
7131
+ },
7132
+ "node_modules/@unpic/react": {
7133
+ "version": "1.0.2",
7134
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
7135
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
7136
+ "license": "MIT",
7137
+ "dependencies": {
7138
+ "@unpic/core": "^1.0.3"
7139
+ },
7140
+ "peerDependencies": {
7141
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
7142
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
7143
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
7144
+ },
7145
+ "peerDependenciesMeta": {
7146
+ "next": {
7147
+ "optional": true
7148
+ }
7149
+ }
7150
+ },
7122
7151
  "node_modules/@vite-pwa/assets-generator": {
7123
7152
  "version": "1.0.2",
7124
7153
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -17208,6 +17237,12 @@
17208
17237
  "node": ">= 10.0.0"
17209
17238
  }
17210
17239
  },
17240
+ "node_modules/unpic": {
17241
+ "version": "4.2.2",
17242
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17243
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17244
+ "license": "MIT"
17245
+ },
17211
17246
  "node_modules/unpipe": {
17212
17247
  "version": "1.0.0",
17213
17248
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -35,6 +35,7 @@
35
35
  "@tanstack/react-store": "latest",
36
36
  "@tanstack/router-plugin": "latest",
37
37
  "@tanstack/store": "latest",
38
+ "@unpic/react": "^1.0.2",
38
39
  "class-variance-authority": "^0.7.1",
39
40
  "clsx": "^2.1.1",
40
41
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";
@@ -26,6 +26,8 @@ public/ink-json
26
26
 
27
27
  # Tauri
28
28
  *Cargo.lock
29
+ src-tauri/target
30
+ src-tauri/gen
29
31
 
30
32
  # Steam redistributable libraries (copied automatically by build.rs)
31
33
  src-tauri/steam_api64.dll
@@ -28,6 +28,7 @@
28
28
  "@tanstack/store": "latest",
29
29
  "@tauri-apps/api": "^2",
30
30
  "@tauri-apps/plugin-opener": "^2",
31
+ "@unpic/react": "^1.0.2",
31
32
  "class-variance-authority": "^0.7.1",
32
33
  "clsx": "^2.1.1",
33
34
  "embla-carousel-react": "^8.6.0",
@@ -7369,6 +7370,34 @@
7369
7370
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
7370
7371
  "license": "ISC"
7371
7372
  },
7373
+ "node_modules/@unpic/core": {
7374
+ "version": "1.0.3",
7375
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
7376
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
7377
+ "license": "MIT",
7378
+ "dependencies": {
7379
+ "unpic": "^4.2.2"
7380
+ }
7381
+ },
7382
+ "node_modules/@unpic/react": {
7383
+ "version": "1.0.2",
7384
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
7385
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
7386
+ "license": "MIT",
7387
+ "dependencies": {
7388
+ "@unpic/core": "^1.0.3"
7389
+ },
7390
+ "peerDependencies": {
7391
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
7392
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
7393
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
7394
+ },
7395
+ "peerDependenciesMeta": {
7396
+ "next": {
7397
+ "optional": true
7398
+ }
7399
+ }
7400
+ },
7372
7401
  "node_modules/@vite-pwa/assets-generator": {
7373
7402
  "version": "1.0.2",
7374
7403
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -17458,6 +17487,12 @@
17458
17487
  "node": ">= 10.0.0"
17459
17488
  }
17460
17489
  },
17490
+ "node_modules/unpic": {
17491
+ "version": "4.2.2",
17492
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17493
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17494
+ "license": "MIT"
17495
+ },
17461
17496
  "node_modules/unpipe": {
17462
17497
  "version": "1.0.0",
17463
17498
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -42,6 +42,7 @@
42
42
  "@tanstack/store": "latest",
43
43
  "@tauri-apps/api": "^2",
44
44
  "@tauri-apps/plugin-opener": "^2",
45
+ "@unpic/react": "^1.0.2",
45
46
  "class-variance-authority": "^0.7.1",
46
47
  "clsx": "^2.1.1",
47
48
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";
@@ -25,6 +25,8 @@ public/assets
25
25
 
26
26
  # Tauri
27
27
  *Cargo.lock
28
+ src-tauri/target
29
+ src-tauri/gen
28
30
 
29
31
  # Steam redistributable libraries (copied automatically by build.rs)
30
32
  src-tauri/steam_api64.dll
@@ -27,6 +27,7 @@
27
27
  "@tanstack/store": "latest",
28
28
  "@tauri-apps/api": "^2",
29
29
  "@tauri-apps/plugin-opener": "^2",
30
+ "@unpic/react": "^1.0.2",
30
31
  "class-variance-authority": "^0.7.1",
31
32
  "clsx": "^2.1.1",
32
33
  "embla-carousel-react": "^8.6.0",
@@ -7152,6 +7153,34 @@
7152
7153
  "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
7153
7154
  "license": "ISC"
7154
7155
  },
7156
+ "node_modules/@unpic/core": {
7157
+ "version": "1.0.3",
7158
+ "resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
7159
+ "integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
7160
+ "license": "MIT",
7161
+ "dependencies": {
7162
+ "unpic": "^4.2.2"
7163
+ }
7164
+ },
7165
+ "node_modules/@unpic/react": {
7166
+ "version": "1.0.2",
7167
+ "resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
7168
+ "integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
7169
+ "license": "MIT",
7170
+ "dependencies": {
7171
+ "@unpic/core": "^1.0.3"
7172
+ },
7173
+ "peerDependencies": {
7174
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
7175
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
7176
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
7177
+ },
7178
+ "peerDependenciesMeta": {
7179
+ "next": {
7180
+ "optional": true
7181
+ }
7182
+ }
7183
+ },
7155
7184
  "node_modules/@vite-pwa/assets-generator": {
7156
7185
  "version": "1.0.2",
7157
7186
  "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -17232,6 +17261,12 @@
17232
17261
  "node": ">= 10.0.0"
17233
17262
  }
17234
17263
  },
17264
+ "node_modules/unpic": {
17265
+ "version": "4.2.2",
17266
+ "resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
17267
+ "integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==",
17268
+ "license": "MIT"
17269
+ },
17235
17270
  "node_modules/unpipe": {
17236
17271
  "version": "1.0.0",
17237
17272
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -41,6 +41,7 @@
41
41
  "@tanstack/store": "latest",
42
42
  "@tauri-apps/api": "^2",
43
43
  "@tauri-apps/plugin-opener": "^2",
44
+ "@unpic/react": "^1.0.2",
44
45
  "class-variance-authority": "^0.7.1",
45
46
  "clsx": "^2.1.1",
46
47
  "embla-carousel-react": "^8.6.0",
@@ -1,5 +1,6 @@
1
1
  import { AspectRatio } from "@/components/ui/aspect-ratio";
2
2
  import { Button } from "@/components/ui/button";
3
+ import { Image } from "@/components/ui/image";
3
4
  import { Skeleton } from "@/components/ui/skeleton";
4
5
  import { overlayTextShadowClass } from "@/constants";
5
6
  import { useSaveActions } from "@/lib/hooks/save-hooks";
@@ -49,7 +50,7 @@ export function SaveSlot({ saveId }: { saveId: number }) {
49
50
  className="m-2 overflow-hidden sm:m-4 md:m-2 lg:m-4 cursor-pointer"
50
51
  onClick={() => handleLoad({ ...saveData, id: saveId })}
51
52
  >
52
- <img
53
+ <Image
53
54
  src={saveData.image}
54
55
  alt={saveData.name}
55
56
  className="absolute inset-0 size-full object-contain rounded-lg"
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/components/ui/item";
11
11
  import { Kbd } from "@/components/ui/kbd";
12
12
  import { ScrollArea } from "@/components/ui/scroll-area";
13
+ import { useImageSrc } from "@/lib/hooks/image-hooks";
13
14
  import { useQueryNarrativeHistory } from "@/lib/query/interface-query";
14
15
  import { cn } from "@/lib/utils";
15
16
  import { useDebouncedValue } from "@tanstack/react-pacer";
@@ -20,6 +21,16 @@ import Markdown from "react-markdown";
20
21
  import rehypeRaw from "rehype-raw";
21
22
  import remarkGfm from "remark-gfm";
22
23
 
24
+ function HistoryItemAvatar({ icon, character }: { icon?: string; character: string }) {
25
+ const resolvedSrc = useImageSrc(icon);
26
+ return (
27
+ <Avatar size="sm">
28
+ <AvatarImage src={resolvedSrc} loading="lazy" />
29
+ <AvatarFallback>{character.slice(0, 1).toUpperCase()}</AvatarFallback>
30
+ </Avatar>
31
+ );
32
+ }
33
+
23
34
  export function HistoryList({ searchString }: { searchString?: string }) {
24
35
  const { data = [] } = useQueryNarrativeHistory({ searchString });
25
36
 
@@ -31,12 +42,10 @@ export function HistoryList({ searchString }: { searchString?: string }) {
31
42
  <Item key={key} variant="outline">
32
43
  {item.character && (
33
44
  <ItemMedia variant="image">
34
- <Avatar size="sm">
35
- <AvatarImage src={item.icon} />
36
- <AvatarFallback>
37
- {item.character?.slice(0, 1).toUpperCase() ?? "?"}
38
- </AvatarFallback>
39
- </Avatar>
45
+ <HistoryItemAvatar
46
+ icon={item.icon}
47
+ character={item.character}
48
+ />
40
49
  </ItemMedia>
41
50
  )}
42
51
  <ItemContent
@@ -1,5 +1,5 @@
1
1
  import { ChoiceMenu } from "@/components/menus/choice-menus";
2
- import { NarrationCards } from "@/components/menus/narration/narration-cards";
2
+ import { NarrationCards } from "@/components/scrrens/narration/narration-cards";
3
3
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
4
4
  import { useQueryDialogue } from "@/lib/query/interface-query";
5
5
 
@@ -1,7 +1,8 @@
1
1
  import { AnimatedDots } from "@/components/loading";
2
- import { QuickTools } from "@/components/menus/quick-tools";
2
+ import { QuickTools } from "@/components/quick-tools";
3
3
  import { AspectRatio } from "@/components/ui/aspect-ratio";
4
4
  import { Card, CardContent } from "@/components/ui/card";
5
+ import { Image } from "@/components/ui/image";
5
6
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
6
7
  import { ScrollArea } from "@/components/ui/scroll-area";
7
8
  import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
@@ -127,7 +128,7 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
127
128
  export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
128
129
  return (
129
130
  <AspectRatio ratio={16 / 9}>
130
- <img src={icon} loading="lazy" alt={alt} className="h-full w-full object-cover" />
131
+ <Image src={icon} alt={alt} className="h-full w-full object-cover" />
131
132
  </AspectRatio>
132
133
  );
133
134
  }
@@ -0,0 +1,50 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { Image as UnpicImage } from "@unpic/react";
3
+ import type * as React from "react";
4
+ import { useMemo } from "react";
5
+
6
+ export function Image({
7
+ src,
8
+ loading = "lazy",
9
+ width,
10
+ height,
11
+ ...props
12
+ }: React.ComponentProps<"img">) {
13
+ const resolvedSrc = useMemo(() => {
14
+ if (!src) {
15
+ return undefined;
16
+ }
17
+ return getPixiJSAsset(src);
18
+ }, [src]);
19
+
20
+ if (!resolvedSrc) {
21
+ return null;
22
+ }
23
+
24
+ const parsedWidth =
25
+ typeof width === "number" ? width : width ? Number.parseFloat(width) : undefined;
26
+ const parsedHeight =
27
+ typeof height === "number" ? height : height ? Number.parseFloat(height) : undefined;
28
+
29
+ if (
30
+ parsedWidth !== undefined &&
31
+ parsedHeight !== undefined &&
32
+ Number.isFinite(parsedWidth) &&
33
+ Number.isFinite(parsedHeight) &&
34
+ parsedWidth > 0 &&
35
+ parsedHeight > 0
36
+ ) {
37
+ return (
38
+ <UnpicImage
39
+ src={resolvedSrc}
40
+ width={parsedWidth}
41
+ height={parsedHeight}
42
+ layout="constrained"
43
+ loading={loading}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ return <UnpicImage src={resolvedSrc} layout="fullWidth" loading={loading} {...props} />;
50
+ }
@@ -0,0 +1,11 @@
1
+ import { getPixiJSAsset } from "@/lib/utils/assets-utility";
2
+ import { useMemo } from "react";
3
+
4
+ export function useImageSrc(src?: string | null): string | undefined {
5
+ return useMemo(() => {
6
+ if (!src) {
7
+ return undefined;
8
+ }
9
+ return getPixiJSAsset(src);
10
+ }, [src]);
11
+ }
@@ -1,5 +1,5 @@
1
- import { NarrationScreen } from "@/components/menus/narration";
2
- import { NarrationClickOverlay } from "@/components/menus/narration/click-overlay";
1
+ import { NarrationScreen } from "@/components/scrrens/narration";
2
+ import { NarrationClickOverlay } from "@/components/scrrens/narration/click-overlay";
3
3
  import { useNarrationHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { useSkipAutoDetector } from "@/lib/hooks/narration-hooks";
5
5
  import { createFileRoute } from "@tanstack/react-router";
@@ -1,5 +1,5 @@
1
- import { QuickActionsWheel } from "@/components/menus/quick-actions-wheel";
2
1
  import { InputRequestDialog } from "@/components/modals/input-request-dialogues";
2
+ import { QuickActionsWheel } from "@/components/modals/quick-actions-wheel";
3
3
  import { useGameHotkeys } from "@/lib/hooks/hotkeys-hooks";
4
4
  import { usePauseGameWhenMenuIsOpen } from "@/lib/hooks/pause-game-hooks";
5
5
  import { createFileRoute, Outlet } from "@tanstack/react-router";