Salles
This commit is contained in:
parent
90b48e710f
commit
9393c88f8e
@ -59,6 +59,12 @@ def change_room_status(room: Room, public: bool, db: Session):
|
||||
return room
|
||||
|
||||
|
||||
def delete_room_db(room: Room, db: Session):
|
||||
db.delete(room)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def get_member_from_user(user_id: int, room_id: int, db: Session):
|
||||
member = db.exec(select(Member).where(Member.room_id ==
|
||||
room_id, Member.user_id == user_id)).first()
|
||||
@ -109,7 +115,8 @@ def get_member_from_clientId(clientId: str, room_id: int, db: Session):
|
||||
return member
|
||||
|
||||
|
||||
def create_member(*, room: Room, user: User | None = None, anonymous: Anonymous | None = None, waiting: bool = False, db: Session):
|
||||
def create_member(*, room: Room, user: User | None = None, anonymous: Anonymous | None = None, waiting: bool = False,
|
||||
db: Session):
|
||||
member_id = generate_unique_code(Member, s=db)
|
||||
member = Member(room=room, user=user, anonymous=anonymous, waiting=waiting,
|
||||
id_code=member_id)
|
||||
@ -269,6 +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.delete(member)
|
||||
db.commit()
|
||||
return None
|
||||
@ -525,7 +534,6 @@ def change_challengers_validation(p: Parcours, validation: int, db: Session):
|
||||
|
||||
|
||||
def change_challenges_validation(p: Parcours, validation: int, db: Session):
|
||||
|
||||
challenges = db.exec(select(Challenge).where(
|
||||
Challenge.parcours_id == p.id_code)).all()
|
||||
print('CHALLS', challenges)
|
||||
@ -844,10 +852,12 @@ def get_exercices(parcours: Parcours = Depends(get_parcours), db: Session = Depe
|
||||
exercices = db.exec(select(Exercice).where(col(Exercice.id_code).in_(
|
||||
[e['exercice_id'] for e in parcours.exercices]))).all()
|
||||
|
||||
return [{"exercice": e, "quantity": [q for q in parcours.exercices if q['exercice_id'] == e.id_code][0]['quantity']} for e in exercices]
|
||||
return [{"exercice": e, "quantity": [q for q in parcours.exercices if q['exercice_id'] == e.id_code][0]['quantity']}
|
||||
for e in exercices]
|
||||
|
||||
|
||||
def get_correction(correction_id: str, parcours_id: str, member: Member = Depends(get_member_dep), db: Session = Depends(get_session)):
|
||||
def get_correction(correction_id: str, parcours_id: str, member: Member = Depends(get_member_dep),
|
||||
db: Session = Depends(get_session)):
|
||||
tmpCorr = db.exec(select(TmpCorrection).where(
|
||||
TmpCorrection.id_code == correction_id, TmpCorrection.parcours_id == parcours_id)).first()
|
||||
if tmpCorr is None:
|
||||
|
@ -24,8 +24,8 @@ class Room(RoomBase, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
id_code: str = Field(index=True)
|
||||
|
||||
members: List['Member'] = Relationship(back_populates="room")
|
||||
parcours: List['Parcours'] = Relationship(back_populates="room")
|
||||
members: List['Member'] = Relationship(back_populates="room",sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
parcours: List['Parcours'] = Relationship(back_populates="room",sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
|
||||
|
||||
class AnonymousBase(SQLModel):
|
||||
@ -58,7 +58,9 @@ class Member(SQLModel, table=True):
|
||||
room_id: int = Field(foreign_key="room.id")
|
||||
room: Room = Relationship(back_populates='members')
|
||||
|
||||
challengers: List["Challenger"] = Relationship(back_populates="member")
|
||||
challengers: List["Challenger"] = Relationship(back_populates="member", sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
corrections: List["TmpCorrection"] = Relationship(back_populates="member", sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
|
||||
|
||||
is_admin: bool = False
|
||||
|
||||
@ -68,7 +70,6 @@ class Member(SQLModel, table=True):
|
||||
|
||||
waiter_code: Optional[str] = Field(default=None)
|
||||
|
||||
corrections: List['TmpCorrection'] = Relationship(back_populates="member")
|
||||
|
||||
|
||||
class ExercicesCreate(SQLModel):
|
||||
@ -101,7 +102,7 @@ class Parcours(SQLModel, table=True):
|
||||
room_id: int = Field(foreign_key="room.id")
|
||||
room: Room = Relationship(back_populates='parcours')
|
||||
|
||||
challengers: list[Challenger] = Relationship(back_populates="parcours")
|
||||
challengers: list[Challenger] = Relationship(back_populates="parcours", sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
|
||||
name: str
|
||||
time: int
|
||||
@ -110,9 +111,9 @@ class Parcours(SQLModel, table=True):
|
||||
max_mistakes: int
|
||||
|
||||
exercices: List[Exercices] = Field(sa_column=Column(JSON))
|
||||
challenges: List["Challenge"] = Relationship(back_populates="parcours")
|
||||
challenges: List["Challenge"] = Relationship(back_populates="parcours", sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
corrections: List["TmpCorrection"] = Relationship(
|
||||
back_populates="parcours")
|
||||
back_populates="parcours", sa_relationship_kwargs={"cascade": "all, delete, delete-orphan"})
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -8,7 +8,7 @@ from database.auth.crud import get_user_from_token
|
||||
from database.room.crud import change_room_name, change_room_status, serialize_member, check_user_in_room, \
|
||||
create_anonymous, create_member, get_member, get_member_from_token, get_member_from_reconnect_code, connect_member, \
|
||||
disconnect_member, get_waiter, accept_waiter, leave_room, refuse_waiter
|
||||
from database.room.models import Room, Member, MemberRead, Waiter
|
||||
from database.room.models import Room, Member, MemberRead, Waiter, Challenger
|
||||
from services.websocket import Consumer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
|
||||
class RoomConsumer(Consumer):
|
||||
|
||||
def __init__(self, ws: WebSocket, room: Room, manager: "RoomManager", db: Session):
|
||||
def __init__(self, ws: WebSocket, room: Room | None, manager: "RoomManager", db: Session):
|
||||
self.room = room
|
||||
self.ws = ws
|
||||
self.manager = manager
|
||||
@ -25,14 +25,22 @@ class RoomConsumer(Consumer):
|
||||
self.member = None
|
||||
self.banned = False
|
||||
|
||||
|
||||
async def connect(self):
|
||||
await self.ws.accept()
|
||||
if self.room is None:
|
||||
await self.send_error("Salle introuvable", code=404)
|
||||
await self.ws.close()
|
||||
return False
|
||||
return True
|
||||
|
||||
# WS Utilities
|
||||
async def send(self, payload: Any | Callable):
|
||||
if callable(payload):
|
||||
payload = payload(self.member)
|
||||
return await super().send(payload)
|
||||
|
||||
async def connect(self):
|
||||
await self.ws.accept()
|
||||
|
||||
|
||||
async def direct_send(self, type: str, payload: Any, code: int | None = None):
|
||||
sending = {'type': type, "data": payload, }
|
||||
@ -243,12 +251,13 @@ class RoomConsumer(Consumer):
|
||||
|
||||
@Consumer.event('leave', conditions=[isMember])
|
||||
async def leave(self):
|
||||
print('LEAVED', self.member, isinstance(self.member, Member), isinstance(self.member, Challenger))
|
||||
if self.member.is_admin is True:
|
||||
await self.send_error("Vous ne pouvez pas quitter une salle dont vous êtes l'administrateur")
|
||||
return
|
||||
member_obj = serialize_member(self.member)
|
||||
leave_room(self.member, self.db)
|
||||
|
||||
self.member = None
|
||||
await self.direct_send(type="successfully_leaved", payload={})
|
||||
await self.broadcast(type='leaved', payload={"member": member_obj})
|
||||
self.member = None
|
||||
@ -294,13 +303,13 @@ class RoomConsumer(Consumer):
|
||||
self.manager.remove(self.room.id, self)
|
||||
return {"waiter_id": waiter_id}
|
||||
|
||||
@Consumer.sending("banned", conditions=[isMember])
|
||||
async def banned(self):
|
||||
self.member = None
|
||||
self.manager.remove(self.room.id_code, self)
|
||||
self.banned = True
|
||||
#await self.ws.close()
|
||||
return {}
|
||||
# @Consumer.sending("banned", conditions=[isMember])
|
||||
# def banned(self):
|
||||
# self.member = None
|
||||
# self.manager.remove(self.room.id_code, self)
|
||||
# self.banned = True
|
||||
# #await self.ws.close()
|
||||
# return {}
|
||||
|
||||
@Consumer.sending('ping', conditions=[isMember])
|
||||
def ping(self):
|
||||
|
@ -8,30 +8,7 @@ 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 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
|
||||
from database.room.models import Challenge, ChallengeRead, Challenges, ParcoursReadUpdate, ChallengeInfo, Member, \
|
||||
Parcours, ParcoursCreate, ParcoursRead, ParcoursReadShort, Room, RoomConnectionInfos, \
|
||||
RoomCreate, RoomInfo, TmpCorrection, CorrigedData, CorrectionData
|
||||
from generateur.generateur_main import generate_from_path, parseGeneratorOut
|
||||
from routes.room.consumer import RoomConsumer
|
||||
from routes.room.manager import RoomManager
|
||||
from services.auth import get_current_user_optional
|
||||
from services.io import add_fast_api_root
|
||||
from services.misc import stripKeyDict
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, WebSocket, status, Query, Body
|
||||
from fastapi.exceptions import HTTPException
|
||||
from pydantic import BaseModel
|
||||
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 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, \
|
||||
@ -68,6 +45,14 @@ 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) ])
|
||||
async def delete_room(room: Room = Depends(get_room), m: RoomManager = Depends(get_manager),
|
||||
db: Session = Depends(get_session)):
|
||||
delete_room_db(room, db)
|
||||
await m.broadcast({"type": "deleted"}, room.id_code)
|
||||
return {"message": "ok"}
|
||||
|
||||
|
||||
@router.post('/room/{room_id}/parcours', response_model=ParcoursRead)
|
||||
async def create_parcours(*, parcours: ParcoursCreate, room_id: str, member: Member = Depends(check_admin),
|
||||
m: RoomManager = Depends(get_manager), db: Session = Depends(get_session)):
|
||||
@ -258,8 +243,7 @@ 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)):
|
||||
if room is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail='Room not found')
|
||||
|
||||
|
||||
consumer = RoomConsumer(ws=ws, room=room, manager=m, db=db)
|
||||
await consumer.run()
|
||||
|
@ -1,9 +1,10 @@
|
||||
from typing import List, Callable, Any, Dict
|
||||
from pydantic import validate_arguments, BaseModel
|
||||
from fastapi.websockets import WebSocketDisconnect, WebSocket
|
||||
from pydantic.error_wrappers import ValidationError
|
||||
import inspect
|
||||
from starlette.websockets import WebSocketState
|
||||
from typing import List, Callable, Any, Dict
|
||||
|
||||
from fastapi.websockets import WebSocketDisconnect, WebSocket
|
||||
from pydantic import validate_arguments, BaseModel
|
||||
from pydantic.error_wrappers import ValidationError
|
||||
|
||||
|
||||
def make_event_decorator(eventsDict):
|
||||
def _(name: str | List, conditions: List[Callable | bool] = []):
|
||||
@ -62,7 +63,8 @@ class Consumer:
|
||||
#self.events: Dict[str, Callable] = {}
|
||||
|
||||
async def connect(self):
|
||||
pass
|
||||
await self.ws.accept()
|
||||
return True
|
||||
|
||||
async def validation_error_handler(self, e: ValidationError):
|
||||
errors = e.errors()
|
||||
@ -132,7 +134,9 @@ class Consumer:
|
||||
pass
|
||||
|
||||
async def run(self):
|
||||
await self.connect()
|
||||
accepted = await self.connect()
|
||||
if accepted is False:
|
||||
return
|
||||
try:
|
||||
while True:
|
||||
data = await self.ws.receive_json()
|
||||
|
@ -1,13 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { autoRefresh } from '../utils/utils';
|
||||
import {env} from '$env/dynamic/private';
|
||||
import axios from "axios";
|
||||
import { autoRefresh } from "../utils/utils";
|
||||
import { env } from "$env/dynamic/public";
|
||||
|
||||
export const authInstance = axios.create({
|
||||
baseURL: `${env.API_BASE}`,
|
||||
baseURL: `${env.PUBLIC_API_BASE}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
//'X-CSRFToken': csrftoken != undefined ? csrftoken : '',
|
||||
}
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import axios from 'axios';
|
||||
import { parse, stringify } from 'qs'
|
||||
import { autoRefresh } from '../utils/utils';
|
||||
import {env} from '$env/dynamic/private';
|
||||
import { env } from "$env/dynamic/public";
|
||||
export const exoInstance = axios.create({
|
||||
paramsSerializer:{encode:(params)=> {return parse(params, {arrayFormat:"brackets"})}, serialize: (p)=>{return stringify(p, {arrayFormat: "repeat"})}},
|
||||
baseURL: `${env.API_BASE}`,
|
||||
baseURL: `${env.PUBLIC_API_BASE}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
|
@ -1,13 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { autoRefresh } from '../utils/utils';
|
||||
import {env} from '$env/dynamic/private';
|
||||
import axios from "axios";
|
||||
import { autoRefresh } from "../utils/utils";
|
||||
import { env } from "$env/dynamic/public";
|
||||
|
||||
export const roomInstance = axios.create({
|
||||
baseURL: `${env.API_BASE}/room`,
|
||||
baseURL: `${env.PUBLIC_API_BASE}room`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
//'X-CSRFToken': csrftoken != undefined ? csrftoken : '',
|
||||
}
|
||||
});
|
||||
|
@ -16,6 +16,7 @@
|
||||
afterNavigate(() => {
|
||||
open = false;
|
||||
});
|
||||
$: console.log("USERNAME", $username);
|
||||
</script>
|
||||
|
||||
<nav data-sveltekit-preload-data="hover" class:open>
|
||||
@ -37,7 +38,8 @@
|
||||
<div class="icon">
|
||||
<FaUser />
|
||||
</div>
|
||||
{$username}</div>
|
||||
{$username}
|
||||
</div>
|
||||
</NavLink>
|
||||
<div class="icon signout" title="Se déconnecter" on:click={()=>{
|
||||
logout()
|
||||
|
@ -10,7 +10,7 @@
|
||||
<ul>
|
||||
{#each rooms as room}
|
||||
<li>
|
||||
<a href="/room/{room.id_code}">{room.name} ({room.admin ? "Administrateur" : "Member"})</a>
|
||||
<a href="/room/{room.id_code}">{room.name} ({room.admin ? "Administrateur" : "Membre"})</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
@ -4,6 +4,10 @@
|
||||
import FaLock from "svelte-icons/fa/FaLock.svelte";
|
||||
import { getContext } from "svelte";
|
||||
import type { Writable } from "svelte/store";
|
||||
import FaTimes from "svelte-icons/fa/FaTimes.svelte";
|
||||
import FaCheck from "svelte-icons/fa/FaCheck.svelte";
|
||||
import MdCheck from "svelte-icons/md/MdCheck.svelte";
|
||||
import MdClose from "svelte-icons/md/MdClose.svelte";
|
||||
|
||||
const room: Writable<Room> = getContext("room");
|
||||
const member: Writable<Member> = getContext("member");
|
||||
@ -111,14 +115,20 @@
|
||||
class="accept"
|
||||
on:click={() => {
|
||||
send('accept', { waiter_id: m.waiter_id });
|
||||
}}>Accept
|
||||
}}
|
||||
title="Accepter"
|
||||
>
|
||||
<MdCheck />
|
||||
</button
|
||||
>
|
||||
<button
|
||||
class="refuse"
|
||||
on:click={() => {
|
||||
send('refuse', { waiter_id: m.waiter_id });
|
||||
}}>Refuse
|
||||
}}
|
||||
title="Refuser"
|
||||
>
|
||||
<MdClose />
|
||||
</button
|
||||
>
|
||||
</p>
|
||||
@ -171,6 +181,9 @@
|
||||
font-size: 1em;
|
||||
width: max-content;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
color: grey;
|
||||
font-size: 0.9em;
|
||||
@ -203,4 +216,27 @@
|
||||
font-weight: 500;
|
||||
font-size: 0.8em !important;
|
||||
}
|
||||
|
||||
.accept, .refuse {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
transition: .3s;
|
||||
margin: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.accept {
|
||||
margin-left: 5px;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.refuse {
|
||||
color: $red;
|
||||
}
|
||||
</style>
|
||||
|
@ -13,6 +13,8 @@
|
||||
import { messages, handlers } from "../../store/ws";
|
||||
import Stats from "./Stats.svelte";
|
||||
import ChallengesList from "./ChallengesList.svelte";
|
||||
import { delParcours } from "../../requests/room.request.js";
|
||||
import FaRegTrashAlt from "svelte-icons/fa/FaRegTrashAlt.svelte";
|
||||
|
||||
export let id_code: string;
|
||||
|
||||
@ -20,7 +22,7 @@
|
||||
const member: Writable<Member> = getContext("member");
|
||||
|
||||
const { send } = getContext<{ send: Function }>("ws");
|
||||
|
||||
const {alert} = getContext<{alert: Function}>("alert");
|
||||
const parcours: Writable<ParcoursRead | null> = getContext("parcours");
|
||||
|
||||
let open = "";
|
||||
@ -53,6 +55,27 @@
|
||||
>
|
||||
<FaEdit />
|
||||
</div>
|
||||
<div
|
||||
class="icon back"
|
||||
on:keydown={() => {}}
|
||||
on:click|stopPropagation={() => {
|
||||
alert({
|
||||
title: 'Supprimer ?',
|
||||
description: 'Voulez vous supprimer ce parcours ?',
|
||||
validate: () => {
|
||||
delParcours(
|
||||
$room?.id_code,
|
||||
$parcours.id_code,
|
||||
!$member.isUser ? $member?.clientId : null
|
||||
).then(()=>{
|
||||
goto(`?${new URLSearchParams().toString()}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaRegTrashAlt />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
@ -187,6 +210,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
|
||||
@ -235,6 +259,7 @@
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: stretch;
|
||||
padding: 3px 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@ -269,4 +294,7 @@
|
||||
.edit {
|
||||
color: $green;
|
||||
}
|
||||
h1{
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,14 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { delParcours } from '../../requests/room.request';
|
||||
import { getContext } from 'svelte';
|
||||
import FaRegTrashAlt from 'svelte-icons/fa/FaRegTrashAlt.svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { Member, Room } from '../../types/room.type';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
const room: Writable<Room> = getContext('room');
|
||||
const member: Writable<Member> = getContext('member');
|
||||
const { alert } = getContext<{ alert: Function }>('alert');
|
||||
import { getContext } from "svelte";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { Member, Room } from "../../types/room.type";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
const room: Writable<Room> = getContext("room");
|
||||
const member: Writable<Member> = getContext("member");
|
||||
</script>
|
||||
|
||||
<div class="parcours">
|
||||
@ -19,7 +16,8 @@
|
||||
class="primary-btn"
|
||||
on:click={() => {
|
||||
goto(`?${new URLSearchParams({ p: 'new' }).toString()}`);
|
||||
}}>Nouveau</button
|
||||
}}>Nouveau
|
||||
</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
@ -35,37 +33,23 @@
|
||||
}}
|
||||
on:keydown={() => {}}
|
||||
>
|
||||
<p>{p.name}</p>
|
||||
<p class="parcours-name">{p.name}</p>
|
||||
|
||||
|
||||
<div class="stats">
|
||||
<p class="stat">
|
||||
{#if p.best_note}
|
||||
<p>Record : {p.best_note} fautes</p>
|
||||
<strong>Record :</strong> {p.best_note} faute{p.best_note > 1 ? "s" : ""}
|
||||
{:else}
|
||||
Aucun essai effectué
|
||||
{/if}
|
||||
{#if p.validated}
|
||||
<p data-testid="valid">Parcours validé</p>
|
||||
{/if}
|
||||
</p>
|
||||
<p class="stat valid" data-testid="valid"
|
||||
class:validated={p.validated}>{ p.validated ? "Parcours validé" : "Parcours non validé"}</p>
|
||||
|
||||
{#if $member.isAdmin}
|
||||
<div
|
||||
class="icon delete"
|
||||
on:keydown={() => {}}
|
||||
on:click|stopPropagation={() => {
|
||||
alert({
|
||||
title: 'Supprimer ?',
|
||||
description: 'Voulez vous supprimer ce parcours ?',
|
||||
validate: () => {
|
||||
delParcours(
|
||||
$room?.id_code,
|
||||
p.id_code,
|
||||
!$member.isUser ? $member?.clientId : null
|
||||
);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaRegTrashAlt />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@ -77,6 +61,9 @@
|
||||
font-style: italic;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.parcours {
|
||||
background-color: rgba($background, 0.4);
|
||||
border: 1px solid $border;
|
||||
@ -87,28 +74,34 @@
|
||||
flex-direction: column;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid $border;
|
||||
padding: 10px 0;
|
||||
|
||||
button {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $red;
|
||||
transition: 0.4s;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
overflow: auto;
|
||||
|
||||
> div {
|
||||
padding: 30px 10px;
|
||||
border-bottom: 1px solid $border-light;
|
||||
@ -117,12 +110,41 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($color: $background, $amount: 10);
|
||||
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.valid {
|
||||
font-weight: 600;
|
||||
color: $red;
|
||||
}
|
||||
|
||||
.validated {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.stat{
|
||||
text-align: justify;
|
||||
height: 1em;
|
||||
margin: 5px 0;
|
||||
width: max-content;
|
||||
&::after{
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.parcours-name {
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,16 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import FaRegTrashAlt from 'svelte-icons/fa/FaRegTrashAlt.svelte';
|
||||
import { getContext } from "svelte";
|
||||
import FaRegTrashAlt from "svelte-icons/fa/FaRegTrashAlt.svelte";
|
||||
|
||||
import FaUndo from 'svelte-icons/fa/FaUndo.svelte';
|
||||
import FaTimes from 'svelte-icons/fa/FaTimes.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { Room, Member } from '../../types/room.type';
|
||||
import type ReconnectingWebSocket from 'reconnecting-websocket';
|
||||
const room: Writable<Room> = getContext('room');
|
||||
const member: Writable<Member>= getContext('member');
|
||||
const { send, ws } = getContext<{send: Function, ws: ReconnectingWebSocket}>('ws');
|
||||
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 { goto } from "$app/navigation";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { Member, Room } from "../../types/room.type";
|
||||
import type ReconnectingWebSocket from "reconnecting-websocket";
|
||||
import { deleteRoom } from "../../requests/room.request.js";
|
||||
|
||||
const room: Writable<Room> = getContext("room");
|
||||
const member: Writable<Member> = getContext("member");
|
||||
const { send, ws } = getContext<{ send: Function, ws: ReconnectingWebSocket }>("ws");
|
||||
const { alert } = getContext<{ alert: Function }>("alert");
|
||||
let editing = false;
|
||||
let name = $room.name;
|
||||
let r: HTMLInputElement;
|
||||
@ -33,14 +38,13 @@
|
||||
{/if}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
bind:this={r}
|
||||
bind:value={name}
|
||||
class="input"
|
||||
class:hide={!editing}
|
||||
on:focusout={() => {
|
||||
editing = false;
|
||||
}}
|
||||
bind:value={name}
|
||||
bind:this={r}
|
||||
on:keydown={(e) => {
|
||||
if (e.key == 'Escape') {
|
||||
editing = false;
|
||||
@ -49,28 +53,54 @@
|
||||
editing = false;
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="icons">
|
||||
{#if $member.isAdmin}
|
||||
<div class="icon trash" data-testid="delete"><FaRegTrashAlt /></div>
|
||||
<div class="icon trash" data-testid="delete" on:click={()=>{
|
||||
alert({title:"Voulez vous vraiment supprimer la salle ?",
|
||||
description:"L'intégralité des données de la salle sera supprimée et vous ne pourrez plus y accéder",
|
||||
validate: ()=>{
|
||||
deleteRoom($room.id_code, !$member.isUser ? $member.clientId: null)
|
||||
},
|
||||
validateButton: "Supprimer la salle"})
|
||||
|
||||
//goto('/room/join')
|
||||
}}>
|
||||
<FaRegTrashAlt />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="icon trash" data-testid="leave" on:click={()=>{
|
||||
alert({title:"Voulez vous vraiment quitter la salle ?",
|
||||
description:"Vous perdrez l'intégralité de vos résultats et ne pourrez plus accéder à cette salle sans y avoir été accepté à nouveau",
|
||||
validate: ()=>{
|
||||
send('leave')
|
||||
},
|
||||
validateButton: "Quitter la salle"})
|
||||
|
||||
//goto('/room/join')
|
||||
}}
|
||||
on:keypress={()=>{}}
|
||||
>
|
||||
<FaSignOutAlt />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="icon refresh"
|
||||
data-testid="refresh"
|
||||
on:click={() => {
|
||||
console.log(ws)
|
||||
ws.reconnect();
|
||||
}}
|
||||
on:keydown={() => {}}
|
||||
data-testid="refresh"
|
||||
>
|
||||
<FaUndo />
|
||||
</div>
|
||||
<div
|
||||
data-testid="leave"
|
||||
class="icon trash"
|
||||
data-testid="leave"
|
||||
on:click={() => {
|
||||
ws.close();
|
||||
goto('/room/join')
|
||||
@ -90,6 +120,7 @@
|
||||
border: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
max-width: 50%;
|
||||
margin: 10px 0;
|
||||
@ -104,16 +135,19 @@
|
||||
padding-left: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
font-size: inherit;
|
||||
|
||||
span {
|
||||
font-size: 0.5em;
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -126,6 +160,7 @@
|
||||
transition: 0.2s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
@ -137,6 +172,7 @@
|
||||
|
||||
.refresh {
|
||||
color: $contrast;
|
||||
|
||||
&:hover {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { ParcoursRead, Member } from '../../types/room.type';
|
||||
import { parseTimer, statsCalculator } from '../../utils/utils';
|
||||
import { getContext } from 'svelte';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import Classement from './Classement.svelte';
|
||||
import type { ParcoursRead, Member } from "../../types/room.type";
|
||||
import { parseTimer, statsCalculator } from "../../utils/utils";
|
||||
import { getContext } from "svelte";
|
||||
import type { Writable } from "svelte/store";
|
||||
import Classement from "./Classement.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");
|
||||
|
||||
$: stats =
|
||||
$parcours != null && $member != null && !!$parcours.challenges[$member.id_code]
|
||||
@ -80,6 +80,8 @@
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../mixins.scss";
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -87,9 +89,11 @@
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.statistics {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
p {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -102,16 +106,31 @@
|
||||
color: $red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.validated {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.ranks {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
@include down(750) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.classement {
|
||||
width: 100%;
|
||||
h2{
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import {authInstance} from '../apis/auth.api';
|
||||
export const loginRequest = (data: { username: string; password: string }) => {
|
||||
return authInstance
|
||||
.request({
|
||||
url: 'http://localhost:8002/login',
|
||||
url: '/login',
|
||||
method: 'POST',
|
||||
data,
|
||||
headers: {
|
||||
@ -23,7 +23,7 @@ export const registerRequest = (data: {
|
||||
}) => {
|
||||
return authInstance
|
||||
.request({
|
||||
url: 'http://localhost:8002/register',
|
||||
url: '/register',
|
||||
method: 'POST',
|
||||
data,
|
||||
headers: {
|
||||
@ -39,7 +39,7 @@ export const registerRequest = (data: {
|
||||
export const refreshRequest = (token: string) => {
|
||||
return authInstance
|
||||
.request({
|
||||
url: 'http://localhost:8002/refresh',
|
||||
url: '/refresh',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
|
@ -13,7 +13,6 @@ export const createRoom = (data: { name: string }, username: string | null = nul
|
||||
};
|
||||
|
||||
export const getRoom = (id_code: string, clientId: string | null = null) => {
|
||||
console.log('GETROOM', clientId, { ...(clientId != null && { clientId }) });
|
||||
return roomInstance
|
||||
.request({
|
||||
url: '/' + id_code,
|
||||
@ -23,6 +22,16 @@ export const getRoom = (id_code: string, clientId: string | null = null) => {
|
||||
.then((r) => r.data);
|
||||
};
|
||||
|
||||
export const deleteRoom = (id_code: string, clientId: string | null = null) => {
|
||||
return roomInstance
|
||||
.request({
|
||||
url: '/' + id_code,
|
||||
method: 'DELETE',
|
||||
params: { ...(clientId != null && { clientId }) }
|
||||
})
|
||||
.then((r) => r.data);
|
||||
};
|
||||
|
||||
export const createParcours = (
|
||||
id_code: string,
|
||||
parcours: { time: number; name: string; max_mistakes: number; exercices: {exercice_id: string, quantity:number}[] },
|
||||
|
@ -21,6 +21,7 @@
|
||||
<Auth>
|
||||
<Alert>
|
||||
<Modal>
|
||||
|
||||
<main>
|
||||
<NavBar/>
|
||||
<slot/>
|
||||
|
@ -26,13 +26,13 @@
|
||||
const room = writable<Room | null>(null);
|
||||
const member = writable<Member | null>(null);
|
||||
const parcours = writable<ParcoursRead | null>(null);
|
||||
const ws = connect("ws://127.0.0.1:8002/ws/room/" + $page.params.slug);
|
||||
const ws = connect("ws://127.0.0.1:8002/api/ws/room/" + $page.params.slug);
|
||||
|
||||
setContext("room", room);
|
||||
setContext("member", member);
|
||||
setContext("parcours", parcours);
|
||||
setContext("ws", { send: (type: string, data: Object) => ws.send({ type, data }), ws: ws.ws });
|
||||
const { error } = getContext("notif");
|
||||
const { error, info } = getContext("notif");
|
||||
const onMessage = (payload: { type: string; data: any }) => {
|
||||
if (payload == undefined) return;
|
||||
const { type, data } = payload;
|
||||
@ -83,11 +83,13 @@
|
||||
|
||||
|
||||
case "waiting":
|
||||
close()
|
||||
$member = { ...data.waiter, room: data.room };
|
||||
goto(`?${new URLSearchParams({ a: "waiting" })}`);
|
||||
return;
|
||||
|
||||
case "refused":
|
||||
error("Refusé", "L'administrateur a refusé votre demande");
|
||||
close();
|
||||
ws?.close(1000);
|
||||
goto("/room/join");
|
||||
@ -107,11 +109,18 @@
|
||||
}
|
||||
return;
|
||||
case "banned":
|
||||
error("Ban", "Vous avez été banni de la salle par l'administrateur");
|
||||
ws?.close(1000);
|
||||
goto("/room/join");
|
||||
sessionStorage.removeItem("reconnect");
|
||||
return;
|
||||
|
||||
case "deleted":
|
||||
info("Suppression", "La salle a été supprimée par l'administrateur");
|
||||
ws.close(1000)
|
||||
goto("/room/join")
|
||||
return
|
||||
|
||||
case "error":
|
||||
const { code, msg } = data;
|
||||
if (code == 401) {
|
||||
@ -148,6 +157,10 @@
|
||||
} else {
|
||||
error("Erreur", "Message : " + msg);
|
||||
}
|
||||
if(code == 404){
|
||||
ws.close(1000)
|
||||
goto("/room/join")
|
||||
}
|
||||
return;
|
||||
case "waiter":
|
||||
|
||||
@ -507,6 +520,7 @@
|
||||
column-gap: 50px;
|
||||
row-gap: 10px;
|
||||
height: 100%;
|
||||
padding: 7px 15px;
|
||||
@include down(800) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
Loading…
Reference in New Issue
Block a user