Swipe cards one by one

This commit is contained in:
TDLaouer 2025-07-13 18:13:22 +02:00
parent 6a818e9da5
commit f9d5f0d856
6 changed files with 67 additions and 117 deletions

View File

@ -1,30 +1,27 @@
<template> <template>
<motion.img <motion.img
:src="props.card.image" v-if="currentCard"
:src="currentCard?.image"
draggable="false" draggable="false"
class="h-[60cqh] max-h-[800px] w-[400px] origin-bottom rounded-lg object-cover hover:cursor-grab active:cursor-grabbing md:w-[600px]" class="h-[60cqh] max-h-[800px] w-[400px] origin-bottom rounded-lg object-cover hover:cursor-grab active:cursor-grabbing md:w-[600px]"
:style="{ :style="{
zIndex: props.isFront ? 500 : props.z,
gridRow: 1, gridRow: 1,
gridColumn: 1, gridColumn: 1,
transition: '0.125s transform', transition: '0.125s transform',
x, x,
opacity, opacity,
rotate, rotate,
boxShadow: isFront boxShadow:
? '0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.5)' '0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.5)',
: undefined,
}" }"
alt="Picture of the author" :alt="currentCard?.name"
:drag="dragValue" drag="x"
:drag-constraints="{ left: 0, right: 0 }" :drag-constraints="{ left: 0, right: 0 }"
:on-drag-end="handleDragEnd" :on-drag-end="handleDragEnd"
:animate="{
scale: props.isFront ? 1 : 0.98,
}"
/> />
<h1 class="h-2 text-5xl">{{ currentCard?.name }}</h1>
<div v-if="props.isFront" class="flex justify-between"> <div v-if="currentCard" class="flex justify-between">
<!-- Pass Button --> <!-- Pass Button -->
<button <button
class="mx-12 flex h-24 w-42 flex-col items-center justify-center rounded-xl bg-violet-500 text-4xl text-white shadow-lg transition-colors duration-150 hover:bg-violet-600 focus:ring-4 focus:ring-violet-300 focus:outline-none" class="mx-12 flex h-24 w-42 flex-col items-center justify-center rounded-xl bg-violet-500 text-4xl text-white shadow-lg transition-colors duration-150 hover:bg-violet-600 focus:ring-4 focus:ring-violet-300 focus:outline-none"
@ -57,39 +54,48 @@ import type { Card } from "~/types/Card";
const cardStore = useCardStore(); const cardStore = useCardStore();
const props = defineProps({ const props = defineProps({
card: { cards: {
type: Object as PropType<Card>, type: Object as PropType<Card[]>,
required: true,
},
isFront: {
type: Boolean,
required: true,
},
z: {
type: Number,
required: true, required: true,
}, },
}); });
const dragValue = computed(() => (props.isFront ? "x" : false)); const emit = defineEmits<{
(e: "empty-list", isEmpty: boolean): void;
}>();
const currentCard = computed(() => props.cards[0]);
const x = useMotionValue(0); const x = useMotionValue(0);
const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]); const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);
const rotate = useTransform(x, [-150, 150], [-18, 18]); const rotate = useTransform(x, [-150, 150], [-18, 18]);
watch(
() => props.cards.length,
(newLength, oldLength) => {
if (oldLength && oldLength > 0 && newLength === 0) {
emit("empty-list", true);
}
},
{ immediate: true },
);
const handleDragEnd = () => { const handleDragEnd = () => {
if (Math.abs(x.get()) > 50) { if (Math.abs(x.get()) > 50) {
cardStore.removeCard(props.card.id); cardStore.removeCard(currentCard?.value.id);
} }
}; };
async function swipeRight() { async function swipeRight() {
console.log(props.card.id); await animate(x, 150, { duration: 0.3 });
await animate(x, 300, { duration: 0.3 }); cardStore.smashList.push(currentCard.value);
cardStore.removeCard(props.card.id); cardStore.removeCard(currentCard.value.id);
await animate(x, 0, { duration: 0.6 });
} }
async function swipeLeft() { async function swipeLeft() {
await animate(x, -300, { duration: 0.3 }); await animate(x, -150, { duration: 0.3 });
cardStore.removeCard(props.card.id); cardStore.passList.push(currentCard.value);
cardStore.removeCard(currentCard.value.id);
await animate(x, 0, { duration: 0.6 });
} }
</script> </script>

View File

@ -18,6 +18,7 @@ export default defineNuxtConfig({
runtimeConfig: { runtimeConfig: {
public: { public: {
apiBase: process.env.API_BASE_URL || "http://localhost:8000", apiBase: process.env.API_BASE_URL || "http://localhost:8000",
isUploadActive: process.env.IS_UPLOAD_ACTIVE || "false",
}, },
}, },
primevue: { primevue: {

View File

@ -1,12 +1,7 @@
<template> <template>
<div class="grid min-h-screen place-items-center"> <div v-if="loading">Loading...</div>
<CardView <div v-else class="grid min-h-screen place-items-center">
v-for="(card, index) in cardStore.cards" <CardView :cards="cardStore.cards" @empty-list="showResults" />
:key="card.id"
:card="card"
:is-front="index === 0"
:z="index"
/>
</div> </div>
</template> </template>
@ -17,6 +12,14 @@ import { useCardStore } from "~/store/Card";
const route = useRoute(); const route = useRoute();
const collection: string = route.query.collection as string; const collection: string = route.query.collection as string;
const cardStore = useCardStore(); const cardStore = useCardStore();
const loading = ref(true);
cardStore.fetchCardsByCollection(collection); onMounted(async () => {
cardStore.fetchCardsByCollection(collection);
loading.value = false;
});
const showResults = () => {
navigateTo("result");
};
</script> </script>

View File

@ -26,68 +26,22 @@
/> />
</li> </li>
</ul> </ul>
<UploadView /> <UploadView v-if="isUploadActive" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useCardStore } from "~/store/Card"; import { useCardStore } from "~/store/Card";
const config = useRuntimeConfig();
const cardStore = useCardStore(); const cardStore = useCardStore();
cardStore.fetchCollections(); cardStore.fetchCollections();
/*
cardStore.cards = [
{
id: 1,
name: "Pipi",
url: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?q=80&w=2370&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 2,
name: "Pipi",
url: "https://images.unsplash.com/photo-1512374382149-233c42b6a83b?q=80&w=2235&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 3,
name: "Pipi",
url: "https://images.unsplash.com/photo-1539185441755-769473a23570?q=80&w=2342&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 4,
name: "Pipi",
url: "https://images.unsplash.com/photo-1549298916-b41d501d3772?q=80&w=2224&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 5,
name: "Pipi",
url: "https://images.unsplash.com/photo-1516478177764-9fe5bd7e9717?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 6,
name: "Pipi",
url: "https://images.unsplash.com/photo-1570464197285-9949814674a7?q=80&w=2273&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 7,
name: "Pipi",
url: "https://images.unsplash.com/photo-1578608712688-36b5be8823dc?q=80&w=2187&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
{
id: 8,
name: "Pipi",
url: "https://images.unsplash.com/photo-1505784045224-1247b2b29cf3?q=80&w=2340&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
},
]; */
const activeItem = ref(); const activeItem = ref();
const isUploadActive = computed(() =>
config.public.isUploadActive === "true" ? true : false,
);
const play = (collection: string) => { const play = (collection: string) => {
navigateTo({ path: "game", query: { collection: collection } }); navigateTo({ path: "game", query: { collection: collection } });

View File

@ -8,14 +8,14 @@
class="grid grid-cols-1 gap-2 lg:grid-cols-3 xl:grid-cols-5 xl:gap-4" class="grid grid-cols-1 gap-2 lg:grid-cols-3 xl:grid-cols-5 xl:gap-4"
> >
<div <div
v-for="(img, idx) in imagesListOne" v-for="card in cardStore.smashList"
:key="idx" :key="card.id"
class="flex items-center justify-center rounded p-2 shadow" class="flex items-center justify-center rounded p-2 shadow"
> >
<img <img
:src="img" :src="card.image"
alt="Image from Smash List" alt="Image from Smash List"
class="h-32 w-full rounded object-cover" class="w-full rounded object-cover"
/> />
</div> </div>
</div> </div>
@ -27,42 +27,29 @@
class="grid grid-cols-1 gap-2 lg:grid-cols-3 xl:grid-cols-5 xl:gap-4" class="grid grid-cols-1 gap-2 lg:grid-cols-3 xl:grid-cols-5 xl:gap-4"
> >
<div <div
v-for="(img, idx) in imagesListTwo" v-for="card in cardStore.passList"
:key="idx" :key="card.id"
class="flex items-center justify-center rounded p-2 shadow" class="flex items-center justify-center rounded p-2 shadow"
> >
<img <img
:src="img" :src="card.image"
alt="Image from pass list" alt="Image from pass list"
class="h-32 w-full rounded object-cover" class="w-full rounded object-cover"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<Button label="Return" class="h-16 w-64" /> <Button label="Return" class="h-16 w-64" @click="goHome" />
</div> </div>
</template> </template>
<script setup> <script setup>
const imagesListOne = [ import { useCardStore } from "~/store/Card";
// Replace with your image URLs
"https://picsum.photos/id/1011/200/200",
"https://picsum.photos/id/1012/800/400",
"https://picsum.photos/id/1013/200/200",
"https://picsum.photos/id/1014/200/200",
"https://picsum.photos/id/1015/200/200",
"https://picsum.photos/id/1016/200/200",
// ...more images
];
const imagesListTwo = [ const cardStore = useCardStore();
"https://picsum.photos/id/1021/200/200",
"https://picsum.photos/id/1022/200/200", const goHome = () => {
"https://picsum.photos/id/1023/200/200", navigateTo("");
"https://picsum.photos/id/1024/200/200", };
"https://picsum.photos/id/1025/200/200",
"https://picsum.photos/id/1026/200/200",
// ...more images
];
</script> </script>

View File

@ -29,7 +29,6 @@ export const useCardStore = defineStore("cardStore", {
...card, ...card,
id: index, id: index,
})) ?? []; })) ?? [];
console.log(this.cards);
}, },
removeCard(id: number) { removeCard(id: number) {
const index = this.cards.findIndex((card) => card.id === id); const index = this.cards.findIndex((card) => card.id === id);