Merge pull request #2581 from rommapp/romm-2414

[ROMM-2414] Refactor data loading in console mode
This commit is contained in:
Georges-Antoine Assi
2025-10-21 12:12:38 -04:00
committed by GitHub
22 changed files with 435 additions and 322 deletions

View File

@@ -207,7 +207,6 @@ class RomSchema(BaseModel):
platform_id: int
platform_slug: str
platform_fs_slug: str
platform_name: str
platform_custom_name: str | None
platform_display_name: str

View File

@@ -379,7 +379,7 @@ async def _identify_platform(
await socket_manager.emit(
"scan:scanning_platform",
PlatformSchema.model_validate(platform).model_dump(
include={"id", "name", "slug", "fs_slug", "is_identified"}
include={"id", "name", "display_name", "slug", "fs_slug", "is_identified"}
),
)
await socket_manager.emit("", None)

View File

@@ -263,10 +263,6 @@ class Rom(BaseModel):
def platform_fs_slug(self) -> str:
return self.platform.fs_slug
@property
def platform_name(self) -> str:
return self.platform.name
@property
def platform_custom_name(self) -> str | None:
return self.platform.custom_name

View File

@@ -34,7 +34,6 @@ export type DetailedRomSchema = {
platform_id: number;
platform_slug: string;
platform_fs_slug: string;
platform_name: string;
platform_custom_name: (string | null);
platform_display_name: string;
fs_name: string;

View File

@@ -28,7 +28,6 @@ export type SimpleRomSchema = {
platform_id: number;
platform_slug: string;
platform_fs_slug: string;
platform_name: string;
platform_custom_name: (string | null);
platform_display_name: string;
fs_name: string;

View File

@@ -20,7 +20,7 @@ const releaseDate = new Date(
});
const platformsStore = storePlatforms();
const { filteredPlatforms } = storeToRefs(platformsStore);
const { allPlatforms } = storeToRefs(platformsStore);
const hashMatches = computed(() => {
return [
@@ -81,7 +81,7 @@ const hashMatches = computed(() => {
>
<MissingFromFSIcon
v-if="
filteredPlatforms.find((p) => p.id === rom.platform_id)
allPlatforms.find((p) => p.id === rom.platform_id)
?.missing_from_fs
"
class="mr-2"
@@ -90,7 +90,7 @@ const hashMatches = computed(() => {
<PlatformIcon
:key="rom.platform_slug"
:slug="rom.platform_slug"
:name="rom.platform_name"
:name="rom.platform_display_name"
:fs-slug="rom.platform_fs_slug"
:size="30"
class="mr-2"

View File

@@ -72,7 +72,7 @@ const {
filterLanguages,
} = storeToRefs(galleryFilterStore);
const { filteredRoms } = storeToRefs(romsStore);
const { filteredPlatforms } = storeToRefs(platformsStore);
const { allPlatforms } = storeToRefs(platformsStore);
const emitter = inject<Emitter<Events>>("emitter");
const onFilterChange = debounce(
@@ -305,7 +305,7 @@ onMounted(async () => {
);
watch(
() => filteredPlatforms.value,
() => allPlatforms.value,
async () => setFilters(),
{ immediate: true }, // Ensure watcher is triggered immediately
);

View File

@@ -36,10 +36,8 @@ const editable = ref(false);
/>
</template>
<p>
Versions of the same platform. A common example is Capcom Play System
1 is an arcade system. Platform versions will let you setup a custom
platform for RomM to import and tell RomM which platform it needs to
scrape against.
Platform versions allow you to create custom platform entries for
games that belong to the same system but have different versions.
</p>
</v-tooltip>
</template>

View File

@@ -12,22 +12,22 @@ const props = defineProps<{
}>();
const { t } = useI18n();
const platformsStore = storePlatforms();
const { filteredPlatforms } = storeToRefs(platformsStore);
const { allPlatforms } = storeToRefs(platformsStore);
const orderBy = ref<"name" | "size" | "count">("name");
const sortedPlatforms = computed(() => {
const platforms = [...filteredPlatforms.value];
if (orderBy.value === "size") {
return platforms.sort(
return allPlatforms.value.sort(
(a, b) => Number(b.fs_size_bytes) - Number(a.fs_size_bytes),
);
}
if (orderBy.value === "count") {
return platforms.sort((a, b) => b.rom_count - a.rom_count);
return allPlatforms.value.sort((a, b) => b.rom_count - a.rom_count);
}
// Default to name
return platforms.sort((a, b) =>
a.name.localeCompare(b.name, undefined, { sensitivity: "base" }),
return allPlatforms.value.sort((a, b) =>
a.display_name.localeCompare(b.display_name, undefined, {
sensitivity: "base",
}),
);
});

View File

@@ -311,7 +311,7 @@ onBeforeUnmount(() => {
:key="rom.platform_slug"
:size="25"
:slug="rom.platform_slug"
:name="rom.platform_name"
:name="rom.platform_display_name"
:fs-slug="rom.platform_fs_slug"
class="ml-1"
/>

View File

@@ -37,13 +37,13 @@ if (!socket.connected) socket.connect();
socket.on(
"scan:scanning_platform",
({
name,
display_name,
slug,
id,
fs_slug,
is_identified,
}: {
name: string;
display_name: string;
slug: string;
id: number;
fs_slug: string;
@@ -51,10 +51,10 @@ socket.on(
}) => {
scanningStore.set(true);
scanningPlatforms.value = scanningPlatforms.value.filter(
(platform) => platform.name !== name,
(platform) => platform.display_name !== display_name,
);
scanningPlatforms.value.push({
name,
display_name,
slug,
id,
fs_slug,
@@ -87,7 +87,7 @@ socket.on("scan:scanning_rom", (rom: SimpleRom) => {
// Add the platform if the socket dropped and it's missing
if (!scannedPlatform) {
scanningPlatforms.value.push({
name: rom.platform_name,
display_name: rom.platform_display_name,
slug: rom.platform_slug,
id: rom.platform_id,
fs_slug: rom.platform_fs_slug,

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useIdle } from "@vueuse/core";
import { onMounted, onUnmounted, provide } from "vue";
import { useIdle, useLocalStorage } from "@vueuse/core";
import { onBeforeMount, onMounted, onUnmounted, provide } from "vue";
import { type RouteLocationNormalized } from "vue-router";
import { useRouter } from "vue-router";
import { useConsoleTheme } from "@/console/composables/useConsoleTheme";
@@ -8,12 +8,28 @@ import { InputBus, InputBusSymbol } from "@/console/input/bus";
import { attachGamepad } from "@/console/input/gamepad";
import { attachKeyboard } from "@/console/input/keyboard";
import { ROUTES } from "@/plugins/router";
import storeCollections from "@/stores/collections";
import storeNavigation from "@/stores/navigation";
import storePlatforms from "@/stores/platforms";
const router = useRouter();
const bus = new InputBus();
const themeStore = useConsoleTheme();
provide(InputBusSymbol, bus);
const navigationStore = storeNavigation();
const platformsStore = storePlatforms();
const collectionsStore = storeCollections();
const showVirtualCollections = useLocalStorage(
"settings.showVirtualCollections",
true,
);
const virtualCollectionTypeRef = useLocalStorage(
"settings.virtualCollectionType",
"collection",
);
// Define route hierarchy for transition direction logic
const routeHierarchy = {
[ROUTES.CONSOLE_HOME]: 0,
@@ -53,6 +69,17 @@ const { idle: mouseIdle } = useIdle(100, {
let detachKeyboard: (() => void) | null = null;
let detachGamepad: (() => void) | null = null;
onBeforeMount(() => {
platformsStore.fetchPlatforms();
collectionsStore.fetchCollections();
collectionsStore.fetchSmartCollections();
if (showVirtualCollections) {
collectionsStore.fetchVirtualCollections(virtualCollectionTypeRef.value);
}
navigationStore.reset();
});
onMounted(() => {
themeStore.initializeTheme();

View File

@@ -2,7 +2,7 @@
import { computed, onMounted, useTemplateRef, watch } from "vue";
import Skeleton from "@/components/common/Game/Card/Skeleton.vue";
import {
recentElementRegistry,
continuePlayingElementRegistry,
gamesListElementRegistry,
} from "@/console/composables/useElementRegistry";
import storeCollections from "@/stores/collections";
@@ -19,8 +19,8 @@ const props = defineProps<{
index: number;
selected?: boolean;
loaded?: boolean;
isRecent?: boolean;
registry?: "recent" | "gamesList";
continuePlaying?: boolean;
registry?: "continuePlaying" | "gamesList";
}>();
const heartbeatStore = storeHeartbeat();
@@ -84,7 +84,10 @@ onMounted(() => {
if (props.registry === "gamesList") {
gamesListElementRegistry.registerElement(props.index, gameCardRef.value);
} else {
recentElementRegistry.registerElement(props.index, gameCardRef.value);
continuePlayingElementRegistry.registerElement(
props.index,
gameCardRef.value,
);
}
});
</script>
@@ -96,7 +99,7 @@ onMounted(() => {
:class="{
'-translate-y-[2px] scale-[1.03] shadow-[0_8px_28px_rgba(0,0,0,0.35),_0_0_0_2px_var(--console-game-card-focus-border),_0_0_16px_var(--console-game-card-focus-border)]':
selected,
'w-[250px] shrink-0': isRecent,
'w-[250px] shrink-0': continuePlaying,
}"
@click="emit('click')"
@focus="emit('focus')"

View File

@@ -25,7 +25,7 @@ export function useElementRegistry() {
// Create a shared registry instance for each section
export const systemElementRegistry = useElementRegistry();
export const recentElementRegistry = useElementRegistry();
export const continuePlayingElementRegistry = useElementRegistry();
export const collectionElementRegistry = useElementRegistry();
export const smartCollectionElementRegistry = useElementRegistry();
export const virtualCollectionElementRegistry = useElementRegistry();

View File

@@ -574,7 +574,7 @@ onUnmounted(() => {
}"
>
{{
rom.platform_name ||
rom.platform_display_name ||
(rom.platform_slug || "RETRO")?.toString().toUpperCase()
}}
</span>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import {
computed,
onMounted,
@@ -6,11 +7,9 @@ import {
ref,
nextTick,
useTemplateRef,
watch,
} from "vue";
import { useRoute, useRouter } from "vue-router";
import type { CollectionSchema } from "@/__generated__/models/CollectionSchema";
import type { SmartCollectionSchema } from "@/__generated__/models/SmartCollectionSchema";
import type { VirtualCollectionSchema } from "@/__generated__/models/VirtualCollectionSchema";
import useFavoriteToggle from "@/composables/useFavoriteToggle";
import BackButton from "@/console/components/BackButton.vue";
import GameCard from "@/console/components/GameCard.vue";
@@ -22,8 +21,8 @@ import { useRovingDom } from "@/console/composables/useRovingDom";
import { useSpatialNav } from "@/console/composables/useSpatialNav";
import type { InputAction } from "@/console/input/actions";
import { ROUTES } from "@/plugins/router";
import collectionApi from "@/services/api/collection";
import storeCollections from "@/stores/collections";
import storeConfig from "@/stores/config";
import storeConsole from "@/stores/console";
import storeGalleryFilter from "@/stores/galleryFilter";
import storePlatforms from "@/stores/platforms";
@@ -32,61 +31,45 @@ import storeRoms, { type SimpleRom } from "@/stores/roms";
const route = useRoute();
const router = useRouter();
const consoleStore = storeConsole();
const platformsStore = storePlatforms();
const collectionsStore = storeCollections();
const galleryFilterStore = storeGalleryFilter();
const platformsStore = storePlatforms();
const { allPlatforms } = storeToRefs(platformsStore);
const collectionsStore = storeCollections();
const { allCollections, smartCollections, virtualCollections } =
storeToRefs(collectionsStore);
const romsStore = storeRoms();
const {
filteredRoms,
allRoms,
fetchingRoms,
currentPlatform,
currentCollection,
currentSmartCollection,
currentVirtualCollection,
} = storeToRefs(romsStore);
const configStore = storeConfig();
const { config } = storeToRefs(configStore);
const { toggleFavorite: toggleFavoriteComposable } = useFavoriteToggle();
const { setSelectedBackgroundArt, clearSelectedBackgroundArt } =
useBackgroundArt();
const isPlatformRoute = route.name === ROUTES.CONSOLE_PLATFORM;
const isCollectionRoute = route.name === ROUTES.CONSOLE_COLLECTION;
const isSmartCollectionRoute = route.name === ROUTES.CONSOLE_SMART_COLLECTION;
const isVirtualCollectionRoute =
route.name === ROUTES.CONSOLE_VIRTUAL_COLLECTION;
const platformId =
isCollectionRoute || isSmartCollectionRoute || isVirtualCollectionRoute
? null
: Number(route.params.id);
const collectionId = isCollectionRoute ? Number(route.params.id) : null;
const smartCollectionId = isSmartCollectionRoute
? Number(route.params.id)
: null;
const virtualCollectionId = isVirtualCollectionRoute
? String(route.params.id)
: null;
const roms = ref<SimpleRom[]>([]);
const collection = ref<CollectionSchema | null>(null);
const smartCollection = ref<SmartCollectionSchema | null>(null);
const virtualCollection = ref<VirtualCollectionSchema | null>(null);
const loading = ref(true);
const error = ref("");
const selectedIndex = ref(0);
const loadedMap = ref<Record<number, boolean>>({});
const inAlphabet = ref(false);
const alphaIndex = ref(0);
const gridRef = useTemplateRef<HTMLDivElement>("game-grid-ref");
// Initialize selection from store
if (platformId != null) {
selectedIndex.value = consoleStore.getPlatformGameIndex(platformId);
} else if (collectionId != null) {
selectedIndex.value = consoleStore.getCollectionGameIndex(collectionId);
} else if (smartCollectionId != null) {
selectedIndex.value = consoleStore.getCollectionGameIndex(smartCollectionId);
} else if (virtualCollectionId != null) {
selectedIndex.value = consoleStore.getCollectionGameIndex(
Number(virtualCollectionId),
);
}
// Generate alphabet letters dynamically based on available games
const letters = computed(() => {
const letterSet = new Set<string>();
roms.value.forEach(({ name }) => {
filteredRoms.value.forEach(({ name }) => {
if (!name) return;
const normalized = normalizeTitle(name);
@@ -111,15 +94,24 @@ const letters = computed(() => {
});
function persistIndex() {
if (platformId != null) {
consoleStore.setPlatformGameIndex(platformId, selectedIndex.value);
} else if (collectionId != null) {
consoleStore.setCollectionGameIndex(collectionId, selectedIndex.value);
} else if (smartCollectionId != null) {
consoleStore.setCollectionGameIndex(smartCollectionId, selectedIndex.value);
} else if (virtualCollectionId != null) {
if (currentPlatform.value != null) {
consoleStore.setPlatformGameIndex(
currentPlatform.value.id,
selectedIndex.value,
);
} else if (currentCollection.value != null) {
consoleStore.setCollectionGameIndex(
Number(virtualCollectionId),
currentCollection.value.id,
selectedIndex.value,
);
} else if (currentSmartCollection.value != null) {
consoleStore.setSmartCollectionGameIndex(
currentSmartCollection.value.id,
selectedIndex.value,
);
} else if (currentVirtualCollection.value != null) {
consoleStore.setVirtualCollectionGameIndex(
currentVirtualCollection.value.id,
selectedIndex.value,
);
}
@@ -132,26 +124,21 @@ function navigateBack() {
const headerTitle = computed(() => {
if (isCollectionRoute) {
return collection.value?.name || "Collection";
return currentCollection.value?.name || "Collection";
}
if (isSmartCollectionRoute) {
return smartCollection.value?.name || "Smart Collection";
return currentSmartCollection.value?.name || "Smart Collection";
}
if (isVirtualCollectionRoute) {
return virtualCollection.value?.name || "Virtual Collection";
return currentVirtualCollection.value?.name || "Virtual Collection";
}
return (
current.value?.platform_name ||
current.value?.platform_slug?.toUpperCase() ||
"Platform"
currentPlatform.value?.display_name ||
currentPlatform.value?.slug.toUpperCase()
);
});
const current = computed(
() => roms.value[selectedIndex.value] || roms.value[0],
);
function getCols(): number {
if (!gridRef.value) return 4;
@@ -176,10 +163,10 @@ const {
moveRight,
moveUp,
moveDown: moveDownBasic,
} = useSpatialNav(selectedIndex, getCols, () => roms.value.length);
} = useSpatialNav(selectedIndex, getCols, () => filteredRoms.value.length);
function handleAction(action: InputAction): boolean {
if (!roms.value.length) return false;
if (!filteredRoms.value.length) return false;
if (inAlphabet.value) {
if (action === "moveLeft") {
inAlphabet.value = false;
@@ -198,7 +185,7 @@ function handleAction(action: InputAction): boolean {
}
if (action === "confirm") {
const L = Array.from(letters.value)[alphaIndex.value];
const idx = roms.value.findIndex((r) => {
const idx = filteredRoms.value.findIndex((r) => {
const normalized = normalizeTitle(r.name || "");
if (L === "#") {
return /^[0-9]/.test(normalized);
@@ -245,7 +232,7 @@ function handleAction(action: InputAction): boolean {
moveDownBasic();
if (selectedIndex.value === before) {
const cols = getCols();
const count = roms.value.length;
const count = filteredRoms.value.length;
const totalRows = Math.ceil(count / cols);
const currentRow = Math.floor(before / cols);
if (totalRows > currentRow + 1) {
@@ -258,11 +245,14 @@ function handleAction(action: InputAction): boolean {
navigateBack();
return true;
case "confirm": {
selectAndOpen(selectedIndex.value, roms.value[selectedIndex.value]);
selectAndOpen(
selectedIndex.value,
filteredRoms.value[selectedIndex.value],
);
return true;
}
case "toggleFavorite": {
const rom = roms.value[selectedIndex.value];
const rom = filteredRoms.value[selectedIndex.value];
if (rom) toggleFavoriteComposable(rom);
return true;
}
@@ -283,10 +273,14 @@ function selectAndOpen(i: number, rom: SimpleRom) {
persistIndex();
const query: Record<string, number | string> = {};
if (platformId != null) query.id = platformId;
if (isCollectionRoute) query.collection = collectionId!;
if (isSmartCollectionRoute) query.smartCollection = smartCollectionId!;
if (isVirtualCollectionRoute) query.virtualCollection = virtualCollectionId!;
if (isPlatformRoute && currentPlatform.value != null)
query.id = currentPlatform.value.id;
if (isCollectionRoute && currentCollection.value != null)
query.collection = currentCollection.value.id;
if (isSmartCollectionRoute && currentSmartCollection.value != null)
query.smartCollection = currentSmartCollection.value.id;
if (isVirtualCollectionRoute && currentVirtualCollection.value != null)
query.virtualCollection = currentVirtualCollection.value.id;
router.push({
name: ROUTES.CONSOLE_ROM,
@@ -296,7 +290,7 @@ function selectAndOpen(i: number, rom: SimpleRom) {
}
function jumpToLetter(L: string) {
const idx = roms.value.findIndex((r) => {
const idx = filteredRoms.value.findIndex((r) => {
const normalized = normalizeTitle(r.name || "");
if (L === "#") {
return /^[0-9]/.test(normalized);
@@ -316,68 +310,150 @@ function normalizeTitle(name: string) {
let off: (() => void) | null = null;
onMounted(async () => {
try {
if (platformId != null) {
const currentPlatform = platformsStore.get(platformId);
if (currentPlatform) romsStore.setCurrentPlatform(currentPlatform);
} else if (collectionId != null) {
const currentCollection = collectionsStore.getCollection(collectionId);
if (currentCollection) romsStore.setCurrentCollection(currentCollection);
} else if (smartCollectionId != null) {
const currentSmartCollection =
collectionsStore.getSmartCollection(smartCollectionId);
if (currentSmartCollection)
romsStore.setCurrentSmartCollection(currentSmartCollection);
} else if (virtualCollectionId != null) {
const currentVirtualCollection =
collectionsStore.getVirtualCollection(virtualCollectionId);
if (currentVirtualCollection)
romsStore.setCurrentVirtualCollection(currentVirtualCollection);
}
function resetGallery() {
romsStore.reset();
galleryFilterStore.resetFilters();
galleryFilterStore.activeFilterDrawer = false;
}
romsStore.setLimit(500);
romsStore.setOrderBy("name");
romsStore.setOrderDir("asc");
romsStore.resetPagination();
async function fetchRoms() {
romsStore.setLimit(500);
romsStore.setOrderBy("name");
romsStore.setOrderDir("asc");
romsStore.resetPagination();
const fetchedRoms = await romsStore.fetchRoms({
galleryFilter: galleryFilterStore,
concat: false,
});
roms.value = fetchedRoms;
const fetchedRoms = await romsStore.fetchRoms({
galleryFilter: galleryFilterStore,
concat: false,
});
if (collectionId != null) {
const { data: col } = await collectionApi.getCollection(collectionId);
collection.value = col ?? null;
} else if (smartCollectionId != null) {
const { data: smartCol } =
await collectionApi.getSmartCollection(smartCollectionId);
smartCollection.value = smartCol ?? null;
} else if (virtualCollectionId != null) {
const { data: virtualCol } =
await collectionApi.getVirtualCollection(virtualCollectionId);
virtualCollection.value = virtualCol ?? null;
}
for (const r of roms.value) {
if (!r.url_cover && !r.path_cover_large && !r.path_cover_small) {
loadedMap.value[r.id] = true;
}
}
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : "Failed to load roms";
} finally {
loading.value = false;
}
if (selectedIndex.value >= roms.value.length) selectedIndex.value = 0;
if (selectedIndex.value >= fetchedRoms.length) selectedIndex.value = 0;
await nextTick();
cardElementAt(selectedIndex.value)?.scrollIntoView({
block: "center",
inline: "nearest",
behavior: "instant" as ScrollBehavior,
});
}
onMounted(async () => {
const routePlatformId = isPlatformRoute ? Number(route.params.id) : null;
const routeCollectionId = isCollectionRoute ? Number(route.params.id) : null;
const routeSmartCollectionId = isSmartCollectionRoute
? Number(route.params.id)
: null;
const routeVirtualCollectionId = isVirtualCollectionRoute
? String(route.params.id)
: null;
watch(
() => allPlatforms.value,
async (platforms) => {
if (platforms.length > 0) {
const platform = platforms.find(
(platform) => platform.id === routePlatformId,
);
// Check if the current platform is different or no ROMs have been loaded
if (
platform &&
(currentPlatform.value?.id !== routePlatformId ||
allRoms.value.length === 0)
) {
resetGallery();
romsStore.setCurrentPlatform(platform);
selectedIndex.value = consoleStore.getPlatformGameIndex(platform.id);
document.title = platform.display_name;
await fetchRoms();
}
}
},
{ immediate: true }, // Ensure watcher is triggered immediately
);
watch(
() => allCollections.value,
async (collections) => {
if (collections.length > 0) {
const collection = collections.find(
(collection) => collection.id === routeCollectionId,
);
// Check if the current collection is different or no ROMs have been loaded
if (
collection &&
(currentCollection.value?.id !== routeCollectionId ||
allRoms.value.length === 0)
) {
resetGallery();
romsStore.setCurrentCollection(collection);
selectedIndex.value = consoleStore.getCollectionGameIndex(
collection.id,
);
document.title = collection.name;
await fetchRoms();
}
}
},
{ immediate: true }, // Ensure watcher is triggered immediately
);
watch(
() => smartCollections.value,
async (smartCollections) => {
if (smartCollections.length > 0) {
const smartCollection = smartCollections.find(
(smartCollection) => smartCollection.id === routeSmartCollectionId,
);
// Check if the current smartCollection is different or no ROMs have been loaded
if (
smartCollection &&
(currentSmartCollection.value?.id !== routeSmartCollectionId ||
allRoms.value.length === 0)
) {
resetGallery();
romsStore.setCurrentSmartCollection(smartCollection);
selectedIndex.value = consoleStore.getSmartCollectionGameIndex(
smartCollection.id,
);
document.title = smartCollection.name;
await fetchRoms();
}
}
},
{ immediate: true }, // Ensure watcher is triggered immediately
);
watch(
() => virtualCollections.value,
async (virtualCollections) => {
if (virtualCollections.length > 0) {
const virtualCollection = virtualCollections.find(
(virtualCollection) =>
virtualCollection.id === routeVirtualCollectionId,
);
// Check if the current virtualCollection is different or no ROMs have been loaded
if (
virtualCollection &&
(currentVirtualCollection.value?.id !== routeVirtualCollectionId ||
allRoms.value.length === 0)
) {
resetGallery();
romsStore.setCurrentVirtualCollection(virtualCollection);
selectedIndex.value = consoleStore.getVirtualCollectionGameIndex(
virtualCollection.id,
);
document.title = virtualCollection.name;
await fetchRoms();
}
}
},
{ immediate: true }, // Ensure watcher is triggered immediately
);
off = subscribe(handleAction);
});
@@ -411,21 +487,17 @@ function handleItemDeselected() {
:style="{ width: 'calc(100vw - 40px)' }"
>
<div
v-if="loading"
v-if="fetchingRoms"
class="text-center mt-8"
:style="{ color: 'var(--console-loading-text)' }"
>
Loading games
</div>
<div
v-else-if="error"
class="text-center mt-8"
:style="{ color: 'var(--console-error-text)' }"
>
{{ error }}
</div>
<div v-else>
<div v-if="roms.length === 0" class="text-center text-fgDim p-4">
<div
v-if="filteredRoms.length === 0"
class="text-center text-fgDim p-4"
>
No games found.
</div>
<div
@@ -434,7 +506,7 @@ function handleItemDeselected() {
@wheel.prevent
>
<GameCard
v-for="(rom, i) in roms"
v-for="(rom, i) in filteredRoms"
:key="rom.id"
:rom="rom"
:index="i"

View File

@@ -7,12 +7,9 @@ import {
nextTick,
watch,
useTemplateRef,
onBeforeMount,
} from "vue";
import { useRouter } from "vue-router";
import type { CollectionSchema } from "@/__generated__/models/CollectionSchema";
import type { PlatformSchema } from "@/__generated__/models/PlatformSchema";
import type { SmartCollectionSchema } from "@/__generated__/models/SmartCollectionSchema";
import type { VirtualCollectionSchema } from "@/__generated__/models/VirtualCollectionSchema";
import RIsotipo from "@/components/common/RIsotipo.vue";
import useFavoriteToggle from "@/composables/useFavoriteToggle";
import CollectionCard from "@/console/components/CollectionCard.vue";
@@ -23,7 +20,7 @@ import SystemCard from "@/console/components/SystemCard.vue";
import useBackgroundArt from "@/console/composables/useBackgroundArt";
import {
systemElementRegistry,
recentElementRegistry,
continuePlayingElementRegistry,
collectionElementRegistry,
smartCollectionElementRegistry,
virtualCollectionElementRegistry,
@@ -31,24 +28,27 @@ import {
import { useInputScope } from "@/console/composables/useInputScope";
import { useRovingDom } from "@/console/composables/useRovingDom";
import { useSpatialNav } from "@/console/composables/useSpatialNav";
import { isSupportedPlatform } from "@/console/constants/platforms";
import type { InputAction } from "@/console/input/actions";
import { ROUTES } from "@/plugins/router";
import collectionApi from "@/services/api/collection";
import platformApi from "@/services/api/platform";
import storeCollections from "@/stores/collections";
import storeConsole from "@/stores/console";
import storePlatforms from "@/stores/platforms";
import storeRoms from "@/stores/roms";
import type { SimpleRom } from "@/stores/roms";
const router = useRouter();
const platformsStore = storePlatforms();
const { allPlatforms, fetchingPlatforms } = storeToRefs(platformsStore);
const collectionsStore = storeCollections();
const consoleStore = storeConsole();
const { allCollections, smartCollections, virtualCollections } =
storeToRefs(collectionsStore);
const romsStore = storeRoms();
const { continuePlayingRoms } = storeToRefs(romsStore);
const consoleStore = storeConsole();
const {
navigationMode,
platformIndex,
recentIndex,
continuePlayingIndex,
collectionsIndex,
smartCollectionsIndex,
virtualCollectionsIndex,
@@ -59,12 +59,6 @@ const { setSelectedBackgroundArt, clearSelectedBackgroundArt } =
useBackgroundArt();
const { subscribe } = useInputScope();
const platforms = ref<PlatformSchema[]>([]);
const recentRoms = ref<SimpleRom[]>([]);
const collections = ref<CollectionSchema[]>([]);
const smartCollections = ref<SmartCollectionSchema[]>([]);
const virtualCollections = ref<VirtualCollectionSchema[]>([]);
const loadingPlatforms = ref(true);
const errorMessage = ref("");
const showSettings = ref(false);
@@ -73,7 +67,9 @@ const scrollContainerRef = useTemplateRef<HTMLDivElement>(
"scroll-container-ref",
);
const platformsRef = useTemplateRef<HTMLDivElement>("platforms-ref");
const recentRef = useTemplateRef<HTMLDivElement>("recent-ref");
const continuePlayingRef = useTemplateRef<HTMLDivElement>(
"continue-playing-ref",
);
const collectionsRef = useTemplateRef<HTMLDivElement>("collections-ref");
const smartCollectionsRef = useTemplateRef<HTMLDivElement>(
"smart-collections-ref",
@@ -81,7 +77,9 @@ const smartCollectionsRef = useTemplateRef<HTMLDivElement>(
const virtualCollectionsRef = useTemplateRef<HTMLDivElement>(
"virtual-collections-ref",
);
const recentSectionRef = useTemplateRef<HTMLElement>("recent-section-ref");
const continuePlayingSectionRef = useTemplateRef<HTMLElement>(
"continue-playing-section-ref",
);
const collectionsSectionRef = useTemplateRef<HTMLElement>(
"collections-section-ref",
);
@@ -93,7 +91,8 @@ const virtualCollectionsSectionRef = useTemplateRef<HTMLElement>(
);
const systemElementAt = (i: number) => systemElementRegistry.getElement(i);
const recentElementAt = (i: number) => recentElementRegistry.getElement(i);
const continuePlayingElementAt = (i: number) =>
continuePlayingElementRegistry.getElement(i);
const collectionElementAt = (i: number) =>
collectionElementRegistry.getElement(i);
const smartCollectionElementAt = (i: number) =>
@@ -104,19 +103,22 @@ const virtualCollectionElementAt = (i: number) =>
// Spatial navigation
const { moveLeft: moveSystemLeft, moveRight: moveSystemRight } = useSpatialNav(
platformIndex,
() => platforms.value.length || 1,
() => platforms.value.length,
() => allPlatforms.value.length || 1,
() => allPlatforms.value.length,
);
const { moveLeft: moveRecentLeft, moveRight: moveRecentRight } = useSpatialNav(
recentIndex,
() => recentRoms.value.length || 1,
() => recentRoms.value.length,
const {
moveLeft: moveContinuePlayingLeft,
moveRight: moveContinuePlayingRight,
} = useSpatialNav(
continuePlayingIndex,
() => continuePlayingRoms.value.length || 1,
() => continuePlayingRoms.value.length,
);
const { moveLeft: moveCollectionLeft, moveRight: moveCollectionRight } =
useSpatialNav(
collectionsIndex,
() => collections.value.length || 1,
() => collections.value.length,
() => allCollections.value.length || 1,
() => allCollections.value.length,
);
const {
moveLeft: moveSmartCollectionLeft,
@@ -141,7 +143,7 @@ useRovingDom(platformIndex, systemElementAt, {
behavior: "smooth",
scroll: false, // handle scrolling manually
});
useRovingDom(recentIndex, recentElementAt, {
useRovingDom(continuePlayingIndex, continuePlayingElementAt, {
inline: "center",
block: "nearest",
behavior: "smooth",
@@ -176,11 +178,11 @@ watch(platformIndex, (newIdx) => {
}
});
watch(recentIndex, (newIdx) => {
watch(continuePlayingIndex, (newIdx) => {
if (!isVerticalScrolling) {
const el = recentElementAt(newIdx);
if (el && recentRef.value) {
centerInCarousel(recentRef.value, el, "smooth");
const el = continuePlayingElementAt(newIdx);
if (el && continuePlayingRef.value) {
centerInCarousel(continuePlayingRef.value, el, "smooth");
}
}
});
@@ -219,7 +221,7 @@ const navigationFunctions = {
const before = platformIndex.value;
moveSystemLeft();
if (platformIndex.value === before) {
platformIndex.value = Math.max(0, platforms.value.length - 1);
platformIndex.value = Math.max(0, allPlatforms.value.length - 1);
}
},
next: () => {
@@ -230,35 +232,42 @@ const navigationFunctions = {
}
},
confirm: () => {
if (!platforms.value[platformIndex.value]) return false;
if (!allPlatforms.value[platformIndex.value]) return false;
router.push({
name: ROUTES.CONSOLE_PLATFORM,
params: { id: platforms.value[platformIndex.value].id },
params: { id: allPlatforms.value[platformIndex.value].id },
});
return true;
},
},
recent: {
continuePlaying: {
prev: () => {
const before = recentIndex.value;
moveRecentLeft();
if (recentIndex.value === before) {
recentIndex.value = Math.max(0, recentRoms.value.length - 1);
const before = continuePlayingIndex.value;
moveContinuePlayingLeft();
if (continuePlayingIndex.value === before) {
continuePlayingIndex.value = Math.max(
0,
continuePlayingRoms.value.length - 1,
);
}
},
next: () => {
const before = recentIndex.value;
moveRecentRight();
if (recentIndex.value === before) {
recentIndex.value = 0;
const before = continuePlayingIndex.value;
moveContinuePlayingRight();
if (continuePlayingIndex.value === before) {
continuePlayingIndex.value = 0;
}
},
confirm: () => {
if (!recentRoms.value[recentIndex.value]) return false;
if (!continuePlayingRoms.value[continuePlayingIndex.value]) return false;
router.push({
name: ROUTES.CONSOLE_ROM,
params: { rom: recentRoms.value[recentIndex.value].id },
query: { id: recentRoms.value[recentIndex.value].platform_id },
params: {
rom: continuePlayingRoms.value[continuePlayingIndex.value].id,
},
query: {
id: continuePlayingRoms.value[continuePlayingIndex.value].platform_id,
},
});
return true;
},
@@ -268,7 +277,7 @@ const navigationFunctions = {
const before = collectionsIndex.value;
moveCollectionLeft();
if (collectionsIndex.value === before) {
collectionsIndex.value = Math.max(0, collections.value.length - 1);
collectionsIndex.value = Math.max(0, allCollections.value.length - 1);
}
},
next: () => {
@@ -279,10 +288,10 @@ const navigationFunctions = {
}
},
confirm: () => {
if (!collections.value[collectionsIndex.value]) return false;
if (!allCollections.value[collectionsIndex.value]) return false;
router.push({
name: ROUTES.CONSOLE_COLLECTION,
params: { id: collections.value[collectionsIndex.value].id },
params: { id: allCollections.value[collectionsIndex.value].id },
});
return true;
},
@@ -374,8 +383,11 @@ function scrollToCurrentRow() {
case "systems":
scrollContainerRef.value?.scrollTo({ top: 0, behavior });
break;
case "recent":
recentSectionRef.value?.scrollIntoView({ behavior, block: "start" });
case "continuePlaying":
continuePlayingSectionRef.value?.scrollIntoView({
behavior,
block: "start",
});
break;
case "collections":
collectionsSectionRef.value?.scrollIntoView({
@@ -513,23 +525,23 @@ function handleAction(action: InputAction): boolean {
navigationMode.value = "controls";
return true;
}
if (currentMode === "recent") {
if (currentMode === "continuePlaying") {
navigationMode.value = "systems";
scrollToCurrentRow();
return true;
}
if (currentMode === "collections") {
navigationMode.value =
recentRoms.value.length > 0 ? "recent" : "systems";
continuePlayingRoms.value.length > 0 ? "continuePlaying" : "systems";
scrollToCurrentRow();
return true;
}
if (currentMode === "smartCollections") {
navigationMode.value =
collections.value.length > 0
allCollections.value.length > 0
? "collections"
: recentRoms.value.length > 0
? "recent"
: continuePlayingRoms.value.length > 0
? "continuePlaying"
: "systems";
scrollToCurrentRow();
return true;
@@ -538,10 +550,10 @@ function handleAction(action: InputAction): boolean {
navigationMode.value =
smartCollections.value.length > 0
? "smartCollections"
: collections.value.length > 0
: allCollections.value.length > 0
? "collections"
: recentRoms.value.length > 0
? "recent"
: continuePlayingRoms.value.length > 0
? "continuePlaying"
: "systems";
scrollToCurrentRow();
return true;
@@ -551,9 +563,9 @@ function handleAction(action: InputAction): boolean {
case "moveDown":
if (currentMode === "systems") {
navigationMode.value =
recentRoms.value.length > 0
? "recent"
: collections.value.length > 0
continuePlayingRoms.value.length > 0
? "continuePlaying"
: allCollections.value.length > 0
? "collections"
: smartCollections.value.length > 0
? "smartCollections"
@@ -563,9 +575,9 @@ function handleAction(action: InputAction): boolean {
scrollToCurrentRow();
return true;
}
if (currentMode === "recent") {
if (currentMode === "continuePlaying") {
navigationMode.value =
collections.value.length > 0
allCollections.value.length > 0
? "collections"
: smartCollections.value.length > 0
? "smartCollections"
@@ -613,8 +625,13 @@ function handleAction(action: InputAction): boolean {
return true;
case "toggleFavorite":
if (currentMode === "recent" && recentRoms.value[recentIndex.value]) {
toggleFavoriteComposable(recentRoms.value[recentIndex.value]);
if (
currentMode === "continuePlaying" &&
continuePlayingRoms.value[continuePlayingIndex.value]
) {
toggleFavoriteComposable(
continuePlayingRoms.value[continuePlayingIndex.value],
);
return true;
}
return false;
@@ -624,42 +641,16 @@ function handleAction(action: InputAction): boolean {
}
}
onBeforeMount(async () => {
await romsStore.fetchContinuePlayingRoms();
});
onMounted(async () => {
try {
const [
{ data: plats },
recents,
{ data: cols },
{ data: smartCols },
{ data: virtualCols },
] = await Promise.all([
platformApi.getPlatforms(),
romsStore.fetchRecentRoms(),
collectionApi.getCollections(),
collectionApi.getSmartCollections(),
collectionApi.getVirtualCollections({ type: "collection" }),
]);
platforms.value = plats.filter(
(p) => p.rom_count > 0 && isSupportedPlatform(p.slug),
);
recentRoms.value = recents ?? [];
collections.value = cols ?? [];
smartCollections.value = smartCols ?? [];
virtualCollections.value = virtualCols ?? [];
collectionsStore.setCollections(cols ?? []);
collectionsStore.setFavoriteCollection(cols?.find((c) => c.is_favorite));
} catch (err: unknown) {
errorMessage.value = err instanceof Error ? err.message : "Failed to load";
} finally {
loadingPlatforms.value = false;
}
// Restore indices within bounds
if (platformIndex.value >= platforms.value.length) platformIndex.value = 0;
if (recentIndex.value >= recentRoms.value.length) recentIndex.value = 0;
if (collectionsIndex.value >= collections.value.length)
if (platformIndex.value >= allPlatforms.value.length) platformIndex.value = 0;
if (continuePlayingIndex.value >= continuePlayingRoms.value.length)
continuePlayingIndex.value = 0;
if (collectionsIndex.value >= allCollections.value.length)
collectionsIndex.value = 0;
if (smartCollectionsIndex.value >= smartCollections.value.length)
smartCollectionsIndex.value = 0;
@@ -671,7 +662,10 @@ onMounted(async () => {
// Center carousels
centerInCarousel(platformsRef.value, systemElementAt(platformIndex.value));
centerInCarousel(recentRef.value, recentElementAt(recentIndex.value));
centerInCarousel(
continuePlayingRef.value,
continuePlayingElementAt(continuePlayingIndex.value),
);
centerInCarousel(
collectionsRef.value,
collectionElementAt(collectionsIndex.value),
@@ -693,7 +687,7 @@ let off: (() => void) | null = null;
onUnmounted(() => {
consoleStore.setHomeState({
platformIndex: platformIndex.value,
recentIndex: recentIndex.value,
continuePlayingIndex: continuePlayingIndex.value,
collectionsIndex: collectionsIndex.value,
smartCollectionsIndex: smartCollectionsIndex.value,
virtualCollectionsIndex: virtualCollectionsIndex.value,
@@ -723,7 +717,7 @@ onUnmounted(() => {
</div>
<div
v-if="loadingPlatforms"
v-if="fetchingPlatforms"
class="text-center mt-16"
:style="{ color: 'var(--console-loading-text)' }"
>
@@ -774,7 +768,7 @@ onUnmounted(() => {
>
<div class="flex items-center gap-6 h-full px-12 min-w-max">
<SystemCard
v-for="(p, i) in platforms"
v-for="(p, i) in allPlatforms"
:key="p.id"
:platform="p"
:index="i"
@@ -790,8 +784,8 @@ onUnmounted(() => {
</section>
<section
v-if="recentRoms.length > 0"
ref="recent-section-ref"
v-if="continuePlayingRoms.length > 0"
ref="continue-playing-section-ref"
class="pb-8"
>
<h2
@@ -808,7 +802,7 @@ onUnmounted(() => {
border: `1px solid var(--console-home-carousel-button-border)`,
color: 'var(--console-home-carousel-button-text)',
}"
@click="navigationFunctions.recent.prev"
@click="navigationFunctions.continuePlaying.prev"
>
</button>
@@ -819,26 +813,29 @@ onUnmounted(() => {
border: `1px solid var(--console-home-carousel-button-border)`,
color: 'var(--console-home-carousel-button-text)',
}"
@click="navigationFunctions.recent.next"
@click="navigationFunctions.continuePlaying.next"
>
</button>
<div
ref="recent-ref"
ref="continue-playing-ref"
class="w-full h-full overflow-x-auto overflow-y-hidden no-scrollbar [scrollbar-width:none] [-ms-overflow-style:none]"
@wheel.prevent
>
<div class="flex items-center gap-4 h-full px-12 min-w-max">
<GameCard
v-for="(g, i) in recentRoms"
v-for="(g, i) in continuePlayingRoms"
:key="`${g.platform_id}-${g.id}`"
:rom="g"
:index="i"
:is-recent="true"
:selected="navigationMode === 'recent' && i === recentIndex"
:continue-playing="true"
:selected="
navigationMode === 'continuePlaying' &&
i === continuePlayingIndex
"
:loaded="true"
@click="goGame(g)"
@focus="recentIndex = i"
@focus="continuePlayingIndex = i"
@select="handleItemSelected"
@deselect="handleItemDeselected"
/>
@@ -848,7 +845,7 @@ onUnmounted(() => {
</section>
<section
v-if="collections.length > 0"
v-if="allCollections.length > 0"
ref="collections-section-ref"
class="pb-8"
>
@@ -888,7 +885,7 @@ onUnmounted(() => {
>
<div class="flex items-center gap-4 h-full px-12 min-w-max">
<CollectionCard
v-for="(c, i) in collections"
v-for="(c, i) in allCollections"
:key="`collection-${c.id}`"
:collection="c"
:index="i"
@@ -1080,7 +1077,7 @@ onUnmounted(() => {
<NavigationHint
:show-back="false"
:show-toggle-favorite="navigationMode === 'recent'"
:show-toggle-favorite="navigationMode === 'continuePlaying'"
/>
</div>
<SettingsModal v-model="showSettings" />

View File

@@ -3,7 +3,7 @@ import { ROUTES } from "@/plugins/router";
export type NavigationMode =
| "systems"
| "recent"
| "continuePlaying"
| "collections"
| "smartCollections"
| "virtualCollections"
@@ -12,7 +12,7 @@ export type NavigationMode =
export default defineStore("console", {
state: () => ({
platformIndex: 0,
recentIndex: 0,
continuePlayingIndex: 0,
collectionsIndex: 0,
smartCollectionsIndex: 0,
virtualCollectionsIndex: 0,
@@ -20,8 +20,12 @@ export default defineStore("console", {
navigationMode: "systems" as NavigationMode,
perPlatformGameIndex: {} as Record<number, number>,
perCollectionGameIndex: {} as Record<number, number>,
perSmartCollectionGameIndex: {} as Record<number, number>,
perVirtualCollectionGameIndex: {} as Record<string, number>,
perPlatformScrollTop: {} as Record<number, number>,
perCollectionScrollTop: {} as Record<number, number>,
perSmartCollectionScrollTop: {} as Record<number, number>,
perVirtualCollectionScrollTop: {} as Record<string, number>,
}),
getters: {
consoleMode: (state) => {
@@ -41,7 +45,7 @@ export default defineStore("console", {
actions: {
setHomeState(payload: {
platformIndex?: number;
recentIndex?: number;
continuePlayingIndex?: number;
collectionsIndex?: number;
smartCollectionsIndex?: number;
virtualCollectionsIndex?: number;
@@ -50,8 +54,8 @@ export default defineStore("console", {
}) {
if (payload.platformIndex !== undefined)
this.platformIndex = payload.platformIndex;
if (payload.recentIndex !== undefined)
this.recentIndex = payload.recentIndex;
if (payload.continuePlayingIndex !== undefined)
this.continuePlayingIndex = payload.continuePlayingIndex;
if (payload.collectionsIndex !== undefined)
this.collectionsIndex = payload.collectionsIndex;
if (payload.smartCollectionsIndex !== undefined)
@@ -75,6 +79,18 @@ export default defineStore("console", {
getCollectionGameIndex(collectionId: number) {
return this.perCollectionGameIndex[collectionId] ?? 0;
},
setSmartCollectionGameIndex(smartCollectionId: number, idx: number) {
this.perSmartCollectionGameIndex[smartCollectionId] = idx;
},
getSmartCollectionGameIndex(smartCollectionId: number) {
return this.perSmartCollectionGameIndex[smartCollectionId] ?? 0;
},
setVirtualCollectionGameIndex(virtualCollectionId: string, idx: number) {
this.perVirtualCollectionGameIndex[virtualCollectionId] = idx;
},
getVirtualCollectionGameIndex(virtualCollectionId: string) {
return this.perVirtualCollectionGameIndex[virtualCollectionId] ?? 0;
},
setPlatformScroll(platformId: number, top: number) {
this.perPlatformScrollTop[platformId] = top;
},
@@ -87,5 +103,17 @@ export default defineStore("console", {
getCollectionScroll(collectionId: number) {
return this.perCollectionScrollTop[collectionId] ?? 0;
},
setSmartCollectionScroll(smartCollectionId: number, top: number) {
this.perSmartCollectionScrollTop[smartCollectionId] = top;
},
getSmartCollectionScroll(smartCollectionId: number) {
return this.perSmartCollectionScrollTop[smartCollectionId] ?? 0;
},
setVirtualCollectionScroll(virtualCollectionId: string, top: number) {
this.perVirtualCollectionScrollTop[virtualCollectionId] = top;
},
getVirtualCollectionScroll(virtualCollectionId: string) {
return this.perVirtualCollectionScrollTop[virtualCollectionId] ?? 0;
},
},
});

View File

@@ -1,19 +1,15 @@
import { defineStore } from "pinia";
import type { SimpleRom } from "@/stores/roms";
import type { Platform } from "./platforms";
interface ScanningPlatforms {
name: string;
slug: string;
fs_slug: string;
id: number;
is_identified: boolean;
interface ScanningPlatform extends Partial<Platform> {
roms: SimpleRom[];
}
export default defineStore("scanning", {
state: () => ({
scanning: false,
scanningPlatforms: [] as ScanningPlatforms[],
scanningPlatforms: [] as ScanningPlatform[],
scanStats: {
scanned_platforms: 0,
new_platforms: 0,
@@ -30,7 +26,7 @@ export default defineStore("scanning", {
},
reset() {
this.scanning = false;
this.scanningPlatforms = [] as ScanningPlatforms[];
this.scanningPlatforms = [] as ScanningPlatform[];
this.scanStats = {
scanned_platforms: 0,
new_platforms: 0,

View File

@@ -183,7 +183,7 @@ onMounted(async () => {
) {
resetGallery();
props.setCurrentCollection(collection);
document.title = `${collection.name}`;
document.title = collection.name;
await fetchRoms();
}
}
@@ -213,7 +213,7 @@ onBeforeRouteUpdate(async (to, from) => {
) {
resetGallery();
props.setCurrentCollection(collection);
document.title = `${collection.name}`;
document.title = collection.name;
await fetchRoms();
}
}

View File

@@ -168,28 +168,26 @@ onMounted(async () => {
() => filteredPlatforms.value,
async (platforms) => {
if (platforms.length > 0) {
if (platforms.some((platform) => platform.id === routePlatformId)) {
const platform = platforms.find(
(platform) => platform.id === routePlatformId,
);
const platform = platforms.find(
(platform) => platform.id === routePlatformId,
);
// Check if the current platform is different or no ROMs have been loaded
if (
(currentPlatform.value?.id !== routePlatformId ||
allRoms.value.length === 0) &&
platform
) {
if (currentPlatform.value) resetGallery();
romsStore.setCurrentPlatform(platform);
document.title = `${platform.display_name}`;
await fetchRoms();
}
} else {
noPlatformError.value = true;
// Check if the current platform is different or no ROMs have been loaded
if (
platform &&
(currentPlatform.value?.id !== routePlatformId ||
allRoms.value.length === 0)
) {
if (currentPlatform.value) resetGallery();
romsStore.setCurrentPlatform(platform);
document.title = platform.display_name;
await fetchRoms();
}
} else {
noPlatformError.value = true;
}
},
{ immediate: true }, // Ensure watcher is triggered immediately
{ immediate: true },
);
});
@@ -215,7 +213,7 @@ onBeforeRouteUpdate(async (to, from) => {
) {
if (currentPlatform.value) resetGallery();
romsStore.setCurrentPlatform(platform);
document.title = `${platform.display_name}`;
document.title = platform.display_name;
await fetchRoms();
} else {
noPlatformError.value = true;

View File

@@ -341,13 +341,14 @@ async function stopScan() {
<template #prepend>
<v-avatar rounded="0" size="40">
<PlatformIcon
v-if="platform.slug"
:key="platform.slug"
:slug="platform.slug"
:name="platform.name"
:name="platform.display_name"
/>
</v-avatar>
</template>
{{ platform.name }}
{{ platform.display_name }}
<template #append>
<v-chip class="ml-3" color="primary" size="x-small" label>
{{ platform.roms.length }}