Add age ratings to UI + filters

This commit is contained in:
Georges-Antoine Assi
2024-08-26 15:55:45 -04:00
parent e55401fa95
commit c3ea419c68
56 changed files with 427 additions and 126 deletions

View File

@@ -91,6 +91,7 @@ class RomSchema(BaseModel):
collections: list[str]
companies: list[str]
game_modes: list[str]
age_ratings: list[str]
igdb_metadata: RomIGDBMetadata | None
moby_metadata: RomMobyMetadata | None

View File

@@ -63,6 +63,7 @@ def test_update_rom(rename_file_mock, get_rom_by_id_mock, client, access_token,
"expanded_games": "[]",
"ports": "[]",
"similar_games": "[]",
"age_ratings": "[1, 2]",
},
)
assert response.status_code == 200

View File

@@ -39,6 +39,12 @@ class IGDBPlatform(TypedDict):
name: NotRequired[str]
class IGDBAgeRating(TypedDict):
rating: str
category: str
rating_cover_url: str
class IGDBMetadataPlatform(TypedDict):
igdb_id: int
name: str
@@ -62,6 +68,7 @@ class IGDBMetadata(TypedDict):
collections: list[str]
companies: list[str]
game_modes: list[str]
age_ratings: list[IGDBAgeRating]
platforms: list[IGDBMetadataPlatform]
expansions: list[IGDBRelatedGame]
dlcs: list[IGDBRelatedGame]
@@ -101,6 +108,9 @@ def extract_metadata_from_igdb_rom(rom: dict) -> IGDBMetadata:
IGDBMetadataPlatform(igdb_id=p.get("id", ""), name=p.get("name", ""))
for p in rom.get("platforms", [])
],
"age_ratings": [
IGDB_AGE_RATINGS[r["rating"]] for r in rom.get("age_ratings", [])
],
"expansions": [
IGDBRelatedGame(
id=e["id"],
@@ -537,8 +547,8 @@ class IGDBBaseHandler(MetadataHandler):
]
return [
IGDBRom( # type: ignore[misc]
{
IGDBRom(
{ # type: ignore[misc]
k: v
for k, v in {
"igdb_id": rom["id"],
@@ -546,12 +556,12 @@ class IGDBBaseHandler(MetadataHandler):
"name": rom["name"],
"summary": rom.get("summary", ""),
"url_cover": self._normalize_cover_url(
rom.get("cover", {})
.get("url", "")
.replace("t_thumb", "t_cover_big")
pydash.get(rom, "cover.url", "").replace(
"t_thumb", "t_cover_big"
)
),
"url_screenshots": [
self._normalize_cover_url(s.get("url", ""))
self._normalize_cover_url(s.get("url", "")) # type: ignore[arg-type]
for s in rom.get("screenshots", [])
],
"igdb_metadata": extract_metadata_from_igdb_rom(rom),
@@ -674,6 +684,7 @@ GAMES_FIELDS = [
"similar_games.slug",
"similar_games.name",
"similar_games.cover.url",
"age_ratings.rating",
]
SEARCH_FIELDS = ["game.id", "name"]
@@ -902,3 +913,186 @@ IGDB_PLATFORM_LIST = [
{"slug": "vc", "name": "Virtual Console"},
{"slug": "airconsole", "name": "AirConsole"},
]
IGDB_AGE_RATINGS: dict[int, IGDBAgeRating] = {
1: {
"rating": "Three",
"category": "PEGI",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_3.png",
},
2: {
"rating": "Seven",
"category": "PEGI",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_7.png",
},
3: {
"rating": "Twelve",
"category": "PEGI",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_12.png",
},
4: {
"rating": "Sixteen",
"category": "PEGI",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_16.png",
},
5: {
"rating": "Eighteen",
"category": "PEGI",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/pegi/pegi_18.png",
},
6: {
"rating": "RP",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_rp.png",
},
7: {
"rating": "EC",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_ec.png",
},
8: {
"rating": "E",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_e.png",
},
9: {
"rating": "E10",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_e10.png",
},
10: {
"rating": "T",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_t.png",
},
11: {
"rating": "M",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_m.png",
},
12: {
"rating": "AO",
"category": "ESRB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/esrb/esrb_ao.png",
},
13: {
"rating": "CERO_A",
"category": "CERO",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_a.png",
},
14: {
"rating": "CERO_B",
"category": "CERO",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_b.png",
},
15: {
"rating": "CERO_C",
"category": "CERO",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_c.png",
},
16: {
"rating": "CERO_D",
"category": "CERO",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_d.png",
},
17: {
"rating": "CERO_Z",
"category": "CERO",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/cero/cero_z.png",
},
18: {
"rating": "USK_0",
"category": "USK",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_0.png",
},
19: {
"rating": "USK_6",
"category": "USK",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_6.png",
},
20: {
"rating": "USK_12",
"category": "USK",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_12.png",
},
21: {
"rating": "USK_16",
"category": "USK",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_16.png",
},
22: {
"rating": "USK_18",
"category": "USK",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/usk/usk_18.png",
},
23: {
"rating": "GRAC_ALL",
"category": "GRAC",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_all.png",
},
24: {
"rating": "GRAC_Twelve",
"category": "GRAC",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_twelve.png",
},
25: {
"rating": "GRAC_Fifteen",
"category": "GRAC",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_fifteen.png",
},
26: {
"rating": "GRAC_Eighteen",
"category": "GRAC",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_eighteen.png",
},
27: {
"rating": "GRAC_TESTING",
"category": "GRAC",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/grac/grac_testing.png",
},
28: {
"rating": "CLASS_IND_L",
"category": "CLASS_IND",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/classind/classind_l.png",
},
29: {
"rating": "CLASS_IND_Ten",
"category": "CLASS_IND",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/classind/classind_ten.png",
},
30: {
"rating": "CLASS_IND_Twelve",
"category": "CLASS_IND",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/classind/classind_twelve.png",
},
31: {
"rating": "ACB_G",
"category": "ACB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_g.png",
},
32: {
"rating": "ACB_PG",
"category": "ACB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_pg.png",
},
33: {
"rating": "ACB_M",
"category": "ACB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_m.png",
},
34: {
"rating": "ACB_MA15",
"category": "ACB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_ma15.png",
},
35: {
"rating": "ACB_R18",
"category": "ACB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_r18.png",
},
36: {
"rating": "ACB_RC",
"category": "ACB",
"rating_cover_url": "https://www.igdb.com/icons/rating_icons/acb/acb_rc.png",
},
}

View File

@@ -162,6 +162,10 @@ class Rom(BaseModel):
def game_modes(self) -> list[str]:
return self.igdb_metadata.get("game_modes", [])
@property
def age_ratings(self) -> list[str]:
return [r["rating"] for r in self.igdb_metadata.get("age_ratings", [])]
@property
def fs_resources_path(self) -> str:
return f"roms/{str(self.platform_id)}/{str(self.id)}"

View File

@@ -123,6 +123,7 @@
}
}
],
"age_ratings": [{ "rating": 1 }, { "rating": 2 }],
"expansions": [
{
"id": 239930,

View File

@@ -19,6 +19,7 @@ export type { DetailedRomSchema } from './models/DetailedRomSchema';
export type { FirmwareSchema } from './models/FirmwareSchema';
export type { HeartbeatResponse } from './models/HeartbeatResponse';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { IGDBAgeRating } from './models/IGDBAgeRating';
export type { IGDBMetadataPlatform } from './models/IGDBMetadataPlatform';
export type { IGDBRelatedGame } from './models/IGDBRelatedGame';
export type { MessageResponse } from './models/MessageResponse';

View File

@@ -38,6 +38,7 @@ export type DetailedRomSchema = {
collections: Array<string>;
companies: Array<string>;
game_modes: Array<string>;
age_ratings: Array<string>;
igdb_metadata: (RomIGDBMetadata | null);
moby_metadata: (RomMobyMetadata | null);
path_cover_s: (string | null);

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type IGDBAgeRating = {
rating: string;
category: string;
rating_cover_url: string;
};

View File

@@ -3,6 +3,7 @@
/* tslint:disable */
/* eslint-disable */
import type { IGDBAgeRating } from './IGDBAgeRating';
import type { IGDBMetadataPlatform } from './IGDBMetadataPlatform';
import type { IGDBRelatedGame } from './IGDBRelatedGame';
@@ -16,6 +17,7 @@ export type RomIGDBMetadata = {
collections?: Array<string>;
companies?: Array<string>;
game_modes?: Array<string>;
age_ratings?: Array<IGDBAgeRating>;
platforms?: Array<IGDBMetadataPlatform>;
expansions?: Array<IGDBRelatedGame>;
dlcs?: Array<IGDBRelatedGame>;

View File

@@ -31,6 +31,7 @@ export type RomSchema = {
collections: Array<string>;
companies: Array<string>;
game_modes: Array<string>;
age_ratings: Array<string>;
igdb_metadata: (RomIGDBMetadata | null);
moby_metadata: (RomMobyMetadata | null);
path_cover_s: (string | null);

View File

@@ -33,6 +33,7 @@ export type SimpleRomSchema = {
collections: Array<string>;
companies: Array<string>;
game_modes: Array<string>;
age_ratings: Array<string>;
igdb_metadata: (RomIGDBMetadata | null);
moby_metadata: (RomMobyMetadata | null);
path_cover_s: (string | null);

View File

@@ -22,7 +22,6 @@ emitter?.on("showCreateUserDialog", () => {
show.value = true;
});
// Functions
async function createUser() {
await userApi
.createUser(user.value)

View File

@@ -22,7 +22,6 @@ emitter?.on("showEditUserDialog", (userToEdit) => {
show.value = true;
});
// Functions
function triggerFileInput() {
const fileInput = document.getElementById("file-input");
fileInput?.click();
@@ -138,8 +137,8 @@ function closeDialog() {
imagePreviewUrl
? imagePreviewUrl
: user.avatar_path
? `/assets/romm/assets/${user.avatar_path}?ts=${user.updated_at}`
: defaultAvatarPath
? `/assets/romm/assets/${user.avatar_path}?ts=${user.updated_at}`
: defaultAvatarPath
"
>
<v-fade-transition>

View File

@@ -58,7 +58,6 @@ const usersPerPage = ref(isNaN(storedUsersPerPage) ? 25 : storedUsersPerPage);
const pageCount = ref(0);
emitter?.on("updateDataTablePages", updateDataTablePages);
// Functions
function updateDataTablePages() {
pageCount.value = Math.ceil(usersStore.allUsers.length / usersPerPage.value);
}

View File

@@ -4,7 +4,11 @@ import romApi from "@/services/api/rom";
import storeDownload from "@/stores/download";
import type { DetailedRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { getDownloadLink, isEJSEmulationSupported, isRuffleEmulationSupported } from "@/utils";
import {
getDownloadLink,
isEJSEmulationSupported,
isRuffleEmulationSupported,
} from "@/utils";
import type { Emitter } from "mitt";
import { inject, ref } from "vue";
@@ -14,7 +18,9 @@ const downloadStore = storeDownload();
const emitter = inject<Emitter<Events>>("emitter");
const playInfoIcon = ref("mdi-play");
const ejsEmulationSupported = isEJSEmulationSupported(props.rom.platform_slug);
const ruffleEmulationSupported = isRuffleEmulationSupported(props.rom.platform_slug);
const ruffleEmulationSupported = isRuffleEmulationSupported(
props.rom.platform_slug,
);
// Functions
async function copyDownloadLink(rom: DetailedRom) {
@@ -26,7 +32,7 @@ async function copyDownloadLink(rom: DetailedRom) {
getDownloadLink({
rom,
files: downloadStore.filesToDownload,
})
}),
);
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(downloadLink);

View File

@@ -23,7 +23,7 @@ const romUser = ref(
note_raw_markdown: "",
note_is_public: false,
is_main_sibling: false,
}
},
);
// Functions
@@ -53,7 +53,7 @@ watch(
note_is_public: false,
is_main_sibling: false,
};
}
},
);
</script>
<template>
@@ -117,7 +117,7 @@ watch(
v-model="downloadStore.filesToDownload"
:label="rom.file_name"
item-title="file_name"
:items="rom.files.map(f => f.filename)"
:items="rom.files.map((f) => f.filename)"
rounded="0"
density="compact"
variant="outlined"
@@ -137,13 +137,28 @@ watch(
<v-chip size="small" label class="mx-1 my-1">
Size: {{ formatBytes(rom.file_size_bytes) }}
</v-chip>
<v-chip v-if="!rom.multi && rom.sha1_hash" size="small" label class="mx-1 my-1">
<v-chip
v-if="!rom.multi && rom.sha1_hash"
size="small"
label
class="mx-1 my-1"
>
SHA-1: {{ rom.sha1_hash }}
</v-chip>
<v-chip v-if="!rom.multi && rom.md5_hash" size="small" label class="mx-1 my-1">
MD5: {{ rom.md5_hash }}
<v-chip
v-if="!rom.multi && rom.md5_hash"
size="small"
label
class="mx-1 my-1"
>
MD5: {{ rom.md5_hash }}
</v-chip>
<v-chip v-if="!rom.multi && rom.crc_hash" size="small" label class="mx-1 my-1">
<v-chip
v-if="!rom.multi && rom.crc_hash"
size="small"
label
class="mx-1 my-1"
>
CRC: {{ rom.crc_hash }}
</v-chip>
</v-col>
@@ -180,7 +195,7 @@ watch(
<v-col>
<v-chip
v-for="collection in collectionsWithoutFavourites(
rom.user_collections
rom.user_collections,
)"
:to="{ name: 'collection', params: { collection: collection.id } }"
size="large"

View File

@@ -23,7 +23,12 @@ function onFilterClick(filter: FilterType, value: string) {
<v-row no-gutters>
<v-col>
<v-divider class="mx-2 my-4" />
<template v-for="filter in galleryFilterStore.filters" :key="filter">
<template
v-for="filter in galleryFilterStore.filters.filter(
(f) => f !== 'age_ratings',
)"
:key="filter"
>
<v-row
v-if="rom[filter].length > 0"
class="align-center my-3"
@@ -47,6 +52,30 @@ function onFilterClick(filter: FilterType, value: string) {
</v-col>
</v-row>
</template>
<!-- Manually add age ratings to display logos -->
<template
v-if="
rom.igdb_metadata?.age_ratings &&
rom.igdb_metadata.age_ratings.length > 0
"
>
<v-row no-gutters class="mt-5">
<v-col cols="3" xl="2" class="text-capitalize">
<span>Age Rating</span>
</v-col>
<div class="d-flex">
<v-img
v-for="value in rom.igdb_metadata.age_ratings"
:key="value.rating"
@click="onFilterClick('age_ratings', value.rating)"
:src="value.rating_cover_url"
height="50"
width="50"
class="mr-4 cursor-pointer"
/>
</div>
</v-row>
</template>
<template v-if="rom.summary != ''">
<v-divider class="mx-2 my-4" />
<v-row no-gutters>

View File

@@ -21,12 +21,11 @@ const romUser = ref(
note_raw_markdown: "",
note_is_public: false,
is_main_sibling: false,
}
},
);
const publicNotes =
props.rom.user_notes?.filter((note) => note.user_id !== auth.user?.id) ?? [];
// Functions
function togglePublic() {
romUser.value.note_is_public = !romUser.value.note_is_public;
romApi.updateUserRomProps({
@@ -61,7 +60,7 @@ watch(
note_is_public: false,
is_main_sibling: false,
};
}
},
);
</script>
<template>
@@ -131,11 +130,7 @@ watch(
</v-card-text>
</v-card>
<v-card
rounded="0"
v-if="publicNotes && publicNotes.length > 0"
class="mt-2"
>
<v-card rounded="0" v-if="publicNotes && publicNotes.length > 0" class="mt-2">
<v-card-title class="bg-terciary">
<v-list-item class="pl-2 pr-0">
<span class="text-h6">Public notes</span>

View File

@@ -60,7 +60,7 @@ async function downloasSaves() {
function updateDataTablePages() {
if (props.rom.user_saves) {
pageCount.value = Math.ceil(
props.rom.user_saves.length / itemsPerPage.value
props.rom.user_saves.length / itemsPerPage.value,
);
}
}

View File

@@ -61,7 +61,7 @@ async function downloasStates() {
function updateDataTablePages() {
if (props.rom.user_states) {
pageCount.value = Math.ceil(
props.rom.user_states.length / itemsPerPage.value
props.rom.user_states.length / itemsPerPage.value,
);
}
}

View File

@@ -53,7 +53,7 @@ function deleteSelectedFirmware() {
function updateDataTablePages() {
pageCount.value = Math.ceil(
Number(currentPlatform.value?.firmware?.length) / itemsPerPage.value
Number(currentPlatform.value?.firmware?.length) / itemsPerPage.value,
);
}

View File

@@ -11,10 +11,10 @@ import { inject, nextTick, ref } from "vue";
import { useDisplay } from "vuetify";
// Props
const show = ref(false);
const { xs } = useDisplay();
const emitter = inject<Emitter<Events>>("emitter");
const galleryFilterStore = storeGalleryFilter();
const {
activeFilterDrawer,
selectedGenre,
@@ -25,7 +25,10 @@ const {
filterCollections,
selectedCompany,
filterCompanies,
selectedAgeRating,
filterAgeRatings,
} = storeToRefs(galleryFilterStore);
const filters = [
{
label: "Genre",
@@ -47,6 +50,11 @@ const filters = [
selected: selectedCompany,
items: filterCompanies,
},
{
label: "Age Rating",
selected: selectedAgeRating,
items: filterAgeRatings,
},
];
// Functions
@@ -55,6 +63,7 @@ function resetFilters() {
selectedFranchise.value = null;
selectedCollection.value = null;
selectedCompany.value = null;
selectedAgeRating.value = null;
galleryFilterStore.disableFilterUnmatched();
galleryFilterStore.disableFilterFavourites();
nextTick(() => emitter?.emit("filter", null));

View File

@@ -11,7 +11,6 @@ const emitter = inject<Emitter<Events>>("emitter");
const galleryFilterStore = storeGalleryFilter();
const { filterSearch } = storeToRefs(galleryFilterStore);
// Functions
const filterRoms = debounce(() => {
emitter?.emit("filter", null);
}, 500);

View File

@@ -69,7 +69,7 @@ function resetSelection() {
async function addToFavourites() {
if (!favCollection.value) return;
favCollection.value.roms = favCollection.value.roms.concat(
selectedRoms.value.map((r) => r.id)
selectedRoms.value.map((r) => r.id),
);
await collectionApi
.updateCollection({ collection: favCollection.value as Collection })
@@ -98,7 +98,7 @@ async function addToFavourites() {
async function removeFromFavourites() {
if (!favCollection.value) return;
favCollection.value.roms = favCollection.value.roms.filter(
(value) => !selectedRoms.value.map((r) => r.id).includes(value)
(value) => !selectedRoms.value.map((r) => r.id).includes(value),
);
if (romsStore.currentCollection?.name.toLowerCase() == "favourites") {
romsStore.remove(selectedRoms.value);
@@ -210,7 +210,7 @@ function onDownload() {
? emitter?.emit('showAddToCollectionDialog', romsStore.selectedRoms)
: emitter?.emit(
'showRemoveFromCollectionDialog',
romsStore.selectedRoms
romsStore.selectedRoms,
)
"
/>
@@ -259,6 +259,6 @@ function onDownload() {
pointer-events: none;
}
.sticky-bottom * {
pointer-events: auto; /* Re-enables pointer events for all child elements */
pointer-events: auto; /* Re-enables pointer events for all child elements */
}
</style>

View File

@@ -42,13 +42,12 @@ emitter?.on(
});
fsSlugToCreate.value = fsSlug;
selectedPlatform.value = supportedPlatforms.value?.find(
(platform) => platform.slug == slug
(platform) => platform.slug == slug,
);
show.value = true;
}
},
);
// Functions
function addBindPlatform() {
if (!selectedPlatform.value) return;
configApi
@@ -60,7 +59,7 @@ function addBindPlatform() {
if (selectedPlatform.value) {
configStore.addPlatformBinding(
fsSlugToCreate.value,
selectedPlatform.value.slug
selectedPlatform.value.slug,
);
}
})

View File

@@ -42,10 +42,10 @@ emitter?.on(
});
fsSlugToCreate.value = fsSlug;
selectedPlatform.value = supportedPlatforms.value?.find(
(platform) => platform.slug == slug
(platform) => platform.slug == slug,
);
show.value = true;
}
},
);
// Functions
@@ -60,7 +60,7 @@ function addVersionPlatform() {
if (selectedPlatform.value) {
configStore.addPlatformBinding(
fsSlugToCreate.value,
selectedPlatform.value.slug
selectedPlatform.value.slug,
);
}
})

View File

@@ -21,7 +21,6 @@ emitter?.on("showDeletePlatformBindingDialog", ({ fsSlug, slug }) => {
show.value = true;
});
// Functions
function deleteBindPlatform() {
configApi
.deletePlatformBindConfig({ fsSlug: platformBindingFSSlugToDelete.value })
@@ -74,10 +73,7 @@ function closeDialog() {
<v-row class="justify-center mb-2" no-gutters>
<v-btn-group divided density="compact">
<v-btn class="bg-terciary" @click="closeDialog"> Cancel </v-btn>
<v-btn
class="bg-terciary text-romm-red"
@click="deleteBindPlatform"
>
<v-btn class="bg-terciary text-romm-red" @click="deleteBindPlatform">
Confirm
</v-btn>
</v-btn-group>

View File

@@ -37,7 +37,6 @@ const itemsPerPage = ref(10);
const pageCount = ref(0);
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Functions
async function addRomsToCollection() {
if (!selectedCollection.value) return;
selectedCollection.value.roms.push(...roms.value.map((r) => r.id));

View File

@@ -30,7 +30,6 @@ emitter?.on("updateUrlCover", (url_cover) => {
imagePreviewUrl.value = url_cover;
});
// Functions
function triggerFileInput() {
const fileInput = document.getElementById("file-input");
fileInput?.click();
@@ -146,13 +145,15 @@ function closeDialog() {
<template #append-inner>
<v-btn-group rounded="0" divided density="compact">
<v-btn
:disabled="!heartbeat.value.METADATA_SOURCES?.STEAMGRIDDB_ENABLED"
:disabled="
!heartbeat.value.METADATA_SOURCES?.STEAMGRIDDB_ENABLED
"
size="small"
class="translucent-dark"
@click="
emitter?.emit(
'showSearchCoverDialog',
collection.name as string
collection.name as string,
)
"
>

View File

@@ -30,7 +30,6 @@ emitter?.on("updateUrlCover", (url_cover) => {
imagePreviewUrl.value = url_cover;
});
// Functions
function triggerFileInput() {
const fileInput = document.getElementById("file-input");
fileInput?.click();
@@ -134,7 +133,11 @@ function closeDialog() {
<v-col>
<v-switch
v-model="collection.is_public"
:label="collection.is_public ? 'Public (visible to everyone)' : 'Private (only visible to me)'"
:label="
collection.is_public
? 'Public (visible to everyone)'
: 'Private (only visible to me)'
"
color="romm-accent-1"
class="px-2"
hide-details
@@ -155,13 +158,15 @@ function closeDialog() {
<template #append-inner>
<v-btn-group rounded="0" divided density="compact">
<v-btn
:disabled="!heartbeat.value.METADATA_SOURCES?.STEAMGRIDDB_ENABLED"
:disabled="
!heartbeat.value.METADATA_SOURCES?.STEAMGRIDDB_ENABLED
"
size="small"
class="translucent-dark"
@click="
emitter?.emit(
'showSearchCoverDialog',
collection.name as string
collection.name as string,
)
"
>

View File

@@ -36,11 +36,10 @@ const itemsPerPage = ref(10);
const pageCount = ref(0);
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Functions
async function removeRomsFromCollection() {
if (!selectedCollection.value) return;
selectedCollection.value.roms = selectedCollection.value.roms.filter(
(id) => !roms.value.map((r) => r.id).includes(id)
(id) => !roms.value.map((r) => r.id).includes(id),
);
await collectionApi
.updateCollection({ collection: selectedCollection.value })

View File

@@ -21,7 +21,6 @@ const collectionsStore = storeCollections();
const romsStore = storeRoms();
const { favCollection } = storeToRefs(collectionsStore);
// Functions
async function switchFromFavourites() {
if (!favCollection.value) {
await collectionApi
@@ -53,7 +52,7 @@ async function switchFromFavourites() {
} else {
if (favCollection.value) {
favCollection.value.roms = favCollection.value.roms.filter(
(id) => id !== props.rom.id
(id) => id !== props.rom.id,
);
if (romsStore.currentCollection?.name.toLowerCase() == "favourites") {
romsStore.remove([props.rom]);

View File

@@ -34,7 +34,6 @@ const itemsPerPage = ref(10);
const pageCount = ref(0);
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Functions
function triggerFileInput() {
const fileInput = document.getElementById("file-input");
fileInput?.click();

View File

@@ -34,7 +34,6 @@ const itemsPerPage = ref(10);
const pageCount = ref(0);
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Functions
function triggerFileInput() {
const fileInput = document.getElementById("file-input");
fileInput?.click();

View File

@@ -15,7 +15,6 @@ emitter?.on("showCopyDownloadLinkDialog", (downloadLink) => {
link.value = downloadLink;
});
// Functions
function closeDialog() {
show.value = false;
link.value = "";

View File

@@ -40,7 +40,6 @@ const itemsPerPage = ref(10);
const pageCount = ref(0);
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Functions
async function deleteRoms() {
await romApi
.deleteRoms({ roms: roms.value, deleteFromFs: romsToDeleteFromFs.value })

View File

@@ -135,7 +135,7 @@ function confirm() {
updateRom(
Object.assign(selectedMatchRom.value, {
url_cover: selectedCover.value.url_cover,
})
}),
);
closeDialog();
}

View File

@@ -36,14 +36,13 @@ emitter?.on("showSearchRomDialog", () => {
show.value = true;
});
// Functions
async function filterRoms() {
if (!selectedPlatform.value) {
filteredRoms.value = searchedRoms.value as SimpleRom[];
} else {
filteredRoms.value = searchedRoms.value.filter(
(rom: { platform_name: string }) =>
rom.platform_name == selectedPlatform.value?.platform_name
rom.platform_name == selectedPlatform.value?.platform_name,
) as SimpleRom[];
}
}
@@ -73,7 +72,7 @@ async function searchRoms() {
platform_name: rom.platform_name,
platform_slug: rom.platform_slug,
},
])
]),
).values(),
];
filterRoms();

View File

@@ -62,7 +62,6 @@ emitter?.on("showUploadRomDialog", (platformWhereUpload) => {
});
});
// Functions
async function uploadRoms() {
if (!selectedPlatform.value) return;
show.value = false;
@@ -104,7 +103,7 @@ async function uploadRoms() {
uploadStore.clear();
const successfulUploads = responses.filter(
(d) => d.status == "fulfilled"
(d) => d.status == "fulfilled",
);
const failedUploads = responses.filter((d) => d.status == "rejected");
@@ -156,7 +155,7 @@ function triggerFileInput() {
function removeRomFromList(romName: string) {
filesToUpload.value = filesToUpload.value.filter(
(rom) => rom.name !== romName
(rom) => rom.name !== romName,
);
}

View File

@@ -49,7 +49,7 @@ async function switchFromFavourites() {
} else {
if (favCollection.value) {
favCollection.value.roms = favCollection.value.roms.filter(
(id) => id !== props.rom.id
(id) => id !== props.rom.id,
);
if (romsStore.currentCollection?.name.toLowerCase() == "favourites") {
romsStore.remove([props.rom]);

View File

@@ -75,14 +75,13 @@ const HEADERS = [
{ title: "", align: "end", key: "actions", sortable: false },
] as const;
// Functions
function rowClick(_: Event, row: { item: SimpleRom }) {
router.push({ name: "rom", params: { rom: row.item.id } });
}
function updateDataTablePages() {
pageCount.value = Math.ceil(
romsStore.filteredRoms.length / itemsPerPage.value
romsStore.filteredRoms.length / itemsPerPage.value,
);
}
@@ -137,7 +136,11 @@ onMounted(() => {
>
<template #append>
<v-chip
v-if="item.sibling_roms && item.sibling_roms.length > 0 && showSiblings"
v-if="
item.sibling_roms &&
item.sibling_roms.length > 0 &&
showSiblings
"
class="translucent-dark ml-2"
size="x-small"
>

View File

@@ -16,7 +16,6 @@ const { filteredCollections, searchText } = storeToRefs(collectionsStore);
const { activeCollectionsDrawer } = storeToRefs(navigationStore);
const emitter = inject<Emitter<Events>>("emitter");
// Functions
async function addCollection() {
emitter?.emit("showCreateCollectionDialog", null);
}

View File

@@ -12,7 +12,6 @@ const platformsStore = storePlatforms();
const { filteredPlatforms, searchText } = storeToRefs(platformsStore);
const { activePlatformsDrawer } = storeToRefs(navigationStore);
// Functions
function clear() {
searchText.value = "";
}

View File

@@ -11,14 +11,13 @@ const { VERSION } = heartbeat.value;
const GITHUB_VERSION = ref(VERSION);
const latestVersionDismissed = ref(VERSION === "development");
// Functions
function dismissVersionBanner() {
localStorage.setItem("dismissedVersion", GITHUB_VERSION.value);
latestVersionDismissed.value = true;
}
onMounted(async () => {
const response = await fetch(
"https://api.github.com/repos/rommapp/romm/releases/latest"
"https://api.github.com/repos/rommapp/romm/releases/latest",
);
const json = await response.json();
GITHUB_VERSION.value = json.tag_name;
@@ -72,6 +71,6 @@ onMounted(async () => {
pointer-events: none;
}
.sticky-bottom * {
pointer-events: auto; /* Re-enables pointer events for all child elements */
pointer-events: auto; /* Re-enables pointer events for all child elements */
}
</style>

View File

@@ -35,7 +35,6 @@ const itemsPerPage = ref(10);
const pageCount = ref(0);
const PER_PAGE_OPTIONS = [10, 25, 50, 100];
// Functions
function triggerFileInput() {
const fileInput = document.getElementById("file-input");
fileInput?.click();

View File

@@ -29,7 +29,7 @@ withDefaults(
icon: null,
width: "",
height: "",
}
},
);
const emit = defineEmits(["update:modelValue", "close"]);
const hasToolbarSlot = ref(false);
@@ -37,7 +37,6 @@ const hasPrependSlot = ref(false);
const hasAppendSlot = ref(false);
const hasFooterSlot = ref(false);
// Functions
function closeDialog() {
emit("update:modelValue", false);
emit("close");

View File

@@ -23,7 +23,6 @@ emitter?.on("showSearchCoverDialog", (term) => {
if (searchTerm.value) searchCovers();
});
// Functions
async function searchCovers() {
covers.value = [];
@@ -47,7 +46,7 @@ async function searchCovers() {
coverType.value === "all"
? game.resources
: game.resources.filter(
(resource) => resource.type === coverType.value
(resource) => resource.type === coverType.value,
),
};
})
@@ -81,7 +80,7 @@ function filterCovers() {
coverType.value === "all"
? game.resources
: game.resources.filter(
(resource) => resource.type === coverType.value
(resource) => resource.type === coverType.value,
),
};
})

View File

@@ -8,7 +8,7 @@ import { useRouter } from "vue-router";
// Props
const romsStore = storeRoms();
const { recentRoms } = storeToRefs(romsStore)
const { recentRoms } = storeToRefs(romsStore);
const router = useRouter();
// Functions

View File

@@ -1,7 +1,13 @@
import { normalizeString } from "@/utils";
import { defineStore } from "pinia";
const filters = ["genres", "franchises", "collections", "companies"] as const;
const filters = [
"genres",
"franchises",
"collections",
"companies",
"age_ratings",
] as const;
export type FilterType = (typeof filters)[number];
@@ -14,6 +20,7 @@ export default defineStore("galleryFilter", {
filterFranchises: [] as string[],
filterCollections: [] as string[],
filterCompanies: [] as string[],
filterAgeRatings: [] as string[],
filterUnmatched: false,
filterFavourites: false,
filterDuplicates: false,
@@ -21,6 +28,7 @@ export default defineStore("galleryFilter", {
selectedFranchise: null as string | null,
selectedCollection: null as string | null,
selectedCompany: null as string | null,
selectedAgeRating: null as string | null,
}),
actions: {
@@ -42,6 +50,9 @@ export default defineStore("galleryFilter", {
setFilterCompanies(companies: string[]) {
this.filterCompanies = companies;
},
setFilterAgeRatings(ageRatings: string[]) {
this.filterAgeRatings = ageRatings;
},
setSelectedFilterGenre(genre: string) {
this.selectedGenre = genre;
},
@@ -54,6 +65,9 @@ export default defineStore("galleryFilter", {
setSelectedFilterCompany(company: string) {
this.selectedCompany = company;
},
setSelectedFilterAgeRating(ageRating: string) {
this.selectedAgeRating = ageRating;
},
switchFilterUnmatched() {
this.filterUnmatched = !this.filterUnmatched;
},
@@ -81,7 +95,8 @@ export default defineStore("galleryFilter", {
this.selectedGenre ||
this.selectedFranchise ||
this.selectedCollection ||
this.selectedCompany,
this.selectedCompany ||
this.selectedAgeRating,
);
},
reset() {
@@ -93,6 +108,7 @@ export default defineStore("galleryFilter", {
this.selectedFranchise = null;
this.selectedCollection = null;
this.selectedCompany = null;
this.selectedAgeRating = null;
},
},
});

View File

@@ -155,6 +155,9 @@ export default defineStore("roms", {
if (galleryFilter.selectedCompany) {
this._filterCompany(galleryFilter.selectedCompany);
}
if (galleryFilter.selectedAgeRating) {
this._filterAgeRating(galleryFilter.selectedAgeRating);
}
},
_filterSearch(searchFilter: string) {
this._filteredIDs = this.filteredRoms
@@ -208,6 +211,13 @@ export default defineStore("roms", {
)
.map((rom) => rom.id);
},
_filterAgeRating(ageRatingToFilter: string) {
this._filteredIDs = this.filteredRoms
.filter((rom) =>
rom.age_ratings.some((ageRating) => ageRating === ageRatingToFilter),
)
.map((rom) => rom.id);
},
// Selected roms
setSelection(roms: SimpleRom[]) {
this._selectedIDs = roms.map((rom) => rom.id);

View File

@@ -27,7 +27,6 @@ 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);
@@ -68,12 +67,12 @@ onMounted(async () => {
} 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)
b.updated_at.localeCompare(a.updated_at),
)[0];
}
const storedBiosID = localStorage.getItem(
`player:${rom.value.platform_slug}:bios_id`
`player:${rom.value.platform_slug}:bios_id`,
);
if (storedBiosID) {
biosRef.value =
@@ -82,7 +81,7 @@ onMounted(async () => {
}
const storedCore = localStorage.getItem(
`player:${rom.value.platform_slug}:core`
`player:${rom.value.platform_slug}:core`,
);
if (storedCore) {
coreRef.value = storedCore;

View File

@@ -69,7 +69,7 @@ async function fetchRoms() {
timeout: 4000,
});
console.error(
`Couldn't fetch roms for collection ID ${currentCollection.value?.id}: ${error}`
`Couldn't fetch roms for collection ID ${currentCollection.value?.id}: ${error}`,
);
noCollectionError.value = true;
})
@@ -87,28 +87,35 @@ function setFilters() {
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.genres.map((genre) => genre))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterFranchises([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.franchises.map((franchise) => franchise))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterCompanies([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.companies.map((company) => company))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterCollections([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.collections.map((collection) => collection))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterAgeRatings([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.age_ratings.map((ageRating) => ageRating))
.sort(),
),
]);
}
@@ -135,7 +142,7 @@ function onGameClick(emitData: { rom: SimpleRom; event: MouseEvent }) {
}
if (emitData.event.shiftKey) {
const [start, end] = [romsStore.lastSelectedIndex, index].sort(
(a, b) => a - b
(a, b) => a - b,
);
if (romsStore.selectedRoms.includes(emitData.rom)) {
for (let i = start + 1; i < end; i++) {
@@ -147,7 +154,7 @@ function onGameClick(emitData: { rom: SimpleRom; event: MouseEvent }) {
}
}
romsStore.updateLastSelected(
romsStore.selectedRoms.includes(emitData.rom) ? index : index - 1
romsStore.selectedRoms.includes(emitData.rom) ? index : index - 1,
);
} else {
romsStore.updateLastSelected(index);

View File

@@ -42,7 +42,6 @@ let timeout: ReturnType<typeof setTimeout>;
const emitter = inject<Emitter<Events>>("emitter");
emitter?.on("filter", onFilterChange);
// Functions
async function fetchRoms() {
if (gettingRoms.value) return;
@@ -69,7 +68,7 @@ async function fetchRoms() {
timeout: 4000,
});
console.error(
`Couldn't fetch roms for platform ID ${currentPlatform.value?.id}: ${error}`
`Couldn't fetch roms for platform ID ${currentPlatform.value?.id}: ${error}`,
);
noPlatformError.value = true;
})
@@ -87,28 +86,35 @@ function setFilters() {
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.genres.map((genre) => genre))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterFranchises([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.franchises.map((franchise) => franchise))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterCompanies([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.companies.map((company) => company))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterCollections([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.collections.map((collection) => collection))
.sort()
.sort(),
),
]);
galleryFilterStore.setFilterAgeRatings([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.age_ratings.map((ageRating) => ageRating))
.sort(),
),
]);
}
@@ -135,7 +141,7 @@ function onGameClick(emitData: { rom: SimpleRom; event: MouseEvent }) {
}
if (emitData.event.shiftKey) {
const [start, end] = [romsStore.lastSelectedIndex, index].sort(
(a, b) => a - b
(a, b) => a - b,
);
if (romsStore.selectedRoms.includes(emitData.rom)) {
for (let i = start + 1; i < end; i++) {
@@ -147,7 +153,7 @@ function onGameClick(emitData: { rom: SimpleRom; event: MouseEvent }) {
}
}
romsStore.updateLastSelected(
romsStore.selectedRoms.includes(emitData.rom) ? index : index - 1
romsStore.selectedRoms.includes(emitData.rom) ? index : index - 1,
);
} else {
romsStore.updateLastSelected(index);
@@ -201,6 +207,7 @@ const filterToSetFilter: Record<FilterType, Function> = {
franchises: galleryFilterStore.setSelectedFilterFranchise,
collections: galleryFilterStore.setSelectedFilterCollection,
companies: galleryFilterStore.setSelectedFilterCompany,
age_ratings: galleryFilterStore.setSelectedFilterAgeRating,
};
onMounted(async () => {

View File

@@ -41,7 +41,6 @@ const noRomError = ref(false);
const romsStore = storeRoms();
const { currentRom } = storeToRefs(romsStore);
// Functions
async function fetchDetails() {
await romApi
.getRom({ romId: parseInt(route.params.rom as string) })
@@ -91,7 +90,7 @@ watch(
() => route.fullPath,
async () => {
await fetchDetails();
}
},
);
</script>

View File

@@ -52,7 +52,6 @@ emitter?.on("refreshDrawer", async () => {
platformsStore.set(platformData);
});
// Functions
onBeforeMount(async () => {
await platformApi
.getPlatforms()
@@ -68,8 +67,8 @@ onBeforeMount(async () => {
collectionsStore.set(collections);
collectionsStore.setFavCollection(
collections.find(
(collection) => collection.name.toLowerCase() === "favourites"
)
(collection) => collection.name.toLowerCase() === "favourites",
),
);
})
.catch((error) => {

View File

@@ -43,12 +43,11 @@ const step = ref(1);
const filledAdminUser = computed(
() =>
defaultAdminUser.value.username != "" &&
defaultAdminUser.value.password != ""
defaultAdminUser.value.password != "",
);
const isFirstStep = computed(() => step.value == 1);
const isLastStep = computed(() => step.value == 2);
// Functions
async function finishWizard() {
await userApi
.createUser(defaultAdminUser.value)
@@ -189,14 +188,20 @@ async function finishWizard() {
<v-stepper-actions :disabled="!filledAdminUser">
<template #prev>
<v-btn class="text-white text-shadow" :ripple="false" :disabled="isFirstStep" @click="prev">{{
isFirstStep ? "" : "previous"
}}</v-btn>
<v-btn
class="text-white text-shadow"
:ripple="false"
:disabled="isFirstStep"
@click="prev"
>{{ isFirstStep ? "" : "previous" }}</v-btn
>
</template>
<template #next>
<v-btn class="text-white text-shadow" @click="!isLastStep ? next() : finishWizard()">{{
!isLastStep ? "Next" : "Finish"
}}</v-btn>
<v-btn
class="text-white text-shadow"
@click="!isLastStep ? next() : finishWizard()"
>{{ !isLastStep ? "Next" : "Finish" }}</v-btn
>
</template>
</v-stepper-actions>
</template>
@@ -223,7 +228,9 @@ async function finishWizard() {
max-width: 300px;
}
#version {
text-shadow: 1px 1px 1px #000000, 0 0 1px #000000;
text-shadow:
1px 1px 1px #000000,
0 0 1px #000000;
bottom: 0.3rem;
right: 0.5rem;
}