diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs
index 4071da6..30e8908 100644
--- a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs
+++ b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs
@@ -856,6 +856,11 @@ fn ui_operation_from_peer(operation: ActiveOperationKind) -> UiOperationKind {
}
}
+#[tauri::command]
+fn game_directory_exists(path: String) -> bool {
+ PathBuf::from(path).is_dir()
+}
+
#[tauri::command]
async fn update_game_directory(app_handle: tauri::AppHandle, path: String) -> tauri::Result<()> {
log::info!("update_game_directory: {path}");
@@ -1803,6 +1808,7 @@ pub fn run() {
install_game,
run_game,
start_server,
+ game_directory_exists,
update_game_directory,
update_game,
uninstall_game,
diff --git a/crates/lanspread-tauri-deno-ts/src/components/empty/NoDirectoryState.tsx b/crates/lanspread-tauri-deno-ts/src/components/empty/NoDirectoryState.tsx
index 0097825..0c15609 100644
--- a/crates/lanspread-tauri-deno-ts/src/components/empty/NoDirectoryState.tsx
+++ b/crates/lanspread-tauri-deno-ts/src/components/empty/NoDirectoryState.tsx
@@ -7,11 +7,7 @@ interface Props {
export const NoDirectoryState = ({ onChooseDirectory }: Props) => (
-
Pick a game directory
-
- SoftLAN scans the folder you point it at for installable game bundles
- and tracks what your peers on the LAN have available.
-
+
Please select a game folder
diff --git a/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts b/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts
index 80eba16..f65c8a2 100644
--- a/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts
+++ b/crates/lanspread-tauri-deno-ts/src/hooks/useGameDirectory.ts
@@ -11,6 +11,7 @@ import { GAME_DIR_KEY, SETTINGS_FILE, SETTINGS_FILE_OPTIONS } from '../lib/store
*/
export const useGameDirectory = () => {
const [gameDir, setGameDir] = useState('');
+ const [gameDirExists, setGameDirExists] = useState(false);
useEffect(() => {
let cancelled = false;
@@ -30,7 +31,11 @@ export const useGameDirectory = () => {
}, []);
useEffect(() => {
- if (!gameDir) return;
+ if (!gameDir.trim()) {
+ setGameDirExists(false);
+ return;
+ }
+ let cancelled = false;
const sync = async () => {
try {
const store = await load(SETTINGS_FILE, SETTINGS_FILE_OPTIONS);
@@ -38,19 +43,48 @@ export const useGameDirectory = () => {
} catch (err) {
console.error('Failed to persist game directory:', err);
}
+
+ let exists = false;
+ try {
+ exists = await invoke('game_directory_exists', { path: gameDir });
+ } catch (err) {
+ console.error('Failed to validate game directory:', err);
+ }
+ if (cancelled) return;
+ setGameDirExists(exists);
+ if (!exists) return;
+
+ invoke('update_game_directory', { path: gameDir }).catch(err =>
+ console.error('Failed to push game directory to backend:', err),
+ );
};
void sync();
- invoke('update_game_directory', { path: gameDir }).catch(err =>
- console.error('Failed to push game directory to backend:', err),
- );
+ return () => {
+ cancelled = true;
+ };
}, [gameDir]);
const rescan = useCallback(() => {
- if (!gameDir) return;
- invoke('update_game_directory', { path: gameDir }).catch(err =>
- console.error('Failed to rescan game directory:', err),
- );
+ if (!gameDir.trim()) {
+ setGameDirExists(false);
+ return;
+ }
+ const sync = async () => {
+ let exists = false;
+ try {
+ exists = await invoke('game_directory_exists', { path: gameDir });
+ } catch (err) {
+ console.error('Failed to validate game directory:', err);
+ }
+ setGameDirExists(exists);
+ if (!exists) return;
+
+ invoke('update_game_directory', { path: gameDir }).catch(err =>
+ console.error('Failed to rescan game directory:', err),
+ );
+ };
+ void sync();
}, [gameDir]);
- return { gameDir, setGameDir, rescan };
+ return { gameDir, gameDirExists, setGameDir, rescan };
};
diff --git a/crates/lanspread-tauri-deno-ts/src/styles/launcher.css b/crates/lanspread-tauri-deno-ts/src/styles/launcher.css
index 242c9ea..7a29a19 100644
--- a/crates/lanspread-tauri-deno-ts/src/styles/launcher.css
+++ b/crates/lanspread-tauri-deno-ts/src/styles/launcher.css
@@ -1718,6 +1718,9 @@
margin: 0 0 20px;
max-width: 44ch;
}
+.empty-state-title + .ghost-btn {
+ margin-top: 14px;
+}
.empty-state .ghost-btn {
background: var(--accent);
color: white;
diff --git a/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx b/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx
index 837705c..947a84a 100644
--- a/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx
+++ b/crates/lanspread-tauri-deno-ts/src/windows/MainWindow.tsx
@@ -45,7 +45,7 @@ const openLogsWindow = async () => {
export const MainWindow = () => {
const { settings, set: setSetting } = useSettings();
- const { gameDir, setGameDir, rescan } = useGameDirectory();
+ const { gameDir, gameDirExists, setGameDir, rescan } = useGameDirectory();
const games = useGames(rescan);
const actions = useGameActions(games, settings);
const thumbnails = useThumbnails();
@@ -54,13 +54,18 @@ export const MainWindow = () => {
const [removeGameId, setRemoveGameId] = useState(null);
const [settingsOpen, setSettingsOpen] = useState(false);
- const counts = useMemo(() => countByFilter(games.games), [games.games]);
+ const hasGameDirectory = !!gameDir.trim() && gameDirExists;
+ const visibleGames = useMemo(
+ () => hasGameDirectory ? games.games : [],
+ [games.games, hasGameDirectory],
+ );
+ const counts = useMemo(() => countByFilter(visibleGames), [visibleGames]);
// Query is local UI state (no need to persist).
const [query, setQuery] = useState('');
const filteredGames = useMemo(
- () => applyFilterAndSort(games.games, settings.filter, settings.sort, query),
- [games.games, settings.filter, settings.sort, query],
+ () => applyFilterAndSort(visibleGames, settings.filter, settings.sort, query),
+ [visibleGames, settings.filter, settings.sort, query],
);
const openGame = useMemo(
@@ -116,25 +121,26 @@ export const MainWindow = () => {
return (
- {gameDir ? (
- <>
- setSetting('filter', v)}
- counts={counts}
- query={query}
- setQuery={setQuery}
- sort={settings.sort}
- setSort={(v) => setSetting('sort', v)}
- gameDir={gameDir}
- onPickDirectory={() => void pickDirectory()}
- kebabItems={kebabItems}
- />
-
+ setSetting('filter', v)}
+ counts={counts}
+ query={query}
+ setQuery={setQuery}
+ sort={settings.sort}
+ setSort={(v) => setSetting('sort', v)}
+ gameDir={gameDir}
+ gameDirExists={gameDirExists}
+ onPickDirectory={() => void pickDirectory()}
+ kebabItems={kebabItems}
+ />
+
+ {hasGameDirectory ? (
+ <>
{filteredGames.length === 0 ? (
- games.games.length === 0 ? (
+ visibleGames.length === 0 ? (
{
onCancelDownload={(g) => actions.cancelDownload(g.id)}
/>
)}
-
- >
- ) : (
-
+ >
+ ) : (
void pickDirectory()} />
-
- )}
+ )}
+
{openGame && (