303 lines
10 KiB
Svelte
303 lines
10 KiB
Svelte
<script>
|
||
import { onDestroy } from 'svelte';
|
||
|
||
import { user } from '$lib/stores/user';
|
||
import { ToastsStore } from '$lib/stores/toasts';
|
||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||
import QuestionForm from '$lib/components/QuestionForm.svelte';
|
||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||
import { getQuestion } from '$lib/questions';
|
||
|
||
export let data;
|
||
let survey;
|
||
$: survey = data.survey;
|
||
|
||
let ws_up = false;
|
||
let show_question = null;
|
||
let value;
|
||
|
||
let req_question;
|
||
let nosend = false;
|
||
let timer_init = null;
|
||
let timer_end = null;
|
||
let timer = 0;
|
||
let timer_cancel = null;
|
||
|
||
function afterQUpdate(q) {
|
||
value = undefined;
|
||
if (q) {
|
||
q.getMyResponse().then((response) => {
|
||
if (response && response.value)
|
||
value = response.value;
|
||
})
|
||
}
|
||
}
|
||
|
||
$: {
|
||
if (show_question) {
|
||
req_question = getQuestion(show_question);
|
||
req_question.then(afterQUpdate);
|
||
}
|
||
}
|
||
|
||
function updTimer() {
|
||
const now = new Date().getTime();
|
||
if (now > timer_end) {
|
||
timer = 100;
|
||
clearInterval(timer_cancel);
|
||
timer_cancel = null;
|
||
} else {
|
||
const dist1 = timer_end - timer_init;
|
||
const dist2 = timer_end - now;
|
||
timer = Math.ceil(100-dist2*100/dist1);
|
||
}
|
||
}
|
||
|
||
let ws = null;
|
||
let autoreconnect = true;
|
||
onDestroy(() => {
|
||
autoreconnect = false;
|
||
console.log("destroy", ws)
|
||
if (ws) {
|
||
ws.close();
|
||
}
|
||
});
|
||
function wsconnect() {
|
||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${data.sid}/ws`);
|
||
|
||
ws.addEventListener("open", () => {
|
||
ws_up = true;
|
||
});
|
||
|
||
ws.addEventListener("close", (e) => {
|
||
ws_up = false;
|
||
show_question = false;
|
||
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason, e);
|
||
if (autoreconnect && e.reason != "end")
|
||
setTimeout(function() {
|
||
wsconnect();
|
||
}, 1500);
|
||
});
|
||
|
||
ws.addEventListener("error", (err) => {
|
||
ws_up = false;
|
||
console.log('Socket closed due to error.', err.message);
|
||
});
|
||
|
||
ws.addEventListener("message", (message) => {
|
||
const data = JSON.parse(message.data);
|
||
if (data.action && data.action == "new_question") {
|
||
show_question = data.question;
|
||
survey.corrected = data.corrected;
|
||
if (data.stats) {
|
||
stats = data.stats;
|
||
} else {
|
||
stats = null;
|
||
}
|
||
if(data.corrected) {
|
||
corrections = data.corrections;
|
||
} else {
|
||
corrections = null;
|
||
}
|
||
if (timer_cancel) {
|
||
clearInterval(timer_cancel);
|
||
timer_cancel = null;
|
||
}
|
||
if (data.timer) {
|
||
timer_init = new Date().getTime();;
|
||
timer_end = timer_init + data.timer;
|
||
updTimer();
|
||
timer_cancel = setInterval(updTimer, 150);
|
||
} else {
|
||
timer_init = null;
|
||
}
|
||
|
||
} else if (data.action && data.action == "where_are_you") {
|
||
ws.send('{"action":"myscroll", "value": "' + (window.scrollY/window.scrollMaxY) +'", "question": '+show_question+', "corrected": '+(survey.corrected==true)+'}')
|
||
} else {
|
||
show_question = null;
|
||
if (timer_cancel) {
|
||
clearInterval(timer_cancel);
|
||
timer_cancel = null;
|
||
}
|
||
timer_init = null;
|
||
}
|
||
});
|
||
}
|
||
wsconnect();
|
||
|
||
let displaySendInProgress = false;
|
||
function sendValue() {
|
||
if (show_question && value && !nosend) {
|
||
displaySendInProgress = true;
|
||
survey.submitAnswers([{"id_question": show_question, "value": value}], $user.id_user).then((response) => {
|
||
setTimeout(() => displaySendInProgress = false, 150);
|
||
console.log("Vos réponses ont bien étés sauvegardées.");
|
||
}, (error) => {
|
||
displaySendInProgress = false;
|
||
value = null;
|
||
ToastsStore.addErrorToast({
|
||
msg: "Une erreur s'est produite durant l'envoi de vos réponses : " + error + "\nVeuillez réessayer dans quelques instants.",
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
let myQuestion = "";
|
||
let submitQuestionInProgress = false;
|
||
function askQuestion() {
|
||
if (!myQuestion) {
|
||
ToastsStore.addErrorToast({
|
||
msg: "Quel est ta question ?",
|
||
});
|
||
return;
|
||
}
|
||
|
||
submitQuestionInProgress = true;
|
||
fetch(`api/surveys/${survey.id}/ask`, {
|
||
method: 'POST',
|
||
headers: {'Accept': 'application/json'},
|
||
body: JSON.stringify({"content": myQuestion}),
|
||
}).then((r) => {
|
||
submitQuestionInProgress = false;
|
||
myQuestion = "";
|
||
ToastsStore.addToast({
|
||
msg: "Ta question a bien été envoyée.",
|
||
title: survey.title,
|
||
color: "success",
|
||
});
|
||
}, (error) => {
|
||
ToastsStore.addErrorToast({
|
||
msg: "Un problème est survenu : " + error.errmsg,
|
||
});
|
||
});
|
||
}
|
||
|
||
let corrections = null;
|
||
let stats = null;
|
||
</script>
|
||
|
||
<div
|
||
style={"transition: opacity 150ms ease-out; opacity: " + (displaySendInProgress?1:0)}
|
||
class="ms-2 float-end"
|
||
>
|
||
<div style="position: relative; left: 25%; top: 4px">
|
||
<div style="position: absolute">
|
||
<i class="bi bi-save"></i>
|
||
</div>
|
||
</div>
|
||
<div class="spinner-border text-primary" role="status"></div>
|
||
</div>
|
||
{#if $user && $user.is_admin}
|
||
<a href="surveys/{survey.id}/admin" class="btn btn-primary ms-1 float-end" title="Aller à l'interface d'administration"><i class="bi bi-pencil"></i></a>
|
||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
||
{/if}
|
||
<div class="d-flex align-items-center mb-3 mb-md-4 mb-lg-5">
|
||
<h2>
|
||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||
{survey.title}
|
||
</h2>
|
||
<div
|
||
class="badge rounded-pill ms-2"
|
||
class:bg-success={ws_up}
|
||
class:bg-danger={!ws_up}
|
||
>
|
||
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
||
</div>
|
||
</div>
|
||
|
||
<form on:submit|preventDefault={sendValue}>
|
||
{#if show_question}
|
||
{#await req_question}
|
||
<div class="text-center">
|
||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||
<span>Chargement d'une nouvelle question …</span>
|
||
</div>
|
||
{:then question}
|
||
{#if stats != null}
|
||
<CorrectionPieChart
|
||
{question}
|
||
proposals={true}
|
||
data={stats}
|
||
/>
|
||
{/if}
|
||
<QuestionForm
|
||
{survey}
|
||
{question}
|
||
readonly={timer >= 100 || survey.corrected}
|
||
{corrections}
|
||
bind:value={value}
|
||
on:change={sendValue}
|
||
>
|
||
{#if timer_init}
|
||
<div class="progress" style="border-radius: 0; height: 4px">
|
||
<div class="progress-bar" class:bg-warning={timer > 85 && timer < 100} class:bg-danger={timer >= 100} role="progressbar" style="width: {timer}%"></div>
|
||
</div>
|
||
{/if}
|
||
</QuestionForm>
|
||
{#if !survey.corrected && question.kind != 'mcq' && question.kind != 'ucq' && question.kind != 'none'}
|
||
<button
|
||
class="btn btn-primary"
|
||
>
|
||
Soumettre cette réponse
|
||
</button>
|
||
{/if}
|
||
{/await}
|
||
{:else if ws_up}
|
||
<h2 class="text-center mb-4">
|
||
Pas de question actuellement.
|
||
</h2>
|
||
<form on:submit|preventDefault={askQuestion}>
|
||
<div class="row">
|
||
<div class="d-none d-sm-block col-sm">
|
||
<hr>
|
||
</div>
|
||
<h3 class="col-sm-auto text-center text-muted mb-3"><label for="askquestion">Vous avez une question ?</label></h3>
|
||
<div class="d-none d-sm-block col-sm">
|
||
<hr>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="offset-md-1 col-md-10 offset-lg-2 col-lg-8 offset-xl-3 col-xl-6 mb-4">
|
||
<div class="input-group">
|
||
<!-- svelte-ignore a11y-autofocus -->
|
||
<textarea
|
||
id="askquestion"
|
||
class="form-control"
|
||
bind:value={myQuestion}
|
||
autofocus
|
||
placeholder="Remarques, soucis, choses pas claires ? Levez la main ou écrivez ici !"
|
||
></textarea>
|
||
<button
|
||
class="d-sm-none btn btn-primary"
|
||
disabled={!myQuestion || submitQuestionInProgress}
|
||
>
|
||
{#if submitQuestionInProgress}
|
||
<div class="spinner-border spinner-border-sm me-1" role="status"></div>
|
||
{/if}
|
||
Poser cette question
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{#if myQuestion}
|
||
<div class="d-none d-sm-block text-center mb-4">
|
||
<button
|
||
class="btn btn-primary"
|
||
disabled={submitQuestionInProgress}
|
||
>
|
||
{#if submitQuestionInProgress}
|
||
<div class="spinner-border spinner-border-sm me-1" role="status"></div>
|
||
{/if}
|
||
Poser cette question
|
||
</button>
|
||
</div>
|
||
{/if}
|
||
</form>
|
||
{:else}
|
||
<h2 class="text-center">
|
||
La session est terminée. <small class="text-muted">On se retrouve une prochaine fois…</small>
|
||
</h2>
|
||
{/if}
|
||
</form>
|