generateur_v3/frontend/src/components/rooms/Challenge.svelte

291 lines
6.9 KiB
Svelte

<script lang="ts">
import type {
Challenge,
Member,
ParcoursInfos,
Room,
Note as NoteType
} from '../../types/room.type';
import { getContext, onDestroy } 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 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;
$: !corrige &&
challenge($room.id_code, id_code, $member.isUser ? $member.clientId : null).then((p) => {
challengeStore.set({ ...p, corriged: false });
});
$: corrige &&
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 });
remaining = p.time;
});
let timer: number | null = null;
let remaining: number | null = null;
$: {
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);
}
});
</script>
{#if $challengeStore != null}
<div class="head">
<h1>
{$challengeStore.parcours.name}
{#if corrige && !!$challengeStore.challenger && remaining != null}
<span class="correction-info">
- Correction de <span class="italic">{$challengeStore.parcours.name}</span> par
<span class="italic underline">{$challengeStore.challenger.name}</span>
en {parseTimer(remaining)}</span
>
{:else}
<span
class="icon"
on:click={() => {
challenge($room.id_code, id_code, $member.isUser ? $member.clientId : null).then(
(p) => {
challengeStore.set({ ...p, corriged: false });
remaining = null;
if (timer != null) {
clearInterval(timer);
timer = null;
}
}
);
}}
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>
{#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(']', ''))]
.correction}
corriged={$challengeStore.corriged}
bind:valid={c.inputs[parseInt(i.replace('[', '').replace(']', ''))].valid}
corrigeable={corrige}
/>
{: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,
id_code,
$challengeStore.id_code,
{
challenge: $challengeStore.challenge,
time: $challengeStore.parcours.time * 60 - remaining
},
$member.isUser ? $member.clientId : null
).then((r) => {
if ($challengeStore != null) {
$challengeStore.challenge = r.data;
$challengeStore.corriged = true;
$challengeStore.mistakes = r.mistakes
$challengeStore.validated = r.validated;
}
if (timer != null) {
clearInterval(timer);
}
});
}}>Valider !</button
>
<button
hidden={!$challengeStore.corriged}
class="primary-btn"
on:click={() => {
challenge($room.id_code, id_code, $member.isUser ? $member.clientId : null).then((p) => {
challengeStore.set({ ...p, corriged: false });
remaining = null;
if (timer != null) {
clearInterval(timer);
timer = null;
}
});
}}>Réessayer !</button
>
{:else}
<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
});
}}>Valider !</button
>
{/if}
<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}
<style lang="scss">
.timer {
font-size: 2em;
color: $green;
font-weight: 800;
}
.oneminute {
color: $orange;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin: 40px 0;
min-height: 70px;
}
.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;
}
</style>