mirror of
https://github.com/rommapp/romm.git
synced 2026-06-28 14:56:01 +00:00
Merge pull request #796 from rommapp/792-feature-remove-cover-art
Improved custom art management
This commit is contained in:
@@ -250,6 +250,7 @@ async def update_rom(
|
||||
request: Request,
|
||||
id: int,
|
||||
rename_as_igdb: bool = False,
|
||||
remove_cover: bool = False,
|
||||
artwork: Optional[UploadFile] = File(None),
|
||||
) -> RomSchema:
|
||||
"""Update rom endpoint
|
||||
@@ -317,22 +318,34 @@ async def update_rom(
|
||||
cleaned_data["file_name_no_ext"] = fs_rom_handler.get_file_name_with_no_extension(
|
||||
fs_safe_file_name
|
||||
)
|
||||
cleaned_data.update(
|
||||
fs_resource_handler.get_rom_cover(
|
||||
overwrite=True,
|
||||
platform_fs_slug=platform_fs_slug,
|
||||
rom_name=cleaned_data["name"],
|
||||
url_cover=cleaned_data.get("url_cover", ""),
|
||||
)
|
||||
)
|
||||
|
||||
cleaned_data.update(
|
||||
fs_resource_handler.get_rom_screenshots(
|
||||
platform_fs_slug=platform_fs_slug,
|
||||
rom_name=cleaned_data["name"],
|
||||
url_screenshots=cleaned_data.get("url_screenshots", []),
|
||||
),
|
||||
)
|
||||
if remove_cover:
|
||||
cleaned_data.update(
|
||||
fs_resource_handler.remove_cover(
|
||||
rom_name=cleaned_data["name"], platform_fs_slug=platform_fs_slug
|
||||
)
|
||||
)
|
||||
else:
|
||||
cleaned_data.update(
|
||||
fs_resource_handler.get_rom_cover(
|
||||
overwrite=True,
|
||||
platform_fs_slug=platform_fs_slug,
|
||||
rom_name=cleaned_data["name"],
|
||||
url_cover=cleaned_data.get("url_cover", ""),
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
cleaned_data["igdb_id"] != db_rom.igdb_id
|
||||
or cleaned_data["moby_id"] != db_rom.moby_id
|
||||
):
|
||||
cleaned_data.update(
|
||||
fs_resource_handler.get_rom_screenshots(
|
||||
platform_fs_slug=platform_fs_slug,
|
||||
rom_name=cleaned_data["name"],
|
||||
url_screenshots=cleaned_data.get("url_screenshots", []),
|
||||
),
|
||||
)
|
||||
|
||||
if artwork is not None:
|
||||
file_ext = artwork.filename.split(".")[-1]
|
||||
|
||||
@@ -23,7 +23,8 @@ class FSResourceHandler(FSHandler):
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def _cover_exists(self, fs_slug: str, rom_name: str, size: CoverSize):
|
||||
@staticmethod
|
||||
def _cover_exists(fs_slug: str, rom_name: str, size: CoverSize):
|
||||
"""Check if rom cover exists in filesystem
|
||||
|
||||
Args:
|
||||
@@ -39,7 +40,8 @@ class FSResourceHandler(FSHandler):
|
||||
)
|
||||
)
|
||||
|
||||
def resize_cover(self, cover_path: str, size: CoverSize = CoverSize.BIG) -> None:
|
||||
@staticmethod
|
||||
def resize_cover(cover_path: str, size: CoverSize = CoverSize.BIG) -> None:
|
||||
"""Resizes the cover image to the standard size
|
||||
|
||||
Args:
|
||||
@@ -48,7 +50,6 @@ class FSResourceHandler(FSHandler):
|
||||
"""
|
||||
cover = Image.open(cover_path)
|
||||
if size == CoverSize.BIG and cover.size[1] > DEFAULT_HEIGHT_COVER_L:
|
||||
|
||||
big_dimensions = (DEFAULT_WIDTH_COVER_L, DEFAULT_HEIGHT_COVER_L)
|
||||
background = Image.new("RGBA", big_dimensions, (0, 0, 0, 0))
|
||||
cover.thumbnail(big_dimensions)
|
||||
@@ -86,7 +87,7 @@ class FSResourceHandler(FSHandler):
|
||||
"""
|
||||
cover_file = f"{size.value}.png"
|
||||
cover_path = f"{RESOURCES_BASE_PATH}/{fs_slug}/{rom_name}/cover"
|
||||
|
||||
|
||||
try:
|
||||
res = requests.get(
|
||||
url_cover.replace("t_thumb", f"t_cover_{size.value}"),
|
||||
@@ -99,14 +100,15 @@ class FSResourceHandler(FSHandler):
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Can't connect to IGDB, check your internet connection.",
|
||||
)
|
||||
|
||||
|
||||
if res.status_code == 200:
|
||||
Path(cover_path).mkdir(parents=True, exist_ok=True)
|
||||
with open(f"{cover_path}/{cover_file}", "wb") as f:
|
||||
shutil.copyfileobj(res.raw, f)
|
||||
self.resize_cover(f"{cover_path}/{cover_file}", size)
|
||||
|
||||
def _get_cover_path(self, fs_slug: str, rom_name: str, size: CoverSize):
|
||||
@staticmethod
|
||||
def _get_cover_path(fs_slug: str, rom_name: str, size: CoverSize):
|
||||
"""Returns rom cover filesystem path adapted to frontend folder structure
|
||||
|
||||
Args:
|
||||
@@ -147,18 +149,35 @@ class FSResourceHandler(FSHandler):
|
||||
"path_cover_l": path_cover_l,
|
||||
}
|
||||
|
||||
def build_artwork_path(self, rom_name: str, fs_slug: str, file_ext: str):
|
||||
@staticmethod
|
||||
def remove_cover(
|
||||
rom_name: str,
|
||||
platform_fs_slug: str,
|
||||
):
|
||||
try:
|
||||
shutil.rmtree(
|
||||
os.path.join(RESOURCES_BASE_PATH, platform_fs_slug, rom_name, "cover")
|
||||
)
|
||||
except FileNotFoundError:
|
||||
log.warning(f"Couldn't remove {rom_name} cover")
|
||||
return {"path_cover_s": "", "path_cover_l": ""}
|
||||
|
||||
@staticmethod
|
||||
def build_artwork_path(rom_name: str, platform_fs_slug: str, file_ext: str):
|
||||
q_rom_name = quote(rom_name)
|
||||
|
||||
path_cover_l = f"{fs_slug}/{q_rom_name}/cover/{CoverSize.BIG.value}.{file_ext}"
|
||||
path_cover_s = (
|
||||
f"{fs_slug}/{q_rom_name}/cover/{CoverSize.SMALL.value}.{file_ext}"
|
||||
path_cover_l = (
|
||||
f"{platform_fs_slug}/{q_rom_name}/cover/{CoverSize.BIG.value}.{file_ext}"
|
||||
)
|
||||
artwork_path = f"{RESOURCES_BASE_PATH}/{fs_slug}/{rom_name}/cover"
|
||||
path_cover_s = (
|
||||
f"{platform_fs_slug}/{q_rom_name}/cover/{CoverSize.SMALL.value}.{file_ext}"
|
||||
)
|
||||
artwork_path = f"{RESOURCES_BASE_PATH}/{platform_fs_slug}/{rom_name}/cover"
|
||||
Path(artwork_path).mkdir(parents=True, exist_ok=True)
|
||||
return path_cover_l, path_cover_s, artwork_path
|
||||
|
||||
def _store_screenshot(self, fs_slug: str, rom_name: str, url: str, idx: int):
|
||||
@staticmethod
|
||||
def _store_screenshot(fs_slug: str, rom_name: str, url: str, idx: int):
|
||||
"""Store roms resources in filesystem
|
||||
|
||||
Args:
|
||||
@@ -168,7 +187,7 @@ class FSResourceHandler(FSHandler):
|
||||
"""
|
||||
screenshot_file = f"{idx}.jpg"
|
||||
screenshot_path = f"{RESOURCES_BASE_PATH}/{fs_slug}/{rom_name}/screenshots"
|
||||
|
||||
|
||||
try:
|
||||
res = requests.get(url, stream=True, timeout=120)
|
||||
except requests.exceptions.ConnectionError:
|
||||
@@ -177,7 +196,7 @@ class FSResourceHandler(FSHandler):
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Can't connect to IGDB, check your internet connection.",
|
||||
)
|
||||
|
||||
|
||||
if res.status_code == 200:
|
||||
Path(screenshot_path).mkdir(parents=True, exist_ok=True)
|
||||
with open(f"{screenshot_path}/{screenshot_file}", "wb") as f:
|
||||
@@ -188,7 +207,8 @@ class FSResourceHandler(FSHandler):
|
||||
f"Failure writing screenshot {url} to file (ProtocolError)"
|
||||
)
|
||||
|
||||
def _get_screenshot_path(self, fs_slug: str, rom_name: str, idx: str):
|
||||
@staticmethod
|
||||
def _get_screenshot_path(fs_slug: str, rom_name: str, idx: str):
|
||||
"""Returns rom cover filesystem path adapted to frontend folder structure
|
||||
|
||||
Args:
|
||||
|
||||
@@ -5,13 +5,13 @@ const theme = useTheme();
|
||||
|
||||
const props = defineProps<{ rom: RomSchema }>();
|
||||
const imgSrc =
|
||||
!props.rom.igdb_id && !props.rom.has_cover
|
||||
!props.rom.igdb_id && !props.rom.moby_id && !props.rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
|
||||
: !props.rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${props.rom.path_cover_s}`;
|
||||
const imgSrcLazy =
|
||||
!props.rom.igdb_id && !props.rom.has_cover
|
||||
!props.rom.igdb_id && !props.rom.moby_id && !props.rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
|
||||
: !props.rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
|
||||
|
||||
@@ -1,36 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import storeDownload from "@/stores/download";
|
||||
import type { Rom } from "@/stores/roms";
|
||||
import { useTheme } from "vuetify";
|
||||
const theme = useTheme();
|
||||
|
||||
defineProps<{ romId: number; src: string; lazySrc: string }>();
|
||||
const downloadStore = storeDownload();
|
||||
defineProps<{ rom: Rom }>();
|
||||
</script>
|
||||
<template>
|
||||
<v-card
|
||||
elevation="2"
|
||||
:loading="downloadStore.value.includes(rom.id) ? 'romm-accent-1' : false"
|
||||
:loading="downloadStore.value.includes(romId) ? 'romm-accent-1' : false"
|
||||
>
|
||||
<v-img
|
||||
:value="rom.id"
|
||||
:key="rom.id"
|
||||
:src="
|
||||
!rom.igdb_id && !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_l}`
|
||||
"
|
||||
:lazy-src="
|
||||
!rom.igdb_id && !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_s}`
|
||||
"
|
||||
:value="romId"
|
||||
:key="romId"
|
||||
:src="src"
|
||||
:lazy-src="lazySrc"
|
||||
:aspect-ratio="3 / 4"
|
||||
>
|
||||
<slot name="editable"></slot>
|
||||
<template v-slot:placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height">
|
||||
<v-progress-circular
|
||||
|
||||
@@ -7,7 +7,7 @@ defineProps<{ rom: Rom }>();
|
||||
<template>
|
||||
<div rounded="0" class="table">
|
||||
<v-row
|
||||
v-if="rom.igdb_id"
|
||||
v-if="rom.igdb_id || rom.moby_id"
|
||||
class="align-center justify-center pa-2"
|
||||
no-gutters
|
||||
>
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject } from "vue";
|
||||
import { useDisplay } from "vuetify";
|
||||
import type { Emitter } from "mitt";
|
||||
import type { Events } from "@/types/emitter";
|
||||
|
||||
import Cover from "@/components/Details/Cover.vue";
|
||||
import romApi, { type UpdateRom } from "@/services/api/rom";
|
||||
import storeRoms from "@/stores/roms";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, ref } from "vue";
|
||||
import { useDisplay, useTheme } from "vuetify";
|
||||
|
||||
const { xs, mdAndDown, lgAndUp } = useDisplay();
|
||||
// Props
|
||||
const theme = useTheme();
|
||||
const { xs, mdAndDown, smAndDown, md, lgAndUp } = useDisplay();
|
||||
const show = ref(false);
|
||||
const rom = ref<UpdateRom>();
|
||||
const romsStore = storeRoms();
|
||||
const imagePreviewUrl = ref<string | undefined>("");
|
||||
const removeCover = ref(false);
|
||||
const fileNameInputRules = {
|
||||
required: (value: string) => !!value || "Required",
|
||||
newFileName: (value: string) => !value.includes("/") || "Invalid characters",
|
||||
};
|
||||
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
emitter?.on("showEditRomDialog", (romToEdit) => {
|
||||
show.value = true;
|
||||
@@ -23,6 +26,27 @@ emitter?.on("showEditRomDialog", (romToEdit) => {
|
||||
});
|
||||
|
||||
// Functions
|
||||
function triggerFileInput() {
|
||||
const fileInput = document.getElementById("file-input");
|
||||
fileInput?.click();
|
||||
}
|
||||
|
||||
function previewImage(event: { target: { files: any[] } }) {
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
imagePreviewUrl.value = reader.result?.toString();
|
||||
};
|
||||
if (file) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeArtwork() {
|
||||
imagePreviewUrl.value = `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`;
|
||||
removeCover.value = true;
|
||||
}
|
||||
|
||||
async function updateRom() {
|
||||
if (!rom.value) return;
|
||||
|
||||
@@ -44,8 +68,9 @@ async function updateRom() {
|
||||
|
||||
show.value = false;
|
||||
emitter?.emit("showLoadingDialog", { loading: true, scrim: true });
|
||||
|
||||
await romApi
|
||||
.updateRom({ rom: rom.value })
|
||||
.updateRom({ rom: rom.value, removeCover: removeCover.value })
|
||||
.then(({ data }) => {
|
||||
emitter?.emit("snackbarShow", {
|
||||
msg: "Rom updated successfully!",
|
||||
@@ -70,6 +95,7 @@ async function updateRom() {
|
||||
|
||||
function closeDialog() {
|
||||
show.value = false;
|
||||
imagePreviewUrl.value = "";
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -113,51 +139,94 @@ function closeDialog() {
|
||||
<v-divider class="border-opacity-25" :thickness="1" />
|
||||
|
||||
<v-card-text>
|
||||
<v-row class="pa-2" no-gutters>
|
||||
<v-text-field
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.name"
|
||||
label="Name"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
</v-row>
|
||||
<v-row class="pa-2" no-gutters>
|
||||
<v-text-field
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.file_name"
|
||||
:rules="[
|
||||
fileNameInputRules.newFileName,
|
||||
fileNameInputRules.required,
|
||||
]"
|
||||
label="File name"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
</v-row>
|
||||
<v-row class="pa-2" no-gutters>
|
||||
<v-textarea
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.summary"
|
||||
label="Summary"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
</v-row>
|
||||
<v-row class="pa-2" no-gutters>
|
||||
<v-file-input
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.artwork"
|
||||
label="Custom artwork"
|
||||
accept="image/*"
|
||||
prepend-inner-icon="mdi-image"
|
||||
prepend-icon=""
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-row class="align-center" no-gutters>
|
||||
<v-col cols="12" md="8" lg="9">
|
||||
<v-text-field
|
||||
class="py-2"
|
||||
:class="{ 'pr-4': lgAndUp }"
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.name"
|
||||
label="Name"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
class="py-2"
|
||||
:class="{ 'pr-4': lgAndUp }"
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.file_name"
|
||||
:rules="[
|
||||
fileNameInputRules.newFileName,
|
||||
fileNameInputRules.required,
|
||||
]"
|
||||
label="File name"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
<v-textarea
|
||||
class="py-2"
|
||||
:class="{ 'pr-4': lgAndUp }"
|
||||
@keyup.enter="updateRom()"
|
||||
v-model="rom.summary"
|
||||
label="Summary"
|
||||
variant="outlined"
|
||||
required
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4" lg="3">
|
||||
<cover
|
||||
:class="{ 'mx-16': smAndDown, 'ml-2': md, 'my-4': smAndDown }"
|
||||
:romId="rom.id"
|
||||
:src="
|
||||
imagePreviewUrl
|
||||
? imagePreviewUrl
|
||||
: !rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_l}`
|
||||
"
|
||||
:lazy-src="
|
||||
imagePreviewUrl
|
||||
? imagePreviewUrl
|
||||
: !rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_s}`
|
||||
"
|
||||
>
|
||||
<template v-slot:editable>
|
||||
<v-chip-group class="position-absolute edit-cover pa-0">
|
||||
<v-chip
|
||||
class="translucent"
|
||||
size="small"
|
||||
@click="triggerFileInput"
|
||||
label
|
||||
><v-icon>mdi-pencil</v-icon>
|
||||
<v-file-input
|
||||
id="file-input"
|
||||
v-model="rom.artwork"
|
||||
accept="image/*"
|
||||
hide-details
|
||||
class="file-input"
|
||||
@change="previewImage"
|
||||
/>
|
||||
</v-chip>
|
||||
<v-chip
|
||||
class="translucent"
|
||||
size="small"
|
||||
@click="removeArtwork"
|
||||
label
|
||||
><v-icon class="text-red">mdi-delete</v-icon></v-chip
|
||||
>
|
||||
</v-chip-group>
|
||||
</template>
|
||||
</cover>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="justify-center pa-2" no-gutters>
|
||||
<v-btn @click="closeDialog" class="bg-terciary">Cancel</v-btn>
|
||||
@@ -174,12 +243,21 @@ function closeDialog() {
|
||||
.edit-content {
|
||||
width: 900px;
|
||||
}
|
||||
|
||||
.edit-content-tablet {
|
||||
width: 570px;
|
||||
width: 620px;
|
||||
}
|
||||
|
||||
.edit-content-mobile {
|
||||
width: 85vw;
|
||||
}
|
||||
.edit-cover {
|
||||
bottom: -0.1rem;
|
||||
right: -0.3rem;
|
||||
}
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
.translucent {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -219,14 +219,14 @@ onBeforeUnmount(() => {
|
||||
<v-img
|
||||
v-bind="props"
|
||||
:src="
|
||||
!rom.igdb_id && !rom.has_cover
|
||||
!rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_l}`
|
||||
"
|
||||
:lazy-src="
|
||||
!rom.igdb_id && !rom.has_cover
|
||||
!rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject } from "vue";
|
||||
import type { Emitter } from "mitt";
|
||||
import type { Events, UserItem } from "@/types/emitter";
|
||||
|
||||
import userApi from "@/services/api/user";
|
||||
import { defaultAvatarPath } from "@/utils";
|
||||
import storeUsers from "@/stores/users";
|
||||
import type { Events, UserItem } from "@/types/emitter";
|
||||
import { defaultAvatarPath } from "@/utils";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, ref } from "vue";
|
||||
|
||||
// Props
|
||||
const user = ref<UserItem | null>(null);
|
||||
const show = ref(false);
|
||||
const usersStore = storeUsers();
|
||||
|
||||
const imagePreviewUrl = ref<string | undefined>("");
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
emitter?.on("showEditUserDialog", (userToEdit) => {
|
||||
user.value = { ...userToEdit, avatar: undefined };
|
||||
@@ -18,6 +18,22 @@ emitter?.on("showEditUserDialog", (userToEdit) => {
|
||||
});
|
||||
|
||||
// Functions
|
||||
function triggerFileInput() {
|
||||
const fileInput = document.getElementById("file-input");
|
||||
fileInput?.click();
|
||||
}
|
||||
|
||||
function previewImage(event: { target: { files: any[] } }) {
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
imagePreviewUrl.value = reader.result?.toString();
|
||||
};
|
||||
if (file) {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
function editUser() {
|
||||
if (!user.value) return;
|
||||
|
||||
@@ -49,10 +65,21 @@ function editUser() {
|
||||
|
||||
function closeDialog() {
|
||||
show.value = false;
|
||||
imagePreviewUrl.value = "";
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<v-dialog v-if="user" v-model="show" max-width="700px" :scrim="false">
|
||||
<v-dialog
|
||||
v-if="user"
|
||||
v-model="show"
|
||||
max-width="700px"
|
||||
:scrim="true"
|
||||
@click:outside="closeDialog"
|
||||
@keydown.esc="closeDialog"
|
||||
scroll-strategy="none"
|
||||
no-click-animation
|
||||
persistent
|
||||
>
|
||||
<v-card>
|
||||
<v-toolbar density="compact" class="bg-terciary">
|
||||
<v-row class="align-center" no-gutters>
|
||||
@@ -74,8 +101,8 @@ function closeDialog() {
|
||||
<v-divider class="border-opacity-25" :thickness="1" />
|
||||
|
||||
<v-card-text>
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12" lg="9">
|
||||
<v-row class="align-center" no-gutters>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-row class="pa-2" no-gutters>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
@@ -116,30 +143,42 @@ function closeDialog() {
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="3">
|
||||
<v-col cols="12" sm="4">
|
||||
<v-row class="pa-2 justify-center" no-gutters>
|
||||
<v-avatar size="128" class="">
|
||||
<v-img
|
||||
:src="
|
||||
user.avatar_path
|
||||
? `/assets/romm/assets/${user.avatar_path}`
|
||||
: defaultAvatarPath
|
||||
"
|
||||
/>
|
||||
</v-avatar>
|
||||
</v-row>
|
||||
<v-row class="pa-2" no-gutters>
|
||||
<v-col>
|
||||
<v-file-input
|
||||
class="text-truncate"
|
||||
v-model="user.avatar"
|
||||
label="Avatar"
|
||||
prepend-inner-icon="mdi-image"
|
||||
prepend-icon=""
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
<v-hover v-slot="{ isHovering, props }">
|
||||
<v-avatar size="190" class="ml-4" v-bind="props">
|
||||
<v-img
|
||||
:src="
|
||||
imagePreviewUrl
|
||||
? imagePreviewUrl
|
||||
: user.avatar_path
|
||||
? `/assets/romm/assets/${user.avatar_path}`
|
||||
: defaultAvatarPath
|
||||
"
|
||||
>
|
||||
<v-fade-transition>
|
||||
<div
|
||||
v-if="isHovering"
|
||||
class="d-flex translucent v-card--reveal text-h4"
|
||||
@click="triggerFileInput"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
<v-file-input
|
||||
id="file-input"
|
||||
class="file-input text-truncate"
|
||||
v-model="user.avatar"
|
||||
label="Avatar"
|
||||
prepend-inner-icon="mdi-image"
|
||||
prepend-icon=""
|
||||
variant="outlined"
|
||||
hide-details
|
||||
@change="previewImage"
|
||||
/>
|
||||
</v-img>
|
||||
</v-avatar>
|
||||
</v-hover>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -153,3 +192,22 @@ function closeDialog() {
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
.translucent {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
.v-card--reveal {
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,6 @@ import FilterTextField from "@/components/Gallery/AppBar/FilterTextField.vue";
|
||||
import SelectingBtn from "@/components/Gallery/AppBar/SelectingBtn.vue";
|
||||
import GalleryViewBtn from "@/components/Gallery/AppBar/GalleryViewBtn.vue";
|
||||
import SortBar from "@/components/Gallery/AppBar/SortBar.vue";
|
||||
import SortBtn from "@/components/Gallery/AppBar/SortBtn.vue";
|
||||
import storeAuth from "@/stores/auth";
|
||||
|
||||
// Props
|
||||
|
||||
@@ -87,14 +87,14 @@ function onTouchEnd() {
|
||||
:key="rom.id"
|
||||
v-bind="props"
|
||||
:src="
|
||||
!rom.igdb_id && !rom.has_cover
|
||||
!rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_l}`
|
||||
"
|
||||
:lazy-src="
|
||||
!rom.igdb_id && !rom.has_cover
|
||||
!rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
|
||||
|
||||
@@ -144,9 +144,11 @@ export type UpdateRom = Rom & {
|
||||
async function updateRom({
|
||||
rom,
|
||||
renameAsIGDB = false,
|
||||
removeCover = false
|
||||
}: {
|
||||
rom: UpdateRom;
|
||||
renameAsIGDB?: boolean;
|
||||
removeCover?: boolean;
|
||||
}): Promise<{ data: RomSchema }> {
|
||||
var formData = new FormData();
|
||||
if (rom.igdb_id) formData.append("igdb_id", rom.igdb_id.toString());
|
||||
@@ -157,7 +159,7 @@ async function updateRom({
|
||||
if (rom.artwork) formData.append("artwork", rom.artwork[0]);
|
||||
|
||||
return api.put(`/roms/${rom.id}`, formData, {
|
||||
params: { rename_as_igdb: renameAsIGDB },
|
||||
params: { rename_as_igdb: renameAsIGDB, remove_cover: removeCover },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ export default defineStore("roms", {
|
||||
},
|
||||
filterUnmatched() {
|
||||
this._filteredIDs = this.filteredRoms
|
||||
.filter((rom) => !rom.igdb_id)
|
||||
.filter((rom) => !rom.igdb_id && !rom.moby_id)
|
||||
.map((roms) => roms.id);
|
||||
},
|
||||
filterGenre(genreToFilter: string) {
|
||||
|
||||
@@ -98,7 +98,7 @@ export function formatTimestamp(timestamp: string | null) {
|
||||
if (!timestamp) return "-";
|
||||
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
return date.toLocaleString("en-GB");
|
||||
}
|
||||
|
||||
export function regionToEmoji(region: string) {
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import storeDownload from "@/stores/download";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, onBeforeMount, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useDisplay } from "vuetify";
|
||||
|
||||
import type { PlatformSchema } from "@/__generated__";
|
||||
import ActionBar from "@/components/Details/ActionBar.vue";
|
||||
import AdditionalContent from "@/components/Details/AdditionalContent.vue";
|
||||
@@ -24,7 +17,13 @@ import EditRomDialog from "@/components/Dialog/Rom/EditRom.vue";
|
||||
import SearchRomDialog from "@/components/Dialog/Rom/SearchRom.vue";
|
||||
import platformApi from "@/services/api/platform";
|
||||
import romApi from "@/services/api/rom";
|
||||
import storeDownload from "@/stores/download";
|
||||
import type { Rom } from "@/stores/roms";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject, onBeforeMount, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useDisplay, useTheme } from "vuetify";
|
||||
|
||||
const route = useRoute();
|
||||
const rom = ref<Rom>();
|
||||
@@ -38,6 +37,7 @@ const tab = ref<
|
||||
| "relatedgames"
|
||||
| "emulation"
|
||||
>("details");
|
||||
const theme = useTheme();
|
||||
const { smAndDown, mdAndDown, mdAndUp, lgAndUp } = useDisplay();
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
const showEmulation = ref(false);
|
||||
@@ -122,7 +122,23 @@ watch(
|
||||
'cover-xs': smAndDown,
|
||||
}"
|
||||
>
|
||||
<cover :rom="rom" />
|
||||
<cover
|
||||
:romId="rom.id"
|
||||
:src="
|
||||
!rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/big_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_l}`
|
||||
"
|
||||
:lazy-src="
|
||||
!rom.igdb_id && !rom.moby_id && !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_unmatched.png`
|
||||
: !rom.has_cover
|
||||
? `/assets/default/cover/small_${theme.global.name.value}_missing_cover.png`
|
||||
: `/assets/romm/resources/${rom.path_cover_s}`
|
||||
"
|
||||
/>
|
||||
<action-bar class="mt-2" :rom="rom" />
|
||||
<related-games class="mt-3 px-2" v-if="mdAndUp" :rom="rom" />
|
||||
</v-col>
|
||||
|
||||
Reference in New Issue
Block a user