From eee8cc120f5a3401ea14947ee4e582e8137602af Mon Sep 17 00:00:00 2001
From: Georges-Antoine Assi
Date: Fri, 22 Aug 2025 09:56:38 -0400
Subject: [PATCH 1/7] Replace v-progress loaders with skeletons
---
.../components/Details/BackgroundHeader.vue | 32 +++++++++----------
.../common/Game/Dialog/MatchRom.vue | 31 ++++++++----------
.../components/common/Navigation/ScanBtn.vue | 6 ++--
.../src/components/common/SearchCover.vue | 23 +++++--------
frontend/src/components/common/ViewLoader.vue | 19 -----------
5 files changed, 40 insertions(+), 71 deletions(-)
delete mode 100644 frontend/src/components/common/ViewLoader.vue
diff --git a/frontend/src/components/Details/BackgroundHeader.vue b/frontend/src/components/Details/BackgroundHeader.vue
index f50164628..e6004792a 100644
--- a/frontend/src/components/Details/BackgroundHeader.vue
+++ b/frontend/src/components/Details/BackgroundHeader.vue
@@ -20,40 +20,40 @@ const unmatchedCoverImage = computed(() =>
-
+
+
diff --git a/frontend/src/components/common/Game/Dialog/MatchRom.vue b/frontend/src/components/common/Game/Dialog/MatchRom.vue
index b6a96859a..d30bddd43 100644
--- a/frontend/src/components/common/Game/Dialog/MatchRom.vue
+++ b/frontend/src/components/common/Game/Dialog/MatchRom.vue
@@ -2,12 +2,13 @@
import type { SearchRomSchema } from "@/__generated__";
import GameCard from "@/components/common/Game/Card/Base.vue";
import RDialog from "@/components/common/RDialog.vue";
-import romApi from "@/services/api/rom";
+import Skeleton from "@/components/common/Game/Card/Skeleton.vue";
import EmptyManualMatch from "@/components/common/EmptyStates/EmptyManualMatch.vue";
import storeGalleryView from "@/stores/galleryView";
import storeHeartbeat from "@/stores/heartbeat";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import storePlatforms from "@/stores/platforms";
+import romApi from "@/services/api/rom";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { computed, inject, onBeforeUnmount, ref } from "vue";
@@ -490,15 +491,10 @@ onBeforeUnmount(() => {
cover
>
-
-
-
+
@@ -580,19 +576,18 @@ onBeforeUnmount(() => {
- {{ t("rom.results-found") }}:{{ !searching ? matchedRoms.length : ""
- }}
+ {{ t("rom.results-found") }}:
+
+ {{ !searching ? matchedRoms.length : "" }}
+
+ />
+
diff --git a/frontend/src/components/common/Navigation/ScanBtn.vue b/frontend/src/components/common/Navigation/ScanBtn.vue
index 68792a70f..d6b82d43d 100644
--- a/frontend/src/components/common/Navigation/ScanBtn.vue
+++ b/frontend/src/components/common/Navigation/ScanBtn.vue
@@ -132,9 +132,9 @@ onBeforeUnmount(() => {
:size="20"
indeterminate
/>
- mdi-magnify-scan
+
+ mdi-magnify-scan
+
([]);
const filteredCovers = ref();
-const galleryViewStore = storeGalleryView();
const panels = ref([0]);
+
const emitter = inject>("emitter");
-const coverAspectRatio = ref(
- parseFloat(galleryViewStore.defaultAspectRatioCover.toString()),
-);
emitter?.on("showSearchCoverDialog", ({ term, aspectRatio = null }) => {
searchText.value = term;
show.value = true;
@@ -30,6 +28,10 @@ emitter?.on("showSearchCoverDialog", ({ term, aspectRatio = null }) => {
if (searchText.value) searchCovers();
});
+const coverAspectRatio = ref(
+ parseFloat(galleryViewStore.defaultAspectRatioCover.toString()),
+);
+
async function searchCovers() {
covers.value = [];
@@ -199,16 +201,7 @@ onBeforeUnmount(() => {
>
-
-
-
+
diff --git a/frontend/src/components/common/ViewLoader.vue b/frontend/src/components/common/ViewLoader.vue
deleted file mode 100644
index 2af6bbfdd..000000000
--- a/frontend/src/components/common/ViewLoader.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
From 5a4da397a953636825394aa46f9906cbb0975d3a Mon Sep 17 00:00:00 2001
From: Georges-Antoine Assi
Date: Fri, 22 Aug 2025 10:28:23 -0400
Subject: [PATCH 2/7] add skeleton loaders to homepage
---
.../components/Details/RetroAchievements.vue | 19 ++--
.../src/components/Home/PlatformsSkeleton.vue | 51 ++++++++++
...etonLoader.vue => RecentAddedSkeleton.vue} | 0
frontend/src/views/Home.vue | 96 ++++++++++---------
4 files changed, 112 insertions(+), 54 deletions(-)
create mode 100644 frontend/src/components/Home/PlatformsSkeleton.vue
rename frontend/src/components/Home/{RecentSkeletonLoader.vue => RecentAddedSkeleton.vue} (100%)
diff --git a/frontend/src/components/Details/RetroAchievements.vue b/frontend/src/components/Details/RetroAchievements.vue
index 81b212f6f..be94d12b4 100644
--- a/frontend/src/components/Details/RetroAchievements.vue
+++ b/frontend/src/components/Details/RetroAchievements.vue
@@ -117,27 +117,30 @@ onMounted(() => {
buffer-opacity="0.6"
:buffer-value="achievementsPercentage"
height="32"
- >
+ >
+
{{
Math.max(
Math.ceil(achievementsPercentage),
Math.ceil(achievementsPercentageHardcore),
)
}}%
-
+
+
{{
- showEarned ? "mdi-checkbox-outline" : "mdi-checkbox-blank-outline"
- }}{{ t("rom.show-earned-only") }}
+
+
+ {{ showEarned ? "mdi-checkbox-outline" : "mdi-checkbox-blank-outline" }}
+
+
+ {{ t("rom.show-earned-only") }}
+
+import RSection from "@/components/common/RSection.vue";
+import Skeleton from "@/components/common/Game/Card/Skeleton.vue";
+import { views } from "@/utils";
+import { ref } from "vue";
+import { isNull } from "lodash";
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
+const storedPlatforms = localStorage.getItem("settings.gridPlatforms");
+const gridPlatforms = ref(
+ isNull(storedPlatforms) ? false : storedPlatforms === "true",
+);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Home/RecentSkeletonLoader.vue b/frontend/src/components/Home/RecentAddedSkeleton.vue
similarity index 100%
rename from frontend/src/components/Home/RecentSkeletonLoader.vue
rename to frontend/src/components/Home/RecentAddedSkeleton.vue
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index b95b1c075..60cbeb0e3 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home.vue
@@ -5,7 +5,8 @@ import { useI18n } from "vue-i18n";
import Stats from "@/components/Home/Stats.vue";
import Collections from "@/components/Home/Collections.vue";
import Platforms from "@/components/Home/Platforms.vue";
-import RecentSkeletonLoader from "@/components/Home/RecentSkeletonLoader.vue";
+import PlatformsSkeleton from "@/components/Home/PlatformsSkeleton.vue";
+import RecentAddedSkeleton from "@/components/Home/RecentAddedSkeleton.vue";
import RecentAdded from "@/components/Home/RecentAdded.vue";
import ContinuePlaying from "@/components/Home/ContinuePlaying.vue";
import EmptyHome from "@/components/Home/EmptyHome.vue";
@@ -16,8 +17,7 @@ import storeRoms from "@/stores/roms";
const { t } = useI18n();
const romsStore = storeRoms();
-const { recentRoms, continuePlayingRoms: recentPlayedRoms } =
- storeToRefs(romsStore);
+const { recentRoms, continuePlayingRoms } = storeToRefs(romsStore);
const platformsStore = storePlatforms();
const { filledPlatforms } = storeToRefs(platformsStore);
const collectionsStore = storeCollections();
@@ -45,28 +45,16 @@ const fetchingContinuePlaying = ref(false);
const isEmpty = computed(
() =>
+ !fetchingRecentAdded.value &&
+ !fetchingContinuePlaying.value &&
recentRoms.value.length === 0 &&
- recentPlayedRoms.value.length === 0 &&
+ continuePlayingRoms.value.length === 0 &&
filledPlatforms.value.length === 0 &&
filteredCollections.value.length === 0 &&
filteredVirtualCollections.value.length === 0 &&
filteredSmartCollections.value.length === 0,
);
-const showRecentSkeleton = computed(
- () =>
- showRecentRoms &&
- fetchingRecentAdded.value &&
- recentRoms.value.length === 0,
-);
-
-const showContinuePlayingSkeleton = computed(
- () =>
- showContinuePlaying &&
- fetchingContinuePlaying.value &&
- recentPlayedRoms.value.length === 0,
-);
-
const fetchRecentRoms = async (): Promise => {
try {
fetchingRecentAdded.value = true;
@@ -102,63 +90,79 @@ onMounted(async () => {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
From d58d250eae1bcae3850b1d50a905dd0e2b6abf58 Mon Sep 17 00:00:00 2001
From: Georges-Antoine Assi
Date: Fri, 22 Aug 2025 10:39:14 -0400
Subject: [PATCH 3/7] fetch through stores
---
.../Settings/UserInterface/Interface.vue | 10 +---
.../Collection/Dialog/DeleteCollection.vue | 2 +-
frontend/src/layouts/Main.vue | 46 ++---------------
frontend/src/stores/collections.ts | 50 +++++++++++++++++++
frontend/src/stores/platforms.ts | 19 +++++++
frontend/src/views/Home.vue | 21 ++++++--
6 files changed, 93 insertions(+), 55 deletions(-)
diff --git a/frontend/src/components/Settings/UserInterface/Interface.vue b/frontend/src/components/Settings/UserInterface/Interface.vue
index 63e9b46b7..fd060dd80 100644
--- a/frontend/src/components/Settings/UserInterface/Interface.vue
+++ b/frontend/src/components/Settings/UserInterface/Interface.vue
@@ -1,7 +1,6 @@
-
+
+
+
From baffdfdf76303dfc6322f7570917924bfe85e627 Mon Sep 17 00:00:00 2001
From: Georges-Antoine Assi
Date: Fri, 22 Aug 2025 11:13:16 -0400
Subject: [PATCH 5/7] correctly promise fetches
---
frontend/src/RomM.vue | 17 +---
.../src/components/Home/PlatformsSkeleton.vue | 4 +-
frontend/src/stores/collections.ts | 92 +++++++++++--------
frontend/src/stores/platforms.ts | 30 +++---
frontend/src/stores/roms.ts | 4 +-
5 files changed, 77 insertions(+), 70 deletions(-)
diff --git a/frontend/src/RomM.vue b/frontend/src/RomM.vue
index d0ebe9a85..6b7037f98 100644
--- a/frontend/src/RomM.vue
+++ b/frontend/src/RomM.vue
@@ -1,7 +1,7 @@
-
+
-
-
diff --git a/frontend/src/components/Home/PlatformsSkeleton.vue b/frontend/src/components/Home/PlatformsSkeleton.vue
index add81cd9c..72d683dc8 100644
--- a/frontend/src/components/Home/PlatformsSkeleton.vue
+++ b/frontend/src/components/Home/PlatformsSkeleton.vue
@@ -11,7 +11,9 @@ const storedPlatforms = localStorage.getItem("settings.gridPlatforms");
const gridPlatforms = ref(
isNull(storedPlatforms) ? false : storedPlatforms === "true",
);
+const PLATFORM_SKELETON_COUNT = 12;
+
@@ -24,7 +26,7 @@ const gridPlatforms = ref(
no-gutters
>
{
+ if (this.fetchingCollections) return Promise.resolve([]);
this.fetchingCollections = true;
- collectionApi
- .getCollections()
- .then(({ data: collections }) => {
- this.allCollections = collections;
- })
- .catch((error) => {
- console.error(error);
- })
- .finally(() => {
- this.fetchingCollections = false;
- });
+
+ return new Promise((resolve, reject) => {
+ collectionApi
+ .getCollections()
+ .then(({ data: collections }) => {
+ this.allCollections = collections;
+ resolve(collections);
+ })
+ .catch((error) => {
+ console.error(error);
+ reject(error);
+ })
+ .finally(() => {
+ this.fetchingCollections = false;
+ });
+ });
},
- fetchSmartCollections() {
- if (this.fetchingSmartCollections) return;
+ fetchSmartCollections(): Promise {
+ if (this.fetchingSmartCollections) return Promise.resolve([]);
this.fetchingSmartCollections = true;
- collectionApi
- .getSmartCollections()
- .then(({ data: smartCollections }) => {
- this.smartCollections = smartCollections;
- })
- .catch((error) => {
- console.error(error);
- })
- .finally(() => {
- this.fetchingSmartCollections = false;
- });
+ return new Promise((resolve, reject) => {
+ collectionApi
+ .getSmartCollections()
+ .then(({ data: smartCollections }) => {
+ this.smartCollections = smartCollections;
+ resolve(smartCollections);
+ })
+ .catch((error) => {
+ console.error(error);
+ reject(error);
+ })
+ .finally(() => {
+ this.fetchingSmartCollections = false;
+ });
+ });
},
- fetchVirtualCollections(type: string) {
- if (this.fetchingVirtualCollections) return;
+ fetchVirtualCollections(type: string): Promise {
+ if (this.fetchingVirtualCollections) return Promise.resolve([]);
this.fetchingVirtualCollections = true;
- collectionApi
- .getVirtualCollections({ type })
- .then(({ data: virtualCollections }) => {
- this.virtualCollections = virtualCollections;
- })
- .catch((error) => {
- console.error(error);
- })
- .finally(() => {
- this.fetchingVirtualCollections = false;
- });
+
+ return new Promise((resolve, reject) => {
+ collectionApi
+ .getVirtualCollections({ type })
+ .then(({ data: virtualCollections }) => {
+ this.virtualCollections = virtualCollections;
+ resolve(virtualCollections);
+ })
+ .catch((error) => {
+ console.error(error);
+ reject(error);
+ })
+ .finally(() => {
+ this.fetchingVirtualCollections = false;
+ });
+ });
},
setFavoriteCollection(favoriteCollection: Collection | undefined) {
this.favoriteCollection = favoriteCollection;
diff --git a/frontend/src/stores/platforms.ts b/frontend/src/stores/platforms.ts
index 05fbf91db..b9896b35f 100644
--- a/frontend/src/stores/platforms.ts
+++ b/frontend/src/stores/platforms.ts
@@ -35,21 +35,25 @@ export default defineStore("platforms", {
return a.name.localeCompare(b.name);
});
},
- fetchPlatforms() {
- if (this.fetchingPlatforms) return;
+ fetchPlatforms(): Promise {
+ if (this.fetchingPlatforms) return Promise.resolve([]);
this.fetchingPlatforms = true;
- platformApi
- .getPlatforms()
- .then(({ data: platforms }) => {
- this.allPlatforms = platforms;
- })
- .catch((error) => {
- console.error(error);
- })
- .finally(() => {
- this.fetchingPlatforms = false;
- });
+ return new Promise((resolve, reject) => {
+ platformApi
+ .getPlatforms()
+ .then(({ data: platforms }) => {
+ this.allPlatforms = platforms;
+ resolve(platforms);
+ })
+ .catch((error) => {
+ console.error(error);
+ reject(error);
+ })
+ .finally(() => {
+ this.fetchingPlatforms = false;
+ });
+ });
},
set(platforms: Platform[]) {
this.allPlatforms = platforms;
diff --git a/frontend/src/stores/roms.ts b/frontend/src/stores/roms.ts
index 661e823a5..06c13cae5 100644
--- a/frontend/src/stores/roms.ts
+++ b/frontend/src/stores/roms.ts
@@ -129,8 +129,8 @@ export default defineStore("roms", {
}: {
galleryFilter: GalleryFilterStore;
concat?: boolean;
- }) {
- if (this.fetchingRoms) return Promise.resolve();
+ }): Promise {
+ if (this.fetchingRoms) return Promise.resolve([]);
this.fetchingRoms = true;
return new Promise((resolve, reject) => {
From fa58b34635cc2d84a2fcad6033bd979a3a7e72c6 Mon Sep 17 00:00:00 2001
From: Georges-Antoine Assi
Date: Fri, 22 Aug 2025 11:25:27 -0400
Subject: [PATCH 6/7] move hack to homepage
---
frontend/src/RomM.vue | 8 +++++++-
frontend/src/layouts/Main.vue | 17 +++++++++++------
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/frontend/src/RomM.vue b/frontend/src/RomM.vue
index 6b7037f98..c14bdd271 100644
--- a/frontend/src/RomM.vue
+++ b/frontend/src/RomM.vue
@@ -18,8 +18,14 @@ storeLanguage.setLanguage(selectedLanguage.value);
-
+
+
+
diff --git a/frontend/src/layouts/Main.vue b/frontend/src/layouts/Main.vue
index ddf3068e5..ef7985835 100644
--- a/frontend/src/layouts/Main.vue
+++ b/frontend/src/layouts/Main.vue
@@ -50,14 +50,19 @@ const virtualCollectionTypeRef = ref(
);
onBeforeMount(async () => {
- platformsStore.fetchPlatforms();
- collectionsStore.fetchCollections();
- collectionsStore.fetchSmartCollections();
- if (showVirtualCollections) {
- collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value);
- }
+ await Promise.all([
+ platformsStore.fetchPlatforms(),
+ collectionsStore.fetchCollections(),
+ collectionsStore.fetchSmartCollections(),
+ showVirtualCollections &&
+ collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value),
+ ]);
navigationStore.reset();
+
+ // Hack to prevent main page transition on first load
+ const main = document.getElementById("main");
+ if (main) main.classList.remove("no-transition");
});
From 38682c49ba3e29f55b763bcf374ed0b8ca6d600e Mon Sep 17 00:00:00 2001
From: Georges-Antoine Assi
Date: Fri, 22 Aug 2025 11:53:15 -0400
Subject: [PATCH 7/7] changes from review
---
frontend/src/layouts/Main.vue | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frontend/src/layouts/Main.vue b/frontend/src/layouts/Main.vue
index ef7985835..b3840fd7b 100644
--- a/frontend/src/layouts/Main.vue
+++ b/frontend/src/layouts/Main.vue
@@ -54,8 +54,9 @@ onBeforeMount(async () => {
platformsStore.fetchPlatforms(),
collectionsStore.fetchCollections(),
collectionsStore.fetchSmartCollections(),
- showVirtualCollections &&
- collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value),
+ showVirtualCollections
+ ? collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value)
+ : Promise.resolve(),
]);
navigationStore.reset();