mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
feat: add vanilla-tilt for enhanced card interaction and update dependencies
This commit is contained in:
7
frontend/package-lock.json
generated
7
frontend/package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"semver": "^7.6.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"vanilla-tilt": "^1.8.1",
|
||||
"vue": "^3.4.27",
|
||||
"vue-i18n": "^11.1.2",
|
||||
"vue-router": "^4.3.2",
|
||||
@@ -8134,6 +8135,12 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vanilla-tilt": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.8.1.tgz",
|
||||
"integrity": "sha512-hPB1XUsnh+SIeVSW2beb5RnuFxz4ZNgxjGD78o52F49gS4xaoLeEMh9qrQnJrnEn/vjjBI7IlxrrXmz4tGV0Kw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"semver": "^7.6.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"vanilla-tilt": "^1.8.1",
|
||||
"vue": "^3.4.27",
|
||||
"vue-i18n": "^11.1.2",
|
||||
"vue-router": "^4.3.2",
|
||||
|
||||
@@ -44,6 +44,7 @@ function toggleGridRecentRoms() {
|
||||
}"
|
||||
class="pa-1"
|
||||
no-gutters
|
||||
style="overflow-y: hidden"
|
||||
>
|
||||
<v-col
|
||||
v-for="rom in recentRoms"
|
||||
|
||||
@@ -11,7 +11,7 @@ import storeGalleryView from "@/stores/galleryView";
|
||||
import { ROUTES } from "@/plugins/router";
|
||||
import storeRoms from "@/stores/roms";
|
||||
import { type SimpleRom } from "@/stores/roms";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import { getMissingCoverImage, getUnmatchedCoverImage } from "@/utils/covers";
|
||||
import { isNull } from "lodash";
|
||||
import { useDisplay } from "vuetify";
|
||||
@@ -93,200 +93,235 @@ const showActionBarAlways = isNull(
|
||||
)
|
||||
? false
|
||||
: localStorage.getItem("settings.showActionBar") === "true";
|
||||
|
||||
import VanillaTilt from "vanilla-tilt";
|
||||
interface TiltHTMLElement extends HTMLElement {
|
||||
vanillaTilt?: {
|
||||
destroy: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const tiltCard = ref<TiltHTMLElement | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
if (tiltCard.value && !smAndDown.value) {
|
||||
VanillaTilt.init(tiltCard.value, {
|
||||
max: 20,
|
||||
speed: 400,
|
||||
glare: true,
|
||||
"max-glare": 0.5,
|
||||
scale: 1.15,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (tiltCard.value?.vanillaTilt) {
|
||||
tiltCard.value.vanillaTilt.destroy();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-hover v-slot="{ isHovering: isOuterHovering, props: hoverProps }">
|
||||
<v-card
|
||||
:style="
|
||||
disableViewTransition ? {} : { viewTransitionName: `card-${rom.id}` }
|
||||
"
|
||||
:minWidth="width"
|
||||
:maxWidth="width"
|
||||
:minHeight="height"
|
||||
:maxHeight="height"
|
||||
v-bind="{
|
||||
...hoverProps,
|
||||
...(withLink && rom.id
|
||||
? {
|
||||
to: { name: ROUTES.ROM, params: { rom: rom.id } },
|
||||
}
|
||||
: {}),
|
||||
}"
|
||||
class="bg-transparent"
|
||||
:class="{
|
||||
'on-hover': isOuterHovering,
|
||||
'border-selected': withBorderPrimary,
|
||||
'transform-scale': transformScale,
|
||||
}"
|
||||
:elevation="isOuterHovering && transformScale ? 20 : 3"
|
||||
:aria-label="`${rom.name} game card`"
|
||||
>
|
||||
<v-card-text class="pa-0">
|
||||
<v-progress-linear
|
||||
v-if="romsStore.isSimpleRom(rom)"
|
||||
color="primary"
|
||||
:active="downloadStore.value.includes(rom.id)"
|
||||
:indeterminate="true"
|
||||
absolute
|
||||
/>
|
||||
<v-hover v-slot="{ isHovering, props: hoverProps }" open-delay="800">
|
||||
<v-img
|
||||
@click="handleClick"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
v-bind="hoverProps"
|
||||
cover
|
||||
:class="{ pointer: pointerOnHover }"
|
||||
:key="romsStore.isSimpleRom(rom) ? rom.updated_at : ''"
|
||||
:src="
|
||||
src ||
|
||||
(romsStore.isSimpleRom(rom)
|
||||
? rom.path_cover_large || fallbackCoverImage
|
||||
: rom.igdb_url_cover ||
|
||||
rom.moby_url_cover ||
|
||||
rom.ss_url_cover ||
|
||||
fallbackCoverImage)
|
||||
"
|
||||
:lazy-src="
|
||||
src ||
|
||||
(romsStore.isSimpleRom(rom)
|
||||
? rom.path_cover_small || fallbackCoverImage
|
||||
: rom.igdb_url_cover ||
|
||||
rom.moby_url_cover ||
|
||||
rom.ss_url_cover ||
|
||||
fallbackCoverImage)
|
||||
"
|
||||
:aspect-ratio="computedAspectRatio"
|
||||
>
|
||||
<div v-bind="props" style="position: absolute; top: 0; width: 100%">
|
||||
<template v-if="titleOnHover">
|
||||
<v-expand-transition>
|
||||
<div
|
||||
v-if="
|
||||
isHovering ||
|
||||
(romsStore.isSimpleRom(rom) &&
|
||||
rom.is_unidentified &&
|
||||
!rom.path_cover_large) ||
|
||||
(!romsStore.isSimpleRom(rom) &&
|
||||
!rom.igdb_url_cover &&
|
||||
!rom.moby_url_cover &&
|
||||
!rom.ss_url_cover)
|
||||
"
|
||||
class="translucent-dark text-white"
|
||||
:class="
|
||||
sizeActionBar === 1 ? 'text-subtitle-1' : 'text-caption'
|
||||
"
|
||||
>
|
||||
<div :class="{ 'pa-2': sizeActionBar === 1 }">
|
||||
<v-list-item>{{ rom.name }}</v-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</template>
|
||||
<sources v-if="!romsStore.isSimpleRom(rom)" :rom="rom" />
|
||||
<v-row no-gutters class="text-white px-1">
|
||||
<game-card-flags
|
||||
v-if="romsStore.isSimpleRom(rom) && showFlags"
|
||||
:rom="rom"
|
||||
/>
|
||||
<slot name="prepend-inner"></slot>
|
||||
</v-row>
|
||||
</div>
|
||||
<div class="position-absolute append-inner-left">
|
||||
<platform-icon
|
||||
v-if="romsStore.isSimpleRom(rom) && showPlatformIcon"
|
||||
:size="25"
|
||||
:key="rom.platform_slug"
|
||||
:slug="rom.platform_slug"
|
||||
:name="rom.platform_name"
|
||||
:fs-slug="rom.platform_slug"
|
||||
class="label-platform"
|
||||
/>
|
||||
</div>
|
||||
<div class="position-absolute append-inner-right">
|
||||
<v-btn
|
||||
v-if="
|
||||
romsStore.isSimpleRom(rom) &&
|
||||
collectionsStore.isFav(rom) &&
|
||||
showFav
|
||||
"
|
||||
tabindex="-1"
|
||||
class="label-fav"
|
||||
rouded="0"
|
||||
size="small"
|
||||
color="primary"
|
||||
>
|
||||
<v-icon class="icon-fav" size="x-small"
|
||||
>{{
|
||||
collectionsStore.isFav(rom)
|
||||
? "mdi-star"
|
||||
: "mdi-star-outline"
|
||||
}}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div
|
||||
class="position-absolute append-inner-left"
|
||||
v-if="!showPlatformIcon"
|
||||
<div data-tilt ref="tiltCard">
|
||||
<!-- ...(isOuterHovering ? { zIndex: 1000 } : {}), -->
|
||||
<v-card
|
||||
:style="{
|
||||
...(disableViewTransition
|
||||
? {}
|
||||
: { viewTransitionName: `card-${rom.id}` }),
|
||||
}"
|
||||
:minWidth="width"
|
||||
:maxWidth="width"
|
||||
:minHeight="height"
|
||||
:maxHeight="height"
|
||||
v-bind="{
|
||||
...hoverProps,
|
||||
...(withLink && rom.id
|
||||
? {
|
||||
to: { name: ROUTES.ROM, params: { rom: rom.id } },
|
||||
}
|
||||
: {}),
|
||||
}"
|
||||
class="bg-transparent"
|
||||
:class="{
|
||||
'on-hover': isOuterHovering,
|
||||
'border-selected': withBorderPrimary,
|
||||
'transform-scale': transformScale,
|
||||
}"
|
||||
:elevation="isOuterHovering && transformScale ? 20 : 3"
|
||||
:aria-label="`${rom.name} game card`"
|
||||
>
|
||||
<v-card-text class="pa-0">
|
||||
<v-progress-linear
|
||||
v-if="romsStore.isSimpleRom(rom)"
|
||||
color="primary"
|
||||
:active="downloadStore.value.includes(rom.id)"
|
||||
:indeterminate="true"
|
||||
absolute
|
||||
/>
|
||||
<v-hover v-slot="{ isHovering, props: hoverProps }" open-delay="800">
|
||||
<v-img
|
||||
@click="handleClick"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchend="handleTouchEnd"
|
||||
v-bind="hoverProps"
|
||||
cover
|
||||
:class="{ pointer: pointerOnHover }"
|
||||
:key="romsStore.isSimpleRom(rom) ? rom.updated_at : ''"
|
||||
:src="
|
||||
src ||
|
||||
(romsStore.isSimpleRom(rom)
|
||||
? rom.path_cover_large || fallbackCoverImage
|
||||
: rom.igdb_url_cover ||
|
||||
rom.moby_url_cover ||
|
||||
rom.ss_url_cover ||
|
||||
fallbackCoverImage)
|
||||
"
|
||||
:lazy-src="
|
||||
src ||
|
||||
(romsStore.isSimpleRom(rom)
|
||||
? rom.path_cover_small || fallbackCoverImage
|
||||
: rom.igdb_url_cover ||
|
||||
rom.moby_url_cover ||
|
||||
rom.ss_url_cover ||
|
||||
fallbackCoverImage)
|
||||
"
|
||||
:aspect-ratio="computedAspectRatio"
|
||||
>
|
||||
<slot name="append-inner-left"></slot>
|
||||
</div>
|
||||
<div class="position-absolute append-inner-right" v-if="!showFav">
|
||||
<slot name="append-inner-right"> </slot>
|
||||
</div>
|
||||
<template #error>
|
||||
<v-img
|
||||
:src="fallbackCoverImage"
|
||||
cover
|
||||
:aspect-ratio="computedAspectRatio"
|
||||
></v-img>
|
||||
</template>
|
||||
<template #placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height">
|
||||
<v-progress-circular
|
||||
:width="2"
|
||||
:size="40"
|
||||
color="primary"
|
||||
indeterminate
|
||||
<div
|
||||
v-bind="props"
|
||||
style="position: absolute; top: 0; width: 100%"
|
||||
>
|
||||
<template v-if="titleOnHover">
|
||||
<v-expand-transition>
|
||||
<div
|
||||
v-if="
|
||||
isHovering ||
|
||||
(romsStore.isSimpleRom(rom) &&
|
||||
rom.is_unidentified &&
|
||||
!rom.path_cover_large) ||
|
||||
(!romsStore.isSimpleRom(rom) &&
|
||||
!rom.igdb_url_cover &&
|
||||
!rom.moby_url_cover &&
|
||||
!rom.ss_url_cover)
|
||||
"
|
||||
class="translucent-dark text-white"
|
||||
:class="
|
||||
sizeActionBar === 1 ? 'text-subtitle-1' : 'text-caption'
|
||||
"
|
||||
>
|
||||
<div :class="{ 'pa-2': sizeActionBar === 1 }">
|
||||
<v-list-item>{{ rom.name }}</v-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</template>
|
||||
<sources v-if="!romsStore.isSimpleRom(rom)" :rom="rom" />
|
||||
<v-row no-gutters class="text-white px-1">
|
||||
<game-card-flags
|
||||
v-if="romsStore.isSimpleRom(rom) && showFlags"
|
||||
:rom="rom"
|
||||
/>
|
||||
<slot name="prepend-inner"></slot>
|
||||
</v-row>
|
||||
</div>
|
||||
<div class="position-absolute append-inner-left">
|
||||
<platform-icon
|
||||
v-if="romsStore.isSimpleRom(rom) && showPlatformIcon"
|
||||
:size="25"
|
||||
:key="rom.platform_slug"
|
||||
:slug="rom.platform_slug"
|
||||
:name="rom.platform_name"
|
||||
:fs-slug="rom.platform_slug"
|
||||
class="label-platform"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<v-expand-transition>
|
||||
<action-bar
|
||||
v-if="
|
||||
showActionBar &&
|
||||
!showActionBarAlways &&
|
||||
(isOuterHovering || activeMenu) &&
|
||||
romsStore.isSimpleRom(rom) &&
|
||||
!smAndDown
|
||||
"
|
||||
class="position-absolute append-inner translucent-dark"
|
||||
@menu-open="activeMenu = true"
|
||||
@menu-close="activeMenu = false"
|
||||
:rom="rom"
|
||||
:sizeActionBar="sizeActionBar"
|
||||
/>
|
||||
</v-expand-transition>
|
||||
</v-img>
|
||||
</v-hover>
|
||||
<v-row v-if="titleOnFooter" class="pa-1 align-center">
|
||||
<v-col class="pa-0 ml-1 text-truncate">
|
||||
<span>{{ rom.name }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<slot name="footer"></slot>
|
||||
<action-bar
|
||||
v-if="
|
||||
(smAndDown || showActionBarAlways) &&
|
||||
showActionBar &&
|
||||
romsStore.isSimpleRom(rom)
|
||||
"
|
||||
:rom="rom"
|
||||
:sizeActionBar="sizeActionBar"
|
||||
/>
|
||||
</v-card>
|
||||
<div class="position-absolute append-inner-right">
|
||||
<v-btn
|
||||
v-if="
|
||||
romsStore.isSimpleRom(rom) &&
|
||||
collectionsStore.isFav(rom) &&
|
||||
showFav
|
||||
"
|
||||
tabindex="-1"
|
||||
class="label-fav"
|
||||
rouded="0"
|
||||
size="small"
|
||||
color="primary"
|
||||
>
|
||||
<v-icon class="icon-fav" size="x-small"
|
||||
>{{
|
||||
collectionsStore.isFav(rom)
|
||||
? "mdi-star"
|
||||
: "mdi-star-outline"
|
||||
}}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div
|
||||
class="position-absolute append-inner-left"
|
||||
v-if="!showPlatformIcon"
|
||||
>
|
||||
<slot name="append-inner-left"></slot>
|
||||
</div>
|
||||
<div class="position-absolute append-inner-right" v-if="!showFav">
|
||||
<slot name="append-inner-right"> </slot>
|
||||
</div>
|
||||
<template #error>
|
||||
<v-img
|
||||
:src="fallbackCoverImage"
|
||||
cover
|
||||
:aspect-ratio="computedAspectRatio"
|
||||
></v-img>
|
||||
</template>
|
||||
<template #placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height">
|
||||
<v-progress-circular
|
||||
:width="2"
|
||||
:size="40"
|
||||
color="primary"
|
||||
indeterminate
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<v-expand-transition>
|
||||
<action-bar
|
||||
v-if="
|
||||
showActionBar &&
|
||||
!showActionBarAlways &&
|
||||
(isOuterHovering || activeMenu) &&
|
||||
romsStore.isSimpleRom(rom) &&
|
||||
!smAndDown
|
||||
"
|
||||
class="position-absolute append-inner translucent-dark"
|
||||
@menu-open="activeMenu = true"
|
||||
@menu-close="activeMenu = false"
|
||||
:rom="rom"
|
||||
:sizeActionBar="sizeActionBar"
|
||||
/>
|
||||
</v-expand-transition>
|
||||
</v-img>
|
||||
</v-hover>
|
||||
<v-row v-if="titleOnFooter" class="pa-1 align-center">
|
||||
<v-col class="pa-0 ml-1 text-truncate">
|
||||
<span>{{ rom.name }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<slot name="footer"></slot>
|
||||
<action-bar
|
||||
v-if="
|
||||
(smAndDown || showActionBarAlways) &&
|
||||
showActionBar &&
|
||||
romsStore.isSimpleRom(rom)
|
||||
"
|
||||
:rom="rom"
|
||||
:sizeActionBar="sizeActionBar"
|
||||
/>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-hover>
|
||||
</template>
|
||||
|
||||
@@ -342,4 +377,8 @@ const showActionBarAlways = isNull(
|
||||
right: 0.25rem;
|
||||
bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.v-card:hover {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user