mirror of
https://github.com/rommapp/romm.git
synced 2026-06-28 06:46:00 +00:00
massive refactor of data loading in console mode
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12,11 +12,11 @@ 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];
|
||||
const platforms = [...allPlatforms.value];
|
||||
if (orderBy.value === "size") {
|
||||
return platforms.sort(
|
||||
(a, b) => Number(b.fs_size_bytes) - Number(a.fs_size_bytes),
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
name: rom.platform_display_name,
|
||||
slug: rom.platform_slug,
|
||||
id: rom.platform_id,
|
||||
fs_slug: rom.platform_fs_slug,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useRouter } from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
text?: string;
|
||||
subtext?: string;
|
||||
onBack?: () => void;
|
||||
}>();
|
||||
|
||||
@@ -25,11 +26,19 @@ function goBack() {
|
||||
>
|
||||
<span aria-hidden="true" class="text-lg leading-none">←</span>
|
||||
</button>
|
||||
<span
|
||||
v-if="text"
|
||||
class="ml-3 text-white/90 text-3xl font-bold drop-shadow select-none"
|
||||
>
|
||||
{{ text }}
|
||||
</span>
|
||||
<div class="ml-3">
|
||||
<span
|
||||
v-if="text"
|
||||
class="text-white/90 text-3xl font-bold drop-shadow select-none"
|
||||
>
|
||||
{{ text }}
|
||||
</span>
|
||||
<span
|
||||
v-if="subtext"
|
||||
class="text-white/50 text-sm font-normal drop-shadow select-none"
|
||||
>
|
||||
{{ subtext }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -574,7 +574,7 @@ onUnmounted(() => {
|
||||
}"
|
||||
>
|
||||
{{
|
||||
rom.platform_name ||
|
||||
rom.platform_display_name ||
|
||||
(rom.platform_slug || "RETRO")?.toString().toUpperCase()
|
||||
}}
|
||||
</span>
|
||||
|
||||
@@ -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,25 +124,34 @@ 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],
|
||||
);
|
||||
const headerSubtext = computed(() => {
|
||||
if (isCollectionRoute) {
|
||||
return currentCollection.value?.description || "";
|
||||
}
|
||||
if (isSmartCollectionRoute) {
|
||||
return currentSmartCollection.value?.description || "";
|
||||
}
|
||||
if (isVirtualCollectionRoute) {
|
||||
return currentVirtualCollection.value?.description || "";
|
||||
}
|
||||
if (!currentPlatform.value?.fs_slug) return "";
|
||||
return config.value.PLATFORMS_VERSIONS[currentPlatform.value.fs_slug];
|
||||
});
|
||||
|
||||
function getCols(): number {
|
||||
if (!gridRef.value) return 4;
|
||||
@@ -176,10 +177,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 +199,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 +246,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 +259,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 +287,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 +304,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 +324,171 @@ 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) {
|
||||
if (platforms.some((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();
|
||||
selectedIndex.value = consoleStore.getPlatformGameIndex(
|
||||
platform.id,
|
||||
);
|
||||
romsStore.setCurrentPlatform(platform);
|
||||
document.title = `${platform.display_name}`;
|
||||
await fetchRoms();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }, // Ensure watcher is triggered immediately
|
||||
);
|
||||
|
||||
watch(
|
||||
() => allCollections.value,
|
||||
async (collections) => {
|
||||
if (collections.length > 0) {
|
||||
if (
|
||||
collections.some((collection) => collection.id === routeCollectionId)
|
||||
) {
|
||||
const collection = collections.find(
|
||||
(collection) => collection.id === routeCollectionId,
|
||||
);
|
||||
|
||||
// Check if the current collection is different or no ROMs have been loaded
|
||||
if (
|
||||
(currentCollection.value?.id !== routeCollectionId ||
|
||||
allRoms.value.length === 0) &&
|
||||
collection
|
||||
) {
|
||||
if (currentCollection.value) resetGallery();
|
||||
selectedIndex.value = consoleStore.getCollectionGameIndex(
|
||||
collection.id,
|
||||
);
|
||||
romsStore.setCurrentCollection(collection);
|
||||
document.title = `${collection.name}`;
|
||||
await fetchRoms();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }, // Ensure watcher is triggered immediately
|
||||
);
|
||||
|
||||
watch(
|
||||
() => smartCollections.value,
|
||||
async (smartCollections) => {
|
||||
if (smartCollections.length > 0) {
|
||||
if (
|
||||
smartCollections.some(
|
||||
(smartCollection) => smartCollection.id === routeSmartCollectionId,
|
||||
)
|
||||
) {
|
||||
const smartCollection = smartCollections.find(
|
||||
(smartCollection) => smartCollection.id === routeSmartCollectionId,
|
||||
);
|
||||
|
||||
// Check if the current smartCollection is different or no ROMs have been loaded
|
||||
if (
|
||||
(currentSmartCollection.value?.id !== routeSmartCollectionId ||
|
||||
allRoms.value.length === 0) &&
|
||||
smartCollection
|
||||
) {
|
||||
if (currentSmartCollection.value) resetGallery();
|
||||
selectedIndex.value = consoleStore.getSmartCollectionGameIndex(
|
||||
smartCollection.id,
|
||||
);
|
||||
romsStore.setCurrentSmartCollection(smartCollection);
|
||||
document.title = `${smartCollection.name}`;
|
||||
await fetchRoms();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }, // Ensure watcher is triggered immediately
|
||||
);
|
||||
|
||||
watch(
|
||||
() => virtualCollections.value,
|
||||
async (virtualCollections) => {
|
||||
if (virtualCollections.length > 0) {
|
||||
if (
|
||||
virtualCollections.some(
|
||||
(virtualCollection) =>
|
||||
virtualCollection.id === routeVirtualCollectionId,
|
||||
)
|
||||
) {
|
||||
const virtualCollection = virtualCollections.find(
|
||||
(virtualCollection) =>
|
||||
virtualCollection.id === routeVirtualCollectionId,
|
||||
);
|
||||
|
||||
// Check if the current virtualCollection is different or no ROMs have been loaded
|
||||
if (
|
||||
(currentVirtualCollection.value?.id !== routeVirtualCollectionId ||
|
||||
allRoms.value.length === 0) &&
|
||||
virtualCollection
|
||||
) {
|
||||
if (currentVirtualCollection.value) resetGallery();
|
||||
selectedIndex.value = consoleStore.getVirtualCollectionGameIndex(
|
||||
virtualCollection.id,
|
||||
);
|
||||
romsStore.setCurrentVirtualCollection(virtualCollection);
|
||||
document.title = `${virtualCollection.name}`;
|
||||
await fetchRoms();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }, // Ensure watcher is triggered immediately
|
||||
);
|
||||
|
||||
off = subscribe(handleAction);
|
||||
});
|
||||
|
||||
@@ -405,27 +516,27 @@ function handleItemDeselected() {
|
||||
class="relative min-h-screen overflow-y-auto overflow-x-hidden max-w-[100vw] flex"
|
||||
@wheel.prevent
|
||||
>
|
||||
<BackButton :text="headerTitle" :on-back="navigateBack" />
|
||||
<BackButton
|
||||
:text="headerTitle"
|
||||
:subtext="headerSubtext"
|
||||
:on-back="navigateBack"
|
||||
/>
|
||||
<div
|
||||
class="relative flex-1 min-w-0 pr-[40px]"
|
||||
: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 +545,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"
|
||||
|
||||
@@ -9,10 +9,6 @@ import {
|
||||
useTemplateRef,
|
||||
} 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";
|
||||
@@ -31,20 +27,23 @@ 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 { recentRoms } = storeToRefs(romsStore);
|
||||
const consoleStore = storeConsole();
|
||||
const {
|
||||
navigationMode,
|
||||
platformIndex,
|
||||
@@ -59,12 +58,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);
|
||||
|
||||
@@ -104,8 +97,8 @@ 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,
|
||||
@@ -115,8 +108,8 @@ const { moveLeft: moveRecentLeft, moveRight: moveRecentRight } = useSpatialNav(
|
||||
const { moveLeft: moveCollectionLeft, moveRight: moveCollectionRight } =
|
||||
useSpatialNav(
|
||||
collectionsIndex,
|
||||
() => collections.value.length || 1,
|
||||
() => collections.value.length,
|
||||
() => allCollections.value.length || 1,
|
||||
() => allCollections.value.length,
|
||||
);
|
||||
const {
|
||||
moveLeft: moveSmartCollectionLeft,
|
||||
@@ -219,7 +212,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,10 +223,10 @@ 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;
|
||||
},
|
||||
@@ -268,7 +261,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 +272,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;
|
||||
},
|
||||
@@ -526,7 +519,7 @@ function handleAction(action: InputAction): boolean {
|
||||
}
|
||||
if (currentMode === "smartCollections") {
|
||||
navigationMode.value =
|
||||
collections.value.length > 0
|
||||
allCollections.value.length > 0
|
||||
? "collections"
|
||||
: recentRoms.value.length > 0
|
||||
? "recent"
|
||||
@@ -538,7 +531,7 @@ 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"
|
||||
@@ -553,7 +546,7 @@ function handleAction(action: InputAction): boolean {
|
||||
navigationMode.value =
|
||||
recentRoms.value.length > 0
|
||||
? "recent"
|
||||
: collections.value.length > 0
|
||||
: allCollections.value.length > 0
|
||||
? "collections"
|
||||
: smartCollections.value.length > 0
|
||||
? "smartCollections"
|
||||
@@ -565,7 +558,7 @@ function handleAction(action: InputAction): boolean {
|
||||
}
|
||||
if (currentMode === "recent") {
|
||||
navigationMode.value =
|
||||
collections.value.length > 0
|
||||
allCollections.value.length > 0
|
||||
? "collections"
|
||||
: smartCollections.value.length > 0
|
||||
? "smartCollections"
|
||||
@@ -625,41 +618,10 @@ function handleAction(action: InputAction): boolean {
|
||||
}
|
||||
|
||||
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 (platformIndex.value >= allPlatforms.value.length) platformIndex.value = 0;
|
||||
if (recentIndex.value >= recentRoms.value.length) recentIndex.value = 0;
|
||||
if (collectionsIndex.value >= collections.value.length)
|
||||
if (collectionsIndex.value >= allCollections.value.length)
|
||||
collectionsIndex.value = 0;
|
||||
if (smartCollectionsIndex.value >= smartCollections.value.length)
|
||||
smartCollectionsIndex.value = 0;
|
||||
@@ -723,7 +685,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="loadingPlatforms"
|
||||
v-if="fetchingPlatforms"
|
||||
class="text-center mt-16"
|
||||
:style="{ color: 'var(--console-loading-text)' }"
|
||||
>
|
||||
@@ -774,7 +736,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"
|
||||
@@ -848,7 +810,7 @@ onUnmounted(() => {
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="collections.length > 0"
|
||||
v-if="allCollections.length > 0"
|
||||
ref="collections-section-ref"
|
||||
class="pb-8"
|
||||
>
|
||||
@@ -888,7 +850,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"
|
||||
|
||||
@@ -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) => {
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user