svelte-migrate: updated files
This commit is contained in:
parent
4d6149760d
commit
ff5a2eef65
@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed ui/build/* ui/build/css/* ui/build/surveys/* ui/build/_app/* ui/build/_app/immutable/* ui/build/_app/immutable/pages/* ui/build/_app/immutable/pages/surveys/* ui/build/_app/immutable/pages/surveys/_sid_/* ui/build/_app/immutable/pages/surveys/_sid_/responses/* ui/build/_app/immutable/pages/grades/* ui/build/_app/immutable/pages/works/* ui/build/_app/immutable/pages/works/_wid_/* ui/build/_app/immutable/pages/users/* ui/build/_app/immutable/pages/users/_uid_/* ui/build/_app/immutable/pages/users/_uid_/surveys/* ui/build/_app/immutable/chunks/* ui/build/_app/immutable/assets/* ui/build/img/* ui/build/works/*
|
||||
//go:embed all:ui/build
|
||||
var _assets embed.FS
|
||||
|
||||
var Assets http.FileSystem
|
||||
|
@ -48,7 +48,6 @@ func serveOrReverse(forced_url string) func(c *gin.Context) {
|
||||
}
|
||||
|
||||
func declareStaticRoutes(router *gin.Engine) {
|
||||
router.GET("/@fs/*_", serveOrReverse(""))
|
||||
router.GET("/", serveOrReverse(""))
|
||||
router.GET("/_app/*_", serveOrReverse(""))
|
||||
router.GET("/auth/", serveOrReverse("/"))
|
||||
@ -74,7 +73,7 @@ func declareStaticRoutes(router *gin.Engine) {
|
||||
router.GET("/.svelte-kit/*_", serveOrReverse(""))
|
||||
router.GET("/node_modules/*_", serveOrReverse(""))
|
||||
router.GET("/@vite/*_", serveOrReverse(""))
|
||||
router.GET("/__vite_ping", serveOrReverse(""))
|
||||
router.GET("/@fs/*_", serveOrReverse(""))
|
||||
router.GET("/src/*_", serveOrReverse(""))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
export async function handle({ event, resolve }) {
|
||||
const response = await resolve(event, {
|
||||
ssr: false,
|
||||
});
|
||||
return response;
|
||||
}
|
@ -8,14 +8,10 @@ function createUserStore() {
|
||||
set: (auth) => {
|
||||
update((m) => auth);
|
||||
},
|
||||
update: (res_auth, cb=null) => {
|
||||
update: (res_auth) => {
|
||||
if (res_auth.status === 200) {
|
||||
res_auth.json().then((auth) => {
|
||||
update((m) => (Object.assign(m?m:{}, auth)));
|
||||
|
||||
if (cb) {
|
||||
cb(my);
|
||||
}
|
||||
});
|
||||
} else if (res_auth.status >= 400 && res_auth.status < 500) {
|
||||
update((m) => (null));
|
||||
@ -24,4 +20,14 @@ function createUserStore() {
|
||||
};
|
||||
}
|
||||
|
||||
export async function refresh_auth() {
|
||||
const res = await fetch('api/auth', {headers: {'Accept': 'application/json'}})
|
||||
if (res.status == 200) {
|
||||
const auth = await res.json();
|
||||
user.set(auth);
|
||||
} else {
|
||||
user.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
export const user = createUserStore();
|
||||
|
16
ui/src/routes/+layout.js
Normal file
16
ui/src/routes/+layout.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { refresh_auth, user } from '$lib/stores/user';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
let refresh_interval_auth = null;
|
||||
|
||||
export async function load({ url }) {
|
||||
refresh_interval_auth = setInterval(refresh_auth, Math.floor(Math.random() * 200000) + 200000);
|
||||
refresh_auth();
|
||||
|
||||
const rroutes = url.pathname.split('/');
|
||||
|
||||
return {
|
||||
rroute: rroutes.length>1?rroutes[1]:'',
|
||||
};
|
||||
}
|
@ -1,51 +1,9 @@
|
||||
<script context="module">
|
||||
import { user } from '$lib/stores/user';
|
||||
let stop_refresh = false;
|
||||
|
||||
let refresh_interval_auth = null;
|
||||
async function refresh_auth(cb=null, interval=null) {
|
||||
if (refresh_interval_auth)
|
||||
clearInterval(refresh_interval_auth);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 200000) + 200000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_auth = setInterval(refresh_auth, interval);
|
||||
|
||||
const res = await fetch('api/auth', {headers: {'Accept': 'application/json'}})
|
||||
if (res.status == 200) {
|
||||
const auth = await res.json();
|
||||
user.set(auth);
|
||||
} else {
|
||||
user.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
export async function load({ props, stuff, url }) {
|
||||
refresh_auth();
|
||||
|
||||
const rroutes = url.pathname.split('/');
|
||||
|
||||
return {
|
||||
props: {
|
||||
...props,
|
||||
rroute: rroutes.length>1?rroutes[1]:'',
|
||||
},
|
||||
stuff: {
|
||||
...stuff,
|
||||
refresh_auth,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import AuthButton from '$lib/components/AuthButton.svelte';
|
||||
import Toaster from '$lib/components/Toaster.svelte';
|
||||
import { refresh_auth, user } from '$lib/stores/user';
|
||||
|
||||
export let rroute = '';
|
||||
export let data;
|
||||
|
||||
function switchAdminMode() {
|
||||
var tmp = $user.is_admin;
|
||||
@ -94,17 +52,17 @@
|
||||
</li>
|
||||
{/if}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" class:active={rroute === 'surveys'} href="surveys">
|
||||
<a class="nav-link" class:active={data.rroute === 'surveys'} href="surveys">
|
||||
Questionnaires
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" class:active={rroute === 'works'} href="works">
|
||||
<a class="nav-link" class:active={data.rroute === 'works'} href="works">
|
||||
Travaux
|
||||
</a>
|
||||
</li>
|
||||
{#if $user && $user.is_admin}
|
||||
<li class="nav-item"><a class="nav-link" class:active={rroute === 'users'} href="users">Étudiants</a></li>
|
||||
<li class="nav-item"><a class="nav-link" class:active={data.rroute === 'users'} href="users">Étudiants</a></li>
|
||||
{/if}
|
||||
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
||||
</ul>
|
||||
@ -129,9 +87,9 @@
|
||||
<img class="rounded-circle" src="//photos.cri.epita.fr/square/{$user.login}" alt="Menu" style="margin: -0.75em 0; max-height: 2.5em; border: 2px solid white;">
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" class:active={rroute === 'keys'} href="keys">Clef PGP</a></li>
|
||||
<li><a class="dropdown-item" class:active={rroute === 'help'} href="help">Besoin d'aide ?</a></li>
|
||||
<li><a class="dropdown-item" class:active={rroute === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
||||
<li><a class="dropdown-item" class:active={data.rroute === 'keys'} href="keys">Clef PGP</a></li>
|
||||
<li><a class="dropdown-item" class:active={data.rroute === 'help'} href="help">Besoin d'aide ?</a></li>
|
||||
<li><a class="dropdown-item" class:active={data.rroute === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<button class="dropdown-item" on:click={disconnectCurrentUser}>
|
||||
|
5
ui/src/routes/categories/[cid]/+page.js
Normal file
5
ui/src/routes/categories/[cid]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
cid: params.cid,
|
||||
};
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
cid: params.cid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -17,12 +5,10 @@
|
||||
import CategoryAdmin from '$lib/components/CategoryAdmin.svelte';
|
||||
import { Category, getCategory } from '$lib/categories';
|
||||
|
||||
export let cid;
|
||||
export let data;
|
||||
|
||||
let categoryP = null;
|
||||
$: {
|
||||
categoryP = getCategory(cid);
|
||||
}
|
||||
$: categoryP = getCategory(data.cid);
|
||||
</script>
|
||||
|
||||
{#await categoryP then category}
|
||||
|
5
ui/src/routes/grades/[promo]/+page.js
Normal file
5
ui/src/routes/grades/[promo]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
promo: params.promo,
|
||||
};
|
||||
}
|
@ -1,17 +1,7 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
promo: params.promo,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import StudentGrades from '$lib/components/StudentGrades.svelte';
|
||||
|
||||
export let promo;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<StudentGrades {promo} />
|
||||
<StudentGrades promo={data.promo} />
|
||||
|
7
ui/src/routes/results/+page.js
Normal file
7
ui/src/routes/results/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ url }) {
|
||||
return {
|
||||
secret: url.searchParams.get("secret"),
|
||||
idsurvey: url.searchParams.get("survey"),
|
||||
exportview_list: url.searchParams.get("graph_list")?false:true,
|
||||
};
|
||||
}
|
@ -1,26 +1,13 @@
|
||||
<script context="module">
|
||||
export async function load({ url }) {
|
||||
return {
|
||||
props: {
|
||||
secret: url.searchParams.get("secret"),
|
||||
idsurvey: url.searchParams.get("survey"),
|
||||
exportview_list: url.searchParams.get("graph_list")?false:true,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { getSharedQuestions } from '$lib/questions';
|
||||
import { getSharedSurvey } from '$lib/surveys';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||
|
||||
export let secret;
|
||||
export let idsurvey;
|
||||
export let data;
|
||||
|
||||
let surveyP = getSharedSurvey(idsurvey, secret);
|
||||
export let exportview_list = true;
|
||||
let surveyP = null;
|
||||
$: surveyP = getSharedSurvey(data.idsurvey, data.secret);
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
@ -32,7 +19,7 @@
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
|
||||
{#await getSharedQuestions(survey.id, secret)}
|
||||
{#await getSharedQuestions(survey.id, data.secret)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
@ -40,8 +27,8 @@
|
||||
{:then questions}
|
||||
{#each questions as question (question.id)}
|
||||
<h3>{question.title}</h3>
|
||||
{#if question.kind == "text" || (exportview_list && question.kind.indexOf("list") == 0)}
|
||||
{#await question.getResponses(secret) then responses}
|
||||
{#if question.kind == "text" || (data.exportview_list && question.kind.indexOf("list") == 0)}
|
||||
{#await question.getResponses(data.secret) then responses}
|
||||
{#each responses as response (response.id)}
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
@ -53,7 +40,7 @@
|
||||
{/each}
|
||||
{/await}
|
||||
{:else}
|
||||
<CorrectionPieChart {question} {secret} />
|
||||
<CorrectionPieChart {question} secret={data.secret} />
|
||||
{/if}
|
||||
<hr class="mb-3">
|
||||
{/each}
|
||||
|
9
ui/src/routes/surveys/[sid]/+layout.js
Normal file
9
ui/src/routes/surveys/[sid]/+layout.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params }) {
|
||||
const survey = getSurvey(params.sid);
|
||||
|
||||
return {
|
||||
survey,
|
||||
};
|
||||
}
|
@ -1,27 +1,8 @@
|
||||
<script context="module">
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
const survey = getSurvey(params.sid);
|
||||
|
||||
return {
|
||||
props: {
|
||||
survey,
|
||||
},
|
||||
stuff: {
|
||||
...stuff,
|
||||
survey,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export let survey;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await survey}
|
||||
{#await data.survey}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement du questionnaire …</span>
|
||||
|
5
ui/src/routes/surveys/[sid]/+page.js
Normal file
5
ui/src/routes/surveys/[sid]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return stuff;
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -19,62 +7,58 @@
|
||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||
import { getQuestions } from '$lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let data;
|
||||
let survey = null;
|
||||
|
||||
$: {
|
||||
if (surveyP) {
|
||||
surveyP.then((survey) => {
|
||||
if (survey.direct && !$user.is_admin) {
|
||||
goto(`surveys/${survey.id}/live`);
|
||||
}
|
||||
})
|
||||
survey = data.survey;
|
||||
if (survey.direct && !$user.is_admin) {
|
||||
goto(`surveys/${survey.id}/live`);
|
||||
}
|
||||
}
|
||||
|
||||
let edit = false;
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<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 survey.direct}
|
||||
<a href="surveys/{survey.id}/live" class="btn btn-danger ms-1 float-end" title="Aller au direct"><i class="bi bi-film"></i></a>
|
||||
{/if}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<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 survey.direct}
|
||||
<a href="surveys/{survey.id}/live" class="btn btn-danger ms-1 float-end" title="Aller au direct"><i class="bi bi-film"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
||||
{/if}
|
||||
|
||||
{#await getQuestions(survey.id)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
||||
{:then questions}
|
||||
<SurveyQuestions {survey} {questions} />
|
||||
{:catch error}
|
||||
<div class="row mt-5">
|
||||
<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">Ce questionnaire n'est pas accessible</label></h3>
|
||||
<div class="d-none d-sm-block col-sm">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
{#if survey.direct != null}
|
||||
<div class="alert alert-warning">
|
||||
<strong><a href="surveys/{survey.id}/live">Cliquez ici pour accéder au direct</a>.</strong> Il s'agit d'un questionnaire en direct, le questionnaire n'est pas accessible sur cette page.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#await getQuestions(survey.id)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
<SurveyQuestions {survey} {questions} />
|
||||
{:catch error}
|
||||
<div class="row mt-5">
|
||||
<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">Ce questionnaire n'est pas accessible</label></h3>
|
||||
<div class="d-none d-sm-block col-sm">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
{#if survey.direct != null}
|
||||
<div class="alert alert-warning">
|
||||
<strong><a href="surveys/{survey.id}/live">Cliquez ici pour accéder au direct</a>.</strong> Il s'agit d'un questionnaire en direct, le questionnaire n'est pas accessible sur cette page.
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/await}
|
||||
|
8
ui/src/routes/surveys/[sid]/admin/+page.js
Normal file
8
ui/src/routes/surveys/[sid]/admin/+page.js
Normal file
@ -0,0 +1,8 @@
|
||||
export async function load({ parent, params }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
sid: params.sid,
|
||||
};
|
||||
}
|
@ -1,14 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
sid: params.sid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { user } from '$lib/stores/user';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
@ -21,28 +10,24 @@
|
||||
import { getQuestion, getQuestions, Question } from '$lib/questions';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export let surveyP;
|
||||
export let sid;
|
||||
export let data;
|
||||
let survey;
|
||||
let req_questions;
|
||||
|
||||
surveyP.then((s) => {
|
||||
survey = s;
|
||||
$: {
|
||||
survey = data.survey;
|
||||
updateQuestions();
|
||||
if (survey.direct !== null) {
|
||||
wsconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSurvey() {
|
||||
surveyP = getSurvey(survey.id);
|
||||
surveyP.then((s) => {
|
||||
survey = s;
|
||||
updateQuestions();
|
||||
if (survey.direct !== null) {
|
||||
wsconnect();
|
||||
}
|
||||
});
|
||||
async function updateSurvey() {
|
||||
survey = await getSurvey(survey.id);
|
||||
updateQuestions();
|
||||
if (survey.direct !== null) {
|
||||
wsconnect();
|
||||
}
|
||||
}
|
||||
|
||||
function updateQuestions() {
|
||||
@ -162,7 +147,7 @@
|
||||
function wsconnect() {
|
||||
if (ws !== null) return;
|
||||
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws-admin`);
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${data.sid}/ws-admin`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws_up = true;
|
||||
@ -233,472 +218,469 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
<StartStopLiveSurvey
|
||||
{survey}
|
||||
class="ms-1 float-end"
|
||||
on:update={() => updateSurvey()}
|
||||
on:end={() => { if (confirm("Sûr ?")) ws.send('{"action":"end"}') }}
|
||||
/>
|
||||
<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">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">
|
||||
Administration
|
||||
</small>
|
||||
{#if asks.length}
|
||||
<a href="surveys/{sid}/admin#questions_part">
|
||||
<i class="bi bi-patch-question-fill text-danger"></i>
|
||||
</a>
|
||||
{/if}
|
||||
</h2>
|
||||
{#if survey.direct !== null}
|
||||
<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>
|
||||
{:else}
|
||||
<SurveyBadge
|
||||
class="mx-2"
|
||||
{survey}
|
||||
/>
|
||||
{#if $user && $user.is_admin}
|
||||
<StartStopLiveSurvey
|
||||
{survey}
|
||||
class="ms-1 float-end"
|
||||
on:update={() => updateSurvey()}
|
||||
on:end={() => { if (confirm("Sûr ?")) ws.send('{"action":"end"}') }}
|
||||
/>
|
||||
<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">
|
||||
<h2>
|
||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">
|
||||
Administration
|
||||
</small>
|
||||
{#if asks.length}
|
||||
<a href="surveys/{data.sid}/admin#questions_part">
|
||||
<i class="bi bi-patch-question-fill text-danger"></i>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if survey.direct === null}
|
||||
<SurveyAdmin
|
||||
{survey}
|
||||
on:saved={updateSurvey}
|
||||
/>
|
||||
</h2>
|
||||
{#if survey.direct !== null}
|
||||
<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>
|
||||
{:else}
|
||||
{#await req_questions}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
<div class="card my-3">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<SurveyBadge
|
||||
class="mx-2"
|
||||
{survey}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if survey.direct === null}
|
||||
<SurveyAdmin
|
||||
{survey}
|
||||
on:saved={updateSurvey}
|
||||
/>
|
||||
{:else}
|
||||
{#await req_questions}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
<div class="card my-3">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Question
|
||||
{#if timer_end}
|
||||
<div class="input-group input-group-sm float-end" style="max-width: 150px;">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
disabled
|
||||
value={timer_remain}
|
||||
>
|
||||
<span class="input-group-text">s</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="input-group input-group-sm float-end" style="max-width: 150px;">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
bind:value={timer}
|
||||
placeholder="Valeur du timer"
|
||||
>
|
||||
<span class="input-group-text">s</span>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1"
|
||||
on:click={updateQuestions}
|
||||
title="Rafraîchir les questions"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
Réponses
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
disabled={!current_question || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"pause"}')} }
|
||||
title="Passer sur une scène sans question"
|
||||
>
|
||||
<i class="bi bi-pause-fill"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
class:btn-outline-success={!next_corrected}
|
||||
class:btn-success={next_corrected}
|
||||
on:click={() => { next_corrected = !next_corrected } }
|
||||
title="La prochaine question est affichée corrigée"
|
||||
>
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info"
|
||||
on:click={() => { edit_question = new Question({ id_survey: survey.id }) } }
|
||||
title="Ajouter une question"
|
||||
>
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
on:click={() => { fetch('api/cache', {method: 'DELETE'}) } }
|
||||
title="Vider les caches"
|
||||
>
|
||||
<i class="bi bi-bandaid-fill"></i>
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each questions as question (question.id)}
|
||||
<tr>
|
||||
<th>
|
||||
Question
|
||||
{#if timer_end}
|
||||
<div class="input-group input-group-sm float-end" style="max-width: 150px;">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
disabled
|
||||
value={timer_remain}
|
||||
>
|
||||
<span class="input-group-text">s</span>
|
||||
</div>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
<a href="surveys/{data.sid}/admin#q{question.id}_res">
|
||||
{question.title}
|
||||
</a>
|
||||
{:else}
|
||||
<div class="input-group input-group-sm float-end" style="max-width: 150px;">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
bind:value={timer}
|
||||
placeholder="Valeur du timer"
|
||||
>
|
||||
<span class="input-group-text">s</span>
|
||||
</div>
|
||||
{question.title}
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1"
|
||||
on:click={updateQuestions}
|
||||
title="Rafraîchir les questions"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
</button>
|
||||
</th>
|
||||
<th>
|
||||
Réponses
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary"
|
||||
disabled={!current_question || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"pause"}')} }
|
||||
title="Passer sur une scène sans question"
|
||||
>
|
||||
<i class="bi bi-pause-fill"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
{Object.keys(responses[question.id]).length}
|
||||
{:else}
|
||||
0
|
||||
{/if}
|
||||
{#if wsstats}/ {wsstats.nb_clients}{/if}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
class:btn-outline-success={!next_corrected}
|
||||
class:btn-primary={!next_corrected}
|
||||
class:btn-success={next_corrected}
|
||||
on:click={() => { next_corrected = !next_corrected } }
|
||||
title="La prochaine question est affichée corrigée"
|
||||
disabled={(question.id === current_question && next_corrected == corrected) || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"new_question", "corrected": ' + next_corrected + ', "timer": 0, "question":' + question.id + '}')} }
|
||||
>
|
||||
<i class="bi bi-eye"></i>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-danger"
|
||||
disabled={question.id === current_question || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"new_question", "corrected": ' + next_corrected + ', "timer": ' + timer * 1000 + ',"question":' + question.id + '}')} }
|
||||
>
|
||||
<i class="bi bi-stopwatch-fill"></i>
|
||||
</button>
|
||||
<a
|
||||
href="/surveys/{survey.id}/responses/{question.id}"
|
||||
target="_blank"
|
||||
type="button"
|
||||
class="btn btn-sm btn-success"
|
||||
>
|
||||
<i class="bi bi-files"></i>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info"
|
||||
on:click={() => { edit_question = new Question({ id_survey: survey.id }) } }
|
||||
title="Ajouter une question"
|
||||
disabled={question.id === current_question}
|
||||
on:click={() => { getQuestion(question.id).then((q) => {edit_question = q})} }
|
||||
>
|
||||
<i class="bi bi-plus"></i>
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
on:click={() => { fetch('api/cache', {method: 'DELETE'}) } }
|
||||
title="Vider les caches"
|
||||
>
|
||||
<i class="bi bi-bandaid-fill"></i>
|
||||
</button>
|
||||
</th>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each questions as question (question.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
<a href="surveys/{sid}/admin#q{question.id}_res">
|
||||
{question.title}
|
||||
</a>
|
||||
{:else}
|
||||
{question.title}
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if responses[question.id]}
|
||||
{Object.keys(responses[question.id]).length}
|
||||
{:else}
|
||||
0
|
||||
{/if}
|
||||
{#if wsstats}/ {wsstats.nb_clients}{/if}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
class:btn-primary={!next_corrected}
|
||||
class:btn-success={next_corrected}
|
||||
disabled={(question.id === current_question && next_corrected == corrected) || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"new_question", "corrected": ' + next_corrected + ', "timer": 0, "question":' + question.id + '}')} }
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-danger"
|
||||
disabled={question.id === current_question || !ws_up}
|
||||
on:click={() => { ws.send('{"action":"new_question", "corrected": ' + next_corrected + ', "timer": ' + timer * 1000 + ',"question":' + question.id + '}')} }
|
||||
>
|
||||
<i class="bi bi-stopwatch-fill"></i>
|
||||
</button>
|
||||
<a
|
||||
href="/surveys/{survey.id}/responses/{question.id}"
|
||||
target="_blank"
|
||||
type="button"
|
||||
class="btn btn-sm btn-success"
|
||||
>
|
||||
<i class="bi bi-files"></i>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info"
|
||||
disabled={question.id === current_question}
|
||||
on:click={() => { getQuestion(question.id).then((q) => {edit_question = q})} }
|
||||
>
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{#if edit_question !== null}
|
||||
<QuestionForm
|
||||
{survey}
|
||||
edit
|
||||
question={edit_question}
|
||||
on:delete={() => deleteQuestion(edit_question)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{#if edit_question !== null}
|
||||
<QuestionForm
|
||||
{survey}
|
||||
edit
|
||||
question={edit_question}
|
||||
on:delete={() => deleteQuestion(edit_question)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_asks", "value": ""}'); asks = []; }}
|
||||
title="Rafraîchir les réponses"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-question-diamond"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-light ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_asks", "value": "unanswered"}'); asks = []; }}
|
||||
title="Rafraîchir les réponses, en rapportant les réponses déjà répondues"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-question-diamond"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success float-end"
|
||||
title="Tout marqué comme répondu"
|
||||
on:click={() => { ws.send('{"action":"mark_answered", "value": "all"}'); asks = [] }}
|
||||
>
|
||||
<i class="bi bi-check-all"></i>
|
||||
</button>
|
||||
<h3 id="questions_part">
|
||||
Questions
|
||||
{#if asks.length}
|
||||
<small class="text-muted">
|
||||
{asks.length} question{#if asks.length > 1}s{/if}
|
||||
</small>
|
||||
{/if}
|
||||
</h3>
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_asks", "value": ""}'); asks = []; }}
|
||||
title="Rafraîchir les réponses"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-question-diamond"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-light ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_asks", "value": "unanswered"}'); asks = []; }}
|
||||
title="Rafraîchir les réponses, en rapportant les réponses déjà répondues"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-question-diamond"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success float-end"
|
||||
title="Tout marqué comme répondu"
|
||||
on:click={() => { ws.send('{"action":"mark_answered", "value": "all"}'); asks = [] }}
|
||||
>
|
||||
<i class="bi bi-check-all"></i>
|
||||
</button>
|
||||
<h3 id="questions_part">
|
||||
Questions
|
||||
{#if asks.length}
|
||||
{#each asks as ask (ask.id)}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
{ask.content}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success float-end"
|
||||
title="Marqué comme répondu"
|
||||
on:click={() => { ws.send('{"action":"mark_answered", "question": ' + ask.id + '}'); asks = asks.filter((e) => e.id != ask.id) }}
|
||||
>
|
||||
<i class="bi bi-check"></i>
|
||||
</button>
|
||||
Par
|
||||
<a href="users/{ask.userid}" target="_blank">
|
||||
{#if users && users[ask.userid]}
|
||||
{users[ask.userid].login}
|
||||
{:else}
|
||||
{ask.userid}
|
||||
{/if}
|
||||
</a>
|
||||
<small class="text-muted">
|
||||
{asks.length} question{#if asks.length > 1}s{/if}
|
||||
</small>
|
||||
{/if}
|
||||
</h3>
|
||||
{#if asks.length}
|
||||
{#each asks as ask (ask.id)}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
{ask.content}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success float-end"
|
||||
title="Marqué comme répondu"
|
||||
on:click={() => { ws.send('{"action":"mark_answered", "question": ' + ask.id + '}'); asks = asks.filter((e) => e.id != ask.id) }}
|
||||
>
|
||||
<i class="bi bi-check"></i>
|
||||
</button>
|
||||
Par
|
||||
<a href="users/{ask.userid}" target="_blank">
|
||||
{#if users && users[ask.userid]}
|
||||
{users[ask.userid].login}
|
||||
{:else}
|
||||
{ask.userid}
|
||||
{/if}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="text-center text-muted">
|
||||
Pas de question pour l'instant.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_responses"}') }}
|
||||
title="Rafraîchir les réponses"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-card-checklist"></i>
|
||||
</button>
|
||||
<h3>
|
||||
Réponses
|
||||
</h3>
|
||||
{#if Object.keys(responses).length}
|
||||
{#each Object.keys(responses) as q, qid (qid)}
|
||||
{#await req_questions then questions}
|
||||
{#each questions as question}
|
||||
{#if question.id == q}
|
||||
<h4 id="q{question.id}_res">
|
||||
{question.title}
|
||||
</h4>
|
||||
{#if question.kind == 'ucq'}
|
||||
{#await question.getProposals()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des propositions …</span>
|
||||
</div>
|
||||
{:then proposals}
|
||||
{#if current_question == question.id}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
{proposals}
|
||||
data={graph_data}
|
||||
/>
|
||||
{:else}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
/>
|
||||
{/if}
|
||||
<div class="card mb-4">
|
||||
<table class="table table-sm table-striped table-hover mb-0">
|
||||
<tbody>
|
||||
{#each proposals as proposal (proposal.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{proposal.label}
|
||||
</td>
|
||||
<td>
|
||||
{responsesbyid[q].filter((e) => e == proposal.id.toString()).length}/{responsesbyid[q].length}
|
||||
</td>
|
||||
<td>
|
||||
{Math.trunc(responsesbyid[q].filter((e) => e == proposal.id.toString()).length / responsesbyid[q].length * 1000)/10} %
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if question.kind == 'mcq'}
|
||||
{#await question.getProposals()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des propositions …</span>
|
||||
</div>
|
||||
{:then proposals}
|
||||
{#if current_question == question.id}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
{proposals}
|
||||
data={graph_data}
|
||||
/>
|
||||
{:else}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
/>
|
||||
{/if}
|
||||
<div class="card mb-4">
|
||||
<table class="table table-sm table-striped table-hover mb-0">
|
||||
<tbody>
|
||||
{#each proposals as proposal (proposal.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{proposal.label}
|
||||
</td>
|
||||
<td>
|
||||
{responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length}/{responsesbyid[q].length}
|
||||
</td>
|
||||
<td>
|
||||
{Math.trunc(responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length / responsesbyid[q].length * 1000)/10} %
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if question.kind && question.kind.startsWith('list')}
|
||||
<ListInputResponses
|
||||
responses={responses[q]}
|
||||
{users}
|
||||
/>
|
||||
{:else}
|
||||
<div class="card mb-4">
|
||||
<ul class="list-group list-group-flush">
|
||||
{#each Object.keys(responses[q]) as user, rid (rid)}
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{responses[q][user]}
|
||||
</span>
|
||||
<a href="users/{user}" target="_blank" class="badge bg-dark rounded-pill">
|
||||
{#if users && users[user]}
|
||||
{users[user].login}
|
||||
{:else}
|
||||
{user}
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{/await}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_stats"}') }}
|
||||
title="Rafraîchir les stats"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-123"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary ms-1 float-end"
|
||||
title="Rafraîchir la liste des utilisateurs"
|
||||
on:click={updateUsers}
|
||||
>
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
<i class="bi bi-people"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-warning ms-1 float-end"
|
||||
on:click={() => { scroll_states = {}; ws.send('{"action":"where_are_you"}')} }
|
||||
title="Rapporter l'avancement"
|
||||
>
|
||||
<i class="bi bi-geo-fill"></i>
|
||||
</button>
|
||||
<h3 id="users">
|
||||
Connectés
|
||||
{#if wsstats}
|
||||
<small class="text-muted">{wsstats.nb_clients} utilisateurs</small>
|
||||
{/if}
|
||||
{#if scroll_mean}
|
||||
<small
|
||||
class:text-danger={scroll_mean >= 0 && scroll_mean < 0.2}
|
||||
class:text-warning={scroll_mean >= 0.2 && scroll_mean < 0.6}
|
||||
class:text-info={scroll_mean >= 0.6 && scroll_mean < 0.9}
|
||||
class:text-success={scroll_mean >= 0.9}
|
||||
>Avancement global : {Math.trunc(scroll_mean*10000)/100} %</small>
|
||||
{/if}
|
||||
</h3>
|
||||
{#if wsstats && wsstats.users}
|
||||
<div class="row row-cols-5 py-3">
|
||||
{#each wsstats.users as user, lid (lid)}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<img alt="{user.login}" src="//photos.cri.epita.fr/thumb/{user.login}" class="card-img-top">
|
||||
<div class="card-footer text-center text-truncate p-0">
|
||||
<a href="users/{user.login}" target="_blank">
|
||||
{user.login}
|
||||
</a>
|
||||
</div>
|
||||
{#if user.myscroll != null}
|
||||
<div
|
||||
class="card-footer py-0 px-1"
|
||||
class:bg-danger={user.myscroll >= 0 && user.myscroll < 0.2}
|
||||
class:bg-warning={user.myscroll >= 0.2 && user.myscroll < 0.6}
|
||||
class:bg-info={user.myscroll >= 0.6 && user.myscroll < 0.9}
|
||||
class:bg-success={user.myscroll >= 0.9}
|
||||
>
|
||||
Avancement : {Math.trunc(user.myscroll*10000)/100} %
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="text-center text-muted">
|
||||
Pas de question pour l'instant.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_responses"}') }}
|
||||
title="Rafraîchir les réponses"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-card-checklist"></i>
|
||||
</button>
|
||||
<h3>
|
||||
Réponses
|
||||
</h3>
|
||||
{#if Object.keys(responses).length}
|
||||
{#each Object.keys(responses) as q, qid (qid)}
|
||||
{#await req_questions then questions}
|
||||
{#each questions as question}
|
||||
{#if question.id == q}
|
||||
<h4 id="q{question.id}_res">
|
||||
{question.title}
|
||||
</h4>
|
||||
{#if question.kind == 'ucq'}
|
||||
{#await question.getProposals()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des propositions …</span>
|
||||
</div>
|
||||
{:then proposals}
|
||||
{#if current_question == question.id}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
{proposals}
|
||||
data={graph_data}
|
||||
/>
|
||||
{:else}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
/>
|
||||
{/if}
|
||||
<div class="card mb-4">
|
||||
<table class="table table-sm table-striped table-hover mb-0">
|
||||
<tbody>
|
||||
{#each proposals as proposal (proposal.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{proposal.label}
|
||||
</td>
|
||||
<td>
|
||||
{responsesbyid[q].filter((e) => e == proposal.id.toString()).length}/{responsesbyid[q].length}
|
||||
</td>
|
||||
<td>
|
||||
{Math.trunc(responsesbyid[q].filter((e) => e == proposal.id.toString()).length / responsesbyid[q].length * 1000)/10} %
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if question.kind == 'mcq'}
|
||||
{#await question.getProposals()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des propositions …</span>
|
||||
</div>
|
||||
{:then proposals}
|
||||
{#if current_question == question.id}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
{proposals}
|
||||
data={graph_data}
|
||||
/>
|
||||
{:else}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
/>
|
||||
{/if}
|
||||
<div class="card mb-4">
|
||||
<table class="table table-sm table-striped table-hover mb-0">
|
||||
<tbody>
|
||||
{#each proposals as proposal (proposal.id)}
|
||||
<tr>
|
||||
<td>
|
||||
{proposal.label}
|
||||
</td>
|
||||
<td>
|
||||
{responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length}/{responsesbyid[q].length}
|
||||
</td>
|
||||
<td>
|
||||
{Math.trunc(responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length / responsesbyid[q].length * 1000)/10} %
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if question.kind && question.kind.startsWith('list')}
|
||||
<ListInputResponses
|
||||
responses={responses[q]}
|
||||
{users}
|
||||
/>
|
||||
{:else}
|
||||
<div class="card mb-4">
|
||||
<ul class="list-group list-group-flush">
|
||||
{#each Object.keys(responses[q]) as user, rid (rid)}
|
||||
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
{responses[q][user]}
|
||||
</span>
|
||||
<a href="users/{user}" target="_blank" class="badge bg-dark rounded-pill">
|
||||
{#if users && users[user]}
|
||||
{users[user].login}
|
||||
{:else}
|
||||
{user}
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{/await}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info ms-1 float-end"
|
||||
on:click={() => { ws.send('{"action":"get_stats"}') }}
|
||||
title="Rafraîchir les stats"
|
||||
>
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
<i class="bi bi-123"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-primary ms-1 float-end"
|
||||
title="Rafraîchir la liste des utilisateurs"
|
||||
on:click={updateUsers}
|
||||
>
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
<i class="bi bi-people"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-warning ms-1 float-end"
|
||||
on:click={() => { scroll_states = {}; ws.send('{"action":"where_are_you"}')} }
|
||||
title="Rapporter l'avancement"
|
||||
>
|
||||
<i class="bi bi-geo-fill"></i>
|
||||
</button>
|
||||
<h3 id="users">
|
||||
Connectés
|
||||
{#if wsstats}
|
||||
<small class="text-muted">{wsstats.nb_clients} utilisateurs</small>
|
||||
{/if}
|
||||
{#if scroll_mean}
|
||||
<small
|
||||
class:text-danger={scroll_mean >= 0 && scroll_mean < 0.2}
|
||||
class:text-warning={scroll_mean >= 0.2 && scroll_mean < 0.6}
|
||||
class:text-info={scroll_mean >= 0.6 && scroll_mean < 0.9}
|
||||
class:text-success={scroll_mean >= 0.9}
|
||||
>Avancement global : {Math.trunc(scroll_mean*10000)/100} %</small>
|
||||
{/if}
|
||||
</h3>
|
||||
{#if wsstats && wsstats.users}
|
||||
<div class="row row-cols-5 py-3">
|
||||
{#each wsstats.users as user, lid (lid)}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<img alt="{user.login}" src="//photos.cri.epita.fr/thumb/{user.login}" class="card-img-top">
|
||||
<div class="card-footer text-center text-truncate p-0">
|
||||
<a href="users/{user.login}" target="_blank">
|
||||
{user.login}
|
||||
</a>
|
||||
</div>
|
||||
{#if user.myscroll != null}
|
||||
<div
|
||||
class="card-footer py-0 px-1"
|
||||
class:bg-danger={user.myscroll >= 0 && user.myscroll < 0.2}
|
||||
class:bg-warning={user.myscroll >= 0.2 && user.myscroll < 0.6}
|
||||
class:bg-info={user.myscroll >= 0.6 && user.myscroll < 0.9}
|
||||
class:bg-success={user.myscroll >= 0.9}
|
||||
>
|
||||
Avancement : {Math.trunc(user.myscroll*10000)/100} %
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{/await}
|
||||
{/if}
|
||||
|
8
ui/src/routes/surveys/[sid]/live/+page.js
Normal file
8
ui/src/routes/surveys/[sid]/live/+page.js
Normal file
@ -0,0 +1,8 @@
|
||||
export async function load({ params, parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
sid: params.sid,
|
||||
};
|
||||
}
|
@ -1,14 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
sid: params.sid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
@ -18,11 +7,9 @@
|
||||
import QuestionForm from '$lib/components/QuestionForm.svelte';
|
||||
import { getQuestion } from '$lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let sid;
|
||||
export let data;
|
||||
let survey;
|
||||
|
||||
surveyP.then((s) => survey = s);
|
||||
$: survey = data.survey;
|
||||
|
||||
let ws_up = false;
|
||||
let show_question = null;
|
||||
@ -75,7 +62,7 @@
|
||||
}
|
||||
});
|
||||
function wsconnect() {
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws`);
|
||||
ws = new WebSocket((window.location.protocol == 'https:'?'wss://':'ws://') + window.location.host + `/api/surveys/${data.sid}/ws`);
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws_up = true;
|
||||
@ -183,120 +170,118 @@
|
||||
let corrections = null;
|
||||
</script>
|
||||
|
||||
{#await surveyP then unused}
|
||||
<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
|
||||
style={"transition: opacity 150ms ease-out; opacity: " + (displaySendInProgress?1:0)}
|
||||
class="ms-2 float-end"
|
||||
class="badge rounded-pill ms-2"
|
||||
class:bg-success={ws_up}
|
||||
class:bg-danger={!ws_up}
|
||||
>
|
||||
<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>
|
||||
{#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}
|
||||
<QuestionForm
|
||||
{survey}
|
||||
{question}
|
||||
readonly={timer >= 100 || survey.corrected}
|
||||
{corrections}
|
||||
bind:value={value}
|
||||
on:change={sendValue}
|
||||
<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}
|
||||
<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 question.kind != 'mcq' && question.kind != 'ucq' && question.kind != 'none'}
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
>
|
||||
{#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 question.kind != 'mcq' && question.kind != 'ucq' && question.kind != 'none'}
|
||||
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">
|
||||
<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}
|
||||
>
|
||||
Soumettre cette réponse
|
||||
{#if submitQuestionInProgress}
|
||||
<div class="spinner-border spinner-border-sm me-1" role="status"></div>
|
||||
{/if}
|
||||
Poser cette question
|
||||
</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">
|
||||
<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>
|
||||
{/await}
|
||||
{/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>
|
||||
|
7
ui/src/routes/surveys/[sid]/responses/+page.js
Normal file
7
ui/src/routes/surveys/[sid]/responses/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
};
|
||||
}
|
@ -1,32 +1,20 @@
|
||||
<script context="module">
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { user } from '$lib/stores/user';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
import StartStopLiveSurvey from '$lib/components/StartStopLiveSurvey.svelte';
|
||||
import SurveyAdmin from '$lib/components/SurveyAdmin.svelte';
|
||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||
import { getQuestions } from '$lib/questions';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export let surveyP;
|
||||
let usersP = null;
|
||||
surveyP.then((s) => {
|
||||
usersP = getUsers(s.promo, s.group);
|
||||
})
|
||||
export let data;
|
||||
let survey;
|
||||
let usersP;
|
||||
$: survey = data.survey;
|
||||
$: usersP = getUsers(data.survey.promo, data.survey.group);
|
||||
let edit = false;
|
||||
let exportview = false;
|
||||
let exportview_list = false;
|
||||
@ -41,122 +29,120 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<StartStopLiveSurvey
|
||||
{survey}
|
||||
class="ms-1 float-end"
|
||||
on:update={() => goto(`surveys/${survey.id}/admin`)}
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-dark ms-1 float-end"
|
||||
title="Partager les résultats"
|
||||
on:click={() => shareResults(survey)}
|
||||
><i class="bi bi-share-fill"></i></button>
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<StartStopLiveSurvey
|
||||
{survey}
|
||||
class="ms-1 float-end"
|
||||
on:update={() => goto(`surveys/${survey.id}/admin`)}
|
||||
/>
|
||||
<button
|
||||
class="btn btn-outline-dark ms-1 float-end"
|
||||
title="Partager les résultats"
|
||||
on:click={() => shareResults(survey)}
|
||||
><i class="bi bi-share-fill"></i></button>
|
||||
<button
|
||||
class="btn ms-1 float-end"
|
||||
class:btn-dark={exportview}
|
||||
class:btn-outline-dark={!exportview}
|
||||
title="Afficher les graphiques"
|
||||
on:click={() => { exportview = !exportview; } }
|
||||
><i class="bi bi-activity"></i></button>
|
||||
{#if exportview}
|
||||
<button
|
||||
class="btn ms-1 float-end"
|
||||
class:btn-dark={exportview}
|
||||
class:btn-outline-dark={!exportview}
|
||||
title="Afficher les graphiques"
|
||||
on:click={() => { exportview = !exportview; } }
|
||||
><i class="bi bi-activity"></i></button>
|
||||
{#if exportview}
|
||||
<button
|
||||
class="btn ms-1 float-end"
|
||||
class:btn-dark={exportview_list}
|
||||
class:btn-outline-dark={!exportview_list}
|
||||
title="Traiter les listes comme des textes : ne pas chercher à les regrouper sous un diagramme"
|
||||
on:click={() => { exportview_list = !exportview_list; } }
|
||||
><i class="bi bi-view-list"></i></button>
|
||||
{/if}
|
||||
class:btn-dark={exportview_list}
|
||||
class:btn-outline-dark={!exportview_list}
|
||||
title="Traiter les listes comme des textes : ne pas chercher à les regrouper sous un diagramme"
|
||||
on:click={() => { exportview_list = !exportview_list; } }
|
||||
><i class="bi bi-view-list"></i></button>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/{survey.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">{#if exportview}Réponses{:else}Corrections{/if}</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/{survey.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">{#if exportview}Réponses{:else}Corrections{/if}</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
||||
{/if}
|
||||
|
||||
{#await getQuestions(survey.id)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
||||
{/if}
|
||||
|
||||
{#await getQuestions(survey.id)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
{#if !exportview}
|
||||
<div class="card mt-3 mb-5">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
{:then questions}
|
||||
{#if !exportview}
|
||||
<div class="card mt-3 mb-5">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Question</th>
|
||||
<th>Réponses</th>
|
||||
<th>Moyenne</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ng-controller="SurveyGradesController">
|
||||
{#each questions as question (question.id)}
|
||||
<tr>
|
||||
<th>Question</th>
|
||||
<th>Réponses</th>
|
||||
<th>Moyenne</th>
|
||||
<td><a href="surveys/{survey.id}/responses/{question.id}">{question.title}</a></td>
|
||||
{#await question.getResponses()}
|
||||
<td colspan="2" class="text-center">
|
||||
<div class="spinner-border mx-3" role="status"></div>
|
||||
<span>Chargement …</span>
|
||||
</td>
|
||||
{:then responses}
|
||||
<td>
|
||||
{#if responses}
|
||||
{responses.filter((r) => !r.time_scored || (r.time_reported && r.time_reported >= r.time_scored)).length} /
|
||||
{responses.length}
|
||||
{#await usersP then users}
|
||||
<br>
|
||||
{Math.trunc(responses.length/users.length*1000)/10} %
|
||||
{/await}
|
||||
{:else}
|
||||
0
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if responses && responses.filter((r) => r.time_scored).length}
|
||||
{Math.trunc(responses.reduce((p, c) => (p + (c.score?c.score:0)), 0)/responses.filter((r) => r.time_scored).length*10)/10} %
|
||||
{:else}
|
||||
-- %
|
||||
{/if}
|
||||
</td>
|
||||
{/await}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ng-controller="SurveyGradesController">
|
||||
{#each questions as question (question.id)}
|
||||
<tr>
|
||||
<td><a href="surveys/{survey.id}/responses/{question.id}">{question.title}</a></td>
|
||||
{#await question.getResponses()}
|
||||
<td colspan="2" class="text-center">
|
||||
<div class="spinner-border mx-3" role="status"></div>
|
||||
<span>Chargement …</span>
|
||||
</td>
|
||||
{:then responses}
|
||||
<td>
|
||||
{#if responses}
|
||||
{responses.filter((r) => !r.time_scored || (r.time_reported && r.time_reported >= r.time_scored)).length} /
|
||||
{responses.length}
|
||||
{#await usersP then users}
|
||||
<br>
|
||||
{Math.trunc(responses.length/users.length*1000)/10} %
|
||||
{/await}
|
||||
{:else}
|
||||
0
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if responses && responses.filter((r) => r.time_scored).length}
|
||||
{Math.trunc(responses.reduce((p, c) => (p + (c.score?c.score:0)), 0)/responses.filter((r) => r.time_scored).length*10)/10} %
|
||||
{:else}
|
||||
-- %
|
||||
{/if}
|
||||
</td>
|
||||
{/await}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{:else}
|
||||
{#each questions as question (question.id)}
|
||||
<h3>{question.title}</h3>
|
||||
{#if question.kind == "text" || (exportview_list && question.kind.indexOf("list") == 0)}
|
||||
{#await question.getResponses() then responses}
|
||||
{#each responses as response (response.id)}
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<p class="card-text" style:white-space="pre-line">
|
||||
{response.value}
|
||||
</p>
|
||||
</div>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{:else}
|
||||
{#each questions as question (question.id)}
|
||||
<h3>{question.title}</h3>
|
||||
{#if question.kind == "text" || (exportview_list && question.kind.indexOf("list") == 0)}
|
||||
{#await question.getResponses() then responses}
|
||||
{#each responses as response (response.id)}
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<p class="card-text" style:white-space="pre-line">
|
||||
{response.value}
|
||||
</p>
|
||||
</div>
|
||||
{/each}
|
||||
{/await}
|
||||
{:else}
|
||||
<CorrectionPieChart {question} />
|
||||
{/if}
|
||||
<hr class="mb-3">
|
||||
{/each}
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
{/each}
|
||||
{/await}
|
||||
{:else}
|
||||
<CorrectionPieChart {question} />
|
||||
{/if}
|
||||
<hr class="mb-3">
|
||||
{/each}
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="shareModal">
|
||||
|
8
ui/src/routes/surveys/[sid]/responses/[rid]/+page.js
Normal file
8
ui/src/routes/surveys/[sid]/responses/[rid]/+page.js
Normal file
@ -0,0 +1,8 @@
|
||||
export async function load({ params, parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
survey: stuff.survey,
|
||||
rid: params.rid,
|
||||
};
|
||||
}
|
@ -1,16 +1,3 @@
|
||||
<script context="module">
|
||||
import { getSurvey } from '$lib/surveys';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
surveyP: stuff.survey,
|
||||
rid: params.rid,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Correction from '$lib/components/Correction.svelte';
|
||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||
@ -20,8 +7,7 @@
|
||||
import { getCorrectionTemplates } from '$lib/correctionTemplates';
|
||||
import { getQuestion } from '$lib/questions';
|
||||
|
||||
export let surveyP;
|
||||
export let rid;
|
||||
export let data;
|
||||
|
||||
let showChart = false;
|
||||
let showResponses = false;
|
||||
@ -31,152 +17,156 @@
|
||||
|
||||
let child;
|
||||
let waitApply = false;
|
||||
let ctpls = getCorrectionTemplates(rid);
|
||||
let filter = "";
|
||||
|
||||
let ctpls;
|
||||
let cts = { };
|
||||
ctpls.then((ctpls) => {
|
||||
for (const tpl of ctpls) {
|
||||
cts[tpl.id] = { };
|
||||
tpl.getCorrections().then((c) => {
|
||||
if (c) {
|
||||
for (const d of c) {
|
||||
cts[tpl.id][d.id_user] = d;
|
||||
}
|
||||
$: {
|
||||
ctpls = getCorrectionTemplates(data.rid);
|
||||
if (ctpls) {
|
||||
ctpls.then((ctpls) => {
|
||||
for (const tpl of ctpls) {
|
||||
cts[tpl.id] = { };
|
||||
tpl.getCorrections().then((c) => {
|
||||
if (c) {
|
||||
for (const d of c) {
|
||||
cts[tpl.id][d.id_user] = d;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
cts = cts;
|
||||
});
|
||||
}
|
||||
let filter = "";
|
||||
|
||||
let nodescription = false;
|
||||
let y = 0;
|
||||
$: nodescription = y > 10;
|
||||
|
||||
let survey = null;
|
||||
$: survey = data.survey;
|
||||
</script>
|
||||
|
||||
<svelte:window bind:scrollY={y} />
|
||||
|
||||
{#await surveyP then survey}
|
||||
{#await getQuestion(rid)}
|
||||
{#await getQuestion(data.rid)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement de la question…</span>
|
||||
</div>
|
||||
{:then question}
|
||||
{#await ctpls}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement de la question…</span>
|
||||
<span>Chargement des templates…</span>
|
||||
</div>
|
||||
{:then correctionTemplates}
|
||||
<div class="float-end">
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="filtre"
|
||||
bind:value={filter}
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/{survey.id}/responses" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">Corrections</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
{:then question}
|
||||
{#await ctpls}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement de la question…</span>
|
||||
</div>
|
||||
{:then correctionTemplates}
|
||||
<div class="float-end">
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="filtre"
|
||||
bind:value={filter}
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="surveys/{survey.id}/responses" class="text-muted" style="text-decoration: none"><</a>
|
||||
{survey.title}
|
||||
<small class="text-muted">Corrections</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" {survey} />
|
||||
</div>
|
||||
|
||||
<div class="card sticky-top" style="overflow-y: auto; max-height: 70vh">
|
||||
<QuestionHeader
|
||||
{question}
|
||||
nodescription={nodescription}
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-link float-start"
|
||||
on:click={() => showResponses = !showResponses}
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
class:bi-chevron-right={!showResponses}
|
||||
class:bi-chevron-down={showResponses}
|
||||
></i>
|
||||
</button>
|
||||
{#if showResponses}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success float-end ms-3 me-1"
|
||||
title="Appliquer les corrections par regexp"
|
||||
on:click={() => {waitApply = true; child.applyCorrections().then(() => { waitApply = false; })} }
|
||||
disabled={waitApply}
|
||||
>
|
||||
{#if waitApply}
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
{:else}
|
||||
<i class="bi bi-check-all"></i>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{#if question.kind == "ucq" || question.kind == "mcq" || question.kind == "int"}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm float-end mx-1"
|
||||
class:btn-outline-info={!showChart}
|
||||
class:btn-info={showChart}
|
||||
on:click={() => showChart = !showChart}
|
||||
title="Afficher les résultats"
|
||||
>
|
||||
<i class="bi bi-bar-chart-line-fill"></i>
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm float-end mx-1"
|
||||
class:btn-outline-info={!showStudent}
|
||||
class:btn-info={showStudent}
|
||||
on:click={() => showStudent = !showStudent}
|
||||
title="Afficher les étudiants"
|
||||
>
|
||||
<i class="bi bi-people"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm float-end mx-1"
|
||||
class:btn-outline-info={!notCorrected}
|
||||
class:btn-info={notCorrected}
|
||||
on:click={() => notCorrected = !notCorrected}
|
||||
title="Afficher les réponses corrigées"
|
||||
>
|
||||
<i class="bi bi-files"></i>
|
||||
</button>
|
||||
</QuestionHeader>
|
||||
{#if showResponses}
|
||||
<CorrectionReference
|
||||
class="card-body"
|
||||
{cts}
|
||||
bind:filter={filter}
|
||||
{nb_responses}
|
||||
{question}
|
||||
templates={correctionTemplates}
|
||||
/>
|
||||
{/if}
|
||||
{#if showChart}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
class="card-body"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Correction
|
||||
{cts}
|
||||
{filter}
|
||||
<div class="card sticky-top" style="overflow-y: auto; max-height: 70vh">
|
||||
<QuestionHeader
|
||||
{question}
|
||||
{showStudent}
|
||||
{notCorrected}
|
||||
bind:child={child}
|
||||
templates={correctionTemplates}
|
||||
on:nb_responses={(v) => { nb_responses = v.detail; } }
|
||||
/>
|
||||
{/await}
|
||||
nodescription={nodescription}
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-link float-start"
|
||||
on:click={() => showResponses = !showResponses}
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
class:bi-chevron-right={!showResponses}
|
||||
class:bi-chevron-down={showResponses}
|
||||
></i>
|
||||
</button>
|
||||
{#if showResponses}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-success float-end ms-3 me-1"
|
||||
title="Appliquer les corrections par regexp"
|
||||
on:click={() => {waitApply = true; child.applyCorrections().then(() => { waitApply = false; })} }
|
||||
disabled={waitApply}
|
||||
>
|
||||
{#if waitApply}
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
{:else}
|
||||
<i class="bi bi-check-all"></i>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{#if question.kind == "ucq" || question.kind == "mcq" || question.kind == "int"}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm float-end mx-1"
|
||||
class:btn-outline-info={!showChart}
|
||||
class:btn-info={showChart}
|
||||
on:click={() => showChart = !showChart}
|
||||
title="Afficher les résultats"
|
||||
>
|
||||
<i class="bi bi-bar-chart-line-fill"></i>
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm float-end mx-1"
|
||||
class:btn-outline-info={!showStudent}
|
||||
class:btn-info={showStudent}
|
||||
on:click={() => showStudent = !showStudent}
|
||||
title="Afficher les étudiants"
|
||||
>
|
||||
<i class="bi bi-people"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm float-end mx-1"
|
||||
class:btn-outline-info={!notCorrected}
|
||||
class:btn-info={notCorrected}
|
||||
on:click={() => notCorrected = !notCorrected}
|
||||
title="Afficher les réponses corrigées"
|
||||
>
|
||||
<i class="bi bi-files"></i>
|
||||
</button>
|
||||
</QuestionHeader>
|
||||
{#if showResponses}
|
||||
<CorrectionReference
|
||||
class="card-body"
|
||||
{cts}
|
||||
bind:filter={filter}
|
||||
{nb_responses}
|
||||
{question}
|
||||
templates={correctionTemplates}
|
||||
/>
|
||||
{/if}
|
||||
{#if showChart}
|
||||
<CorrectionPieChart
|
||||
{question}
|
||||
class="card-body"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Correction
|
||||
{cts}
|
||||
{filter}
|
||||
{question}
|
||||
{showStudent}
|
||||
{notCorrected}
|
||||
bind:child={child}
|
||||
templates={correctionTemplates}
|
||||
on:nb_responses={(v) => { nb_responses = v.detail; } }
|
||||
/>
|
||||
{/await}
|
||||
{/await}
|
||||
|
||||
|
5
ui/src/routes/users/[uid]/+page.js
Normal file
5
ui/src/routes/users/[uid]/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
uid: params.uid,
|
||||
};
|
||||
}
|
@ -1,13 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
uid: params.uid,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import UserKeys from '$lib/components/UserKeys.svelte';
|
||||
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
||||
@ -15,13 +5,13 @@
|
||||
import { getSurveys } from '$lib/surveys';
|
||||
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
||||
|
||||
export let uid;
|
||||
export let data;
|
||||
|
||||
let allPromos = false;
|
||||
|
||||
let myuser = null;
|
||||
let userP = null;
|
||||
$: userP = getUser(uid).then((u) => myuser = u)
|
||||
$: userP = getUser(data.uid).then((u) => myuser = u)
|
||||
|
||||
function impersonate() {
|
||||
fetch('api/auth/impersonate', {
|
||||
|
5
ui/src/routes/users/[uid]/surveys/+page.js
Normal file
5
ui/src/routes/users/[uid]/surveys/+page.js
Normal file
@ -0,0 +1,5 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
uid: params.uid,
|
||||
};
|
||||
}
|
@ -1,25 +1,15 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
uid: params.uid,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { getSurveys } from '$lib/surveys';
|
||||
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
||||
|
||||
export let uid;
|
||||
export let data;
|
||||
|
||||
let allPromos = false;
|
||||
</script>
|
||||
|
||||
{#await getUser(uid)}
|
||||
{#await getUser(data.uid)}
|
||||
<h2>
|
||||
Étudiant
|
||||
</h2>
|
||||
|
6
ui/src/routes/users/[uid]/surveys/[sid]/+page.js
Normal file
6
ui/src/routes/users/[uid]/surveys/[sid]/+page.js
Normal file
@ -0,0 +1,6 @@
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
sid: params.sid,
|
||||
uid: params.uid,
|
||||
};
|
||||
}
|
@ -1,14 +1,3 @@
|
||||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
sid: params.sid,
|
||||
uid: params.uid,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||
@ -16,11 +5,10 @@
|
||||
import { getQuestions } from '$lib/questions';
|
||||
import { getUser } from '$lib/users';
|
||||
|
||||
export let sid;
|
||||
export let uid;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await getUser(uid)}
|
||||
{#await getUser(data.uid)}
|
||||
<h2>
|
||||
Étudiant
|
||||
</h2>
|
||||
@ -30,7 +18,7 @@
|
||||
Chargement des détails…
|
||||
</div>
|
||||
{:then student}
|
||||
{#await getSurvey(sid)}
|
||||
{#await getSurvey(data.sid)}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement du questionnaire …</span>
|
||||
@ -50,7 +38,7 @@
|
||||
<span>Chargement des questions …</span>
|
||||
</div>
|
||||
{:then questions}
|
||||
<SurveyQuestions {survey} {questions} id_user={uid} />
|
||||
<SurveyQuestions {survey} {questions} id_user={data.uid} />
|
||||
{/await}
|
||||
{:catch error}
|
||||
<div class="text-center">
|
||||
|
9
ui/src/routes/works/[wid]/+layout.js
Normal file
9
ui/src/routes/works/[wid]/+layout.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params }) {
|
||||
const work = getWork(params.wid);
|
||||
|
||||
return {
|
||||
work,
|
||||
};
|
||||
}
|
@ -1,27 +1,8 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
const work = getWork(params.wid);
|
||||
|
||||
return {
|
||||
props: {
|
||||
work,
|
||||
},
|
||||
stuff: {
|
||||
...stuff,
|
||||
work,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export let work;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await work}
|
||||
{#await data.work}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement du rendu …</span>
|
||||
|
7
ui/src/routes/works/[wid]/+page.js
Normal file
7
ui/src/routes/works/[wid]/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
work: stuff.work,
|
||||
};
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
work: stuff.work,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -21,222 +9,220 @@
|
||||
import WorkRepository from '$lib/components/WorkRepository.svelte';
|
||||
import { getScore } from '$lib/users';
|
||||
|
||||
export let work = null;
|
||||
export let data;
|
||||
let edit = false;
|
||||
let my_submission = null;
|
||||
let warn_already_used = false;
|
||||
let w = null;
|
||||
|
||||
work.then((w) => {
|
||||
refresh_submission(w);
|
||||
})
|
||||
$: w = data.work;
|
||||
$: refresh_submission(data.work);
|
||||
|
||||
function refresh_submission(w) {
|
||||
my_submission = w.getSubmission();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await work then w}
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<a class="btn btn-success ms-1 float-end" href="works/{w.id}/rendus" title="Voir les rendus"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="works/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{w.title}
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" survey={w} />
|
||||
</div>
|
||||
{#if $user && $user.is_admin}
|
||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
||||
<a class="btn btn-success ms-1 float-end" href="works/{w.id}/rendus" title="Voir les rendus"><i class="bi bi-files"></i></a>
|
||||
{/if}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="works/" class="text-muted" style="text-decoration: none"><</a>
|
||||
{w.title}
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" survey={w} />
|
||||
</div>
|
||||
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<WorkAdmin work={w} on:saved={() => edit = false} />
|
||||
{#if $user && $user.is_admin && edit}
|
||||
<WorkAdmin work={w} on:saved={() => edit = false} />
|
||||
|
||||
{#if w.description}
|
||||
{#if w.description}
|
||||
<hr>
|
||||
{@html w.description}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
<h3 class="mt-3">Notes</h3>
|
||||
<div class="card mt-3 mb-5">
|
||||
{#await w.getGrades()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des notes …</span>
|
||||
</div>
|
||||
{:then grades}
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Login</th>
|
||||
<th>Note</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Date de la note</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if !grades}
|
||||
<div class="text-center">
|
||||
Aucune note n'a encore été envoyée pour ce travail.
|
||||
</div>
|
||||
{:else}
|
||||
{#each grades as grade, gid (grade.id)}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="users/{grade.id_user}">{grade.login}</a>
|
||||
</td>
|
||||
<td>{grade.score}</td>
|
||||
<td>{#if grade.comment}{grade.comment}{:else}-{/if}</td>
|
||||
<td>{grade.date}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
</div>
|
||||
{:else if (!$user || !$user.is_admin) && new Date(w.start_availability) > new Date()}
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-stopwatch-fill"></i>
|
||||
<strong>Ce travail n'est pas encore ouvert.</strong> Revenez plus tard !
|
||||
</div>
|
||||
{:else}
|
||||
<dl style="columns: 3">
|
||||
<dt>Date de début</dt>
|
||||
<dd><DateFormat date={new Date(w.start_availability)} dateStyle="medium" timeStyle="medium" /></dd>
|
||||
<dt>Date de fin</dt>
|
||||
<dd><DateFormat date={new Date(w.end_availability)} dateStyle="medium" timeStyle="medium" /></dd>
|
||||
{#if w.submission_url != "-"}
|
||||
<dt>Rendu ?</dt>
|
||||
<dd>
|
||||
{#if w.submission_url}
|
||||
<SubmissionStatus work={w} user={$user} />
|
||||
<hr>
|
||||
<h3 class="mt-3">Notes</h3>
|
||||
<div class="card mt-3 mb-5">
|
||||
{#await w.getGrades()}
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||
<span>Chargement des notes …</span>
|
||||
</div>
|
||||
{:then grades}
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Login</th>
|
||||
<th>Note</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Date de la note</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if !grades}
|
||||
<div class="text-center">
|
||||
Aucune note n'a encore été envoyée pour ce travail.
|
||||
</div>
|
||||
{:else}
|
||||
{#each grades as grade, gid (grade.id)}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="users/{grade.id_user}">{grade.login}</a>
|
||||
</td>
|
||||
<td>{grade.score}</td>
|
||||
<td>{#if grade.comment}{grade.comment}{:else}-{/if}</td>
|
||||
<td>{grade.date}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
</div>
|
||||
{:else if (!$user || !$user.is_admin) && new Date(w.start_availability) > new Date()}
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-stopwatch-fill"></i>
|
||||
<strong>Ce travail n'est pas encore ouvert.</strong> Revenez plus tard !
|
||||
</div>
|
||||
{:else}
|
||||
<dl style="columns: 3">
|
||||
<dt>Date de début</dt>
|
||||
<dd><DateFormat date={new Date(w.start_availability)} dateStyle="medium" timeStyle="medium" /></dd>
|
||||
<dt>Date de fin</dt>
|
||||
<dd><DateFormat date={new Date(w.end_availability)} dateStyle="medium" timeStyle="medium" /></dd>
|
||||
{#if w.submission_url != "-"}
|
||||
<dt>Rendu ?</dt>
|
||||
<dd>
|
||||
{#if w.submission_url}
|
||||
<SubmissionStatus work={w} user={$user} />
|
||||
{:else}
|
||||
{#await my_submission}
|
||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
||||
{:then submission}
|
||||
<i
|
||||
class="bi bi-check-circle text-success"
|
||||
title="Oui !"
|
||||
></i>
|
||||
<DateFormat date={new Date(submission.date)} dateStyle="medium" timeStyle="medium" />
|
||||
{:catch}
|
||||
<i
|
||||
class="bi bi-x-circle text-danger"
|
||||
title="Pas de rendu trouvé"
|
||||
></i>
|
||||
Non
|
||||
{/await}
|
||||
{/if}
|
||||
</dd>
|
||||
{/if}
|
||||
</dl>
|
||||
{#if w.description}
|
||||
<hr>
|
||||
{@html w.description}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="mt-3">Rendu</h3>
|
||||
|
||||
{#if !w.corrected && w.submission_url != "-"}
|
||||
<p>
|
||||
Pour rendre votre travail, vous devez préalablement créer un dépôt Git sur la <a href="https://gitlab.cri.epita.fr/" target="_blank">forge de l'école</a>.<br>Ce dépôt DOIT :
|
||||
</p>
|
||||
<ul>
|
||||
<li>être dans l'espace de nom de votre utilisateur (à la fin de la liste des <span class="fst-italic">namespaces</span>),</li>
|
||||
<li>avoir la visibilité « Privé »,</li>
|
||||
<li>avoir invité <a href="https://gitlab.cri.epita.fr/nemunaire" target="_blank" style="font-family: monospace">nemunaire</a> avec le rôle <span class="fst-italic">Reporter</span> une fois le dépôt créé.</li>
|
||||
</ul>
|
||||
|
||||
{#if w.tag}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-lightbulb-fill text-info me-1"></i>
|
||||
Vous pouvez utiliser un dépôt pour tous les travaux à effectuer, ou créer un dépôt par travail.
|
||||
Son nom/slug n'a pas d'importance car c'est à vous de le sélectionner dans le formulaire ci-après.
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if w.submission_url != "-"}
|
||||
<WorkRepository class="mb-3" readonly={w.corrected || new Date(w.end_availability) <= new Date()} work={w} on:update_submission={() => refresh_submission(w)} bind:already_used={warn_already_used} />
|
||||
<div class="card mb-3">
|
||||
<div class="card-body d-flex justify-content-between">
|
||||
<div>
|
||||
{#if w.submission_url}
|
||||
<strong>État du rendu :</strong> <SubmissionStatus work={w} user={$user} />
|
||||
{:else}
|
||||
Rendu :
|
||||
{#await my_submission}
|
||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
||||
{:then submission}
|
||||
<i
|
||||
class="bi bi-check-circle text-success"
|
||||
title="Oui !"
|
||||
></i>
|
||||
<DateFormat date={new Date(submission.date)} dateStyle="medium" timeStyle="medium" />
|
||||
<strong>{submission.commit} ({submission.tag})</strong> (taille : {submission.size} o, date : <DateFormat date={new Date(submission.date)} dateStyle="medium" timeStyle="medium" />)
|
||||
{:catch}
|
||||
<i
|
||||
class="bi bi-x-circle text-danger"
|
||||
title="Pas de rendu trouvé"
|
||||
></i>
|
||||
Non
|
||||
<strong>-</strong>
|
||||
{/await}
|
||||
{/if}
|
||||
</dd>
|
||||
{/if}
|
||||
</dl>
|
||||
{#if w.description}
|
||||
<hr>
|
||||
{@html w.description}
|
||||
{/if}
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="mt-3">Rendu</h3>
|
||||
|
||||
{#if !w.corrected && w.submission_url != "-"}
|
||||
<p>
|
||||
Pour rendre votre travail, vous devez préalablement créer un dépôt Git sur la <a href="https://gitlab.cri.epita.fr/" target="_blank">forge de l'école</a>.<br>Ce dépôt DOIT :
|
||||
</p>
|
||||
<ul>
|
||||
<li>être dans l'espace de nom de votre utilisateur (à la fin de la liste des <span class="fst-italic">namespaces</span>),</li>
|
||||
<li>avoir la visibilité « Privé »,</li>
|
||||
<li>avoir invité <a href="https://gitlab.cri.epita.fr/nemunaire" target="_blank" style="font-family: monospace">nemunaire</a> avec le rôle <span class="fst-italic">Reporter</span> une fois le dépôt créé.</li>
|
||||
</ul>
|
||||
|
||||
{#if w.tag}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-lightbulb-fill text-info me-1"></i>
|
||||
Vous pouvez utiliser un dépôt pour tous les travaux à effectuer, ou créer un dépôt par travail.
|
||||
Son nom/slug n'a pas d'importance car c'est à vous de le sélectionner dans le formulaire ci-après.
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if w.submission_url != "-"}
|
||||
<WorkRepository class="mb-3" readonly={w.corrected || new Date(w.end_availability) <= new Date()} work={w} on:update_submission={() => refresh_submission(w)} bind:already_used={warn_already_used} />
|
||||
<div class="card mb-3">
|
||||
<div class="card-body d-flex justify-content-between">
|
||||
<div>
|
||||
{#if w.submission_url}
|
||||
<strong>État du rendu :</strong> <SubmissionStatus work={w} user={$user} />
|
||||
{:else}
|
||||
Rendu :
|
||||
{#await my_submission}
|
||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
||||
{:then submission}
|
||||
<strong>{submission.commit} ({submission.tag})</strong> (taille : {submission.size} o, date : <DateFormat date={new Date(submission.date)} dateStyle="medium" timeStyle="medium" />)
|
||||
{:catch}
|
||||
<strong>-</strong>
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
{#await my_submission then submission}
|
||||
<a
|
||||
href="/api/works/{w.id}/download"
|
||||
class="btn btn-sm btn-dark"
|
||||
title="Voir la tarball de mon rendu"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
{:catch}
|
||||
<i
|
||||
class="bi bi-x-circle text-danger"
|
||||
title="Pas de rendu trouvé"
|
||||
></i>
|
||||
{/await}
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
{#await my_submission then submission}
|
||||
<a
|
||||
href="/api/works/{w.id}/download"
|
||||
class="btn btn-sm btn-dark"
|
||||
title="Voir la tarball de mon rendu"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
{:catch}
|
||||
<i
|
||||
class="bi bi-x-circle text-danger"
|
||||
title="Pas de rendu trouvé"
|
||||
></i>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if w.corrected}
|
||||
{#await getScore(w)}
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
{:then grade}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-clipboard2-check-fill text-info me-1"></i>
|
||||
<strong>Note finale :</strong> <span title="Établie le {grade.date}">{grade.score}</span> {#if grade.comment}– {grade.comment}{/if}
|
||||
</div>
|
||||
{:catch error}
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
||||
<strong>{error.message}</strong>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if w.submission_url != "-"}
|
||||
{#if warn_already_used}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Vous avez déjà utilisé ce dépôt pour rendre un autre travail.</strong> Pour conserver ce que vous avez fait, tout en respectant l'arborescence de rendu attendue, vous devriez partir d'une nouvelle branche vide :
|
||||
<pre class="mx-2 mt-1 mb-2">
|
||||
{#if w.corrected}
|
||||
{#await getScore(w)}
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
{:then grade}
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-clipboard2-check-fill text-info me-1"></i>
|
||||
<strong>Note finale :</strong> <span title="Établie le {grade.date}">{grade.score}</span> {#if grade.comment}– {grade.comment}{/if}
|
||||
</div>
|
||||
{:catch error}
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
||||
<strong>{error.message}</strong>
|
||||
</div>
|
||||
{/await}
|
||||
{:else if w.submission_url != "-"}
|
||||
{#if warn_already_used}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Vous avez déjà utilisé ce dépôt pour rendre un autre travail.</strong> Pour conserver ce que vous avez fait, tout en respectant l'arborescence de rendu attendue, vous devriez partir d'une nouvelle branche vide :
|
||||
<pre class="mx-2 mt-1 mb-2">
|
||||
42sh$ git checkout --orphan renduX
|
||||
42sh$ git reset
|
||||
42sh$ rm -r *
|
||||
# Créez l'arborescence de rendu ensuite</pre>
|
||||
|
||||
Pour retrouver ensuite vos rendus des travaux précédents :
|
||||
<pre class="mx-2 my-1">
|
||||
Pour retrouver ensuite vos rendus des travaux précédents :
|
||||
<pre class="mx-2 my-1">
|
||||
42sh$ git checkout renduY
|
||||
-- ou --
|
||||
42sh$ git checkout master
|
||||
</pre>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="alert alert-warning">
|
||||
Pour être reconnu, vous devez pousser un tag <strong><a href="keys">signé</a></strong> sur votre dépôt. {#if w.tag}Le tag attendu doit commencer par : <code>{w.tag}</code>. Par exemple <code>{w.tag}v1.0</code>, <code>{w.tag}v1.1</code>, …{/if} Seul le dernier tag <strong>alphabétique</strong> que vous envoyez avant la date du rendu sera pris en compte. Vous pouvez donc faire autant de tag que vous le souhaitez d'ici la date du rendu.
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert alert-primary">
|
||||
Ce travail n'a pas de modalité de rendu.
|
||||
</pre>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="alert alert-warning">
|
||||
Pour être reconnu, vous devez pousser un tag <strong><a href="keys">signé</a></strong> sur votre dépôt. {#if w.tag}Le tag attendu doit commencer par : <code>{w.tag}</code>. Par exemple <code>{w.tag}v1.0</code>, <code>{w.tag}v1.1</code>, …{/if} Seul le dernier tag <strong>alphabétique</strong> que vous envoyez avant la date du rendu sera pris en compte. Vous pouvez donc faire autant de tag que vous le souhaitez d'ici la date du rendu.
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert alert-primary">
|
||||
Ce travail n'a pas de modalité de rendu.
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
<div class="mb-5"></div>
|
||||
|
7
ui/src/routes/works/[wid]/rendus/+page.js
Normal file
7
ui/src/routes/works/[wid]/rendus/+page.js
Normal file
@ -0,0 +1,7 @@
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
return {
|
||||
work: stuff.work,
|
||||
};
|
||||
}
|
@ -1,15 +1,3 @@
|
||||
<script context="module">
|
||||
import { getWork } from '$lib/works';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
return {
|
||||
props: {
|
||||
work: stuff.work,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -23,11 +11,13 @@
|
||||
import { ToastsStore } from '$lib/stores/toasts';
|
||||
import { getUsers } from '$lib/users';
|
||||
|
||||
export let work = null;
|
||||
export let data;
|
||||
let usersP = null;
|
||||
let show_dl_btn = { };
|
||||
let repositoriesP = { };
|
||||
work.then((w) => {
|
||||
let w = null;
|
||||
$: {
|
||||
w = data.work;
|
||||
usersP = getUsers(w.promo, w.group);
|
||||
usersP.then((users) => {
|
||||
nb_users = users.length;
|
||||
@ -36,7 +26,7 @@
|
||||
updateRepoUser(w.id, user.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateRepoUser(wid, userid) {
|
||||
repositoriesP[userid] = getRepositories(wid, userid);
|
||||
@ -50,34 +40,33 @@
|
||||
let search_repo_for = {repo: null, user: null};
|
||||
</script>
|
||||
|
||||
{#await work then w}
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="works/{w.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||
{w.title}
|
||||
<small class="text-muted">Rendus {Math.trunc(nb_rendus/nb_users*100)} % ({nb_rendus}/{nb_users})</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" survey={w} />
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>
|
||||
<a href="works/{w.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||
{w.title}
|
||||
<small class="text-muted">Rendus {Math.trunc(nb_rendus/nb_users*100)} % ({nb_rendus}/{nb_users})</small>
|
||||
</h2>
|
||||
<SurveyBadge class="ms-2" survey={w} />
|
||||
</div>
|
||||
|
||||
{#await usersP then users}
|
||||
<table class="w-100 mb-5">
|
||||
<thead>
|
||||
{#await usersP then users}
|
||||
<table class="w-100 mb-5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Login</th>
|
||||
<th>Rendu</th>
|
||||
<th>Dépôts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each users as user (user.id)}
|
||||
<tr>
|
||||
<th>Login</th>
|
||||
<th>Rendu</th>
|
||||
<th>Dépôts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each users as user (user.id)}
|
||||
<tr>
|
||||
<td><a href="users/{user.login}">{user.login}</a></td>
|
||||
<td>
|
||||
<SubmissionStatus work={w} user={user} on:done={() => { nb_rendus += 1; show_dl_btn[user.id] = true; }} />
|
||||
</td>
|
||||
<td>
|
||||
{#if repositoriesP[user.id]}
|
||||
<td><a href="users/{user.login}">{user.login}</a></td>
|
||||
<td>
|
||||
<SubmissionStatus work={w} user={user} on:done={() => { nb_rendus += 1; show_dl_btn[user.id] = true; }} />
|
||||
</td>
|
||||
<td>
|
||||
{#if repositoriesP[user.id]}
|
||||
{#await repositoriesP[user.id] then repos}
|
||||
<div class="d-flex flex-column">
|
||||
{#each repos as repo (repo.id)}
|
||||
@ -90,7 +79,7 @@
|
||||
<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />
|
||||
<BuildState
|
||||
repo_pull_state={repo.getBuildState()}
|
||||
on:show_logs={() => { show_logs = repo.getBuildLogs(user.id); (new bootstrap.Modal(document.getElementById('logsModal'))).show(); }}
|
||||
on:show_logs={() => { show_logs = repo.getBuildLogs(user.id); (new bootstrap.Modal(document.getElementById('logsModal'))).show(); }}
|
||||
/>
|
||||
{:else}
|
||||
-
|
||||
@ -115,7 +104,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:catch err}
|
||||
{:catch err}
|
||||
<div class="d-flex justify-content-between">
|
||||
-
|
||||
<button
|
||||
@ -127,69 +116,68 @@
|
||||
</button>
|
||||
</div>
|
||||
{/await}
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="/api/users/{user.id}/works/{w.id}/download"
|
||||
class="btn btn-sm btn-dark"
|
||||
class:disabled={!show_dl_btn[user.id]}
|
||||
title="Télécharger la tarball du rendu"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="pullModal">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" on:submit|preventDefault={() => {run_pull_for.modal.hide(); try { run_pull_for.repo.retrieveWork(run_pull_for.struct); updateRepoUser(w.id, run_pull_for.user.id); } catch(err) { ToastsStore.addToast({color: "danger", title: "Connexion impossible", msg: err}) };}}>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Repository Pull {#if run_pull_for.user}{run_pull_for.user.login}{/if}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row mb-2">
|
||||
<label class="col-2 col-form-label" for="pull-tag">Tag</label>
|
||||
<input class="form-control col" id="pull-tag" autofocus placeholder={w.tag} bind:value={run_pull_for.struct.tag}>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="optional-sig" bind:checked={run_pull_for.struct.sig_optional}>
|
||||
<label class="form-check-label" for="optional-sig">
|
||||
Signature du tag optionnelle
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Récupérer le travail
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="repoModal">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" on:submit|preventDefault={() => {search_repo_for.modal.hide(); try { search_repo_for.repo.retrieveWork(run_pull_for.struct); updateRepoUser(w.id, run_pull_for.user.id); } catch(err) { ToastsStore.addToast({color: "danger", title: "Connexion impossible", msg: err}) }; search_repo_for.user = null; }}>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Repository selector {#if search_repo_for.user}{search_repo_for.user.login}{/if}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{#if search_repo_for.user}
|
||||
<WorkRepository work={w} user={search_repo_for.user} />
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="/api/users/{user.id}/works/{w.id}/download"
|
||||
class="btn btn-sm btn-dark"
|
||||
class:disabled={!show_dl_btn[user.id]}
|
||||
title="Télécharger la tarball du rendu"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="pullModal">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" on:submit|preventDefault={() => {run_pull_for.modal.hide(); try { run_pull_for.repo.retrieveWork(run_pull_for.struct); updateRepoUser(w.id, run_pull_for.user.id); } catch(err) { ToastsStore.addToast({color: "danger", title: "Connexion impossible", msg: err}) };}}>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Repository Pull {#if run_pull_for.user}{run_pull_for.user.login}{/if}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row mb-2">
|
||||
<label class="col-2 col-form-label" for="pull-tag">Tag</label>
|
||||
<input class="form-control col" id="pull-tag" autofocus placeholder={w.tag} bind:value={run_pull_for.struct.tag}>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="optional-sig" bind:checked={run_pull_for.struct.sig_optional}>
|
||||
<label class="form-check-label" for="optional-sig">
|
||||
Signature du tag optionnelle
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Récupérer le travail
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="repoModal">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" on:submit|preventDefault={() => {search_repo_for.modal.hide(); try { search_repo_for.repo.retrieveWork(run_pull_for.struct); updateRepoUser(w.id, run_pull_for.user.id); } catch(err) { ToastsStore.addToast({color: "danger", title: "Connexion impossible", msg: err}) }; search_repo_for.user = null; }}>
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Repository selector {#if search_repo_for.user}{search_repo_for.user.login}{/if}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{#if search_repo_for.user}
|
||||
<WorkRepository work={w} user={search_repo_for.user} />
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="logsModal">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
|
Reference in New Issue
Block a user