Compare commits

..

1 Commits

Author SHA1 Message Date
Alix JEUDI--LEMOINE 1fe4450781 Ajout du GANTT réel
1 year ago

@ -1,39 +0,0 @@
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

@ -1,26 +0,0 @@
# 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.

6
package-lock.json generated

@ -1,6 +0,0 @@
{
"name": "ScienceQuest",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

@ -1,30 +0,0 @@
# 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

@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

@ -1,29 +0,0 @@
# 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
```

@ -1,13 +0,0 @@
<!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>

@ -1,8 +0,0 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

File diff suppressed because it is too large Load Diff

@ -1,21 +0,0 @@
{
"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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

@ -1,20 +0,0 @@
<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>

@ -1,5 +0,0 @@
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

@ -1,4 +0,0 @@
#app {
margin: 0 auto;
font-weight: normal;
}

@ -1,54 +0,0 @@
<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>

@ -1,61 +0,0 @@
<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>

@ -1,38 +0,0 @@
<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>

@ -1,36 +0,0 @@
<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>

@ -1,3 +0,0 @@
<template>
<div>{{ $route.params.id }}</div>
</template>

@ -1,102 +0,0 @@
<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>

@ -1,49 +0,0 @@
<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>

@ -1,103 +0,0 @@
<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>

@ -1,79 +0,0 @@
<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>

@ -1,84 +0,0 @@
<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>

@ -1,143 +0,0 @@
<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>

@ -1,15 +0,0 @@
<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>

@ -1,198 +0,0 @@
<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>

@ -1,226 +0,0 @@
<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>

@ -1,131 +0,0 @@
<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>

@ -1,22 +0,0 @@
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))
}
}

@ -1,57 +0,0 @@
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"
} ]
}
*/

@ -1,43 +0,0 @@
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"
} ]
} ]
*/

@ -1,132 +0,0 @@
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()
}
}

@ -1,71 +0,0 @@
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"
}
}
*/

@ -1,184 +0,0 @@
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
}
} ]
}
*/

@ -1,57 +0,0 @@
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"
} ]
}
*/

@ -1,73 +0,0 @@
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"}
*/

@ -1,45 +0,0 @@
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')

@ -1,65 +0,0 @@
// 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)
}

@ -1,123 +0,0 @@
//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;
}
}

@ -1 +0,0 @@
@import "bootstrap/scss/bootstrap";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

@ -1,16 +0,0 @@
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…
Cancel
Save