-
Notifications
You must be signed in to change notification settings - Fork 17
Add grid view for games in library & Edge WebView CSP fix for windows #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4b158fe
feat(library): add grid/list view toggle with settings persistence
AdenMGB 847fb01
fix(remote): modify CSP header to allow iframe embedding
AdenMGB cf3a795
refactor(library): extract GamePanel component with slight blur effect
AdenMGB ee0431b
fix(remote): remove CSP header rewrite
AdenMGB c52ec0e
refactor(library): extract library games logic into composable
AdenMGB ba810f4
feat(library): add setView function to library-view composable
AdenMGB ca660ee
refactor(library): simplify GamePanel props using Game type
AdenMGB e628a71
refactor(library): use library-games composable in components
AdenMGB 8312de1
refactor(settings): use setView from library-view composable
AdenMGB fa87450
fix(library): resolve merge conflict in LibrarySearch component
AdenMGB File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| <template> | ||
| <NuxtLink | ||
| :href="href" | ||
| :class="[ | ||
| 'group relative flex-1 min-w-42 max-w-48 h-64 rounded-lg overflow-hidden', | ||
| 'transition-all duration-300 text-left hover:scale-[1.02] hover:shadow-lg hover:-translate-y-0.5', | ||
| 'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-zinc-900', | ||
| isSelected | ||
| ? 'ring-2 ring-blue-500 shadow-lg shadow-blue-500/20' | ||
| : '', | ||
| ]" | ||
| > | ||
| <!-- Background Image --> | ||
| <div | ||
| :class="{ | ||
| 'transition-all duration-300 group-hover:scale-110': true, | ||
| }" | ||
| class="absolute inset-0" | ||
| > | ||
| <!-- Cover Image (if available) --> | ||
| <img | ||
| v-if="coverSrc" | ||
| :src="coverSrc" | ||
| :alt="game.mName" | ||
| class="w-full h-full object-cover brightness-[90%] blur-[1px]" | ||
| /> | ||
| <!-- Fallback to Icon --> | ||
| <div | ||
| v-else-if="iconSrc" | ||
| class="w-full h-full flex items-center justify-center bg-gradient-to-br from-zinc-800 to-zinc-900" | ||
| > | ||
| <img | ||
| class="w-2/3 h-2/3 object-contain brightness-[90%] blur-[1px]" | ||
| :src="iconSrc" | ||
| :alt="game.mName" | ||
| /> | ||
| </div> | ||
| <!-- Placeholder --> | ||
| <div | ||
| v-else | ||
| class="w-full h-full bg-gradient-to-br from-zinc-800 to-zinc-900" | ||
| /> | ||
| <!-- Gradient Overlay --> | ||
| <div | ||
| class="absolute inset-0 bg-gradient-to-t from-zinc-950/80 via-zinc-950/0 to-transparent" | ||
| /> | ||
| </div> | ||
|
|
||
| <!-- Content Overlay --> | ||
| <div class="absolute bottom-0 left-0 w-full p-3"> | ||
| <h1 | ||
| :class="{ | ||
| 'group-hover:text-white transition-colors': true, | ||
| }" | ||
| class="text-zinc-100 text-sm font-bold font-display mb-1" | ||
| > | ||
| {{ game.mName }} | ||
| </h1> | ||
| <p | ||
| v-if="game.mShortDescription" | ||
| :class="{ | ||
| 'group-hover:text-zinc-300 transition-colors': true, | ||
| }" | ||
| class="text-zinc-400 text-xs line-clamp-2 mb-1.5" | ||
| > | ||
| {{ game.mShortDescription }} | ||
| </p> | ||
| <!-- Status Badge --> | ||
| <div v-if="statusText" class="flex items-center mt-1"> | ||
| <span | ||
| :class="[ | ||
| 'inline-flex items-center px-2 py-0.5 rounded-md text-[10px] font-bold uppercase font-display', | ||
| statusClass, | ||
| ]" | ||
| > | ||
| {{ statusText }} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Selected Indicator --> | ||
| <div | ||
| v-if="isSelected" | ||
| class="absolute top-3 right-3 z-20 w-2.5 h-2.5 rounded-full bg-blue-500 shadow-lg shadow-blue-500/50 ring-2 ring-blue-500/30" | ||
| /> | ||
| </NuxtLink> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import type { Game, GameStatus } from "~/types"; | ||
|
|
||
| type GameWithStatus = { | ||
| game: Game; | ||
| status: GameStatus; | ||
| }; | ||
|
|
||
| defineProps<{ | ||
| game: GameWithStatus; | ||
| href: string; | ||
| coverSrc?: string; | ||
| iconSrc?: string; | ||
| statusText?: string; | ||
| statusClass?: string; | ||
| isSelected?: boolean; | ||
| }>(); | ||
| </script> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<GameStatus, GameStatus> }; | ||
| } = {}; | ||
| const icons: { [key: string]: string } = {}; | ||
| const covers: { [key: string]: string } = {}; | ||
| const collections: Ref<Collection[]> = 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<Game[]>("fetch_library", { | ||
| hardRefresh: forceRefresh, | ||
| }); | ||
| const otherCollections = await invoke<Collection[]>("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, | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| 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<Settings>("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 }, | ||
| }); | ||
| }; | ||
|
|
||
| const setView = async (view: "list" | "grid") => { | ||
| if (libraryView.value !== view) { | ||
| libraryView.value = view; | ||
| await invoke("update_settings", { | ||
| newSettings: { libraryView: view }, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| return { | ||
| libraryView, | ||
| toggleView, | ||
| setView, | ||
| }; | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.