clean css and fetch image uniterally
This commit is contained in:
parent
89da3d4c9f
commit
92de3895d7
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Smash or Pass</h1>
|
||||||
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<router-view :key="$route.fullPath"></router-view>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -68,7 +68,6 @@
|
|||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
*::after {
|
*::after {
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,14 @@
|
|||||||
:class="{ 'slide-right': animateTo === 'smash', 'slide-left': animateTo === 'pass' }"
|
:class="{ 'slide-right': animateTo === 'smash', 'slide-left': animateTo === 'pass' }"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="image.imageSrc"
|
:src="image?.imageSrc"
|
||||||
:alt="image.name"
|
:alt="image?.name"
|
||||||
class="card-image"
|
class="card-image"
|
||||||
@click="openImageInNewTab"
|
@click="openImageInNewTab"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
/>
|
/>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<h2>{{ image.name }}</h2>
|
<h2>{{ image?.name }}</h2>
|
||||||
<div>
|
<div>
|
||||||
<button class="button button-pass" @click="choose('pass')">PASS</button>
|
<button class="button button-pass" @click="choose('pass')">PASS</button>
|
||||||
<button class="button button-smash" @click="choose('smash')">SMASH</button>
|
<button class="button button-smash" @click="choose('smash')">SMASH</button>
|
||||||
@ -22,11 +22,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import type { FrontVisualAsset, VisualAsset } from '@/types/types.ts'
|
import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
image: {
|
image: {
|
||||||
type: Object as () => FrontVisualAsset,
|
type: Object as () => FrontVisualAssetWithSrc | null,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -44,7 +44,7 @@ const choose = (choice: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openImageInNewTab = () => {
|
const openImageInNewTab = () => {
|
||||||
window.open(props.image.imageSrc, '_blank')?.focus()
|
window.open(props.image?.imageSrc, '_blank')?.focus()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,38 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visualAssetStore.visualAssets.length">
|
<div v-if="visualAssetStore.visualAssets.length">
|
||||||
<Card
|
<Card
|
||||||
v-if="currentIndex < visualAssetStore.visualAssets.length"
|
v-if="currentIndex < visualAssetStore.visualAssets.length"
|
||||||
:image="visualAssetStore.visualAssets[currentIndex]"
|
:image="currentImage"
|
||||||
@choose="handleChoice"
|
@choose="handleChoice"
|
||||||
/>
|
/>
|
||||||
<Result
|
<Result v-else :smashList="smashList" :passList="passList" @restart="restart" />
|
||||||
v-else
|
</div>
|
||||||
:smashList="smashList"
|
|
||||||
:passList="passList"
|
|
||||||
@restart="restart"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Card from './Card.vue'
|
import Card from './Card.vue'
|
||||||
import Result from './Result.vue'
|
import Result from './Result.vue'
|
||||||
import { ref } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import type { FrontVisualAsset, VisualAsset } from '@/types/types'
|
import type { FrontVisualAsset, FrontVisualAssetWithSrc } from '@/types/types'
|
||||||
import { useVisualAssetStore } from '@/stores/visualAssetStore';
|
import { useVisualAssetStore } from '@/stores/visualAssetStore'
|
||||||
import router from '@/route';
|
import router from '@/route'
|
||||||
|
import { fetchVisualAssetById } from '@/services/visualAssetService'
|
||||||
|
|
||||||
const visualAssetStore = useVisualAssetStore();
|
const visualAssetStore = useVisualAssetStore()
|
||||||
const currentIndex = ref(0)
|
const currentIndex = ref(0)
|
||||||
|
|
||||||
const smashList = ref<FrontVisualAsset[]>([])
|
const smashList = ref<FrontVisualAssetWithSrc[]>([])
|
||||||
const passList = ref<FrontVisualAsset[]>([])
|
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) => {
|
const handleChoice = (choice: string) => {
|
||||||
|
if (!currentImage.value) return
|
||||||
if (choice === 'smash') {
|
if (choice === 'smash') {
|
||||||
smashList.value.push(visualAssetStore.visualAssets[currentIndex.value])
|
smashList.value.push(currentImage.value)
|
||||||
} else {
|
} else {
|
||||||
passList.value.push(visualAssetStore.visualAssets[currentIndex.value])
|
passList.value.push(currentImage.value)
|
||||||
}
|
}
|
||||||
currentIndex.value++
|
currentIndex.value++
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<h1>Smash or Pass</h1>
|
|
||||||
<ul class="collection-list">
|
<ul class="collection-list">
|
||||||
<li class="collection-item" v-for="(collection, name) in groupedAssets" :key="name">
|
<li class="collection-item" v-for="(collection, name) in groupedAssets" :key="name">
|
||||||
<span class="collection-name">{{ name }}</span>
|
<span class="collection-name">{{ name }}</span>
|
||||||
<button class="button button-start" @click="startGame(name)">Start</button>
|
<button class="button button-start" @click="startGame(name)">Start</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<UploadImages></UploadImages>
|
<UploadImages v-if="isAdmin"></UploadImages>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,6 +33,10 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isAdmin = computed(() => {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
const groupedAssets = computed(() => {
|
const groupedAssets = computed(() => {
|
||||||
const groupByCollection: Record<string, FrontVisualAsset[]> = {}
|
const groupByCollection: Record<string, FrontVisualAsset[]> = {}
|
||||||
for (const asset of assets.value) {
|
for (const asset of assets.value) {
|
||||||
@ -58,17 +61,20 @@ const startGame = (collection: string) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-list {
|
.collection-list {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 400px;
|
max-width: 800px;
|
||||||
padding: 0;
|
padding: 0 2rem;
|
||||||
margin: 1.5rem 0;
|
margin: 2rem 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-item {
|
.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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FrontVisualAsset } from '@/types/types.ts';
|
import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
smashList: {
|
smashList: {
|
||||||
type: Array<FrontVisualAsset>,
|
type: Array<FrontVisualAssetWithSrc>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
passList: {
|
passList: {
|
||||||
type: Array<FrontVisualAsset>,
|
type: Array<FrontVisualAssetWithSrc>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.result {
|
.result {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
gap: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.lists {
|
.lists {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -55,6 +60,7 @@ defineProps({
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-grid {
|
.image-grid {
|
||||||
@ -78,7 +84,7 @@ defineProps({
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
border-radius: 8px;
|
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;
|
object-fit: contain;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import 'vue3-toastify/dist/index.css'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
axios.defaults.baseURL = 'http://localhost:3000'
|
axios.defaults.baseURL = 'http://localhost:3001'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
|||||||
11
src/route.ts
11
src/route.ts
@ -17,16 +17,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: 'Smash or Pass Game',
|
title: 'Smash or Pass Game',
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
{
|
|
||||||
path: '/login',
|
|
||||||
name: 'login',
|
|
||||||
props: true,
|
|
||||||
component: () => import('@/components/Login.vue'),
|
|
||||||
meta: {
|
|
||||||
title: 'Login',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios'
|
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> => {
|
export const upload = async (visualAssets: VisualAsset[]): Promise<void> => {
|
||||||
try {
|
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 {
|
try {
|
||||||
const response = await axios.get(`/api/visualAssets/${id}`)
|
const response = await axios.get(`/api/visualAssets/${id}`)
|
||||||
return response.data as FrontVisualAsset
|
return response.data as FrontVisualAssetWithSrc
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching visual asset with id ${id}:`, error)
|
console.error(`Error fetching visual asset with id ${id}:`, error)
|
||||||
throw 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 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