swipe game with overlay

This commit is contained in:
TDLaouer 2025-07-13 16:29:02 +02:00
parent f74eb98414
commit 6a818e9da5
10 changed files with 122 additions and 26 deletions

View File

@ -1,10 +1,10 @@
<template>
<motion.img
:src="props.card.url"
:src="props.card.image"
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]"
:style="{
zIndex: props.isFront ? 10 : props.z,
zIndex: props.isFront ? 500 : props.z,
gridRow: 1,
gridColumn: 1,
transition: '0.125s transform',
@ -75,11 +75,7 @@ const dragValue = computed(() => (props.isFront ? "x" : false));
const x = useMotionValue(0);
const opacity = useTransform(x, [-150, 0, 150], [0, 1, 0]);
const rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
const rotate = useTransform(() => {
const offset = props.isFront ? 0 : props.card.id % 2 ? 4 : -4;
return `${rotateRaw.get() + offset}deg`;
});
const rotate = useTransform(x, [-150, 150], [-18, 18]);
const handleDragEnd = () => {
if (Math.abs(x.get()) > 50) {
@ -88,6 +84,7 @@ const handleDragEnd = () => {
};
async function swipeRight() {
console.log(props.card.id);
await animate(x, 300, { duration: 0.3 });
cardStore.removeCard(props.card.id);
}

62
components/UploadView.vue Normal file
View File

@ -0,0 +1,62 @@
<template>
<div class="mt-20 flex flex-col items-center gap-5">
<FloatLabel variant="in">
<InputText id="collection" v-model="collectionName" variant="filled" />
<label for="collection">Collection Name</label>
</FloatLabel>
<FileUpload
name="files[]"
:auto="false"
:multiple="true"
accept="image/*"
custom-upload
@uploader="onUpload"
>
<template #empty>
<p>Drag and drop files here to upload.</p>
</template>
</FileUpload>
</div>
</template>
<script setup lang="ts">
import type { FileUploadUploaderEvent } from "primevue";
const toast = useToast();
const collectionName = ref("");
const onUpload = async (event: FileUploadUploaderEvent) => {
const files = Array.isArray(event.files) ? event.files : [event.files];
if (collectionName.value.trim().length === 0) {
toast.error({
title: "Collection name missing",
message: "Please enter a valid collection name.",
});
return;
}
for (const file of files) {
const formData = new FormData();
formData.append("image", file);
formData.append("name", toTitleCaseWithSpaces(parseImageName(file.name)));
formData.append("collection_name", collectionName.value);
try {
await $fetch("/api/cards/", {
method: "POST",
body: formData,
baseURL: useApiBase(),
});
toast.success({
title: "Success!",
message: "Files uploaded successfully",
});
} catch (e) {
console.error(e);
toast.error({
title: "Upload failed!",
message: "Something went wrong.",
});
}
}
};
</script>

View File

@ -0,0 +1 @@
export const useApiBase = () => useRuntimeConfig().public.apiBase;

View File

@ -15,8 +15,10 @@ export default defineNuxtConfig({
],
css: ["~/assets/css/main.css"],
vite: { plugins: [tailwindcss()] },
app: {
baseURL: process.env.BASE_URL || "/",
runtimeConfig: {
public: {
apiBase: process.env.API_BASE_URL || "http://localhost:8000",
},
},
primevue: {
components: {
@ -35,4 +37,4 @@ export default defineNuxtConfig({
},
},
},
});
});

View File

@ -4,7 +4,7 @@
v-for="(card, index) in cardStore.cards"
:key="card.id"
:card="card"
:is-front="index === cardStore.cards.length - 1"
:is-front="index === 0"
:z="index"
/>
</div>
@ -15,6 +15,8 @@ import CardView from "~/components/CardView.vue";
import { useCardStore } from "~/store/Card";
const route = useRoute();
const collection = route.query.collection;
const collection: string = route.query.collection as string;
const cardStore = useCardStore();
cardStore.fetchCardsByCollection(collection);
</script>

View File

@ -1,13 +1,13 @@
<template>
<div class="flex min-h-screen flex-col items-center justify-center">
<div class="flex min-h-screen flex-col items-center justify-center gap-5">
<h1 class="mb-8 text-3xl font-bold">Select a collection to begin</h1>
<ul class="w-full max-w-md space-y-4">
<li
v-for="collection in cardStore.collections"
:key="collection"
:key="collection.name"
class="flex items-center justify-between rounded p-4 shadow"
>
<span class="text-lg font-medium">{{ collection }}</span>
<span class="text-lg font-bold">{{ collection.name }}</span>
<!-- Large Play Button -->
<Button
icon="pi pi-play"
@ -22,10 +22,11 @@
width 0.3s,
height 0.3s;
"
@click="play(collection)"
@click="play(collection.name)"
/>
</li>
</ul>
<UploadView />
</div>
</template>
@ -34,8 +35,8 @@ import { useCardStore } from "~/store/Card";
const cardStore = useCardStore();
cardStore.collections = ["Test", "Test1"];
cardStore.fetchCollections();
/*
cardStore.cards = [
{
id: 1,
@ -84,7 +85,7 @@ cardStore.cards = [
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();

View File

@ -1,20 +1,35 @@
import { defineStore } from "pinia";
import type { Card } from "~/types/Card";
import type { Collection } from "~/types/Collection";
export const useCardStore = defineStore("cardStore", {
state: () => ({
collections: [] as string[],
collections: [] as Collection[],
cards: [] as Card[],
smashList: [] as Card[],
passList: [] as Card[],
}),
actions: {
async fetchCollections() {
const { data: collections } =
await useFetch<string[]>("/api/collections");
this.collections = collections.value ?? [];
const data = await $fetch<Collection[]>("/api/collections/", {
baseURL: useApiBase(),
});
this.collections = data ?? [];
},
async fetchCardsByCollection(collectionName: string) {
const response = useFetch<Card[]>(`/api/cards/${collectionName}`);
this.cards = response.data.value ?? [];
const data = await $fetch<Card[]>(
`/api/cards/?collection__name=${collectionName}`,
{
baseURL: useApiBase(),
},
);
this.cards =
data.map((card, index) => ({
...card,
id: index,
})) ?? [];
console.log(this.cards);
},
removeCard(id: number) {
const index = this.cards.findIndex((card) => card.id === id);

View File

@ -1,5 +1,5 @@
export type Card = {
id: number;
url: string;
image: string;
name: string;
};

3
types/Collection.ts Normal file
View File

@ -0,0 +1,3 @@
export type Collection = {
name: string;
};

13
utils/fileNameParser.ts Normal file
View File

@ -0,0 +1,13 @@
export const toTitleCaseWithSpaces = (input: string): string => {
return input
.replace(/[-_]/g, " ")
.split(" ")
.filter(Boolean)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
};
export const parseImageName = (fileName: string): string => {
// Extract the name without the extension
return fileName.split(".").slice(0, -1).join(".");
};