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
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"react-dom": "^19.2.6",
|
|
35
35
|
"react-i18next": "^17.0.8",
|
|
36
36
|
"react-markdown": "^10.1.0",
|
|
37
|
-
"react-markdown-typewriter": "^1.
|
|
37
|
+
"react-markdown-typewriter": "^1.3.4",
|
|
38
38
|
"react-resizable-panels": "^4.11.2",
|
|
39
39
|
"rehype-raw": "^7.0.0",
|
|
40
40
|
"remark-gfm": "^4.0.1",
|
|
@@ -9929,6 +9929,7 @@
|
|
|
9929
9929
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
|
9930
9930
|
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
|
9931
9931
|
"license": "MIT",
|
|
9932
|
+
"peer": true,
|
|
9932
9933
|
"dependencies": {
|
|
9933
9934
|
"motion-dom": "^12.38.0",
|
|
9934
9935
|
"motion-utils": "^12.36.0",
|
|
@@ -13592,6 +13593,7 @@
|
|
|
13592
13593
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz",
|
|
13593
13594
|
"integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==",
|
|
13594
13595
|
"license": "MIT",
|
|
13596
|
+
"peer": true,
|
|
13595
13597
|
"dependencies": {
|
|
13596
13598
|
"framer-motion": "^12.38.0",
|
|
13597
13599
|
"tslib": "^2.4.0"
|
|
@@ -13618,6 +13620,7 @@
|
|
|
13618
13620
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
|
13619
13621
|
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
|
13620
13622
|
"license": "MIT",
|
|
13623
|
+
"peer": true,
|
|
13621
13624
|
"dependencies": {
|
|
13622
13625
|
"motion-utils": "^12.36.0"
|
|
13623
13626
|
}
|
|
@@ -13626,7 +13629,8 @@
|
|
|
13626
13629
|
"version": "12.36.0",
|
|
13627
13630
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
|
13628
13631
|
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
|
13629
|
-
"license": "MIT"
|
|
13632
|
+
"license": "MIT",
|
|
13633
|
+
"peer": true
|
|
13630
13634
|
},
|
|
13631
13635
|
"node_modules/ms": {
|
|
13632
13636
|
"version": "2.1.3",
|
|
@@ -14713,12 +14717,12 @@
|
|
|
14713
14717
|
}
|
|
14714
14718
|
},
|
|
14715
14719
|
"node_modules/react-markdown-typewriter": {
|
|
14716
|
-
"version": "1.
|
|
14717
|
-
"resolved": "https://registry.npmjs.org/react-markdown-typewriter/-/react-markdown-typewriter-1.
|
|
14718
|
-
"integrity": "sha512-
|
|
14719
|
-
"
|
|
14720
|
-
"
|
|
14721
|
-
|
|
14720
|
+
"version": "1.3.4",
|
|
14721
|
+
"resolved": "https://registry.npmjs.org/react-markdown-typewriter/-/react-markdown-typewriter-1.3.4.tgz",
|
|
14722
|
+
"integrity": "sha512-LE/7xzyta4A6Gz6CfeTP0saCgVsxbCEVj4le4xobrkM/IG9MVWdrpyGFGFdsKmZ1k5otnRF3HP52YXbMv7P1rg==",
|
|
14723
|
+
"workspaces": [
|
|
14724
|
+
"playground"
|
|
14725
|
+
],
|
|
14722
14726
|
"peerDependencies": {
|
|
14723
14727
|
"motion": ">=12.9.0",
|
|
14724
14728
|
"react": ">=18.0.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"react-dom": "^19.2.6",
|
|
44
44
|
"react-i18next": "^17.0.8",
|
|
45
45
|
"react-markdown": "^10.1.0",
|
|
46
|
-
"react-markdown-typewriter": "^1.
|
|
46
|
+
"react-markdown-typewriter": "^1.3.4",
|
|
47
47
|
"react-resizable-panels": "^4.11.2",
|
|
48
48
|
"rehype-raw": "^7.0.0",
|
|
49
49
|
"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 (
|
|
@@ -2,22 +2,47 @@ import { useAlertDialog } from "@/components/providers/alert-dialog-provider";
|
|
|
2
2
|
import { useNarrationFunctions } from "@/lib/hooks/narration-hooks";
|
|
3
3
|
import { useSetSearchParamState } from "@/lib/hooks/navigation-hooks";
|
|
4
4
|
import { useGameProps } from "@/lib/hooks/props-hooks";
|
|
5
|
-
import { useQueryInputValue } from "@/lib/query/narration-query";
|
|
5
|
+
import { useQueryCanGoNext, useQueryInputValue } from "@/lib/query/narration-query";
|
|
6
6
|
import {
|
|
7
7
|
LAST_SAVE_USE_QUERY_KEY,
|
|
8
8
|
SAVES_USE_QUERY_KEY,
|
|
9
9
|
useQueryLastSave,
|
|
10
10
|
} from "@/lib/query/save-query";
|
|
11
|
+
import { AlertDialogState } from "@/lib/stores/alert-dialog-store";
|
|
11
12
|
import { QuickActionsWheelState } from "@/lib/stores/quick-actions-wheel-store";
|
|
13
|
+
import { SearchParams } from "@/lib/stores/search-param-store";
|
|
12
14
|
import { SkipSettings } from "@/lib/stores/skip-settings-store";
|
|
15
|
+
import { TextDisplaySettings } from "@/lib/stores/text-display-settings-store";
|
|
13
16
|
import { loadSave, saveGameToIndexDB } from "@/lib/utils/save-utility";
|
|
17
|
+
import { narration } from "@drincs/pixi-vn";
|
|
14
18
|
import { useHotkeys } from "@tanstack/react-hotkeys";
|
|
15
19
|
import { useQueryClient } from "@tanstack/react-query";
|
|
16
20
|
import { useLocation } from "@tanstack/react-router";
|
|
17
|
-
import {
|
|
21
|
+
import { useSelector } from "@tanstack/react-store";
|
|
22
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
18
23
|
import { useTranslation } from "react-i18next";
|
|
19
24
|
import { toast } from "sonner";
|
|
20
25
|
|
|
26
|
+
function useMenuDialogState() {
|
|
27
|
+
const searchParams = useSelector(SearchParams.store, (state) => state);
|
|
28
|
+
const alertDialogCount = useSelector(AlertDialogState.store, (state) => state.count);
|
|
29
|
+
const { data: { isRequired } = {} } = useQueryInputValue<string | number>();
|
|
30
|
+
|
|
31
|
+
/** true when the settings menu (or any sub-page) is open */
|
|
32
|
+
const isSettingsOpen = useMemo(
|
|
33
|
+
() => Object.values(searchParams).some((v) => v !== undefined),
|
|
34
|
+
[searchParams],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/** true when an alert confirmation dialog or the input-request dialog is blocking interaction */
|
|
38
|
+
const isAlertOrInputOpen = alertDialogCount > 0 || !!isRequired;
|
|
39
|
+
|
|
40
|
+
/** true when any menu or dialog that should disable hotkeys is open */
|
|
41
|
+
const isAnyMenuOrDialogOpen = isSettingsOpen || isAlertOrInputOpen;
|
|
42
|
+
|
|
43
|
+
return { isAnyMenuOrDialogOpen, isSettingsOpen, isAlertOrInputOpen };
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
/**
|
|
22
47
|
* useSaveHotkeys
|
|
23
48
|
*
|
|
@@ -40,6 +65,7 @@ export function useSaveHotkeys(): null {
|
|
|
40
65
|
const { data: lastSave = null } = useQueryLastSave();
|
|
41
66
|
const { openAlertDialog } = useAlertDialog();
|
|
42
67
|
const gameProps = useGameProps();
|
|
68
|
+
const { isSettingsOpen } = useMenuDialogState();
|
|
43
69
|
|
|
44
70
|
const quickSave = useCallback(() => {
|
|
45
71
|
if (location.pathname === "/") {
|
|
@@ -87,6 +113,7 @@ export function useSaveHotkeys(): null {
|
|
|
87
113
|
hotkey: "F5",
|
|
88
114
|
callback: quickSave,
|
|
89
115
|
options: {
|
|
116
|
+
enabled: !isSettingsOpen,
|
|
90
117
|
meta: {
|
|
91
118
|
name: t("quick_save"),
|
|
92
119
|
description: t("quick_save_hotkey_description"),
|
|
@@ -97,6 +124,7 @@ export function useSaveHotkeys(): null {
|
|
|
97
124
|
hotkey: "Control+S",
|
|
98
125
|
callback: quickSave,
|
|
99
126
|
options: {
|
|
127
|
+
enabled: !isSettingsOpen,
|
|
100
128
|
meta: {
|
|
101
129
|
name: t("quick_save"),
|
|
102
130
|
description: t("quick_save_hotkey_alternative_description"),
|
|
@@ -107,6 +135,7 @@ export function useSaveHotkeys(): null {
|
|
|
107
135
|
hotkey: "F9",
|
|
108
136
|
callback: quickLoad,
|
|
109
137
|
options: {
|
|
138
|
+
enabled: !isSettingsOpen,
|
|
110
139
|
meta: {
|
|
111
140
|
name: t("load_last_save"),
|
|
112
141
|
description: t("quick_load_hotkey_description"),
|
|
@@ -117,6 +146,7 @@ export function useSaveHotkeys(): null {
|
|
|
117
146
|
hotkey: "Control+L",
|
|
118
147
|
callback: quickLoad,
|
|
119
148
|
options: {
|
|
149
|
+
enabled: !isSettingsOpen,
|
|
120
150
|
meta: {
|
|
121
151
|
name: t("load_last_save"),
|
|
122
152
|
description: t("quick_load_hotkey_alternative_description"),
|
|
@@ -132,6 +162,7 @@ export function useSettingsHotkeys(): null {
|
|
|
132
162
|
const { t } = useTranslation(["ui"]);
|
|
133
163
|
const setSettingsOpen = useSetSearchParamState<boolean>("settings");
|
|
134
164
|
const setSettingsTab = useSetSearchParamState<string>("settings_tab");
|
|
165
|
+
const { isAnyMenuOrDialogOpen } = useMenuDialogState();
|
|
135
166
|
|
|
136
167
|
const openControlsPage = useCallback(() => {
|
|
137
168
|
setSettingsOpen(true);
|
|
@@ -150,9 +181,10 @@ export function useSettingsHotkeys(): null {
|
|
|
150
181
|
},
|
|
151
182
|
},
|
|
152
183
|
{
|
|
153
|
-
hotkey: "
|
|
184
|
+
hotkey: "K",
|
|
154
185
|
callback: openControlsPage,
|
|
155
186
|
options: {
|
|
187
|
+
enabled: !isAnyMenuOrDialogOpen,
|
|
156
188
|
meta: {
|
|
157
189
|
name: t("hotkeys_menu"),
|
|
158
190
|
description: t("hotkeys_menu_shortcut_description"),
|
|
@@ -169,6 +201,7 @@ export function useGameHotkeys(): null {
|
|
|
169
201
|
const setSettingsTab = useSetSearchParamState<string>("settings_tab");
|
|
170
202
|
const setHistory = useSetSearchParamState<boolean>("history");
|
|
171
203
|
const { t } = useTranslation(["ui"]);
|
|
204
|
+
const { isAnyMenuOrDialogOpen } = useMenuDialogState();
|
|
172
205
|
|
|
173
206
|
const openHistoryPage = useCallback(() => {
|
|
174
207
|
setHistory(undefined);
|
|
@@ -187,9 +220,10 @@ export function useGameHotkeys(): null {
|
|
|
187
220
|
|
|
188
221
|
useHotkeys([
|
|
189
222
|
{
|
|
190
|
-
hotkey: "
|
|
223
|
+
hotkey: "H",
|
|
191
224
|
callback: openHistoryPage,
|
|
192
225
|
options: {
|
|
226
|
+
enabled: !isAnyMenuOrDialogOpen,
|
|
193
227
|
meta: {
|
|
194
228
|
name: t("history"),
|
|
195
229
|
description: t("history_hotkey_description"),
|
|
@@ -200,6 +234,7 @@ export function useGameHotkeys(): null {
|
|
|
200
234
|
hotkey: "Tab",
|
|
201
235
|
callback: toggleQuickActionsWheel,
|
|
202
236
|
options: {
|
|
237
|
+
enabled: !isAnyMenuOrDialogOpen,
|
|
203
238
|
meta: {
|
|
204
239
|
name: t("quick_actions"),
|
|
205
240
|
description: t("quick_actions_open_description"),
|
|
@@ -211,38 +246,104 @@ export function useGameHotkeys(): null {
|
|
|
211
246
|
return null;
|
|
212
247
|
}
|
|
213
248
|
|
|
249
|
+
export function useChoiceMenuHotkeys(menuLength: number) {
|
|
250
|
+
const { t } = useTranslation(["ui"]);
|
|
251
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
252
|
+
|
|
253
|
+
function getMenuItems(): HTMLButtonElement[] {
|
|
254
|
+
if (!menuRef.current) return [];
|
|
255
|
+
return Array.from(
|
|
256
|
+
menuRef.current.querySelectorAll<HTMLButtonElement>(
|
|
257
|
+
"button[role='menuitem']:not(:disabled)",
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function focusMenuItem(direction: "up" | "down") {
|
|
263
|
+
const items = getMenuItems();
|
|
264
|
+
if (!items.length) return;
|
|
265
|
+
const active = document.activeElement as HTMLElement;
|
|
266
|
+
const currentIndex = items.indexOf(active as HTMLButtonElement);
|
|
267
|
+
let next: number;
|
|
268
|
+
if (direction === "down") {
|
|
269
|
+
next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
270
|
+
} else {
|
|
271
|
+
next = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
272
|
+
}
|
|
273
|
+
items[next].focus();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
useHotkeys([
|
|
277
|
+
{
|
|
278
|
+
hotkey: "ArrowDown",
|
|
279
|
+
callback: () => focusMenuItem("down"),
|
|
280
|
+
options: {
|
|
281
|
+
enabled: menuLength > 0,
|
|
282
|
+
preventDefault: true,
|
|
283
|
+
meta: {
|
|
284
|
+
name: t("choice_navigation"),
|
|
285
|
+
description: t("choice_navigation_description"),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
hotkey: "ArrowUp",
|
|
291
|
+
callback: () => focusMenuItem("up"),
|
|
292
|
+
options: {
|
|
293
|
+
enabled: menuLength > 0,
|
|
294
|
+
preventDefault: true,
|
|
295
|
+
meta: {
|
|
296
|
+
name: t("choice_navigation"),
|
|
297
|
+
description: t("choice_navigation_description"),
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
return { menuRef };
|
|
304
|
+
}
|
|
305
|
+
|
|
214
306
|
export function useNarrationHotkeys(): null {
|
|
215
307
|
const { t } = useTranslation(["ui"]);
|
|
216
308
|
const { goNext } = useNarrationFunctions();
|
|
217
|
-
const
|
|
309
|
+
const typewriterInProgress = useSelector(
|
|
310
|
+
TextDisplaySettings.store,
|
|
311
|
+
(state) => state.inProgress,
|
|
312
|
+
);
|
|
313
|
+
const { isAnyMenuOrDialogOpen } = useMenuDialogState();
|
|
314
|
+
const { data: canGoNext } = useQueryCanGoNext();
|
|
218
315
|
|
|
219
316
|
const onSkipKeyDown = useCallback(() => SkipSettings.setEnabled(true), []);
|
|
220
317
|
const onSkipKeyUp = useCallback(() => {
|
|
221
318
|
SkipSettings.setEnabled(false);
|
|
319
|
+
if (typewriterInProgress && !narration.dialogGlue) {
|
|
320
|
+
TextDisplaySettings.complete();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
222
323
|
goNext();
|
|
223
|
-
}, [goNext]);
|
|
324
|
+
}, [goNext, typewriterInProgress]);
|
|
224
325
|
|
|
225
326
|
useHotkeys([
|
|
226
327
|
{
|
|
227
328
|
hotkey: "Enter",
|
|
228
329
|
callback: onSkipKeyDown,
|
|
229
330
|
options: {
|
|
331
|
+
enabled: !isAnyMenuOrDialogOpen && canGoNext,
|
|
230
332
|
meta: {
|
|
231
333
|
name: t("skip"),
|
|
232
334
|
description: t("skip_hold_description"),
|
|
233
335
|
},
|
|
234
|
-
enabled: !isRequired,
|
|
235
336
|
},
|
|
236
337
|
},
|
|
237
338
|
{
|
|
238
339
|
hotkey: "Space",
|
|
239
340
|
callback: onSkipKeyDown,
|
|
240
341
|
options: {
|
|
342
|
+
enabled: !isAnyMenuOrDialogOpen && canGoNext,
|
|
241
343
|
meta: {
|
|
242
344
|
name: t("skip"),
|
|
243
345
|
description: t("skip_hold_space_description"),
|
|
244
346
|
},
|
|
245
|
-
enabled: !isRequired,
|
|
246
347
|
},
|
|
247
348
|
},
|
|
248
349
|
{
|
|
@@ -251,11 +352,11 @@ export function useNarrationHotkeys(): null {
|
|
|
251
352
|
options: {
|
|
252
353
|
eventType: "keyup",
|
|
253
354
|
conflictBehavior: "allow",
|
|
355
|
+
enabled: !isAnyMenuOrDialogOpen && canGoNext,
|
|
254
356
|
meta: {
|
|
255
357
|
name: t("next"),
|
|
256
358
|
description: t("skip_release_description"),
|
|
257
359
|
},
|
|
258
|
-
enabled: !isRequired,
|
|
259
360
|
},
|
|
260
361
|
},
|
|
261
362
|
{
|
|
@@ -264,11 +365,11 @@ export function useNarrationHotkeys(): null {
|
|
|
264
365
|
options: {
|
|
265
366
|
eventType: "keyup",
|
|
266
367
|
conflictBehavior: "allow",
|
|
368
|
+
enabled: !isAnyMenuOrDialogOpen && canGoNext,
|
|
267
369
|
meta: {
|
|
268
370
|
name: t("next"),
|
|
269
371
|
description: t("skip_release_space_description"),
|
|
270
372
|
},
|
|
271
|
-
enabled: !isRequired,
|
|
272
373
|
},
|
|
273
374
|
},
|
|
274
375
|
]);
|
|
@@ -22,6 +22,7 @@ export function useNarrationFunctions() {
|
|
|
22
22
|
|
|
23
23
|
const goNext = useCallback(async () => {
|
|
24
24
|
if (hasOpenMenu) return;
|
|
25
|
+
TextDisplaySettings.resetForNext();
|
|
25
26
|
GameStatus.setLoading(true);
|
|
26
27
|
try {
|
|
27
28
|
if (!narration.canContinue) {
|
|
@@ -94,6 +95,7 @@ const isDragGesture = (dx: number, dy: number) =>
|
|
|
94
95
|
export function useNarrationPointerHandlers() {
|
|
95
96
|
const { goNext } = useNarrationFunctions();
|
|
96
97
|
const skipEnabled = useSelector(SkipSettings.store, (state) => state.enabled);
|
|
98
|
+
const typewriterInProgress = useSelector(TextDisplaySettings.store, (state) => state.inProgress);
|
|
97
99
|
const pointerDownPos = useRef<{ x: number; y: number } | null>(null);
|
|
98
100
|
const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
99
101
|
const longPressTriggered = useRef(false);
|
|
@@ -166,9 +168,13 @@ export function useNarrationPointerHandlers() {
|
|
|
166
168
|
if (skipEnabled) {
|
|
167
169
|
SkipSettings.setEnabled(false);
|
|
168
170
|
}
|
|
171
|
+
if (typewriterInProgress && !narration.dialogGlue) {
|
|
172
|
+
TextDisplaySettings.complete();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
169
175
|
goNext();
|
|
170
176
|
},
|
|
171
|
-
[clearLongPressTimer, goNext, skipEnabled],
|
|
177
|
+
[clearLongPressTimer, goNext, skipEnabled, typewriterInProgress],
|
|
172
178
|
);
|
|
173
179
|
|
|
174
180
|
const handlePointerMove = useCallback(
|