diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d27e047f..d1b9c492c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,29 @@ Thank you for considering contributing to RomM! This document outlines some guid Please note that this project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating in this project, you are expected to uphold this code. +## AI Assistance Notice + +> [!IMPORTANT] +> +> If you are using **any kind of AI assistance** to contribute to RomM, it must be disclosed in the pull request. + +If you are using any kind of AI assistance while contributing to RomM **this must be disclosed in the pull request**, along with the extent to which AI assistance was used (e.g. docs only vs. code generation). If PR responses are being generated by an AI, disclose that as well. As a small exception, trivial tab-completion doesn't need to be disclosed. + +An example disclosure: + +> This PR was written primarily by Claude Code. + +Or a more detailed disclosure: + +> I consulted ChatGPT to understand the codebase but the solution +> was fully authored manually by myself. + +Failure to disclose this is rude to the human operators on the other end of the pull request, but it also makes it difficult to determine how much scrutiny to apply to the contribution. + +In a perfect world, AI assistance would produce equal or higher quality work than any human. That isn't the world we live in today, and in most cases it's generating slop. + +Please be respectful to maintainers and disclose AI assistance. + ## Contributing to the Docs If you would like to contribute to the project's [documentation](https://docs.romm.app), open a pull request against [the docs repo](https://github.com/rommapp/docs). We welcome any contributions that help improve the documentation (new pages, updates, or corrections). diff --git a/backend/models/platform.py b/backend/models/platform.py index 3251b5015..7d9a19c9e 100644 --- a/backend/models/platform.py +++ b/backend/models/platform.py @@ -4,7 +4,7 @@ from functools import cached_property from typing import TYPE_CHECKING from models.base import BaseModel -from models.rom import Rom, RomFile +from models.rom import Rom from sqlalchemy import String, func, select from sqlalchemy.orm import Mapped, column_property, mapped_column, relationship @@ -59,10 +59,9 @@ class Platform(BaseModel): ) fs_size_bytes: Mapped[int] = column_property( - select(func.coalesce(func.sum(RomFile.file_size_bytes), 0)) - .select_from(RomFile.__table__.join(Rom.__table__, RomFile.rom_id == Rom.id)) + select(func.coalesce(func.sum(Rom.fs_size_bytes), 0)) .where(Rom.platform_id == id) - .correlate_except(RomFile, Rom) + .correlate_except(Rom) .scalar_subquery() ) diff --git a/frontend/index.html b/frontend/index.html index d8dad12e3..f3539d56d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -15,7 +15,54 @@ -
+
+ +
+ + + diff --git a/frontend/src/RomM.vue b/frontend/src/RomM.vue index c14bdd271..5e986a758 100644 --- a/frontend/src/RomM.vue +++ b/frontend/src/RomM.vue @@ -19,7 +19,18 @@ storeLanguage.setLanguage(selectedLanguage.value); @@ -28,4 +39,14 @@ storeLanguage.setLanguage(selectedLanguage.value); #main.no-transition { transition: none; } + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.35s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} diff --git a/frontend/src/components/Gallery/AppBar/common/CharIndexBar.vue b/frontend/src/components/Gallery/AppBar/common/CharIndexBar.vue index 33ae0b4eb..deb2e110e 100644 --- a/frontend/src/components/Gallery/AppBar/common/CharIndexBar.vue +++ b/frontend/src/components/Gallery/AppBar/common/CharIndexBar.vue @@ -90,7 +90,7 @@ watch( height="100%" class="position-fixed bg-surface mt-4 char-index-toolbar" :style="{ - 'max-height': calculatedHeight, + height: calculatedHeight, }" > smAndDown.value || showActionBarAlways); + const computedAspectRatio = computed(() => { const ratio = props.aspectRatio || @@ -31,12 +42,17 @@ const computedAspectRatio = computed(() => { diff --git a/frontend/src/components/common/NewVersionDialog.vue b/frontend/src/components/common/NewVersionDialog.vue index 09869ac9c..e6c7dc71f 100644 --- a/frontend/src/components/common/NewVersionDialog.vue +++ b/frontend/src/components/common/NewVersionDialog.vue @@ -2,7 +2,6 @@ import storeHeartbeat from "@/stores/heartbeat"; import semver from "semver"; import { onMounted, ref } from "vue"; -import { useDisplay } from "vuetify"; const heartbeat = storeHeartbeat(); const { VERSION } = heartbeat.value.SYSTEM; @@ -13,13 +12,15 @@ function dismissVersionBanner() { localStorage.setItem("dismissedVersion", GITHUB_VERSION.value); latestVersionDismissed.value = true; } -onMounted(async () => { + +async function fetchLatestVersion() { try { const response = await fetch( "https://api.github.com/repos/rommapp/romm/releases/latest", ); const json = await response.json(); GITHUB_VERSION.value = json.tag_name; + const publishedAt = new Date(json.published_at); latestVersionDismissed.value = // Hide if the version is not valid @@ -31,6 +32,12 @@ onMounted(async () => { } catch (error) { console.error("Failed to fetch latest version from Github", error); } + + document.removeEventListener("network-quiesced", fetchLatestVersion); +} + +onMounted(async () => { + document.addEventListener("network-quiesced", fetchLatestVersion); }); diff --git a/frontend/src/layouts/Main.vue b/frontend/src/layouts/Main.vue index b3840fd7b..0b35f8e0a 100644 --- a/frontend/src/layouts/Main.vue +++ b/frontend/src/layouts/Main.vue @@ -29,6 +29,7 @@ import { isNull } from "lodash"; const navigationStore = storeNavigation(); const platformsStore = storePlatforms(); const collectionsStore = storeCollections(); + const emitter = inject>("emitter"); emitter?.on("refreshDrawer", async () => { platformsStore.fetchPlatforms(); @@ -49,21 +50,25 @@ const virtualCollectionTypeRef = ref( : storedVirtualCollectionType, ); -onBeforeMount(async () => { - await Promise.all([ - platformsStore.fetchPlatforms(), - collectionsStore.fetchCollections(), - collectionsStore.fetchSmartCollections(), - showVirtualCollections - ? collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value) - : Promise.resolve(), - ]); - - navigationStore.reset(); +function unhackNavbar() { + document.removeEventListener("network-quiesced", unhackNavbar); // Hack to prevent main page transition on first load const main = document.getElementById("main"); if (main) main.classList.remove("no-transition"); +} + +onBeforeMount(async () => { + document.addEventListener("network-quiesced", unhackNavbar); + + platformsStore.fetchPlatforms(); + collectionsStore.fetchCollections(); + collectionsStore.fetchSmartCollections(); + if (showVirtualCollections) { + collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value); + } + + navigationStore.reset(); }); diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 0a0c96a91..a1b818a3d 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -21,18 +21,22 @@ async function initializeData() { try { const { data: heartbeatData } = await api.get("/heartbeat"); heartbeat.set(heartbeatData); + } catch (heartbeatError) { + console.error("Error fetching heartbeat: ", heartbeatError); + } - try { - const { data: userData } = await userApi.fetchCurrentUser(); - auth.setUser(userData); - } catch (userError) { - console.error("Error loading user: ", userError); - } + try { + const { data: userData } = await userApi.fetchCurrentUser(); + auth.setUser(userData); + } catch (userError) { + console.error("Error loading user: ", userError); + } + try { const { data: configData } = await api.get("/config"); configStore.set(configData); - } catch (error) { - console.error("Error during initialization: ", error); + } catch (configError) { + console.error("Error fetching config: ", configError); } } diff --git a/frontend/src/stores/roms.ts b/frontend/src/stores/roms.ts index 06c13cae5..5d8abf4d5 100644 --- a/frontend/src/stores/roms.ts +++ b/frontend/src/stores/roms.ts @@ -201,6 +201,32 @@ export default defineStore("roms", { }); }); }, + fetchRecentRoms(): Promise { + return new Promise((resolve, reject) => { + romApi + .getRecentRoms() + .then(({ data: { items } }) => { + this.setRecentRoms(items); + resolve(items); + }) + .catch((error) => { + reject(error); + }); + }); + }, + fetchContinuePlayingRoms(): Promise { + return new Promise((resolve, reject) => { + romApi + .getRecentPlayedRoms() + .then(({ data: { items } }) => { + this.setContinuePlayingRoms(items); + resolve(items); + }) + .catch((error) => { + reject(error); + }); + }); + }, add(roms: SimpleRom[]) { this.allRoms = this.allRoms.concat(roms); }, diff --git a/frontend/src/styles/common.css b/frontend/src/styles/common.css index 19f1f33fe..3ee059314 100644 --- a/frontend/src/styles/common.css +++ b/frontend/src/styles/common.css @@ -2,6 +2,7 @@ html, body { background-color: rgba(var(--v-theme-background)) !important; overflow-y: auto; + margin: 0 !important; } .main-layout { z-index: 1010 !important; diff --git a/frontend/src/views/Gallery/Platform.vue b/frontend/src/views/Gallery/Platform.vue index ce851f305..13e3d9e3e 100644 --- a/frontend/src/views/Gallery/Platform.vue +++ b/frontend/src/views/Gallery/Platform.vue @@ -299,12 +299,12 @@ onBeforeUnmount(() => { - + diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index ad7abbabe..385d1b475 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -1,5 +1,5 @@ diff --git a/frontend/vite.config.js b/frontend/vite.config.js index f987148ab..2287a6dff 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -16,6 +16,7 @@ export default defineConfig(({ mode }) => { ...loadEnv(mode, "../", envPrefixes), ...loadEnv(mode, "./", envPrefixes), }; + const backendPort = env.DEV_PORT ?? "5000"; const allowedHosts = env.VITE_ALLOWED_HOSTS == "true" ? true : false;