mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
feat: refactor asset display components and improve styling
This commit is contained in:
@@ -2,15 +2,13 @@
|
||||
import type { Emitter } from "mitt";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { inject, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { SaveSchema } from "@/__generated__";
|
||||
import EmptySaves from "@/components/common/EmptyStates/EmptySaves.vue";
|
||||
import AssetCard from "@/components/common/Game/AssetCard.vue";
|
||||
import storeAuth from "@/stores/auth";
|
||||
import { type DetailedRom } from "@/stores/roms";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import { formatBytes, formatTimestamp, formatRelativeDate } from "@/utils";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const auth = storeAuth();
|
||||
const { scopes } = storeToRefs(auth);
|
||||
const props = defineProps<{ rom: DetailedRom }>();
|
||||
@@ -112,70 +110,16 @@ function onCardClick(save: SaveSchema, event: MouseEvent) {
|
||||
:key="save.id"
|
||||
cols="6"
|
||||
sm="4"
|
||||
class="pa-1"
|
||||
class="pa-1 align-self-end"
|
||||
>
|
||||
<v-hover v-slot="{ isHovering, props: saveProps }">
|
||||
<v-card
|
||||
v-bind="saveProps"
|
||||
class="bg-toplayer transform-scale"
|
||||
:class="{
|
||||
'border-selected': selectedSaves.some((s) => s.id === save.id),
|
||||
}"
|
||||
@click="(e: MouseEvent) => onCardClick(save, e)"
|
||||
>
|
||||
<v-card-text class="pa-2">
|
||||
<v-slide-x-transition>
|
||||
<v-btn-group
|
||||
v-if="isHovering"
|
||||
class="position-absolute"
|
||||
density="compact"
|
||||
style="bottom: 4px; right: 4px"
|
||||
>
|
||||
<v-btn drawer :href="save.download_path" download size="small">
|
||||
<v-icon>mdi-download</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="scopes.includes('assets.write')"
|
||||
drawer
|
||||
size="small"
|
||||
@click="
|
||||
emitter?.emit('showDeleteSavesDialog', {
|
||||
rom: props.rom,
|
||||
saves: [save],
|
||||
})
|
||||
"
|
||||
>
|
||||
<v-icon class="text-romm-red"> mdi-delete </v-icon>
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
</v-slide-x-transition>
|
||||
<v-row class="pa-1 text-caption" no-gutters>
|
||||
{{ save.file_name }}
|
||||
</v-row>
|
||||
<v-row class="ga-1 pa-1" no-gutters>
|
||||
<v-col v-if="save.emulator" cols="12">
|
||||
<v-chip size="x-small" color="orange" label>
|
||||
{{ save.emulator }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-chip size="x-small" label>
|
||||
{{ formatBytes(save.file_size_bytes) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<div class="mt-1">
|
||||
{{ t("rom.updated") }}:
|
||||
{{ formatTimestamp(save.updated_at, locale) }}
|
||||
<span class="ml-1 text-grey text-caption"
|
||||
>({{ formatRelativeDate(save.updated_at) }})</span
|
||||
>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<AssetCard
|
||||
:asset="save"
|
||||
type="save"
|
||||
:selected="selectedSaves.some((s) => s.id === save.id)"
|
||||
:rom="props.rom"
|
||||
:scopes="scopes"
|
||||
@click="(e: MouseEvent) => onCardClick(save, e)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<EmptySaves v-else />
|
||||
|
||||
@@ -2,16 +2,13 @@
|
||||
import type { Emitter } from "mitt";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { inject, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { StateSchema } from "@/__generated__";
|
||||
import EmptySates from "@/components/common/EmptyStates/EmptyStates.vue";
|
||||
import AssetCard from "@/components/common/Game/AssetCard.vue";
|
||||
import storeAuth from "@/stores/auth";
|
||||
import { type DetailedRom } from "@/stores/roms";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import { formatBytes, formatTimestamp, formatRelativeDate } from "@/utils";
|
||||
import { getEmptyCoverImage } from "@/utils/covers";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const auth = storeAuth();
|
||||
const { scopes } = storeToRefs(auth);
|
||||
const props = defineProps<{ rom: DetailedRom }>();
|
||||
@@ -115,88 +112,16 @@ function onCardClick(state: StateSchema, event: MouseEvent) {
|
||||
:key="state.id"
|
||||
cols="6"
|
||||
sm="4"
|
||||
class="pa-1"
|
||||
class="pa-1 align-self-end"
|
||||
>
|
||||
<v-hover v-slot="{ isHovering, props: stateProps }">
|
||||
<v-card
|
||||
v-bind="stateProps"
|
||||
class="bg-toplayer transform-scale"
|
||||
:class="{
|
||||
'border-selected': selectedStates.some((s) => s.id === state.id),
|
||||
}"
|
||||
@click="(e: MouseEvent) => onCardClick(state, e)"
|
||||
>
|
||||
<v-card-text class="pa-2">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-img
|
||||
rounded
|
||||
:src="
|
||||
state.screenshot?.download_path ??
|
||||
getEmptyCoverImage(state.file_name, 16 / 9)
|
||||
"
|
||||
:aspect-ratio="16 / 9"
|
||||
>
|
||||
<v-slide-x-transition>
|
||||
<v-btn-group
|
||||
v-if="isHovering"
|
||||
class="position-absolute"
|
||||
density="compact"
|
||||
style="bottom: 4px; right: 4px"
|
||||
>
|
||||
<v-btn
|
||||
drawer
|
||||
:href="state.download_path"
|
||||
download
|
||||
size="small"
|
||||
>
|
||||
<v-icon>mdi-download</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="scopes.includes('assets.write')"
|
||||
drawer
|
||||
size="small"
|
||||
@click="
|
||||
emitter?.emit('showDeleteStatesDialog', {
|
||||
rom: props.rom,
|
||||
states: [state],
|
||||
})
|
||||
"
|
||||
>
|
||||
<v-icon class="text-romm-red"> mdi-delete </v-icon>
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
</v-slide-x-transition>
|
||||
</v-img>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="py-2 text-caption" no-gutters>
|
||||
{{ state.file_name }}
|
||||
</v-row>
|
||||
<v-row class="ga-1" no-gutters>
|
||||
<v-col v-if="state.emulator" cols="12">
|
||||
<v-chip size="x-small" color="orange" label>
|
||||
{{ state.emulator }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-chip size="x-small" label>
|
||||
{{ formatBytes(state.file_size_bytes) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<div class="mt-1">
|
||||
{{ t("rom.updated") }}:
|
||||
{{ formatTimestamp(state.updated_at, locale) }}
|
||||
<span class="ml-1 text-grey text-caption"
|
||||
>({{ formatRelativeDate(state.updated_at) }})</span
|
||||
>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
<AssetCard
|
||||
:asset="state"
|
||||
type="state"
|
||||
:selected="selectedStates.some((s) => s.id === state.id)"
|
||||
:rom="props.rom"
|
||||
:scopes="scopes"
|
||||
@click="(e: MouseEvent) => onCardClick(state, e)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<EmptySates v-else />
|
||||
|
||||
175
frontend/src/components/common/Game/AssetCard.vue
Normal file
175
frontend/src/components/common/Game/AssetCard.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<script setup lang="ts">
|
||||
import type { Emitter } from "mitt";
|
||||
import { inject } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { SaveSchema, StateSchema } from "@/__generated__";
|
||||
import type { DetailedRom } from "@/stores/roms";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import { formatBytes, formatTimestamp, formatRelativeDate } from "@/utils";
|
||||
import { getEmptyCoverImage } from "@/utils/covers";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
|
||||
export type AssetType = "save" | "state";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
asset: SaveSchema | StateSchema;
|
||||
type: AssetType;
|
||||
selected: boolean;
|
||||
rom?: DetailedRom;
|
||||
showHoverActions?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
scopes?: string[];
|
||||
cardStyle?: Record<string, any>;
|
||||
transformScale?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showHoverActions: true,
|
||||
showCloseButton: false,
|
||||
scopes: () => [],
|
||||
cardStyle: () => ({}),
|
||||
transformScale: true,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "click", event: MouseEvent): void;
|
||||
(e: "close"): void;
|
||||
}>();
|
||||
|
||||
function isState(asset: SaveSchema | StateSchema): asset is StateSchema {
|
||||
return "screenshot" in asset;
|
||||
}
|
||||
|
||||
function handleClick(event: MouseEvent) {
|
||||
emit("click", event);
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit("close");
|
||||
}
|
||||
|
||||
function handleDelete(event: Event) {
|
||||
event.stopPropagation();
|
||||
if (props.type === "save") {
|
||||
emitter?.emit("showDeleteSavesDialog", {
|
||||
rom: props.rom!,
|
||||
saves: [props.asset as SaveSchema],
|
||||
});
|
||||
} else {
|
||||
emitter?.emit("showDeleteStatesDialog", {
|
||||
rom: props.rom!,
|
||||
states: [props.asset as StateSchema],
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-hover v-slot="{ isHovering, props: hoverProps }">
|
||||
<v-card
|
||||
v-bind="hoverProps"
|
||||
:style="cardStyle"
|
||||
class="bg-toplayer"
|
||||
:class="{
|
||||
'border-selected': selected,
|
||||
'transform-scale': transformScale,
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<v-card-text class="pa-2">
|
||||
<v-slide-x-transition>
|
||||
<v-btn-group
|
||||
v-if="isHovering && showHoverActions"
|
||||
class="position-absolute"
|
||||
density="compact"
|
||||
style="bottom: 4px; right: 4px"
|
||||
>
|
||||
<v-btn drawer :href="asset.download_path" download size="small">
|
||||
<v-icon>mdi-download</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="scopes.includes('assets.write')"
|
||||
drawer
|
||||
size="small"
|
||||
@click="handleDelete"
|
||||
>
|
||||
<v-icon class="text-romm-red">mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
</v-slide-x-transition>
|
||||
|
||||
<!-- Screenshot for states -->
|
||||
<v-row
|
||||
v-if="type === 'state' && isState(asset)"
|
||||
no-gutters
|
||||
class="bg-surface"
|
||||
>
|
||||
<v-col cols="12">
|
||||
<v-img
|
||||
rounded
|
||||
:src="
|
||||
asset.screenshot?.download_path ??
|
||||
getEmptyCoverImage(asset.file_name, 16 / 9)
|
||||
"
|
||||
:aspect-ratio="16 / 9"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- File name -->
|
||||
<v-row class="text-caption py-2 px-1 text-primary" no-gutters>
|
||||
{{ asset.file_name }}
|
||||
</v-row>
|
||||
|
||||
<!-- Metadata -->
|
||||
<v-row class="ga-1 pa-1" no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-chip
|
||||
v-if="asset.emulator"
|
||||
size="x-small"
|
||||
color="orange"
|
||||
class="mr-2"
|
||||
label
|
||||
>
|
||||
{{ asset.emulator }}
|
||||
</v-chip>
|
||||
<v-chip size="x-small" label>
|
||||
{{ formatBytes(asset.file_size_bytes) }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<div class="mt-2">
|
||||
{{ t("rom.updated") }}:
|
||||
{{ formatTimestamp(asset.updated_at, locale) }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<div class="mt-1">
|
||||
<span class="text-grey text-caption"
|
||||
>({{ formatRelativeDate(asset.updated_at) }})</span
|
||||
>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Close button -->
|
||||
<v-row v-if="showCloseButton" no-gutters>
|
||||
<v-col class="text-right mt-auto pt-1">
|
||||
<v-btn
|
||||
variant="flat"
|
||||
color="toplayer"
|
||||
size="small"
|
||||
icon
|
||||
@click.stop="handleClose"
|
||||
>
|
||||
<v-icon>mdi-close-circle-outline</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</template>
|
||||
@@ -47,14 +47,14 @@ body {
|
||||
}
|
||||
.transform-scale:hover,
|
||||
.transform-scale:focus {
|
||||
transform: scale(1.05) !important;
|
||||
transform: scale(1.07) !important;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
.border-selected {
|
||||
border: 1px solid rgba(var(--v-theme-primary)) !important;
|
||||
transform: scale(1.1);
|
||||
transform: scale(1.04);
|
||||
}
|
||||
.greyscale {
|
||||
filter: grayscale(100%);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useDisplay } from "vuetify";
|
||||
import type { FirmwareSchema, SaveSchema, StateSchema } from "@/__generated__";
|
||||
import EmptySaves from "@/components/common/EmptyStates/EmptySaves.vue";
|
||||
import EmptyStates from "@/components/common/EmptyStates/EmptyStates.vue";
|
||||
import AssetCard from "@/components/common/Game/AssetCard.vue";
|
||||
import RomListItem from "@/components/common/Game/ListItem.vue";
|
||||
import { ROUTES } from "@/plugins/router";
|
||||
import firmwareApi from "@/services/api/firmware";
|
||||
@@ -287,7 +288,7 @@ onBeforeUnmount(async () => {
|
||||
'mt-2': smAndDown,
|
||||
'pr-1': !smAndDown,
|
||||
}"
|
||||
:cols="smAndDown ? 12 : 6"
|
||||
:cols="smAndDown ? 6 : 4"
|
||||
>
|
||||
<v-btn
|
||||
block
|
||||
@@ -309,75 +310,19 @@ onBeforeUnmount(async () => {
|
||||
}}
|
||||
</v-btn>
|
||||
<v-expand-transition>
|
||||
<v-card
|
||||
<AssetCard
|
||||
v-if="selectedState"
|
||||
class="bg-toplayer transform-scale selected-card mx-1"
|
||||
:asset="selectedState"
|
||||
type="state"
|
||||
:selected="false"
|
||||
:show-hover-actions="false"
|
||||
:show-close-button="true"
|
||||
:card-style="{}"
|
||||
class="selected-card mx-1"
|
||||
:transform-scale="false"
|
||||
:class="{ 'disabled-card': openSaveSelector }"
|
||||
>
|
||||
<v-card-text class="px-2 pb-2 pt-4">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="6">
|
||||
<v-img
|
||||
rounded
|
||||
:src="
|
||||
selectedState.screenshot?.download_path ??
|
||||
getEmptyCoverImage(
|
||||
selectedState.file_name,
|
||||
16 / 9,
|
||||
)
|
||||
"
|
||||
:aspect-ratio="16 / 9"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="pl-2 d-flex flex-column" cols="6">
|
||||
<v-row
|
||||
class="px-1 text-caption text-primary"
|
||||
no-gutters
|
||||
>
|
||||
{{ selectedState.file_name }}
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-list-item rounded class="px-1 text-caption">
|
||||
{{ t("rom.updated") }}:
|
||||
{{
|
||||
formatTimestamp(
|
||||
selectedState.updated_at,
|
||||
locale,
|
||||
)
|
||||
}}
|
||||
<span class="text-grey text-caption"
|
||||
>({{
|
||||
formatRelativeDate(
|
||||
selectedState.updated_at,
|
||||
)
|
||||
}})</span
|
||||
>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
<v-col v-if="selectedState.emulator" cols="12">
|
||||
<v-chip size="x-small" color="orange" label>
|
||||
{{ selectedState.emulator }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col class="text-right mt-auto pt-1">
|
||||
<v-btn
|
||||
variant="flat"
|
||||
color="toplayer"
|
||||
size="small"
|
||||
icon
|
||||
@click="unselectState"
|
||||
>
|
||||
<v-icon>mdi-close-circle-outline</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@close="unselectState"
|
||||
/>
|
||||
</v-expand-transition>
|
||||
</v-col>
|
||||
</v-expand-transition>
|
||||
@@ -390,7 +335,7 @@ onBeforeUnmount(async () => {
|
||||
'mt-2': smAndDown,
|
||||
'pl-1': !smAndDown,
|
||||
}"
|
||||
:cols="smAndDown ? 12 : 6"
|
||||
:cols="smAndDown ? 6 : 4"
|
||||
>
|
||||
<v-btn
|
||||
block
|
||||
@@ -405,60 +350,19 @@ onBeforeUnmount(async () => {
|
||||
}}
|
||||
</v-btn>
|
||||
<v-expand-transition>
|
||||
<v-card
|
||||
<AssetCard
|
||||
v-if="selectedSave"
|
||||
class="bg-toplayer transform-scale selected-card mx-1"
|
||||
:asset="selectedSave"
|
||||
type="save"
|
||||
:selected="false"
|
||||
:show-hover-actions="false"
|
||||
:show-close-button="true"
|
||||
:card-style="{}"
|
||||
class="selected-card mx-1"
|
||||
:transform-scale="false"
|
||||
:class="{ 'disabled-card': openStateSelector }"
|
||||
>
|
||||
<v-card-text class="px-2 pb-2 pt-4">
|
||||
<v-row no-gutters>
|
||||
<v-col class="pl-2 d-flex flex-column" cols="6">
|
||||
<v-row
|
||||
class="px-1 text-caption text-primary"
|
||||
no-gutters
|
||||
>
|
||||
{{ selectedSave.file_name }}
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-list-item rounded class="px-1 text-caption">
|
||||
{{ t("rom.updated") }}:
|
||||
{{
|
||||
formatTimestamp(
|
||||
selectedSave.updated_at,
|
||||
locale,
|
||||
)
|
||||
}}
|
||||
<span class="text-grey text-caption"
|
||||
>({{
|
||||
formatRelativeDate(selectedSave.updated_at)
|
||||
}})</span
|
||||
>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
<v-col v-if="selectedSave.emulator" cols="12">
|
||||
<v-chip size="x-small" color="orange" label>
|
||||
{{ selectedSave.emulator }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row no-gutters>
|
||||
<v-col class="text-right mt-auto pt-2">
|
||||
<v-btn
|
||||
variant="flat"
|
||||
color="toplayer"
|
||||
size="small"
|
||||
icon
|
||||
@click="unselectSave"
|
||||
>
|
||||
<v-icon>mdi-close-circle-outline</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@close="unselectSave"
|
||||
/>
|
||||
</v-expand-transition>
|
||||
</v-col>
|
||||
</v-expand-transition>
|
||||
@@ -480,57 +384,18 @@ onBeforeUnmount(async () => {
|
||||
:key="state.id"
|
||||
cols="6"
|
||||
sm="4"
|
||||
class="pa-1"
|
||||
class="pa-1 align-self-end"
|
||||
>
|
||||
<v-card
|
||||
:style="{
|
||||
<AssetCard
|
||||
:asset="state"
|
||||
type="state"
|
||||
:selected="selectedState?.id === state.id"
|
||||
:show-hover-actions="false"
|
||||
:card-style="{
|
||||
zIndex: selectedState?.id === state.id ? 11 : undefined,
|
||||
}"
|
||||
class="bg-toplayer transform-scale"
|
||||
:class="{
|
||||
'border-selected': selectedState?.id === state.id,
|
||||
}"
|
||||
@click="selectState(state)"
|
||||
>
|
||||
<v-card-text class="pa-2">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-img
|
||||
rounded
|
||||
:src="
|
||||
state.screenshot?.download_path ??
|
||||
getEmptyCoverImage(state.file_name, 16 / 9)
|
||||
"
|
||||
:aspect-ratio="16 / 9"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
class="py-2 px-1 text-caption text-primary"
|
||||
no-gutters
|
||||
>
|
||||
{{ state.file_name }}
|
||||
</v-row>
|
||||
<v-row class="ga-1" no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-list-item rounded class="pa-1 text-caption">
|
||||
{{ t("rom.updated") }}:
|
||||
{{ formatTimestamp(state.updated_at, locale) }}
|
||||
<span class="ml-1 text-grey text-caption"
|
||||
>({{
|
||||
formatRelativeDate(state.updated_at)
|
||||
}})</span
|
||||
>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
<v-col v-if="state.emulator" cols="12" class="mt-1">
|
||||
<v-chip size="x-small" color="orange" label>
|
||||
{{ state.emulator }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
/>
|
||||
</v-col>
|
||||
</template>
|
||||
<v-col v-else class="pa-1 mt-1">
|
||||
@@ -553,43 +418,15 @@ onBeforeUnmount(async () => {
|
||||
:key="save.id"
|
||||
cols="6"
|
||||
sm="4"
|
||||
class="pa-1"
|
||||
class="pa-1 align-self-end"
|
||||
>
|
||||
<v-card
|
||||
:style="{
|
||||
zIndex: selectedSave?.id === save.id ? 11 : undefined,
|
||||
}"
|
||||
class="bg-toplayer transform-scale"
|
||||
:class="{
|
||||
'border-selected': selectedSave?.id === save.id,
|
||||
}"
|
||||
<AssetCard
|
||||
:asset="save"
|
||||
type="save"
|
||||
:selected="selectedSave?.id === save.id"
|
||||
:show-hover-actions="false"
|
||||
@click="selectSave(save)"
|
||||
>
|
||||
<v-card-text class="pa-2">
|
||||
<v-row
|
||||
class="py-2 px-1 text-caption text-primary"
|
||||
no-gutters
|
||||
>
|
||||
{{ save.file_name }}
|
||||
</v-row>
|
||||
<v-row class="ga-1" no-gutters>
|
||||
<v-col cols="12">
|
||||
<v-list-item rounded class="pa-1 text-caption">
|
||||
{{ t("rom.updated") }}:
|
||||
{{ formatTimestamp(save.updated_at, locale) }}
|
||||
<span class="ml-1 text-grey text-caption"
|
||||
>({{ formatRelativeDate(save.updated_at) }})</span
|
||||
>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
<v-col v-if="save.emulator" cols="12" class="mt-1">
|
||||
<v-chip size="x-small" color="orange" label>
|
||||
{{ save.emulator }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
/>
|
||||
</v-col>
|
||||
</template>
|
||||
<v-col v-else class="pa-1 mt-1">
|
||||
|
||||
Reference in New Issue
Block a user