Use claude to fix bugs & add a custom data location setting
This commit is contained in:
8 files changed
+242
-59
No files matched your search
@@ -21,6 +21,7 @@
|
|||||||
"decorum:allow-show-snap-overlay",
|
"decorum:allow-show-snap-overlay",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
|
"dialog:allow-open",
|
||||||
"notification:default",
|
"notification:default",
|
||||||
"clipboard-manager:default",
|
"clipboard-manager:default",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
|
|||||||
+164
-17
@@ -6,7 +6,7 @@ use openssl::sign::Verifier;
|
|||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::OnceLock;
|
use std::sync::{OnceLock, RwLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{File, create_dir_all},
|
fs::{File, create_dir_all},
|
||||||
@@ -33,6 +33,19 @@ use std::os::unix::fs::PermissionsExt;
|
|||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
use tauri_plugin_updater::UpdaterExt;
|
use tauri_plugin_updater::UpdaterExt;
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
custom_data_dir: RwLock<Option<PathBuf>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_dir(app: &AppHandle) -> Result<PathBuf, String> {
|
||||||
|
let state = app.state::<AppState>();
|
||||||
|
let guard = state.custom_data_dir.read().unwrap();
|
||||||
|
match &*guard {
|
||||||
|
Some(p) => Ok(p.clone()),
|
||||||
|
None => app.path().app_local_data_dir().map_err(|e| e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static CANCEL_MAP: OnceLock<DashMap<String, bool>> = OnceLock::new();
|
static CANCEL_MAP: OnceLock<DashMap<String, bool>> = OnceLock::new();
|
||||||
|
|
||||||
fn cancel_map() -> &'static DashMap<String, bool> {
|
fn cancel_map() -> &'static DashMap<String, bool> {
|
||||||
@@ -120,7 +133,7 @@ async fn unzip_to_dir(app: AppHandle, zip_path: PathBuf, out_dir: PathBuf, name:
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn folder_size(app: AppHandle, version: String) -> String {
|
fn folder_size(app: AppHandle, version: String) -> String {
|
||||||
let path = match app.path().app_local_data_dir() {
|
let path = match data_dir(&app) {
|
||||||
Ok(p) => p.join("game").join(&version),
|
Ok(p) => p.join("game").join(&version),
|
||||||
Err(_) => return "-1".to_string(),
|
Err(_) => return "-1".to_string(),
|
||||||
};
|
};
|
||||||
@@ -160,20 +173,15 @@ async fn download(
|
|||||||
let _ = app.emit("download-start", format!("{}", name));
|
let _ = app.emit("download-start", format!("{}", name));
|
||||||
let window = app.get_webview_window("main").expect("main window missing");
|
let window = app.get_webview_window("main").expect("main window missing");
|
||||||
|
|
||||||
let downloads_path = match app.path().app_local_data_dir() {
|
let base_dir = match data_dir(&app) {
|
||||||
Ok(p) => p.join("downloads"),
|
Ok(p) => p,
|
||||||
Err(_) => {
|
|
||||||
let _ = &app.emit("download-stop", format!("{}", name));
|
|
||||||
return "-1".to_string();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let game_path = match app.path().app_local_data_dir() {
|
|
||||||
Ok(p) => p.join("game"),
|
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let _ = &app.emit("download-stop", format!("{}", name));
|
let _ = &app.emit("download-stop", format!("{}", name));
|
||||||
return "-1".to_string();
|
return "-1".to_string();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let downloads_path = base_dir.join("downloads");
|
||||||
|
let game_path = base_dir.join("game");
|
||||||
|
|
||||||
let download_part_path = downloads_path.join(format!("{}.part", name));
|
let download_part_path = downloads_path.join(format!("{}.part", name));
|
||||||
let download_zip_path = downloads_path.join(format!("{}.zip", name));
|
let download_zip_path = downloads_path.join(format!("{}.zip", name));
|
||||||
@@ -414,7 +422,7 @@ fn launch_game(
|
|||||||
use_wine: bool,
|
use_wine: bool,
|
||||||
wine_command: String,
|
wine_command: String,
|
||||||
) {
|
) {
|
||||||
let game_folder = match app.path().app_local_data_dir() {
|
let game_folder = match data_dir(&app) {
|
||||||
Ok(p) => p.join("game").join(&name),
|
Ok(p) => p.join("game").join(&name),
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
@@ -473,10 +481,7 @@ fn launch_game(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bepinex_folder = match app.path().app_local_data_dir() {
|
let bepinex_folder = game_folder.join("BepInEx");
|
||||||
Ok(p) => p.join("game").join(&name).join("BepInEx"),
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if platform() == "macos" && !bepinex_folder.exists() && executable.ends_with(".app") {
|
if platform() == "macos" && !bepinex_folder.exists() && executable.ends_with(".app") {
|
||||||
if let Err(_) = Command::new("open")
|
if let Err(_) = Command::new("open")
|
||||||
@@ -530,6 +535,125 @@ async fn cancel_download(name: String) {
|
|||||||
cancel_map().insert(name.clone(), true);
|
cancel_map().insert(name.clone(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||||
|
fs::create_dir_all(dst)?;
|
||||||
|
for entry in fs::read_dir(src)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let src_path = entry.path();
|
||||||
|
let dst_path = dst.join(entry.file_name());
|
||||||
|
if src_path.is_dir() {
|
||||||
|
copy_dir_recursive(&src_path, &dst_path)?;
|
||||||
|
} else {
|
||||||
|
fs::copy(&src_path, &dst_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
|
||||||
|
if !src.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(parent) = dst.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
if dst.exists() {
|
||||||
|
fs::remove_dir_all(dst)?;
|
||||||
|
}
|
||||||
|
match fs::rename(src, dst) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(_) => {
|
||||||
|
copy_dir_recursive(src, dst)?;
|
||||||
|
fs::remove_dir_all(src)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn move_game_data(app: AppHandle, destination: String) -> Result<(), String> {
|
||||||
|
let default_dir = app.path().app_local_data_dir().map_err(|e| e.to_string())?;
|
||||||
|
let current_dir = data_dir(&app)?;
|
||||||
|
|
||||||
|
let new_dir = if destination.is_empty() {
|
||||||
|
default_dir.clone()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(&destination)
|
||||||
|
};
|
||||||
|
|
||||||
|
if current_dir == new_dir {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let src_game = current_dir.join("game");
|
||||||
|
let dst_game = new_dir.join("game");
|
||||||
|
let src_downloads = current_dir.join("downloads");
|
||||||
|
let dst_downloads = new_dir.join("downloads");
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn_blocking(move || {
|
||||||
|
fs::create_dir_all(&new_dir).map_err(|e| e.to_string())?;
|
||||||
|
move_dir(&src_game, &dst_game).map_err(|e| e.to_string())?;
|
||||||
|
move_dir(&src_downloads, &dst_downloads).map_err(|e| e.to_string())?;
|
||||||
|
Ok::<(), String>(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())??;
|
||||||
|
|
||||||
|
let state = app.state::<AppState>();
|
||||||
|
let mut guard = state.custom_data_dir.write().unwrap();
|
||||||
|
if destination.is_empty() {
|
||||||
|
*guard = None;
|
||||||
|
} else {
|
||||||
|
*guard = Some(PathBuf::from(&destination));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn restart_app(app: AppHandle) {
|
||||||
|
app.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn open_game_folder(app: AppHandle, version: String) -> Result<(), String> {
|
||||||
|
let dir = data_dir(&app)?;
|
||||||
|
let path = dir.join("game").join(&version);
|
||||||
|
if !path.exists() || !path.is_dir() {
|
||||||
|
return Err(format!("Game folder \"{}\" not found.", path.display()));
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
Command::new("open")
|
||||||
|
.arg(&path)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
Command::new("explorer")
|
||||||
|
.arg(&path)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
Command::new("xdg-open")
|
||||||
|
.arg(&path)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn remove_stale_executable(app: AppHandle, version: String, executable: String) -> bool {
|
||||||
|
let dir = match data_dir(&app) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
let path = dir.join("game").join(&version).join(&executable);
|
||||||
|
if path.exists() {
|
||||||
|
fs::remove_file(&path).is_ok()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn open_new_window(
|
fn open_new_window(
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
@@ -597,9 +721,32 @@ pub fn run() {
|
|||||||
folder_size,
|
folder_size,
|
||||||
verify_signature,
|
verify_signature,
|
||||||
cancel_download,
|
cancel_download,
|
||||||
open_new_window
|
open_new_window,
|
||||||
|
move_game_data,
|
||||||
|
restart_app,
|
||||||
|
open_game_folder,
|
||||||
|
remove_stale_executable
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
let custom_dir = app
|
||||||
|
.path()
|
||||||
|
.app_config_dir()
|
||||||
|
.ok()
|
||||||
|
.map(|d| d.join("settings.json"))
|
||||||
|
.filter(|p| p.exists())
|
||||||
|
.and_then(|p| fs::read_to_string(p).ok())
|
||||||
|
.and_then(|c| serde_json::from_str::<serde_json::Value>(&c).ok())
|
||||||
|
.and_then(|v| {
|
||||||
|
v.get("customDataLocation")
|
||||||
|
.and_then(|s| s.as_str())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(PathBuf::from)
|
||||||
|
});
|
||||||
|
|
||||||
|
app.manage(AppState {
|
||||||
|
custom_data_dir: RwLock::new(custom_dir),
|
||||||
|
});
|
||||||
|
|
||||||
let window = app.get_webview_window("main").unwrap();
|
let window = app.get_webview_window("main").unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|||||||
+20
-11
@@ -89,11 +89,13 @@ export default function RootLayout ({
|
|||||||
const [linuxWineCommand, setLinuxWineCommand] =
|
const [linuxWineCommand, setLinuxWineCommand] =
|
||||||
useState<string>('wine %path%')
|
useState<string>('wine %path%')
|
||||||
const [theme, setTheme] = useState<string>('dark')
|
const [theme, setTheme] = useState<string>('dark')
|
||||||
|
const [customDataLocation, setCustomDataLocation] = useState<string>('')
|
||||||
|
|
||||||
const [versionsList, setVersionsList] = useState<Record<string, number>>({})
|
const [versionsList, setVersionsList] = useState<Record<string, number>>({})
|
||||||
const [modsList, setModsList] = useState<
|
const [modsList, setModsList] = useState<
|
||||||
Record<string, Record<string, number>>
|
Record<string, Record<string, number>>
|
||||||
>({})
|
>({})
|
||||||
|
const [movingData, setMovingData] = useState(false)
|
||||||
|
|
||||||
function getSpecialVersionsList (game?: number): GameVersion[] {
|
function getSpecialVersionsList (game?: number): GameVersion[] {
|
||||||
if (!serverVersionList) return []
|
if (!serverVersionList) return []
|
||||||
@@ -117,7 +119,7 @@ export default function RootLayout ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closePopup = useCallback(() => {
|
const closePopup = useCallback(() => {
|
||||||
if (popupMode == 0 && selectedGame && pathname === '/') {
|
if (popupMode == 0 && selectedGame && pathname === '/main') {
|
||||||
setSelectedGame(null)
|
setSelectedGame(null)
|
||||||
setSelectedVersionList([])
|
setSelectedVersionList([])
|
||||||
} else if (viewingInfoFromDownloads) {
|
} else if (viewingInfoFromDownloads) {
|
||||||
@@ -312,7 +314,8 @@ export default function RootLayout ({
|
|||||||
sidebarAlwaysShowGames: true,
|
sidebarAlwaysShowGames: true,
|
||||||
linuxUseWine: false,
|
linuxUseWine: false,
|
||||||
linuxWineCommand: 'wine %path%',
|
linuxWineCommand: 'wine %path%',
|
||||||
theme: 'dark'
|
theme: 'dark',
|
||||||
|
customDataLocation: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const versionsLocal = await load('versions.json', {
|
const versionsLocal = await load('versions.json', {
|
||||||
@@ -623,14 +626,9 @@ export default function RootLayout ({
|
|||||||
verInfo.lastRevision > 0 &&
|
verInfo.lastRevision > 0 &&
|
||||||
value / 1000 <= verInfo.lastRevision
|
value / 1000 <= verInfo.lastRevision
|
||||||
) {
|
) {
|
||||||
if (
|
await invoke('remove_stale_executable', {
|
||||||
await exists('game/' + key + '/' + verInfo.executable, {
|
version: key,
|
||||||
baseDir: BaseDirectory.AppLocalData
|
executable: verInfo.executable
|
||||||
})
|
|
||||||
)
|
|
||||||
await remove('game/' + key + '/' + verInfo.executable, {
|
|
||||||
baseDir: BaseDirectory.AppLocalData,
|
|
||||||
recursive: true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,6 +686,9 @@ export default function RootLayout ({
|
|||||||
watchSettings<string>('linuxWineCommand', v =>
|
watchSettings<string>('linuxWineCommand', v =>
|
||||||
setLinuxWineCommand(v ?? 'wine %path%')
|
setLinuxWineCommand(v ?? 'wine %path%')
|
||||||
)
|
)
|
||||||
|
watchSettings<string>('customDataLocation', v =>
|
||||||
|
setCustomDataLocation(v ?? '')
|
||||||
|
)
|
||||||
|
|
||||||
watchVersions<Record<string, number>>('list', v => setVersionsList(v ?? {}))
|
watchVersions<Record<string, number>>('list', v => setVersionsList(v ?? {}))
|
||||||
watchVersions<Record<string, Record<string, number>>>('mods', v =>
|
watchVersions<Record<string, Record<string, number>>>('mods', v =>
|
||||||
@@ -756,8 +757,11 @@ export default function RootLayout ({
|
|||||||
linuxUseWine,
|
linuxUseWine,
|
||||||
linuxWineCommand,
|
linuxWineCommand,
|
||||||
theme,
|
theme,
|
||||||
|
customDataLocation,
|
||||||
versionsList,
|
versionsList,
|
||||||
modsList
|
modsList,
|
||||||
|
movingData,
|
||||||
|
setMovingData
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -825,6 +829,11 @@ export default function RootLayout ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{movingData && (
|
||||||
|
<div className='fixed inset-0 z-999999 bg-(--col0) flex items-center justify-center'>
|
||||||
|
<p className='text-5xl text-center'>Moving data...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</GlobalProvider>
|
</GlobalProvider>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Setting } from '@/components/Setting'
|
|||||||
import { useGlobal } from '@/providers/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import { copyToClipboard } from '@/lib/clipboard'
|
import { copyToClipboard } from '@/lib/clipboard'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import Dropdown from '@/components/Dropdown'
|
import Dropdown from '@/components/Dropdown'
|
||||||
|
|
||||||
export default function Settings () {
|
export default function Settings () {
|
||||||
@@ -14,7 +16,10 @@ export default function Settings () {
|
|||||||
sidebarAlwaysShowGames,
|
sidebarAlwaysShowGames,
|
||||||
linuxUseWine,
|
linuxUseWine,
|
||||||
linuxWineCommand,
|
linuxWineCommand,
|
||||||
theme
|
theme,
|
||||||
|
customDataLocation,
|
||||||
|
downloadProgress,
|
||||||
|
setMovingData
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -58,6 +63,45 @@ export default function Settings () {
|
|||||||
className='input-field my-1'
|
className='input-field my-1'
|
||||||
hidden={!(platform() == 'linux' && linuxUseWine)}
|
hidden={!(platform() == 'linux' && linuxUseWine)}
|
||||||
></input>
|
></input>
|
||||||
|
<Setting
|
||||||
|
label='Use custom data location'
|
||||||
|
value={!!customDataLocation}
|
||||||
|
disabled={downloadProgress.length > 0}
|
||||||
|
onChange={async () => {
|
||||||
|
if (customDataLocation) {
|
||||||
|
try {
|
||||||
|
setMovingData(true)
|
||||||
|
await invoke('move_game_data', { destination: '' })
|
||||||
|
await settings?.set('customDataLocation', '')
|
||||||
|
await settings?.save()
|
||||||
|
await invoke('restart_app')
|
||||||
|
} catch {
|
||||||
|
setMovingData(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const selected = await open({
|
||||||
|
directory: true,
|
||||||
|
title: 'Select data location'
|
||||||
|
})
|
||||||
|
if (!selected || typeof selected !== 'string') return
|
||||||
|
try {
|
||||||
|
setMovingData(true)
|
||||||
|
await invoke('move_game_data', { destination: selected })
|
||||||
|
await settings?.set('customDataLocation', selected)
|
||||||
|
await settings?.save()
|
||||||
|
await invoke('restart_app')
|
||||||
|
} catch {
|
||||||
|
setMovingData(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title='Move game data to a custom folder location. Disabled during downloads.'
|
||||||
|
/>
|
||||||
|
{customDataLocation && (
|
||||||
|
<p className='text-sm opacity-50 ml-6 -mt-1 mb-2'>
|
||||||
|
{customDataLocation}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<div className='flex flex-row gap-2 items-center'>
|
<div className='flex flex-row gap-2 items-center'>
|
||||||
<p
|
<p
|
||||||
title='The theme you want the launcher to use.'
|
title='The theme you want the launcher to use.'
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ export function Setting ({
|
|||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
title
|
title,
|
||||||
|
disabled
|
||||||
}: SettingProps) {
|
}: SettingProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-2 mb-2 ${className}`} title={title}>
|
<div className={`flex items-center gap-2 mb-2 ${disabled ? 'opacity-50 pointer-events-none' : ''} ${className}`} title={title}>
|
||||||
<label className='text-white text-lg'>{label}</label>
|
<label className='text-white text-lg'>{label}</label>
|
||||||
<div className='setting-checkbox-wrapper'>
|
<div className='setting-checkbox-wrapper'>
|
||||||
<input
|
<input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
className='setting-checkbox'
|
className='setting-checkbox'
|
||||||
checked={value}
|
checked={value}
|
||||||
|
disabled={disabled}
|
||||||
onChange={() => onChange(!value)}
|
onChange={() => onChange(!value)}
|
||||||
/>
|
/>
|
||||||
{value && <FontAwesomeIcon icon={faCheck} className='fa-check-icon' />}
|
{value && <FontAwesomeIcon icon={faCheck} className='fa-check-icon' />}
|
||||||
|
|||||||
+4
-28
@@ -1,39 +1,15 @@
|
|||||||
import { message } from '@tauri-apps/plugin-dialog'
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
import { BaseDirectory, exists, stat } from '@tauri-apps/plugin-fs'
|
|
||||||
import { openPath } from '@tauri-apps/plugin-opener'
|
|
||||||
import { join, appLocalDataDir } from '@tauri-apps/api/path'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
export const openFolder = async (name: string) => {
|
export const openFolder = async (name: string) => {
|
||||||
const relativePath = await join('game', name)
|
try {
|
||||||
const basePath = await appLocalDataDir()
|
await invoke('open_game_folder', { version: name })
|
||||||
const absolutePath = await join(basePath, relativePath)
|
} catch (e) {
|
||||||
|
await message(String(e), {
|
||||||
const folderExists = await exists(relativePath, {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!folderExists) {
|
|
||||||
await message(`Game folder "${absolutePath}" not found.`, {
|
|
||||||
title: 'Folder not found',
|
title: 'Folder not found',
|
||||||
kind: 'error'
|
kind: 'error'
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderStat = await stat(relativePath, {
|
|
||||||
baseDir: BaseDirectory.AppLocalData
|
|
||||||
})
|
|
||||||
|
|
||||||
if (folderStat.isFile) {
|
|
||||||
await message(`Game folder "${absolutePath}" not found.`, {
|
|
||||||
title: 'Folder not found',
|
|
||||||
kind: 'error'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await openPath(absolutePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatEtaSmart = (seconds: number) => {
|
export const formatEtaSmart = (seconds: number) => {
|
||||||
|
|||||||
@@ -57,8 +57,11 @@ type GlobalCtxType = {
|
|||||||
linuxUseWine: boolean
|
linuxUseWine: boolean
|
||||||
linuxWineCommand: string
|
linuxWineCommand: string
|
||||||
theme: string
|
theme: string
|
||||||
|
customDataLocation: string
|
||||||
versionsList: Record<string, number>
|
versionsList: Record<string, number>
|
||||||
modsList: Record<string, Record<string, number>>
|
modsList: Record<string, Record<string, number>>
|
||||||
|
movingData: boolean
|
||||||
|
setMovingData: Dispatch<SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
const GlobalCtx = createContext<GlobalCtxType | null>(null)
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export type SettingProps = {
|
|||||||
onChange: (val: boolean) => void
|
onChange: (val: boolean) => void
|
||||||
className?: string
|
className?: string
|
||||||
title?: string
|
title?: string
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user