mirror of
https://github.com/Floriansylvain/ParisMuseesQuizz.git
synced 2026-08-19 19:53:22 +02:00
Implemented style, better game flow, learning mode
This commit is contained in:
@@ -175,7 +175,7 @@ async function getPainting(id?: number, name?: string): Promise<Painting | null>
|
||||
|
||||
return await prisma.painting.findUnique({
|
||||
where: id ? { id } : { name },
|
||||
include: { PaintingAuthor: { select: { author: true } } },
|
||||
include: { PaintingAuthor: { select: { author: true } }, Musuem: true },
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 137 287" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_2)">
|
||||
<rect width="137" height="287" fill="white"/>
|
||||
<path d="M-85.7607 146.769L90.991 -51.6342L166.405 15.55L-10.3467 213.953L-85.7607 146.769Z" fill="#4B68D7"/>
|
||||
<path d="M-115.288 123.92L47.3721 -80.1942L152.099 3.26319L-10.5611 207.378L-115.288 123.92Z" fill="#15245A"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_2">
|
||||
<rect width="137" height="287" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 485 B |
+8
-2
@@ -3,7 +3,13 @@ import { RouterView } from "vue-router"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
<div class="container">
|
||||
<RouterView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,49 @@
|
||||
:root {
|
||||
--color-primary: #15245A;
|
||||
--color-secondary: #4B68D7;
|
||||
--color-tertiary: #E6E6E6;
|
||||
--color-success: #0AA607;
|
||||
--color-fail: #A52853;
|
||||
|
||||
--color-text: #1e1e1e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
background-image: url(/background.svg);
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
|
||||
padding: 6px 12px;
|
||||
|
||||
background-color: var(--color-tertiary);
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-neutral {
|
||||
color: var(--color-text);
|
||||
border: solid 2px var(--color-text);
|
||||
}
|
||||
|
||||
.button-success {
|
||||
color: var(--color-success);
|
||||
border: solid 2px var(--color-success);
|
||||
}
|
||||
|
||||
.button-fail {
|
||||
color: var(--color-fail);
|
||||
border: solid 2px var(--color-fail);
|
||||
}
|
||||
@@ -1,23 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, Ref, ref } from "vue"
|
||||
|
||||
interface AuthorData {
|
||||
author: {
|
||||
id: number
|
||||
fullname: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Painting {
|
||||
id: number
|
||||
name: string
|
||||
image_url: string
|
||||
link: string
|
||||
musuemId: number
|
||||
PaintingAuthor: AuthorData[]
|
||||
}
|
||||
import { AuthorData, fetchRandomPainting, Painting } from "../utils/apiFetch"
|
||||
|
||||
const emits = defineEmits(["win", "lose"])
|
||||
const props = defineProps<{ disabled: boolean }>()
|
||||
@@ -25,17 +8,8 @@ const props = defineProps<{ disabled: boolean }>()
|
||||
const paintings: Ref<Painting[]> = ref([])
|
||||
const winningPainting: Ref<Painting> = ref({} as Painting)
|
||||
|
||||
function fetchRandomPainting(): Promise<{ paintings: Painting[] }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(`${__APP_ENV__.API_ADDRESS}/paintings/random/4`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => resolve(data))
|
||||
.catch((error) => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
async function initPaintings(): Promise<void> {
|
||||
const fetchedData = await fetchRandomPainting()
|
||||
const fetchedData = await fetchRandomPainting(4)
|
||||
paintings.value = fetchedData.paintings
|
||||
}
|
||||
|
||||
@@ -44,10 +18,20 @@ function setWinningPainting(): void {
|
||||
winningPainting.value = paintings.value[randomIndex]
|
||||
}
|
||||
|
||||
function authorClickHandler(auhtorId: number): void {
|
||||
if (auhtorId === winningPainting.value.PaintingAuthor[0].author.id) {
|
||||
function setElementClassById(id: string, cssClass: string): void {
|
||||
document.getElementById(id)?.classList.add(cssClass)
|
||||
}
|
||||
|
||||
function authorClickHandler(authorData: AuthorData): void {
|
||||
if (authorData.author.fullname === winningPainting.value.PaintingAuthor[0].author.fullname) {
|
||||
setElementClassById(authorData.author.fullname, "button-success")
|
||||
emits("win")
|
||||
} else {
|
||||
setElementClassById(authorData.author.fullname, "button-fail")
|
||||
setElementClassById(
|
||||
winningPainting.value.PaintingAuthor[0].author.fullname,
|
||||
"button-neutral"
|
||||
)
|
||||
emits("lose")
|
||||
}
|
||||
}
|
||||
@@ -68,7 +52,8 @@ onMounted(async () => {
|
||||
<div class="choices">
|
||||
<button
|
||||
v-for="painting of paintings"
|
||||
@click="() => authorClickHandler(painting.PaintingAuthor[0].author.id)"
|
||||
@click="() => authorClickHandler({ author: painting.PaintingAuthor[0].author })"
|
||||
:id="painting.PaintingAuthor[0].author.fullname"
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
>
|
||||
|
||||
+11
-1
@@ -1,4 +1,4 @@
|
||||
import { createRouter, createWebHistory } from "vue-router"
|
||||
import { createRouter, createWebHistory, RouteComponent } from "vue-router"
|
||||
import HomeView from "../views/HomeView.vue"
|
||||
|
||||
const router = createRouter({
|
||||
@@ -9,6 +9,16 @@ const router = createRouter({
|
||||
name: "home",
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: "/game/normal",
|
||||
name: "gameNormal",
|
||||
component: () => import("../views/NormalGame.vue"),
|
||||
},
|
||||
{
|
||||
path: "/game/learn",
|
||||
name: "gameLearn",
|
||||
component: () => import("../views/LearnGame.vue"),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
export interface AuthorData {
|
||||
author: {
|
||||
id: number
|
||||
fullname: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface Painting {
|
||||
id: number
|
||||
name: string
|
||||
image_url: string
|
||||
link: string
|
||||
musuemId: number
|
||||
PaintingAuthor: AuthorData[]
|
||||
Musuem: {
|
||||
id: number
|
||||
name: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchRandomPainting(limit: number): Promise<{ paintings: Painting[] }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(`${__APP_ENV__.API_ADDRESS}/paintings/random/${limit}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => resolve(data))
|
||||
.catch((error) => reject(error))
|
||||
})
|
||||
}
|
||||
+15
-84
@@ -1,68 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, Ref, ref } from "vue"
|
||||
import Quizz from "../components/Quizz.vue"
|
||||
import { useRouter } from "vue-router"
|
||||
|
||||
const gameMessage = ref("choisissez une réponse")
|
||||
const gameProgression = ref(1)
|
||||
const gameProgressionMax = 10
|
||||
const gameScore = ref(0)
|
||||
const gameShouldGoNext = ref(false)
|
||||
|
||||
function gameHandler(callback: () => void): void {
|
||||
if (gameShouldGoNext.value) return
|
||||
gameShouldGoNext.value = true
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
function winHandler(): void {
|
||||
gameMessage.value = "Bonne réponse !"
|
||||
gameScore.value += 1
|
||||
}
|
||||
|
||||
function loseHandler(): void {
|
||||
gameMessage.value = "Mauvaise réponse"
|
||||
}
|
||||
|
||||
function nextGame(): void {
|
||||
if (!gameShouldGoNext.value) return
|
||||
gameShouldGoNext.value = false
|
||||
|
||||
gameMessage.value = "choisissez une réponse"
|
||||
gameProgression.value += 1
|
||||
}
|
||||
|
||||
function resetGame(): void {
|
||||
gameMessage.value = "choisissez une réponse"
|
||||
gameProgression.value = 1
|
||||
gameScore.value = 0
|
||||
}
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<template v-if="gameProgression < gameProgressionMax">
|
||||
<p>Quiz ({{ gameProgression }}/{{ gameProgressionMax }})</p>
|
||||
<h2>Qui a peint cette oeuvre ?</h2>
|
||||
|
||||
<Quizz
|
||||
@win="() => gameHandler(winHandler)"
|
||||
@lose="() => gameHandler(loseHandler)"
|
||||
:key="gameProgression"
|
||||
:disabled="gameShouldGoNext"
|
||||
></Quizz>
|
||||
<div class="quizz-footer">
|
||||
<p>{{ gameMessage }}</p>
|
||||
<button type="button" @click="nextGame" :disabled="!gameShouldGoNext">
|
||||
suivant
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="game-over" v-else>
|
||||
<p>Vous avez {{ gameScore }}/{{ gameProgressionMax }} bonnes réponses !</p>
|
||||
<button type="button" @click="resetGame">rejouer</button>
|
||||
</div>
|
||||
<h1>Quizz peintres / peintures Paris Musées</h1>
|
||||
<button @click="() => router.push('/game/learn')">Mode apprentissage</button>
|
||||
<button @click="() => router.push('/game/normal')">Mode normal</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -70,32 +16,17 @@ function resetGame(): void {
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.container > p,
|
||||
.container > h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container > p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.quizz-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.container h1 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container button {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, Ref, ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { fetchRandomPainting, Painting } from "../utils/apiFetch"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const gameProgression = ref(1)
|
||||
const gameProgressionMax = 9
|
||||
|
||||
const paintings: Ref<Painting[]> = ref([])
|
||||
|
||||
async function initGame(): Promise<void> {
|
||||
paintings.value = (await fetchRandomPainting(gameProgressionMax)).paintings
|
||||
console.log(paintings.value)
|
||||
}
|
||||
|
||||
function nextGame(): void {
|
||||
gameProgression.value += 1
|
||||
}
|
||||
|
||||
async function resetGame(): Promise<void> {
|
||||
await initGame()
|
||||
gameProgression.value = 1
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initGame()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<template v-if="gameProgression <= gameProgressionMax">
|
||||
<p>Apprentissage ({{ gameProgression }}/{{ gameProgressionMax }})</p>
|
||||
<h2>Qui a peint cette oeuvre ?</h2>
|
||||
|
||||
<img :src="paintings[gameProgression - 1]?.image_url" alt="" />
|
||||
<div class="description">
|
||||
<p>Cette oeuvre a été peinte par</p>
|
||||
<p class="author">
|
||||
{{ paintings[gameProgression - 1]?.PaintingAuthor[0].author.fullname }}
|
||||
</p>
|
||||
<p>{{ paintings[gameProgression - 1]?.name }}</p>
|
||||
<p class="museum">{{ paintings[gameProgression - 1]?.Musuem.name }}</p>
|
||||
</div>
|
||||
|
||||
<div class="quizz-footer">
|
||||
<button type="button" @click="nextGame">suivant</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="game-over" v-else>
|
||||
<p>Vous avez terminé une série de {{ gameProgressionMax }} peintres / peintures !</p>
|
||||
<button type="button" @click="resetGame">recommencer</button>
|
||||
<button type="button" @click="() => router.push('/')">accueil</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
margin: auto;
|
||||
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.container > p,
|
||||
.container > h2 {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container > p {
|
||||
align-self: center;
|
||||
padding: 8px;
|
||||
border-bottom: solid 1px var(--color-secondary);
|
||||
}
|
||||
|
||||
.quizz-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
padding: 16px;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.museum {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.author {
|
||||
padding: 6px 8px;
|
||||
background-color: var(--color-tertiary);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import Quizz from "../components/Quizz.vue"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const gameMessageContainer = ref<HTMLElement>()
|
||||
const gameMessage = ref("choisissez une réponse")
|
||||
const gameProgression = ref(1)
|
||||
const gameProgressionMax = 10
|
||||
const gameScore = ref(0)
|
||||
const gameShouldGoNext = ref(false)
|
||||
|
||||
function gameHandler(callback: () => void): void {
|
||||
if (gameShouldGoNext.value) return
|
||||
gameShouldGoNext.value = true
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
function winHandler(): void {
|
||||
gameMessage.value = "Bonne réponse !"
|
||||
gameScore.value += 1
|
||||
gameMessageContainer.value?.classList.add("game-message-win")
|
||||
}
|
||||
|
||||
function loseHandler(): void {
|
||||
gameMessage.value = "Mauvaise réponse"
|
||||
gameMessageContainer.value?.classList.add("game-message-lose")
|
||||
}
|
||||
|
||||
function resetGameMessageClasses(): void {
|
||||
gameMessageContainer.value?.classList.remove("game-message-win")
|
||||
gameMessageContainer.value?.classList.remove("game-message-lose")
|
||||
}
|
||||
|
||||
function nextGame(): void {
|
||||
if (!gameShouldGoNext.value) return
|
||||
gameShouldGoNext.value = false
|
||||
|
||||
resetGameMessageClasses()
|
||||
gameMessage.value = "choisissez une réponse"
|
||||
gameProgression.value += 1
|
||||
}
|
||||
|
||||
function resetGame(): void {
|
||||
resetGameMessageClasses()
|
||||
gameMessage.value = "choisissez une réponse"
|
||||
gameProgression.value = 1
|
||||
gameScore.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<template v-if="gameProgression <= gameProgressionMax">
|
||||
<p>Quiz ({{ gameProgression }}/{{ gameProgressionMax }})</p>
|
||||
<h2>Qui a peint cette oeuvre ?</h2>
|
||||
|
||||
<Quizz
|
||||
@win="() => gameHandler(winHandler)"
|
||||
@lose="() => gameHandler(loseHandler)"
|
||||
:key="gameProgression"
|
||||
:disabled="gameShouldGoNext"
|
||||
></Quizz>
|
||||
<div class="quizz-footer">
|
||||
<p ref="gameMessageContainer" class="game-message">{{ gameMessage }}</p>
|
||||
<button type="button" @click="nextGame" :disabled="!gameShouldGoNext">
|
||||
suivant
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="game-over" v-else>
|
||||
<p>Vous avez {{ gameScore }}/{{ gameProgressionMax }} bonnes réponses !</p>
|
||||
<button type="button" @click="resetGame">rejouer</button>
|
||||
<button type="button" @click="() => router.push('/')">accueil</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
margin: auto;
|
||||
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.container > p,
|
||||
.container > h2 {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container > p {
|
||||
align-self: center;
|
||||
padding: 8px;
|
||||
border-bottom: solid 1px var(--color-secondary);
|
||||
}
|
||||
|
||||
.quizz-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.game-message {
|
||||
color: #848484;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.game-message-win {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.game-message-lose {
|
||||
color: var(--color-fail);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user