From 4b158fedb685efafd7a9fd09ac4af2de5e19b4a9 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Mon, 5 Jan 2026 14:04:33 +1030 Subject: [PATCH 01/10] feat(library): add grid/list view toggle with settings persistence --- main/components/LibrarySearch.vue | 15 +- main/composables/library-view.ts | 29 ++++ main/pages/library/index.vue | 279 ++++++++++++++++++++++++++++-- main/pages/settings/interface.vue | 67 +++++-- main/types.ts | 1 + src-tauri/database/src/models.rs | 4 +- 6 files changed, 367 insertions(+), 28 deletions(-) create mode 100644 main/composables/library-view.ts diff --git a/main/components/LibrarySearch.vue b/main/components/LibrarySearch.vue index d4beffaf..e5ffdd9b 100644 --- a/main/components/LibrarySearch.vue +++ b/main/components/LibrarySearch.vue @@ -25,6 +25,14 @@ > + @@ -52,7 +60,7 @@ }; } = {}; diff --git a/main/composables/library-view.ts b/main/composables/library-view.ts new file mode 100644 index 00000000..59ed79cf --- /dev/null +++ b/main/composables/library-view.ts @@ -0,0 +1,29 @@ +import { invoke } from "@tauri-apps/api/core"; +import type { Settings } from "~/types"; + +export const useLibraryView = () => { + const libraryView = useState<"list" | "grid">("library-view", () => "list"); + + // Load initial value from settings if not already loaded + const state = libraryView.value; + if (state === "list") { + invoke("fetch_settings").then((settings) => { + if (libraryView.value === "list") { + libraryView.value = (settings?.libraryView as "list" | "grid") || "list"; + } + }); + } + + const toggleView = async () => { + const newView = libraryView.value === "list" ? "grid" : "list"; + libraryView.value = newView; + await invoke("update_settings", { + newSettings: { libraryView: newView }, + }); + }; + + return { + libraryView, + toggleView, + }; +}; diff --git a/main/pages/library/index.vue b/main/pages/library/index.vue index a23a7993..9ffd5aa3 100644 --- a/main/pages/library/index.vue +++ b/main/pages/library/index.vue @@ -1,19 +1,276 @@ \ No newline at end of file diff --git a/main/pages/settings/interface.vue b/main/pages/settings/interface.vue index 7df32e15..cd36b3a4 100644 --- a/main/pages/settings/interface.vue +++ b/main/pages/settings/interface.vue @@ -1,23 +1,60 @@ diff --git a/main/types.ts b/main/types.ts index f63a9c57..9e05c255 100644 --- a/main/types.ts +++ b/main/types.ts @@ -93,4 +93,5 @@ export type Settings = { autostart: boolean; maxDownloadThreads: number; forceOffline: boolean; + libraryView: "list" | "grid"; }; diff --git a/src-tauri/database/src/models.rs b/src-tauri/database/src/models.rs index d05a0143..bca77bb4 100644 --- a/src-tauri/database/src/models.rs +++ b/src-tauri/database/src/models.rs @@ -137,7 +137,8 @@ pub mod data { pub struct Settings { pub autostart: bool, pub max_download_threads: usize, - pub force_offline: bool, // ... other settings ... + pub force_offline: bool, + pub library_view: String, // "list" or "grid" } impl Default for Settings { fn default() -> Self { @@ -145,6 +146,7 @@ pub mod data { autostart: false, max_download_threads: 4, force_offline: false, + library_view: "list".to_string(), } } } From 847fb01f2cee83eefe20c7a0e9297c75e4e828bc Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Mon, 5 Jan 2026 14:07:49 +1030 Subject: [PATCH 02/10] fix(remote): modify CSP header to allow iframe embedding --- src-tauri/remote/src/server_proto.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src-tauri/remote/src/server_proto.rs b/src-tauri/remote/src/server_proto.rs index 47406d2a..8a93ee73 100644 --- a/src-tauri/remote/src/server_proto.rs +++ b/src-tauri/remote/src/server_proto.rs @@ -112,7 +112,29 @@ fn handle_server_proto(request: Request>) -> Result>, S { let client_response_headers = client_http_response.headers_mut().unwrap(); for (header, header_value) in response.headers() { - client_response_headers.insert(header, header_value.clone()); + // Remove or modify Content-Security-Policy to allow framing + if header.as_str().eq_ignore_ascii_case("content-security-policy") { + let csp_value = header_value.to_str().unwrap_or(""); + // Remove frame-ancestors directive or replace it to allow all + let modified_csp = csp_value + .split(';') + .map(|directive| directive.trim()) + .filter(|directive| !directive.starts_with("frame-ancestors")) + .collect::>() + .join("; "); + // Add frame-ancestors * to allow framing from any origin + let new_csp = if modified_csp.is_empty() { + "frame-ancestors *".to_string() + } else { + format!("{}; frame-ancestors *", modified_csp) + }; + client_response_headers.insert( + header.clone(), + HeaderValue::from_str(&new_csp).unwrap_or(header_value.clone()), + ); + } else { + client_response_headers.insert(header, header_value.clone()); + } } }; From cf3a795ea4ec648eadc337f05158aff5c07a8169 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Mon, 5 Jan 2026 14:16:45 +1030 Subject: [PATCH 03/10] refactor(library): extract GamePanel component with slight blur effect --- main/components/GamePanel.vue | 100 ++++++++++++++++++++++++++++++++++ main/pages/library/index.vue | 89 ++++-------------------------- 2 files changed, 110 insertions(+), 79 deletions(-) create mode 100644 main/components/GamePanel.vue diff --git a/main/components/GamePanel.vue b/main/components/GamePanel.vue new file mode 100644 index 00000000..94bee8d2 --- /dev/null +++ b/main/components/GamePanel.vue @@ -0,0 +1,100 @@ + + + diff --git a/main/pages/library/index.vue b/main/pages/library/index.vue index 9ffd5aa3..4b1a8ff8 100644 --- a/main/pages/library/index.vue +++ b/main/pages/library/index.vue @@ -29,88 +29,18 @@ class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-4" > @@ -253,6 +183,7 @@ const navigation = computed(() => prefix: `/library/${game.id}`, isInstalled, id: game.id, + game: game, // Include full game object for description access }; return item; }); From ee0431b5a0fa8a9c33ca3be5a64b846f690ffead Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Fri, 16 Jan 2026 08:03:59 +1030 Subject: [PATCH 04/10] fix(remote): remove CSP header rewrite --- src-tauri/remote/src/server_proto.rs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src-tauri/remote/src/server_proto.rs b/src-tauri/remote/src/server_proto.rs index 8a93ee73..47406d2a 100644 --- a/src-tauri/remote/src/server_proto.rs +++ b/src-tauri/remote/src/server_proto.rs @@ -112,29 +112,7 @@ fn handle_server_proto(request: Request>) -> Result>, S { let client_response_headers = client_http_response.headers_mut().unwrap(); for (header, header_value) in response.headers() { - // Remove or modify Content-Security-Policy to allow framing - if header.as_str().eq_ignore_ascii_case("content-security-policy") { - let csp_value = header_value.to_str().unwrap_or(""); - // Remove frame-ancestors directive or replace it to allow all - let modified_csp = csp_value - .split(';') - .map(|directive| directive.trim()) - .filter(|directive| !directive.starts_with("frame-ancestors")) - .collect::>() - .join("; "); - // Add frame-ancestors * to allow framing from any origin - let new_csp = if modified_csp.is_empty() { - "frame-ancestors *".to_string() - } else { - format!("{}; frame-ancestors *", modified_csp) - }; - client_response_headers.insert( - header.clone(), - HeaderValue::from_str(&new_csp).unwrap_or(header_value.clone()), - ); - } else { - client_response_headers.insert(header, header_value.clone()); - } + client_response_headers.insert(header, header_value.clone()); } }; From c52ec0e32127a0100607cf6c1bd49919359d56f5 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Fri, 16 Jan 2026 08:04:06 +1030 Subject: [PATCH 05/10] refactor(library): extract library games logic into composable --- main/composables/library-games.ts | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 main/composables/library-games.ts diff --git a/main/composables/library-games.ts b/main/composables/library-games.ts new file mode 100644 index 00000000..ea4b2ecd --- /dev/null +++ b/main/composables/library-games.ts @@ -0,0 +1,71 @@ +import { invoke } from "@tauri-apps/api/core"; +import { + GameStatusEnum, + type Collection, + type Game, + type GameStatus, +} from "~/types"; + +export const useLibraryGames = () => { + const loading = ref(false); + const games: { + [key: string]: { game: Game; status: Ref }; + } = {}; + const icons: { [key: string]: string } = {}; + const covers: { [key: string]: string } = {}; + const collections: Ref = ref([]); + + async function calculateGames(clearAll = false, forceRefresh = false) { + if (clearAll) { + collections.value = []; + loading.value = true; + } + // If we update immediately, the navigation gets re-rendered before we + // add all the necessary state, and it freaks tf out + const newGames = await invoke("fetch_library", { + hardRefresh: forceRefresh, + }); + const otherCollections = await invoke("fetch_collections", { + hardRefresh: forceRefresh, + }); + const allGames = [ + ...newGames, + ...otherCollections + .map((e) => e.entries) + .flat() + .map((e) => e.game), + ].filter((v, i, a) => a.indexOf(v) === i); + + for (const game of allGames) { + if (games[game.id]) continue; + games[game.id] = await useGame(game.id); + } + for (const game of allGames) { + if (icons[game.id]) continue; + icons[game.id] = await useObject(game.mIconObjectId); + } + for (const game of allGames) { + if (covers[game.id]) continue; + covers[game.id] = await useObject(game.mCoverObjectId); + } + + const libraryCollection = { + id: "library", + name: "Library", + isDefault: true, + entries: newGames.map((e) => ({ gameId: e.id, game: e })), + } satisfies Collection; + + loading.value = false; + collections.value = [libraryCollection, ...otherCollections]; + } + + return { + loading: readonly(loading), + games, + icons, + covers, + collections: readonly(collections), + calculateGames, + }; +}; From ba810f4339c665cd3a53f1261d4278d981291b54 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Fri, 16 Jan 2026 08:04:13 +1030 Subject: [PATCH 06/10] feat(library): add setView function to library-view composable --- main/composables/library-view.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main/composables/library-view.ts b/main/composables/library-view.ts index 59ed79cf..c21cd22e 100644 --- a/main/composables/library-view.ts +++ b/main/composables/library-view.ts @@ -22,8 +22,18 @@ export const useLibraryView = () => { }); }; + const setView = async (view: "list" | "grid") => { + if (libraryView.value !== view) { + libraryView.value = view; + await invoke("update_settings", { + newSettings: { libraryView: view }, + }); + } + }; + return { libraryView, toggleView, + setView, }; }; From ca660ee8464a019f7428e07d58690cc0b921c8a8 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Fri, 16 Jan 2026 08:04:20 +1030 Subject: [PATCH 07/10] refactor(library): simplify GamePanel props using Game type --- main/components/GamePanel.vue | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/main/components/GamePanel.vue b/main/components/GamePanel.vue index 94bee8d2..a931f308 100644 --- a/main/components/GamePanel.vue +++ b/main/components/GamePanel.vue @@ -21,7 +21,7 @@ @@ -32,7 +32,7 @@ @@ -54,16 +54,16 @@ }" class="text-zinc-100 text-sm font-bold font-display mb-1" > - {{ gameName }} + {{ game.mName }}

- {{ description }} + {{ game.mShortDescription }}

@@ -87,10 +87,16 @@ From fa87450e5e76ea15b0389b88c96f6a02ba708640 Mon Sep 17 00:00:00 2001 From: Aden Linday Date: Fri, 16 Jan 2026 08:08:22 +1030 Subject: [PATCH 10/10] fix(library): resolve merge conflict in LibrarySearch component --- main/components/LibrarySearch.vue | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/main/components/LibrarySearch.vue b/main/components/LibrarySearch.vue index d67078ee..b19288ab 100644 --- a/main/components/LibrarySearch.vue +++ b/main/components/LibrarySearch.vue @@ -149,7 +149,7 @@ const gameStatusTextStyle: { [key in GameStatusEnum]: string } = { [GameStatusEnum.Installed]: "text-green-500", [GameStatusEnum.Downloading]: "text-zinc-400", [GameStatusEnum.Validating]: "text-blue-300", - [GameStatusEnum.Running]: "text-green-500", + [GameStatusEnum.Running]: "text-blue-500", [GameStatusEnum.Remote]: "text-zinc-700", [GameStatusEnum.Queued]: "text-zinc-400", [GameStatusEnum.Updating]: "text-zinc-400", @@ -244,15 +244,6 @@ const filteredNavigation = computed(() => { })) .filter((e) => e.items.length > 0); }); - -listen("update_library", async (event) => { - console.log("Updating library"); - let oldNavigation = currentNavigation.value; - await calculateGames(); - if (oldNavigation !== currentNavigation.value) { - router.push("/library"); - } -});