create-pixi-vn 2.1.4 → 2.1.5

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 (119) hide show
  1. package/package.json +1 -1
  2. package/template-lib/package.json +3 -0
  3. package/template-nqtr-react-vite-muijoy/package.json +2 -2
  4. package/template-nqtr-react-vite-muijoy/src/assets/index.ts +2 -11
  5. package/template-nqtr-react-vite-muijoy/src/assets/manifest.gen.json +2 -7
  6. package/template-nqtr-react-vite-muijoy/src/components/menus/settings/index.tsx +15 -6
  7. package/template-nqtr-react-vite-muijoy/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  8. package/template-nqtr-react-vite-muijoy/src/components/menus/settings/quick-menus.tsx +25 -1
  9. package/template-nqtr-react-vite-muijoy/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  10. package/template-nqtr-react-vite-muijoy/src/lib/hooks/hotkeys-hooks.ts +16 -0
  11. package/template-nqtr-react-vite-muijoy/src/lib/utils/device-diagnostics-utility.ts +382 -0
  12. package/template-nqtr-react-vite-muijoy/src/locales/en.json +50 -0
  13. package/template-nqtr-react-vite-muijoy/src/pixi-vn.keys.gen.ts +4 -7
  14. package/template-nqtr-react-vite-muijoy-ink/package.json +3 -3
  15. package/template-nqtr-react-vite-muijoy-ink/src/assets/index.ts +2 -11
  16. package/template-nqtr-react-vite-muijoy-ink/src/assets/manifest.gen.json +2 -7
  17. package/template-nqtr-react-vite-muijoy-ink/src/components/menus/settings/index.tsx +15 -6
  18. package/template-nqtr-react-vite-muijoy-ink/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  19. package/template-nqtr-react-vite-muijoy-ink/src/components/menus/settings/quick-menus.tsx +25 -1
  20. package/template-nqtr-react-vite-muijoy-ink/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  21. package/template-nqtr-react-vite-muijoy-ink/src/lib/hooks/hotkeys-hooks.ts +16 -0
  22. package/template-nqtr-react-vite-muijoy-ink/src/lib/utils/device-diagnostics-utility.ts +382 -0
  23. package/template-nqtr-react-vite-muijoy-ink/src/locales/en.json +50 -0
  24. package/template-nqtr-react-vite-muijoy-ink/src/pixi-vn.keys.gen.ts +4 -7
  25. package/template-nqtr-react-vite-muijoy-ink-tauri/.vscode/launch.json +1 -1
  26. package/template-nqtr-react-vite-muijoy-ink-tauri/.vscode/tasks.json +1 -35
  27. package/template-nqtr-react-vite-muijoy-ink-tauri/package.json +3 -3
  28. package/template-nqtr-react-vite-muijoy-ink-tauri/src/assets/index.ts +2 -11
  29. package/template-nqtr-react-vite-muijoy-ink-tauri/src/assets/manifest.gen.json +2 -7
  30. package/template-nqtr-react-vite-muijoy-ink-tauri/src/components/menus/settings/index.tsx +15 -6
  31. package/template-nqtr-react-vite-muijoy-ink-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  32. package/template-nqtr-react-vite-muijoy-ink-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  33. package/template-nqtr-react-vite-muijoy-ink-tauri/src/content/ink/hashtag-commands.ts +17 -0
  34. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  35. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  36. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/system-info.ts +54 -0
  37. package/template-nqtr-react-vite-muijoy-ink-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  38. package/template-nqtr-react-vite-muijoy-ink-tauri/src/locales/en.json +66 -0
  39. package/template-nqtr-react-vite-muijoy-ink-tauri/src/pixi-vn.keys.gen.ts +4 -7
  40. package/template-nqtr-react-vite-muijoy-ink-tauri/src-tauri/Cargo.toml +1 -0
  41. package/template-nqtr-react-vite-muijoy-ink-tauri/src-tauri/src/lib.rs +34 -22
  42. package/template-nqtr-react-vite-muijoy-ink-tauri/src-tauri/src/system_info.rs +33 -0
  43. package/template-nqtr-react-vite-muijoy-tauri/.vscode/launch.json +1 -1
  44. package/template-nqtr-react-vite-muijoy-tauri/.vscode/tasks.json +1 -35
  45. package/template-nqtr-react-vite-muijoy-tauri/package.json +2 -2
  46. package/template-nqtr-react-vite-muijoy-tauri/src/assets/index.ts +2 -11
  47. package/template-nqtr-react-vite-muijoy-tauri/src/assets/manifest.gen.json +2 -7
  48. package/template-nqtr-react-vite-muijoy-tauri/src/components/menus/settings/index.tsx +15 -6
  49. package/template-nqtr-react-vite-muijoy-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  50. package/template-nqtr-react-vite-muijoy-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  51. package/template-nqtr-react-vite-muijoy-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  52. package/template-nqtr-react-vite-muijoy-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  53. package/template-nqtr-react-vite-muijoy-tauri/src/lib/system-info.ts +54 -0
  54. package/template-nqtr-react-vite-muijoy-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  55. package/template-nqtr-react-vite-muijoy-tauri/src/locales/en.json +66 -0
  56. package/template-nqtr-react-vite-muijoy-tauri/src/pixi-vn.keys.gen.ts +4 -7
  57. package/template-nqtr-react-vite-muijoy-tauri/src-tauri/Cargo.toml +1 -0
  58. package/template-nqtr-react-vite-muijoy-tauri/src-tauri/src/lib.rs +32 -20
  59. package/template-nqtr-react-vite-muijoy-tauri/src-tauri/src/system_info.rs +33 -0
  60. package/template-react-vite-muijoy/package.json +1 -1
  61. package/template-react-vite-muijoy/src/assets/index.ts +55 -55
  62. package/template-react-vite-muijoy/src/assets/manifest.gen.json +2 -7
  63. package/template-react-vite-muijoy/src/components/menus/settings/index.tsx +15 -6
  64. package/template-react-vite-muijoy/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  65. package/template-react-vite-muijoy/src/components/menus/settings/quick-menus.tsx +25 -1
  66. package/template-react-vite-muijoy/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  67. package/template-react-vite-muijoy/src/lib/hooks/hotkeys-hooks.ts +16 -0
  68. package/template-react-vite-muijoy/src/lib/utils/device-diagnostics-utility.ts +382 -0
  69. package/template-react-vite-muijoy/src/locales/en.json +50 -0
  70. package/template-react-vite-muijoy/src/pixi-vn.keys.gen.ts +2 -3
  71. package/template-react-vite-muijoy-ink/ink/second_part.ink +0 -1
  72. package/template-react-vite-muijoy-ink/ink/start.ink +4 -6
  73. package/template-react-vite-muijoy-ink/package.json +2 -2
  74. package/template-react-vite-muijoy-ink/src/assets/index.ts +55 -55
  75. package/template-react-vite-muijoy-ink/src/assets/manifest.gen.json +2 -7
  76. package/template-react-vite-muijoy-ink/src/components/menus/settings/index.tsx +15 -6
  77. package/template-react-vite-muijoy-ink/src/components/menus/settings/menus/diagnostics.tsx +266 -0
  78. package/template-react-vite-muijoy-ink/src/components/menus/settings/quick-menus.tsx +25 -1
  79. package/template-react-vite-muijoy-ink/src/lib/hooks/device-diagnostics-hooks.ts +68 -0
  80. package/template-react-vite-muijoy-ink/src/lib/hooks/hotkeys-hooks.ts +16 -0
  81. package/template-react-vite-muijoy-ink/src/lib/utils/device-diagnostics-utility.ts +382 -0
  82. package/template-react-vite-muijoy-ink/src/locales/en.json +50 -0
  83. package/template-react-vite-muijoy-ink/src/pixi-vn.keys.gen.ts +2 -3
  84. package/template-react-vite-muijoy-ink-tauri/.vscode/launch.json +1 -1
  85. package/template-react-vite-muijoy-ink-tauri/.vscode/tasks.json +1 -35
  86. package/template-react-vite-muijoy-ink-tauri/ink/second_part.ink +0 -1
  87. package/template-react-vite-muijoy-ink-tauri/ink/start.ink +4 -6
  88. package/template-react-vite-muijoy-ink-tauri/package.json +2 -2
  89. package/template-react-vite-muijoy-ink-tauri/src/assets/index.ts +55 -55
  90. package/template-react-vite-muijoy-ink-tauri/src/assets/manifest.gen.json +2 -7
  91. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/index.tsx +15 -6
  92. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  93. package/template-react-vite-muijoy-ink-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  94. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  95. package/template-react-vite-muijoy-ink-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  96. package/template-react-vite-muijoy-ink-tauri/src/lib/system-info.ts +54 -0
  97. package/template-react-vite-muijoy-ink-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  98. package/template-react-vite-muijoy-ink-tauri/src/locales/en.json +66 -0
  99. package/template-react-vite-muijoy-ink-tauri/src/pixi-vn.keys.gen.ts +2 -3
  100. package/template-react-vite-muijoy-ink-tauri/src-tauri/Cargo.toml +1 -0
  101. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/lib.rs +32 -20
  102. package/template-react-vite-muijoy-ink-tauri/src-tauri/src/system_info.rs +33 -0
  103. package/template-react-vite-muijoy-tauri/.vscode/launch.json +1 -1
  104. package/template-react-vite-muijoy-tauri/.vscode/tasks.json +1 -35
  105. package/template-react-vite-muijoy-tauri/package.json +1 -1
  106. package/template-react-vite-muijoy-tauri/src/assets/index.ts +55 -55
  107. package/template-react-vite-muijoy-tauri/src/assets/manifest.gen.json +2 -7
  108. package/template-react-vite-muijoy-tauri/src/components/menus/settings/index.tsx +15 -6
  109. package/template-react-vite-muijoy-tauri/src/components/menus/settings/menus/diagnostics.tsx +332 -0
  110. package/template-react-vite-muijoy-tauri/src/components/menus/settings/quick-menus.tsx +32 -1
  111. package/template-react-vite-muijoy-tauri/src/lib/hooks/device-diagnostics-hooks.ts +96 -0
  112. package/template-react-vite-muijoy-tauri/src/lib/hooks/hotkeys-hooks.ts +16 -0
  113. package/template-react-vite-muijoy-tauri/src/lib/system-info.ts +54 -0
  114. package/template-react-vite-muijoy-tauri/src/lib/utils/device-diagnostics-utility.ts +511 -0
  115. package/template-react-vite-muijoy-tauri/src/locales/en.json +66 -0
  116. package/template-react-vite-muijoy-tauri/src/pixi-vn.keys.gen.ts +2 -3
  117. package/template-react-vite-muijoy-tauri/src-tauri/Cargo.toml +1 -0
  118. package/template-react-vite-muijoy-tauri/src-tauri/src/lib.rs +32 -20
  119. package/template-react-vite-muijoy-tauri/src-tauri/src/system_info.rs +33 -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.1.4",
4
+ "version": "2.1.5",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
7
7
  "author": "DRincs-Productions",
@@ -9,6 +9,9 @@
9
9
  "files": [
10
10
  "dist"
11
11
  ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
12
15
  "workspaces": [
13
16
  "playground"
14
17
  ],
@@ -19,8 +19,8 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@base-ui/react": "^1.6.0",
22
- "@drincs/nqtr": "^1.0.2",
23
- "@drincs/pixi-vn": "^1.8.21",
22
+ "@drincs/nqtr": "^1.0.3",
23
+ "@drincs/pixi-vn": "^1.8.25",
24
24
  "@tailwindcss/vite": "^4.3.0",
25
25
  "@tanstack/hotkeys": "^0.8.0",
26
26
  "@tanstack/react-hotkeys": "^0.10.0",
@@ -14,16 +14,7 @@ export const manifest: AssetsManifest = {
14
14
  ...generatedManifestJson.bundles,
15
15
  {
16
16
  name: AUDIO_BUNDLE_NAME,
17
- assets: [
18
- {
19
- alias: "bgm_cheerful",
20
- src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/audio/bgm_cheerful.wav",
21
- },
22
- {
23
- alias: "sfx_whoosh",
24
- src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/audio/sfx_whoosh.wav",
25
- },
26
- ],
17
+ assets: [],
27
18
  },
28
19
  // screens
29
20
  {
@@ -31,7 +22,7 @@ export const manifest: AssetsManifest = {
31
22
  assets: [
32
23
  {
33
24
  alias: "background_main_menu",
34
- src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/main-menu.png",
25
+ src: "https://pub-72ff059a2c6642fb9eab15df80fb3b45.r2.dev/main-menu.png",
35
26
  },
36
27
  ],
37
28
  },
@@ -1,8 +1,3 @@
1
1
  {
2
- "bundles": [
3
- {
4
- "name": "default",
5
- "assets": []
6
- }
7
- ]
8
- }
2
+ "bundles": []
3
+ }
@@ -1,6 +1,7 @@
1
1
  import { About } from "@/components/menus/settings/about";
2
2
  import { DialoguesControls } from "@/components/menus/settings/dialogues-controls";
3
3
  import { ControlsListSettingsPage } from "@/components/menus/settings/menus/controls";
4
+ import { DiagnosticsSettingsPage } from "@/components/menus/settings/menus/diagnostics";
4
5
  import { HistoryListSettingsPage } from "@/components/menus/settings/menus/history";
5
6
  import { SaveLoadSettingsPage } from "@/components/menus/settings/menus/save-load";
6
7
  import { QuickMenus } from "@/components/menus/settings/quick-menus";
@@ -23,7 +24,7 @@ import { ArrowLeftIcon } from "lucide-react";
23
24
  import { Fragment } from "react";
24
25
  import { useTranslation } from "react-i18next";
25
26
 
26
- type SettingsTabPath = "menus/controls" | "menus/history" | "menus/save-load";
27
+ type SettingsTabPath = "menus/controls" | "menus/history" | "menus/save-load" | "menus/diagnostics";
27
28
 
28
29
  type BreadcrumbEntry = {
29
30
  id: string;
@@ -38,7 +39,8 @@ export function Settings() {
38
39
  const normalizedTab: SettingsTabPath | undefined =
39
40
  currentTab === "menus/controls" ||
40
41
  currentTab === "menus/history" ||
41
- currentTab === "menus/save-load"
42
+ currentTab === "menus/save-load" ||
43
+ currentTab === "menus/diagnostics"
42
44
  ? (currentTab as SettingsTabPath)
43
45
  : undefined;
44
46
 
@@ -55,7 +57,8 @@ export function Settings() {
55
57
  if (
56
58
  normalizedTab === "menus/controls" ||
57
59
  normalizedTab === "menus/history" ||
58
- normalizedTab === "menus/save-load"
60
+ normalizedTab === "menus/save-load" ||
61
+ normalizedTab === "menus/diagnostics"
59
62
  ) {
60
63
  trail.push({ id: "menus", label: t("menus") });
61
64
  trail.push({
@@ -64,13 +67,17 @@ export function Settings() {
64
67
  ? "menus-controls"
65
68
  : normalizedTab === "menus/history"
66
69
  ? "menus-history"
67
- : "menus-save-load",
70
+ : normalizedTab === "menus/save-load"
71
+ ? "menus-save-load"
72
+ : "menus-diagnostics",
68
73
  label:
69
74
  normalizedTab === "menus/controls"
70
75
  ? t("hotkeys_menu")
71
76
  : normalizedTab === "menus/history"
72
77
  ? t("history")
73
- : `${t("save")}/${t("load")}`,
78
+ : normalizedTab === "menus/save-load"
79
+ ? `${t("save")}/${t("load")}`
80
+ : t("diagnostics"),
74
81
  });
75
82
  }
76
83
  return trail;
@@ -112,7 +119,8 @@ export function Settings() {
112
119
  if (
113
120
  normalizedTab === "menus/controls" ||
114
121
  normalizedTab === "menus/history" ||
115
- normalizedTab === "menus/save-load"
122
+ normalizedTab === "menus/save-load" ||
123
+ normalizedTab === "menus/diagnostics"
116
124
  ) {
117
125
  return (
118
126
  <>
@@ -130,6 +138,7 @@ export function Settings() {
130
138
  {normalizedTab === "menus/controls" ? <ControlsListSettingsPage /> : null}
131
139
  {normalizedTab === "menus/history" ? <HistoryListSettingsPage /> : null}
132
140
  {normalizedTab === "menus/save-load" ? <SaveLoadSettingsPage /> : null}
141
+ {normalizedTab === "menus/diagnostics" ? <DiagnosticsSettingsPage /> : null}
133
142
  </>
134
143
  );
135
144
  }
@@ -0,0 +1,266 @@
1
+ import { Button } from "@/components/ui/button";
2
+ import { ScrollArea } from "@/components/ui/scroll-area";
3
+ import { useDeviceDiagnostics } from "@/lib/hooks/device-diagnostics-hooks";
4
+ import {
5
+ buildDiagnosticsReportText,
6
+ downloadDiagnosticsReportJson,
7
+ } from "@/lib/utils/device-diagnostics-utility";
8
+ import { AlertTriangleIcon, CheckIcon, ClipboardCopyIcon, DownloadIcon, XIcon } from "lucide-react";
9
+ import type { ReactNode } from "react";
10
+ import { useTranslation } from "react-i18next";
11
+ import { toast } from "sonner";
12
+
13
+ function DiagnosticsSection({ title, children }: { title: string; children: ReactNode }) {
14
+ return (
15
+ <div className="flex flex-col gap-2">
16
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
17
+ {title}
18
+ </h3>
19
+ <div className="flex flex-col gap-1 rounded-lg border bg-card/80 p-3">{children}</div>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ function DiagnosticRow({ label, value }: { label: string; value: ReactNode }) {
25
+ return (
26
+ <div className="flex items-start justify-between gap-4 text-sm">
27
+ <span className="text-muted-foreground">{label}</span>
28
+ <span className="text-right font-medium break-all">{value}</span>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ function CapabilityRow({
34
+ label,
35
+ supported,
36
+ hint,
37
+ }: {
38
+ label: string;
39
+ supported: boolean | null;
40
+ hint?: string;
41
+ }) {
42
+ const { t } = useTranslation(["ui"]);
43
+ return (
44
+ <div className="flex items-center justify-between gap-4 text-sm">
45
+ <div>
46
+ <p className="font-medium">{label}</p>
47
+ {hint && <p className="text-xs text-muted-foreground">{hint}</p>}
48
+ </div>
49
+ {supported === null ? (
50
+ <span className="shrink-0 text-xs text-muted-foreground">
51
+ {t("diagnostics_unknown")}
52
+ </span>
53
+ ) : supported ? (
54
+ <CheckIcon className="size-4 shrink-0 text-green-600 dark:text-green-400" />
55
+ ) : (
56
+ <XIcon className="size-4 shrink-0 text-red-600 dark:text-red-400" />
57
+ )}
58
+ </div>
59
+ );
60
+ }
61
+
62
+ export function DiagnosticsSettingsPage() {
63
+ const diagnostics = useDeviceDiagnostics();
64
+ const { t } = useTranslation(["ui"]);
65
+ const { webgl, capabilities, resolution, memory, fps } = diagnostics;
66
+ const yesNo = (value: boolean | null) =>
67
+ value === null
68
+ ? t("diagnostics_unknown")
69
+ : value
70
+ ? t("diagnostics_yes")
71
+ : t("diagnostics_no");
72
+
73
+ const handleCopyReport = async () => {
74
+ try {
75
+ await navigator.clipboard.writeText(buildDiagnosticsReportText(diagnostics));
76
+ toast.success(t("diagnostics_report_copied"));
77
+ } catch {
78
+ toast.error(t("diagnostics_report_copy_failed"));
79
+ }
80
+ };
81
+
82
+ const handleDownloadReport = () => {
83
+ downloadDiagnosticsReportJson(diagnostics);
84
+ toast.success(t("diagnostics_report_downloaded"));
85
+ };
86
+
87
+ return (
88
+ <ScrollArea className="flex-1 min-h-0">
89
+ <div className="mx-auto flex max-w-3xl flex-col gap-4 p-4">
90
+ <div className="flex justify-end gap-2">
91
+ <Button variant="outline" onClick={handleDownloadReport}>
92
+ <DownloadIcon />
93
+ {t("diagnostics_download_report")}
94
+ </Button>
95
+ <Button onClick={handleCopyReport}>
96
+ <ClipboardCopyIcon />
97
+ {t("diagnostics_copy_report")}
98
+ </Button>
99
+ </div>
100
+
101
+ {!webgl.webgl2Supported && (
102
+ <div className="flex items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive">
103
+ <AlertTriangleIcon className="mt-0.5 size-5 shrink-0" />
104
+ <p>{t("diagnostics_webgl2_missing_message")}</p>
105
+ </div>
106
+ )}
107
+
108
+ <DiagnosticsSection title={t("diagnostics_section_engine")}>
109
+ <DiagnosticRow
110
+ label={t("diagnostics_game_version")}
111
+ value={diagnostics.gameVersion}
112
+ />
113
+ <DiagnosticRow
114
+ label={t("diagnostics_pixijs_version")}
115
+ value={diagnostics.pixiJsVersion}
116
+ />
117
+ <DiagnosticRow
118
+ label={t("diagnostics_pixivn_version")}
119
+ value={diagnostics.pixiVnVersion}
120
+ />
121
+ <DiagnosticRow
122
+ label={t("diagnostics_tonejs_version")}
123
+ value={diagnostics.toneJsVersion}
124
+ />
125
+ <DiagnosticRow
126
+ label={t("diagnostics_motion_version")}
127
+ value={diagnostics.motionVersion}
128
+ />
129
+ </DiagnosticsSection>
130
+
131
+ <DiagnosticsSection title={t("diagnostics_section_device")}>
132
+ <DiagnosticRow label={t("diagnostics_platform")} value={diagnostics.platform} />
133
+ <DiagnosticRow
134
+ label={t("diagnostics_browser_engine")}
135
+ value={diagnostics.browserEngine}
136
+ />
137
+ <DiagnosticRow
138
+ label={t("diagnostics_user_agent")}
139
+ value={diagnostics.userAgent}
140
+ />
141
+ </DiagnosticsSection>
142
+
143
+ <DiagnosticsSection title={t("diagnostics_section_graphics")}>
144
+ <DiagnosticRow
145
+ label={t("diagnostics_webgl1")}
146
+ value={yesNo(webgl.webgl1Supported)}
147
+ />
148
+ <DiagnosticRow
149
+ label={t("diagnostics_webgl2")}
150
+ value={yesNo(webgl.webgl2Supported)}
151
+ />
152
+ <DiagnosticRow
153
+ label={t("diagnostics_hardware_acceleration")}
154
+ value={yesNo(webgl.hardwareAccelerated)}
155
+ />
156
+ <DiagnosticRow
157
+ label={t("diagnostics_gpu_vendor")}
158
+ value={webgl.unmaskedVendor ?? webgl.vendor ?? t("diagnostics_unknown")}
159
+ />
160
+ <DiagnosticRow
161
+ label={t("diagnostics_gpu_renderer")}
162
+ value={webgl.unmaskedRenderer ?? webgl.renderer ?? t("diagnostics_unknown")}
163
+ />
164
+ <DiagnosticRow
165
+ label={t("diagnostics_max_texture_size")}
166
+ value={webgl.maxTextureSize ?? t("diagnostics_unknown")}
167
+ />
168
+ </DiagnosticsSection>
169
+
170
+ <DiagnosticsSection title={t("diagnostics_section_display")}>
171
+ <DiagnosticRow
172
+ label={t("diagnostics_resolution")}
173
+ value={`${resolution.innerWidth}x${resolution.innerHeight}`}
174
+ />
175
+ <DiagnosticRow
176
+ label={t("diagnostics_device_pixel_ratio")}
177
+ value={resolution.devicePixelRatio}
178
+ />
179
+ <DiagnosticRow
180
+ label={t("diagnostics_fps")}
181
+ value={fps ?? t("diagnostics_unknown")}
182
+ />
183
+ </DiagnosticsSection>
184
+
185
+ <DiagnosticsSection title={t("diagnostics_section_memory")}>
186
+ {memory ? (
187
+ <DiagnosticRow
188
+ label={t("diagnostics_js_heap")}
189
+ value={`${(memory.usedJSHeapSize / 1024 / 1024).toFixed(1)} / ${(
190
+ memory.totalJSHeapSize / 1024 / 1024
191
+ ).toFixed(1)} MB`}
192
+ />
193
+ ) : (
194
+ <p className="text-sm text-muted-foreground">
195
+ {t("diagnostics_js_heap_unavailable")}
196
+ </p>
197
+ )}
198
+ </DiagnosticsSection>
199
+
200
+ <DiagnosticsSection title={t("diagnostics_section_capabilities")}>
201
+ <CapabilityRow
202
+ label={t("diagnostics_webgl2")}
203
+ supported={webgl.webgl2Supported}
204
+ />
205
+ <CapabilityRow
206
+ label={t("diagnostics_hardware_acceleration")}
207
+ supported={webgl.hardwareAccelerated}
208
+ />
209
+ <CapabilityRow
210
+ label={t("diagnostics_gpu_supported")}
211
+ supported={webgl.webgl1Supported || webgl.webgl2Supported}
212
+ />
213
+ <CapabilityRow
214
+ label={t("diagnostics_max_texture_size_check")}
215
+ supported={(webgl.maxTextureSize ?? 0) >= 8192}
216
+ />
217
+ <CapabilityRow
218
+ label={t("diagnostics_float_textures")}
219
+ supported={webgl.floatTexturesSupported}
220
+ />
221
+ <CapabilityRow label={t("diagnostics_msaa")} supported={webgl.msaaSupported} />
222
+ <CapabilityRow
223
+ label={t("diagnostics_shaders_compile")}
224
+ supported={webgl.shadersCompile}
225
+ />
226
+ <CapabilityRow label={t("diagnostics_audio")} supported={capabilities.audio} />
227
+ <CapabilityRow
228
+ label={t("diagnostics_gamepad")}
229
+ supported={capabilities.gamepad}
230
+ />
231
+ <CapabilityRow
232
+ label={t("diagnostics_clipboard")}
233
+ supported={capabilities.clipboard}
234
+ />
235
+ <CapabilityRow
236
+ label={t("diagnostics_fullscreen")}
237
+ supported={capabilities.fullscreen}
238
+ />
239
+ <CapabilityRow
240
+ label={t("diagnostics_local_storage")}
241
+ supported={capabilities.localStorage}
242
+ />
243
+ <CapabilityRow
244
+ label={t("diagnostics_indexed_db")}
245
+ supported={capabilities.indexedDb}
246
+ />
247
+ <CapabilityRow
248
+ label={t("diagnostics_steam_overlay")}
249
+ supported={capabilities.steamOverlayDetected}
250
+ hint={t("diagnostics_steam_overlay_hint")}
251
+ />
252
+ </DiagnosticsSection>
253
+
254
+ <DiagnosticsSection title={t("diagnostics_section_extensions")}>
255
+ {webgl.keyExtensions.map((extension) => (
256
+ <CapabilityRow
257
+ key={extension.name}
258
+ label={extension.name}
259
+ supported={extension.supported}
260
+ />
261
+ ))}
262
+ </DiagnosticsSection>
263
+ </div>
264
+ </ScrollArea>
265
+ );
266
+ }
@@ -5,7 +5,7 @@ import { Separator } from "@/components/ui/separator";
5
5
  import { useSetSearchParamState } from "@/lib/hooks/navigation-hooks";
6
6
  import { Game } from "@drincs/pixi-vn";
7
7
  import { useLocation, useNavigate } from "@tanstack/react-router";
8
- import { Gamepad2Icon, HistoryIcon, LogOutIcon, SaveIcon } from "lucide-react";
8
+ import { Gamepad2Icon, HistoryIcon, LogOutIcon, SaveIcon, StethoscopeIcon } from "lucide-react";
9
9
  import { useTranslation } from "react-i18next";
10
10
 
11
11
  export function QuickMenus() {
@@ -18,6 +18,7 @@ export function QuickMenus() {
18
18
  {isInGame && <OpenHistorySettingButton />}
19
19
  <SaveLoadMenuButton />
20
20
  <OpenControlsListSettingButton />
21
+ <OpenDiagnosticsSettingButton />
21
22
  </div>
22
23
  {isInGame && <Separator />}
23
24
  {isInGame && <ReturnMainMenuButton />}
@@ -49,6 +50,29 @@ export function OpenControlsListSettingButton() {
49
50
  );
50
51
  }
51
52
 
53
+ export function OpenDiagnosticsSettingButton() {
54
+ const { t } = useTranslation(["ui"]);
55
+ const setSettingsOpen = useSetSearchParamState<boolean>("settings");
56
+ const setSettingsTab = useSetSearchParamState<string>("settings_tab");
57
+
58
+ return (
59
+ <Button
60
+ variant="outline"
61
+ className="w-full justify-start"
62
+ onClick={() => {
63
+ setSettingsOpen(true);
64
+ setSettingsTab("menus/diagnostics");
65
+ }}
66
+ >
67
+ <StethoscopeIcon />
68
+ {t("diagnostics")}
69
+ <KbdGroup className="ml-auto">
70
+ <Kbd>F8</Kbd>
71
+ </KbdGroup>
72
+ </Button>
73
+ );
74
+ }
75
+
52
76
  export function ReturnMainMenuButton() {
53
77
  const navigate = useNavigate();
54
78
  const { t } = useTranslation(["ui"]);
@@ -0,0 +1,68 @@
1
+ import {
2
+ type DeviceDiagnosticsSnapshot,
3
+ detectBrowserEngine,
4
+ detectPlatform,
5
+ getBrowserCapabilities,
6
+ getCurrentFps,
7
+ getGameVersion,
8
+ getJsMemoryInfo,
9
+ getMotionVersion,
10
+ getPixiJsVersion,
11
+ getPixiVnVersion,
12
+ getResolutionInfo,
13
+ getToneJsVersion,
14
+ getWebglDiagnostics,
15
+ } from "@/lib/utils/device-diagnostics-utility";
16
+ import { useEffect, useMemo, useState } from "react";
17
+
18
+ const LIVE_REFRESH_INTERVAL_MS = 1000;
19
+
20
+ /**
21
+ * WebGL support, browser capabilities, UA, platform and engine don't change during a session,
22
+ * so they're probed once. FPS, resolution and JS memory are polled since they change live.
23
+ */
24
+ export function useDeviceDiagnostics(): DeviceDiagnosticsSnapshot {
25
+ const webgl = useMemo(() => getWebglDiagnostics(), []);
26
+ const capabilities = useMemo(() => getBrowserCapabilities(), []);
27
+ const platform = useMemo(() => detectPlatform(), []);
28
+ const browserEngine = useMemo(() => detectBrowserEngine(), []);
29
+ const gameVersion = useMemo(() => getGameVersion(), []);
30
+ const pixiJsVersion = useMemo(() => getPixiJsVersion(), []);
31
+ const pixiVnVersion = useMemo(() => getPixiVnVersion(), []);
32
+ const toneJsVersion = useMemo(() => getToneJsVersion(), []);
33
+ const motionVersion = useMemo(() => getMotionVersion(), []);
34
+
35
+ const [resolution, setResolution] = useState(() => getResolutionInfo());
36
+ const [memory, setMemory] = useState(() => getJsMemoryInfo());
37
+ const [fps, setFps] = useState<number | null>(() => getCurrentFps());
38
+
39
+ useEffect(() => {
40
+ const onResize = () => setResolution(getResolutionInfo());
41
+ window.addEventListener("resize", onResize);
42
+ return () => window.removeEventListener("resize", onResize);
43
+ }, []);
44
+
45
+ useEffect(() => {
46
+ const interval = setInterval(() => {
47
+ setFps(getCurrentFps());
48
+ setMemory(getJsMemoryInfo());
49
+ }, LIVE_REFRESH_INTERVAL_MS);
50
+ return () => clearInterval(interval);
51
+ }, []);
52
+
53
+ return {
54
+ webgl,
55
+ capabilities,
56
+ resolution,
57
+ memory,
58
+ fps,
59
+ userAgent: navigator.userAgent,
60
+ platform,
61
+ browserEngine,
62
+ gameVersion,
63
+ pixiJsVersion,
64
+ pixiVnVersion,
65
+ toneJsVersion,
66
+ motionVersion,
67
+ };
68
+ }
@@ -175,6 +175,11 @@ export function useSettingsHotkeys(): null {
175
175
  setSettingsTab("menus/controls");
176
176
  }, [setSettingsOpen, setSettingsTab]);
177
177
 
178
+ const openDiagnosticsPage = useCallback(() => {
179
+ setSettingsOpen(true);
180
+ setSettingsTab("menus/diagnostics");
181
+ }, [setSettingsOpen, setSettingsTab]);
182
+
178
183
  useHotkeys([
179
184
  {
180
185
  hotkey: "Escape",
@@ -197,6 +202,17 @@ export function useSettingsHotkeys(): null {
197
202
  },
198
203
  },
199
204
  },
205
+ {
206
+ hotkey: "F8",
207
+ callback: openDiagnosticsPage,
208
+ options: {
209
+ enabled: !isAnyMenuOrDialogOpen,
210
+ meta: {
211
+ name: t("diagnostics"),
212
+ description: t("diagnostics_hotkey_description"),
213
+ },
214
+ },
215
+ },
200
216
  ]);
201
217
 
202
218
  return null;