mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Merge remote-tracking branch 'origin/master' into feature/retroachievements
This commit is contained in:
@@ -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
|
||||
|
||||
92
backend/alembic/versions/0038_add_ssid_to_sibling_roms.py
Normal file
92
backend/alembic/versions/0038_add_ssid_to_sibling_roms.py
Normal file
@@ -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
|
||||
),
|
||||
)
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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}"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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),
|
||||
|
||||
9
frontend/package-lock.json
generated
9
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -170,16 +170,19 @@ watch(
|
||||
class="my-1 text-grey-lighten-2"
|
||||
style="padding: 10px 14px"
|
||||
@click="toggleMainSibling"
|
||||
><v-icon
|
||||
>
|
||||
<v-icon
|
||||
:class="romUser.is_main_sibling ? '' : 'mr-1'"
|
||||
:color="romUser.is_main_sibling ? 'primary' : ''"
|
||||
>{{
|
||||
>
|
||||
{{
|
||||
romUser.is_main_sibling
|
||||
? "mdi-checkbox-outline"
|
||||
: "mdi-checkbox-blank-outline"
|
||||
}}</v-icon
|
||||
>{{ romUser.is_main_sibling ? "" : t("rom.default") }}</v-btn
|
||||
>
|
||||
}}
|
||||
</v-icon>
|
||||
{{ romUser.is_main_sibling ? "" : t("rom.default") }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-row>
|
||||
|
||||
@@ -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() {
|
||||
<v-text-field
|
||||
v-model="user.username"
|
||||
variant="outlined"
|
||||
label="username"
|
||||
:label="t('settings.username')"
|
||||
required
|
||||
hide-details
|
||||
clearable
|
||||
@@ -72,7 +74,7 @@ function closeDialog() {
|
||||
<v-text-field
|
||||
v-model="user.password"
|
||||
variant="outlined"
|
||||
label="Password"
|
||||
:label="t('settings.password')"
|
||||
required
|
||||
hide-details
|
||||
clearable
|
||||
@@ -84,7 +86,7 @@ function closeDialog() {
|
||||
<v-text-field
|
||||
v-model="user.email"
|
||||
variant="outlined"
|
||||
label="email"
|
||||
:label="t('settings.email')"
|
||||
required
|
||||
hide-details
|
||||
clearable
|
||||
@@ -97,7 +99,7 @@ function closeDialog() {
|
||||
v-model="user.role"
|
||||
variant="outlined"
|
||||
:items="['viewer', 'editor', 'admin']"
|
||||
label="Role"
|
||||
:label="t('settings.role')"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
@@ -109,14 +111,16 @@ function closeDialog() {
|
||||
<template #append>
|
||||
<v-row class="justify-center mb-2" no-gutters>
|
||||
<v-btn-group divided density="compact">
|
||||
<v-btn class="bg-toplayer" @click="closeDialog"> Cancel </v-btn>
|
||||
<v-btn class="bg-toplayer" @click="closeDialog">
|
||||
{{ t("common.cancel") }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!user.username || !user.password"
|
||||
:variant="!user.username || !user.password ? 'plain' : 'flat'"
|
||||
class="text-romm-green bg-toplayer"
|
||||
@click="createUser()"
|
||||
>
|
||||
Create
|
||||
{{ t("common.create") }}
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
</v-row>
|
||||
|
||||
@@ -120,7 +120,7 @@ function closeDialog() {
|
||||
<v-text-field
|
||||
v-model="user.email"
|
||||
variant="outlined"
|
||||
label="email"
|
||||
:label="t('settings.email')"
|
||||
required
|
||||
hide-details
|
||||
clearable
|
||||
|
||||
@@ -87,7 +87,6 @@ const noMetadataMatch = computed(() => {
|
||||
async function handleRomUpdate(
|
||||
options: {
|
||||
rom: UpdateRom;
|
||||
renameAsSource?: boolean;
|
||||
removeCover?: boolean;
|
||||
unmatch?: boolean;
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ const matchedRoms = ref<SearchRomSchema[]>([]);
|
||||
const filteredMatchedRoms = ref<SearchRomSchema[]>();
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
const showSelectSource = ref(false);
|
||||
const renameAsSource = ref(false);
|
||||
const renameFromSource = ref(false);
|
||||
const selectedMatchRom = ref<SearchRomSchema>();
|
||||
const selectedCover = ref<MatchedSource>();
|
||||
const sources = ref<MatchedSource[]>([]);
|
||||
@@ -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(() => {
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-col cols="12" v-if="selectedMatchRom">
|
||||
<v-row class="mt-4 text-center" no-gutters>
|
||||
<v-col>
|
||||
<v-chip
|
||||
@click="toggleRenameAsSource"
|
||||
:variant="renameAsSource ? 'flat' : 'outlined'"
|
||||
:color="renameAsSource ? 'primary' : ''"
|
||||
:variant="renameFromSource ? 'flat' : 'outlined'"
|
||||
:color="renameFromSource ? 'primary' : ''"
|
||||
:disabled="selectedCover == undefined"
|
||||
><v-icon class="mr-1">{{
|
||||
selectedCover && renameAsSource
|
||||
selectedCover && renameFromSource
|
||||
? "mdi-checkbox-outline"
|
||||
: "mdi-checkbox-blank-outline"
|
||||
}}</v-icon
|
||||
@@ -516,17 +520,15 @@ onBeforeUnmount(() => {
|
||||
t("rom.rename-file-part1", { source: selectedCover?.name })
|
||||
}}</v-chip
|
||||
>
|
||||
<v-list-item v-if="renameAsSource" class="mt-2">
|
||||
<v-list-item v-if="rom && renameFromSource" class="mt-2">
|
||||
<span>{{ t("rom.rename-file-part2") }}</span>
|
||||
<br />
|
||||
<span>{{ t("rom.rename-file-part3") }}</span
|
||||
><span class="text-primary ml-1"
|
||||
>{{ rom?.fs_name_no_tags }}.{{ rom?.fs_extension }}</span
|
||||
>
|
||||
><span class="text-primary ml-1">{{ rom.fs_name }}</span>
|
||||
<br />
|
||||
<span class="mx-1">{{ t("rom.rename-file-part4") }}</span
|
||||
><span class="text-secondary"
|
||||
>{{ selectedMatchRom?.name }}.{{ rom?.fs_extension }}</span
|
||||
>{{ selectedMatchRom.name }}.{{ rom.fs_extension }}</span
|
||||
>
|
||||
<br />
|
||||
<span class="text-caption font-italic font-weight-bold"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Versteckt",
|
||||
"info": "Info",
|
||||
"languages": "Sprachen",
|
||||
"manual": "Benutzerhandbuch",
|
||||
"manual": "Handbuch",
|
||||
"manual-match": "Manuell zuweisen",
|
||||
"my-notes": "Meine Notizen",
|
||||
"no-metadata-source": "Keine Quelle für Metadaten aktiv",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Weiterspielen als Raster",
|
||||
"continue-playing-as-grid-desc": "Zeige die Weiterspielen-Karten als Raster auf der Startseite",
|
||||
"email": "E-Mail",
|
||||
"excluded": "Ausnahmen",
|
||||
"excluded-multi-rom-files": "Unterverzeichnisse in Plattform-Verzeichnissen",
|
||||
"excluded-multi-rom-parts-extensions": "Dateiendungen in Unterverzeichnisse in Plattform-Verzeichnissen",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Hidden",
|
||||
"info": "Info",
|
||||
"languages": "Languages",
|
||||
"manual": "User manual",
|
||||
"manual": "Manual",
|
||||
"manual-match": "Manual match",
|
||||
"my-notes": "My notes",
|
||||
"no-metadata-source": "No metadata source enabled",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Continue playing as grid",
|
||||
"continue-playing-as-grid-desc": "View continue playing rom cards as a grid at the home page",
|
||||
"email": "Email",
|
||||
"excluded": "Excluded",
|
||||
"excluded-multi-rom-files": "Multi rom files",
|
||||
"excluded-multi-rom-parts-extensions": "Multi rom parts extensions",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Hidden",
|
||||
"info": "Info",
|
||||
"languages": "Languages",
|
||||
"manual": "User manual",
|
||||
"manual": "Manual",
|
||||
"manual-match": "Manual match",
|
||||
"my-notes": "My notes",
|
||||
"no-metadata-source": "No metadata source enabled",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Continue playing as grid",
|
||||
"continue-playing-as-grid-desc": "View continue playing rom cards as a grid at the home page",
|
||||
"email": "Email",
|
||||
"excluded": "Excluded",
|
||||
"excluded-multi-rom-files": "Multi rom files",
|
||||
"excluded-multi-rom-parts-extensions": "Multi rom parts extensions",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Oculto",
|
||||
"info": "Info",
|
||||
"languages": "Idiomas",
|
||||
"manual": "Manual de usuario",
|
||||
"manual": "Manual",
|
||||
"manual-match": "Identificación manual",
|
||||
"my-notes": "Mis notas",
|
||||
"no-metadata-source": "No hay fuente de metadatos habilitada",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Continuar jugando como cuadrícula",
|
||||
"continue-playing-as-grid-desc": "Ver tarjetas de continuar jugando como cuadrícula en la página principal",
|
||||
"email": "Correo electrónico",
|
||||
"excluded": "Exclusiones",
|
||||
"excluded-multi-rom-files": "Roms multi fichero",
|
||||
"excluded-multi-rom-parts-extensions": "Extensiones de partes de roms multi fichero",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Caché",
|
||||
"info": "Info",
|
||||
"languages": "Langues",
|
||||
"manual": "Manuel utilisateur",
|
||||
"manual": "Manuel",
|
||||
"manual-match": "Correspondance manuelle",
|
||||
"my-notes": "Mes notes",
|
||||
"no-metadata-source": "Aucune source de métadonnées activée",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Continuer à jouer en grille",
|
||||
"continue-playing-as-grid-desc": "Voir les cartes de continuer à jouer en grille sur la page d'accueil",
|
||||
"email": "E-mail",
|
||||
"excluded": "Exclu",
|
||||
"excluded-multi-rom-files": "Fichiers de rom multiples",
|
||||
"excluded-multi-rom-parts-extensions": "Extensions de parties de rom multiples",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Nascosto",
|
||||
"info": "Info",
|
||||
"languages": "Lingue",
|
||||
"manual": "Manuale utente",
|
||||
"manual": "Manuale",
|
||||
"manual-match": "Associazione manuale",
|
||||
"my-notes": "Mie note",
|
||||
"no-metadata-source": "Nessuna fonte di metadati abilitata",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "\"Continua a giocare\" come griglia",
|
||||
"continue-playing-as-grid-desc": "Visualizza le rom di \"Continua a giocare\" come una griglia nella home",
|
||||
"email": "Email",
|
||||
"excluded": "Escluso",
|
||||
"excluded-multi-rom-files": "File rom multiple",
|
||||
"excluded-multi-rom-parts-extensions": "Estensioni parti di rom multiple",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "非表示",
|
||||
"info": "詳細",
|
||||
"languages": "言語",
|
||||
"manual": "ユーザーマニュアル",
|
||||
"manual": "マニュアル",
|
||||
"manual-match": "手動マッチ",
|
||||
"my-notes": "メモ",
|
||||
"no-metadata-source": "メタデータ検索が無効です",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "プレイを継続をグリッドで表示",
|
||||
"continue-playing-as-grid-desc": "ホームのプレイを継続をグリッドで表示",
|
||||
"email": "メール",
|
||||
"excluded": "除外",
|
||||
"excluded-multi-rom-files": "複数ファイル",
|
||||
"excluded-multi-rom-parts-extensions": "複数ファイルの拡張子",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "숨김",
|
||||
"info": "정보",
|
||||
"languages": "언어",
|
||||
"manual": "사용자 설명서",
|
||||
"manual": "매뉴얼",
|
||||
"manual-match": "수동으로 DB 대응",
|
||||
"my-notes": "개인 노트",
|
||||
"no-metadata-source": "메타데이터 DB 연동 안됨",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "계속 플레이하기 그리드 뷰",
|
||||
"continue-playing-as-grid-desc": "홈페이지에 계속 플레이하기를 그리드 뷰로 보여줍니다",
|
||||
"email": "이메일",
|
||||
"excluded": "제외됨",
|
||||
"excluded-multi-rom-files": "멀티 롬 파일",
|
||||
"excluded-multi-rom-parts-extensions": "멀티 롬 부분 확장자",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Oculto",
|
||||
"info": "Informações",
|
||||
"languages": "Idiomas",
|
||||
"manual": "Manual do usuário",
|
||||
"manual": "Manual",
|
||||
"manual-match": "Correspondência manual",
|
||||
"my-notes": "Minhas notas",
|
||||
"no-metadata-source": "Nenhuma fonte de metadados habilitada",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Continuar jogando como grade",
|
||||
"continue-playing-as-grid-desc": "Ver cartões de continuar jogando como uma grade na página inicial",
|
||||
"email": "E-mail",
|
||||
"excluded": "Excluído",
|
||||
"excluded-multi-rom-files": "Arquivos de rom múltiplos",
|
||||
"excluded-multi-rom-parts-extensions": "Extensões de partes de rom múltiplos",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Ascuns",
|
||||
"info": "Informații",
|
||||
"languages": "Limbi",
|
||||
"manual": "Manual utilizator",
|
||||
"manual": "Manual",
|
||||
"manual-match": "Potrivire manuală",
|
||||
"my-notes": "Notițele mele",
|
||||
"no-metadata-source": "Nicio sursă de metadate activată",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Continuă jocul în grilă",
|
||||
"continue-playing-as-grid-desc": "Vezi cardurile „Continuă jocul” în grilă pe pagina principală",
|
||||
"email": "Email",
|
||||
"excluded": "Exclus",
|
||||
"excluded-multi-rom-files": "Fișiere rom multiple",
|
||||
"excluded-multi-rom-parts-extensions": "Extensii părți rom multiple",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "Скрыто",
|
||||
"info": "Информация",
|
||||
"languages": "Языки",
|
||||
"manual": "Руководство пользователя",
|
||||
"manual": "Руководство",
|
||||
"manual-match": "Ручное совпадение",
|
||||
"my-notes": "Мои заметки",
|
||||
"no-metadata-source": "Источник метаданных не включен",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "Продолжение игры в виде сетки",
|
||||
"continue-playing-as-grid-desc": "Просмотр карточек продолжения игры в виде сетки на главной странице",
|
||||
"email": "Электронная почта",
|
||||
"excluded": "Исключено",
|
||||
"excluded-multi-rom-files": "Многосоставные файлы ромов",
|
||||
"excluded-multi-rom-parts-extensions": "Расширения частей многосоставных ромов",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"hidden": "隐藏",
|
||||
"info": "信息",
|
||||
"languages": "语言",
|
||||
"manual": "用户手册",
|
||||
"manual": "手册",
|
||||
"manual-match": "手动匹配",
|
||||
"my-notes": "我的笔记",
|
||||
"name": "名称",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"continue-playing-as-grid": "继续游玩(网格)",
|
||||
"continue-playing-as-grid-desc": "以网格形式在主页上显示继续游玩(卡片)部分",
|
||||
"email": "电子邮件",
|
||||
"excluded": "排除",
|
||||
"excluded-multi-rom-files": "多个 Rom 文件",
|
||||
"excluded-multi-rom-parts-extensions": "多个 Rom 部分扩展",
|
||||
|
||||
@@ -192,12 +192,10 @@ export type UpdateRom = SimpleRom & {
|
||||
|
||||
async function updateRom({
|
||||
rom,
|
||||
renameAsSource = false,
|
||||
removeCover = false,
|
||||
unmatch = false,
|
||||
}: {
|
||||
rom: UpdateRom;
|
||||
renameAsSource?: boolean;
|
||||
removeCover?: boolean;
|
||||
unmatch?: boolean;
|
||||
}): Promise<{ data: DetailedRom }> {
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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<Events>>("emitter");
|
||||
const heartbeat = storeHeartbeat();
|
||||
@@ -121,7 +123,7 @@ async function finishWizard() {
|
||||
<v-form @submit.prevent>
|
||||
<v-text-field
|
||||
v-model="defaultAdminUser.username"
|
||||
label="Username *"
|
||||
:label="`${t('settings.username')} *`"
|
||||
type="text"
|
||||
required
|
||||
autocomplete="on"
|
||||
@@ -130,7 +132,7 @@ async function finishWizard() {
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="defaultAdminUser.email"
|
||||
label="Email"
|
||||
:label="`${t('settings.email')} *`"
|
||||
type="text"
|
||||
required
|
||||
autocomplete="on"
|
||||
@@ -139,7 +141,7 @@ async function finishWizard() {
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="defaultAdminUser.password"
|
||||
label="Password *"
|
||||
:label="`${t('settings.password')} *`"
|
||||
:type="visiblePassword ? 'text' : 'password'"
|
||||
required
|
||||
autocomplete="on"
|
||||
|
||||
@@ -143,7 +143,7 @@ onUnmounted(() => {
|
||||
<v-text-field
|
||||
v-model="userToEdit.email"
|
||||
variant="outlined"
|
||||
label="email"
|
||||
:label="t('settings.email')"
|
||||
required
|
||||
hide-details
|
||||
clearable
|
||||
|
||||
Reference in New Issue
Block a user