Prêt a déployer

This commit is contained in:
Benjamin 2023-02-26 16:29:05 +01:00 committed by Kilton937342
parent 9393c88f8e
commit 71038c169c
35 changed files with 1369 additions and 1073 deletions

View File

@ -5,7 +5,7 @@ from typing import List
from fastapi import Depends, HTTPException, status, Query
from pydantic import BaseModel
from sqlalchemy import func
from sqlmodel import Session, delete, select, col
from sqlmodel import Session, delete, select, col, update
from database.auth.crud import get_user_from_token
from database.auth.models import User
@ -276,8 +276,8 @@ def refuse_waiter(member: Member, db: Session):
def leave_room(member: Member, db: Session):
#db.execute(delete(Challenger).where(col(Challenger.member_id) == member.id))
#db.execute(delete(TmpCorrection).where(col(TmpCorrection.member_id) == member.id))
# db.execute(delete(Challenger).where(col(Challenger.member_id) == member.id))
# db.execute(delete(TmpCorrection).where(col(TmpCorrection.member_id) == member.id))
db.delete(member)
db.commit()
return None
@ -333,6 +333,12 @@ def getChallenges(c: Challenger, db: Session):
return challenges
def getMemberChallenges(m: Member, p: Parcours, db: Session):
challenges = db.exec(select(Challenge).where(Challenge.challenger_mid == m.id,
Challenge.challenger_pid == p.id)).all()
return challenges
def getTops(p: Parcours, db: Session):
tops = db.exec(select(Challenge).where(Challenge.parcours_id == p.id_code).order_by(
col(Challenge.mistakes), col(Challenge.time)).limit(3)).all()
@ -379,6 +385,13 @@ def getMemberAvgRank(m: Member, p: Parcours, db: Session):
return getAvgRank(challenger, p, db)
def getMemberValidated(m: Member, p: Parcours, db: Session):
challenger = db.exec(select(Challenger).where(Challenger.member_id == m.id)).first()
if challenger is None or challenger.validated is None:
return None
return challenger.validated
def serialize_parcours(parcours: Parcours, member: Member, db: Session):
tops = getTops(parcours, db)
avgTop = getAvgTops(parcours, db)
@ -401,55 +414,13 @@ def serialize_parcours(parcours: Parcours, member: Member, db: Session):
challengers = db.exec(statement).all()
challs = {c.member.id_code: {
"challenger": {"id_code": c.member.id_code, "name": getUsername(c.member)},
"challenger": {"id_code": c.member.id_code, "name": getUsername(c.member), "validated": c.validated},
# 'validated': chall.mistakes <= parcours.max_mistakes
"challenges": [Challenges(**{**chall.dict(), "canCorrige": chall.data != []}) for chall in getChallenges(c, db)]
} for c in challengers}
return {**parcours.dict(), "pb": pb, "tops": tops, "challenges": challs, "rank": noteRank, "memberRank": avgRank,
"validated": challenger.validated if challenger != None else False, "ranking": avgTop}
tops = []
challs = {}
challenges = sorted(parcours.challenges, key=lambda x: (
x.note['value'], x.time), reverse=True)
memberRank = None
rank = None
pb = None
validated = False
total = 0
for i, chall in enumerate(challenges):
total += chall.note['value']
id = chall.challenger.id_code
name = chall.challenger.user.username if chall.challenger.user_id != None else chall.challenger.anonymous.username
if i <= 2:
tops.append({"challenger": {"id_code": id, "name": name},
"note": chall.note, "time": chall.time})
if id == member.id_code:
if challs.get(id) is None:
rank = i + 1
memberRank = len(challs) + 1
pb = {"note": chall.note, "time": chall.time}
if validated is False and chall.validated:
validated = True
if member.is_admin or chall.challenger.id_code == member.id_code:
t = challs.get(id, {"total": 0})['total']
challs[id] = {"challenger": {"id_code": id, "name": name
}, "challenges": [*challs.get(id, {'challenges': []})['challenges'],
Challenges(
**{**chall.dict(), "canCorrige": chall.data != []})],
"total": t + chall.note['value']}
topMembers = [{**c['challenger'], "avg": c['total'] /
len(c['challenges'])} for id, c in challs.items()]
topMembers.sort(key=lambda x: x['avg'], reverse=True)
return {**parcours.dict(), "tops": tops, "challenges": challs, "rank": rank, "memberRank": memberRank, "pb": pb,
"validated": validated,
'avg': None if len(parcours.challenges) == 0 else round(total / len(parcours.challenges), 2),
"ranking": topMembers}
def change_anonymous_clientId(anonymous: Anonymous, db: Session):
@ -519,28 +490,23 @@ def deleteParcoursRelated(parcours: Parcours, db: Session):
db.commit()
def change_challengers_validation(p: Parcours, validation: int, db: Session):
challengers = db.exec(select(Challenger).where(
Challenger.parcours_id == p.id)).all()
challs = []
for c in challengers:
validated = c.best <= validation
if validated != c.validated:
c.validated = validated
challs.append(c)
db.bulk_save_objects(challs)
def change_challengers_validation(p: Parcours, db: Session):
stmt = update(Challenger).values(
validated=select(Challenge.id).where(Challenge.challenger_mid == Challenger.member_id,
Challenge.challenger_pid == Challenger.parcours_id,
Challenge.validated == 1).exists()).where(
Challenger.parcours_id == p.id)
db.execute(stmt)
db.commit()
return
def change_challenges_validation(p: Parcours, validation: int, db: Session):
def change_challenges_validation(p: Parcours, db: Session):
challenges = db.exec(select(Challenge).where(
Challenge.parcours_id == p.id_code)).all()
print('CHALLS', challenges)
challs = []
for c in challenges:
validated = c.mistakes <= validation
print('CHAL', validated, c.validated, c)
validated = c.time <= p.time * 60 and c.mistakes <= p.max_mistakes
if validated != c.validated:
c.validated = validated
challs.append(c)
@ -549,9 +515,10 @@ def change_challenges_validation(p: Parcours, validation: int, db: Session):
db.commit()
def changeValidation(p: Parcours, validation: int, db: Session):
change_challengers_validation(p, validation, db)
change_challenges_validation(p, validation, db)
def changeValidation(p: Parcours, db: Session):
change_challenges_validation(p, db)
change_challengers_validation(p, db)
def compareExercices(old: list[Exercices], new: list[ExercicesCreate]):
@ -571,9 +538,7 @@ def update_parcours_db(parcours: ParcoursCreate, parcours_obj: Parcours, db: Ses
update_challenges = True
parcours_obj.exercices = exercices
if parcours_obj.max_mistakes != parcours.max_mistakes:
changeValidation(parcours_obj, parcours.max_mistakes, db)
update_validated = parcours_obj.max_mistakes != parcours.max_mistakes or parcours_obj.time != parcours.time
parcours_obj.name = parcours.name
parcours_obj.time = parcours.time
parcours_obj.max_mistakes = parcours.max_mistakes
@ -582,6 +547,8 @@ def update_parcours_db(parcours: ParcoursCreate, parcours_obj: Parcours, db: Ses
db.commit()
db.refresh(parcours_obj)
if update_validated:
changeValidation(parcours_obj, db)
return parcours_obj, update_challenges
@ -736,7 +703,9 @@ def checkValidated(challenger: Challenger, db: Session, challenge: Challenge | N
def create_challenge(data: List[CorrigedData], challenger: Member, parcours: Parcours, time: int, mistakes: int,
isCorriged: bool, db: Session):
challenger_obj: Challenger = getChallenger(parcours, challenger, db)
validated = mistakes <= parcours.max_mistakes
print('VALIDATING', time <= parcours.time * 60 and mistakes <= parcours.max_mistakes, time, parcours.time)
validated = time <= parcours.time * 60 and mistakes <= parcours.max_mistakes
challenge = Challenge(data=data, challenger_pid=challenger_obj.parcours_id, challenger_mid=challenger_obj.member_id,
parcours=parcours, time=time, mistakes=mistakes, isCorriged=isCorriged,
id_code=generate_unique_code(Challenge, s=db), validated=validated)
@ -775,7 +744,7 @@ def change_challenge(challenge: Challenge, corriged: CorrigedChallenge, db: Sess
challenger.best = corriged['mistakes']
challenger.best_time = challenge.time
validated = corriged['mistakes'] <= parcours.max_mistakes
validated = challenge.time <= parcours.time * 60 and corriged['mistakes'] <= parcours.max_mistakes
challenge.validated = validated
if challenger.validated == False and validated:

View File

@ -145,6 +145,7 @@ class ParcoursReadUpdate(SQLModel):
class ChallengerInfo(BaseModel):
name: str
id_code: str
validated: bool = False
class ChallengerAverage(ChallengerInfo):

Binary file not shown.

View File

@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, Query, UploadFile, HTTPException, status
from fastapi.responses import FileResponse, StreamingResponse
from fastapi_pagination.ext.sqlalchemy_future import paginate as p
from pydantic import BaseModel
from sqlmodel import Session, select
from sqlmodel import Session, select, col
from database.auth.models import User
from database.db import get_session
@ -85,6 +85,8 @@ def get_user_exercices(user: User = Depends(get_current_user),
sub = select(ExercicesTagLink).where(ExercicesTagLink.exercice_id == Exercice.id).where(
ExercicesTagLink.tag_id == t).exists()
statement = statement.where(sub)
statement = statement.order_by(col(Exercice.id).desc())
page = p(db, statement)
exercices = page.items
page.items = [
@ -116,7 +118,7 @@ def get_public_exercices(user: User | None = Depends(get_current_user_optional),
sub = select(ExercicesTagLink).where(ExercicesTagLink.exercice_id == Exercice.id).where(
ExercicesTagLink.tag_id == t).exists()
statement = statement.where(sub)
statement = statement.order_by(col(Exercice.id).desc())
page = p(db, statement)
print('¨PAGE', page)
exercices = page.items

View File

@ -39,7 +39,6 @@ class RoomManager:
if group in self.active_connections:
for connection in list(set(self.active_connections[group])):
print(connection, connection.ws.state, connection.ws.client_state, connection.ws.application_state)
if connection not in exclude and all(f(connection) for f in conditions):
await self._send(connection, message, group)

View File

@ -8,12 +8,12 @@ from sqlmodel import Session, select
from database.auth.models import User
from database.db import get_session
from database.exercices.models import Exercice
from database.room.crud import delete_room_db
from database.room.crud import delete_room_db, getUsername, getMemberValidated
from database.room.crud import serialize_parcours_short, change_correction, corrige_challenge, \
create_parcours_db, delete_parcours_db, create_room_db, get_member_dep, check_room, serialize_room, \
update_parcours_db, get_parcours, get_room, check_admin, get_exercices, get_challenge, get_correction, \
create_tmp_correction, create_challenge, change_challenge, serialize_parcours, getTops, getAvgRank, getRank, \
getAvgTops, ChallengerFromChallenge, getMemberAvgRank, getMemberRank
getAvgTops, ChallengerFromChallenge, getMemberAvgRank, getMemberRank, getMemberChallenges
from database.room.models import Challenge, ChallengeRead, Challenges, ParcoursReadUpdate, ChallengeInfo, Member, \
Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, Room, RoomConnectionInfos, \
RoomCreate, RoomInfo, TmpCorrection, CorrigedData, CorrectionData
@ -45,7 +45,7 @@ def get_room_route(room: Room = Depends(get_room), member: Member = Depends(get_
return serialize_room(room, member, db)
@router.delete('/room/{room_id}', dependencies=[ Depends(check_admin) ])
@router.delete('/room/{room_id}', dependencies=[Depends(check_admin)])
async def delete_room(room: Room = Depends(get_room), m: RoomManager = Depends(get_manager),
db: Session = Depends(get_session)):
delete_room_db(room, db)
@ -73,6 +73,8 @@ async def get_parcours_route(*, parcours: Parcours = Depends(get_parcours), memb
return serialize_parcours(parcours, member, db)
@router.put('/room/{room_id}/parcours/{parcours_id}', response_model=ParcoursRead)
async def update_parcours(*, room_id: str, parcours: ParcoursCreate, member: Member = Depends(check_admin),
parcours_old: Parcours = Depends(get_parcours), m: RoomManager = Depends(get_manager),
@ -88,9 +90,16 @@ async def update_parcours(*, room_id: str, parcours: ParcoursCreate, member: Mem
await m.broadcast({"type": "edit_parcours", "data": {
"parcours": ParcoursReadUpdate(**parcours_obj.dict(), update_challenges=update_challenges).dict()}},
parcours_old.id_code)
print('BROADCASTING')
await m.broadcast(
lambda m: {"type": "update_challenges", "data": {"challenger": {"id_code": m.id_code, "name": getUsername(m), "validated": getMemberValidated(m, parcours_obj, db)},
"challenges": [Challenges(
**{**chall.dict(), "canCorrige": chall.data != []}).dict() for
chall in
getMemberChallenges(m, parcours_obj, db)]}},
parcours_old.id_code, conditions=[lambda m: m.member.id_code != member.id_code])
return serialize_parcours(parcours_obj, member, db)
return {**parcours_obj.dict()}
@router.delete('/room/{room_id}/parcours/{parcours_id}', dependencies=[Depends(check_admin)])
@ -168,7 +177,7 @@ async def send_challenge(*, challenge: List[CorrectionData], correction: TmpCorr
}}, parcours.id_code)
print('CHALLENGE', chall)
db.delete(correction)
returnValue = {**chall.dict(), 'validated': chall.mistakes <= correction.parcours.max_mistakes}
returnValue = {**chall.dict()}
db.commit()
return returnValue
# return {**chall.dict(), 'validated': chall.mistakes <= correction.parcours.max_mistakes}
@ -243,7 +252,5 @@ async def corrige(*, correction: List[CorrigedData] = Body(), challenge: Challen
@router.websocket('/ws/room/{room_id}')
async def room_ws(ws: WebSocket, room: Room | None = Depends(check_room), db: Session = Depends(get_session),
m: RoomManager = Depends(get_manager)):
consumer = RoomConsumer(ws=ws, room=room, manager=m, db=db)
await consumer.run()

View File

@ -1,12 +1,13 @@
/* Write your global styles here, in SCSS syntax. Variables and mixins from the src/variables.scss file are available here without importing */
* {
box-sizing: border-box;
margin: 0;
box-sizing: border-box;
margin: 0;
}
.spinner {
width: 30px;
height:30px;
height: 30px;
border: 3px solid $contrast;
border-bottom-color: transparent;
border-radius: 50%;
@ -14,14 +15,15 @@
display: inline-block;
box-sizing: border-box;
}
.italic {
font-style: italic;
font-style: italic;
}
.underline {
text-decoration: underline;
text-decoration: underline;
}
@keyframes rotation {
@ -34,95 +36,109 @@
}
.container {
height: calc(100vh - 100px); // 100% - nav
height: calc(100vh - 100px); // 100% - nav
}
*{
scrollbar-width: auto!important;
* {
scrollbar-width: auto !important;
scrollbar-color: $contrast transparent;
}
.btn {
border: none;
border-radius: 5px;
height: 38px;
font-weight: 700;
transition: 0.3s;
margin-bottom: 10px;
margin-right: 7px;
padding: 0 50px;
width: max-content;
cursor: pointer;
&:disabled{
cursor: not-allowed
}
border: none;
border-radius: 5px;
height: 38px;
font-weight: 700;
transition: 0.3s;
margin-bottom: 10px;
margin-right: 7px;
padding: 0 50px;
width: max-content;
cursor: pointer;
&:disabled {
cursor: not-allowed
}
}
.primary-btn {
@extend .btn;
background-color: #fcbf49;
&:hover {
background-color: #ac7b19;
}
@extend .btn;
background-color: #fcbf49;
&:hover {
background-color: #ac7b19;
}
}
.danger-btn {
@extend .btn;
background-color: #fc5e49;
&:hover {
background-color: #ac1919;
}
@extend .btn;
background-color: #fc5e49;
&:hover {
background-color: #ac1919;
}
}
.border-primary-btn {
@extend .btn;
background-color: transparent;
border: 1px solid #fcbf49;
color: #fcbf49;
&:hover {
background-color: #fcbf49;
color: black;
}
@extend .btn;
background-color: transparent;
border: 1px solid #fcbf49;
color: #fcbf49;
&:hover {
background-color: #fcbf49;
color: black;
}
}
.input {
background-color: inherit;
color: inherit;
padding: 5px 10px;
width: 100%;
font-size: 16px;
font-weight: 450;
margin: 10px 0;
float: left;
border: none;
border-bottom: 1px solid #181553;
transition: 0.3s;
border-radius: 0;
margin: 0;
&:focus {
outline: none;
border-bottom-color: $contrast;
}
background-color: inherit;
color: inherit;
padding: 5px 10px;
width: 100%;
font-size: 16px;
font-weight: 450;
margin: 10px 0;
float: left;
border: none;
border-bottom: 1px solid #181553;
transition: 0.3s;
border-radius: 0;
margin: 0;
&:focus {
outline: none;
border-bottom-color: $contrast;
}
}
.flex-row-center {
display: flex;
justify-content: center;
display: flex;
justify-content: center;
}
@for $f from 0 through 100 {
.wp-#{$f} {
width: 1% * $f;
}
.wp-#{$f} {
width: 1% * $f;
}
}
.sv-dropdown{
z-index: 10!important;
.sv-dropdown {
z-index: 10 !important;
}
.strong{
font-weight: 900;
.strong {
font-weight: 900;
}
.contrast {
color: $contrast;
}
.loading {
cursor: progress;
}

View File

@ -5,7 +5,7 @@
import FaHome from "svelte-icons/fa/FaHome.svelte";
import { afterNavigate } from "$app/navigation";
import FaUser from "svelte-icons/fa/FaUser.svelte";
import FaSignOutAlt from "svelte-icons/fa/FaSignOutAlt.svelte";
import IoIosLogOut from 'svelte-icons/io/IoIosLogOut.svelte'
const {
isAuth,
@ -21,40 +21,41 @@
<nav data-sveltekit-preload-data="hover" class:open>
<div class="navigate">
<NavLink href="/" exact no_hover class="home">
<div class="icon">
<FaHome />
</div>
</NavLink>
<NavLink href="/" exact no_hover class="home">
<div class="icon">
<FaHome />
</div>
</NavLink>
<NavLink href="/exercices">Exercices</NavLink>
<NavLink href="/room">Salles</NavLink>
</div>
<div class="auth">
{#if $isAuth && $username != null}
<div class="right">
<div class="auth">
{#if $isAuth && $username != null}
<NavLink href="/dashboard">
<div class="dashboard">
<div class="icon">
<FaUser />
<NavLink href="/dashboard">
<div class="dashboard">
<div class="icon">
<FaUser />
</div>
{$username}
</div>
{$username}
</div>
</NavLink>
<div class="icon signout" title="Se déconnecter" on:click={()=>{
</NavLink>
<div class="icon signout" title="Se déconnecter" on:click={()=>{
logout()
}}>
<FaSignOutAlt />
</div>
<IoIosLogOut />
</div>
{:else}
<NavLink href="/signup" exact>S'inscrire</NavLink>
<NavLink href="/signin" exact>Se connecter</NavLink>
{/if}
{:else}
<NavLink href="/signup" exact>S'inscrire</NavLink>
<NavLink href="/signin" exact>Se connecter</NavLink>
{/if}
</div>
<div class="burger" on:click={()=>{open=!open}}><span> </span></div>
</div>
</nav>
<style lang="scss">
@import "../mixins";
@ -109,9 +110,20 @@
}
}
.auth {
transition: .3s;
display: flex;
gap: 7px;
@include down(666) {
//display: none;
opacity: 0;
}
}
.burger {
background: 0 0;
@include up(750px) {
@include up(666) {
display: none;
}
border: none;
@ -166,15 +178,11 @@
.open {
@include down(750px) {
@include down(666px) {
.navigate {
*:first-child {
display: none
}
max-height: 1000000px;
// Remove home icon
transition: .2s;
background: rgba($background-dark, 0.8);
height: 100%;
position: fixed;
@ -186,18 +194,31 @@
flex-direction: column;
justify-content: center;
align-items: center;
gap: 42px;
z-index: 100;
//gap: 42px;
z-index: 600;
animation: open .1s ease-in-out forwards;
}
:global(.home){
display: none;
}
.right {
justify-content: end;
width: 100%;
z-index: 601;
display: flex;
}
.auth {
justify-content: end;
width: 100%;
z-index: 101;
display: flex;
opacity: 1;
}
& .burger {
z-index: 1000;
& span::before {
bottom: 0;
@ -222,4 +243,14 @@
padding-right: 20px;
}
@keyframes open {
0% {
gap: 10px;
opacity: .3;
}
100% {
gap: 42px;
opacity: 1;
}
}
</style>

View File

@ -1,46 +1,51 @@
<script lang="ts">
import {field, form} from "svelte-forms";
import {max, min, required, email} from "svelte-forms/validators";
import LabeledInput from "../forms/LabeledInput.svelte";
import type {User} from "../../types/auth.type";
import {onMount} from "svelte";
import {errorMsg} from "../../utils/forms.js";
import { field, form } from "svelte-forms";
import { max, min, required, email } from "svelte-forms/validators";
import LabeledInput from "../forms/LabeledInput.svelte";
import type { User } from "../../types/auth.type";
import { onMount } from "svelte";
import { errorMsg } from "../../utils/forms.js";
export let user: User
export let myForm;
const username = field('username', user.username, [required(), max(20), min(2)], {
checkOnInit: true
});
const name = field('name', user.name || "", [max(50)], {
checkOnInit: true
});
const firstname = field('firstname', user.firstname || "", [max(50),], {
checkOnInit: true
});
const emailField = field('email', user.email || "", [ /*email()*/], {
checkOnInit: true
});
onMount(() => {
myForm = form(username, name, firstname, emailField);
})
export let user: User;
export let myForm;
const username = field("username", user.username, [required(), max(20), min(2)], {
checkOnInit: true
});
const name = field("name", user.name || "", [max(50)], {
checkOnInit: true
});
const firstname = field("firstname", user.firstname || "", [max(50)], {
checkOnInit: true
});
const emailField = field("email", user.email || "", [ /*email()*/], {
checkOnInit: true
});
onMount(() => {
myForm = form(username, name, firstname, emailField);
});
</script>
{#if !!$myForm}
<div class="">
<LabeledInput bind:value={$username.value} label="Nom d'utilisateur" type="text" placeholder="Nom d'utilisateur..."
errors={errorMsg($myForm, 'username')}/>
<LabeledInput bind:value={$emailField.value} label="Email" type="email" placeholder="Email..."
errors={errorMsg($myForm, 'email')}/>
<LabeledInput bind:value={$name.value} label="Nom" type="text" placeholder="Nom..."
errors={errorMsg($myForm, 'name')}/>
<LabeledInput bind:value={$firstname.value} label="Prénom" type="text" placeholder="Prénom..."
errors={errorMsg($myForm, 'firstname')}/>
</div>
<div class="">
<LabeledInput bind:value={$username.value} label="Nom d'utilisateur" type="text" placeholder="Nom d'utilisateur..."
errors={errorMsg($myForm, 'username')} />
<LabeledInput bind:value={$emailField.value} label="Email" type="email" placeholder="Email..."
errors={errorMsg($myForm, 'email')} />
<LabeledInput bind:value={$name.value} label="Nom" type="text" placeholder="Nom..."
errors={errorMsg($myForm, 'name')} />
<LabeledInput bind:value={$firstname.value} label="Prénom" type="text" placeholder="Prénom..."
errors={errorMsg($myForm, 'firstname')} />
</div>
{/if}
<style lang="scss">
@import "../../mixins.scss";
div {
display: grid;
grid-template-columns: 1fr 1fr;
@include down(800px){
grid-template-columns: 1fr;
}
}
</style>

View File

@ -1,31 +1,35 @@
<script lang="ts">
import {field, form} from "svelte-forms";
import {matchField, min, pattern, required} from "svelte-forms/validators";
import LabeledInput from "../forms/LabeledInput.svelte";
import {onMount} from "svelte";
import {errorMsg} from "../../utils/forms";
import { field, form } from "svelte-forms";
import { matchField, min, pattern, required } from "svelte-forms/validators";
import LabeledInput from "../forms/LabeledInput.svelte";
import { onMount } from "svelte";
import { errorMsg } from "../../utils/forms";
const password = field('password', '', [required(), min(8), pattern(/[0-9]/), pattern(/[A-Z]/)], {checkOnInit: true});
const confirm = field('password_confirm', '', [required(), matchField(password)],{checkOnInit: true});
export let myForm;
onMount(() => {
myForm = form(password, confirm)
})
const password = field("password", "", [required(), min(8), pattern(/[0-9]/), pattern(/[A-Z]/)], { checkOnInit: true });
const confirm = field("password_confirm", "", [required(), matchField(password)], { checkOnInit: true });
export let myForm;
onMount(() => {
myForm = form(password, confirm);
});
</script>
{#if !!$myForm}
<div>
<LabeledInput bind:value={$password.value} type="password" placeholder="Mot de passe..."
errors={errorMsg($myForm, 'password')}/>
<LabeledInput bind:value={$confirm.value} type="password" placeholder="Confirmer..."
errors={errorMsg($myForm, 'password_confirm')}/>
</div>
<div>
<LabeledInput bind:value={$password.value} type="password" placeholder="Mot de passe..."
errors={errorMsg($myForm, 'password')} />
<LabeledInput bind:value={$confirm.value} type="password" placeholder="Confirmer..."
errors={errorMsg($myForm, 'password_confirm')} />
</div>
{/if}
<style lang="scss">
@import "../../mixins.scss";
div {
display: grid;
grid-template-columns: 1fr 1fr;
@include down(800){
grid-template-columns: 1fr;}
}
</style>

View File

@ -1,34 +1,41 @@
<script lang="ts">
export let icon = null
export let title;
export let validate = "Valider !"
export let onValidate = null
export let canValid = false
export let icon = null;
export let title;
export let validate = "Valider !";
export let onValidate = null;
export let canValid = false;
</script>
<div>
<h2>
<div class="icon">
<svelte:component this={icon}/></div>
{title}</h2>
<div class="content">
<slot/>
<h2>
<div class="icon">
<svelte:component this={icon} />
</div>
{title}</h2>
<div class="content">
<slot />
</div>
{#if !!onValidate}
<div class="btn-container">
<button on:click={onValidate} class="primary-btn" disabled={!canValid}>{validate}</button>
</div>
{#if !!onValidate}
<div class="btn-container">
<button on:click={onValidate} class="primary-btn" disabled={!canValid}>{validate}</button>
</div>
{/if}
{/if}
</div>
<style lang="scss">
@import "../../mixins.scss";
h2 {
display: flex;
align-items: center;
@include down(800){
justify-content: center;
}
}
.icon{
.icon {
margin-right: 10px;
width: 25px;
height: 25px;
@ -49,5 +56,8 @@
display: flex;
justify-content: flex-end;
margin-top: 10px;
@include down(800){
justify-content: center;
}
}
</style>

View File

@ -1,71 +1,71 @@
<script lang="ts">
import type { Exercice } from '../../types/exo.type';
import { getContext } from 'svelte';
import ModalCard from './ModalCard.svelte';
import { goto } from '$app/navigation';
import { cloneExo } from '../../requests/exo.request';
import TagContainer from './TagContainer.svelte';
import PrivacyIndicator from './PrivacyIndicator.svelte';
import MdContentCopy from 'svelte-icons/md/MdContentCopy.svelte';
import type { Writable } from 'svelte/store';
import type { Exercice } from "../../types/exo.type";
import { getContext } from "svelte";
import ModalCard from "./ModalCard.svelte";
import { goto } from "$app/navigation";
import { cloneExo } from "../../requests/exo.request";
import TagContainer from "./TagContainer.svelte";
import PrivacyIndicator from "./PrivacyIndicator.svelte";
import MdContentCopy from "svelte-icons/md/MdContentCopy.svelte";
import type { Writable } from "svelte/store";
export let exo: Exercice;
export let exo: Exercice;
const { show } = getContext<{ show: Function }>('modal');
const { navigate } = getContext<{ navigate: Function }>('navigation');
const { isAuth } = getContext<{ isAuth: Writable<boolean> }>('auth');
const exerciceStore = getContext('exos');
const tagsStore = getContext('tags');
const { show } = getContext<{ show: Function }>("modal");
const { navigate } = getContext<{ navigate: Function }>("navigation");
const { isAuth } = getContext<{ isAuth: Writable<boolean> }>("auth");
const exerciceStore = getContext("exos");
const tagsStore = getContext("tags");
let opened = false;
const handleClick = () => {
opened = true;
navigate(`/exercices/${exo.id_code}`);
show(
ModalCard,
{
exo,
exos: exerciceStore,
tags: tagsStore
},
() => {
navigate(-1);
opened = false;
}
);
};
let opened = false;
const handleClick = () => {
opened = true;
navigate(`/exercices/${exo.id_code}`);
show(
ModalCard,
{
exo,
exos: exerciceStore,
tags: tagsStore
},
() => {
navigate(-1);
opened = false;
}
);
};
</script>
<div class="card" on:click={handleClick} on:dblclick={() => {}} on:keypress={() => {}}>
<h1>{exo.name}</h1>
<div class="examples">
{#if exo.examples != null}
<h2>Exemples</h2>
{#if !!exo.consigne}<p data-testid="consigne">{exo.consigne}</p>{/if}
{#each exo.examples.data.slice(0, 3) as ex}
<p>{ex.calcul}</p>
{/each}
{:else}
<p>Aucun exemple disponible</p>
{/if}
</div>
{#if !!$isAuth && exo.is_author && exo.original == null }
<div class="status">
<PrivacyIndicator color={exo.private == true ? 'red' : 'green'}>
{exo.private == true ? 'Privé' : 'Public'}</PrivacyIndicator
>
</div>
{:else if !exo.is_author}
<div class="status">
<PrivacyIndicator color={'blue'}>
Par <strong>{exo.author.username}</strong>
</PrivacyIndicator>
{#if !!$isAuth}
<div
data-testid="copy"
class="icon"
on:keydown={() => {}}
on:click|stopPropagation={() => {
<h1>{exo.name}</h1>
<div class="examples">
{#if exo.examples != null}
<h2>Exemples</h2>
{#if !!exo.consigne}<p data-testid="consigne">{exo.consigne}</p>{/if}
{#each exo.examples.data.slice(0, 3) as ex}
<p>{ex.calcul}</p>
{/each}
{:else}
<p>Aucun exemple disponible</p>
{/if}
</div>
{#if !!$isAuth && exo.is_author && exo.original == null }
<div class="status">
<PrivacyIndicator color={exo.private == true ? 'red' : 'green'}>
{exo.private == true ? 'Privé' : 'Public'}</PrivacyIndicator
>
</div>
{:else if !exo.is_author}
<div class="status">
<PrivacyIndicator color={'blue'}>
Par <strong>{exo.author.username}</strong>
</PrivacyIndicator>
{#if !!$isAuth}
<div
data-testid="copy"
class="icon"
on:keydown={() => {}}
on:click|stopPropagation={() => {
cloneExo(exo.id_code).then((r) => {
goto('/exercices/' + r.id_code);
show(ModalCard, { exo: r }, () => {
@ -73,121 +73,133 @@
});
});
}}
>
<MdContentCopy />
</div>
{/if}
</div>
{:else if exo.is_author && exo.original != null}
<div class="status">
<PrivacyIndicator color="blue">Par <strong>{exo.original?.author}</strong></PrivacyIndicator>
</div>
{/if}
<div class="card-hover" />
{#if !!$isAuth}
<TagContainer bind:exo />
{/if}
<!-- TagContainer Must be directly after card-hover for the hover effect -->
>
<MdContentCopy />
</div>
{/if}
</div>
{:else if exo.is_author && exo.original != null}
<div class="status">
<PrivacyIndicator color="blue">Par <strong>{exo.original?.author}</strong></PrivacyIndicator>
</div>
{/if}
<div class="card-hover" />
{#if !!$isAuth}
<TagContainer bind:exo />
{/if}
<!-- TagContainer Must be directly after card-hover for the hover effect -->
</div>
<style lang="scss">
@import '../../variables';
* {
transition: 0.45s;
}
.icon {
width: 18px;
height: 18px;
transition: 0.3s;
cursor: pointer;
opacity: 0.7;
transform: scale(0.9);
&:hover {
transform: scale(1);
opacity: 1;
}
}
@import '../../variables';
.status {
position: absolute;
top: 0;
right: 0;
margin: 10px;
z-index: 3;
display: flex;
align-items: center;
gap: 10px;
}
* {
transition: 0.45s;
}
.examples {
color: gray;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 20px;
p {
margin: 10px;
margin-left: 18px;
font-size: 0.95em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
h2 {
font-size: 0.95em;
margin: 10px;
margin-left: 0;
}
}
.icon {
width: 18px;
height: 18px;
transition: 0.3s;
cursor: pointer;
opacity: 0.7;
transform: scale(0.9);
.card-hover {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 1;
border: 1px solid $border;
+ :global(div) {
transition: 0.45s;
}
&:hover {
border: 1px solid $primary;
+ :global(div) {
border: 1px solid $primary;
border-top: none;
}
}
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
}
&:hover {
transform: scale(1);
opacity: 1;
}
}
h1 {
font-size: 1.3em;
margin: 0;
position: relative;
z-index: 2;
max-width: 88%;
.status {
position: absolute;
top: 0;
right: 0;
margin: 10px;
z-index: 3;
display: flex;
align-items: center;
gap: 10px;
}
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-wrap: break-word;
&:hover {
color: $primary;
}
}
.examples {
color: gray;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 20px;
.card {
border: 1px solid black;
padding: 20px;
cursor: pointer;
position: relative;
background-color: $background;
min-height: 250px;
max-height: 300px;
&:hover {
transform: translateX(10px) translateY(-10px);
}
}
p {
margin: 10px;
margin-left: 18px;
font-size: 0.95em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
h2 {
font-size: 0.95em;
margin: 10px;
margin-left: 0;
}
}
.card-hover {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 1;
border: 1px solid $border;
+ :global(div) {
transition: 0.45s;
}
&:hover {
border: 1px solid $primary;
+ :global(div) {
border: 1px solid $primary;
border-top: none;
}
}
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
}
h1 {
font-size: 1.3em;
margin: 0;
position: relative;
z-index: 2;
max-width: 88%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-wrap: break-word;
&:hover {
color: $primary;
}
}
.card {
border: 1px solid black;
padding: 20px;
cursor: pointer;
position: relative;
background-color: $background;
min-height: 250px;
max-height: 300px;
min-width: 250px;
&:hover {
transform: translateX(10px) translateY(-10px);
}
}
</style>

View File

@ -1,33 +1,43 @@
<script lang="ts">
import type { Exercice, Page } from '../../types/exo.type';
import type { Writable } from 'svelte/store';
import EditForm from './EditForm.svelte';
import type { Exercice, Page } from "../../types/exo.type";
import type { Writable } from "svelte/store";
import EditForm from "./EditForm.svelte";
export let cancel: Function;
export let exos: Writable<{ isLoading: boolean; isFetching: boolean; data: Page }>;
const updateExo = (e: Exercice) => {
exos.update((o) => {
return { ...o, data: { ...o.data, items: [e, ...o.data.items] } };
});
};
export let cancel: Function;
export let exos: Writable<{ isLoading: boolean; isFetching: boolean; data: Page }>;
const updateExo = (e: Exercice) => {
exos.update((o) => {
return { ...o, data: { ...o.data, items: [e, ...o.data.items] } };
});
};
</script>
<div>
<h1>Nouvel exercice</h1>
<EditForm editing={false} {cancel} {updateExo} />
<h1>Nouvel exercice</h1>
<EditForm editing={false} {cancel} {updateExo} />
</div>
<style lang="scss">
@import '../../variables';
div {
background: $background;
padding: 50px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: flex-start;
}
h1 {
font-size: 1.5em;
}
@import '../../variables';
@import '../../mixins';
div {
background: $background;
padding: 50px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: flex-start;
@include down(800){
width: 100%;
height: 100%;
min-width: 0;
}
min-width: 800px;
}
h1 {
font-size: 1.5em;
}
</style>

View File

@ -86,7 +86,7 @@
{/if}
<div class="icons">
<div>
<div class="icon" style:color="black" on:click={() => close()} on:keypress={() => {}}>
<div class="icon contrast" on:click={() => close()} on:keypress={() => {}}>
<MdClose />
</div>
</div>
@ -148,6 +148,7 @@
{/if}
<style lang="scss">
@import '../../mixins';
.icon {
width: 25px;
height: 25px;
@ -183,7 +184,7 @@
span.name {
overflow: hidden;
word-wrap: break-word;
width: 100%;
}
span:not(.name) {
position: relative;
@ -191,6 +192,11 @@
font-size: 18px;
cursor: pointer;
}
@include down(750px){
flex-direction: column;
gap: 10px;
}
}
.examples {

View File

@ -1,43 +1,51 @@
<script lang="ts">
import { form, field } from 'svelte-forms';
import { required, max, min } from 'svelte-forms/validators';
import FileInput from '../forms/FileInput.svelte';
import InputWithLabel from '../forms/InputWithLabel.svelte';
import { getContext } from 'svelte';
import { createExo, editExo } from '../../requests/exo.request';
import type { Exercice } from '../../types/exo.type';
import { checkFile, errorMsg } from '../../utils/forms';
import { compareObject } from '../../utils/utils';
export let editing = true;
export let updateExo: Function = (e: Exercice) => {};
import { form, field } from "svelte-forms";
import { required, max, min } from "svelte-forms/validators";
import FileInput from "../forms/FileInput.svelte";
import InputWithLabel from "../forms/InputWithLabel.svelte";
import { getContext } from "svelte";
import { createExo, editExo } from "../../requests/exo.request";
import type { Exercice } from "../../types/exo.type";
import { checkFile, errorMsg } from "../../utils/forms";
import { compareObject } from "../../utils/utils";
import { goto } from "$app/navigation";
import ModalCard from "./ModalCard.svelte";
export let exo: Exercice | null = null;
export let cancel: Function;
export let editing = true;
export let updateExo: Function = (e: Exercice) => {
};
const { alert } = getContext<{ alert: Function }>('alert');
export let exo: Exercice | null = null;
export let cancel: Function;
// "Legally" initiate empty FileList for model field (simple list raises warning)
/* let list = new DataTransfer();
let file = new File(['content'], !editing || exo == null ? 'filename.py' : exo.exo_source);
list.items.add(file);
!editing && list.items.remove(0); */
const { alert } = getContext<{ alert: Function }>("alert");
const { show } = getContext<{ show: Function }>("modal");
// Initiate fields and form
const name = field('name', !!exo ? exo.name : '', [required(), max(50), min(5)], {
checkOnInit: true
});
const consigne = field('consigne', !!exo && exo.consigne != null ? exo.consigne : '', [max(200)], { checkOnInit: true });
const prv = field('private', !!exo ? exo.private : false);
const model = field('model', [], [checkFile(), required()], {
checkOnInit: !editing
});
const myForm = form(name, consigne, prv, model);
// "Legally" initiate empty FileList for model field (simple list raises warning)
/* let list = new DataTransfer();
let file = new File(['content'], !editing || exo == null ? 'filename.py' : exo.exo_source);
list.items.add(file);
!editing && list.items.remove(0); */
// Initiate fields and form
const name = field("name", !!exo ? exo.name : "", [required(), max(50), min(5)], {
checkOnInit: true
});
const consigne = field("consigne", !!exo && exo.consigne != null ? exo.consigne : "", [max(200)], { checkOnInit: true });
const prv = field("private", !!exo ? exo.private : false);
const model = field("model", [], [checkFile(), required()], {
checkOnInit: !editing
});
const myForm = form(name, consigne, prv, model);
const exerciceStore = getContext<{ exerciceStore: any }>("exerciceStore");
const tagsStore = getContext<{ tagsStore: any }>("tagsStore");
const { navigate } = getContext<{ navigate: Function }>("navigation");
const {success, error} = getContext<{ success: Function, error: Function }>("notif");
</script>
<form
action=""
on:submit|preventDefault={() => {
action=""
on:submit|preventDefault={() => {
if (editing && exo != null) {
editExo(exo.id_code, {
name: $name.value,
@ -45,10 +53,14 @@
private: $prv.value,
...($model.dirty == true && { file: $model.value[0] })
}).then((r) => {
success('Exercice modifié !', `Exercice ${r.data.name} modifié avec succès !`)
exo=r.data
updateExo(r.data);
cancel()
});
}).catch((e) => {
console.log(e)
error('Erreur', 'Une erreur est survenue lors de la modification de l\'exercice')
});
} else {
createExo({
name: $name.value,
@ -57,42 +69,58 @@
file: $model.value[0]
}).then((r) => {
updateExo(r.data);
cancel()
});
success('Exercice créé !', `Exercice ${r.data.name} créé avec succès !`)
goto(`/exercices/${r.data.id_code}`)
//cancel()
show(
ModalCard,
{
exo: r.data,
exos: exerciceStore,
tags: tagsStore
},
() => {
navigate(-1)
}, true
);
}).catch((e) => {
error('Erreur', 'Une erreur est survenue lors de la création de l\'exercice')
});
}
}}
>
<InputWithLabel
type="text"
bind:value={$name.value}
maxlength="50"
minlength="5"
required
label="Nom"
errors={errorMsg($myForm, 'name')}
name="name"
/>
<InputWithLabel
type="text"
bind:value={$name.value}
maxlength="50"
minlength="5"
required
label="Nom"
errors={errorMsg($myForm, 'name')}
name="name"
/>
<InputWithLabel
type="text"
bind:value={$consigne.value}
maxlength="200"
label="Consigne"
errors={errorMsg($myForm, 'consigne')}
name="consigne"
/>
<InputWithLabel
type="text"
bind:value={$consigne.value}
maxlength="200"
label="Consigne"
errors={errorMsg($myForm, 'consigne')}
name="consigne"
/>
<div>
<input type="checkbox" bind:checked={$prv.value} name="private" id="private" />
<label for="private">Privé</label>
</div>
<FileInput bind:value={$model.value} accept=".py" id_code={exo?.id_code} defaultValue={editing &&exo!= null? exo.exo_source: null}/>
<div>
<input type="checkbox" bind:checked={$prv.value} name="private" id="private" />
<label for="private">Privé</label>
</div>
<FileInput bind:value={$model.value} accept=".py" id_code={exo?.id_code}
defaultValue={editing &&exo!= null? exo.exo_source: null} />
<div class="wp-100">
<button class="primary-btn" disabled={!$myForm.valid}>Modifier</button>
<button
class="danger-btn"
on:click|preventDefault={() => {
<div class="wp-100">
<button class="primary-btn" disabled={!$myForm.valid}>Modifier</button>
<button
class="danger-btn"
on:click|preventDefault={() => {
if (exo != null && ($model.dirty || !compareObject({...exo, consigne: exo.consigne == null ? "": exo.consigne}, myForm.summary()))) {
alert({
@ -104,17 +132,18 @@
} else {
cancel();
}
}}>Annuler</button
>
</div>
}}>Annuler
</button
>
</div>
</form>
<style>
form {
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
form {
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
</style>

View File

@ -137,6 +137,7 @@
</div>
<style lang="scss">
@import '../../mixins';
.auth-head {
display: flex;
@ -157,6 +158,11 @@
background-color: $background;
padding: 20px;
height: 100%;
min-width: 600px;
@include down(800){
min-width: 0;
width: 100%;
}
}
.selected {

View File

@ -112,54 +112,59 @@
</script>
{#if $tagStore.data != undefined}
<Head location={filter} bind:search bind:selected />
{/if}
{#if $tagStore.isFetching == true}
Fetching
{/if}
<div class="feed">
<div class="title">
{#if filter == 'user'}
<h1>
Vos <span>exercices</span>
</h1>
<p>
Vous retrouverez ici tous les exercices que vous avez créé ou copié depuis les exercices
publics
</p>
<div class="full" class:loading={$tagStore.isFetching || $exerciceStore.isFetching}>
{#if $tagStore.data != undefined}
<Head location={filter} bind:search bind:selected />
{/if}
<div class="feed">
<div class="title">
{#if filter == 'user'}
<h1>
Vos <span>exercices</span>
</h1>
<p>
Vous retrouverez ici tous les exercices que vous avez créé ou copié depuis les exercices
publics
</p>
{:else}
<h1>
Tous les <span>exercices</span>
</h1>
<p>Vous retrouverez ici tous les exercices créés par les autres utilisateurs</p>
{/if}
</div>
{#if $exerciceStore.data != undefined}
{#each $exerciceStore.data.items.filter((e) => e != null && selected.every((t) => e.tags
.map((s) => s.id_code)
.includes(t.id_code))) as e}
<Card bind:exo={e} />
{/each}
{#if $exerciceStore.data.items.length == 0}
<p class="empty">Aucun exercice</p>
{/if}
{:else}
<h1>
Tous les <span>exercices</span>
</h1>
<p>Vous retrouverez ici tous les exercices créés par les autres utilisateurs</p>
{#each Array(10) as i}
<div class="skeleton"><span /></div>
{/each}
{/if}
</div>
{#if $exerciceStore.data != undefined}
{#each $exerciceStore.data.items.filter((e) => e != null && selected.every((t) => e.tags
.map((s) => s.id_code)
.includes(t.id_code))) as e}
<Card bind:exo={e} />
{/each}
{#if $exerciceStore.data.items.length == 0}
<p>Aucun exercices</p>
{/if}
{:else}
{#each Array(10) as i}
<div class="skeleton"><span /></div>
{/each}
<Pagination bind:page={activePage} total={$exerciceStore.data.totalPage} />
{/if}
</div>
{#if $exerciceStore.data != undefined}
<Pagination bind:page={activePage} total={$exerciceStore.data.totalPage} />
{/if}
<style lang="scss">
@import '../../variables';
@import "../../mixins.scss";
.skeleton {
width: 330px;
//width: 330px;
max-width: 330px;
height: 250px;
opacity: .8;
@ -214,6 +219,10 @@
margin-top: 20px;
}
.full {
padding: 0 20px;
}
.title {
grid-column: 1/3;
display: flex;
@ -221,6 +230,11 @@
justify-content: center;
flex-direction: column;
@include down(600) {
grid-column: 1/2;
text-align: center;
}
h1 {
font-size: 3.5em;
font-weight: bolder;
@ -235,4 +249,11 @@
color: $primary;
}
}
.empty{
display: flex;
justify-content: center;
align-items: center;
font-style: italic;
}
</style>

View File

@ -70,6 +70,7 @@
</div>
<style lang="scss">
@import "../../mixins.scss";
.head {
display: flex;
justify-content: space-between;
@ -77,6 +78,17 @@
div {
width: 50%;
}
@include down(600){
flex-direction: column;
gap: 10px;
> * {
width: 100%!important;
}
button{
width: 100%!important;
}
}
}
.search {
display: flex;

View File

@ -51,6 +51,7 @@
}}
/>
{:else if editing === true}
<h1>Modification</h1>
<EditForm
bind:exo
cancel={() => {
@ -63,8 +64,10 @@
<style lang="scss">
@import '../../variables';
@import '../../mixins';
.modal {
min-width: 820px;
min-width: 800px;
background: $background;
padding: 70px;
grid-gap: 10px;
@ -73,5 +76,15 @@
flex-direction: column;
align-items: flex-start;
gap: 20px;
@include down(800){
width: 100%;
height: 100%;
min-width: 0;
padding: 50px;
}
}
h1{
font-size: 1.8rem;
}
</style>

View File

@ -104,7 +104,6 @@
display: flex;
margin: 30px;
height: max-content;
width: 100%;
justify-content: center;
align-items: center;
button {

View File

@ -1,91 +1,91 @@
<script lang="ts">
import type {
Challenge,
Member,
ParcoursInfos,
Room,
Note as NoteType
} from '../../types/room.type';
import { getContext, onDestroy, onMount } from "svelte";
import { writable, type Writable } from 'svelte/store';
import { challenge, corrigeChallenge, getChallenge, getParcours, sendChallenge } from '../../requests/room.request';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import InputChallenge from './InputChallenge.svelte';
import { parseTimer } from '../../utils/utils';
import FaUndo from 'svelte-icons/fa/FaUndo.svelte';
import type {
Challenge,
Member,
ParcoursInfos,
Room,
Note as NoteType
} from "../../types/room.type";
import { getContext, onDestroy, onMount } from "svelte";
import { writable, type Writable } from "svelte/store";
import { challenge, corrigeChallenge, getChallenge, getParcours, sendChallenge } from "../../requests/room.request";
import { goto } from "$app/navigation";
import { page } from "$app/stores";
import InputChallenge from "./InputChallenge.svelte";
import { parseTimer } from "../../utils/utils";
import FaUndo from "svelte-icons/fa/FaUndo.svelte";
const room: Writable<Room> = getContext('room');
const member: Writable<Member> = getContext('member');
const room: Writable<Room> = getContext("room");
const member: Writable<Member> = getContext("member");
const challengeStore: Writable<{
challenge: Challenge[];
id_code: string;
parcours: ParcoursInfos;
corriged: boolean;
mistakes?: number
validated?: boolean;
challenger?: { name: string };
isCorriged?: boolean,
} | null> = writable(null);
const challengeStore: Writable<{
challenge: Challenge[];
id_code: string;
parcours: ParcoursInfos;
corriged: boolean;
mistakes?: number
validated?: boolean;
challenger?: { name: string };
isCorriged?: boolean,
} | null> = writable(null);
export let id_code: string;
export let corrige: boolean = false;
export let id_code: string;
export let corrige: boolean = false;
onMount(()=>{
if(!corrige) {
challenge($room.id_code, id_code, !$member.isUser ? $member.clientId : null).then((p) => {
challengeStore.set({ ...p, corriged: false });
});
} else {
onMount(() => {
if (!corrige) {
challenge($room.id_code, id_code, !$member.isUser ? $member.clientId : null).then((p) => {
challengeStore.set({ ...p, corriged: false });
});
} else {
getChallenge($room.id_code, id_code, !$member.isUser ? $member.clientId : null).then((p) => {
challengeStore.set({ ...p, challenge: p.data, note: {...p.note, temporary: !p.isCorriged}, corriged: true });
challengeStore.set({ ...p, challenge: p.data, note: { ...p.note, temporary: !p.isCorriged }, corriged: true });
remaining = p.time;
});
}
})
});
let timer: number | null = null;
let remaining: number | null = null;
const {error, success} = getContext("notif");
$: {
if (!corrige && $challengeStore != null && remaining == null) {
remaining = $challengeStore.parcours.time * 60;
}
}
let timer: number | null = null;
let remaining: number | null = null;
$: {
if (!corrige && $challengeStore != null && timer == null && remaining != null) {
timer = window.setInterval(() => {
remaining = remaining! - 1;
}, 1000);
}
}
$: {
if (!corrige && $challengeStore != null && remaining == null) {
remaining = $challengeStore.parcours.time * 60;
}
}
$: {
if (!corrige && $challengeStore != null && timer == null && remaining != null) {
timer = window.setInterval(() => {
remaining = remaining! - 1;
}, 1000);
}
}
onDestroy(() => {
if (timer != null) {
clearInterval(timer);
}
});
onDestroy(() => {
if (timer != null) {
clearInterval(timer);
}
});
</script>
{#if $challengeStore != null}
<div class="head">
<h1>
{$challengeStore.parcours.name}
<div class="full">
{#if $challengeStore != null}
<div class="head">
<h1>
{$challengeStore.parcours.name}
{#if corrige && !!$challengeStore.challenger && remaining != null}
{#if corrige && !!$challengeStore.challenger && remaining != null}
<span class="correction-info">
- Correction de <span class="italic">{$challengeStore.parcours.name}</span> par
Correction de <span class="italic">{$challengeStore.parcours.name}</span> par
<span class="italic underline">{$challengeStore.challenger.name}</span>
en {parseTimer(remaining)} (cliquez sur les réponses pour voir)</span
>
{:else}
>
{:else}
<span
class="icon"
on:click={() => {
class="icon"
on:click={() => {
challenge($room.id_code, id_code, !$member.isUser ? $member.clientId : null).then(
(p) => {
challengeStore.set({ ...p, corriged: false });
@ -97,63 +97,67 @@
}
);
}}
title={'Réessayer'}
on:keydown={() => {}}><FaUndo /></span
>
{/if}
</h1>
{#if $challengeStore.mistakes}
{$challengeStore.mistakes} fautes
{/if}
{#if !corrige}
<p
class="timer"
class:oneminute={remaining != null && remaining < 60}
class:late={(remaining != null && remaining < 0) ||
[9, 7, 5, 3, 1].includes(remaining != null ? remaining : 0)}
>
{remaining != null && parseTimer(remaining)}
</p>
{/if}
</div>
title={'Réessayer'}
on:keydown={() => {}}><FaUndo /></span
>
{/if}
</h1>
{#each $challengeStore.challenge as e, d (`${$challengeStore.id_code}_${d}`)}
<div class="exo">
<div class="infos">
<h2>Exercice {d + 1} : <span>{e.exo.name}</span></h2>
<p>
{e.exo.consigne}
</p>
</div>
<div class="data">
{#each e.data as c, a}
<div class="calcul">
{#each c.calcul.replace(']', '] ').replace('[', ' [').split(' ') as i, b}
{#if i.startsWith('[') && i.endsWith(']')}
<InputChallenge
bind:value={c.inputs[parseInt(i.replace('[', '').replace(']', ''))].value}
bind:correction={c.inputs[parseInt(i.replace('[', '').replace(']', ''))]
{#if $challengeStore.mistakes}
<p class="mistakes" class:validated={$challengeStore.validated}>{$challengeStore.mistakes} fautes</p>
{/if}
{#if !corrige}
<p
class="timer"
class:oneminute={remaining != null && remaining < 60}
class:late={(remaining != null && remaining < 0) ||
[9, 7, 5, 3, 1].includes(remaining != null ? remaining : 0)}
>
{remaining != null && parseTimer(remaining)}
</p>
{/if}
</div>
{#each $challengeStore.challenge as e, d (`${$challengeStore.id_code}_${d}`)}
<div class="exo">
<div class="infos">
<h2>Exercice {d + 1} : <span>{e.exo.name}</span></h2>
{#if e.exo.consigne != null}
<p>
{e.exo.consigne}
</p>
{/if}
</div>
<div class="data">
{#each e.data as c, a}
<div class="calcul">
{#each c.calcul.replace(']', '] ').replace('[', ' [').split(' ') as i, b}
{#if i.startsWith('[') && i.endsWith(']')}
<InputChallenge
bind:value={c.inputs[parseInt(i.replace('[', '').replace(']', ''))].value}
bind:correction={c.inputs[parseInt(i.replace('[', '').replace(']', ''))]
.correction}
corriged={$challengeStore.corriged}
bind:valid={c.inputs[parseInt(i.replace('[', '').replace(']', ''))].valid}
corrigeable={corrige}
admin = {$member.isAdmin}
/>
{:else}
{i}{' '}
{/if}
{/each}
</div>
{/each}
</div>
</div>
{/each}
<div>
{#if !corrige}
<button
hidden={$challengeStore.corriged}
class="primary-btn"
on:click={() => {
corriged={$challengeStore.corriged}
bind:valid={c.inputs[parseInt(i.replace('[', '').replace(']', ''))].valid}
corrigeable={corrige}
admin={$member.isAdmin}
/>
{:else}
{i}{' '}
{/if}
{/each}
</div>
{/each}
</div>
</div>
{/each}
<div>
{#if !corrige}
<button
hidden={$challengeStore.corriged}
class="primary-btn"
on:click={() => {
if ($challengeStore == null || remaining == null) return;
sendChallenge(
$room.id_code,
@ -175,12 +179,13 @@
clearInterval(timer);
}
});
}}>Valider !</button
>
<button
hidden={!$challengeStore.corriged}
class="primary-btn"
on:click={() => {
}}>Valider !
</button
>
<button
hidden={!$challengeStore.corriged}
class="primary-btn"
on:click={() => {
console.log('RETRY CLICKED')
challenge($room.id_code, id_code, !$member.isUser ? $member.clientId : null).then((p) => {
challengeStore.set({ ...p, corriged: false });
@ -190,107 +195,149 @@
timer = null;
}
});
}}>Réessayer !</button
>
{:else if $member.isAdmin}
<button
hidden={!$challengeStore.corriged}
class="primary-btn"
on:click={() => {
}}>Réessayer !
</button
>
{:else if $member.isAdmin}
<button
hidden={!$challengeStore.corriged}
class="primary-btn"
on:click={() => {
corrigeChallenge($room.id_code, id_code,$challengeStore?.challenge, !$member.isUser ? $member.clientId : null).then((p) => {
if($challengeStore == null) return
$challengeStore.challenge = p.data
$challengeStore.mistakes = p.mistakes
$challengeStore.validated = p.validated
success('Corrigé !', 'Le challenge a été corrigé avec succès !')
}).catch(()=>{
error('Erreur', 'Une erreur est survenue lors de la correction du challenge')
});
}}>Valider !</button
>
{/if}
}}>Valider !
</button
>
{/if}
<button
class="danger-btn"
on:click={() => {
<button
class="danger-btn"
on:click={() => {
if ($challengeStore == null) return;
goto(`?${new URLSearchParams({p: $challengeStore.parcours.id_code}).toString()}`);
}}>{!$challengeStore.corriged?"Annuler !":"Retour"}</button
>
</div>
{/if}
}}>{!$challengeStore.corriged ? "Annuler !" : "Retour"}</button
>
</div>
{/if}
</div>
<style lang="scss">
.timer {
font-size: 2em;
color: $green;
font-weight: 800;
}
@import "../../mixins";
.oneminute {
color: $orange;
}
.full {
padding: 7px 15px;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin: 40px 0;
min-height: 70px;
}
.timer {
font-size: 2em;
color: $green;
font-weight: 800;
}
.late {
color: $red;
}
.oneminute {
color: $orange;
}
.calcul {
display: flex;
align-items: center;
gap: 10px;
position: relative;
}
.data {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin: 40px 0;
min-height: 70px;
@include down(800px) {
flex-direction: column;
text-align: center;
h1{
flex-direction: column;
}
}
.infos {
h2 {
font-size: 1.2em;
span {
font-style: italic;
}
}
p {
font-size: 1em;
font-style: italic;
text-decoration: underline;
}
margin-bottom: 10px;
}
@include up(800px) {
.correction-info::before{
content: " - ";
}
}
.exo {
margin-bottom: 30px;
margin-top: 20px;
}
.icon {
height: 20px;
width: 20px;
cursor: pointer;
display: flex;
transition: 0.2s;
&:hover {
transform: rotate(360deg);
}
}
h1 {
display: flex;
align-items: center;
gap: 20px;
font-size: 2.3em;
}
.correction-info {
font-size: 0.6em;
color: grey;
font-weight: 600;
}
}
.late {
color: $red;
}
.calcul {
display: flex;
align-items: center;
gap: 10px;
position: relative;
}
.data {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
}
.infos {
h2 {
font-size: 1.2em;
span {
font-style: italic;
}
}
p {
font-size: 1em;
font-style: italic;
text-decoration: underline;
}
margin-bottom: 10px;
}
.exo {
margin-bottom: 30px;
margin-top: 20px;
}
.icon {
height: 20px;
width: 20px;
cursor: pointer;
display: flex;
transition: 0.2s;
&:hover {
transform: rotate(360deg);
}
}
h1 {
display: flex;
align-items: center;
gap: 20px;
font-size: 2.3em;
}
.correction-info {
font-size: 0.6em;
color: grey;
font-weight: 600;
}
.mistakes {
font-weight: 700;
font-size: 2em;
color: $red;
}
.validated {
color: $green;
}
</style>

View File

@ -1,113 +1,134 @@
<script lang="ts">
import type { Member, ParcoursRead } from '../../types/room.type';
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import IoIosArrowDown from 'svelte-icons/io/IoIosArrowDown.svelte';
import { goto } from '$app/navigation';
import { parseTimer } from '../../utils/utils';
import IoMdOpen from 'svelte-icons/io/IoMdOpen.svelte';
import type { Member, ParcoursRead } from "../../types/room.type";
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
import IoIosArrowDown from "svelte-icons/io/IoIosArrowDown.svelte";
import { goto } from "$app/navigation";
import { parseTimer } from "../../utils/utils";
import IoMdOpen from "svelte-icons/io/IoMdOpen.svelte";
const parcours: Writable<ParcoursRead | null> = getContext('parcours');
const member: Writable<Member | null> = getContext('member');
const parcours: Writable<ParcoursRead | null> = getContext("parcours");
const member: Writable<Member | null> = getContext("member");
let selected = "";
let selected = '';
</script>
{#if $parcours != null && $member != null}
<div class="trylist">
{#if Object.keys($parcours.challenges).length == 0}
<p class="italic">Aucun essai effectué :(</p>
{/if}
<div class="trylist">
{#if Object.keys($parcours.challenges).length == 0}
<p class="italic">Aucun essai effectué :(</p>
{/if}
{#each Object.entries($parcours.challenges) as [id, chall]}
<p
on:click={() => {
{#each Object.entries($parcours.challenges) as [id, chall]}
<p
on:click={() => {
selected = selected == chall.challenger.id_code ? '' : chall.challenger.id_code;
}}
class:selected={selected == chall.challenger.id_code}
class="tries"
on:keydown={() => {}}
>
<span class="icon"><IoIosArrowDown /></span>
{chall.challenger.id_code == $member.id_code ? 'Vos essais' : chall.challenger.name}
</p>
class:selected={selected == chall.challenger.id_code}
class="tries"
on:keydown={() => {}}
>
<span class="icon"><IoIosArrowDown /></span>
{chall.challenger.id_code == $member.id_code ? 'Vos essais' : chall.challenger.name}
<span class:valid = {chall.challenger.validated} class="validation-status">{chall.challenger.validated ? "Validé": "Non validé"}</span>
</p>
{#if selected == chall.challenger.id_code}
{#each chall.challenges as c}
<div
class="try"
on:click={() => {
{#if selected == chall.challenger.id_code}
{#each chall.challenges as c}
<div
class="try"
on:click={() => {
goto(`?${new URLSearchParams({corr: c.id_code}).toString()}`);
}}
on:keydown={() => {}}
title="Voir la correction"
>
<p><span class:validated={c.validated} class:uncorriged={!c.isCorriged} class="note"
>{c.mistakes} faute{c.mistakes > 1 ?"s": ""} </span> en <strong >{parseTimer(c.time)}</strong>
</p>
<span class="corrige-link icon"><IoMdOpen /></span>
</div>
{/each}
{/if}
{/each}
</div>
on:keydown={() => {}}
title="Voir la correction"
>
<p><span class:validated={c.validated} class:uncorriged={!c.isCorriged} class="note"
>{c.mistakes} faute{c.mistakes > 1 ? "s" : ""} </span> en <strong>{c.time < $parcours.time * 60 ? parseTimer(c.time): parseTimer($parcours.time*60)} <span title={`Vous avez dépassé de ${parseTimer(c.time - $parcours.time*60)}`} class="time-overflow">{c.time > $parcours.time * 60? `( + ${parseTimer(c.time - $parcours.time*60)} )`:""}</span></strong>
</p>
<span class="corrige-link icon"><IoMdOpen /></span>
</div>
{/each}
{/if}
{/each}
</div>
{/if}
<style lang="scss">
.tries {
cursor: pointer;
display: flex;
gap: 5px;
align-items: center;
transition: 0.3s;
.icon {
width: 20px;
display: flex;
align-items: center;
transform: rotate(-90deg);
transition: 0.2s;
}
}
.selected {
font-weight: 700;
.icon {
transform: rotate(0);
}
}
.tries {
cursor: pointer;
display: flex;
gap: 5px;
align-items: center;
transition: 0.3s;
.try {
display: flex;
gap: 10px;
cursor: pointer;
width: max-content;
margin-left: 30px;
p span{
color: $red;
font-weight: 600;
}
}
.icon {
width: 20px;
display: flex;
align-items: center;
transform: rotate(-90deg);
transition: 0.2s;
}
}
.icon {
display: flex;
align-items: center;
width: 20px;
}
.selected {
font-weight: 700;
.icon {
transform: rotate(0);
}
}
.try {
display: flex;
gap: 10px;
cursor: pointer;
width: max-content;
margin-left: 30px;
p > span {
color: $red;
font-weight: 600;
}
}
.icon {
display: flex;
align-items: center;
width: 20px;
}
.corrige-link {
color: $primary;
}
.uncorriged {
color: grey;
font-weight: 900;
}
.trylist {
display: flex;
flex-direction: column;
gap: 10px;
}
.validation-status{
color: $red;
font-weight: 600;
&::before{
content: " - ";
}
}
.validated, .valid {
color: $green !important;
}
.time-overflow{
color: $red;
font-weight: 400;
font-size: .8em;
}
.corrige-link {
color: $primary;
}
.uncorriged {
color: grey;
font-weight: 900;
}
.trylist {
display: flex;
flex-direction: column;
gap: 10px;
}
.validated {
color: $green !important;
}
</style>

View File

@ -4,8 +4,7 @@
import FaUndo from "svelte-icons/fa/FaUndo.svelte";
import FaTimes from "svelte-icons/fa/FaTimes.svelte";
import FaSignOutAlt from "svelte-icons/fa/FaSignOutAlt.svelte";
import IoMdLogOut from 'svelte-icons/io/IoMdLogOut.svelte'
import { goto } from "$app/navigation";
import type { Writable } from "svelte/store";
import type { Member, Room } from "../../types/room.type";
@ -84,7 +83,7 @@
}}
on:keypress={()=>{}}
>
<FaSignOutAlt />
<IoMdLogOut />
</div>
{/if}

View File

@ -94,7 +94,7 @@
if (checkExpire(exp) && refresh != null) {
refreshRequest(refresh).then((r) => {
localStorage.setItem('token', r.access_token);
$username = username;
$username = name;
$isAuth = true;
});

View File

@ -69,16 +69,19 @@
position: fixed;
top: 50%;
left: 50%;
width: 50%;
//width: 50%;
transform: translateX(-50%) translateY(-50%) scale(0.7);
visibility: hidden;
transition: 0.4s;
opacity: 0;
z-index: 1000;
height: 57vh;
max-height: 57vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&.visible {
visibility: visible !important;
transform: translateX(-50%) translateY(-50%) scale(1) !important;
@ -95,7 +98,7 @@
.overlay {
background-color: black;
opacity: 0;
z-index: 999;
z-index: 500;
width: 100%;
position: absolute;
top: 0;

View File

@ -1,160 +1,174 @@
<script lang="ts">
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
import SvelteMarkdown from 'svelte-markdown'
type Notif = {
title: string;
description: string;
type: 'alert' | 'info' | 'success' | 'error';
};
type Notification = {
title: string;
description: string;
type: 'alert' | 'info' | 'success' | 'error';
id: number;
deleted: boolean;
};
import { setContext } from "svelte";
import { writable } from "svelte/store";
import SvelteMarkdown from "svelte-markdown";
const notifications = writable<Notification[]>([]);
type Notif = {
title: string;
description: string;
type: "alert" | "info" | "success" | "error";
};
type Notification = {
title: string;
description: string;
type: "alert" | "info" | "success" | "error";
id: number;
deleted: boolean;
};
const getId = () => {
return Math.round(Date.now() * Math.random());
};
const notifications = writable<Notification[]>([]);
const toast = (notif: Notif) => {
let id = getId();
notifications.update((n) => {
return [...n, { ...notif, id, deleted: false }];
});
setTimeout(() => {
notifications.update((n) => {
return n.map((o) => {
if (o.id == id) {
return { ...o, deleted: true };
}
return o;
});
});
setTimeout(() => {
notifications.update((n) => {
return n.filter((o) => o.id != id);
});
}, 500);
}, 3000);
};
const getId = () => {
return Math.round(Date.now() * Math.random());
};
const alert = (title: string, description: string) => {
toast({ title, description, type: 'alert' });
};
const info = (title: string, description: string) => {
toast({ title, description, type: 'info' });
};
const success = (title: string, description: string) => {
toast({ title, description, type: 'success' });
};
const error = (title: string, description: string) => {
toast({ title, description, type: 'error' });
};
setContext('notif', { toast, alert, info, success, error });
const toast = (notif: Notif) => {
let id = getId();
notifications.update((n) => {
return [...n, { ...notif, id, deleted: false }];
});
setTimeout(() => {
notifications.update((n) => {
return n.map((o) => {
if (o.id == id) {
return { ...o, deleted: true };
}
return o;
});
});
setTimeout(() => {
notifications.update((n) => {
return n.filter((o) => o.id != id);
});
}, 500);
}, 3000);
};
const alert = (title: string, description: string) => {
toast({ title, description, type: "alert" });
};
const info = (title: string, description: string) => {
toast({ title, description, type: "info" });
};
const success = (title: string, description: string) => {
toast({ title, description, type: "success" });
};
const error = (title: string, description: string) => {
toast({ title, description, type: "error" });
};
setContext("notif", { toast, alert, info, success, error });
</script>
<slot />
<div class="notifs" class:empty ={$notifications.length == 0}>
{#each $notifications as n}
<div
on:click={() => {
<div class="notifs" class:empty={$notifications.length == 0}>
{#each $notifications as n}
<div
on:click={() => {
n.deleted = true;
setTimeout(() => {
notifications.update((o) => o.filter((i) => i.id != n.id));
}, 500);
}}
class={n.type}
on:keydown={() => {}}
class:deleted={n.deleted}
>
<h1>{n.title}</h1>
<p><SvelteMarkdown source={n.description} /></p>
</div>
{/each}
class={n.type}
on:keydown={() => {}}
class:deleted={n.deleted}
>
<h1>{n.title}</h1>
<p>
<SvelteMarkdown source={n.description} />
</p>
</div>
{/each}
</div>
<style lang="scss">
.notifs {
position: absolute;
right: 0;
top: 0;
padding: 30px;
gap: 10px;
display: flex;
flex-direction: column;
width: 500px;
z-index: 1000;
.notifs {
position: absolute;
right: 0;
top: 0;
margin: 30px;
gap: 10px;
display: flex;
flex-direction: column;
width: min(calc(100% - 60px), 500px);
z-index: 500;
div {
background-color: $background;
z-index: 1000;
padding: 10px;
cursor: pointer;
position: relative;
overflow: hidden;
word-wrap: break-word;
h1 {
font-size: 1.1em;
}
p {
font-size: 0.9em;
}
div {
background-color: $background;
z-index: 1000;
padding: 10px;
cursor: pointer;
position: relative;
overflow: hidden;
word-wrap: break-word;
&::before,
&::after {
content: '';
display: block;
height: 3px;
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
&::before {
z-index: 3;
background-color: $background-light;
transition: 3s;
width: 0%;
animation: slide 3s forwards ease-in-out;
}
&::after {
width: 100%;
h1 {
font-size: 1.1em;
}
z-index: 2;
}
&.deleted {
transition: 0.5s;
opacity: 0;
}
}
}
.empty{
z-index: -20;
}
.alert::after{
background-color: $orange;
p {
font-size: 0.9em;
}
&::before,
&::after {
content: '';
display: block;
height: 3px;
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
&::before {
z-index: 3;
background-color: $background-light;
transition: 3s;
width: 0%;
animation: slide 3s forwards ease-in-out;
}
&::after {
width: 100%;
z-index: 2;
}
&.deleted {
transition: 0.5s;
opacity: 0;
}
}
.info::after{
background-color: $bleu;
}
.empty {
z-index: -20;
}
.alert::after {
background-color: $orange;
}
.info::after {
background-color: $bleu;
}
.error::after {
background-color: $red;
}
.success::after {
background-color: $green;
}
@keyframes slide {
from {
width: 0%;
}
.error::after{
background-color: $red;
to {
width: 100%;
}
.success::after{
background-color: $green;
}
@keyframes slide {
from {
width: 0%;
}
to {
width: 100%;
}
}
}
</style>

View File

@ -1,5 +1,6 @@
import axios from 'axios';
import {authInstance} from '../apis/auth.api';
import { env } from "$env/dynamic/public";
export const loginRequest = (data: { username: string; password: string }) => {
return authInstance
@ -37,9 +38,9 @@ export const registerRequest = (data: {
};
export const refreshRequest = (token: string) => {
return authInstance
return axios
.request({
url: '/refresh',
url: `${env.PUBLIC_API_BASE}/refresh`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',

View File

@ -131,9 +131,11 @@ export const getExoSource = (id_code: string,) => {
link.click();
link.remove();
});
};export const generateRequest = (id_code: string,filename: string) => {
};
export const generateRequest = (id_code: string,filename: string) => {
return exoInstance({
url: `/generator/csv/${id_code}/`,
url: `generator/csv/${id_code}`,
method: 'Get',
params: {filename}
}).then((r) => {

View File

@ -4,7 +4,7 @@ import { roomInstance } from '../apis/room.api';
export const createRoom = (data: { name: string }, username: string | null = null) => {
return roomInstance
.request({
url: '/',
url: '',
method: 'POST',
params: { username },
data: { ...data, public: false, global_results: false }

View File

@ -79,7 +79,7 @@
height: calc(100vh - var(--navbar-height) - 10px);
overflow: auto;
height: 100%;
overflow-x: hidden;
a {
color: red;
}

View File

@ -1,10 +1,30 @@
<script>
<script lang="ts">
import {goto} from "$app/navigation";
</script>
<button
on:click={() => {
}}>test</button
>
<div class="rooms">
<h1>Générateur d'exercices</h1>
<div class="btns">
<button class="primary-btn" on:click={()=>{goto('/exercices')}}>Générer</button>
<button class="primary-btn" on:click={()=>{goto('/room')}}>Salles en ligne</button>
</div>
</div>
<style lang="scss">
h1{
font-size: 4rem;
margin-bottom: 1rem;
}
.rooms {
display: flex;
flex-direction: column;
align-items: center;
}
.btns {
display: flex;
gap: 20px;
}
</style>

View File

@ -1,42 +1,42 @@
<script lang='ts'>
import {getContext} from "svelte";
import {dashBoardRequest} from "../../requests/auth.request";
import Section from "../../components/auth/Section.svelte";
import InfoForm from "../../components/auth/InfoForm.svelte";
import RoomList from "../../components/auth/RoomList.svelte";
import type {User} from "../../types/auth.type";
import type {Writable} from "svelte/store";
import {writable} from "svelte/store";
import PasswordForm from "../../components/auth/PasswordForm.svelte";
import {updatePassword, updateUserRequest} from "../../requests/auth.request.js";
import UserConfirm from "../../components/auth/UserConfirm.svelte";
import MdInfo from 'svelte-icons/md/MdInfo.svelte'
import FaUsers from 'svelte-icons/fa/FaUsers.svelte'
import FaUserLock from 'svelte-icons/fa/FaUserLock.svelte'
import {goto} from "$app/navigation";
<script lang="ts">
import { getContext } from "svelte";
import { dashBoardRequest } from "../../requests/auth.request";
import Section from "../../components/auth/Section.svelte";
import InfoForm from "../../components/auth/InfoForm.svelte";
import RoomList from "../../components/auth/RoomList.svelte";
import type { User } from "../../types/auth.type";
import type { Writable } from "svelte/store";
import { writable } from "svelte/store";
import PasswordForm from "../../components/auth/PasswordForm.svelte";
import { updatePassword, updateUserRequest } from "../../requests/auth.request.js";
import UserConfirm from "../../components/auth/UserConfirm.svelte";
import MdInfo from "svelte-icons/md/MdInfo.svelte";
import FaUsers from "svelte-icons/fa/FaUsers.svelte";
import FaUserLock from "svelte-icons/fa/FaUserLock.svelte";
import { goto } from "$app/navigation";
let u = '';
let p = '';
const user: Writable<User | null> = writable(null)
const {logout, username, isAuth,initialLoading} = getContext('auth')
let u = "";
let p = "";
const user: Writable<User | null> = writable(null);
const { logout, username, isAuth, initialLoading } = getContext("auth");
$: !$initialLoading && $isAuth && dashBoardRequest().then((res) => {
console.log(res)
user.set(res)
})
const {show, close} = getContext("modal")
const {success, error} = getContext("notif")
let passwordForm = null
let infoForm = null
$: !$initialLoading && $isAuth && dashBoardRequest().then((res) => {
console.log(res);
user.set(res);
});
const { show, close } = getContext("modal");
const { success, error } = getContext("notif");
let passwordForm = null;
let infoForm = null;
$: !$initialLoading && !$isAuth && goto('/')
$: !$initialLoading && !$isAuth && goto("/");
</script>
<div>
{#if $user != null}
<h1>Mon compte</h1>
<Section title="Mes informations" icon={MdInfo} onValidate={()=>{
{#if $user != null}
<h1>Mon compte</h1>
<Section title="Mes informations" icon={MdInfo} onValidate={()=>{
if(infoForm == null) return
updateUserRequest($infoForm.summary).then(res=>{
user.update((o)=>{return {...o,...res}})
@ -46,12 +46,12 @@
})
}} canValid={infoForm != null && $infoForm.valid}>
<InfoForm user={$user} bind:myForm={infoForm}/>
</Section>
<Section title="Salles" icon={FaUsers}>
<RoomList rooms={$user.rooms}/>
</Section>
<Section title="Sécurité" icon={FaUserLock} validate="Modifier mon mot de passe" onValidate={()=>{
<InfoForm user={$user} bind:myForm={infoForm} />
</Section>
<Section title="Salles" icon={FaUsers}>
<RoomList rooms={$user.rooms} />
</Section>
<Section title="Sécurité" icon={FaUserLock} validate="Modifier mon mot de passe" onValidate={()=>{
show(UserConfirm, {onValidate: ( p )=>{
updatePassword({...$passwordForm.summary, old_password: p}).then((r)=>{
localStorage.setItem("token", r.access_token)
@ -65,15 +65,24 @@
}, validate: "Changer mon mot de passe", cancel: close, cancelMsg: "Garder le mot de passe actuel"})
}} canValid={passwordForm != null && $passwordForm.valid}>
<PasswordForm bind:myForm={passwordForm}/>
</Section>
{/if}
<PasswordForm bind:myForm={passwordForm} />
</Section>
{/if}
</div>
<style lang='scss'>
<style lang="scss">
@import "../../mixins.scss";
h1 {
font-size: 3em;
margin-bottom: 20px;
@include down(800){
text-align: center;
}
}
div {
padding: 7px 15px;
}
</style>

View File

@ -3,14 +3,27 @@
import {goto} from "$app/navigation";
</script>
<div>
<div class="rooms">
<h1>Salles</h1>
<div>
<div class="btns">
<button class="primary-btn" on:click={()=>{goto('/room/join')}}>Rejoindre</button>
<button class="primary-btn" on:click={()=>{goto('/room/create')}}>Créer</button>
</div>
</div>
<style lang="scss">
h1{
font-size: 4rem;
margin-bottom: 1rem;
}
.rooms {
display: flex;
flex-direction: column;
align-items: center;
}
.btns {
display: flex;
gap: 20px;
}
</style>

View File

@ -63,7 +63,6 @@
if ($page.url.searchParams.get("a") == "waiting") {
goto(`?`);
}
console.log("ACCEPTED", data.member);
member.set(data.member);
getRoom($page.params.slug, !$isAuth ? data.member.clientId : null).then((r) => {
@ -83,7 +82,7 @@
case "waiting":
close()
close();
$member = { ...data.waiter, room: data.room };
goto(`?${new URLSearchParams({ a: "waiting" })}`);
return;
@ -117,9 +116,9 @@
case "deleted":
info("Suppression", "La salle a été supprimée par l'administrateur");
ws.close(1000)
goto("/room/join")
return
ws.close(1000);
goto("/room/join");
return;
case "error":
const { code, msg } = data;
@ -157,9 +156,9 @@
} else {
error("Erreur", "Message : " + msg);
}
if(code == 404){
ws.close(1000)
goto("/room/join")
if (code == 404) {
ws.close(1000);
goto("/room/join");
}
return;
case "waiter":
@ -360,7 +359,7 @@
}
}
case "challenge_change":
if ($parcours != null && !!$parcours.challenges[data.member]) {
if ($parcours != null && $member != null && !!$parcours.challenges[data.member]) {
$parcours.challenges[data.member].challenges = $parcours.challenges[
data.member
].challenges.map((c) => {
@ -369,8 +368,24 @@
}
return c;
});
$parcours.validated = data.validated;
$parcours.challenges[data.member].challenger.validated = data.validated;
if (data.member == $member.id_code) {
$parcours.validated = data.validated;
}
}
return;
case "update_challenges":
if ($parcours != null && $member != null) {
const { challenger, challenges } = data;
if (challenges.length != 0) {
$parcours.challenges[challenger.id_code] = data;
}
if (challenger.id_code == $member.id_code) {
$parcours.validated = data.challenger.validated;
}
}
return;
}
};

View File

@ -24,7 +24,7 @@
if(!$isAuth){
sessionStorage.setItem('reconnect', r.member)
}
goto(`/room/${r.room}`);
goto(`/room/${r.room}`);
});
}}
>