mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
added platform versions support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,6 +48,7 @@ romm_mock
|
||||
# testing
|
||||
backend/romm_test/resources
|
||||
backend/romm_test/logs
|
||||
backend/romm_test/config
|
||||
.pytest_cache
|
||||
|
||||
# service worker
|
||||
|
||||
@@ -36,6 +36,7 @@ class Config:
|
||||
EXCLUDED_MULTI_PARTS_EXT: list[str]
|
||||
EXCLUDED_MULTI_PARTS_FILES: list[str]
|
||||
PLATFORMS_BINDING: dict[str, str]
|
||||
PLATFORMS_VERSIONS: dict[str, str]
|
||||
ROMS_FOLDER_NAME: str
|
||||
SAVES_FOLDER_NAME: str
|
||||
STATES_FOLDER_NAME: str
|
||||
@@ -128,6 +129,7 @@ class ConfigManager:
|
||||
self._raw_config, "exclude.roms.multi_file.parts.names", []
|
||||
),
|
||||
PLATFORMS_BINDING=pydash.get(self._raw_config, "system.platforms", {}),
|
||||
PLATFORMS_VERSIONS=pydash.get(self._raw_config, "system.versions", {}),
|
||||
ROMS_FOLDER_NAME=pydash.get(
|
||||
self._raw_config, "filesystem.roms_folder", "roms"
|
||||
),
|
||||
@@ -189,6 +191,17 @@ class ConfigManager:
|
||||
)
|
||||
sys.exit(3)
|
||||
|
||||
if not isinstance(self.config.PLATFORMS_VERSIONS, dict):
|
||||
log.critical("Invalid config.yml: system.versions must be a dictionary")
|
||||
sys.exit(3)
|
||||
else:
|
||||
for fs_slug, slug in self.config.PLATFORMS_VERSIONS.items():
|
||||
if slug is None:
|
||||
log.critical(
|
||||
f"Invalid config.yml: system.versions.{fs_slug} must be a string"
|
||||
)
|
||||
sys.exit(3)
|
||||
|
||||
if not isinstance(self.config.ROMS_FOLDER_NAME, str):
|
||||
log.critical("Invalid config.yml: filesystem.roms_folder must be a string")
|
||||
sys.exit(3)
|
||||
@@ -276,6 +289,25 @@ class ConfigManager:
|
||||
pass
|
||||
self.update_config()
|
||||
|
||||
def add_version(self, fs_slug: str, slug: str) -> None:
|
||||
try:
|
||||
_ = self._raw_config["system"]
|
||||
except KeyError:
|
||||
self._raw_config = {"system": {"versions": {}}}
|
||||
try:
|
||||
_ = self._raw_config["system"]["versions"]
|
||||
except KeyError:
|
||||
self._raw_config["system"]["versions"] = {}
|
||||
self._raw_config["system"]["versions"][fs_slug] = slug
|
||||
self.update_config()
|
||||
|
||||
def remove_version(self, fs_slug: str) -> None:
|
||||
try:
|
||||
del self._raw_config["system"]["versions"][fs_slug]
|
||||
except KeyError:
|
||||
pass
|
||||
self.update_config()
|
||||
|
||||
# def _get_exclude_path(self, exclude):
|
||||
# exclude_base = self._raw_config["exclude"]
|
||||
# exclusions = {
|
||||
|
||||
@@ -67,6 +67,42 @@ async def delete_platform_binding(request: Request, fs_slug: str) -> MessageResp
|
||||
return {"msg": f"{fs_slug} bind removed successfully!"}
|
||||
|
||||
|
||||
@protected_route(router.post, "/config/system/versions", ["platforms.write"])
|
||||
async def add_platform_version(request: Request) -> MessageResponse:
|
||||
"""Add platform version to the configuration"""
|
||||
|
||||
data = await request.json()
|
||||
fs_slug = data["fs_slug"]
|
||||
slug = data["slug"]
|
||||
|
||||
try:
|
||||
cm.add_version(fs_slug, slug)
|
||||
except ConfigNotWritableException as e:
|
||||
log.critical(e.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
|
||||
return {"msg": f"Added {fs_slug} as version of: {slug} successfully!"}
|
||||
|
||||
|
||||
@protected_route(
|
||||
router.delete, "/config/system/versions/{fs_slug}", ["platforms.write"]
|
||||
)
|
||||
async def delete_platform_version(request: Request, fs_slug: str) -> MessageResponse:
|
||||
"""Delete platform version from the configuration"""
|
||||
|
||||
try:
|
||||
cm.remove_version(fs_slug)
|
||||
except ConfigNotWritableException as e:
|
||||
log.critical(e.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
|
||||
return {"msg": f"{fs_slug} version removed successfully!"}
|
||||
|
||||
|
||||
# @protected_route(router.post, "/config/exclude", ["platforms.write"])
|
||||
# async def add_exclusion(request: Request) -> MessageResponse:
|
||||
# """Add platform binding to the configuration"""
|
||||
|
||||
@@ -9,6 +9,7 @@ class ConfigResponse(TypedDict):
|
||||
EXCLUDED_MULTI_PARTS_EXT: list[str]
|
||||
EXCLUDED_MULTI_PARTS_FILES: list[str]
|
||||
PLATFORMS_BINDING: dict[str, str]
|
||||
PLATFORMS_VERSIONS: dict[str, str]
|
||||
ROMS_FOLDER_NAME: str
|
||||
SAVES_FOLDER_NAME: str
|
||||
STATES_FOLDER_NAME: str
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from handler.igdb_handler import IGDBRomType
|
||||
from handler.igdb_handler import IGDBRom
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
|
||||
class RomSearchResponse(TypedDict):
|
||||
msg: str
|
||||
roms: list[IGDBRomType]
|
||||
roms: list[IGDBRom]
|
||||
|
||||
@@ -43,12 +43,12 @@ SWITCH_PRODUCT_ID_FILE: Final = os.path.join(
|
||||
MAME_XML_FILE: Final = os.path.join(os.path.dirname(__file__), "fixtures", "mame.xml")
|
||||
|
||||
|
||||
class IGDBPlatformType(TypedDict):
|
||||
class IGDBPlatform(TypedDict):
|
||||
igdb_id: int
|
||||
name: str
|
||||
|
||||
|
||||
class IGDBRomType(TypedDict):
|
||||
class IGDBRom(TypedDict):
|
||||
igdb_id: int
|
||||
slug: str
|
||||
name: str
|
||||
@@ -60,6 +60,7 @@ class IGDBRomType(TypedDict):
|
||||
class IGDBHandler:
|
||||
def __init__(self) -> None:
|
||||
self.platform_url = "https://api.igdb.com/v4/platforms/"
|
||||
self.platform_version_url = "https://api.igdb.com/v4/platform_versions/"
|
||||
self.games_url = "https://api.igdb.com/v4/games/"
|
||||
self.covers_url = "https://api.igdb.com/v4/covers/"
|
||||
self.screenshots_url = "https://api.igdb.com/v4/screenshots/"
|
||||
@@ -263,23 +264,36 @@ class IGDBHandler:
|
||||
return search_term
|
||||
|
||||
@check_twitch_token
|
||||
def get_platform(self, slug: str) -> IGDBPlatformType:
|
||||
def get_platform(self, slug: str) -> IGDBPlatform:
|
||||
platforms = self._request(
|
||||
self.platform_url,
|
||||
data=f'fields id, name; where slug="{slug.lower()}";',
|
||||
)
|
||||
|
||||
platform = pydash.get(platforms, "[0]", None)
|
||||
if not platform:
|
||||
return IGDBPlatformType(igdb_id=None, name=slug.replace("-", " ").title())
|
||||
|
||||
return IGDBPlatformType(
|
||||
# Check if platform is a version if not found
|
||||
if not platform:
|
||||
platform_versions = self._request(
|
||||
self.platform_version_url,
|
||||
data=f'fields id, name; where slug="{slug.lower()}";',
|
||||
)
|
||||
version = pydash.get(platform_versions, "[0]", None)
|
||||
if not version:
|
||||
return IGDBPlatform(igdb_id=None, igdb_id_base_platform=None, name=slug.replace("-", " ").title())
|
||||
|
||||
return IGDBPlatform(
|
||||
igdb_id=version["id"],
|
||||
name=version["name"],
|
||||
)
|
||||
|
||||
return IGDBPlatform(
|
||||
igdb_id=platform["id"],
|
||||
name=platform["name"],
|
||||
)
|
||||
|
||||
@check_twitch_token
|
||||
async def get_rom(self, file_name: str, platform_idgb_id: int) -> IGDBRomType:
|
||||
async def get_rom(self, file_name: str, platform_idgb_id: int) -> IGDBRom:
|
||||
from handler import fs_rom_handler
|
||||
|
||||
search_term = fs_rom_handler.get_file_name_with_no_tags(file_name)
|
||||
@@ -314,7 +328,7 @@ class IGDBHandler:
|
||||
)
|
||||
|
||||
igdb_id = res.get("id", None)
|
||||
rom = IGDBRomType(
|
||||
rom = IGDBRom(
|
||||
igdb_id=igdb_id,
|
||||
slug=res.get("slug", ""),
|
||||
name=res.get("name", search_term),
|
||||
@@ -330,7 +344,7 @@ class IGDBHandler:
|
||||
return rom
|
||||
|
||||
@check_twitch_token
|
||||
def get_rom_by_id(self, igdb_id: int) -> IGDBRomType:
|
||||
def get_rom_by_id(self, igdb_id: int) -> IGDBRom:
|
||||
roms = self._request(
|
||||
self.games_url,
|
||||
f"fields slug, name, summary; where id={igdb_id};",
|
||||
@@ -347,7 +361,7 @@ class IGDBHandler:
|
||||
}
|
||||
|
||||
@check_twitch_token
|
||||
def get_matched_roms_by_id(self, igdb_id: int) -> list[IGDBRomType]:
|
||||
def get_matched_roms_by_id(self, igdb_id: int) -> list[IGDBRom]:
|
||||
matched_rom = self.get_rom_by_id(igdb_id)
|
||||
matched_rom.update(
|
||||
url_cover=matched_rom["url_cover"].replace("t_thumb", "t_cover_big"),
|
||||
@@ -357,7 +371,7 @@ class IGDBHandler:
|
||||
@check_twitch_token
|
||||
def get_matched_roms_by_name(
|
||||
self, search_term: str, platform_idgb_id: int
|
||||
) -> list[IGDBRomType]:
|
||||
) -> list[IGDBRom]:
|
||||
if not platform_idgb_id:
|
||||
return []
|
||||
|
||||
@@ -371,7 +385,7 @@ class IGDBHandler:
|
||||
)
|
||||
|
||||
return [
|
||||
IGDBRomType(
|
||||
IGDBRom(
|
||||
igdb_id=rom["id"],
|
||||
slug=rom["slug"],
|
||||
name=rom["name"],
|
||||
|
||||
@@ -1,17 +1,38 @@
|
||||
import os
|
||||
from typing import Any
|
||||
import emoji
|
||||
|
||||
import emoji
|
||||
from config.config_manager import config_manager as cm
|
||||
from handler import fs_asset_handler, igdb_handler, fs_resource_handler, fs_rom_handler, db_platform_handler
|
||||
from handler import (
|
||||
db_platform_handler,
|
||||
fs_asset_handler,
|
||||
fs_resource_handler,
|
||||
fs_rom_handler,
|
||||
igdb_handler,
|
||||
)
|
||||
from logger.logger import log
|
||||
from models.assets import Save, Screenshot, State
|
||||
from models.platform import Platform
|
||||
from models.rom import Rom
|
||||
from models.assets import Save, Screenshot, State
|
||||
|
||||
SWAPPED_PLATFORM_BINDINGS = dict(
|
||||
(v, k) for k, v in cm.config.PLATFORMS_BINDING.items()
|
||||
)
|
||||
SWAPPED_PLATFORM_BINDINGS = dict((v, k) for k, v in cm.config.PLATFORMS_BINDING.items())
|
||||
|
||||
|
||||
def _get_main_platform_igdb_id(platform: Platform):
|
||||
if platform.fs_slug in cm.config.PLATFORMS_VERSIONS.keys():
|
||||
main_platform_slug = cm.config.PLATFORMS_VERSIONS[platform.fs_slug]
|
||||
main_platform = db_platform_handler.get_platform_by_slug(main_platform_slug)
|
||||
if main_platform:
|
||||
main_platform_igdb_id = main_platform.igdb_id
|
||||
else:
|
||||
main_platform_igdb_id = igdb_handler.get_platform(main_platform_slug)[
|
||||
"igdb_id"
|
||||
]
|
||||
if not main_platform_igdb_id:
|
||||
main_platform_igdb_id = platform.igdb_id
|
||||
else:
|
||||
main_platform_igdb_id = platform.igdb_id
|
||||
return main_platform_igdb_id
|
||||
|
||||
|
||||
def scan_platform(fs_slug: str, fs_platforms) -> Platform:
|
||||
@@ -51,7 +72,9 @@ def scan_platform(fs_slug: str, fs_platforms) -> Platform:
|
||||
if platform["igdb_id"]:
|
||||
log.info(emoji.emojize(f" Identified as {platform['name']} :video_game:"))
|
||||
else:
|
||||
log.warning(emoji.emojize(f" {platform_attrs['slug']} not found in IGDB :cross_mark:"))
|
||||
log.warning(
|
||||
emoji.emojize(f" {platform_attrs['slug']} not found in IGDB :cross_mark:")
|
||||
)
|
||||
|
||||
platform_attrs.update(platform)
|
||||
|
||||
@@ -85,8 +108,12 @@ async def scan_rom(
|
||||
"platform_id": platform.id,
|
||||
"file_path": roms_path,
|
||||
"file_name": rom_attrs["file_name"],
|
||||
"file_name_no_tags": fs_rom_handler.get_file_name_with_no_tags(rom_attrs["file_name"]),
|
||||
"file_extension": fs_rom_handler.parse_file_extension(rom_attrs["file_name"]),
|
||||
"file_name_no_tags": fs_rom_handler.get_file_name_with_no_tags(
|
||||
rom_attrs["file_name"]
|
||||
),
|
||||
"file_extension": fs_rom_handler.parse_file_extension(
|
||||
rom_attrs["file_name"]
|
||||
),
|
||||
"file_size_bytes": file_size,
|
||||
"multi": rom_attrs["multi"],
|
||||
"regions": regs,
|
||||
@@ -96,11 +123,13 @@ async def scan_rom(
|
||||
}
|
||||
)
|
||||
|
||||
main_platform_igdb_id = _get_main_platform_igdb_id(platform)
|
||||
|
||||
# Search in IGDB
|
||||
igdb_handler_rom = (
|
||||
igdb_handler.get_rom_by_id(int(r_igbd_id_search))
|
||||
if r_igbd_id_search
|
||||
else await igdb_handler.get_rom(rom_attrs["file_name"], platform.igdb_id)
|
||||
else await igdb_handler.get_rom(rom_attrs["file_name"], main_platform_igdb_id)
|
||||
)
|
||||
|
||||
rom_attrs.update(igdb_handler_rom)
|
||||
@@ -108,11 +137,15 @@ async def scan_rom(
|
||||
# Return early if not found in IGDB
|
||||
if not igdb_handler_rom["igdb_id"]:
|
||||
log.warning(
|
||||
emoji.emojize(f"\t {r_igbd_id_search or rom_attrs['file_name']} not found in IGDB :cross_mark:")
|
||||
emoji.emojize(
|
||||
f"\t {r_igbd_id_search or rom_attrs['file_name']} not found in IGDB :cross_mark:"
|
||||
)
|
||||
)
|
||||
return Rom(**rom_attrs)
|
||||
|
||||
log.info(emoji.emojize(f"\t Identified as {igdb_handler_rom['name']} :alien_monster:"))
|
||||
log.info(
|
||||
emoji.emojize(f"\t Identified as {igdb_handler_rom['name']} :alien_monster:")
|
||||
)
|
||||
|
||||
# Update properties from IGDB
|
||||
rom_attrs.update(
|
||||
@@ -180,6 +213,4 @@ def scan_screenshot(file_name: str, platform_slug: Platform = None) -> Screensho
|
||||
if platform_slug:
|
||||
return Screenshot(**_scan_asset(file_name, screenshots_path))
|
||||
|
||||
return Screenshot(
|
||||
**_scan_asset(file_name, cm.config.SCREENSHOTS_FOLDER_NAME)
|
||||
)
|
||||
return Screenshot(**_scan_asset(file_name, cm.config.SCREENSHOTS_FOLDER_NAME))
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
volumes:
|
||||
- "/path/to/library:/romm/library"
|
||||
- "/path/to/resources:/romm/resources" # [Optional] Path where roms metadata (covers) are stored
|
||||
- "/path/to/config.yml:/romm/config.yml" # [Optional] Path where config is stored
|
||||
- "/path/to/config:/romm/config" # [Optional] Path where config is stored
|
||||
- "/path/to/database:/romm/database" # [Optional] Only needed if ROMM_DB_DRIVER=sqlite or not set
|
||||
- "/path/to/logs:/romm/logs" # [Optional] Path where logs are stored
|
||||
ports:
|
||||
|
||||
2
frontend/src/__generated__/index.ts
generated
2
frontend/src/__generated__/index.ts
generated
@@ -15,7 +15,7 @@ export type { CursorPage_RomSchema_ } from './models/CursorPage_RomSchema_';
|
||||
export type { EnhancedRomSchema } from './models/EnhancedRomSchema';
|
||||
export type { HeartbeatResponse } from './models/HeartbeatResponse';
|
||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||
export type { IGDBRomType } from './models/IGDBRomType';
|
||||
export type { IGDBRom } from './models/IGDBRom';
|
||||
export type { MessageResponse } from './models/MessageResponse';
|
||||
export type { PlatformSchema } from './models/PlatformSchema';
|
||||
export type { Role } from './models/Role';
|
||||
|
||||
@@ -11,6 +11,7 @@ export type ConfigResponse = {
|
||||
EXCLUDED_MULTI_PARTS_EXT: Array<string>;
|
||||
EXCLUDED_MULTI_PARTS_FILES: Array<string>;
|
||||
PLATFORMS_BINDING: Record<string, string>;
|
||||
PLATFORMS_VERSIONS: Record<string, string>;
|
||||
ROMS_FOLDER_NAME: string;
|
||||
SAVES_FOLDER_NAME: string;
|
||||
STATES_FOLDER_NAME: string;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type IGDBRomType = {
|
||||
export type IGDBRom = {
|
||||
igdb_id: number;
|
||||
slug: string;
|
||||
name: string;
|
||||
@@ -3,10 +3,10 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import type { IGDBRomType } from './IGDBRomType';
|
||||
import type { IGDBRom } from './IGDBRom';
|
||||
|
||||
export type RomSearchResponse = {
|
||||
msg: string;
|
||||
roms: Array<IGDBRomType>;
|
||||
roms: Array<IGDBRom>;
|
||||
};
|
||||
|
||||
|
||||
104
frontend/src/components/Dialog/Config/CreatePlatformVersion.vue
Normal file
104
frontend/src/components/Dialog/Config/CreatePlatformVersion.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import api_config from "@/services/api_config";
|
||||
import storeConfig from "@/stores/config";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, ref } from "vue";
|
||||
|
||||
// Props
|
||||
const show = ref(false);
|
||||
const configStore = storeConfig();
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
const fsSlugToCreate = ref();
|
||||
const slugToCreate = ref();
|
||||
emitter?.on("showCreatePlatformVersionDialog", ({ fsSlug = "", slug = "" }) => {
|
||||
fsSlugToCreate.value = fsSlug;
|
||||
slugToCreate.value = slug;
|
||||
show.value = true;
|
||||
});
|
||||
|
||||
// Functions
|
||||
function addVersionPlatform() {
|
||||
api_config
|
||||
.addPlatformVersionConfig({
|
||||
fsSlug: fsSlugToCreate.value,
|
||||
slug: slugToCreate.value,
|
||||
})
|
||||
.then(() => {
|
||||
configStore.addPlatformVersion(fsSlugToCreate.value, slugToCreate.value);
|
||||
})
|
||||
.catch(({ response, message }) => {
|
||||
emitter?.emit("snackbarShow", {
|
||||
msg: `${response?.data?.detail || response?.statusText || message}`,
|
||||
icon: "mdi-close-circle",
|
||||
color: "red",
|
||||
timeout: 4000,
|
||||
});
|
||||
});
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
show.value = false;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<v-dialog v-model="show" max-width="500px" :scrim="true">
|
||||
<v-card>
|
||||
<v-toolbar density="compact" class="bg-terciary">
|
||||
<v-row class="align-center" no-gutters>
|
||||
<v-col cols="10">
|
||||
<v-icon icon="mdi-gamepad-variant" class="ml-5" />
|
||||
<v-icon icon="mdi-approximately-equal" class="ml-1 text-romm-gray" />
|
||||
<v-icon icon="mdi-controller" class="ml-1 text-romm-accent-1" />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-btn
|
||||
@click="closeDialog"
|
||||
class="bg-terciary"
|
||||
rounded="0"
|
||||
variant="text"
|
||||
icon="mdi-close"
|
||||
block
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-toolbar>
|
||||
<v-divider class="border-opacity-25" :thickness="1" />
|
||||
|
||||
<v-card-text>
|
||||
<v-row class="pa-2 align-center" no-gutters>
|
||||
<v-text-field
|
||||
@keyup.enter=""
|
||||
v-model="fsSlugToCreate"
|
||||
label="Platform version"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
<v-icon icon="mdi-menu-right" class="mx-2 text-romm-gray" />
|
||||
<v-text-field
|
||||
class="text-romm-accent-1"
|
||||
@keyup.enter=""
|
||||
v-model="slugToCreate"
|
||||
label="Main platform"
|
||||
color="romm-accent-1"
|
||||
base-color="romm-accent-1"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
</v-row>
|
||||
<v-row class="justify-center pa-2" no-gutters>
|
||||
<v-btn @click="closeDialog" class="bg-terciary">Cancel</v-btn>
|
||||
<v-btn
|
||||
@click="addVersionPlatform()"
|
||||
class="text-romm-green bg-terciary ml-5"
|
||||
>
|
||||
Confirm
|
||||
</v-btn>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import api_config from "@/services/api_config";
|
||||
import storeConfig from "@/stores/config";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, ref } from "vue";
|
||||
|
||||
// Props
|
||||
const show = ref(false);
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
const configStore = storeConfig();
|
||||
const fsSlugToDelete = ref();
|
||||
const slugToDelete = ref();
|
||||
emitter?.on("showDeletePlatformVersionDialog", ({ fsSlug, slug }) => {
|
||||
fsSlugToDelete.value = fsSlug;
|
||||
slugToDelete.value = slug;
|
||||
show.value = true;
|
||||
});
|
||||
|
||||
// Functions
|
||||
function removeVersionPlatform() {
|
||||
api_config
|
||||
.deletePlatformVersionConfig({ fsSlug: fsSlugToDelete.value })
|
||||
.then(() => {
|
||||
configStore.removePlatformVersion(fsSlugToDelete.value);
|
||||
})
|
||||
.catch(({ response, message }) => {
|
||||
emitter?.emit("snackbarShow", {
|
||||
msg: `${response?.data?.detail || response?.statusText || message}`,
|
||||
icon: "mdi-close-circle",
|
||||
color: "red",
|
||||
timeout: 4000,
|
||||
});
|
||||
});
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
show.value = false;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="show"
|
||||
width="auto"
|
||||
@click:outside="closeDialog"
|
||||
@keydown.esc="closeDialog"
|
||||
no-click-animation
|
||||
persistent
|
||||
:scrim="true"
|
||||
>
|
||||
<v-card>
|
||||
<v-toolbar density="compact" class="bg-terciary">
|
||||
<v-row class="align-center" no-gutters>
|
||||
<v-col cols="10">
|
||||
<v-icon icon="mdi-delete" class="ml-5 mr-2" />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-btn
|
||||
@click="closeDialog"
|
||||
class="bg-terciary"
|
||||
rounded="0"
|
||||
variant="text"
|
||||
icon="mdi-close"
|
||||
block
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-toolbar>
|
||||
<v-divider class="border-opacity-25" :thickness="1" />
|
||||
|
||||
<v-card-text>
|
||||
<v-row class="justify-center pa-2" no-gutters>
|
||||
<span class="mr-1">Deleting platform version [</span>
|
||||
<span class="text-romm-accent-1 mr-1">{{
|
||||
fsSlugToDelete
|
||||
}}</span>
|
||||
<span>:</span>
|
||||
<span class="text-romm-accent-1 ml-1">{{
|
||||
slugToDelete
|
||||
}}</span
|
||||
><span class="ml-1">].</span>
|
||||
<span class="ml-1">Do you confirm?</span>
|
||||
</v-row>
|
||||
<v-row class="justify-center pa-2" no-gutters>
|
||||
<v-btn @click="closeDialog" class="bg-terciary">Cancel</v-btn>
|
||||
<v-btn
|
||||
@click="removeVersionPlatform()"
|
||||
class="text-romm-red bg-terciary ml-5"
|
||||
>
|
||||
Confirm
|
||||
</v-btn>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
133
frontend/src/components/Settings/Config/PlatformVersionsCard.vue
Normal file
133
frontend/src/components/Settings/Config/PlatformVersionsCard.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<script setup lang="ts">
|
||||
import CreatePlatformVersionDialog from "@/components/Dialog/Config/CreatePlatformVersion.vue";
|
||||
import DeletePlatformVersionDialog from "@/components/Dialog/Config/DeletePlatformVersion.vue";
|
||||
import PlatformIcon from "@/components/Platform/PlatformIcon.vue";
|
||||
import storeAuth from "@/stores/auth";
|
||||
import storeConfig from "@/stores/config";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, ref } from "vue";
|
||||
|
||||
// Props
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
const configStore = storeConfig();
|
||||
const authStore = storeAuth();
|
||||
const platformsVersions = configStore.value.PLATFORMS_VERSIONS;
|
||||
const editable = ref(false);
|
||||
</script>
|
||||
<template>
|
||||
<v-card rounded="0">
|
||||
<v-toolbar class="bg-terciary" density="compact">
|
||||
<v-toolbar-title class="text-button">
|
||||
<v-icon class="mr-3">mdi-gamepad-variant</v-icon>
|
||||
Platforms Versions
|
||||
</v-toolbar-title>
|
||||
<v-btn
|
||||
v-if="authStore.scopes.includes('platforms.write')"
|
||||
class="ma-2"
|
||||
rounded="0"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="editable = !editable"
|
||||
icon="mdi-cog"
|
||||
>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<v-divider class="border-opacity-25" />
|
||||
|
||||
<v-card-text class="pa-1">
|
||||
<v-row no-gutters class="align-center">
|
||||
<v-col
|
||||
cols="6"
|
||||
sm="4"
|
||||
md="3"
|
||||
lg="2"
|
||||
xl="2"
|
||||
v-for="(slug, fsSlug) in platformsVersions"
|
||||
:key="slug"
|
||||
:title="slug"
|
||||
>
|
||||
<v-list-item class="bg-terciary ma-1 pa-1 text-truncate">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar :rounded="0" size="40" class="mx-2">
|
||||
<platform-icon class="platform-icon" :key="slug" :slug="slug" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item class="bg-primary pr-2 pl-2">
|
||||
<span>{{ fsSlug }}</span>
|
||||
<template v-slot:append>
|
||||
<v-slide-x-reverse-transition>
|
||||
<v-btn
|
||||
v-if="
|
||||
authStore.scopes.includes('platforms.write') && editable
|
||||
"
|
||||
rounded="0"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
icon="mdi-pencil"
|
||||
@click="
|
||||
emitter?.emit('showCreatePlatformVersionDialog', {
|
||||
fsSlug,
|
||||
slug,
|
||||
})
|
||||
"
|
||||
class="ml-2"
|
||||
/>
|
||||
</v-slide-x-reverse-transition>
|
||||
<v-slide-x-reverse-transition>
|
||||
<v-btn
|
||||
v-if="
|
||||
authStore.scopes.includes('platforms.write') && editable
|
||||
"
|
||||
rounded="0"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
icon="mdi-delete"
|
||||
@click="
|
||||
emitter?.emit('showDeletePlatformVersionDialog', {
|
||||
fsSlug,
|
||||
slug,
|
||||
})
|
||||
"
|
||||
class="text-romm-red"
|
||||
/>
|
||||
</v-slide-x-reverse-transition>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="4" md="3" lg="2" xl="2" class="px-1">
|
||||
<v-expand-transition>
|
||||
<v-btn
|
||||
v-if="authStore.scopes.includes('platforms.write') && editable"
|
||||
block
|
||||
rounded="0"
|
||||
size="large"
|
||||
prepend-icon="mdi-plus"
|
||||
variant="outlined"
|
||||
class="text-romm-accent-1"
|
||||
@click="
|
||||
emitter?.emit('showCreatePlatformVersionDialog', {
|
||||
fsSlug: '',
|
||||
slug: '',
|
||||
})
|
||||
"
|
||||
>
|
||||
Add
|
||||
</v-btn>
|
||||
</v-expand-transition>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<create-platform-version-dialog />
|
||||
<delete-platform-version-dialog />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.platform-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,24 @@ async function deletePlatformBindConfig({
|
||||
return api.delete(`/config/system/platforms/${fsSlug}`);
|
||||
}
|
||||
|
||||
async function addPlatformVersionConfig({
|
||||
fsSlug,
|
||||
slug,
|
||||
}: {
|
||||
fsSlug: string;
|
||||
slug: string;
|
||||
}): Promise<{ data: MessageResponse }> {
|
||||
return api.post("/config/system/versions", { fs_slug: fsSlug, slug: slug });
|
||||
}
|
||||
|
||||
async function deletePlatformVersionConfig({
|
||||
fsSlug,
|
||||
}: {
|
||||
fsSlug: string;
|
||||
}): Promise<{ data: MessageResponse }> {
|
||||
return api.delete(`/config/system/versions/${fsSlug}`);
|
||||
}
|
||||
|
||||
async function addExclusion({
|
||||
exclude,
|
||||
exclusion,
|
||||
@@ -37,5 +55,7 @@ async function addExclusion({
|
||||
export default {
|
||||
addPlatformBindConfig,
|
||||
deletePlatformBindConfig,
|
||||
addPlatformVersionConfig,
|
||||
deletePlatformVersionConfig,
|
||||
addExclusion,
|
||||
};
|
||||
|
||||
@@ -17,5 +17,11 @@ export default defineStore("config", {
|
||||
removePlatformBinding(fsSlug: string) {
|
||||
delete this.value.PLATFORMS_BINDING[fsSlug];
|
||||
},
|
||||
addPlatformVersion(fsSlug: string, slug: string) {
|
||||
this.value.PLATFORMS_VERSIONS[fsSlug] = slug;
|
||||
},
|
||||
removePlatformVersion(fsSlug: string) {
|
||||
delete this.value.PLATFORMS_VERSIONS[fsSlug];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
8
frontend/src/types/emitter.d.ts
vendored
8
frontend/src/types/emitter.d.ts
vendored
@@ -30,6 +30,14 @@ export type Events = {
|
||||
fsSlug: string;
|
||||
slug: string;
|
||||
};
|
||||
showCreatePlatformVersionDialog: {
|
||||
fsSlug: string;
|
||||
slug: string;
|
||||
};
|
||||
showDeletePlatformVersionDialog: {
|
||||
fsSlug: string;
|
||||
slug: string;
|
||||
};
|
||||
showCreateExclusionDialog: { exclude: string };
|
||||
showCreateUserDialog: null;
|
||||
showEditUserDialog: UserItem;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import ExclusionsCard from "@/components/Settings/Config/ExclusionsCard.vue";
|
||||
import PlatformBinding from "@/components/Settings/Config/PlatformBindingCard.vue";
|
||||
import PlatformVersions from "@/components/Settings/Config/PlatformVersionsCard.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<platform-binding />
|
||||
<platform-versions />
|
||||
<exclusions-card class="mt-1" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user