clean css and fetch image uniterally
This commit is contained in:
parent
89da3d4c9f
commit
92de3895d7
@ -2,6 +2,9 @@
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Smash or Pass</h1>
|
||||
</header>
|
||||
<main>
|
||||
<router-view :key="$route.fullPath"></router-view>
|
||||
</main>
|
||||
|
||||
@ -68,7 +68,6 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
:class="{ 'slide-right': animateTo === 'smash', 'slide-left': animateTo === 'pass' }"
|
||||
>
|
||||
<img
|
||||
:src="image.imageSrc"
|
||||
:alt="image.name"
|
||||
:src="image?.imageSrc"
|
||||
:alt="image?.name"
|
||||
class="card-image"
|
||||
@click="openImageInNewTab"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
<div class="controls">
|
||||
<h2>{{ image.name }}</h2>
|
||||
<h2>{{ image?.name }}</h2>
|
||||
<div>
|
||||
<button class="button button-pass" @click="choose('pass')">PASS</button>
|
||||
<button class="button button-smash" @click="choose('smash')">SMASH</button>
|
||||
@ -22,11 +22,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { FrontVisualAsset, VisualAsset } from '@/types/types.ts'
|
||||
import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
|
||||
|
||||
const props = defineProps({
|
||||
image: {
|
||||
type: Object as () => FrontVisualAsset,
|
||||
type: Object as () => FrontVisualAssetWithSrc | null,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
@ -44,7 +44,7 @@ const choose = (choice: string) => {
|
||||
}
|
||||
|
||||
const openImageInNewTab = () => {
|
||||
window.open(props.image.imageSrc, '_blank')?.focus()
|
||||
window.open(props.image?.imageSrc, '_blank')?.focus()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -2,37 +2,48 @@
|
||||
<div v-if="visualAssetStore.visualAssets.length">
|
||||
<Card
|
||||
v-if="currentIndex < visualAssetStore.visualAssets.length"
|
||||
:image="visualAssetStore.visualAssets[currentIndex]"
|
||||
:image="currentImage"
|
||||
@choose="handleChoice"
|
||||
/>
|
||||
<Result
|
||||
v-else
|
||||
:smashList="smashList"
|
||||
:passList="passList"
|
||||
@restart="restart"
|
||||
/>
|
||||
<Result v-else :smashList="smashList" :passList="passList" @restart="restart" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Card from './Card.vue'
|
||||
import Result from './Result.vue'
|
||||
import { ref } from 'vue'
|
||||
import type { FrontVisualAsset, VisualAsset } from '@/types/types'
|
||||
import { useVisualAssetStore } from '@/stores/visualAssetStore';
|
||||
import router from '@/route';
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { FrontVisualAsset, FrontVisualAssetWithSrc } from '@/types/types'
|
||||
import { useVisualAssetStore } from '@/stores/visualAssetStore'
|
||||
import router from '@/route'
|
||||
import { fetchVisualAssetById } from '@/services/visualAssetService'
|
||||
|
||||
const visualAssetStore = useVisualAssetStore();
|
||||
const visualAssetStore = useVisualAssetStore()
|
||||
const currentIndex = ref(0)
|
||||
|
||||
const smashList = ref<FrontVisualAsset[]>([])
|
||||
const passList = ref<FrontVisualAsset[]>([])
|
||||
const smashList = ref<FrontVisualAssetWithSrc[]>([])
|
||||
const passList = ref<FrontVisualAssetWithSrc[]>([])
|
||||
|
||||
const currentImage = ref<FrontVisualAssetWithSrc | null>(null)
|
||||
|
||||
watch(
|
||||
() => visualAssetStore.visualAssets[currentIndex.value],
|
||||
async (newImage: FrontVisualAsset) => {
|
||||
if (newImage) {
|
||||
currentImage.value = await fetchVisualAssetById(newImage._id)
|
||||
} else {
|
||||
currentImage.value = null
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const handleChoice = (choice: string) => {
|
||||
if (!currentImage.value) return
|
||||
if (choice === 'smash') {
|
||||
smashList.value.push(visualAssetStore.visualAssets[currentIndex.value])
|
||||
smashList.value.push(currentImage.value)
|
||||
} else {
|
||||
passList.value.push(visualAssetStore.visualAssets[currentIndex.value])
|
||||
passList.value.push(currentImage.value)
|
||||
}
|
||||
currentIndex.value++
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<div class="flex-container">
|
||||
<h1>Smash or Pass</h1>
|
||||
<ul class="collection-list">
|
||||
<li class="collection-item" v-for="(collection, name) in groupedAssets" :key="name">
|
||||
<span class="collection-name">{{ name }}</span>
|
||||
<button class="button button-start" @click="startGame(name)">Start</button>
|
||||
</li>
|
||||
</ul>
|
||||
<UploadImages></UploadImages>
|
||||
<UploadImages v-if="isAdmin"></UploadImages>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -34,6 +33,10 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const isAdmin = computed(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
const groupedAssets = computed(() => {
|
||||
const groupByCollection: Record<string, FrontVisualAsset[]> = {}
|
||||
for (const asset of assets.value) {
|
||||
@ -58,17 +61,20 @@ const startGame = (collection: string) => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.collection-list {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 0;
|
||||
margin: 1.5rem 0;
|
||||
max-width: 800px;
|
||||
padding: 0 2rem;
|
||||
margin: 2rem 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.collection-item {
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<h2>Login</h2>
|
||||
<form @submit.prevent="login">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" v-model="username" type="text" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" v-model="password" type="password" required />
|
||||
</div>
|
||||
<button class="button" type="submit">Login</button>
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const error = ref('')
|
||||
|
||||
const login = () => {
|
||||
if (username.value === 'admin' && password.value === 'password') {
|
||||
error.value = ''
|
||||
// Replace with your actual login logic or navigation
|
||||
alert('Login successful!')
|
||||
} else {
|
||||
error.value = 'Invalid username or password'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
max-width: 350px;
|
||||
margin: 3rem auto;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
|
||||
text-align: center;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem;
|
||||
text-align: left;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
input[type='text'],
|
||||
input[type='password'] {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.button {
|
||||
width: 100%;
|
||||
background: #4f8cff;
|
||||
}
|
||||
.button:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
.error {
|
||||
color: #e74c3c;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
@ -24,23 +24,28 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FrontVisualAsset } from '@/types/types.ts';
|
||||
import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
|
||||
|
||||
defineProps({
|
||||
smashList: {
|
||||
type: Array<FrontVisualAsset>,
|
||||
type: Array<FrontVisualAssetWithSrc>,
|
||||
required: true,
|
||||
},
|
||||
passList: {
|
||||
type: Array<FrontVisualAsset>,
|
||||
type: Array<FrontVisualAssetWithSrc>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.result {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
gap: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.lists {
|
||||
display: flex;
|
||||
@ -55,6 +60,7 @@ defineProps({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5rem;
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
@ -78,7 +84,7 @@ defineProps({
|
||||
max-width: 100%;
|
||||
min-width: 60px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
object-fit: contain;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import 'vue3-toastify/dist/index.css'
|
||||
import axios from 'axios'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
axios.defaults.baseURL = 'http://localhost:3000'
|
||||
axios.defaults.baseURL = 'http://localhost:3001'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
|
||||
11
src/route.ts
11
src/route.ts
@ -17,16 +17,7 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: {
|
||||
title: 'Smash or Pass Game',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
props: true,
|
||||
component: () => import('@/components/Login.vue'),
|
||||
meta: {
|
||||
title: 'Login',
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import type { FrontVisualAsset, VisualAsset } from '@/types/types.ts'
|
||||
import type { FrontVisualAsset, FrontVisualAssetWithSrc, VisualAsset } from '@/types/types.ts'
|
||||
|
||||
export const upload = async (visualAssets: VisualAsset[]): Promise<void> => {
|
||||
try {
|
||||
@ -44,10 +44,10 @@ export const fetchVisualAssetsByCollection = async (
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchVisualAssetById = async (id: string): Promise<FrontVisualAsset> => {
|
||||
export const fetchVisualAssetById = async (id: string): Promise<FrontVisualAssetWithSrc> => {
|
||||
try {
|
||||
const response = await axios.get(`/api/visualAssets/${id}`)
|
||||
return response.data as FrontVisualAsset
|
||||
return response.data as FrontVisualAssetWithSrc
|
||||
} catch (error) {
|
||||
console.error(`Error fetching visual asset with id ${id}:`, error)
|
||||
throw error
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const user = ref<string | null>(localStorage.getItem('user') || null)
|
||||
const isLoggedIn = ref<boolean>(!!user.value)
|
||||
|
||||
function login(username: string) {
|
||||
user.value = username
|
||||
isLoggedIn.value = true
|
||||
localStorage.setItem('user', username)
|
||||
}
|
||||
|
||||
function logout() {
|
||||
user.value = null
|
||||
isLoggedIn.value = false
|
||||
localStorage.removeItem('user')
|
||||
}
|
||||
|
||||
// Keep session persistent if user changes
|
||||
watch(user, (newUser) => {
|
||||
if (newUser) {
|
||||
localStorage.setItem('user', newUser)
|
||||
isLoggedIn.value = true
|
||||
} else {
|
||||
localStorage.removeItem('user')
|
||||
isLoggedIn.value = false
|
||||
}
|
||||
})
|
||||
|
||||
return { user, isLoggedIn, login, logout }
|
||||
})
|
||||
@ -1,2 +1,8 @@
|
||||
export type VisualAsset = { collection: string; image: File; name: string }
|
||||
export type FrontVisualAsset = { _id: string; collection: string; imageSrc: string; name: string }
|
||||
export type FrontVisualAsset = { _id: string; collection: string; name: string; imageSrc: string }
|
||||
export type FrontVisualAssetWithSrc = {
|
||||
_id: string
|
||||
collection: string
|
||||
name: string
|
||||
imageSrc: string
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user