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
@@ -5,6 +5,7 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
5
5
  // Steam is only compiled on desktop targets when the "steam" feature is on.
6
6
  #[cfg(all(feature = "steam", not(target_os = "ios"), not(target_os = "android")))]
7
7
  mod steam;
8
+ mod system_info;
8
9
 
9
10
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
10
11
  pub fn run() {
@@ -24,36 +25,47 @@ pub fn run() {
24
25
  // is set in Cargo.toml). Steam is not supported on iOS / Android.
25
26
  #[cfg(all(feature = "steam", not(target_os = "ios"), not(target_os = "android")))]
26
27
  {
27
- builder = builder
28
- .manage(steam::SteamClient {
29
- client: std::sync::Mutex::new(steam::try_init()),
30
- })
31
- .invoke_handler(tauri::generate_handler![
32
- steam::steam_is_available,
33
- steam::steam_get_player_name,
34
- steam::steam_get_app_id,
35
- steam::steam_unlock_achievement,
36
- steam::steam_is_achievement_unlocked,
37
- steam::steam_clear_achievement,
38
- steam::steam_set_stat_int,
39
- steam::steam_get_stat_int,
40
- steam::steam_set_stat_float,
41
- steam::steam_get_stat_float,
42
- steam::steam_store_stats,
43
- steam::steam_is_dlc_installed,
44
- steam::steam_open_overlay,
45
- steam::steam_open_store,
46
- ]);
28
+ builder = builder.manage(steam::SteamClient {
29
+ client: std::sync::Mutex::new(steam::try_init()),
30
+ });
47
31
  }
48
32
 
33
+ // `generate_handler!` needs a static list, so the Steam commands can't be
34
+ // appended conditionally — the two variants below are kept in sync by hand.
35
+ #[cfg(all(feature = "steam", not(target_os = "ios"), not(target_os = "android")))]
36
+ let builder = builder.invoke_handler(tauri::generate_handler![
37
+ system_info::get_system_info,
38
+ system_info::get_webview_version,
39
+ steam::steam_is_available,
40
+ steam::steam_get_player_name,
41
+ steam::steam_get_app_id,
42
+ steam::steam_unlock_achievement,
43
+ steam::steam_is_achievement_unlocked,
44
+ steam::steam_clear_achievement,
45
+ steam::steam_set_stat_int,
46
+ steam::steam_get_stat_int,
47
+ steam::steam_set_stat_float,
48
+ steam::steam_get_stat_float,
49
+ steam::steam_store_stats,
50
+ steam::steam_is_dlc_installed,
51
+ steam::steam_open_overlay,
52
+ steam::steam_open_store,
53
+ ]);
54
+
55
+ #[cfg(not(all(feature = "steam", not(target_os = "ios"), not(target_os = "android"))))]
56
+ let builder = builder.invoke_handler(tauri::generate_handler![
57
+ system_info::get_system_info,
58
+ system_info::get_webview_version,
59
+ ]);
60
+
49
61
  builder
50
62
  .run(tauri::generate_context!())
51
63
  .expect("error while running tauri application");
52
64
  }
53
65
 
54
- fn on_page_load(window: &tauri::Webview, _: &tauri::webview::PageLoadPayload<'_>) {
66
+ fn on_page_load(_window: &tauri::Webview, _: &tauri::webview::PageLoadPayload<'_>) {
55
67
  #[cfg(not(debug_assertions))]
56
- let _ = window.eval("document.addEventListener('contextmenu', e => e.preventDefault())");
68
+ let _ = _window.eval("document.addEventListener('contextmenu', e => e.preventDefault())");
57
69
  }
58
70
 
59
71
  #[cfg(not(mobile))]
@@ -0,0 +1,33 @@
1
+ use serde::Serialize;
2
+
3
+ #[derive(Serialize)]
4
+ pub struct SystemInfo {
5
+ /// Specific OS/distro name (e.g. "Ubuntu", "Windows", "Mac OS"), not just the generic platform.
6
+ os_type: String,
7
+ os_version: String,
8
+ bitness: String,
9
+ architecture: String,
10
+ }
11
+
12
+ /// Native OS/distro details, unavailable to plain web JS (browsers deliberately
13
+ /// don't expose this to limit fingerprinting).
14
+ #[tauri::command]
15
+ pub fn get_system_info() -> SystemInfo {
16
+ let info = os_info::get();
17
+ SystemInfo {
18
+ os_type: info.os_type().to_string(),
19
+ os_version: info.version().to_string(),
20
+ bitness: info.bitness().to_string(),
21
+ architecture: info
22
+ .architecture()
23
+ .map(str::to_string)
24
+ .unwrap_or_else(|| std::env::consts::ARCH.to_string()),
25
+ }
26
+ }
27
+
28
+ /// Underlying WebView engine version (WebView2 on Windows, WebKitGTK on Linux,
29
+ /// WKWebView on macOS/iOS, Android System WebView on Android).
30
+ #[tauri::command]
31
+ pub fn get_webview_version() -> Result<String, String> {
32
+ tauri::webview_version().map_err(|e| e.to_string())
33
+ }
@@ -26,7 +26,7 @@
26
26
  "cwd": "${workspaceFolder}",
27
27
  "terminal": "console",
28
28
  "preLaunchTask": "Cargo Build (Desktop Dev)",
29
- "postDebugTask": "Stop Vite Desktop Server"
29
+ "postDebugTask": "Stop Vite Dev Server"
30
30
  },
31
31
  {
32
32
  "name": "Debug Game Preview",
@@ -106,7 +106,7 @@
106
106
  "label": "Cargo Build (Desktop Dev)",
107
107
  "type": "shell",
108
108
  "command": "cargo build --manifest-path=./src-tauri/Cargo.toml --no-default-features",
109
- "dependsOn": ["Start Vite Desktop Server"],
109
+ "dependsOn": ["Restart Vite Dev Server"],
110
110
  "presentation": {
111
111
  "reveal": "always",
112
112
  "panel": "dedicated",
@@ -114,40 +114,6 @@
114
114
  "close": true
115
115
  },
116
116
  "problemMatcher": ["$rustc"]
117
- },
118
- {
119
- "label": "Start Vite Desktop Server",
120
- "type": "shell",
121
- "command": "npm run dev",
122
- "isBackground": true,
123
- "dependsOn": ["Install Dependencies"],
124
- "problemMatcher": {
125
- "owner": "vite",
126
- "pattern": {
127
- "regexp": "^(?!x)x"
128
- },
129
- "background": {
130
- "activeOnStart": true,
131
- "beginsPattern": ".",
132
- "endsPattern": "localhost:5173"
133
- }
134
- },
135
- "presentation": {
136
- "reveal": "always",
137
- "panel": "dedicated",
138
- "clear": false,
139
- "close": true
140
- }
141
- },
142
- {
143
- "label": "Stop Vite Desktop Server",
144
- "type": "shell",
145
- "command": "pkill -f 'vite' 2>/dev/null; pkill -f 'npm run dev' 2>/dev/null; exit 0",
146
- "presentation": {
147
- "reveal": "never",
148
- "panel": "dedicated",
149
- "close": true
150
- }
151
117
  }
152
118
  ]
153
119
  }
@@ -24,8 +24,8 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@base-ui/react": "^1.6.0",
27
- "@drincs/nqtr": "^1.0.2",
28
- "@drincs/pixi-vn": "^1.8.21",
27
+ "@drincs/nqtr": "^1.0.3",
28
+ "@drincs/pixi-vn": "^1.8.25",
29
29
  "@tailwindcss/vite": "^4.3.0",
30
30
  "@tanstack/hotkeys": "^0.8.0",
31
31
  "@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,332 @@
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, mobile, native, 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_engine_version")}
139
+ value={diagnostics.engineVersion ?? t("diagnostics_unknown")}
140
+ />
141
+ <DiagnosticRow
142
+ label={t("diagnostics_user_agent")}
143
+ value={diagnostics.userAgent}
144
+ />
145
+ </DiagnosticsSection>
146
+
147
+ <DiagnosticsSection title={t("diagnostics_section_native")}>
148
+ {native ? (
149
+ <>
150
+ <DiagnosticRow
151
+ label={t("diagnostics_os")}
152
+ value={`${native.osType} ${native.osVersion}`}
153
+ />
154
+ <DiagnosticRow
155
+ label={t("diagnostics_os_bitness")}
156
+ value={native.bitness}
157
+ />
158
+ <DiagnosticRow
159
+ label={t("diagnostics_os_architecture")}
160
+ value={native.architecture}
161
+ />
162
+ <DiagnosticRow
163
+ label={t("diagnostics_webview_version")}
164
+ value={native.webviewVersion ?? t("diagnostics_unknown")}
165
+ />
166
+ </>
167
+ ) : (
168
+ <p className="text-sm text-muted-foreground">
169
+ {t("diagnostics_native_unavailable")}
170
+ </p>
171
+ )}
172
+ </DiagnosticsSection>
173
+
174
+ <DiagnosticsSection title={t("diagnostics_section_graphics")}>
175
+ <DiagnosticRow
176
+ label={t("diagnostics_webgl1")}
177
+ value={yesNo(webgl.webgl1Supported)}
178
+ />
179
+ <DiagnosticRow
180
+ label={t("diagnostics_webgl2")}
181
+ value={yesNo(webgl.webgl2Supported)}
182
+ />
183
+ <DiagnosticRow
184
+ label={t("diagnostics_hardware_acceleration")}
185
+ value={yesNo(webgl.hardwareAccelerated)}
186
+ />
187
+ <DiagnosticRow
188
+ label={t("diagnostics_gpu_vendor")}
189
+ value={webgl.unmaskedVendor ?? webgl.vendor ?? t("diagnostics_unknown")}
190
+ />
191
+ <DiagnosticRow
192
+ label={t("diagnostics_gpu_renderer")}
193
+ value={webgl.unmaskedRenderer ?? webgl.renderer ?? t("diagnostics_unknown")}
194
+ />
195
+ <DiagnosticRow
196
+ label={t("diagnostics_gpu_vendor_brand")}
197
+ value={webgl.gpuVendorBrand}
198
+ />
199
+ <DiagnosticRow
200
+ label={t("diagnostics_mesa_driver")}
201
+ value={webgl.mesaVersion ?? t("diagnostics_not_detected")}
202
+ />
203
+ <DiagnosticRow
204
+ label={t("diagnostics_max_texture_size")}
205
+ value={webgl.maxTextureSize ?? t("diagnostics_unknown")}
206
+ />
207
+ </DiagnosticsSection>
208
+
209
+ <DiagnosticsSection title={t("diagnostics_section_display")}>
210
+ <DiagnosticRow
211
+ label={t("diagnostics_resolution")}
212
+ value={`${resolution.innerWidth}x${resolution.innerHeight}`}
213
+ />
214
+ <DiagnosticRow
215
+ label={t("diagnostics_device_pixel_ratio")}
216
+ value={resolution.devicePixelRatio}
217
+ />
218
+ <DiagnosticRow
219
+ label={t("diagnostics_fps")}
220
+ value={fps ?? t("diagnostics_unknown")}
221
+ />
222
+ </DiagnosticsSection>
223
+
224
+ <DiagnosticsSection title={t("diagnostics_section_mobile")}>
225
+ <DiagnosticRow
226
+ label={t("diagnostics_touch_support")}
227
+ value={
228
+ mobile.touchSupported
229
+ ? `${t("diagnostics_yes")} (${mobile.maxTouchPoints})`
230
+ : t("diagnostics_no")
231
+ }
232
+ />
233
+ <DiagnosticRow
234
+ label={t("diagnostics_device_memory")}
235
+ value={
236
+ mobile.deviceMemoryGb !== null
237
+ ? `${mobile.deviceMemoryGb} GB`
238
+ : t("diagnostics_not_available")
239
+ }
240
+ />
241
+ <DiagnosticRow
242
+ label={t("diagnostics_network_type")}
243
+ value={mobile.networkEffectiveType ?? t("diagnostics_not_available")}
244
+ />
245
+ <DiagnosticRow
246
+ label={t("diagnostics_screen_orientation")}
247
+ value={mobile.screenOrientation ?? t("diagnostics_unknown")}
248
+ />
249
+ </DiagnosticsSection>
250
+
251
+ <DiagnosticsSection title={t("diagnostics_section_memory")}>
252
+ {memory ? (
253
+ <DiagnosticRow
254
+ label={t("diagnostics_js_heap")}
255
+ value={`${(memory.usedJSHeapSize / 1024 / 1024).toFixed(1)} / ${(
256
+ memory.totalJSHeapSize / 1024 / 1024
257
+ ).toFixed(1)} MB`}
258
+ />
259
+ ) : (
260
+ <p className="text-sm text-muted-foreground">
261
+ {t("diagnostics_js_heap_unavailable")}
262
+ </p>
263
+ )}
264
+ </DiagnosticsSection>
265
+
266
+ <DiagnosticsSection title={t("diagnostics_section_capabilities")}>
267
+ <CapabilityRow
268
+ label={t("diagnostics_webgl2")}
269
+ supported={webgl.webgl2Supported}
270
+ />
271
+ <CapabilityRow
272
+ label={t("diagnostics_hardware_acceleration")}
273
+ supported={webgl.hardwareAccelerated}
274
+ />
275
+ <CapabilityRow
276
+ label={t("diagnostics_gpu_supported")}
277
+ supported={webgl.webgl1Supported || webgl.webgl2Supported}
278
+ />
279
+ <CapabilityRow
280
+ label={t("diagnostics_max_texture_size_check")}
281
+ supported={(webgl.maxTextureSize ?? 0) >= 8192}
282
+ />
283
+ <CapabilityRow
284
+ label={t("diagnostics_float_textures")}
285
+ supported={webgl.floatTexturesSupported}
286
+ />
287
+ <CapabilityRow label={t("diagnostics_msaa")} supported={webgl.msaaSupported} />
288
+ <CapabilityRow
289
+ label={t("diagnostics_shaders_compile")}
290
+ supported={webgl.shadersCompile}
291
+ />
292
+ <CapabilityRow label={t("diagnostics_audio")} supported={capabilities.audio} />
293
+ <CapabilityRow
294
+ label={t("diagnostics_gamepad")}
295
+ supported={capabilities.gamepad}
296
+ />
297
+ <CapabilityRow
298
+ label={t("diagnostics_clipboard")}
299
+ supported={capabilities.clipboard}
300
+ />
301
+ <CapabilityRow
302
+ label={t("diagnostics_fullscreen")}
303
+ supported={capabilities.fullscreen}
304
+ />
305
+ <CapabilityRow
306
+ label={t("diagnostics_local_storage")}
307
+ supported={capabilities.localStorage}
308
+ />
309
+ <CapabilityRow
310
+ label={t("diagnostics_indexed_db")}
311
+ supported={capabilities.indexedDb}
312
+ />
313
+ <CapabilityRow
314
+ label={t("diagnostics_steam_overlay")}
315
+ supported={capabilities.steamOverlayDetected}
316
+ hint={t("diagnostics_steam_overlay_hint")}
317
+ />
318
+ </DiagnosticsSection>
319
+
320
+ <DiagnosticsSection title={t("diagnostics_section_extensions")}>
321
+ {webgl.keyExtensions.map((extension) => (
322
+ <CapabilityRow
323
+ key={extension.name}
324
+ label={extension.name}
325
+ supported={extension.supported}
326
+ />
327
+ ))}
328
+ </DiagnosticsSection>
329
+ </div>
330
+ </ScrollArea>
331
+ );
332
+ }