Compare commits
241 Commits
@ -0,0 +1,39 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: ScienceQuestFront
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- front
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-container-image
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
registry: hub.codefirst.iut.uca.fr
|
||||||
|
repo: hub.codefirst.iut.uca.fr/alix.jeudi--lemoine/front
|
||||||
|
username:
|
||||||
|
from_secret: SECRET_REGISTRY_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: SECRET_REGISTRY_PASSWORD
|
||||||
|
|
||||||
|
- name: deploy-container
|
||||||
|
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
|
||||||
|
depends_on: [build-container-image]
|
||||||
|
environment:
|
||||||
|
IMAGENAME: hub.codefirst.iut.uca.fr/alix.jeudi--lemoine/front:latest
|
||||||
|
CONTAINERNAME: front
|
||||||
|
COMMAND: create
|
||||||
|
OVERWRITE: true
|
||||||
|
ADMINS: alixjeudi--lemoine,victorsoulier,gwenaelplanchon
|
||||||
|
|
||||||
|
- name: code-analysis-android
|
||||||
|
image: aosapps/drone-sonar-plugin
|
||||||
|
settings:
|
||||||
|
sonar_host:
|
||||||
|
from_secret: sonar_host
|
||||||
|
sonar_token:
|
||||||
|
from_secret: sonar_sae_token
|
@ -0,0 +1,26 @@
|
|||||||
|
# On part de l'image long term service (en alpine pour le poids)
|
||||||
|
FROM node:lts-alpine
|
||||||
|
|
||||||
|
# Installe http-server (simple serveur pour contenu statique)
|
||||||
|
RUN npm install -g http-server
|
||||||
|
|
||||||
|
# On se rends dans (et on créé) le dossier /front
|
||||||
|
WORKDIR /front
|
||||||
|
|
||||||
|
# Copie du projet node dans le dossier /projet
|
||||||
|
COPY --chown=node:node science-quest/ .
|
||||||
|
|
||||||
|
# Met en variable d'environnement le port 443 pour http-server
|
||||||
|
ENV PORT=443 BASE_URL=/containers/tombiard-front/
|
||||||
|
|
||||||
|
# Installe les dépendances nécéssaires
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Lance la construction du projet
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Expose le port 443
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
# Lance le serveur sur le dossier dist (le projet est build dedans)
|
||||||
|
CMD ["http-server", "dist"]
|
Binary file not shown.
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "ScienceQuest",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
# science-quest
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to Science Quest</title>
|
||||||
|
</head>
|
||||||
|
<body data-bs-theme="light">
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="./src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "science-quest",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build --base=$BASE_URL",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"sass": "^1.71.1",
|
||||||
|
"vue": "^3.4.15",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
|
"vite": "^5.0.11"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 206 KiB |
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import Titre from './components/Titre.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { Titre }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- header -->
|
||||||
|
<Titre />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<router-view></router-view>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,5 @@
|
|||||||
|
export const NOM_APP="ScienceQuest"
|
||||||
|
|
||||||
|
export const REST_API="https://codefirst.iut.uca.fr/containers/tombiard-api/api/v1"
|
||||||
|
|
||||||
|
export const KAHOOT_NB_APRES_LA_VIRGULE_COMPTE_A_REBOURS=2
|
After Width: | Height: | Size: 206 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
#app {
|
||||||
|
margin: 0 auto;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
<script>
|
||||||
|
import { Utilisateur } from "@/data/utilisateur"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
messageErreur:""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
creerCompte(event){
|
||||||
|
if(!formajouter.checkValidity()){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const utilisateur=new Utilisateur(Object.fromEntries(new FormData(formajouter)))
|
||||||
|
utilisateur.creerCompte().then(response=>this.$router.push("/login")).catch(ex=>this.messageErreur=ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form id="formajouter" @submit.prevent>
|
||||||
|
<h1 class="h3 mb-3 fw-normal">S'inscrire</h1>
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="email" class="form-control" id="emailInput" name="email" required>
|
||||||
|
<label for="emailInput">Email</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="text" class="form-control" id="pseudoInput" name="pseudo" required min="6">
|
||||||
|
<label for="pseudoInput">Pseudo</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="password" class="form-control" id="motDePasseInput" name="motDePasse" required min="6">
|
||||||
|
<label for="motDePasseInput">Mot de passe</label>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<p class="text-danger">{{ messageErreur }}</p>
|
||||||
|
<br/>
|
||||||
|
<button class="btn btn-lg btn-primary" @click="creerCompte">S'inscrire</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,61 @@
|
|||||||
|
<script>
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
import { Utilisateur } from "@/data/utilisateur"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
messageErreur:""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
login(){
|
||||||
|
if(!formajouter.checkValidity()){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const utilisateur=new Utilisateur(Object.fromEntries(new FormData(formajouter)))
|
||||||
|
utilisateur.connecter().then(response=>
|
||||||
|
//rediriger vers la page de son profil
|
||||||
|
this.$router.push("/profil")
|
||||||
|
).catch(ex=>this.messageErreur=ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form @submit.prevent id="formajouter">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Se connecter</h1>
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="email" class="form-control" id="emailInput" name="email" required>
|
||||||
|
<label for="emailInput">Email</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<input type="password" class="form-control" id="motDePasseInput" name="motDePasse" required min="6">
|
||||||
|
<label for="motDePasseInput">Mot de passe</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--div class="checkbox mb-3">
|
||||||
|
<label>
|
||||||
|
<label for="rememberMe">Se souvenir de moi</label>
|
||||||
|
<input type="checkbox" value="1" id="rememberMe" name="rememberMe">
|
||||||
|
</label>
|
||||||
|
</div-->
|
||||||
|
<RouterLink to="/inscription">Pas de compte?</RouterLink>
|
||||||
|
<br/>
|
||||||
|
<p class="text-danger">{{ messageErreur }}</p>
|
||||||
|
<br/>
|
||||||
|
<button class="btn btn-lg btn-primary" @click="login">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import { NOM_APP } from "@/assets/const";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nomApp: NOM_APP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/scss/landingPage.scss";
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="landingPage">
|
||||||
|
<div class="landingContent">
|
||||||
|
<h1>{{ nomApp }}</h1>
|
||||||
|
<p> Science Quest est un jeu réalisé par 6 étudiants en deuxième années de l'IUT Clermont Auvergne <br />
|
||||||
|
dans le cadre d'un projet tuteuré. Le but de ce jeu est de vous faire découvrir des scientifiques (principalement des femmes) <br />
|
||||||
|
et leurs découvertes à travers des énigmes et des mini-jeux. Bonne chance !
|
||||||
|
</p>
|
||||||
|
<p> Actuellement les jeux sont en cours de développement 🚧</p> <!-- TODO: Rajouter les jeux disponibles -->
|
||||||
|
<div class="routes-button">
|
||||||
|
<router-link to="/kahoot" class="btn btn-dark">Kahoot 🚧</router-link>
|
||||||
|
<router-link to="/pendu" class="btn btn-dark">Pendu 🚧</router-link>
|
||||||
|
</div>
|
||||||
|
<img src="@/temporary_ressources/img/marie-curie.png" alt="Marie Curie" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<p>Réalisé par des étudiants de l'IUT Clermont Auvergne <br /> <!-- TODO: Mettre les noms des étudiants -->
|
||||||
|
© 2024 Science Quest
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</template>
|
@ -0,0 +1,36 @@
|
|||||||
|
<script>
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
import { Utilisateur } from "@/data/utilisateur"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
utilisateur:null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
Utilisateur.utilisateurConnecte().then(user=>{
|
||||||
|
this.utilisateur=user
|
||||||
|
if(!this.utilisateur){
|
||||||
|
//rediriger si on n'a pas d'utilisateur connecté
|
||||||
|
//TODO : mettre noms aux routes au lieu de mettre le lien
|
||||||
|
this.$router.push("/login")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ JSON.stringify(utilisateur) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<div>{{ $route.params.id }}</div>
|
||||||
|
</template>
|
@ -0,0 +1,102 @@
|
|||||||
|
<script>
|
||||||
|
import { NOM_APP } from "@/assets/const";
|
||||||
|
import { Utilisateur } from "@/data/utilisateur";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nomApp: NOM_APP,
|
||||||
|
utilisateur: {},
|
||||||
|
estConnecte: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changerDarkMode: function () {
|
||||||
|
document.body.dataset.bsTheme != "dark" ? document.body.dataset.bsTheme = "dark" : document.body.dataset.bsTheme = "light"
|
||||||
|
},
|
||||||
|
obtenirUtilisateur(){
|
||||||
|
Utilisateur.utilisateurConnecte().then(user => {
|
||||||
|
this.estConnecte = user != null
|
||||||
|
this.utilisateur = user
|
||||||
|
})
|
||||||
|
},
|
||||||
|
seDeconnecter(){
|
||||||
|
Utilisateur.deconnecter().then(location.reload())
|
||||||
|
},
|
||||||
|
creerInvite(){
|
||||||
|
Utilisateur.utilisateurConnecteOuCreerInvite().then(user=>{location.reload()})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.obtenirUtilisateur()
|
||||||
|
//HACK : ecouter Utilisateur en boucle pour mettre a jour la barre en "temps pas trop mais suffisament reel"
|
||||||
|
window.setInterval(this.obtenirUtilisateur,5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- logo temporaire
|
||||||
|
<img alt="Vue logo" src="@/assets/logo.png" />
|
||||||
|
-->
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<router-link class="navbar-brand" to="/">{{ nomApp }}</router-link>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
|
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<!-- cote gauche -->
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<!--li class="nav-item">
|
||||||
|
TODO : trouver un moyen de gerer la page courante
|
||||||
|
<a class="nav-link active" aria-current="page" href="#">Accueil</a>
|
||||||
|
</li-->
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link class="nav-link" to="/">Accueil</router-link>
|
||||||
|
</li>
|
||||||
|
<!--li class="nav-item">
|
||||||
|
<router-link class="nav-link" to="/partie">Créer une partie</router-link>
|
||||||
|
</li-->
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
Jeux
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><router-link class="dropdown-item" to="/pendu">Pendu</router-link></li>
|
||||||
|
<li><router-link class="dropdown-item" to="/kahoot">Kahoot</router-link></li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<li><router-link class="dropdown-item disabled" to="/qui_est_ce">Qui-est-ce</router-link></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!-- cote droit -->
|
||||||
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||||
|
<button id="boutondarkmode" class="btn" v-on:click="changerDarkMode">💡</button>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
{{ utilisateur ? utilisateur?.pseudo : "Mon compte" }}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<div v-if="!estConnecte">
|
||||||
|
<li><router-link class="dropdown-item" to="/login">Se connecter</router-link></li>
|
||||||
|
<li><a @click="creerInvite">Se connecter en tant qu'invité</a></li>
|
||||||
|
</div>
|
||||||
|
<div v-if="estConnecte">
|
||||||
|
<li><router-link class="dropdown-item" to="/profil">Profil</router-link></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a @click="seDeconnecter">Se déconnecter</a></li>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -0,0 +1,49 @@
|
|||||||
|
<script>
|
||||||
|
import { REST_API } from '@/assets/const';
|
||||||
|
|
||||||
|
export default{
|
||||||
|
props:["champs", "endpoint"], //format : {"nomColonne":"typeChamp", ...} => {"nom":"text", "desc":"text", ...}
|
||||||
|
methods:{
|
||||||
|
envoyerDonnees: function(event){
|
||||||
|
const donnees=new FormData(formajouter)
|
||||||
|
|
||||||
|
//envoyer le form en JSON
|
||||||
|
fetch(REST_API+"/"+this.endpoint, {method:"POST", body:JSON.stringify(Object.fromEntries(donnees)), headers: {"Content-Type": "application/json"}})
|
||||||
|
//sans le JSON.stringify et Object.fromEntries ca fait une requete en Content-Disposition
|
||||||
|
},
|
||||||
|
typeDeChamp: function(champ){ //TODO mettre cette fonction dans un fichier commun
|
||||||
|
switch(typeof champ){
|
||||||
|
case 'number':
|
||||||
|
case 'bigint':
|
||||||
|
return "number"
|
||||||
|
case 'string':
|
||||||
|
return this.estUneDate(champ) ? "date" : "text"
|
||||||
|
case 'boolean':
|
||||||
|
return "checkbox"
|
||||||
|
case 'symbol':
|
||||||
|
case 'undefined':
|
||||||
|
case 'object':
|
||||||
|
case 'function':
|
||||||
|
return "hidden" //TODO : implementer le reste
|
||||||
|
//TODO faire un select au lieu d'un input si jamais on trouve un endpoint en mettant un "s" a la fin du nomColonne
|
||||||
|
}
|
||||||
|
},
|
||||||
|
estUneDate: function(date) { //TODO mettre cette fonction dans un fichier commun
|
||||||
|
return new Date(date) != "Invalid Date";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form id="formajouter" @submit.prevent>
|
||||||
|
<div>
|
||||||
|
<fieldset v-for="nomColonne in Object.keys(champs??{})">
|
||||||
|
<label v-show="typeDeChamp(champs[nomColonne])!='hidden'" :for="nomColonne+'_temp_add_form'">{{nomColonne}}</label>
|
||||||
|
<input class="form-control" :type="typeDeChamp(champs[nomColonne])" :id="nomColonne+'_temp_add_form'" :aria-label="nomColonne" :name="nomColonne"/>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button v-on:click="envoyerDonnees" class="btn btn-primary">Ajouter</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
@ -0,0 +1,103 @@
|
|||||||
|
<script>
|
||||||
|
import { REST_API } from '@/assets/const';
|
||||||
|
|
||||||
|
import LigneDonnee from './ListeLigne.vue';
|
||||||
|
import Ajout from './Ajout.vue';
|
||||||
|
|
||||||
|
export default{
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
endpoint:this.$route.query.endpoint ?? "thematiques", //endpoint de l'api a recuperer
|
||||||
|
//données obtenues par l'api
|
||||||
|
donnees: [],
|
||||||
|
page:0,
|
||||||
|
REST_API:REST_API,
|
||||||
|
|
||||||
|
//HATEOAS
|
||||||
|
self:"",
|
||||||
|
first:null, // a prendre a partir de la requete
|
||||||
|
prev:null,
|
||||||
|
next:null,
|
||||||
|
last:null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
this.rafraichirEndpoint()
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
rafraichirEndpoint(){
|
||||||
|
this.self=`${REST_API}/${this.endpoint}?page=${this.page}`
|
||||||
|
this.getDonnees(this.self)
|
||||||
|
},
|
||||||
|
getDonnees(url){
|
||||||
|
//HACK : s'assurer que les liens sont en HTTPS
|
||||||
|
url=url.replace("http://", "https://")
|
||||||
|
//enlever les anciens du tableau
|
||||||
|
this.donnees=[]
|
||||||
|
//TODO : ajouter un delai si jamais la requete est trop rapide pour VueJS
|
||||||
|
//appeler l'API
|
||||||
|
fetch(url).then(response=>{
|
||||||
|
response.json().then(json=>{
|
||||||
|
const oldLength=this.donnees.length
|
||||||
|
//prendre les donnees de la requete
|
||||||
|
this.donnees.push(...json._embedded)
|
||||||
|
|
||||||
|
//HATEOAS
|
||||||
|
this.self=json._links.self.href;
|
||||||
|
this.first=json._links.first ? json._links.first.href : null;
|
||||||
|
this.prev=json._links.prev ? json._links.prev.href : null;
|
||||||
|
this.next=json._links.next ? json._links.next.href : null;
|
||||||
|
this.last=json._links.last ? json._links.last.href : null;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: { LigneDonnee, Ajout }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- TODO : remplacer input par select ?-->
|
||||||
|
<label for="endpointInput">Endpoint API (REST) {{ REST_API }}/</label>
|
||||||
|
<input v-model="endpoint" id="endpointInput">
|
||||||
|
<button @click="rafraichirEndpoint()">Rafraichir</button>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" v-for="nomColonne in Object.keys(donnees[0]??{})">{{nomColonne}}</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<LigneDonnee v-for="champ in donnees"
|
||||||
|
:champs-init="champ"
|
||||||
|
></LigneDonnee>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button :class="{ invisible: !first }" class="btn btn-secondary" @click="this.getDonnees(this.first)">First</button>
|
||||||
|
<button :class="{ invisible: !prev }" class="btn btn-secondary" @click="this.getDonnees(this.prev)">Prev</button>
|
||||||
|
<button :class="{ invisible: !next }" class="btn btn-secondary" @click="this.getDonnees(this.next)">Next</button>
|
||||||
|
<button :class="{ invisible: !last }" class="btn btn-secondary" @click="this.getDonnees(this.last)">Last</button>
|
||||||
|
|
||||||
|
<!-- Button trigger modal -->
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#popupAjout">
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="popupAjout" tabindex="-1" aria-labelledby="popupLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="popupLabel">Ajout de données</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<Ajout :endpoint="endpoint" :champs="donnees[0]??{}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
@ -0,0 +1,79 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ["champsInit"],
|
||||||
|
data(){
|
||||||
|
return{
|
||||||
|
modeEdition:false, //vrai = remplacer les champs par des inputs
|
||||||
|
|
||||||
|
champs:this.champsInit
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
changerModeEdition: function(){
|
||||||
|
this.modeEdition=!this.modeEdition
|
||||||
|
},
|
||||||
|
annuler: function(){
|
||||||
|
//remettre les valeurs a 0
|
||||||
|
this.champs=this.champsInit
|
||||||
|
|
||||||
|
this.changerModeEdition()
|
||||||
|
},
|
||||||
|
sauverScientifique: function(){
|
||||||
|
//TODO : comme dans ajout
|
||||||
|
//TODO comment trouver l'endpoint depuis Liste.vue ?
|
||||||
|
//fetch("localhost/api/v1/scientifiques", {method:"PUT", body:JSON.stringify(donnees)})
|
||||||
|
console.log(this.champs)
|
||||||
|
this.changerModeEdition()
|
||||||
|
},
|
||||||
|
typeDeChamp: function(champ){
|
||||||
|
switch(typeof champ){
|
||||||
|
case 'number':
|
||||||
|
case 'bigint':
|
||||||
|
return "number"
|
||||||
|
case 'string':
|
||||||
|
return this.estUneDate(champ) ? "date" : "text"
|
||||||
|
case 'boolean':
|
||||||
|
return "checkbox"
|
||||||
|
case 'symbol':
|
||||||
|
case 'undefined':
|
||||||
|
case 'object':
|
||||||
|
case 'function':
|
||||||
|
return "hidden" //TODO : implementer le reste
|
||||||
|
}
|
||||||
|
},
|
||||||
|
estUneDate: function(date) {
|
||||||
|
return new Date(date) != "Invalid Date";
|
||||||
|
},
|
||||||
|
formatterString(champ){
|
||||||
|
//mieux formatter les dates
|
||||||
|
if(this.typeDeChamp(champ)=="date"){
|
||||||
|
return new Date(champ).toLocaleString()
|
||||||
|
}
|
||||||
|
return champ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr v-if="!this.modeEdition">
|
||||||
|
<td v-for="champ in champs">
|
||||||
|
<p>{{ formatterString(champ) }}</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn-outline-secondary btn" v-on:click="changerModeEdition()">Modifier</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="this.modeEdition">
|
||||||
|
|
||||||
|
<!-- TODO : fix date, creer input objet (bouton qui ouvre une popup qui propose de changer les champs de l'objet)-->
|
||||||
|
<td v-for="cleChamp in Object.keys(champs)">
|
||||||
|
<input class="form-control" :type="typeDeChamp(champs[cleChamp])" v-model="champs[cleChamp]" :aria-label="cleChamp">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-success" v-on:click="sauverScientifique()">Sauvegarder</button>
|
||||||
|
<button class="btn btn-secondary" v-on:click="annuler()">Annuler</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
404
|
||||||
|
</template>
|
@ -0,0 +1,84 @@
|
|||||||
|
<script>
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
import { Partie } from '@/data/partie';
|
||||||
|
import { ListeJeux } from '@/data/jeu';
|
||||||
|
import { Thematiques } from '@/data/thematique';
|
||||||
|
import { Difficultes } from '@/data/difficulte';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
jeuxDispo:[],
|
||||||
|
|
||||||
|
thematiquesDispo:[],
|
||||||
|
choixThematiques:[],
|
||||||
|
|
||||||
|
difficultesDispo:[],
|
||||||
|
choixDifficulte:-1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
login(){
|
||||||
|
const partieACreer=new Partie(Object.fromEntries(new FormData(formajouter)))
|
||||||
|
partieACreer.thematiques=this.choixThematiques
|
||||||
|
partieACreer.creerPartie().then(response=>console.log(response))
|
||||||
|
//console.log(partieACreer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
ListeJeux.get().then(jeux=>this.jeuxDispo=Object.values(jeux))
|
||||||
|
Difficultes.getPage(0,999).then(difficultes=>{
|
||||||
|
this.difficultesDispo=difficultes._embedded
|
||||||
|
//choisir une difficulté par défaut
|
||||||
|
this.choixDifficulte=this.difficultesDispo[0].id
|
||||||
|
})
|
||||||
|
Thematiques.getPage(0,999).then(thematiques=>this.thematiquesDispo=thematiques._embedded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form @submit.prevent id="formajouter">
|
||||||
|
<h1 class="h3 mb-3 fw-normal">Creer une partie</h1>
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
</div>
|
||||||
|
<div class="form-floating">
|
||||||
|
<select name="idJeu">
|
||||||
|
<option v-for="jeu in jeuxDispo" :value="jeu.id" required>
|
||||||
|
{{ jeu.nom }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox mb-3">
|
||||||
|
<label for="thematiquesInput">Thématiques</label>
|
||||||
|
<br/>
|
||||||
|
<select v-model="choixThematiques" id="thematiquesInput" multiple required>
|
||||||
|
<option v-for="thematique in thematiquesDispo" :value="thematique.id">
|
||||||
|
{{ thematique.libelle }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox mb-3">
|
||||||
|
<label for="idDifficulteInput">Difficulté</label>
|
||||||
|
<br/>
|
||||||
|
<select v-model="choixDifficulte" id="idDifficulteInput" name="idDifficulte" required>
|
||||||
|
<option v-for="difficulte in difficultesDispo" :value="difficulte.id">
|
||||||
|
{{ difficulte.libelle }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary" @click="login">Créer une partie</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,143 @@
|
|||||||
|
<script>
|
||||||
|
import { Difficultes } from '@/data/difficulte';
|
||||||
|
import KahootListeParties from './KahootListeParties.vue'
|
||||||
|
import {Partie} from "@/data/partie"
|
||||||
|
import { Thematiques } from '@/data/thematique';
|
||||||
|
import {KahootPartie} from '@/data/kahoot';
|
||||||
|
|
||||||
|
//TODO définir les méthodes -> à définir grâce à l'API
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
//creer partie
|
||||||
|
titreKahoot: "",
|
||||||
|
nbQuestions: 0,
|
||||||
|
//rejoindre partie
|
||||||
|
codeKahootARejoindre: "",
|
||||||
|
//listes parties crees ( TODO : appeler l'api pour obtenir les parties)
|
||||||
|
partiesCrees: [],
|
||||||
|
|
||||||
|
//input popup creation partie
|
||||||
|
thematiquesDispo:[],
|
||||||
|
choixThematiques:[],
|
||||||
|
|
||||||
|
difficultesDispo:[],
|
||||||
|
choixDifficulte:-1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
this.STUB_partiesCrees().then(response=>this.partiesCrees=response)
|
||||||
|
|
||||||
|
Difficultes.getPage(0,999).then(difficultes=>{
|
||||||
|
this.difficultesDispo=difficultes._embedded
|
||||||
|
//choisir une difficulté par défaut
|
||||||
|
this.choixDifficulte=this.difficultesDispo[0].id
|
||||||
|
})
|
||||||
|
Thematiques.getPage(0,999).then(thematiques=>this.thematiquesDispo=thematiques._embedded)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// TODO : demander a l'api de creer un kahoot (et rediriger vers la partie si possible via HATEOAS)
|
||||||
|
creerKahoot: function () {
|
||||||
|
if(!formCreerKahoot.checkValidity()){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const partie=new KahootPartie({
|
||||||
|
"thematiques": this.choixThematiques,
|
||||||
|
"idDifficulte": this.choixDifficulte
|
||||||
|
})
|
||||||
|
partie.creerPartie().then(kahoot=>{
|
||||||
|
//fermer la popup
|
||||||
|
fermerPopup.click()
|
||||||
|
//rejoindre sa propre partie
|
||||||
|
this.codeKahootARejoindre=kahoot.codeInvitation
|
||||||
|
this.rejoindrePartie()
|
||||||
|
//afficher le bouton qui uniquement la pour le créateur de la partie
|
||||||
|
window.setTimeout(function(){boutonDemarrerKahoot.style.visibility=""}, 1000)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// TODO : demander a l'api de rejoindre un kahoot (et rediriger vers la partie si possible via HATEOAS)
|
||||||
|
rejoindrePartie(){
|
||||||
|
this.$router.push(`/kahoot/partie/${this.codeKahootARejoindre}`)
|
||||||
|
},
|
||||||
|
async STUB_partiesCrees(){
|
||||||
|
return JSON.parse(`[
|
||||||
|
{"titreKahoot":"Titre du Kahoot", "nbQuestions":10, "createur":"Professeur X"},
|
||||||
|
{"titreKahoot":"Titre du Kahoot2", "nbQuestions":69, "createur":"Professeur Y"},
|
||||||
|
{"titreKahoot":"Titre du Kahoot3", "nbQuestions":234, "createur":"Professeur XXX"}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: { KahootListeParties }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/scss/kahoot.scss";
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="Kahoot">
|
||||||
|
<h1 style="padding-left: 0.5em;">Kahoot</h1>
|
||||||
|
<div class="Kahoot-Header">
|
||||||
|
<h2>Rejoindre un Kahoot</h2>
|
||||||
|
<button type="button" class="btn btn-light" data-bs-toggle="modal" data-bs-target="#createKahootModal">
|
||||||
|
Créer un Kahoot
|
||||||
|
</button>
|
||||||
|
<form @submit.prevent>
|
||||||
|
<label for="Kahoot-Code">Code</label>
|
||||||
|
<input class="form-control bg-light" type="text" id="Kahoot-Code" name="Kahoot-Code" v-model="codeKahootARejoindre" minlength="6" maxlength="10">
|
||||||
|
<button class="btn btn-light" v-on:click="rejoindrePartie">Rejoindre</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="Kahoot-content">
|
||||||
|
<div class="Kahoot-List">
|
||||||
|
<h2> Vos Quizz</h2>
|
||||||
|
<KahootListeParties v-for="partie in partiesCrees" :titreKahoot="partie.titreKahoot" :nbQuestions="partie.nbQuestions" :createur="partie.createur"></KahootListeParties>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="createKahootModal" tabindex="-1" role="dialog" aria-labelledby="creationKahoot" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title" id="creationKahoot">Créer un Kahoot</h2>
|
||||||
|
</div>
|
||||||
|
<form id="formCreerKahoot" @submit.prevent>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!--div>
|
||||||
|
<label for="Kahoot-Create-Title">Titre</label>
|
||||||
|
<input class="form-control" type="text" id="Kahoot-Create-Title" name="Kahoot-Create-Title" v-model="titreKahoot" required minlength="2" maxlength="255">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="Kahoot-Create-Questions">Nombre de questions</label>
|
||||||
|
<input class="form-control" type="number" id="Kahoot-Create-Questions" name="Kahoot-Create-Questions" v-model="nbQuestions" required min="1" max="99">
|
||||||
|
</div-->
|
||||||
|
<div class="checkbox mb-3">
|
||||||
|
<label for="thematiquesInput">Thématiques</label>
|
||||||
|
<br/>
|
||||||
|
<select v-model="choixThematiques" id="thematiquesInput" multiple required>
|
||||||
|
<option v-for="thematique in thematiquesDispo" :value="thematique.id">
|
||||||
|
{{ thematique.libelle }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox mb-3">
|
||||||
|
<label for="idDifficulteInput">Difficulté</label>
|
||||||
|
<br/>
|
||||||
|
<select v-model="choixDifficulte" id="idDifficulteInput" name="idDifficulte" required>
|
||||||
|
<option v-for="difficulte in difficultesDispo" :value="difficulte.id">
|
||||||
|
{{ difficulte.libelle }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="fermerPopup" type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button class="btn btn-primary" v-on:click="creerKahoot">Créer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ["titreKahoot", "nbQuestions", "createur"]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="Kahoot-List-Item">
|
||||||
|
<h2>{{ titreKahoot }}</h2>
|
||||||
|
<p>{{ nbQuestions }}</p>
|
||||||
|
<p>{{ createur }}</p>
|
||||||
|
<button class="btn btn-dark">Jouer</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -0,0 +1,198 @@
|
|||||||
|
<script>
|
||||||
|
import { KAHOOT_NB_APRES_LA_VIRGULE_COMPTE_A_REBOURS} from "@/assets/const"
|
||||||
|
import { Kahoot } from "@/data/kahoot"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
codePartie: this.$route.params.code ?? -1,
|
||||||
|
kahootAPI:null,
|
||||||
|
//unix timestamp pour indiquer la date limite pour repondre a la question, -1 indiquera la fin de la partie
|
||||||
|
tempsLimite:0,
|
||||||
|
compteARebours:0,
|
||||||
|
compteAReboursId:0, //id donné par le setInterval pour pouvoir l'arreter quand il est a 0
|
||||||
|
obtenirTimeoutId:0, //id donné par le setTimeout
|
||||||
|
|
||||||
|
//affichage dans la vue
|
||||||
|
etats:{
|
||||||
|
question:true, //afficher la question
|
||||||
|
score:false, //afficher les scores
|
||||||
|
salleAttente:false, //afficher la salle d'attente (ecran avec pseudos)
|
||||||
|
},
|
||||||
|
|
||||||
|
//variables pour l'etat question
|
||||||
|
question:{
|
||||||
|
question:"",
|
||||||
|
reponses:{},
|
||||||
|
},
|
||||||
|
//variables pour la salle d'attente
|
||||||
|
salleAttente:{
|
||||||
|
joueurs:[],
|
||||||
|
partieDemarree:false,
|
||||||
|
},
|
||||||
|
//variables pour les scores
|
||||||
|
score:{
|
||||||
|
score:0,
|
||||||
|
pointsGagne:0,
|
||||||
|
},
|
||||||
|
pointsAnimation:0,
|
||||||
|
partieTerminee:false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
this.kahootAPI=new Kahoot(this.codePartie)
|
||||||
|
this.obtenirSalleAttente()
|
||||||
|
|
||||||
|
},
|
||||||
|
unmounted(){
|
||||||
|
//arreter le jeu quand la page n'est plus affichée
|
||||||
|
window.clearTimeout(this.obtenirTimeoutId)
|
||||||
|
window.clearInterval(this.compteAReboursId)
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
//retourne la fonction appropriée
|
||||||
|
choisirLeProchainEtat(){
|
||||||
|
//si la partie n'a pas démarrée, on reste dans la salle d'attente
|
||||||
|
if(!this.salleAttente.partieDemarree){
|
||||||
|
return this.obtenirSalleAttente
|
||||||
|
}
|
||||||
|
//si on vient de faire une question, on demander les resultats
|
||||||
|
if(this.etats.question){
|
||||||
|
return this.obtenirScores
|
||||||
|
}
|
||||||
|
return this.obtenirQuestion
|
||||||
|
},
|
||||||
|
obtenirQuestion(){
|
||||||
|
this.kahootAPI.obtenirQuestion().then(response=>{
|
||||||
|
this.resetEtats() //cacher l'etat precedent
|
||||||
|
this.tempsLimite=response.tempsLimite
|
||||||
|
//afficher cet etat
|
||||||
|
this.etats.question=true
|
||||||
|
|
||||||
|
this.question=response.questionActuel
|
||||||
|
|
||||||
|
if(this.tempsLimite!=-1){
|
||||||
|
//executer la fonction en boucle jusqu'a ce que la partie se termine
|
||||||
|
this.obtenirTimeoutId=window.setTimeout(this.choisirLeProchainEtat(),(this.tempsLimite+100)-Date.now())
|
||||||
|
//demarrer le compte a rebours
|
||||||
|
this.compteAReboursId=window.setInterval(this.calculerCompteARebours,22)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).catch(ex=>this.partieTerminee=true)
|
||||||
|
},
|
||||||
|
obtenirScores(){
|
||||||
|
this.resetEtats() //cacher l'etat precedent
|
||||||
|
this.kahootAPI.obtenirScore(this.score.score).then(response=>{
|
||||||
|
this.tempsLimite=response.tempsLimite
|
||||||
|
this.score=response
|
||||||
|
//afficher cet etat
|
||||||
|
this.etats.score=true
|
||||||
|
|
||||||
|
//reduire les points pour pouvoir l'incrementer progressivement
|
||||||
|
this.pointsAnimation=this.score.score-this.score.pointsGagne
|
||||||
|
this.animerIncrementationPoints()
|
||||||
|
|
||||||
|
if(this.tempsLimite!=-1){
|
||||||
|
//executer la fonction en boucle jusqu'a ce que la partie se termine
|
||||||
|
this.obtenirTimeoutId=window.setTimeout(this.choisirLeProchainEtat(),(this.tempsLimite+100)-Date.now())
|
||||||
|
//demarrer le compte a rebours
|
||||||
|
this.compteAReboursId=window.setInterval(this.calculerCompteARebours,22)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
obtenirSalleAttente(){
|
||||||
|
this.resetEtats() //cacher l'etat precedent
|
||||||
|
//afficher cet etat
|
||||||
|
this.etats.salleAttente=true
|
||||||
|
this.kahootAPI.obtenirSalleAttente().then(response=>{
|
||||||
|
this.tempsLimite=response.tempsLimite
|
||||||
|
this.salleAttente=response
|
||||||
|
|
||||||
|
this.obtenirTimeoutId=window.setTimeout(this.choisirLeProchainEtat(),(this.tempsLimite+100)-Date.now())
|
||||||
|
if(this.salleAttente.partieDemarree){
|
||||||
|
//demarrer le compte a rebours
|
||||||
|
this.compteAReboursId=window.setInterval(this.calculerCompteARebours,22)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
},
|
||||||
|
repondre(reponse){
|
||||||
|
this.kahootAPI.repondreQuestion(reponse).then()
|
||||||
|
|
||||||
|
const reponseEnvoyee=this.question.reponses.find(r=>r.id==reponse).reponse
|
||||||
|
this.question.question=`Réponse "${reponseEnvoyee}" envoyée`
|
||||||
|
this.question.reponses=[]
|
||||||
|
},
|
||||||
|
calculerCompteARebours(){
|
||||||
|
if(this.tempsLimite<Date.now()){
|
||||||
|
//si il reste plus de temps
|
||||||
|
this.compteARebours=(0).toFixed(KAHOOT_NB_APRES_LA_VIRGULE_COMPTE_A_REBOURS)
|
||||||
|
//arreter le compte a rebours
|
||||||
|
window.clearInterval(this.compteAReboursId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.compteARebours=((this.tempsLimite-Date.now())/1000).toFixed(KAHOOT_NB_APRES_LA_VIRGULE_COMPTE_A_REBOURS)
|
||||||
|
},
|
||||||
|
resetEtats(){
|
||||||
|
Object.keys(this.etats).forEach(nomEtat=>this.etats[nomEtat]=0)
|
||||||
|
},
|
||||||
|
animerIncrementationPoints(){
|
||||||
|
if(this.pointsAnimation<this.score.score){
|
||||||
|
this.pointsAnimation++
|
||||||
|
//continuer jusqu'a que les points sont tous additionnés
|
||||||
|
window.setTimeout(this.animerIncrementationPoints,5)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
demarrerPartie(){
|
||||||
|
this.kahootAPI.demarrerPartie().then(boutonDemarrerKahoot.style.visibility="hidden")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.jeu{
|
||||||
|
display:flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard{
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="jeu">
|
||||||
|
<!-- Afficher le compte a rebours seulement quand la partie va demarrer, pour eviter de prendre par surprise les joueurs qui attendent dans la salle d'attente-->
|
||||||
|
<p v-if="salleAttente.partieDemarree">Temps : {{ compteARebours }}s</p>
|
||||||
|
<div v-show="etats.question">
|
||||||
|
<p>{{ question.question }}</p>
|
||||||
|
<button v-for="reponse in question.reponses" @click="repondre(reponse.id)">{{ reponse.reponse }}</button>
|
||||||
|
</div>
|
||||||
|
<div v-show="etats.score">
|
||||||
|
<h2 v-if="partieTerminee">Partie Terminée</h2>
|
||||||
|
<h2>Votre score : {{ pointsAnimation }} (+{{ score.pointsGagne }})</h2>
|
||||||
|
<ol class="leaderboard">
|
||||||
|
<li v-for="scorejoueur in score.scores">
|
||||||
|
{{ scorejoueur.joueur.pseudo }} : {{scorejoueur.score}}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div v-show="etats.salleAttente">
|
||||||
|
<ul>
|
||||||
|
<h2>Code : {{ codePartie }}</h2>
|
||||||
|
<p>Invitez tout le monde !</p>
|
||||||
|
<li v-for="joueur in salleAttente.joueurs">
|
||||||
|
{{ joueur }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button style="visibility: hidden" id="boutonDemarrerKahoot" @click="demarrerPartie()">Démarrer la partie</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -0,0 +1,226 @@
|
|||||||
|
<script>
|
||||||
|
import PenduDessin from './PenduDessin.vue'
|
||||||
|
import { REST_API } from "@/assets/const";
|
||||||
|
import { Scientifiques } from "@/data/scientifique"
|
||||||
|
import { Thematiques } from '@/data/thematique';
|
||||||
|
import { Difficultes } from '@/data/difficulte';
|
||||||
|
|
||||||
|
export default{
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nbLettresADeviner: 0,
|
||||||
|
progression: "",
|
||||||
|
viesRestantes: 0, //-1 == pendu; partie terminée,
|
||||||
|
viesMaximum:10,
|
||||||
|
partieTerminee: true, //plus de lettres a deviner
|
||||||
|
premierePartie: true, //ne pas afficher "Perdu" pour ceux qui viennent de rejoindre
|
||||||
|
lettresDejaDevine: "",
|
||||||
|
|
||||||
|
afficherChoixThematiques:false,
|
||||||
|
thematiquesDispo:[],
|
||||||
|
choixThematique:-1,
|
||||||
|
|
||||||
|
afficherChoixDifficultes:false,
|
||||||
|
difficultesDispo:[],
|
||||||
|
choixDifficulte:-1,
|
||||||
|
|
||||||
|
//a recuperer a partir de l'api (prendre nom et prenom d'un scientifique nous meme)
|
||||||
|
motADeviner: "einstein",
|
||||||
|
description: "", //s'affiche en dessous du resultat a la fin
|
||||||
|
imageScientifique: "",
|
||||||
|
api_pagesMaximum: 0, //impossible de connaitre le nombre de page a l'avance
|
||||||
|
|
||||||
|
regexExceptions: [ //caracteres qu'on ne fera pas deviner au joueur
|
||||||
|
/\W/, //caracteres blanc
|
||||||
|
/[^a-z]/, //non alphabetique minuscule
|
||||||
|
],
|
||||||
|
lettresANePasFaireDevinerAuJoueur:"", //meme utilité que lettresDejaDevine mais n'est pas visible au joueur
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
afficherChoixThematiques(to){
|
||||||
|
if(to && this.thematiquesDispo.length==0){
|
||||||
|
Thematiques.getPage(0,999).then(thematiques=>this.thematiquesDispo=thematiques._embedded)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
afficherChoixDifficultes(to){
|
||||||
|
if(to && this.difficultesDispo.length==0){
|
||||||
|
Difficultes.getPage(0,999).then(difficultes=>this.difficultesDispo=difficultes._embedded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
creerPartie: function () {
|
||||||
|
this.lettresDejaDevine = "";
|
||||||
|
this.lettresANePasFaireDevinerAuJoueur="";
|
||||||
|
this.progression="";
|
||||||
|
|
||||||
|
//appeler l'API
|
||||||
|
Scientifiques.getPage(
|
||||||
|
this.intAleatoire(this.api_pagesMaximum),
|
||||||
|
0,
|
||||||
|
this.afficherChoixThematiques ? this.choixThematique : -1,
|
||||||
|
this.afficherChoixDifficultes ? this.choixDifficulte : -1
|
||||||
|
).then(json=>{
|
||||||
|
//prendre le scientifique de la requete
|
||||||
|
const arrayScientifique=json._embedded
|
||||||
|
const scientifiqueADeviner=arrayScientifique[this.intAleatoire(arrayScientifique.length)]
|
||||||
|
//prendre le mot a deviner a partir du nom du scientifique
|
||||||
|
this.motADeviner = scientifiqueADeviner.nomComplet.toLowerCase()
|
||||||
|
this.description = scientifiqueADeviner.descriptif
|
||||||
|
this.imageScientifique = scientifiqueADeviner.pathToPhoto
|
||||||
|
|
||||||
|
//mettre a jour le nombre de pages maximum de l'api scientifiques
|
||||||
|
this.api_pagesMaximum=json.page.totalPages
|
||||||
|
|
||||||
|
//verifier que le mot a deviner ne contient pas des lettres exemptées
|
||||||
|
this.motADeviner.split("").forEach(lettre=>
|
||||||
|
this.regexExceptions.forEach(regex=>regex.test(lettre) ? this.lettresANePasFaireDevinerAuJoueur+=lettre /* faire jouer la lettre a la place de l'utilisateur */ : null)
|
||||||
|
)
|
||||||
|
|
||||||
|
//rafraichir la progression pour enlever les lettres a ne pas faire deviner
|
||||||
|
this.progression = this.afficherProgression()
|
||||||
|
//compter le nombre de trous (enlever tout ce qui est pas underscore et compter)
|
||||||
|
this.nbLettresADeviner = this.progression.replace(/[^_]/g, "").length
|
||||||
|
|
||||||
|
this.viesRestantes=this.viesMaximum;
|
||||||
|
//demarrer le jeu
|
||||||
|
this.afficherLeJeu()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
afficherLeJeu(){
|
||||||
|
this.partieTerminee = false;
|
||||||
|
this.premierePartie = false;
|
||||||
|
},
|
||||||
|
deviner: function (event) {
|
||||||
|
//prendre la lettre depuis l'event
|
||||||
|
const lettreDevinee = event.data.toLowerCase();
|
||||||
|
//vider l'input
|
||||||
|
event.target.value = "";
|
||||||
|
//voir si la lettre devinée est valide
|
||||||
|
let lettreValide=true
|
||||||
|
this.regexExceptions.forEach(regex=>lettreValide ? lettreValide=!regex.test(lettreDevinee) : null)
|
||||||
|
if(!lettreValide){
|
||||||
|
//ne pas faire deviner une lettre invalide
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//ajouter la lettre dans la liste des lettres devinées
|
||||||
|
if (!this.lettresDejaDevine.includes(lettreDevinee)) {
|
||||||
|
this.lettresDejaDevine += lettreDevinee;
|
||||||
|
} else {
|
||||||
|
//ne pas faire deviner une lettre qui a deja été devinée
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//comparer la progression
|
||||||
|
const oldprogression = this.progression;
|
||||||
|
this.progression = this.afficherProgression();
|
||||||
|
|
||||||
|
if (oldprogression == this.progression) {
|
||||||
|
//si on n'a pas progressé = lettre incorrecte
|
||||||
|
this.viesRestantes--; //l'api devrait aussi retourner le nombre de vies restantes
|
||||||
|
|
||||||
|
if(this.viesRestantes<0){
|
||||||
|
this.partieTerminee = true
|
||||||
|
this.progression = this.afficherProgression();
|
||||||
|
}
|
||||||
|
} else if (!this.progression.includes("_")) {
|
||||||
|
//plus de lettres a deviner
|
||||||
|
this.partieTerminee = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
afficherProgression: function () {
|
||||||
|
if (this.viesRestantes < 0) {
|
||||||
|
return this.motADeviner; //plus de vies = fin de la partie, on retourne le mot qu'on devait trouver
|
||||||
|
}
|
||||||
|
let progression = "";
|
||||||
|
const lettresAAfficher=this.lettresDejaDevine + this.lettresANePasFaireDevinerAuJoueur;
|
||||||
|
this.motADeviner.split("").forEach(w =>lettresAAfficher.includes(w) ? progression += w : progression += "_");
|
||||||
|
return progression;
|
||||||
|
},
|
||||||
|
intAleatoire: function(nb){
|
||||||
|
return Math.floor(Math.random() * nb)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: { PenduDessin }
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1 style="padding-left: 0.5em;">Pendu</h1>
|
||||||
|
<div class="separateur">
|
||||||
|
<div v-if="partieTerminee" class="divjeu">
|
||||||
|
<!-- hors partie -->
|
||||||
|
<div v-if="!premierePartie">
|
||||||
|
<div v-if="viesRestantes >= 0">
|
||||||
|
Gagné!
|
||||||
|
</div>
|
||||||
|
<div v-if="viesRestantes < 0">
|
||||||
|
Perdu!
|
||||||
|
</div>
|
||||||
|
<p>Le mot était : </p>
|
||||||
|
<!-- l'api devrait retourner le mot entier quand la vie est a 0 -->
|
||||||
|
<h2 style="font-family: monospace">{{ progression }}</h2>
|
||||||
|
<p>{{ description }}</p>
|
||||||
|
<img :src="imageScientifique" :alt="'Photo de '+motADeviner">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" v-on:click="creerPartie">Créer une partie</button>
|
||||||
|
<div>
|
||||||
|
<label for="afficherChoixThematiquesCheckbox">Choisir une thématique </label>
|
||||||
|
<input type="checkbox" id="afficherChoixThematiquesCheckbox" v-model="afficherChoixThematiques"/>
|
||||||
|
<br/>
|
||||||
|
<select v-if="afficherChoixThematiques" v-model="choixThematique">
|
||||||
|
<option v-for="thematique in thematiquesDispo" :value="thematique.id">
|
||||||
|
{{ thematique.libelle }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="afficherChoixDifficultesCheckbox">Choisir une difficulté </label>
|
||||||
|
<input type="checkbox" id="afficherChoixDifficultesCheckbox" v-model="afficherChoixDifficultes"/>
|
||||||
|
<br/>
|
||||||
|
<select v-if="afficherChoixDifficultes" v-model="choixDifficulte">
|
||||||
|
<option v-for="difficulte in difficultesDispo" :value="difficulte.id">
|
||||||
|
{{ difficulte.libelle }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!partieTerminee" class="divjeu">
|
||||||
|
<!--PenduDessin :viesRestantes="viesRestantes"></PenduDessin-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!partieTerminee" class="divjeu">
|
||||||
|
<!-- dans une partie -->
|
||||||
|
|
||||||
|
<p>Mot a deviner ({{ nbLettresADeviner }} lettres) : </p>
|
||||||
|
<h2 class="trous">{{ progression }}</h2>
|
||||||
|
<input class="form-control" type="text" minlength="1" maxlength="1" @input="deviner"
|
||||||
|
placeholder="Devinez la lettre ici">
|
||||||
|
<p>Vies restantes : {{ viesRestantes }}</p>
|
||||||
|
<label for="barreViePendu">Barre de vie</label>
|
||||||
|
<meter min="0" :max="viesMaximum" :value="viesRestantes"></meter>
|
||||||
|
<p>Lettres devinées : <span style="font-family: monospace">{{ lettresDejaDevine }}</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.trous{
|
||||||
|
letter-spacing:0.5em;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.divjeu {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separateur{
|
||||||
|
display: flex;
|
||||||
|
flex:1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,131 @@
|
|||||||
|
<script>
|
||||||
|
export default{
|
||||||
|
props:["viesRestantes"], //maximum 10, 10 par defaut
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
ordreDessin:[
|
||||||
|
this.potenceVertical,
|
||||||
|
this.potencePoutre,
|
||||||
|
this.potenceEquerre,
|
||||||
|
this.corde,
|
||||||
|
this.tete,
|
||||||
|
this.corps,
|
||||||
|
this.brasGauche,
|
||||||
|
this.brasDroit,
|
||||||
|
this.jambeDroite,
|
||||||
|
this.jambeGauche
|
||||||
|
],
|
||||||
|
viesEpuisees:0,
|
||||||
|
ctx:null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
this.$refs.pendu.width=200
|
||||||
|
this.$refs.pendu.height=200
|
||||||
|
this.ctx = this.$refs.pendu.getContext("2d");
|
||||||
|
this.viesEpuisees=this.ordreDessin.length-(this.viesRestantes??this.ordreDessin.length)
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
viesRestantes: function(newVal, oldVal){
|
||||||
|
const viesAEpuiser=this.ordreDessin.length-(newVal??this.ordreDessin.length)
|
||||||
|
this.viesEpuisees=0;
|
||||||
|
//nettoyer le canvas
|
||||||
|
this.ctx.clearRect(0, 0, this.$refs.width, this.$refs.height);
|
||||||
|
|
||||||
|
for(let i=0; i<viesAEpuiser; i++){
|
||||||
|
this.dessiner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
dessiner(){
|
||||||
|
if(this.viesEpuisees<this.ordreDessin.length){
|
||||||
|
this.ordreDessin[this.viesEpuisees]()
|
||||||
|
this.viesEpuisees++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
potenceBas: function(){
|
||||||
|
// Tracer la potence (trait bas)
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(50, 200);
|
||||||
|
this.ctx.lineTo(150, 200);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
potenceVertical: function(){
|
||||||
|
this.potenceBas()
|
||||||
|
// Tracer la potence (trait vertical)
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(100, 200);
|
||||||
|
this.ctx.lineTo(100, 50);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
potencePoutre: function(){
|
||||||
|
// Tracer la potence (poutre)
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(100, 50);
|
||||||
|
this.ctx.lineTo(150, 50);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
potenceEquerre: function(){
|
||||||
|
// Tracer la potence (equerre)
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(100, 70);
|
||||||
|
this.ctx.lineTo(130, 50);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
corde: function(){
|
||||||
|
// Tracer la corde
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(150, 50); // Déplacer le point de départ au sommet de la potence
|
||||||
|
this.ctx.lineTo(150, 75); // Tracer une ligne verticale jusqu'à la tête du pendu
|
||||||
|
this.ctx.stroke(); // Dessiner le trait
|
||||||
|
},
|
||||||
|
tete: function(){
|
||||||
|
// Tracer la tête
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(150, 87, 12, 0, Math.PI * 2);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
corps: function(){
|
||||||
|
// Tracer le corps
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(150, 100);
|
||||||
|
this.ctx.lineTo(150, 150);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
brasGauche: function(){
|
||||||
|
// Tracer le bras gauche
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(150, 110);
|
||||||
|
this.ctx.lineTo(130, 130);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
brasDroit: function(){
|
||||||
|
// Tracer le bras droit
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(150, 110);
|
||||||
|
this.ctx.lineTo(170, 130);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
jambeGauche: function(){
|
||||||
|
// Tracer la jambe gauche
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(150, 150);
|
||||||
|
this.ctx.lineTo(130, 180);
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
jambeDroite: function(){
|
||||||
|
// Tracer la jambe droite
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(150, 150);
|
||||||
|
this.ctx.lineTo(170, 180);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<canvas ref="pendu"></canvas>
|
||||||
|
</template>
|
@ -0,0 +1,22 @@
|
|||||||
|
export class DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
//mettre les données du json directement dans l'objet
|
||||||
|
Object.keys(parsedJSON).forEach(dataName=>this[dataName]=parsedJSON[dataName])
|
||||||
|
//mettre les alias ici
|
||||||
|
//ex : l'API change _embedded en _objectList mais que l'ancien code utilisait _embedded
|
||||||
|
//this._objectList = this._embedded
|
||||||
|
|
||||||
|
//ne pas autoriser les messages d'erreur, on va plutot lancer une exception
|
||||||
|
if(this.error){
|
||||||
|
throw this.error + " : " + this.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PagedDataObject extends DataObject{
|
||||||
|
constructor(parsedJSON, dataObject){
|
||||||
|
super(parsedJSON)
|
||||||
|
//mettre objets correspondant dans la liste (ex : new Scientifique(obj) dans Scientifiques)
|
||||||
|
this._embedded=this._embedded.map(obj=>new dataObject(obj))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
|
||||||
|
export class Difficulte extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Difficultes extends PagedDataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON, Difficulte)
|
||||||
|
}
|
||||||
|
static async getPage(pageNb=0, size=0){
|
||||||
|
let params=""
|
||||||
|
if(size>0){
|
||||||
|
params+=`&size=${size}`
|
||||||
|
}
|
||||||
|
const response = await fetch(`${REST_API}/difficultes?page=${pageNb}${params}`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* JSON de reference (Difficultes)
|
||||||
|
{
|
||||||
|
"_links" : {
|
||||||
|
"first" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/difficultes?page=0&size=2"
|
||||||
|
},
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/difficultes?page=0&size=2"
|
||||||
|
},
|
||||||
|
"next" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/difficultes?page=1&size=2"
|
||||||
|
},
|
||||||
|
"last" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/difficultes?page=1&size=2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page" : {
|
||||||
|
"size" : 2,
|
||||||
|
"totalElements" : 3,
|
||||||
|
"totalPages" : 2,
|
||||||
|
"number" : 0
|
||||||
|
},
|
||||||
|
"_embedded" : [ {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Facile"
|
||||||
|
}, {
|
||||||
|
"id" : 2,
|
||||||
|
"libelle" : "Intermédiaire"
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,43 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
|
||||||
|
export class ListeJeux extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
static async get(){
|
||||||
|
const response = await fetch(`${REST_API}/jeux`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* JSON de reference (get ListeJeux)
|
||||||
|
[ {
|
||||||
|
"id" : 1,
|
||||||
|
"nom" : "Qui-est-ce ?",
|
||||||
|
"nbrParties" : 0,
|
||||||
|
"links" : [ {
|
||||||
|
"rel" : "self",
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/jeux/1"
|
||||||
|
} ]
|
||||||
|
}, {
|
||||||
|
"id" : 2,
|
||||||
|
"nom" : "Science Quizz",
|
||||||
|
"nbrParties" : 0,
|
||||||
|
"links" : [ {
|
||||||
|
"rel" : "self",
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/jeux/2"
|
||||||
|
} ]
|
||||||
|
}, {
|
||||||
|
"id" : 3,
|
||||||
|
"nom" : "Pendu",
|
||||||
|
"nbrParties" : 0,
|
||||||
|
"links" : [ {
|
||||||
|
"rel" : "self",
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/jeux/3"
|
||||||
|
} ]
|
||||||
|
} ]
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,132 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
import { Utilisateur } from "./utilisateur"
|
||||||
|
|
||||||
|
export class Kahoot{
|
||||||
|
constructor(codeInvitation){
|
||||||
|
this.codeInvitation=codeInvitation
|
||||||
|
this.rejoindrePartie().then()
|
||||||
|
}
|
||||||
|
async obtenirSalleAttente(){
|
||||||
|
const response=await fetch(`${REST_API}/partie/kahoot/${this.codeInvitation}/status`)
|
||||||
|
return new KahootSalleAttente(await response.json())
|
||||||
|
}
|
||||||
|
async obtenirQuestion(){
|
||||||
|
const response=await fetch(`${REST_API}/partie/kahoot/${this.codeInvitation}/question`)
|
||||||
|
return new KahootQuestion(await response.json())
|
||||||
|
}
|
||||||
|
async obtenirScore(ancienScore=0){
|
||||||
|
const user = await Utilisateur.utilisateurConnecteOuCreerInvite()
|
||||||
|
const response=await fetch(`${REST_API}/partie/kahoot/${this.codeInvitation}/status`)
|
||||||
|
let json=await response.json()
|
||||||
|
json.score=json.scores.find(score=>score.joueur.id==user.id).score
|
||||||
|
json.pointsGagne=json.score-ancienScore
|
||||||
|
return new KahootScore(json)
|
||||||
|
}
|
||||||
|
async repondreQuestion(id){
|
||||||
|
const user = await Utilisateur.utilisateurConnecteOuCreerInvite()
|
||||||
|
const response = await fetch(`${REST_API}/partie/kahoot/${this.codeInvitation}/reponse`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:JSON.stringify({"idJoueur":user.id, "idReponse":id})
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
async rejoindrePartie(){
|
||||||
|
const user = await Utilisateur.utilisateurConnecteOuCreerInvite()
|
||||||
|
const response = await fetch(`${REST_API}/partie/kahoot/${this.codeInvitation}`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:JSON.stringify({"idJoueur":user.id})
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
async demarrerPartie(){
|
||||||
|
const response = await fetch(`${REST_API}/partie/kahoot/${this.codeInvitation}/demarrer`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"}
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* JSON de reference (salleAttente)
|
||||||
|
{
|
||||||
|
"joueurs":["Moi","Titouan"],
|
||||||
|
"partieDemarree":true,
|
||||||
|
"tempsLimite":${Date.now()+this.DEBUG_temps maintenant + 1 seconde}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export class KahootSalleAttente extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
this.partieDemarree=this.status!="Pending"
|
||||||
|
this.joueurs=this.scores.map(score=>score.joueur.pseudo)
|
||||||
|
this.tempsLimite=Date.now()+1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JSON de reference (PartieDetails)
|
||||||
|
in: {"idJoueur": 0, "thematiques": [0,1,2,3], "idDifficulte": 0}
|
||||||
|
|
||||||
|
out:
|
||||||
|
{"id": 0,
|
||||||
|
"codeInvitation": 0,
|
||||||
|
"joueurs": [
|
||||||
|
{"id": 0, "pseudo": 0},
|
||||||
|
],
|
||||||
|
"thematiques": [
|
||||||
|
{"id": 0, "libelle": 0},
|
||||||
|
],
|
||||||
|
"difficulte": {"id":0, "libelle": 0}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export class KahootPartie extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
async creerPartie(){
|
||||||
|
const user = await Utilisateur.utilisateurConnecteOuCreerInvite()
|
||||||
|
const response = await fetch(`${REST_API}/partie/kahoot`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
//{"idJoueur": 0, "thematiques": [0,1,2,3], "idDifficulte": 0}
|
||||||
|
body:JSON.stringify({"idJoueur":user.id, "thematiques":this.thematiques, "idDifficulte":this.idDifficulte})
|
||||||
|
})
|
||||||
|
return new this.constructor(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JSON de reference (question)
|
||||||
|
{
|
||||||
|
"question":"Qui a reçu le prix Nobel de chimie en 1911, pour avoir réussi à isoler un gramme de radium ?",
|
||||||
|
"reponses":["Marie Curie","Einstein","Sophie Germain","Ada Lovelace"],
|
||||||
|
"tempsLimite":${Date.now()+this.DEBUG_temps maintenant + 10 secondes pour repondre}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export class KahootQuestion extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
this.tempsLimite=new Date(this.tempsLimiteReponse).getTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JSON de reference (score)
|
||||||
|
{
|
||||||
|
"status": "Pending|Started|Ended",
|
||||||
|
"scores": [
|
||||||
|
{"joueur": {"id": 0, "pseudo": 0},
|
||||||
|
"score": 0},
|
||||||
|
],
|
||||||
|
"pointsGagne":100,
|
||||||
|
"score":1337,
|
||||||
|
"tempsLimite":${Date.now()+this.DEBUG_temps maintenant + 10 secondes le temps de regarder les scores}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
export class KahootScore extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
this.tempsLimite=new Date(this.tempsAffichageScore).getTime()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
|
||||||
|
export class Partie extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
async creerPartie(){
|
||||||
|
const response = await fetch(`${REST_API}/partie`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:JSON.stringify(this)
|
||||||
|
})
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
static async rejoindrePartie(codeInvitation, idJoueur){
|
||||||
|
const response = await fetch(`${REST_API}/partie/${codeInvitation}`,{
|
||||||
|
method:"PUT",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:{"idJoueur":idJoueur}
|
||||||
|
})
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* JSON de reference (creerInvite)
|
||||||
|
in : {pseudo: "...."}
|
||||||
|
out : {id: ?, pseudo: "?"}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* JSON de reference (creerPartie)
|
||||||
|
in:
|
||||||
|
{
|
||||||
|
"idJeu": 1,
|
||||||
|
"idJoueur": 1,
|
||||||
|
"thematiques": [1],
|
||||||
|
"idDifficulte": 1
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"codeInvitation": "44122",
|
||||||
|
"joueurs": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"pseudo": "moi, le meilleur joueur du monde"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"jeu": {
|
||||||
|
"id": 1,
|
||||||
|
"nom": "Qui-est-ce ?",
|
||||||
|
"nbrParties": 0
|
||||||
|
},
|
||||||
|
"thematiques": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"libelle": "Nucléaire"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"libelle": "Mathématiques"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"difficulte": {
|
||||||
|
"id": 3,
|
||||||
|
"libelle": "Difficile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,184 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
|
||||||
|
export class Scientifique extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
//exemple d'alias pour le pendu
|
||||||
|
this.nomComplet = this.nomComplet ?? this.nom + " " + this.prenom
|
||||||
|
}
|
||||||
|
static async get(id){
|
||||||
|
const response = await fetch(`${REST_API}/scientifiques/${id}`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Scientifiques extends PagedDataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON, Scientifique)
|
||||||
|
}
|
||||||
|
static async getPage(pageNb, size=0, thematiqueId=-1, difficulteId=-1){
|
||||||
|
let params=""
|
||||||
|
if(size>0){
|
||||||
|
params+=`&size=${size}`
|
||||||
|
}
|
||||||
|
if(thematiqueId>-1){
|
||||||
|
params+=`&thematiqueId=${thematiqueId}`
|
||||||
|
}
|
||||||
|
if(difficulteId>-1){
|
||||||
|
params+=`&difficulteId=${difficulteId}`
|
||||||
|
}
|
||||||
|
const response = await fetch(`${REST_API}/scientifiques?page=${pageNb}${params}`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ScientifiqueIndice extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ScientifiqueIndices extends PagedDataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON, ScientifiqueIndice)
|
||||||
|
}
|
||||||
|
static async getPage(idScientifique, size){
|
||||||
|
const response = await fetch(`${REST_API}/scientifiques/${idScientifique}/indices`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JSON de reference (Scientifique)
|
||||||
|
{
|
||||||
|
"id" : 1,
|
||||||
|
"difficulte" : {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Facile"
|
||||||
|
},
|
||||||
|
"thematique" : {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Nucléaire"
|
||||||
|
},
|
||||||
|
"pathToPhoto" : "",
|
||||||
|
"nom" : "Marie",
|
||||||
|
"prenom" : "Curie",
|
||||||
|
"descriptif" : "desc",
|
||||||
|
"dateNaissance" : "2024-03-01T00:00:00.000+00:00",
|
||||||
|
"sexe" : "F",
|
||||||
|
"ratioTrouve" : 0.5,
|
||||||
|
"_links" : {
|
||||||
|
"indices" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/1/indices"
|
||||||
|
},
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* JSON de reference (Scientifiques / Page de scientifiques)
|
||||||
|
|
||||||
|
{
|
||||||
|
"_links" : {
|
||||||
|
"first" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques?page=0&size=2"
|
||||||
|
},
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques?page=0&size=2"
|
||||||
|
},
|
||||||
|
"next" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques?page=1&size=2"
|
||||||
|
},
|
||||||
|
"last" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques?page=1&size=2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page" : {
|
||||||
|
"size" : 2,
|
||||||
|
"totalElements" : 3,
|
||||||
|
"totalPages" : 2,
|
||||||
|
"number" : 0
|
||||||
|
},
|
||||||
|
"_embedded" : [ {
|
||||||
|
"id" : 1,
|
||||||
|
"difficulte" : {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Facile"
|
||||||
|
},
|
||||||
|
"thematique" : {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Nucléaire"
|
||||||
|
},
|
||||||
|
"pathToPhoto" : "",
|
||||||
|
"nom" : "Marie",
|
||||||
|
"prenom" : "Curie",
|
||||||
|
"descriptif" : "desc",
|
||||||
|
"dateNaissance" : "2024-03-01T00:00:00.000+00:00",
|
||||||
|
"sexe" : "F",
|
||||||
|
"ratioTrouve" : 0.5,
|
||||||
|
"_links" : {
|
||||||
|
"indices" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/1/indices"
|
||||||
|
},
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"id" : 2,
|
||||||
|
"difficulte" : {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Facile"
|
||||||
|
},
|
||||||
|
"thematique" : {
|
||||||
|
"id" : 2,
|
||||||
|
"libelle" : "Mathématiques"
|
||||||
|
},
|
||||||
|
"pathToPhoto" : "",
|
||||||
|
"nom" : "Albert",
|
||||||
|
"prenom" : "Einstein",
|
||||||
|
"descriptif" : "desc",
|
||||||
|
"dateNaissance" : "2024-03-01T00:00:00.000+00:00",
|
||||||
|
"sexe" : "H",
|
||||||
|
"ratioTrouve" : 0.754,
|
||||||
|
"_links" : {
|
||||||
|
"indices" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/2/indices"
|
||||||
|
},
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* JSON de reference (ScientifiqueIndices)
|
||||||
|
|
||||||
|
{
|
||||||
|
"_links" : {
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/scientifiques/1/indices"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_embedded" : [ {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Indice pour aider",
|
||||||
|
"scientifique" : {
|
||||||
|
"id" : 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"id" : 2,
|
||||||
|
"libelle" : "S'appelle Marie",
|
||||||
|
"scientifique" : {
|
||||||
|
"id" : 1
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
@ -0,0 +1,57 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
|
||||||
|
export class Thematique extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Thematiques extends PagedDataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON, Thematique)
|
||||||
|
}
|
||||||
|
static async getPage(pageNb=0, size=0){
|
||||||
|
let params=""
|
||||||
|
if(size>0){
|
||||||
|
params+=`&size=${size}`
|
||||||
|
}
|
||||||
|
const response = await fetch(`${REST_API}/thematiques?page=${pageNb}${params}`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* JSON de reference (Thematiques)
|
||||||
|
{
|
||||||
|
"_links" : {
|
||||||
|
"first" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/thematiques?page=0&size=2"
|
||||||
|
},
|
||||||
|
"self" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/thematiques?page=0&size=2"
|
||||||
|
},
|
||||||
|
"next" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/thematiques?page=1&size=2"
|
||||||
|
},
|
||||||
|
"last" : {
|
||||||
|
"href" : "http://sae-java.alix-jdlm.fr/api/v1/thematiques?page=2&size=2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page" : {
|
||||||
|
"size" : 2,
|
||||||
|
"totalElements" : 6,
|
||||||
|
"totalPages" : 3,
|
||||||
|
"number" : 0
|
||||||
|
},
|
||||||
|
"_embedded" : [ {
|
||||||
|
"id" : 1,
|
||||||
|
"libelle" : "Nucléaire"
|
||||||
|
}, {
|
||||||
|
"id" : 2,
|
||||||
|
"libelle" : "Mathématiques"
|
||||||
|
} ]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,73 @@
|
|||||||
|
import { REST_API } from "@/assets/const"
|
||||||
|
import { DataObject, PagedDataObject } from "./dataObject"
|
||||||
|
|
||||||
|
export class Utilisateur extends DataObject{
|
||||||
|
constructor(parsedJSON){
|
||||||
|
super(parsedJSON)
|
||||||
|
}
|
||||||
|
static async get(id){
|
||||||
|
const response = await fetch(`${REST_API}/utilisateur/${id}`)
|
||||||
|
return new this(await response.json())
|
||||||
|
}
|
||||||
|
async creerCompte(){
|
||||||
|
const response = await fetch(`${REST_API}/utilisateur`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:JSON.stringify(this)
|
||||||
|
})
|
||||||
|
return new this.constructor(await response.json())
|
||||||
|
}
|
||||||
|
async creerInvite(){
|
||||||
|
const response = await fetch(`${REST_API}/invite`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:JSON.stringify(this)
|
||||||
|
})
|
||||||
|
const utilisateurConnecte=new this.constructor(await response.json())
|
||||||
|
localStorage.setItem("utilisateurConnecte",JSON.stringify(utilisateurConnecte))
|
||||||
|
return utilisateurConnecte;
|
||||||
|
}
|
||||||
|
async connecter(){
|
||||||
|
const response = await fetch(`${REST_API}/utilisateur/connexion`,{
|
||||||
|
method:"POST",
|
||||||
|
headers:{"Content-Type":"application/json"},
|
||||||
|
body:JSON.stringify(this)
|
||||||
|
})
|
||||||
|
const utilisateurConnecte=new this.constructor(await response.json())
|
||||||
|
localStorage.setItem("utilisateurConnecte",JSON.stringify(utilisateurConnecte))
|
||||||
|
return utilisateurConnecte;
|
||||||
|
}
|
||||||
|
static async deconnecter(){
|
||||||
|
localStorage.removeItem("utilisateurConnecte")
|
||||||
|
}
|
||||||
|
static async utilisateurConnecte(){
|
||||||
|
const utilisateur=JSON.parse(localStorage.getItem("utilisateurConnecte"))
|
||||||
|
if(utilisateur){
|
||||||
|
return new this(utilisateur)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
static async utilisateurConnecteOuCreerInvite(){
|
||||||
|
const utilisateur=JSON.parse(localStorage.getItem("utilisateurConnecte"))
|
||||||
|
if(utilisateur==null){
|
||||||
|
const invite=new this({"pseudo":"invitetest123123"+Date.now()})
|
||||||
|
return await invite.creerInvite()
|
||||||
|
}
|
||||||
|
return new this(utilisateur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JSON de reference pour le get, et retour des autres fonctions
|
||||||
|
{"email":"amogus@amog.us", "pseudo":"amogus", "id":"2"}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* JSON de reference (creerCompte)
|
||||||
|
{"email":"amogus@amog.us", "pseudo":"amogus", "motDePasse":"hunter2"}
|
||||||
|
*/
|
||||||
|
/* JSON de reference (creerInvite)
|
||||||
|
{"pseudo":"amogus"}
|
||||||
|
*/
|
||||||
|
/* JSON de reference (connecter)
|
||||||
|
in : {"email":"amogus@amog.us", "motDePasse":"hunter2"}
|
||||||
|
out : {"email":"amogus@amog.us", "pseudo":"amogus", "id":"2"}
|
||||||
|
*/
|
@ -0,0 +1,45 @@
|
|||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
//importer bootstrap
|
||||||
|
import * as bootstrap from 'bootstrap'
|
||||||
|
import './scss/styles.scss'
|
||||||
|
|
||||||
|
//importer les components pour le routing
|
||||||
|
import PagePrincipale from "./components/PagePrincipale.vue"
|
||||||
|
import NotFound from "./components/erreurs/NotFound.vue"
|
||||||
|
import TestParametreURL from "./components/TestParametreURL.vue"
|
||||||
|
|
||||||
|
import Login from "./components/Login.vue"
|
||||||
|
import Inscription from './components/Inscription.vue'
|
||||||
|
import Profil from './components/Profil.vue'
|
||||||
|
|
||||||
|
import KahootVue from './components/jeux/kahoot/Kahoot.vue'
|
||||||
|
import KahootPartie from './components/jeux/kahoot/KahootPartie.vue'
|
||||||
|
import CreerPartie from './components/jeux/creerPartie.vue'
|
||||||
|
import Pendu from './components/jeux/pendu/Pendu.vue'
|
||||||
|
import AdminGestionDonnees from "./components/admin/gestion/Liste.vue"
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ path: '/', component: PagePrincipale },
|
||||||
|
{ path: '/login', component: Login },
|
||||||
|
{ path: '/inscription', component: Inscription },
|
||||||
|
{ path: '/profil', component: Profil },
|
||||||
|
{ path: '/kahoot', component: KahootVue}, //TODO: changer la route pour qu'elle soit trouvée automatiquement par le serveur (ce que demande l'utilisateur)
|
||||||
|
{ path: '/kahoot/partie/:code', component: KahootPartie},
|
||||||
|
{ path: '/partie', component: CreerPartie},
|
||||||
|
{ path: '/pendu', component: Pendu },
|
||||||
|
{ path: '/exemple/:id', component: TestParametreURL },
|
||||||
|
{ path: '/admin/gestion', component: AdminGestionDonnees },
|
||||||
|
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes, // short for `routes: routes`
|
||||||
|
})
|
||||||
|
|
||||||
|
createApp(App).use(router).mount('#app')
|
@ -0,0 +1,65 @@
|
|||||||
|
// kahoot styles
|
||||||
|
|
||||||
|
@import "styles.scss";
|
||||||
|
|
||||||
|
.Kahoot-Header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
color: $light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kahoot-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kahoot-List {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kahoot-Create {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
color: $light;
|
||||||
|
}
|
||||||
|
|
||||||
|
// kahoot styles
|
||||||
|
.Kahoot-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kahoot-List {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kahoot-Create {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kahoot-List-Item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
background-color: var(--bs-secondary-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px var(--bs-border-color)
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
//template
|
||||||
|
template {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
//landing page styles
|
||||||
|
.landingPage {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
animation: dropIn 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
animation: dropIn 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingPage img {
|
||||||
|
width: 20%;
|
||||||
|
height: auto;
|
||||||
|
animation: slideInRight 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.routes-button{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
animation: dropIn 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
//footer
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
//keyframes
|
||||||
|
@keyframes dropIn {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media queries for responsiveness
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.landingPage {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: start;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent p {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingPage img {
|
||||||
|
width: 50%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landingContent button {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
@import "bootstrap/scss/bootstrap";
|
After Width: | Height: | Size: 110 KiB |
@ -0,0 +1,16 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in new issue