create-pixi-vn 2.0.14 → 2.0.15
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.
- package/package.json +1 -1
- package/template-react-vite-muijoy/package-lock.json +12 -8
- package/template-react-vite-muijoy/package.json +1 -1
- package/template-react-vite-muijoy/src/components/loading.tsx +11 -1
- package/template-react-vite-muijoy/src/components/menus/choice-menus.tsx +4 -0
- package/template-react-vite-muijoy/src/components/menus/main-menu.tsx +1 -6
- package/template-react-vite-muijoy/src/components/menus/settings/index.tsx +3 -1
- package/template-react-vite-muijoy/src/components/modals/input-request-dialogues.tsx +1 -1
- package/template-react-vite-muijoy/src/components/modals/quick-actions-wheel.tsx +3 -1
- package/template-react-vite-muijoy/src/components/providers/alert-dialog-provider.tsx +3 -0
- package/template-react-vite-muijoy/src/components/screens/narration/narration-cards.tsx +40 -19
- package/template-react-vite-muijoy/src/lib/hooks/hotkeys-hooks.ts +111 -10
- package/template-react-vite-muijoy/src/lib/hooks/narration-hooks.ts +7 -1
- package/template-react-vite-muijoy/src/lib/query/narration-query.ts +32 -2
- package/template-react-vite-muijoy/src/lib/stores/alert-dialog-store.ts +13 -0
- package/template-react-vite-muijoy/src/lib/stores/text-display-settings-store.ts +20 -1
- package/template-react-vite-muijoy/src/locales/en.json +2 -0
- package/template-react-vite-muijoy/src/routes/__root.tsx +3 -4
- package/template-react-vite-muijoy-ink/package-lock.json +12 -8
- package/template-react-vite-muijoy-ink/package.json +1 -1
- package/template-react-vite-muijoy-ink/src/components/loading.tsx +11 -1
- package/template-react-vite-muijoy-ink/src/components/menus/choice-menus.tsx +4 -0
- package/template-react-vite-muijoy-ink/src/components/menus/main-menu.tsx +1 -6
- package/template-react-vite-muijoy-ink/src/components/menus/settings/index.tsx +3 -1
- package/template-react-vite-muijoy-ink/src/components/modals/input-request-dialogues.tsx +1 -1
- package/template-react-vite-muijoy-ink/src/components/modals/quick-actions-wheel.tsx +3 -1
- package/template-react-vite-muijoy-ink/src/components/providers/alert-dialog-provider.tsx +3 -0
- package/template-react-vite-muijoy-ink/src/components/screens/narration/narration-cards.tsx +40 -19
- package/template-react-vite-muijoy-ink/src/lib/hooks/hotkeys-hooks.ts +111 -10
- package/template-react-vite-muijoy-ink/src/lib/hooks/narration-hooks.ts +7 -1
- package/template-react-vite-muijoy-ink/src/lib/query/narration-query.ts +32 -2
- package/template-react-vite-muijoy-ink/src/lib/stores/alert-dialog-store.ts +13 -0
- package/template-react-vite-muijoy-ink/src/lib/stores/text-display-settings-store.ts +20 -1
- package/template-react-vite-muijoy-ink/src/locales/en.json +2 -0
- package/template-react-vite-muijoy-ink/src/routes/__root.tsx +3 -4
- package/template-react-vite-muijoy-ink-tauri/package-lock.json +12 -8
- package/template-react-vite-muijoy-ink-tauri/package.json +1 -1
- package/template-react-vite-muijoy-ink-tauri/src/components/loading.tsx +11 -1
- package/template-react-vite-muijoy-ink-tauri/src/components/menus/choice-menus.tsx +4 -0
- package/template-react-vite-muijoy-ink-tauri/src/components/menus/main-menu.tsx +1 -6
- package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/index.tsx +3 -1
- package/template-react-vite-muijoy-ink-tauri/src/components/modals/input-request-dialogues.tsx +1 -1
- package/template-react-vite-muijoy-ink-tauri/src/components/modals/quick-actions-wheel.tsx +3 -1
- package/template-react-vite-muijoy-ink-tauri/src/components/providers/alert-dialog-provider.tsx +3 -0
- package/template-react-vite-muijoy-ink-tauri/src/components/screens/narration/narration-cards.tsx +40 -19
- package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/hotkeys-hooks.ts +111 -10
- package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/narration-hooks.ts +7 -1
- package/template-react-vite-muijoy-ink-tauri/src/lib/query/narration-query.ts +32 -2
- package/template-react-vite-muijoy-ink-tauri/src/lib/stores/alert-dialog-store.ts +13 -0
- package/template-react-vite-muijoy-ink-tauri/src/lib/stores/text-display-settings-store.ts +20 -1
- package/template-react-vite-muijoy-ink-tauri/src/locales/en.json +2 -0
- package/template-react-vite-muijoy-ink-tauri/src/routes/__root.tsx +3 -4
- package/template-react-vite-muijoy-tauri/package-lock.json +12 -8
- package/template-react-vite-muijoy-tauri/package.json +1 -1
- package/template-react-vite-muijoy-tauri/src/components/loading.tsx +11 -1
- package/template-react-vite-muijoy-tauri/src/components/menus/choice-menus.tsx +4 -0
- package/template-react-vite-muijoy-tauri/src/components/menus/main-menu.tsx +1 -6
- package/template-react-vite-muijoy-tauri/src/components/menus/settings/index.tsx +3 -1
- package/template-react-vite-muijoy-tauri/src/components/modals/input-request-dialogues.tsx +1 -1
- package/template-react-vite-muijoy-tauri/src/components/modals/quick-actions-wheel.tsx +3 -1
- package/template-react-vite-muijoy-tauri/src/components/providers/alert-dialog-provider.tsx +3 -0
- package/template-react-vite-muijoy-tauri/src/components/screens/narration/narration-cards.tsx +40 -19
- package/template-react-vite-muijoy-tauri/src/lib/hooks/hotkeys-hooks.ts +111 -10
- package/template-react-vite-muijoy-tauri/src/lib/hooks/narration-hooks.ts +7 -1
- package/template-react-vite-muijoy-tauri/src/lib/query/narration-query.ts +32 -2
- package/template-react-vite-muijoy-tauri/src/lib/stores/alert-dialog-store.ts +13 -0
- package/template-react-vite-muijoy-tauri/src/lib/stores/text-display-settings-store.ts +20 -1
- package/template-react-vite-muijoy-tauri/src/locales/en.json +2 -0
- package/template-react-vite-muijoy-tauri/src/routes/__root.tsx +3 -4
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { INTERFACE_DATA_USE_QUERY_KEY } from "@/constants";
|
|
2
|
+
import { TextDisplaySettings } from "@/lib/stores/text-display-settings-store";
|
|
2
3
|
import { type CharacterInterface, narration, stepHistory } from "@drincs/pixi-vn";
|
|
3
4
|
import { keepPreviousData, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { useSelector } from "@tanstack/react-store";
|
|
6
|
+
import { useCallback } from "react";
|
|
4
7
|
import { useTranslation } from "react-i18next";
|
|
5
8
|
|
|
6
9
|
const CAN_GO_BACK_USE_QUERY_KEY = "can_go_back_use_query_key";
|
|
@@ -24,7 +27,6 @@ export function useQueryChoiceMenuOptions() {
|
|
|
24
27
|
? t(option.text)
|
|
25
28
|
: option.text.map((text) => t(text)).join(" "),
|
|
26
29
|
})) || [],
|
|
27
|
-
placeholderData: keepPreviousData,
|
|
28
30
|
});
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -45,13 +47,30 @@ type DialogueModel = {
|
|
|
45
47
|
animatedText?: string;
|
|
46
48
|
text?: string;
|
|
47
49
|
character?: CharacterInterface;
|
|
50
|
+
lastText?: string;
|
|
48
51
|
};
|
|
49
52
|
const DIALOGUE_USE_QUERY_KEY = "dialogue_use_query_key";
|
|
50
53
|
export function useQueryDialogue() {
|
|
51
54
|
const { t } = useTranslation(["narration"]);
|
|
52
55
|
const queryClient = useQueryClient();
|
|
56
|
+
const forceComplete = useSelector(TextDisplaySettings.store, (state) => state.forceComplete);
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
const finalizeDialogue = useCallback(() => {
|
|
59
|
+
queryClient.setQueryData<DialogueModel>(
|
|
60
|
+
[INTERFACE_DATA_USE_QUERY_KEY, DIALOGUE_USE_QUERY_KEY],
|
|
61
|
+
(prev) => {
|
|
62
|
+
if (!prev) return prev;
|
|
63
|
+
const fullText = (prev.text || "") + (prev.animatedText || "");
|
|
64
|
+
return {
|
|
65
|
+
...prev,
|
|
66
|
+
text: fullText || undefined,
|
|
67
|
+
animatedText: undefined,
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
}, [queryClient]);
|
|
72
|
+
|
|
73
|
+
const query = useQuery<DialogueModel>({
|
|
55
74
|
queryKey: [INTERFACE_DATA_USE_QUERY_KEY, DIALOGUE_USE_QUERY_KEY],
|
|
56
75
|
queryFn: async ({ queryKey }) => {
|
|
57
76
|
const dialogue = narration.dialogue;
|
|
@@ -78,6 +97,7 @@ export function useQueryDialogue() {
|
|
|
78
97
|
}
|
|
79
98
|
return {
|
|
80
99
|
animatedText: newText,
|
|
100
|
+
lastText: newText,
|
|
81
101
|
text: oldText,
|
|
82
102
|
character: character,
|
|
83
103
|
};
|
|
@@ -85,11 +105,21 @@ export function useQueryDialogue() {
|
|
|
85
105
|
|
|
86
106
|
return {
|
|
87
107
|
animatedText: text,
|
|
108
|
+
lastText: text,
|
|
88
109
|
character: character,
|
|
89
110
|
};
|
|
90
111
|
},
|
|
91
112
|
placeholderData: keepPreviousData,
|
|
113
|
+
select: forceComplete
|
|
114
|
+
? (data) => ({
|
|
115
|
+
...data,
|
|
116
|
+
text: (data.text || "") + (data.animatedText || "") || undefined,
|
|
117
|
+
animatedText: undefined,
|
|
118
|
+
})
|
|
119
|
+
: undefined,
|
|
92
120
|
});
|
|
121
|
+
|
|
122
|
+
return { ...query, finalizeDialogue };
|
|
93
123
|
}
|
|
94
124
|
|
|
95
125
|
const CAN_GO_NEXT_USE_QUERY_KEY = "can_go_next_use_query_key";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Store } from "@tanstack/store";
|
|
2
|
+
|
|
3
|
+
export namespace AlertDialogState {
|
|
4
|
+
export const store = new Store<{ count: number }>({ count: 0 });
|
|
5
|
+
|
|
6
|
+
export function increment() {
|
|
7
|
+
store.setState((state) => ({ count: state.count + 1 }));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function decrement() {
|
|
11
|
+
store.setState((state) => ({ count: Math.max(0, state.count - 1) }));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -9,6 +9,10 @@ type TextDisplayStorage = {
|
|
|
9
9
|
* Whether the typewriter effect is in progress
|
|
10
10
|
*/
|
|
11
11
|
inProgress: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the typewriter effect should be force-completed instantly
|
|
14
|
+
*/
|
|
15
|
+
forceComplete: boolean;
|
|
12
16
|
/**
|
|
13
17
|
* The font size in percent (100 = default)
|
|
14
18
|
*/
|
|
@@ -22,6 +26,7 @@ export namespace TextDisplaySettings {
|
|
|
22
26
|
export const store = new Store<TextDisplayStorage>({
|
|
23
27
|
delay: Number(localStorage.getItem("typewriter_delay_millisecond") ?? 10),
|
|
24
28
|
inProgress: false,
|
|
29
|
+
forceComplete: false,
|
|
25
30
|
fontSize: initialFontSize,
|
|
26
31
|
});
|
|
27
32
|
|
|
@@ -57,6 +62,20 @@ export namespace TextDisplaySettings {
|
|
|
57
62
|
* End the typewriter effect
|
|
58
63
|
*/
|
|
59
64
|
export function end() {
|
|
60
|
-
store.setState((state) => ({ ...state, inProgress: false }));
|
|
65
|
+
store.setState((state) => ({ ...state, inProgress: false, forceComplete: false }));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Force the typewriter effect to complete instantly
|
|
70
|
+
*/
|
|
71
|
+
export function complete() {
|
|
72
|
+
store.setState((state) => ({ ...state, forceComplete: true, inProgress: false }));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Clear the forceComplete flag before advancing to the next step
|
|
77
|
+
*/
|
|
78
|
+
export function resetForNext() {
|
|
79
|
+
store.setState((state) => ({ ...state, forceComplete: false }));
|
|
61
80
|
}
|
|
62
81
|
}
|
|
@@ -81,6 +81,8 @@
|
|
|
81
81
|
"menu_navigation_up_description": "Focus the previous menu item.",
|
|
82
82
|
"menu_navigation_home_description": "Focus the first menu item.",
|
|
83
83
|
"menu_navigation_end_description": "Focus the last menu item.",
|
|
84
|
+
"choice_navigation": "Choices",
|
|
85
|
+
"choice_navigation_description": "Use ↑ / ↓ to select an choice, then Enter to run it.",
|
|
84
86
|
"success_delete": "Delete successful",
|
|
85
87
|
"success_load": "Load successful",
|
|
86
88
|
"success_save": "Save successful",
|
|
@@ -11,13 +11,12 @@ import { defineAssets } from "@/lib/utils/assets-utility";
|
|
|
11
11
|
import { initializeIndexedDB } from "@/lib/utils/db-utility";
|
|
12
12
|
import { loadRefreshSave } from "@/lib/utils/save-utility";
|
|
13
13
|
import type { RouterContext } from "@/router";
|
|
14
|
+
import { narration } from "@drincs/pixi-vn";
|
|
14
15
|
import { setupPixivnViteData } from "@drincs/pixi-vn/vite-listener";
|
|
15
16
|
import { createRootRouteWithContext, ErrorComponent, Outlet } from "@tanstack/react-router";
|
|
16
17
|
import { lazy, Suspense } from "react";
|
|
17
18
|
|
|
18
|
-
const DevDevtools = import.meta.env.DEV
|
|
19
|
-
? lazy(() => import("@/components/dev-devtools"))
|
|
20
|
-
: null;
|
|
19
|
+
const DevDevtools = import.meta.env.DEV ? lazy(() => import("@/components/dev-devtools")) : null;
|
|
21
20
|
|
|
22
21
|
export const Route = createRootRouteWithContext<RouterContext>()({
|
|
23
22
|
validateSearch: (search) => SearchParams.setMany(search),
|
|
@@ -27,7 +26,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|
|
27
26
|
// Game.onNavigate(async (to) => redirect({ to }));
|
|
28
27
|
await Promise.all([import("@/content"), initializeIndexedDB(), defineAssets(), useI18n()]);
|
|
29
28
|
await setupPixivnViteData();
|
|
30
|
-
if (location.pathname !== "/") {
|
|
29
|
+
if (location.pathname !== "/" && narration.stepCounter === 0) {
|
|
31
30
|
const isRefreshSaveExist = await loadRefreshSave();
|
|
32
31
|
if (isRefreshSaveExist) {
|
|
33
32
|
await context.queryClient.invalidateQueries({
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"react-dom": "^19.2.6",
|
|
36
36
|
"react-i18next": "^17.0.8",
|
|
37
37
|
"react-markdown": "^10.1.0",
|
|
38
|
-
"react-markdown-typewriter": "^1.
|
|
38
|
+
"react-markdown-typewriter": "^1.3.4",
|
|
39
39
|
"react-resizable-panels": "^4.11.2",
|
|
40
40
|
"rehype-raw": "^7.0.0",
|
|
41
41
|
"remark-gfm": "^4.0.1",
|
|
@@ -10108,6 +10108,7 @@
|
|
|
10108
10108
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
|
10109
10109
|
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
|
10110
10110
|
"license": "MIT",
|
|
10111
|
+
"peer": true,
|
|
10111
10112
|
"dependencies": {
|
|
10112
10113
|
"motion-dom": "^12.38.0",
|
|
10113
10114
|
"motion-utils": "^12.36.0",
|
|
@@ -13771,6 +13772,7 @@
|
|
|
13771
13772
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz",
|
|
13772
13773
|
"integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==",
|
|
13773
13774
|
"license": "MIT",
|
|
13775
|
+
"peer": true,
|
|
13774
13776
|
"dependencies": {
|
|
13775
13777
|
"framer-motion": "^12.38.0",
|
|
13776
13778
|
"tslib": "^2.4.0"
|
|
@@ -13797,6 +13799,7 @@
|
|
|
13797
13799
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
|
13798
13800
|
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
|
13799
13801
|
"license": "MIT",
|
|
13802
|
+
"peer": true,
|
|
13800
13803
|
"dependencies": {
|
|
13801
13804
|
"motion-utils": "^12.36.0"
|
|
13802
13805
|
}
|
|
@@ -13805,7 +13808,8 @@
|
|
|
13805
13808
|
"version": "12.36.0",
|
|
13806
13809
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
|
13807
13810
|
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
|
13808
|
-
"license": "MIT"
|
|
13811
|
+
"license": "MIT",
|
|
13812
|
+
"peer": true
|
|
13809
13813
|
},
|
|
13810
13814
|
"node_modules/ms": {
|
|
13811
13815
|
"version": "2.1.3",
|
|
@@ -14892,12 +14896,12 @@
|
|
|
14892
14896
|
}
|
|
14893
14897
|
},
|
|
14894
14898
|
"node_modules/react-markdown-typewriter": {
|
|
14895
|
-
"version": "1.
|
|
14896
|
-
"resolved": "https://registry.npmjs.org/react-markdown-typewriter/-/react-markdown-typewriter-1.
|
|
14897
|
-
"integrity": "sha512-
|
|
14898
|
-
"
|
|
14899
|
-
"
|
|
14900
|
-
|
|
14899
|
+
"version": "1.3.4",
|
|
14900
|
+
"resolved": "https://registry.npmjs.org/react-markdown-typewriter/-/react-markdown-typewriter-1.3.4.tgz",
|
|
14901
|
+
"integrity": "sha512-LE/7xzyta4A6Gz6CfeTP0saCgVsxbCEVj4le4xobrkM/IG9MVWdrpyGFGFdsKmZ1k5otnRF3HP52YXbMv7P1rg==",
|
|
14902
|
+
"workspaces": [
|
|
14903
|
+
"playground"
|
|
14904
|
+
],
|
|
14901
14905
|
"peerDependencies": {
|
|
14902
14906
|
"motion": ">=12.9.0",
|
|
14903
14907
|
"react": ">=18.0.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"react-dom": "^19.2.6",
|
|
45
45
|
"react-i18next": "^17.0.8",
|
|
46
46
|
"react-markdown": "^10.1.0",
|
|
47
|
-
"react-markdown-typewriter": "^1.
|
|
47
|
+
"react-markdown-typewriter": "^1.3.4",
|
|
48
48
|
"react-resizable-panels": "^4.11.2",
|
|
49
49
|
"rehype-raw": "^7.0.0",
|
|
50
50
|
"remark-gfm": "^4.0.1",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Spinner } from "@/components/ui/spinner";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
2
3
|
|
|
3
4
|
export function PendingComponent() {
|
|
4
5
|
return (
|
|
@@ -12,10 +13,19 @@ export function PendingComponent() {
|
|
|
12
13
|
|
|
13
14
|
export function AnimatedDots() {
|
|
14
15
|
return (
|
|
15
|
-
<span className="flex pointer-events-none select-none">
|
|
16
|
+
<span className="inline-flex pointer-events-none select-none">
|
|
16
17
|
<span className="animate-bounce [animation-delay:0s]">.</span>
|
|
17
18
|
<span className="animate-bounce [animation-delay:0.2s]">.</span>
|
|
18
19
|
<span className="animate-bounce [animation-delay:0.4s]">.</span>
|
|
19
20
|
</span>
|
|
20
21
|
);
|
|
21
22
|
}
|
|
23
|
+
|
|
24
|
+
export function DelayedAnimatedDots({ delay = 300 }: { delay?: number }) {
|
|
25
|
+
const [visible, setVisible] = useState(false);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const id = setTimeout(() => setVisible(true), delay);
|
|
28
|
+
return () => clearTimeout(id);
|
|
29
|
+
}, [delay]);
|
|
30
|
+
return visible ? <AnimatedDots /> : null;
|
|
31
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button } from "@/components/ui/button";
|
|
2
|
+
import { useChoiceMenuHotkeys } from "@/lib/hooks/hotkeys-hooks";
|
|
2
3
|
import { useNarrationFunctions } from "@/lib/hooks/narration-hooks";
|
|
3
4
|
import { useQueryChoiceMenuOptions } from "@/lib/query/narration-query";
|
|
4
5
|
import { GameStatus } from "@/lib/stores/game-status-store";
|
|
@@ -13,9 +14,11 @@ export function ChoiceMenu() {
|
|
|
13
14
|
const isTyping = useSelector(TextDisplaySettings.store, (state) => state.inProgress);
|
|
14
15
|
const { selectChoice } = useNarrationFunctions();
|
|
15
16
|
const [debouncedMenu] = useDebouncedValue(isTyping ? [] : menu, { wait: 50 });
|
|
17
|
+
const { menuRef } = useChoiceMenuHotkeys(debouncedMenu.length);
|
|
16
18
|
|
|
17
19
|
return (
|
|
18
20
|
<div
|
|
21
|
+
ref={menuRef}
|
|
19
22
|
className="flex flex-col items-center justify-center gap-2 w-full h-full overflow-auto"
|
|
20
23
|
role="menu"
|
|
21
24
|
>
|
|
@@ -28,6 +31,7 @@ export function ChoiceMenu() {
|
|
|
28
31
|
style={{ animationDelay: `${index * 150}ms` }}
|
|
29
32
|
>
|
|
30
33
|
<Button
|
|
34
|
+
role="menuitem"
|
|
31
35
|
disabled={loading}
|
|
32
36
|
onClick={() => selectChoice(item)}
|
|
33
37
|
size="sm"
|
|
@@ -26,7 +26,6 @@ const menuButtonClass =
|
|
|
26
26
|
"justify-start hover:scale-105 focus-visible:scale-105 transition-transform duration-150 ease-out";
|
|
27
27
|
|
|
28
28
|
export function MainMenu() {
|
|
29
|
-
const queryClient = useQueryClient();
|
|
30
29
|
const gameProps = useGameProps();
|
|
31
30
|
const { uiTransition: t, navigate } = gameProps;
|
|
32
31
|
const setSettings = useSetSearchParamState<boolean>("settings");
|
|
@@ -147,11 +146,7 @@ export function MainMenu() {
|
|
|
147
146
|
setLoading(true);
|
|
148
147
|
await navigate({ to: "/game/narration" });
|
|
149
148
|
Game.start("start", gameProps)
|
|
150
|
-
.then(() =>
|
|
151
|
-
queryClient.invalidateQueries({
|
|
152
|
-
queryKey: [INTERFACE_DATA_USE_QUERY_KEY],
|
|
153
|
-
}),
|
|
154
|
-
)
|
|
149
|
+
.then(() => gameProps.invalidateInterfaceData())
|
|
155
150
|
.finally(() => setLoading(false));
|
|
156
151
|
}}
|
|
157
152
|
disabled={loading}
|
|
@@ -208,7 +208,9 @@ export function SettingsDialogue() {
|
|
|
208
208
|
onOpenChange={(isOpen) => {
|
|
209
209
|
setOpen(isOpen || undefined);
|
|
210
210
|
if (!isOpen) {
|
|
211
|
-
|
|
211
|
+
// Delay clearing the tab until after the 100ms close animation
|
|
212
|
+
// to avoid briefly flashing the main settings view during close.
|
|
213
|
+
setTimeout(() => setSettingsTab(undefined), 150);
|
|
212
214
|
}
|
|
213
215
|
}}
|
|
214
216
|
>
|
|
@@ -14,7 +14,7 @@ import rehypeRaw from "rehype-raw";
|
|
|
14
14
|
import remarkGfm from "remark-gfm";
|
|
15
15
|
|
|
16
16
|
export function InputRequestDialog() {
|
|
17
|
-
const { data: {
|
|
17
|
+
const { data: { lastText: text } = {} } = useQueryDialogue();
|
|
18
18
|
const {
|
|
19
19
|
data: { isRequired, type, currentValue } = { currentValue: undefined, isRequired: false },
|
|
20
20
|
} = useQueryInputValue<string | number>();
|
|
@@ -223,7 +223,9 @@ function QuickActionsWheelContent({
|
|
|
223
223
|
>
|
|
224
224
|
<span className="font-semibold">{registration.label}</span>
|
|
225
225
|
<span className="text-[11px] opacity-85">
|
|
226
|
-
{registration.hotkeyLabel
|
|
226
|
+
{registration.hotkeyLabel.length > 15
|
|
227
|
+
? `${registration.hotkeyLabel.slice(0, 12)}…`
|
|
228
|
+
: registration.hotkeyLabel}
|
|
227
229
|
</span>
|
|
228
230
|
</button>
|
|
229
231
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AlertDialogState } from "@/lib/stores/alert-dialog-store";
|
|
1
2
|
import { createContext, type ReactNode, useCallback, useContext, useState } from "react";
|
|
2
3
|
import { useTranslation } from "react-i18next";
|
|
3
4
|
import {
|
|
@@ -47,6 +48,7 @@ export function AlertDialogProvider({ children }: { children: ReactNode }) {
|
|
|
47
48
|
|
|
48
49
|
const closeDialog = useCallback((id: string) => {
|
|
49
50
|
setDialogs((prev) => prev.map((d) => (d.id === id ? { ...d, open: false } : d)));
|
|
51
|
+
AlertDialogState.decrement();
|
|
50
52
|
setTimeout(() => {
|
|
51
53
|
setDialogs((prev) => prev.filter((d) => d.id !== id));
|
|
52
54
|
}, DIALOG_CLOSE_ANIMATION_DURATION_MS);
|
|
@@ -54,6 +56,7 @@ export function AlertDialogProvider({ children }: { children: ReactNode }) {
|
|
|
54
56
|
|
|
55
57
|
const openAlertDialog = useCallback((options: AlertDialogOptions) => {
|
|
56
58
|
const id = generateId();
|
|
59
|
+
AlertDialogState.increment();
|
|
57
60
|
setDialogs((prev) => [...prev, { id, open: true, loading: false, options }]);
|
|
58
61
|
}, []);
|
|
59
62
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DelayedAnimatedDots } from "@/components/loading";
|
|
2
2
|
import { QuickTools } from "@/components/quick-tools";
|
|
3
3
|
import { Card, CardContent } from "@/components/ui/card";
|
|
4
4
|
import { Image } from "@/components/ui/image";
|
|
@@ -8,7 +8,7 @@ import { useNarrationPointerHandlers } from "@/lib/hooks/narration-hooks";
|
|
|
8
8
|
import { useQueryDialogue } from "@/lib/query/narration-query";
|
|
9
9
|
import { TextDisplaySettings } from "@/lib/stores/text-display-settings-store";
|
|
10
10
|
import { useSelector } from "@tanstack/react-store";
|
|
11
|
-
import { type RefObject, useCallback, useRef } from "react";
|
|
11
|
+
import { type RefObject, memo, useCallback, useMemo, useRef } from "react";
|
|
12
12
|
import Markdown from "react-markdown";
|
|
13
13
|
import { MarkdownTypewriterHooks } from "react-markdown-typewriter";
|
|
14
14
|
import rehypeRaw from "rehype-raw";
|
|
@@ -63,9 +63,13 @@ export function NarrationCards() {
|
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export
|
|
66
|
+
export const Text = memo(function Text({
|
|
67
|
+
paragraphRef,
|
|
68
|
+
}: {
|
|
69
|
+
paragraphRef: RefObject<HTMLDivElement | null>;
|
|
70
|
+
}) {
|
|
67
71
|
const typewriterDelay = useSelector(TextDisplaySettings.store, (state) => state.delay);
|
|
68
|
-
const { data: { animatedText, text } = {} } = useQueryDialogue();
|
|
72
|
+
const { data: { animatedText, text } = {}, finalizeDialogue } = useQueryDialogue();
|
|
69
73
|
|
|
70
74
|
const handleCharacterAnimationComplete = useCallback(
|
|
71
75
|
(ref: { current: HTMLSpanElement | null }) => {
|
|
@@ -74,7 +78,6 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
|
|
|
74
78
|
if (container && char) {
|
|
75
79
|
const containerRect = container.getBoundingClientRect();
|
|
76
80
|
const charRect = char.getBoundingClientRect();
|
|
77
|
-
// Position of the character relative to the top of the scroll container
|
|
78
81
|
const charOffsetInContainer =
|
|
79
82
|
charRect.top - containerRect.top + container.scrollTop;
|
|
80
83
|
const scrollTop = charOffsetInContainer - container.clientHeight / 2;
|
|
@@ -87,15 +90,40 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
|
|
|
87
90
|
[paragraphRef],
|
|
88
91
|
);
|
|
89
92
|
|
|
93
|
+
const markdownComponents = useMemo(() => ({ p: (props: object) => <span {...props} /> }), []);
|
|
94
|
+
|
|
95
|
+
const motionProps = useMemo(
|
|
96
|
+
() => ({
|
|
97
|
+
onAnimationStart: TextDisplaySettings.start,
|
|
98
|
+
onAnimationComplete: (definition: "visible" | "hidden") => {
|
|
99
|
+
if (definition === "visible") {
|
|
100
|
+
finalizeDialogue();
|
|
101
|
+
TextDisplaySettings.end();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
onCharacterAnimationComplete: handleCharacterAnimationComplete,
|
|
105
|
+
}),
|
|
106
|
+
[finalizeDialogue, handleCharacterAnimationComplete],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const specialCharacters = useMemo(
|
|
110
|
+
() => ({
|
|
111
|
+
".": { delayAfter: typewriterDelay + 300 },
|
|
112
|
+
"!": { delayAfter: typewriterDelay + 300 },
|
|
113
|
+
"?": { delayAfter: typewriterDelay + 300 },
|
|
114
|
+
",": { delayAfter: typewriterDelay + 75 },
|
|
115
|
+
":": { delay: typewriterDelay + 50, delayAfter: typewriterDelay + 150 },
|
|
116
|
+
}),
|
|
117
|
+
[typewriterDelay],
|
|
118
|
+
);
|
|
119
|
+
|
|
90
120
|
return (
|
|
91
121
|
<p className="prose dark:prose-invert m-0 max-w-full p-0">
|
|
92
122
|
<span>
|
|
93
123
|
<Markdown
|
|
94
124
|
remarkPlugins={[remarkGfm]}
|
|
95
125
|
rehypePlugins={[rehypeRaw]}
|
|
96
|
-
components={
|
|
97
|
-
p: (props) => <span {...props} />,
|
|
98
|
-
}}
|
|
126
|
+
components={markdownComponents}
|
|
99
127
|
>
|
|
100
128
|
{text}
|
|
101
129
|
</Markdown>
|
|
@@ -106,23 +134,16 @@ export function Text({ paragraphRef }: { paragraphRef: RefObject<HTMLDivElement
|
|
|
106
134
|
remarkPlugins={[remarkGfm]}
|
|
107
135
|
rehypePlugins={[rehypeRaw]}
|
|
108
136
|
delay={typewriterDelay}
|
|
109
|
-
motionProps={
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (definition === "visible") {
|
|
113
|
-
TextDisplaySettings.end();
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
onCharacterAnimationComplete: handleCharacterAnimationComplete,
|
|
117
|
-
}}
|
|
118
|
-
fallback={<AnimatedDots />}
|
|
137
|
+
motionProps={motionProps}
|
|
138
|
+
fallback={<DelayedAnimatedDots />}
|
|
139
|
+
specialCharacters={specialCharacters}
|
|
119
140
|
>
|
|
120
141
|
{animatedText}
|
|
121
142
|
</MarkdownTypewriterHooks>
|
|
122
143
|
</span>
|
|
123
144
|
</p>
|
|
124
145
|
);
|
|
125
|
-
}
|
|
146
|
+
});
|
|
126
147
|
|
|
127
148
|
export function CharacterIcon({ alt, icon }: { icon: string; alt: string }) {
|
|
128
149
|
return (
|