291 lines
6.9 KiB
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>
|