Files
romm/frontend/src/views/Player/EmulatorJS/Base.vue
2024-12-21 12:06:37 -05:00

375 lines
12 KiB
Vue

<script setup lang="ts">
import type { FirmwareSchema, SaveSchema, StateSchema } from "@/__generated__";
import RomListItem from "@/components/common/Game/ListItem.vue";
import firmwareApi from "@/services/api/firmware";
import romApi from "@/services/api/rom";
import storeGalleryView from "@/stores/galleryView";
import type { DetailedRom } from "@/stores/roms";
import { formatBytes, formatTimestamp, getSupportedEJSCores } from "@/utils";
import Player from "@/views/Player/EmulatorJS/Player.vue";
import { isNull } from "lodash";
import { storeToRefs } from "pinia";
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
// Props
const { t } = useI18n();
const route = useRoute();
const galleryViewStore = storeGalleryView();
const { defaultAspectRatioScreenshot } = storeToRefs(galleryViewStore);
const rom = ref<DetailedRom | null>(null);
const firmwareOptions = ref<FirmwareSchema[]>([]);
const biosRef = ref<FirmwareSchema | null>(null);
const saveRef = ref<SaveSchema | null>(null);
const stateRef = ref<StateSchema | null>(null);
const coreRef = ref<string | null>(null);
const supportedCores = ref<string[]>([]);
const gameRunning = ref(false);
const storedFSOP = localStorage.getItem("fullScreenOnPlay");
const fullScreenOnPlay = ref(isNull(storedFSOP) ? true : storedFSOP === "true");
const script = document.createElement("script");
script.src = "/assets/emulatorjs/loader.js";
script.async = true;
// Functions
function onPlay() {
window.EJS_fullscreenOnLoaded = fullScreenOnPlay.value;
document.body.appendChild(script);
gameRunning.value = true;
}
function onFullScreenChange() {
fullScreenOnPlay.value = !fullScreenOnPlay.value;
localStorage.setItem("fullScreenOnPlay", fullScreenOnPlay.value.toString());
}
onMounted(async () => {
const romResponse = await romApi.getRom({
romId: parseInt(route.params.rom as string),
});
rom.value = romResponse.data;
const firmwareResponse = await firmwareApi.getFirmware({
platformId: romResponse.data.platform_id,
});
firmwareOptions.value = firmwareResponse.data;
supportedCores.value = [...getSupportedEJSCores(rom.value.platform_slug)];
// Load stored bios, save, state, and core
const storedSaveID = localStorage.getItem(`player:${rom.value.id}:save_id`);
if (storedSaveID) {
saveRef.value =
rom.value.user_saves?.find((s) => s.id === parseInt(storedSaveID)) ??
null;
}
const storedStateID = localStorage.getItem(`player:${rom.value.id}:state_id`);
if (storedStateID) {
stateRef.value =
rom.value.user_states?.find((s) => s.id === parseInt(storedStateID)) ??
null;
} else if (rom.value.user_states) {
// Otherwise auto select most recent state by last updated date
stateRef.value =
rom.value.user_states?.sort((a, b) =>
b.updated_at.localeCompare(a.updated_at),
)[0] ?? null;
}
const storedBiosID = localStorage.getItem(
`player:${rom.value.platform_slug}:bios_id`,
);
if (storedBiosID) {
biosRef.value =
firmwareOptions.value.find((f) => f.id === parseInt(storedBiosID)) ??
null;
}
const storedCore = localStorage.getItem(
`player:${rom.value.platform_slug}:core`,
);
if (storedCore) {
coreRef.value = storedCore;
} else {
// Otherwise auto select first supported core
coreRef.value = supportedCores.value[0];
}
});
</script>
<template>
<v-row v-if="rom" class="align-center justify-center scroll" no-gutters>
<v-col
v-if="gameRunning"
cols="12"
md="8"
xl="10"
id="game-wrapper"
:style="`aspect-ratio: ${defaultAspectRatioScreenshot}`"
class="bg-primary"
rounded
>
<player
:rom="rom"
:state="stateRef"
:save="saveRef"
:bios="biosRef"
:core="coreRef"
/>
</v-col>
<v-col
cols="12"
:sm="!gameRunning ? 10 : 10"
:md="!gameRunning ? 8 : 4"
:xl="!gameRunning ? 6 : 2"
>
<v-row class="px-3 mt-6" no-gutters>
<v-col>
<v-img
class="mx-auto"
width="250"
src="/assets/emulatorjs/powered_by_emulatorjs.png"
/>
<v-divider class="my-4" />
<rom-list-item :rom="rom" with-filename with-size />
<v-divider class="my-4" />
<v-select
v-if="supportedCores.length > 1"
:disabled="gameRunning"
v-model="coreRef"
class="my-1"
rounded="0"
hide-details
variant="outlined"
clearable
label="Core"
:items="
supportedCores.map((c) => ({
title: c,
value: c,
}))
"
/>
<v-select
v-model="biosRef"
:disabled="gameRunning"
class="my-1"
hide-details
rounded="0"
variant="outlined"
clearable
:label="t('common.firmware')"
:items="
firmwareOptions.map((f) => ({
title: f.file_name,
value: f,
})) ?? []
"
/>
<v-select
v-model="saveRef"
:disabled="gameRunning"
class="my-1"
hide-details
variant="outlined"
clearable
rounded="0"
:label="t('common.save')"
:items="
rom.user_saves?.map((s) => ({
title: s.file_name,
subtitle: `${s.emulator} - ${formatBytes(s.file_size_bytes)}`,
value: s,
})) ?? []
"
>
<template #selection="{ item }">
<v-list-item class="py-4" :title="item.value.file_name ?? ''">
<template #append>
<v-chip size="x-small" class="ml-1" color="orange" label>{{
item.value.emulator
}}</v-chip>
<v-chip size="x-small" class="ml-1" label>
{{ formatTimestamp(item.value.updated_at) }}
</v-chip>
<v-chip size="x-small" class="ml-1" label
>{{ formatBytes(item.value.file_size_bytes) }}
</v-chip>
</template>
</v-list-item>
</template>
<template #item="{ props, item }">
<v-list-item
class="py-4"
v-bind="props"
:title="item.value.file_name ?? ''"
>
<template #append>
<v-chip size="x-small" class="ml-1" color="orange" label>{{
item.value.emulator
}}</v-chip>
<v-chip size="x-small" class="ml-1" label>
{{ formatTimestamp(item.value.updated_at) }}
</v-chip>
<v-chip size="x-small" class="ml-1" label
>{{ formatBytes(item.value.file_size_bytes) }}
</v-chip>
</template>
</v-list-item>
</template>
</v-select>
<v-select
v-model="stateRef"
:disabled="gameRunning"
class="my-1"
hide-details
rounded="0"
variant="outlined"
clearable
:label="t('common.state')"
:items="
rom.user_states?.map((s) => ({
title: s.file_name,
subtitle: `${s.emulator} - ${formatBytes(s.file_size_bytes)}`,
value: s,
})) ?? []
"
>
<template #selection="{ item }">
<v-list-item class="pa-0" :title="item.value.file_name ?? ''">
<template #append>
<v-chip size="x-small" class="ml-1" color="orange" label>{{
item.value.emulator
}}</v-chip>
<v-chip size="x-small" class="ml-1" label
>{{ formatBytes(item.value.file_size_bytes) }}
</v-chip>
<v-chip size="small" class="ml-1" label>
{{ formatTimestamp(item.value.updated_at) }}
</v-chip>
</template>
</v-list-item>
</template>
<template #item="{ props, item }">
<v-list-item
class="py-4"
v-bind="props"
:title="item.value.file_name ?? ''"
>
<template #append>
<v-chip size="x-small" class="ml-1" color="orange" label>{{
item.value.emulator
}}</v-chip>
<v-chip size="x-small" class="ml-1" label
>{{ formatBytes(item.value.file_size_bytes) }}
</v-chip>
<v-chip size="small" class="ml-1" label>
{{ formatTimestamp(item.value.updated_at) }}
</v-chip>
</template>
</v-list-item>
</template>
</v-select>
<!-- <v-select
class="my-1"
hide-details
variant="outlined"
clearable
rounded="0"
disabled
label="Patch"
:items="[
'Advance Wars Balance (AW1) by Kartal',
'War Room Sturm (AW1) by Kartal',
]"
/> -->
</v-col>
</v-row>
<v-row class="px-3 py-3 text-center" no-gutters>
<v-col>
<v-divider class="my-4" />
<v-row class="align-center" no-gutters>
<v-col>
<v-btn
block
size="large"
rounded="0"
@click="onFullScreenChange"
:disabled="gameRunning"
:variant="fullScreenOnPlay ? 'flat' : 'outlined'"
:color="fullScreenOnPlay ? 'romm-accent-1' : ''"
><v-icon class="mr-1">{{
fullScreenOnPlay
? "mdi-checkbox-outline"
: "mdi-checkbox-blank-outline"
}}</v-icon
>{{ t("play.full-screen") }}</v-btn
>
</v-col>
<v-col
cols="12"
:sm="gameRunning ? 12 : 7"
:xl="gameRunning ? 12 : 9"
>
<v-btn
color="romm-accent-1"
block
:disabled="gameRunning"
rounded="0"
variant="outlined"
size="large"
prepend-icon="mdi-play"
@click="onPlay()"
>{{ t("play.play") }}
</v-btn>
</v-col>
</v-row>
<v-btn
class="mt-4"
block
rounded="0"
variant="outlined"
size="large"
prepend-icon="mdi-refresh"
@click="$router.go(0)"
>{{ t("play.reset-session") }}
</v-btn>
<v-btn
class="mt-4"
block
rounded="0"
variant="outlined"
size="large"
prepend-icon="mdi-arrow-left"
@click="
$router.push({
name: 'rom',
params: { rom: rom?.id },
})
"
>{{ t("play.back-to-game-details") }}
</v-btn>
<v-btn
class="mt-4"
block
rounded="0"
variant="outlined"
size="large"
prepend-icon="mdi-arrow-left"
@click="
$router.push({
name: 'platform',
params: { platform: rom?.platform_id },
})
"
>{{ t("play.back-to-gallery") }}
</v-btn>
</v-col>
</v-row>
</v-col>
</v-row>
</template>