mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Merge pull request #2581 from rommapp/romm-2414
[ROMM-2414] Refactor 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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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')"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
Reference in New Issue
Block a user