This repository has been archived on 2024-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
atsebay.t/ui/src/routes/surveys/[sid]/live/+page.svelte

303 lines
10 KiB
Svelte
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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">&lt;</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 &hellip;</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&nbsp;?</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&hellip;</small>
</h2>
{/if}
</form>