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/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/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",
+);
+const PLATFORM_SKELETON_COUNT = 12;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/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 @@
-
-
-
-
-
-
diff --git a/frontend/src/layouts/Main.vue b/frontend/src/layouts/Main.vue
index 3bdab6437..b3840fd7b 100644
--- a/frontend/src/layouts/Main.vue
+++ b/frontend/src/layouts/Main.vue
@@ -18,8 +18,6 @@ import SelectStateDialog from "@/components/common/Game/Dialog/Asset/SelectState
import DeleteSavesDialog from "@/components/common/Game/Dialog/Asset/DeleteSaves.vue";
import DeleteStatesDialog from "@/components/common/Game/Dialog/Asset/DeleteStates.vue";
import NoteDialog from "@/components/common/Game/Dialog/NoteDialog.vue";
-import collectionApi from "@/services/api/collection";
-import platformApi from "@/services/api/platform";
import storeCollections from "@/stores/collections";
import storeNavigation from "@/stores/navigation";
import storePlatforms from "@/stores/platforms";
@@ -33,8 +31,7 @@ const platformsStore = storePlatforms();
const collectionsStore = storeCollections();
const emitter = inject>("emitter");
emitter?.on("refreshDrawer", async () => {
- const { data: platformData } = await platformApi.getPlatforms();
- platformsStore.set(platformData);
+ platformsStore.fetchPlatforms();
});
const showVirtualCollections = isNull(
@@ -53,47 +50,20 @@ const virtualCollectionTypeRef = ref(
);
onBeforeMount(async () => {
- await platformApi
- .getPlatforms()
- .then(({ data: platforms }) => {
- platformsStore.set(platforms);
- })
- .catch((error) => {
- console.error(error);
- });
-
- await collectionApi
- .getCollections()
- .then(({ data: collections }) => {
- collectionsStore.setCollections(collections);
- collectionsStore.setFavoriteCollection(
- collections.find(
- (collection) => collection.name.toLowerCase() === "favourites",
- ),
- );
- })
- .catch((error) => {
- console.error(error);
- });
-
- await collectionApi
- .getSmartCollections()
- .then(({ data: smartCollections }) => {
- collectionsStore.setSmartCollection(smartCollections);
- })
- .catch((error) => {
- console.error(error);
- });
-
- if (showVirtualCollections) {
- await collectionApi
- .getVirtualCollections({ type: virtualCollectionTypeRef.value })
- .then(({ data: virtualCollections }) => {
- collectionsStore.setVirtualCollections(virtualCollections);
- });
- }
+ await Promise.all([
+ platformsStore.fetchPlatforms(),
+ collectionsStore.fetchCollections(),
+ collectionsStore.fetchSmartCollections(),
+ showVirtualCollections
+ ? collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value)
+ : Promise.resolve(),
+ ]);
navigationStore.reset();
+
+ // Hack to prevent main page transition on first load
+ const main = document.getElementById("main");
+ if (main) main.classList.remove("no-transition");
});
diff --git a/frontend/src/stores/collections.ts b/frontend/src/stores/collections.ts
index 6bebd9de3..68eb73ce1 100644
--- a/frontend/src/stores/collections.ts
+++ b/frontend/src/stores/collections.ts
@@ -6,6 +6,7 @@ import type {
import { uniqBy } from "lodash";
import { defineStore } from "pinia";
import type { SimpleRom } from "./roms";
+import collectionApi from "@/services/api/collection";
export type Collection = CollectionSchema;
export type VirtualCollection = VirtualCollectionSchema;
@@ -19,6 +20,9 @@ export default defineStore("collections", {
smartCollections: [] as SmartCollection[],
favoriteCollection: {} as Collection | undefined,
filterText: "" as string,
+ fetchingCollections: false as boolean,
+ fetchingSmartCollections: false as boolean,
+ fetchingVirtualCollections: false as boolean,
}),
getters: {
filteredCollections: ({ allCollections, filterText }) =>
@@ -54,6 +58,66 @@ export default defineStore("collections", {
},
);
},
+ fetchCollections(): Promise {
+ if (this.fetchingCollections) return Promise.resolve([]);
+ this.fetchingCollections = true;
+
+ 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(): Promise {
+ if (this.fetchingSmartCollections) return Promise.resolve([]);
+ this.fetchingSmartCollections = true;
+
+ 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): Promise {
+ if (this.fetchingVirtualCollections) return Promise.resolve([]);
+ this.fetchingVirtualCollections = true;
+
+ 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 257c4b5bc..b9896b35f 100644
--- a/frontend/src/stores/platforms.ts
+++ b/frontend/src/stores/platforms.ts
@@ -1,4 +1,5 @@
import type { PlatformSchema } from "@/__generated__";
+import platformApi from "@/services/api/platform";
import { uniqBy } from "lodash";
import { defineStore } from "pinia";
@@ -8,6 +9,7 @@ export default defineStore("platforms", {
state: () => ({
allPlatforms: [] as Platform[],
filterText: "" as string,
+ fetchingPlatforms: false as boolean,
}),
getters: {
@@ -26,12 +28,33 @@ export default defineStore("platforms", {
)
.sort((a, b) => a.display_name.localeCompare(b.display_name)),
},
+
actions: {
_reorder() {
this.allPlatforms = uniqBy(this.allPlatforms, "id").sort((a, b) => {
return a.name.localeCompare(b.name);
});
},
+ fetchPlatforms(): Promise {
+ if (this.fetchingPlatforms) return Promise.resolve([]);
+ this.fetchingPlatforms = true;
+
+ 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) => {
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index b95b1c075..ad7abbabe 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,15 +17,17 @@ 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 { filledPlatforms, fetchingPlatforms } = storeToRefs(platformsStore);
const collectionsStore = storeCollections();
const {
filteredCollections,
filteredVirtualCollections,
filteredSmartCollections,
+ fetchingCollections,
+ fetchingSmartCollections,
+ fetchingVirtualCollections,
} = storeToRefs(collectionsStore);
function getSettingValue(key: string, defaultValue: boolean = true): boolean {
@@ -45,28 +48,20 @@ const fetchingContinuePlaying = ref(false);
const isEmpty = computed(
() =>
+ !fetchingPlatforms.value &&
+ !fetchingCollections.value &&
+ !fetchingSmartCollections.value &&
+ !fetchingVirtualCollections.value &&
+ !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 +97,83 @@ onMounted(async () => {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-