ui: Working flags
This commit is contained in:
parent
ef899ee99b
commit
1def2c97c1
@ -8,17 +8,88 @@
|
|||||||
Icon,
|
Icon,
|
||||||
ListGroup,
|
ListGroup,
|
||||||
ListGroupItem,
|
ListGroupItem,
|
||||||
|
Spinner,
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
export let exercice = {};
|
import FlagKey from './FlagKey.svelte';
|
||||||
|
import FlagMCQ from './FlagMCQ.svelte';
|
||||||
|
|
||||||
|
export let exercice = { };
|
||||||
export let flags = [];
|
export let flags = [];
|
||||||
|
|
||||||
function submitFlags(event) {
|
export let refresh_my;
|
||||||
console.log(event);
|
export let refresh_teams;
|
||||||
|
|
||||||
|
function waitDiff(i) {
|
||||||
|
refresh_my((my) => {
|
||||||
|
if (my && my.exercices[exercice.id].tries != exercice.tries) {
|
||||||
|
submitInProgress = false;
|
||||||
|
refresh_teams();
|
||||||
|
} else if (i > 0) {
|
||||||
|
setTimeout(waitDiff, 450, i-1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let responses = { };
|
||||||
|
async function submitFlags() {
|
||||||
|
submitInProgress = true;
|
||||||
|
sberr = "";
|
||||||
|
message = "";
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
"/submit/" + exercice.id,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(responses),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (response.status < 300) {
|
||||||
|
const data = await response.json();
|
||||||
|
messageClass = 'text-success';
|
||||||
|
message = data.errmsg;
|
||||||
|
waitDiff(20);
|
||||||
|
} else {
|
||||||
|
submitInProgress = false;
|
||||||
|
|
||||||
|
messageClass = 'text-danger';
|
||||||
|
|
||||||
|
let data = "";
|
||||||
|
try {
|
||||||
|
data = await response.json();
|
||||||
|
} catch(e) {
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && data.errmsg)
|
||||||
|
message = data.errmsg;
|
||||||
|
if (response.statys != 402)
|
||||||
|
sberr = "Oups !";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetResponses() {
|
||||||
|
responses = {
|
||||||
|
flags: { },
|
||||||
|
mcqs: { },
|
||||||
|
justifications: { },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_exercice = null;
|
||||||
|
$: {
|
||||||
|
if (!last_exercice || last_exercice != exercice.id) {
|
||||||
|
last_exercice = exercice.id;
|
||||||
|
resetResponses()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sberr = "";
|
let sberr = "";
|
||||||
let message = "";
|
let message = "";
|
||||||
|
let messageClass = "text-danger";
|
||||||
|
let timeouted = false;
|
||||||
|
let submitInProgress = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card class="border-danger mb-2">
|
<Card class="border-danger mb-2">
|
||||||
@ -40,7 +111,7 @@
|
|||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#if exercice.submitted || sberr}
|
{#if exercice.submitted || sberr}
|
||||||
<ListGroupItem>
|
<ListGroupItem class="{messageClass}">
|
||||||
{#if !sberr}
|
{#if !sberr}
|
||||||
<strong>Votre solution a bien été envoyée !</strong>
|
<strong>Votre solution a bien été envoyée !</strong>
|
||||||
{:else}
|
{:else}
|
||||||
@ -48,7 +119,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</ListGroupItem>
|
</ListGroupItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#if exercice.timeouted}
|
{#if timeouted}
|
||||||
<ListGroupItem class="text-danger">
|
<ListGroupItem class="text-danger">
|
||||||
<strong>Oops</strong>
|
<strong>Oops</strong>
|
||||||
La requête a dépassé le délai d'attente. Vous devriez réessayer dans quelques instant…
|
La requête a dépassé le délai d'attente. Vous devriez réessayer dans quelques instant…
|
||||||
@ -59,12 +130,34 @@
|
|||||||
{#if !exercice.submitted || sberr}
|
{#if !exercice.submitted || sberr}
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<form on:submit|preventDefault={submitFlags}>
|
<form on:submit|preventDefault={submitFlags}>
|
||||||
{JSON.stringify(flags)}
|
{#each flags as flag ((flag.type?flag.type:"i") + flag.id)}
|
||||||
|
{#if flag.type == "mcq"}
|
||||||
|
<FlagMCQ
|
||||||
|
exercice_id={exercice.id}
|
||||||
|
{flag}
|
||||||
|
{refresh_my}
|
||||||
|
bind:values={responses.mcqs}
|
||||||
|
bind:justifications={responses.justifications}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<FlagKey
|
||||||
|
exercice_id={exercice.id}
|
||||||
|
{flag}
|
||||||
|
{refresh_my}
|
||||||
|
bind:value={responses.flags[flag.id]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
<div class="form-group mt-2">
|
<div class="form-group mt-2">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
color="danger"
|
color="danger"
|
||||||
|
disabled={submitInProgress}
|
||||||
>
|
>
|
||||||
|
{#if submitInProgress}
|
||||||
|
<Spinner size="sm" class="me-2" />
|
||||||
|
{/if}
|
||||||
Soumettre
|
Soumettre
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
173
frontend/ui/src/components/FlagKey.svelte
Normal file
173
frontend/ui/src/components/FlagKey.svelte
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Spinner,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import { settings } from '../stores/settings.js';
|
||||||
|
|
||||||
|
export let refresh_my = null;
|
||||||
|
export let exercice_id = 0;
|
||||||
|
export let flag = { };
|
||||||
|
export let value = "";
|
||||||
|
let values = [""];
|
||||||
|
|
||||||
|
function waitChoices(i) {
|
||||||
|
refresh_my((my) => {
|
||||||
|
let haveChoices = false;
|
||||||
|
|
||||||
|
if (my && my.exercices[exercice_id].flags) {
|
||||||
|
my.exercices[exercice_id].flags.forEach((f) => {
|
||||||
|
if (f.id == flag.id && f.choices) {
|
||||||
|
haveChoices = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveChoices) {
|
||||||
|
wcsubmitted = false;
|
||||||
|
} else if (i > 0) {
|
||||||
|
setTimeout(waitChoices, 450, i-1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let wcsubmitted = false;
|
||||||
|
async function wantchoices() {
|
||||||
|
wcsubmitted = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
"/wantchoices/" + exercice_id,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ id: Number(flag.id) }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (response.status < 300) {
|
||||||
|
waitChoices(15);
|
||||||
|
} else {
|
||||||
|
wcsubmitted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItem() {
|
||||||
|
values.push("");
|
||||||
|
values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
let v = values.slice();
|
||||||
|
|
||||||
|
// Remove empty cells
|
||||||
|
if (!flag.nb_lines) {
|
||||||
|
for (let i = v.length - 1; i > 0; i--) {
|
||||||
|
if (!v[i].length) {
|
||||||
|
v.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort cells
|
||||||
|
if (flag.ignore_order) {
|
||||||
|
v = v.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
value = v.join(flag.separator ? flag.separator : ',');
|
||||||
|
if (flag.separator) {
|
||||||
|
value += flag.separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (flag.nb_lines) {
|
||||||
|
while (values.length != flag.nb_lines) {
|
||||||
|
if (values.length > flag.nb_lines) {
|
||||||
|
values.pop();
|
||||||
|
} else {
|
||||||
|
values.push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="sol_{flag.type}{flag.id}_0">{flag.label} :</label>
|
||||||
|
{#if flag.found && flag.value}
|
||||||
|
<span>{flag.value}</span>
|
||||||
|
{/if}
|
||||||
|
{#if !flag.found}
|
||||||
|
{#each values as v, index}
|
||||||
|
<div class="input-group" class:mt-1={index != 0}>
|
||||||
|
{#if !flag.choices}
|
||||||
|
{#if !flag.multiline}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control flag"
|
||||||
|
id="sol_{flag.type}{flag.id}_{index}"
|
||||||
|
autocomplete="off"
|
||||||
|
bind:value={values[index]}
|
||||||
|
placeholder={flag.placeholder}
|
||||||
|
title={flag.placeholder}
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<textarea
|
||||||
|
class="form-control flag"
|
||||||
|
id="sol_{flag.type}{flag.id}_{index}"
|
||||||
|
autocomplete="off"
|
||||||
|
bind:value={values[index]}
|
||||||
|
placeholder="{flag.placeholder}"
|
||||||
|
title="{flag.placeholder}"
|
||||||
|
></textarea>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="sol_{flag.type}{flag.id}_{index}"
|
||||||
|
bind:value={values[index]}
|
||||||
|
>
|
||||||
|
{#each Object.keys(flag.choices) as l}
|
||||||
|
<option value={l}>{flag.choices[l]}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{/if}
|
||||||
|
{#if flag.choices_cost > 0}
|
||||||
|
<Button
|
||||||
|
color="success"
|
||||||
|
type="button"
|
||||||
|
on:click={wantchoices}
|
||||||
|
disabled={wcsubmitted}
|
||||||
|
title="Cliquez pour échanger ce champ de texte par une liste de choix. L'opération vous coûtera {flag.choices_cost * $settings.wchoiceCurrentCoefficient} points."
|
||||||
|
>
|
||||||
|
{#if wcsubmitted}
|
||||||
|
<Spinner size="sm" class="me-2" />
|
||||||
|
{/if}
|
||||||
|
<Icon name="tasks" />
|
||||||
|
Liste de propositions ({flag.choices_cost * $settings.wchoiceCurrentCoefficient} {flag.choices_cost * $settings.wchoiceCurrentCoefficient===1?"point":"points"})
|
||||||
|
</Button>
|
||||||
|
{:else if flag.separator && !flag.nb_lines && index == values.length - 1}
|
||||||
|
<Button
|
||||||
|
color="success"
|
||||||
|
type="button"
|
||||||
|
title="Ajouter un élément."
|
||||||
|
on:click={addItem}
|
||||||
|
>
|
||||||
|
<Icon name="plus" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#if flag.help}
|
||||||
|
<small class="form-text text-muted">{flag.help}</small>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<Icon
|
||||||
|
name="check"
|
||||||
|
class="form-control-feedback text-success"
|
||||||
|
aria-hidden="true"
|
||||||
|
title="Flag trouvé à {flag.found}"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
49
frontend/ui/src/components/FlagMCQ.svelte
Normal file
49
frontend/ui/src/components/FlagMCQ.svelte
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import FlagKey from './FlagKey.svelte';
|
||||||
|
|
||||||
|
export let refresh_my = null;
|
||||||
|
export let exercice_id = 0;
|
||||||
|
export let flag = { };
|
||||||
|
export let values = { };
|
||||||
|
export let justifications = { };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if flag.label}
|
||||||
|
<p class="mb-1">
|
||||||
|
{flag.label} :
|
||||||
|
{#if flag.found}
|
||||||
|
<Icon name="check" class="form-control-feedback text-success" aria-hidden="true" title="QCM réussi à {flag.solved}" />
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{#if !flag.found || flag.justify}
|
||||||
|
{#each Object.keys(flag.choices) as cid, index}
|
||||||
|
<div class="form-check ms-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="mcq_{flag.id}_{cid}" bind:checked={values[Number(cid)]} disabled={flag.found}>
|
||||||
|
<label class="form-check-label" for="mcq_{flag.id}_{cid}">
|
||||||
|
{#if typeof flag.choices[cid] == "Object"}
|
||||||
|
{flag.choices[cid].label}
|
||||||
|
{:else}
|
||||||
|
{flag.choices[cid]}
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
|
{#if values[Number(cid)] && flag.justify && (!flag.choices[cid].justification || !flag.choices[cid].justification.solved)}
|
||||||
|
<FlagKey
|
||||||
|
{exercice_id}
|
||||||
|
flag={flag.choices[cid].justification}
|
||||||
|
{refresh_my}
|
||||||
|
bind:values={justifications[flag.choices[cid].justification.id]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if flag.choices[cid].justification && flag.choices[cid].justification.solved}
|
||||||
|
<Icon name="check" class="form-control-feedback text-success" aria-hidden="true" title="Flag trouvé !" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
<hr>
|
@ -20,7 +20,7 @@
|
|||||||
messageClass = "info";
|
messageClass = "info";
|
||||||
sberr = "Votre nom d'équipe a été changé avec succès.";
|
sberr = "Votre nom d'équipe a été changé avec succès.";
|
||||||
message = "";
|
message = "";
|
||||||
} else {
|
} else if (i > 0) {
|
||||||
setTimeout(gotoHomeOnDiff, 850, i-1);
|
setTimeout(gotoHomeOnDiff, 850, i-1);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
props: {
|
props: {
|
||||||
theme: context.theme,
|
theme: context.theme,
|
||||||
exercice: exercice,
|
exercice: exercice,
|
||||||
|
refresh_my: context.refresh_my,
|
||||||
|
refresh_teams: context.refresh_teams,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -45,6 +47,9 @@
|
|||||||
|
|
||||||
export let theme;
|
export let theme;
|
||||||
export let exercice;
|
export let exercice;
|
||||||
|
|
||||||
|
export let refresh_my;
|
||||||
|
export let refresh_teams;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -147,8 +152,13 @@
|
|||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
<Col lg class="mb-5">
|
<Col lg class="mb-5">
|
||||||
{#if !$my.exercices[exercice.id].solved}
|
{#if !$my.exercices[exercice.id].solved_rank}
|
||||||
<ExerciceFlags {exercice} flags={$my.exercices[exercice.id].flags} />
|
<ExerciceFlags
|
||||||
|
{refresh_my}
|
||||||
|
{refresh_teams}
|
||||||
|
exercice={$my.exercices[exercice.id]}
|
||||||
|
flags={$my.exercices[exercice.id].flags}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<ExerciceSolved theme={theme} exercice={$my.exercices[exercice.id]} />
|
<ExerciceSolved theme={theme} exercice={$my.exercices[exercice.id]} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -38,19 +38,19 @@
|
|||||||
on:click={goto(`/${theme.urlid}/${theme.exercices[k].urlid}`)}
|
on:click={goto(`/${theme.urlid}/${theme.exercices[k].urlid}`)}
|
||||||
>
|
>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-1" style="margin-top: -0.5rem; margin-bottom: -0.5rem; text-align: right; border-right: 5px solid #{$my && $my.exercices[k] && $my.exercices[k].solved ? '62c462' : 'aaa'}">
|
<div class="col-1" style="margin-top: -0.5rem; margin-bottom: -0.5rem; text-align: right; border-right: 5px solid #{$my && $my.exercices[k] && $my.exercices[k].solved_rank ? '62c462' : 'aaa'}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<div style="position: absolute; margin-left: calc(var(--bs-gutter-x) * -.5 - 15px); margin-top: -0.5rem;">
|
<div style="position: absolute; margin-left: calc(var(--bs-gutter-x) * -.5 - 15px); margin-top: -0.5rem;">
|
||||||
<svg style="height: 50px; width: 23px;">
|
<svg style="height: 50px; width: 23px;">
|
||||||
<rect
|
<rect
|
||||||
style="fill:#{$my && $my.exercices[k] && (index < 1 || $my.exercices[Object.keys(theme.exercices)[index-1]].solved) ? '62c462' : 'aaa'}"
|
style="fill:#{$my && $my.exercices[k] && (index < 1 || $my.exercices[Object.keys(theme.exercices)[index-1]].solved_rank) ? '62c462' : 'aaa'}"
|
||||||
width="5"
|
width="5"
|
||||||
height="30"
|
height="30"
|
||||||
x="10"
|
x="10"
|
||||||
y="0" />
|
y="0" />
|
||||||
<path
|
<path
|
||||||
style="fill:#{$my && $my.exercices[k] ? ($my.exercices[k].solved ? '62c462' : (theme.exercices[k].curcoeff > 1.0 ? 'f89406' : '5bc0de')) : '555'}"
|
style="fill:#{$my && $my.exercices[k] ? ($my.exercices[k].solved_rank ? '62c462' : (theme.exercices[k].curcoeff > 1.0 ? 'f89406' : '5bc0de')) : '555'}"
|
||||||
d="m 22,20 a 9.5700617,9.5700617 0 0 1 -9.5690181,9.57006 9.5700617,9.5700617 0 0 1 -9.57110534,-9.56797 9.5700617,9.5700617 0 0 1 9.56692984,-9.57215 9.5700617,9.5700617 0 0 1 9.5731926,9.56588" />
|
d="m 22,20 a 9.5700617,9.5700617 0 0 1 -9.5690181,9.57006 9.5700617,9.5700617 0 0 1 -9.57110534,-9.56797 9.5700617,9.5700617 0 0 1 9.56692984,-9.57215 9.5700617,9.5700617 0 0 1 9.5731926,9.56588" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
refresh_my((my) => {
|
refresh_my((my) => {
|
||||||
if (my && my.team_id) {
|
if (my && my.team_id) {
|
||||||
goto('/');
|
goto('/');
|
||||||
} else {
|
} else if (i > 0) {
|
||||||
setTimeout(gotoHomeOnDiff, 650, i-1);
|
setTimeout(gotoHomeOnDiff, 650, i-1);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ export const myThemes = derived([my, themesStore], ([$my, $themesStore]) => {
|
|||||||
|
|
||||||
if ($my && $my.exercices) {
|
if ($my && $my.exercices) {
|
||||||
for (let k in $themesStore.themes[key].exercices) {
|
for (let k in $themesStore.themes[key].exercices) {
|
||||||
if ($my.exercices[k] && $my.exercices[k].solved) {
|
if ($my.exercices[k] && $my.exercices[k].solved_rank) {
|
||||||
themes[key].exercice_solved++;
|
themes[key].exercice_solved++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ export const tags = derived([my, themesStore], ([$my, $themesStore]) => {
|
|||||||
else
|
else
|
||||||
tags[tag].count += 1;
|
tags[tag].count += 1;
|
||||||
|
|
||||||
if ($my && $my.exercices && $my.exercices[k] && $my.exercices[k].solved)
|
if ($my && $my.exercices && $my.exercices[k] && $my.exercices[k].solved_rank)
|
||||||
tags[tag].solved += 1;
|
tags[tag].solved += 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user