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), ) }}% -

+

+ {{ t("rom.show-earned-only") }} + + {{ 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 () => {