swipe game with overlay
This commit is contained in:
parent
f74eb98414
commit
6a818e9da5
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<motion.img
|
<motion.img
|
||||||
:src="props.card.url"
|
:src="props.card.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 ? 10 : props.z,
|
zIndex: props.isFront ? 500 : props.z,
|
||||||
gridRow: 1,
|
gridRow: 1,
|
||||||
gridColumn: 1,
|
gridColumn: 1,
|
||||||
transition: '0.125s transform',
|
transition: '0.125s transform',
|
||||||
@ -75,11 +75,7 @@ const dragValue = computed(() => (props.isFront ? "x" : false));
|
|||||||
|
|
||||||
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 rotateRaw = useTransform(x, [-150, 150], [-18, 18]);
|
const rotate = 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 handleDragEnd = () => {
|
const handleDragEnd = () => {
|
||||||
if (Math.abs(x.get()) > 50) {
|
if (Math.abs(x.get()) > 50) {
|
||||||
@ -88,6 +84,7 @@ const handleDragEnd = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function swipeRight() {
|
async function swipeRight() {
|
||||||
|
console.log(props.card.id);
|
||||||
await animate(x, 300, { duration: 0.3 });
|
await animate(x, 300, { duration: 0.3 });
|
||||||
cardStore.removeCard(props.card.id);
|
cardStore.removeCard(props.card.id);
|
||||||
}
|
}
|
||||||
|
|||||||
62
components/UploadView.vue
Normal file
62
components/UploadView.vue
Normal 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>
|
||||||
1
composables/useApiBase.ts
Normal file
1
composables/useApiBase.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const useApiBase = () => useRuntimeConfig().public.apiBase;
|
||||||
@ -15,8 +15,10 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
css: ["~/assets/css/main.css"],
|
css: ["~/assets/css/main.css"],
|
||||||
vite: { plugins: [tailwindcss()] },
|
vite: { plugins: [tailwindcss()] },
|
||||||
app: {
|
runtimeConfig: {
|
||||||
baseURL: process.env.BASE_URL || "/",
|
public: {
|
||||||
|
apiBase: process.env.API_BASE_URL || "http://localhost:8000",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
primevue: {
|
primevue: {
|
||||||
components: {
|
components: {
|
||||||
@ -35,4 +37,4 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
v-for="(card, index) in cardStore.cards"
|
v-for="(card, index) in cardStore.cards"
|
||||||
:key="card.id"
|
:key="card.id"
|
||||||
:card="card"
|
:card="card"
|
||||||
:is-front="index === cardStore.cards.length - 1"
|
:is-front="index === 0"
|
||||||
:z="index"
|
:z="index"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -15,6 +15,8 @@ import CardView from "~/components/CardView.vue";
|
|||||||
import { useCardStore } from "~/store/Card";
|
import { useCardStore } from "~/store/Card";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const collection = route.query.collection;
|
const collection: string = route.query.collection as string;
|
||||||
const cardStore = useCardStore();
|
const cardStore = useCardStore();
|
||||||
|
|
||||||
|
cardStore.fetchCardsByCollection(collection);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<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>
|
<h1 class="mb-8 text-3xl font-bold">Select a collection to begin</h1>
|
||||||
<ul class="w-full max-w-md space-y-4">
|
<ul class="w-full max-w-md space-y-4">
|
||||||
<li
|
<li
|
||||||
v-for="collection in cardStore.collections"
|
v-for="collection in cardStore.collections"
|
||||||
:key="collection"
|
:key="collection.name"
|
||||||
class="flex items-center justify-between rounded p-4 shadow"
|
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 -->
|
<!-- Large Play Button -->
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-play"
|
icon="pi pi-play"
|
||||||
@ -22,10 +22,11 @@
|
|||||||
width 0.3s,
|
width 0.3s,
|
||||||
height 0.3s;
|
height 0.3s;
|
||||||
"
|
"
|
||||||
@click="play(collection)"
|
@click="play(collection.name)"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<UploadView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,8 +35,8 @@ import { useCardStore } from "~/store/Card";
|
|||||||
|
|
||||||
const cardStore = useCardStore();
|
const cardStore = useCardStore();
|
||||||
|
|
||||||
cardStore.collections = ["Test", "Test1"];
|
cardStore.fetchCollections();
|
||||||
|
/*
|
||||||
cardStore.cards = [
|
cardStore.cards = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -84,7 +85,7 @@ cardStore.cards = [
|
|||||||
name: "Pipi",
|
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",
|
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();
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,35 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import type { Card } from "~/types/Card";
|
import type { Card } from "~/types/Card";
|
||||||
|
import type { Collection } from "~/types/Collection";
|
||||||
|
|
||||||
export const useCardStore = defineStore("cardStore", {
|
export const useCardStore = defineStore("cardStore", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
collections: [] as string[],
|
collections: [] as Collection[],
|
||||||
cards: [] as Card[],
|
cards: [] as Card[],
|
||||||
|
smashList: [] as Card[],
|
||||||
|
passList: [] as Card[],
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async fetchCollections() {
|
async fetchCollections() {
|
||||||
const { data: collections } =
|
const data = await $fetch<Collection[]>("/api/collections/", {
|
||||||
await useFetch<string[]>("/api/collections");
|
baseURL: useApiBase(),
|
||||||
this.collections = collections.value ?? [];
|
});
|
||||||
|
this.collections = data ?? [];
|
||||||
},
|
},
|
||||||
async fetchCardsByCollection(collectionName: string) {
|
async fetchCardsByCollection(collectionName: string) {
|
||||||
const response = useFetch<Card[]>(`/api/cards/${collectionName}`);
|
const data = await $fetch<Card[]>(
|
||||||
this.cards = response.data.value ?? [];
|
`/api/cards/?collection__name=${collectionName}`,
|
||||||
|
{
|
||||||
|
baseURL: useApiBase(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cards =
|
||||||
|
data.map((card, index) => ({
|
||||||
|
...card,
|
||||||
|
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);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export type Card = {
|
export type Card = {
|
||||||
id: number;
|
id: number;
|
||||||
url: string;
|
image: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|||||||
3
types/Collection.ts
Normal file
3
types/Collection.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type Collection = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
13
utils/fileNameParser.ts
Normal file
13
utils/fileNameParser.ts
Normal 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(".");
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user