diff --git a/README.md b/README.md
index 04cd6c29b..8a11b95e8 100644
--- a/README.md
+++ b/README.md
@@ -212,7 +212,6 @@ Here are a few projects maintained by members of our community. Please note that
- [romm-comm][romm-comm-discord-bot]: Discord Bot by @idio-sync
- [DeckRommSync][deck-romm-sync]: SteamOS downloader and sync by @PeriBluGaming
- CasaOS app via the [BigBear App Store][big-bear-casaos]
-- [Helm Chart to deploy on Kubernetes][kubernetes-helm-chart] by @psych0d0g
Join us on Discord, where you can ask questions, submit ideas, get help, showcase your collection, and discuss RomM with other users.
@@ -283,7 +282,6 @@ Here are a few projects that we think you might like:
[screenscraper-api]: https://www.screenscraper.fr/membreinscription.php
[mobygames-api]: https://www.mobygames.com/info/api/
[big-bear-casaos]: https://github.com/bigbeartechworld/big-bear-casaos
-[kubernetes-helm-chart]: https://artifacthub.io/packages/helm/crystalnet/romm
[romm-comm-discord-bot]: https://github.com/idio-sync/romm-comm
[deck-romm-sync]: https://github.com/PeriBluGaming/DeckRommSync-Standalone
[playnite-app]: https://github.com/rommapp/playnite-plugin
diff --git a/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py b/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py
new file mode 100644
index 000000000..9fa40e7b1
--- /dev/null
+++ b/backend/alembic/versions/0038_add_ssid_to_sibling_roms.py
@@ -0,0 +1,92 @@
+"""empty message
+
+Revision ID: 0038_add_ssid_to_sibling_roms
+Revises: 0037_virtual_rom_columns
+Create Date: 2025-04-23 00:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+from utils.database import is_postgresql
+
+# revision identifiers, used by Alembic.
+revision = "0038_add_ssid_to_sibling_roms"
+down_revision = "0037_virtual_rom_columns"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ connection = op.get_bind()
+ null_safe_equal_operator = (
+ "IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>"
+ )
+
+ connection.execute(
+ sa.text(
+ f"""
+ CREATE OR REPLACE VIEW sibling_roms AS
+ SELECT
+ r1.id AS rom_id,
+ r2.id AS sibling_rom_id,
+ r1.platform_id AS platform_id,
+ NOW() AS created_at,
+ NOW() AS updated_at,
+ CASE WHEN r1.igdb_id {null_safe_equal_operator} r2.igdb_id THEN r1.igdb_id END AS igdb_id,
+ CASE WHEN r1.moby_id {null_safe_equal_operator} r2.moby_id THEN r1.moby_id END AS moby_id,
+ CASE WHEN r1.ss_id {null_safe_equal_operator} r2.ss_id THEN r1.ss_id END AS ss_id
+ FROM
+ roms r1
+ JOIN
+ roms r2
+ ON
+ r1.platform_id = r2.platform_id
+ AND r1.id != r2.id
+ AND (
+ (r1.igdb_id = r2.igdb_id AND r1.igdb_id IS NOT NULL)
+ OR
+ (r1.moby_id = r2.moby_id AND r1.moby_id IS NOT NULL)
+ OR
+ (r1.ss_id = r2.ss_id AND r1.ss_id IS NOT NULL)
+ );
+ """ # nosec B608
+ ),
+ )
+
+
+def downgrade() -> None:
+ connection = op.get_bind()
+ null_safe_equal_operator = (
+ "IS NOT DISTINCT FROM" if is_postgresql(connection) else "<=>"
+ )
+
+ connection.execute(sa.text("DROP VIEW IF EXISTS sibling_roms;"))
+
+ connection.execute(
+ sa.text(
+ f"""
+ CREATE VIEW sibling_roms AS
+ SELECT
+ r1.id AS rom_id,
+ r2.id AS sibling_rom_id,
+ r1.platform_id AS platform_id,
+ NOW() AS created_at,
+ NOW() AS updated_at,
+ CASE WHEN r1.igdb_id {null_safe_equal_operator} r2.igdb_id THEN r1.igdb_id END AS igdb_id,
+ CASE WHEN r1.moby_id {null_safe_equal_operator} r2.moby_id THEN r1.moby_id END AS moby_id
+ FROM
+ roms r1
+ JOIN
+ roms r2
+ ON
+ r1.platform_id = r2.platform_id
+ AND r1.id != r2.id
+ AND (
+ (r1.igdb_id = r2.igdb_id AND r1.igdb_id IS NOT NULL)
+ OR
+ (r1.moby_id = r2.moby_id AND r1.moby_id IS NOT NULL)
+ );
+ """ # nosec B608
+ ),
+ )
diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py
index 61ea7f048..b50622a37 100644
--- a/backend/endpoints/rom.py
+++ b/backend/endpoints/rom.py
@@ -476,7 +476,6 @@ async def get_rom_content(
async def update_rom(
request: Request,
id: int,
- rename_as_source: bool = False,
remove_cover: bool = False,
artwork: UploadFile | None = None,
unmatch_metadata: bool = False,
@@ -486,12 +485,11 @@ async def update_rom(
Args:
request (Request): Fastapi Request object
id (Rom): Rom internal id
- rename_as_source (bool, optional): Flag to rename rom file as matched IGDB game. Defaults to False.
artwork (UploadFile, optional): Custom artwork to set as cover. Defaults to File(None).
unmatch_metadata: Remove the metadata matches for this game. Defaults to False.
Raises:
- HTTPException: If a rom already have that name when enabling the rename_as_source flag
+ HTTPException: Rom not found in database
Returns:
DetailedRomSchema: Rom stored in the database
@@ -542,9 +540,13 @@ async def update_rom(
"ss_id": data.get("ss_id", rom.ss_id),
}
- moby_id = cleaned_data["moby_id"]
- if moby_id and int(moby_id) != rom.moby_id:
- moby_rom = await meta_moby_handler.get_rom_by_id(int(moby_id))
+ if (
+ cleaned_data.get("moby_id", "")
+ and int(cleaned_data.get("moby_id", "")) != rom.moby_id
+ ):
+ moby_rom = await meta_moby_handler.get_rom_by_id(
+ int(cleaned_data.get("moby_id", ""))
+ )
cleaned_data.update(moby_rom)
path_screenshots = await fs_resource_handler.get_rom_screenshots(
rom=rom,
@@ -653,33 +655,22 @@ async def update_rom(
# Rename the file/folder if the name has changed
should_update_fs = new_fs_name != rom.fs_name
- try:
- if rename_as_source:
- new_fs_name = rom.fs_name.replace(
- rom.fs_name_no_tags or rom.fs_name_no_ext,
- rom.name or rom.fs_name,
- )
+ if should_update_fs:
+ try:
new_fs_name = sanitize_filename(new_fs_name)
fs_rom_handler.rename_fs_rom(
old_name=rom.fs_name,
new_name=new_fs_name,
fs_path=rom.fs_path,
)
- elif should_update_fs:
- new_fs_name = sanitize_filename(new_fs_name)
- fs_rom_handler.rename_fs_rom(
- old_name=rom.fs_name,
- new_name=new_fs_name,
- fs_path=rom.fs_path,
- )
- except RomAlreadyExistsException as exc:
- log.error(exc)
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc
- ) from exc
+ except RomAlreadyExistsException as exc:
+ log.error(exc)
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc
+ ) from exc
# Update the rom files with the new fs_name
- if rename_as_source or should_update_fs:
+ if should_update_fs:
for file in rom.files:
db_rom_handler.update_rom_file(
file.id,
diff --git a/backend/endpoints/tests/test_rom.py b/backend/endpoints/tests/test_rom.py
index 92c1d2a45..74cac13bd 100644
--- a/backend/endpoints/tests/test_rom.py
+++ b/backend/endpoints/tests/test_rom.py
@@ -49,7 +49,6 @@ def test_update_rom(rename_fs_rom_mock, get_rom_by_id_mock, client, access_token
response = client.put(
f"/api/roms/{rom.id}",
headers={"Authorization": f"Bearer {access_token}"},
- params={"rename_as_source": True},
data={
"igdb_id": "236663",
"name": "Metroid Prime Remastered",
diff --git a/backend/handler/filesystem/base_handler.py b/backend/handler/filesystem/base_handler.py
index 297b3b017..64de4f8d8 100644
--- a/backend/handler/filesystem/base_handler.py
+++ b/backend/handler/filesystem/base_handler.py
@@ -101,14 +101,13 @@ class FSHandler:
file_name_no_extension = self.get_file_name_with_no_extension(file_name)
return TAG_REGEX.split(file_name_no_extension)[0].strip()
- def parse_file_extension(self, file_name) -> str:
+ def parse_file_extension(self, file_name: str) -> str:
match = EXTENSION_REGEX.search(file_name)
return match.group(1) if match else ""
- def _exclude_files(self, files, filetype) -> list[str]:
- cnfg = cm.get_config()
- excluded_extensions = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_EXT")
- excluded_names = getattr(cnfg, f"EXCLUDED_{filetype.upper()}_FILES")
+ def exclude_single_files(self, files: list[str]) -> list[str]:
+ excluded_extensions = cm.get_config().EXCLUDED_SINGLE_EXT
+ excluded_names = cm.get_config().EXCLUDED_SINGLE_FILES
excluded_files: list = []
for file_name in files:
@@ -120,10 +119,9 @@ class FSHandler:
excluded_files.append(file_name)
# Additionally, check if the file name mathes a pattern in the excluded list.
- if len(excluded_names) > 0:
- for name in excluded_names:
- if file_name == name or fnmatch.fnmatch(file_name, name):
- excluded_files.append(file_name)
+ for name in excluded_names:
+ if file_name == name or fnmatch.fnmatch(file_name, name):
+ excluded_files.append(file_name)
# Return files that are not in the filtered list.
return [f for f in files if f not in excluded_files]
diff --git a/backend/handler/filesystem/firmware_handler.py b/backend/handler/filesystem/firmware_handler.py
index 82b70d798..897366a82 100644
--- a/backend/handler/filesystem/firmware_handler.py
+++ b/backend/handler/filesystem/firmware_handler.py
@@ -43,7 +43,7 @@ class FSFirmwareHandler(FSHandler):
except IndexError as exc:
raise FirmwareNotFoundException(platform_fs_slug) from exc
- return [f for f in self._exclude_files(fs_firmware_files, "single")]
+ return [f for f in self.exclude_single_files(fs_firmware_files)]
def get_firmware_file_size(self, firmware_path: str, file_name: str):
files = [f"{LIBRARY_BASE_PATH}/{firmware_path}/{file_name}"]
diff --git a/backend/handler/filesystem/roms_handler.py b/backend/handler/filesystem/roms_handler.py
index c4c065d51..d846d26cf 100644
--- a/backend/handler/filesystem/roms_handler.py
+++ b/backend/handler/filesystem/roms_handler.py
@@ -1,5 +1,6 @@
import binascii
import bz2
+import fnmatch
import hashlib
import os
import re
@@ -274,11 +275,27 @@ class FSRomsHandler(FSHandler):
abs_fs_path = f"{LIBRARY_BASE_PATH}/{roms_path}" # Absolute path to roms
rom_files: list[RomFile] = []
+ excluded_file_names = cm.get_config().EXCLUDED_MULTI_PARTS_FILES
+ excluded_file_exts = cm.get_config().EXCLUDED_MULTI_PARTS_EXT
+
# Check if rom is a multi-part rom
if os.path.isdir(f"{abs_fs_path}/{rom}"):
- for f_path, file in iter_files(f"{abs_fs_path}/{rom}", recursive=True):
+ for f_path, file_name in iter_files(f"{abs_fs_path}/{rom}", recursive=True):
+ # Check if file is excluded
+ ext = self.parse_file_extension(file_name)
+ if not ext or ext in excluded_file_exts:
+ continue
+
+ if any(
+ file_name == exc_name or fnmatch.fnmatch(file_name, exc_name)
+ for exc_name in excluded_file_names
+ ):
+ continue
+
rom_files.append(
- self._build_rom_file(f_path.relative_to(LIBRARY_BASE_PATH), file)
+ self._build_rom_file(
+ f_path.relative_to(LIBRARY_BASE_PATH), file_name
+ )
)
else:
rom_files.append(self._build_rom_file(Path(roms_path), rom))
@@ -433,7 +450,7 @@ class FSRomsHandler(FSHandler):
fs_roms: list[dict] = [
{"multi": False, "fs_name": rom}
- for rom in self._exclude_files(fs_single_roms, "single")
+ for rom in self.exclude_single_files(fs_single_roms)
] + [
{"multi": True, "fs_name": rom}
for rom in self._exclude_multi_roms(fs_multi_roms)
diff --git a/backend/handler/filesystem/tests/test_fs.py b/backend/handler/filesystem/tests/test_fs.py
index 1cc9d9192..ba4178c3c 100644
--- a/backend/handler/filesystem/tests/test_fs.py
+++ b/backend/handler/filesystem/tests/test_fs.py
@@ -39,7 +39,7 @@ def test_get_roms():
assert roms[1]["multi"]
-def test_exclude_files():
+def testexclude_single_files():
from config.config_manager import ConfigManager
empty_config_file = os.path.join(
@@ -52,50 +52,46 @@ def test_exclude_files():
cm.add_exclusion("EXCLUDED_SINGLE_FILES", "Super Mario 64 (J) (Rev A) [Part 1].z64")
- filtered_files = fs_rom_handler._exclude_files(
+ filtered_files = fs_rom_handler.exclude_single_files(
files=[
"Super Mario 64 (J) (Rev A) [Part 1].z64",
"Super Mario 64 (J) (Rev A) [Part 2].z64",
],
- filetype="single",
)
assert len(filtered_files) == 1
cm.add_exclusion("EXCLUDED_SINGLE_EXT", "z64")
- filtered_files = fs_rom_handler._exclude_files(
+ filtered_files = fs_rom_handler.exclude_single_files(
files=[
"Super Mario 64 (J) (Rev A) [Part 1].z64",
"Super Mario 64 (J) (Rev A) [Part 2].z64",
],
- filetype="single",
)
assert len(filtered_files) == 0
cm.add_exclusion("EXCLUDED_SINGLE_FILES", "*.z64")
- filtered_files = fs_rom_handler._exclude_files(
+ filtered_files = fs_rom_handler.exclude_single_files(
files=[
"Super Mario 64 (J) (Rev A) [Part 1].z64",
"Super Mario 64 (J) (Rev A) [Part 2].z64",
],
- filetype="single",
)
assert len(filtered_files) == 0
cm.add_exclusion("EXCLUDED_SINGLE_FILES", "_.*")
- filtered_files = fs_rom_handler._exclude_files(
+ filtered_files = fs_rom_handler.exclude_single_files(
files=[
"Links Awakening.nsp",
"_.Links Awakening.nsp",
"Kirby's Adventure.nsp",
"_.Kirby's Adventure.nsp",
],
- filetype="single",
)
assert len(filtered_files) == 2
diff --git a/backend/handler/metadata/moby_handler.py b/backend/handler/metadata/moby_handler.py
index 6a8f4c9f7..02fd309b8 100644
--- a/backend/handler/metadata/moby_handler.py
+++ b/backend/handler/metadata/moby_handler.py
@@ -52,7 +52,6 @@ class MobyMetadata(TypedDict):
class MobyGamesRom(TypedDict):
moby_id: int | None
- slug: NotRequired[str]
name: NotRequired[str]
summary: NotRequired[str]
url_cover: NotRequired[str]
@@ -280,7 +279,6 @@ class MobyGamesHandler(MetadataHandler):
rom = {
"moby_id": res["game_id"],
"name": res["title"],
- "slug": res["moby_url"].split("/")[-1],
"summary": res.get("description", ""),
"url_cover": pydash.get(res, "sample_cover.image", ""),
"url_screenshots": [s["image"] for s in res.get("sample_screenshots", [])],
@@ -303,7 +301,6 @@ class MobyGamesHandler(MetadataHandler):
rom = {
"moby_id": res["game_id"],
"name": res["title"],
- "slug": res["moby_url"].split("/")[-1],
"summary": res.get("description", None),
"url_cover": pydash.get(res, "sample_cover.image", None),
"url_screenshots": [s["image"] for s in res.get("sample_screenshots", [])],
@@ -341,7 +338,6 @@ class MobyGamesHandler(MetadataHandler):
for k, v in {
"moby_id": rom["game_id"],
"name": rom["title"],
- "slug": rom["moby_url"].split("/")[-1],
"summary": rom.get("description", ""),
"url_cover": pydash.get(rom, "sample_cover.image", ""),
"url_screenshots": [
diff --git a/backend/handler/metadata/ss_handler.py b/backend/handler/metadata/ss_handler.py
index 043ab096a..9b28ae94b 100644
--- a/backend/handler/metadata/ss_handler.py
+++ b/backend/handler/metadata/ss_handler.py
@@ -129,7 +129,6 @@ class SSMetadata(TypedDict):
class SSRom(TypedDict):
ss_id: int | None
- slug: NotRequired[str]
name: NotRequired[str]
summary: NotRequired[str]
url_cover: NotRequired[str]
@@ -422,35 +421,44 @@ class SSHandler(MetadataHandler):
if res:
break
- if not res or not res.get("id", None):
+ if not res:
return fallback_rom
- ss_id: int = int(res.get("id", None))
+ res_ss_id = res.get("id", None)
+ if not res_ss_id:
+ return fallback_rom
- rom = {
- "ss_id": ss_id,
- "name": pydash.chain(res.get("noms", []))
+ ss_id: int = int(res_ss_id)
+
+ res_name = (
+ pydash.chain(res.get("noms", []))
.filter({"region": "ss"})
.map("text")
.head()
- .value(),
- "slug": pydash.chain(res.get("noms", []))
- .filter({"region": "ss"})
- .map("text")
- .head()
- .value(),
- "summary": pydash.chain(res.get("synopsis", []))
+ .value()
+ )
+ res_summary = (
+ pydash.chain(res.get("synopsis", []))
.filter({"langue": "en"})
.map("text")
.head()
- .value(),
- "url_cover": pydash.chain(res.get("medias", []))
+ .value()
+ )
+ res_url_cover = (
+ pydash.chain(res.get("medias", []))
.filter({"region": "us", "type": "box-2D", "parent": "jeu"})
.map("url")
.head()
.value()
- or "",
- "url_manual": pydash.chain(res.get("medias", []))
+ or pydash.chain(res.get("medias", []))
+ .filter({"region": "ss", "type": "box-2D", "parent": "jeu"})
+ .map("url")
+ .head()
+ .value()
+ or ""
+ )
+ res_url_manual = (
+ pydash.chain(res.get("medias", []))
.filter(
{"region": "us", "type": "manuel", "parent": "jeu", "format": "pdf"}
)
@@ -464,7 +472,15 @@ class SSHandler(MetadataHandler):
.map("url")
.head()
.value()
- or "",
+ or ""
+ )
+
+ rom = {
+ "ss_id": ss_id,
+ "name": res_name,
+ "summary": res_summary,
+ "url_cover": res_url_cover,
+ "url_manual": res_url_manual,
"url_screenshots": [],
"ss_metadata": extract_metadata_from_ss_rom(res),
}
@@ -481,30 +497,35 @@ class SSHandler(MetadataHandler):
if not res:
return SSRom(ss_id=None)
- rom = {
- "ss_id": res.get("id"),
- "name": pydash.chain(res.get("noms", []))
+ res_name = (
+ pydash.chain(res.get("noms", []))
.filter({"region": "ss"})
.map("text")
.head()
- .value(),
- "slug": pydash.chain(res.get("noms", []))
- .filter({"region": "ss"})
- .map("text")
- .head()
- .value(),
- "summary": pydash.chain(res.get("synopsis", []))
+ .value()
+ )
+ res_summary = (
+ pydash.chain(res.get("synopsis", []))
.filter({"langue": "en"})
.map("text")
.head()
- .value(),
- "url_cover": pydash.chain(res.get("medias", []))
+ .value()
+ )
+ res_url_cover = (
+ pydash.chain(res.get("medias", []))
.filter({"region": "us", "type": "box-2D", "parent": "jeu"})
.map("url")
.head()
.value()
- or "",
- "url_manual": pydash.chain(res.get("medias", []))
+ or pydash.chain(res.get("medias", []))
+ .filter({"region": "ss", "type": "box-2D", "parent": "jeu"})
+ .map("url")
+ .head()
+ .value()
+ or ""
+ )
+ res_url_manual = (
+ pydash.chain(res.get("medias", []))
.filter(
{"region": "us", "type": "manuel", "parent": "jeu", "format": "pdf"}
)
@@ -518,7 +539,15 @@ class SSHandler(MetadataHandler):
.map("url")
.head()
.value()
- or "",
+ or ""
+ )
+
+ rom = {
+ "ss_id": res.get("id"),
+ "name": res_name,
+ "summary": res_summary,
+ "url_cover": res_url_cover,
+ "url_manual": res_url_manual,
"url_screenshots": [],
"ss_metadata": extract_metadata_from_ss_rom(res),
}
@@ -533,7 +562,7 @@ class SSHandler(MetadataHandler):
return rom if rom.get("ss_id", "") else None
async def get_matched_roms_by_name(
- self, search_term: str, platform_ss_id: int
+ self, search_term: str, platform_ss_id: int | None
) -> list[SSRom]:
if not SS_API_ENABLED:
return []
@@ -559,15 +588,6 @@ class SSHandler(MetadataHandler):
.value()
)
- def _get_slug(rom: dict) -> str | None:
- return (
- pydash.chain(rom.get("noms", []))
- .filter({"region": "ss"})
- .map("text")
- .head()
- .value()
- )
-
def _get_summary(rom: dict) -> str | None:
return (
pydash.chain(rom.get("synopsis", []))
@@ -584,6 +604,11 @@ class SSHandler(MetadataHandler):
.map("url")
.head()
.value()
+ or pydash.chain(rom.get("medias", []))
+ .filter({"region": "ss", "type": "box-2D", "parent": "jeu"})
+ .map("url")
+ .head()
+ .value()
or ""
)
@@ -632,7 +657,6 @@ class SSHandler(MetadataHandler):
for k, v in {
"ss_id": rom.get("id"),
"name": _get_name(rom),
- "slug": _get_slug(rom),
"summary": _get_summary(rom),
"url_cover": _get_url_cover(rom),
"url_manual": _get_url_manual(rom),
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index c42e4754b..6bb36c8eb 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -14,7 +14,7 @@
"cronstrue": "^2.57.0",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
- "md-editor-v3": "^5.4.5",
+ "md-editor-v3": "^5.5.0",
"mitt": "^3.0.1",
"nanoid": "^5.1.4",
"pinia": "^3.0.1",
@@ -6316,10 +6316,9 @@
}
},
"node_modules/md-editor-v3": {
- "version": "5.4.5",
- "resolved": "https://registry.npmjs.org/md-editor-v3/-/md-editor-v3-5.4.5.tgz",
- "integrity": "sha512-Vw0mjlhGArlCt5aG15a2/W3BoQJLCACUGROgR64uVvRzepsh/26Pj1C3oGlU2lis4FrlnX7rzAN+m8g76eVFRA==",
- "license": "MIT",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/md-editor-v3/-/md-editor-v3-5.5.0.tgz",
+ "integrity": "sha512-+YVHwbcUh7nPn5i+tD2QLQBuDlShJAuEzkhkFMJpN+UYMjfQG27rE3+cMfArP1C0xodpf4POn32jr7MlOfFHdA==",
"dependencies": {
"@codemirror/lang-markdown": "^6.3.0",
"@codemirror/language-data": "^6.5.1",
diff --git a/frontend/package.json b/frontend/package.json
index 29b4ae907..4df6ead69 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -32,7 +32,7 @@
"cronstrue": "^2.57.0",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
- "md-editor-v3": "^5.4.5",
+ "md-editor-v3": "^5.5.0",
"mitt": "^3.0.1",
"nanoid": "^5.1.4",
"pinia": "^3.0.1",
diff --git a/frontend/src/components/Details/Info/FileInfo.vue b/frontend/src/components/Details/Info/FileInfo.vue
index f3c19ccd1..476fa731f 100644
--- a/frontend/src/components/Details/Info/FileInfo.vue
+++ b/frontend/src/components/Details/Info/FileInfo.vue
@@ -170,16 +170,19 @@ watch(
class="my-1 text-grey-lighten-2"
style="padding: 10px 14px"
@click="toggleMainSibling"
- >
+ {{
+ >
+ {{
romUser.is_main_sibling
? "mdi-checkbox-outline"
: "mdi-checkbox-blank-outline"
- }}{{ romUser.is_main_sibling ? "" : t("rom.default") }}
+ }}
+
+ {{ romUser.is_main_sibling ? "" : t("rom.default") }}
+
diff --git a/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue b/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue
index 4c89b900d..3e7c4b9b7 100644
--- a/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue
+++ b/frontend/src/components/Settings/Administration/Users/Dialog/CreateUser.vue
@@ -5,9 +5,11 @@ import storeUsers from "@/stores/users";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { inject, ref } from "vue";
+import { useI18n } from "vue-i18n";
import { useDisplay } from "vuetify";
// Props
+const { t } = useI18n();
const user = ref({
username: "",
password: "",
@@ -60,7 +62,7 @@ function closeDialog() {
@@ -109,14 +111,16 @@ function closeDialog() {
- Cancel
+
+ {{ t("common.cancel") }}
+
- Create
+ {{ t("common.create") }}
diff --git a/frontend/src/components/Settings/Administration/Users/Dialog/EditUser.vue b/frontend/src/components/Settings/Administration/Users/Dialog/EditUser.vue
index 35ebfd31c..49dcc5cbb 100644
--- a/frontend/src/components/Settings/Administration/Users/Dialog/EditUser.vue
+++ b/frontend/src/components/Settings/Administration/Users/Dialog/EditUser.vue
@@ -120,7 +120,7 @@ function closeDialog() {
{
async function handleRomUpdate(
options: {
rom: UpdateRom;
- renameAsSource?: boolean;
removeCover?: boolean;
unmatch?: boolean;
},
diff --git a/frontend/src/components/common/Game/Dialog/MatchRom.vue b/frontend/src/components/common/Game/Dialog/MatchRom.vue
index 0fc10b1ac..2999d440c 100644
--- a/frontend/src/components/common/Game/Dialog/MatchRom.vue
+++ b/frontend/src/components/common/Game/Dialog/MatchRom.vue
@@ -37,7 +37,7 @@ const matchedRoms = ref([]);
const filteredMatchedRoms = ref();
const emitter = inject>("emitter");
const showSelectSource = ref(false);
-const renameAsSource = ref(false);
+const renameFromSource = ref(false);
const selectedMatchRom = ref();
const selectedCover = ref();
const sources = ref([]);
@@ -182,7 +182,7 @@ function confirm() {
}
function toggleRenameAsSource() {
- renameAsSource.value = !renameAsSource.value;
+ renameFromSource.value = !renameFromSource.value;
}
function backToMatched() {
@@ -190,7 +190,7 @@ function backToMatched() {
selectedCover.value = undefined;
selectedMatchRom.value = undefined;
sources.value = [];
- renameAsSource.value = false;
+ renameFromSource.value = false;
}
async function updateRom(
@@ -205,16 +205,20 @@ async function updateRom(
// Set the properties from the selected rom
rom.value = {
...rom.value,
+ fs_name:
+ renameFromSource.value && selectedMatchRom.value
+ ? `${selectedMatchRom.value.name}.${rom.value.fs_extension}`
+ : rom.value.fs_name,
igdb_id: selectedRom.igdb_id || null,
moby_id: selectedRom.moby_id || null,
ss_id: selectedRom.ss_id || null,
- name: selectedRom.name,
- slug: selectedRom.slug,
- summary: selectedRom.summary,
+ name: selectedRom.name || null,
+ slug: selectedRom.slug || null,
+ summary: selectedRom.summary || null,
url_cover:
urlCover ||
- selectedRom.ss_url_cover ||
selectedRom.igdb_url_cover ||
+ selectedRom.ss_url_cover ||
selectedRom.moby_url_cover ||
null,
};
@@ -225,7 +229,7 @@ async function updateRom(
}
await romApi
- .updateRom({ rom: rom.value, renameAsSource: renameAsSource.value })
+ .updateRom({ rom: rom.value })
.then(({ data }) => {
emitter?.emit("snackbarShow", {
msg: "Rom updated successfully!",
@@ -256,7 +260,7 @@ function closeDialog() {
showSelectSource.value = false;
selectedCover.value = undefined;
selectedMatchRom.value = undefined;
- renameAsSource.value = false;
+ renameFromSource.value = false;
}
onBeforeUnmount(() => {
@@ -499,16 +503,16 @@ onBeforeUnmount(() => {
-
+
{{
- selectedCover && renameAsSource
+ selectedCover && renameFromSource
? "mdi-checkbox-outline"
: "mdi-checkbox-blank-outline"
}} {
t("rom.rename-file-part1", { source: selectedCover?.name })
}}
-
+
{{ t("rom.rename-file-part2") }}
{{ t("rom.rename-file-part3") }}{{ rom?.fs_name_no_tags }}.{{ rom?.fs_extension }}
+ >{{ rom.fs_name }}
{{ t("rom.rename-file-part4") }}{{ selectedMatchRom?.name }}.{{ rom?.fs_extension }}{{ selectedMatchRom.name }}.{{ rom.fs_extension }}
{
@@ -213,7 +211,6 @@ async function updateRom({
return api.put(`/roms/${rom.id}`, formData, {
params: {
- rename_as_source: renameAsSource,
remove_cover: removeCover,
unmatch_metadata: unmatch,
},
diff --git a/frontend/src/views/Auth/Setup.vue b/frontend/src/views/Auth/Setup.vue
index be998517b..7d39e0234 100644
--- a/frontend/src/views/Auth/Setup.vue
+++ b/frontend/src/views/Auth/Setup.vue
@@ -9,8 +9,10 @@ import { ROUTES } from "@/plugins/router";
import type { Emitter } from "mitt";
import { computed, inject, ref } from "vue";
import { useDisplay } from "vuetify";
+import { useI18n } from "vue-i18n";
// Props
+const { t } = useI18n();
const { xs } = useDisplay();
const emitter = inject>("emitter");
const heartbeat = storeHeartbeat();
@@ -121,7 +123,7 @@ async function finishWizard() {
{