svelte-migrate: updated files
This commit is contained in:
parent
4d6149760d
commit
ff5a2eef65
@ -10,7 +10,7 @@ import (
|
|||||||
"net/http"
|
"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 embed.FS
|
||||||
|
|
||||||
var Assets http.FileSystem
|
var Assets http.FileSystem
|
||||||
|
@ -48,7 +48,6 @@ func serveOrReverse(forced_url string) func(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func declareStaticRoutes(router *gin.Engine) {
|
func declareStaticRoutes(router *gin.Engine) {
|
||||||
router.GET("/@fs/*_", serveOrReverse(""))
|
|
||||||
router.GET("/", serveOrReverse(""))
|
router.GET("/", serveOrReverse(""))
|
||||||
router.GET("/_app/*_", serveOrReverse(""))
|
router.GET("/_app/*_", serveOrReverse(""))
|
||||||
router.GET("/auth/", serveOrReverse("/"))
|
router.GET("/auth/", serveOrReverse("/"))
|
||||||
@ -74,7 +73,7 @@ func declareStaticRoutes(router *gin.Engine) {
|
|||||||
router.GET("/.svelte-kit/*_", serveOrReverse(""))
|
router.GET("/.svelte-kit/*_", serveOrReverse(""))
|
||||||
router.GET("/node_modules/*_", serveOrReverse(""))
|
router.GET("/node_modules/*_", serveOrReverse(""))
|
||||||
router.GET("/@vite/*_", serveOrReverse(""))
|
router.GET("/@vite/*_", serveOrReverse(""))
|
||||||
router.GET("/__vite_ping", serveOrReverse(""))
|
router.GET("/@fs/*_", serveOrReverse(""))
|
||||||
router.GET("/src/*_", 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) => {
|
set: (auth) => {
|
||||||
update((m) => auth);
|
update((m) => auth);
|
||||||
},
|
},
|
||||||
update: (res_auth, cb=null) => {
|
update: (res_auth) => {
|
||||||
if (res_auth.status === 200) {
|
if (res_auth.status === 200) {
|
||||||
res_auth.json().then((auth) => {
|
res_auth.json().then((auth) => {
|
||||||
update((m) => (Object.assign(m?m:{}, auth)));
|
update((m) => (Object.assign(m?m:{}, auth)));
|
||||||
|
|
||||||
if (cb) {
|
|
||||||
cb(my);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else if (res_auth.status >= 400 && res_auth.status < 500) {
|
} else if (res_auth.status >= 400 && res_auth.status < 500) {
|
||||||
update((m) => (null));
|
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();
|
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>
|
<script>
|
||||||
import AuthButton from '$lib/components/AuthButton.svelte';
|
import AuthButton from '$lib/components/AuthButton.svelte';
|
||||||
import Toaster from '$lib/components/Toaster.svelte';
|
import Toaster from '$lib/components/Toaster.svelte';
|
||||||
|
import { refresh_auth, user } from '$lib/stores/user';
|
||||||
|
|
||||||
export let rroute = '';
|
export let data;
|
||||||
|
|
||||||
function switchAdminMode() {
|
function switchAdminMode() {
|
||||||
var tmp = $user.is_admin;
|
var tmp = $user.is_admin;
|
||||||
@ -94,17 +52,17 @@
|
|||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
<li class="nav-item">
|
<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
|
Questionnaires
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<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
|
Travaux
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{#if $user && $user.is_admin}
|
{#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}
|
{/if}
|
||||||
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
||||||
</ul>
|
</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;">
|
<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>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<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={data.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={data.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 === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<button class="dropdown-item" on:click={disconnectCurrentUser}>
|
<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>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
@ -17,12 +5,10 @@
|
|||||||
import CategoryAdmin from '$lib/components/CategoryAdmin.svelte';
|
import CategoryAdmin from '$lib/components/CategoryAdmin.svelte';
|
||||||
import { Category, getCategory } from '$lib/categories';
|
import { Category, getCategory } from '$lib/categories';
|
||||||
|
|
||||||
export let cid;
|
export let data;
|
||||||
|
|
||||||
let categoryP = null;
|
let categoryP = null;
|
||||||
$: {
|
$: categoryP = getCategory(data.cid);
|
||||||
categoryP = getCategory(cid);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await categoryP then category}
|
{#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>
|
<script>
|
||||||
import StudentGrades from '$lib/components/StudentGrades.svelte';
|
import StudentGrades from '$lib/components/StudentGrades.svelte';
|
||||||
|
|
||||||
export let promo;
|
export let data;
|
||||||
</script>
|
</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>
|
<script>
|
||||||
import { getSharedQuestions } from '$lib/questions';
|
import { getSharedQuestions } from '$lib/questions';
|
||||||
import { getSharedSurvey } from '$lib/surveys';
|
import { getSharedSurvey } from '$lib/surveys';
|
||||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||||
|
|
||||||
export let secret;
|
export let data;
|
||||||
export let idsurvey;
|
|
||||||
|
|
||||||
let surveyP = getSharedSurvey(idsurvey, secret);
|
let surveyP = null;
|
||||||
export let exportview_list = true;
|
$: surveyP = getSharedSurvey(data.idsurvey, data.secret);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await surveyP then survey}
|
{#await surveyP then survey}
|
||||||
@ -32,7 +19,7 @@
|
|||||||
<SurveyBadge class="ms-2" {survey} />
|
<SurveyBadge class="ms-2" {survey} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#await getSharedQuestions(survey.id, secret)}
|
{#await getSharedQuestions(survey.id, data.secret)}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||||
<span>Chargement des questions …</span>
|
<span>Chargement des questions …</span>
|
||||||
@ -40,8 +27,8 @@
|
|||||||
{:then questions}
|
{:then questions}
|
||||||
{#each questions as question (question.id)}
|
{#each questions as question (question.id)}
|
||||||
<h3>{question.title}</h3>
|
<h3>{question.title}</h3>
|
||||||
{#if question.kind == "text" || (exportview_list && question.kind.indexOf("list") == 0)}
|
{#if question.kind == "text" || (data.exportview_list && question.kind.indexOf("list") == 0)}
|
||||||
{#await question.getResponses(secret) then responses}
|
{#await question.getResponses(data.secret) then responses}
|
||||||
{#each responses as response (response.id)}
|
{#each responses as response (response.id)}
|
||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -53,7 +40,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/await}
|
{/await}
|
||||||
{:else}
|
{:else}
|
||||||
<CorrectionPieChart {question} {secret} />
|
<CorrectionPieChart {question} secret={data.secret} />
|
||||||
{/if}
|
{/if}
|
||||||
<hr class="mb-3">
|
<hr class="mb-3">
|
||||||
{/each}
|
{/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">
|
<script lang="ts">
|
||||||
|
export let data;
|
||||||
export let survey;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await survey}
|
{#await data.survey}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||||
<span>Chargement du questionnaire …</span>
|
<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">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
@ -19,62 +7,58 @@
|
|||||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||||
import { getQuestions } from '$lib/questions';
|
import { getQuestions } from '$lib/questions';
|
||||||
|
|
||||||
export let surveyP;
|
export let data;
|
||||||
|
let survey = null;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (surveyP) {
|
survey = data.survey;
|
||||||
surveyP.then((survey) => {
|
if (survey.direct && !$user.is_admin) {
|
||||||
if (survey.direct && !$user.is_admin) {
|
goto(`surveys/${survey.id}/live`);
|
||||||
goto(`surveys/${survey.id}/live`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let edit = false;
|
let edit = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await surveyP then survey}
|
{#if $user && $user.is_admin}
|
||||||
{#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>
|
||||||
<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>
|
||||||
<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}
|
||||||
{#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>
|
||||||
<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}
|
{/if}
|
||||||
<div class="d-flex align-items-center">
|
{/if}
|
||||||
<h2>
|
<div class="d-flex align-items-center">
|
||||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
<h2>
|
||||||
{survey.title}
|
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||||
</h2>
|
{survey.title}
|
||||||
<SurveyBadge class="ms-2" {survey} />
|
</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>
|
</div>
|
||||||
|
{:then questions}
|
||||||
{#if $user && $user.is_admin && edit}
|
<SurveyQuestions {survey} {questions} />
|
||||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
{: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}
|
{/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}
|
{/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>
|
<script>
|
||||||
import { user } from '$lib/stores/user';
|
import { user } from '$lib/stores/user';
|
||||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||||
@ -21,28 +10,24 @@
|
|||||||
import { getQuestion, getQuestions, Question } from '$lib/questions';
|
import { getQuestion, getQuestions, Question } from '$lib/questions';
|
||||||
import { getUsers } from '$lib/users';
|
import { getUsers } from '$lib/users';
|
||||||
|
|
||||||
export let surveyP;
|
export let data;
|
||||||
export let sid;
|
|
||||||
let survey;
|
let survey;
|
||||||
let req_questions;
|
let req_questions;
|
||||||
|
|
||||||
surveyP.then((s) => {
|
$: {
|
||||||
survey = s;
|
survey = data.survey;
|
||||||
updateQuestions();
|
updateQuestions();
|
||||||
if (survey.direct !== null) {
|
if (survey.direct !== null) {
|
||||||
wsconnect();
|
wsconnect();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
function updateSurvey() {
|
async function updateSurvey() {
|
||||||
surveyP = getSurvey(survey.id);
|
survey = await getSurvey(survey.id);
|
||||||
surveyP.then((s) => {
|
updateQuestions();
|
||||||
survey = s;
|
if (survey.direct !== null) {
|
||||||
updateQuestions();
|
wsconnect();
|
||||||
if (survey.direct !== null) {
|
}
|
||||||
wsconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQuestions() {
|
function updateQuestions() {
|
||||||
@ -162,7 +147,7 @@
|
|||||||
function wsconnect() {
|
function wsconnect() {
|
||||||
if (ws !== null) return;
|
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.addEventListener("open", () => {
|
||||||
ws_up = true;
|
ws_up = true;
|
||||||
@ -233,472 +218,469 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await surveyP then survey}
|
{#if $user && $user.is_admin}
|
||||||
{#if $user && $user.is_admin}
|
<StartStopLiveSurvey
|
||||||
<StartStopLiveSurvey
|
{survey}
|
||||||
{survey}
|
class="ms-1 float-end"
|
||||||
class="ms-1 float-end"
|
on:update={() => updateSurvey()}
|
||||||
on:update={() => updateSurvey()}
|
on:end={() => { if (confirm("Sûr ?")) ws.send('{"action":"end"}') }}
|
||||||
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>
|
||||||
<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}
|
||||||
{/if}
|
<div class="d-flex align-items-center">
|
||||||
<div class="d-flex align-items-center">
|
<h2>
|
||||||
<h2>
|
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
||||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
{survey.title}
|
||||||
{survey.title}
|
<small class="text-muted">
|
||||||
<small class="text-muted">
|
Administration
|
||||||
Administration
|
</small>
|
||||||
</small>
|
{#if asks.length}
|
||||||
{#if asks.length}
|
<a href="surveys/{data.sid}/admin#questions_part">
|
||||||
<a href="surveys/{sid}/admin#questions_part">
|
<i class="bi bi-patch-question-fill text-danger"></i>
|
||||||
<i class="bi bi-patch-question-fill text-danger"></i>
|
</a>
|
||||||
</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}
|
{/if}
|
||||||
</div>
|
</h2>
|
||||||
|
{#if survey.direct !== null}
|
||||||
{#if survey.direct === null}
|
<div
|
||||||
<SurveyAdmin
|
class="badge rounded-pill ms-2"
|
||||||
{survey}
|
class:bg-success={ws_up}
|
||||||
on:saved={updateSurvey}
|
class:bg-danger={!ws_up}
|
||||||
/>
|
>
|
||||||
|
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#await req_questions}
|
<SurveyBadge
|
||||||
<div class="text-center">
|
class="mx-2"
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
{survey}
|
||||||
<span>Chargement des questions …</span>
|
/>
|
||||||
</div>
|
{/if}
|
||||||
{:then questions}
|
</div>
|
||||||
<div class="card my-3">
|
|
||||||
<table class="table table-hover table-striped mb-0">
|
{#if survey.direct === null}
|
||||||
<thead>
|
<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>
|
<tr>
|
||||||
<th>
|
<td>
|
||||||
Question
|
{#if responses[question.id]}
|
||||||
{#if timer_end}
|
<a href="surveys/{data.sid}/admin#q{question.id}_res">
|
||||||
<div class="input-group input-group-sm float-end" style="max-width: 150px;">
|
{question.title}
|
||||||
<input
|
</a>
|
||||||
type="number"
|
|
||||||
class="form-control"
|
|
||||||
disabled
|
|
||||||
value={timer_remain}
|
|
||||||
>
|
|
||||||
<span class="input-group-text">s</span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<div class="input-group input-group-sm float-end" style="max-width: 150px;">
|
{question.title}
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="form-control"
|
|
||||||
bind:value={timer}
|
|
||||||
placeholder="Valeur du timer"
|
|
||||||
>
|
|
||||||
<span class="input-group-text">s</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
</td>
|
||||||
type="button"
|
<td>
|
||||||
class="btn btn-sm btn-info ms-1"
|
{#if responses[question.id]}
|
||||||
on:click={updateQuestions}
|
{Object.keys(responses[question.id]).length}
|
||||||
title="Rafraîchir les questions"
|
{:else}
|
||||||
>
|
0
|
||||||
<i class="bi bi-arrow-counterclockwise"></i>
|
{/if}
|
||||||
</button>
|
{#if wsstats}/ {wsstats.nb_clients}{/if}
|
||||||
</th>
|
</td>
|
||||||
<th>
|
<td>
|
||||||
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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
class:btn-outline-success={!next_corrected}
|
class:btn-primary={!next_corrected}
|
||||||
class:btn-success={next_corrected}
|
class:btn-success={next_corrected}
|
||||||
on:click={() => { next_corrected = !next_corrected } }
|
disabled={(question.id === current_question && next_corrected == corrected) || !ws_up}
|
||||||
title="La prochaine question est affichée corrigée"
|
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>
|
||||||
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-info"
|
class="btn btn-sm btn-info"
|
||||||
on:click={() => { edit_question = new Question({ id_survey: survey.id }) } }
|
disabled={question.id === current_question}
|
||||||
title="Ajouter une 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>
|
||||||
<button
|
</td>
|
||||||
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>
|
</tr>
|
||||||
</thead>
|
{/each}
|
||||||
<tbody>
|
</tbody>
|
||||||
{#each questions as question (question.id)}
|
</table>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
{/await}
|
||||||
{#if responses[question.id]}
|
{#if edit_question !== null}
|
||||||
<a href="surveys/{sid}/admin#q{question.id}_res">
|
<QuestionForm
|
||||||
{question.title}
|
{survey}
|
||||||
</a>
|
edit
|
||||||
{:else}
|
question={edit_question}
|
||||||
{question.title}
|
on:delete={() => deleteQuestion(edit_question)}
|
||||||
{/if}
|
/>
|
||||||
</td>
|
{/if}
|
||||||
<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}
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-info ms-1 float-end"
|
class="btn btn-sm btn-info ms-1 float-end"
|
||||||
on:click={() => { ws.send('{"action":"get_asks", "value": ""}'); asks = []; }}
|
on:click={() => { ws.send('{"action":"get_asks", "value": ""}'); asks = []; }}
|
||||||
title="Rafraîchir les réponses"
|
title="Rafraîchir les réponses"
|
||||||
>
|
>
|
||||||
<i class="bi bi-arrow-counterclockwise"></i>
|
<i class="bi bi-arrow-counterclockwise"></i>
|
||||||
<i class="bi bi-question-diamond"></i>
|
<i class="bi bi-question-diamond"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-light ms-1 float-end"
|
class="btn btn-sm btn-light ms-1 float-end"
|
||||||
on:click={() => { ws.send('{"action":"get_asks", "value": "unanswered"}'); asks = []; }}
|
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"
|
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-arrow-counterclockwise"></i>
|
||||||
<i class="bi bi-question-diamond"></i>
|
<i class="bi bi-question-diamond"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-success float-end"
|
class="btn btn-sm btn-success float-end"
|
||||||
title="Tout marqué comme répondu"
|
title="Tout marqué comme répondu"
|
||||||
on:click={() => { ws.send('{"action":"mark_answered", "value": "all"}'); asks = [] }}
|
on:click={() => { ws.send('{"action":"mark_answered", "value": "all"}'); asks = [] }}
|
||||||
>
|
>
|
||||||
<i class="bi bi-check-all"></i>
|
<i class="bi bi-check-all"></i>
|
||||||
</button>
|
</button>
|
||||||
<h3 id="questions_part">
|
<h3 id="questions_part">
|
||||||
Questions
|
Questions
|
||||||
{#if asks.length}
|
|
||||||
<small class="text-muted">
|
|
||||||
{asks.length} question{#if asks.length > 1}s{/if}
|
|
||||||
</small>
|
|
||||||
{/if}
|
|
||||||
</h3>
|
|
||||||
{#if asks.length}
|
{#if asks.length}
|
||||||
{#each asks as ask (ask.id)}
|
<small class="text-muted">
|
||||||
<div class="card mb-3">
|
{asks.length} question{#if asks.length > 1}s{/if}
|
||||||
<div class="card-body">
|
</small>
|
||||||
<p class="card-text">
|
{/if}
|
||||||
{ask.content}
|
</h3>
|
||||||
</p>
|
{#if asks.length}
|
||||||
</div>
|
{#each asks as ask (ask.id)}
|
||||||
<div class="card-footer">
|
<div class="card mb-3">
|
||||||
<button
|
<div class="card-body">
|
||||||
type="button"
|
<p class="card-text">
|
||||||
class="btn btn-sm btn-success float-end"
|
{ask.content}
|
||||||
title="Marqué comme répondu"
|
</p>
|
||||||
on:click={() => { ws.send('{"action":"mark_answered", "question": ' + ask.id + '}'); asks = asks.filter((e) => e.id != ask.id) }}
|
</div>
|
||||||
>
|
<div class="card-footer">
|
||||||
<i class="bi bi-check"></i>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
Par
|
class="btn btn-sm btn-success float-end"
|
||||||
<a href="users/{ask.userid}" target="_blank">
|
title="Marqué comme répondu"
|
||||||
{#if users && users[ask.userid]}
|
on:click={() => { ws.send('{"action":"mark_answered", "question": ' + ask.id + '}'); asks = asks.filter((e) => e.id != ask.id) }}
|
||||||
{users[ask.userid].login}
|
>
|
||||||
{:else}
|
<i class="bi bi-check"></i>
|
||||||
{ask.userid}
|
</button>
|
||||||
{/if}
|
Par
|
||||||
</a>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
</div>
|
||||||
<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}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
{/await}
|
|
||||||
|
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>
|
<script>
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
@ -18,11 +7,9 @@
|
|||||||
import QuestionForm from '$lib/components/QuestionForm.svelte';
|
import QuestionForm from '$lib/components/QuestionForm.svelte';
|
||||||
import { getQuestion } from '$lib/questions';
|
import { getQuestion } from '$lib/questions';
|
||||||
|
|
||||||
export let surveyP;
|
export let data;
|
||||||
export let sid;
|
|
||||||
let survey;
|
let survey;
|
||||||
|
$: survey = data.survey;
|
||||||
surveyP.then((s) => survey = s);
|
|
||||||
|
|
||||||
let ws_up = false;
|
let ws_up = false;
|
||||||
let show_question = null;
|
let show_question = null;
|
||||||
@ -75,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
function wsconnect() {
|
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.addEventListener("open", () => {
|
||||||
ws_up = true;
|
ws_up = true;
|
||||||
@ -183,120 +170,118 @@
|
|||||||
let corrections = null;
|
let corrections = null;
|
||||||
</script>
|
</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
|
<div
|
||||||
style={"transition: opacity 150ms ease-out; opacity: " + (displaySendInProgress?1:0)}
|
class="badge rounded-pill ms-2"
|
||||||
class="ms-2 float-end"
|
class:bg-success={ws_up}
|
||||||
|
class:bg-danger={!ws_up}
|
||||||
>
|
>
|
||||||
<div style="position: relative; left: 25%; top: 4px">
|
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
||||||
<div style="position: absolute">
|
|
||||||
<i class="bi bi-save"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="spinner-border text-primary" role="status"></div>
|
|
||||||
</div>
|
|
||||||
{#if $user && $user.is_admin}
|
|
||||||
<a href="surveys/{survey.id}/admin" class="btn btn-primary ms-1 float-end" title="Aller à l'interface d'administration"><i class="bi bi-pencil"></i></a>
|
|
||||||
<a href="surveys/{survey.id}/responses" class="btn btn-success ms-1 float-end" title="Voir les réponses"><i class="bi bi-files"></i></a>
|
|
||||||
{/if}
|
|
||||||
<div class="d-flex align-items-center mb-3 mb-md-4 mb-lg-5">
|
|
||||||
<h2>
|
|
||||||
<a href="surveys/" class="text-muted" style="text-decoration: none"><</a>
|
|
||||||
{survey.title}
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
class="badge rounded-pill ms-2"
|
|
||||||
class:bg-success={ws_up}
|
|
||||||
class:bg-danger={!ws_up}
|
|
||||||
>
|
|
||||||
{#if ws_up}Connecté{:else}Déconnecté{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form on:submit|preventDefault={sendValue}>
|
<form on:submit|preventDefault={sendValue}>
|
||||||
{#if show_question}
|
{#if show_question}
|
||||||
{#await req_question}
|
{#await req_question}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||||
<span>Chargement d'une nouvelle question …</span>
|
<span>Chargement d'une nouvelle question …</span>
|
||||||
</div>
|
</div>
|
||||||
{:then question}
|
{:then question}
|
||||||
<QuestionForm
|
<QuestionForm
|
||||||
{survey}
|
{survey}
|
||||||
{question}
|
{question}
|
||||||
readonly={timer >= 100 || survey.corrected}
|
readonly={timer >= 100 || survey.corrected}
|
||||||
{corrections}
|
{corrections}
|
||||||
bind:value={value}
|
bind:value={value}
|
||||||
on:change={sendValue}
|
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}
|
Soumettre cette réponse
|
||||||
<div class="progress" style="border-radius: 0; height: 4px">
|
</button>
|
||||||
<div class="progress-bar" class:bg-warning={timer > 85 && timer < 100} class:bg-danger={timer >= 100} role="progressbar" style="width: {timer}%"></div>
|
{/if}
|
||||||
</div>
|
{/await}
|
||||||
{/if}
|
{:else if ws_up}
|
||||||
</QuestionForm>
|
<h2 class="text-center mb-4">
|
||||||
{#if question.kind != 'mcq' && question.kind != 'ucq' && question.kind != 'none'}
|
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
|
<button
|
||||||
class="btn btn-primary"
|
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>
|
</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>
|
||||||
<div class="row">
|
{/if}
|
||||||
<div class="offset-md-1 col-md-10 offset-lg-2 col-lg-8 offset-xl-3 col-xl-6 mb-4">
|
</form>
|
||||||
<div class="input-group">
|
{:else}
|
||||||
<textarea
|
<h2 class="text-center">
|
||||||
id="askquestion"
|
La session est terminée. <small class="text-muted">On se retrouve une prochaine fois…</small>
|
||||||
class="form-control"
|
</h2>
|
||||||
bind:value={myQuestion}
|
{/if}
|
||||||
autofocus
|
</form>
|
||||||
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}
|
|
||||||
|
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">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import { user } from '$lib/stores/user';
|
import { user } from '$lib/stores/user';
|
||||||
|
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||||
import StartStopLiveSurvey from '$lib/components/StartStopLiveSurvey.svelte';
|
import StartStopLiveSurvey from '$lib/components/StartStopLiveSurvey.svelte';
|
||||||
import SurveyAdmin from '$lib/components/SurveyAdmin.svelte';
|
import SurveyAdmin from '$lib/components/SurveyAdmin.svelte';
|
||||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||||
import { getQuestions } from '$lib/questions';
|
import { getQuestions } from '$lib/questions';
|
||||||
|
import { getUsers } from '$lib/users';
|
||||||
|
|
||||||
export let surveyP;
|
export let data;
|
||||||
let usersP = null;
|
let survey;
|
||||||
surveyP.then((s) => {
|
let usersP;
|
||||||
usersP = getUsers(s.promo, s.group);
|
$: survey = data.survey;
|
||||||
})
|
$: usersP = getUsers(data.survey.promo, data.survey.group);
|
||||||
let edit = false;
|
let edit = false;
|
||||||
let exportview = false;
|
let exportview = false;
|
||||||
let exportview_list = false;
|
let exportview_list = false;
|
||||||
@ -41,122 +29,120 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await surveyP then survey}
|
{#if $user && $user.is_admin}
|
||||||
{#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>
|
||||||
<button class="btn btn-primary ms-1 float-end" on:click={() => { edit = !edit; } } title="Éditer"><i class="bi bi-pencil"></i></button>
|
<StartStopLiveSurvey
|
||||||
<StartStopLiveSurvey
|
{survey}
|
||||||
{survey}
|
class="ms-1 float-end"
|
||||||
class="ms-1 float-end"
|
on:update={() => goto(`surveys/${survey.id}/admin`)}
|
||||||
on:update={() => goto(`surveys/${survey.id}/admin`)}
|
/>
|
||||||
/>
|
<button
|
||||||
<button
|
class="btn btn-outline-dark ms-1 float-end"
|
||||||
class="btn btn-outline-dark ms-1 float-end"
|
title="Partager les résultats"
|
||||||
title="Partager les résultats"
|
on:click={() => shareResults(survey)}
|
||||||
on:click={() => shareResults(survey)}
|
><i class="bi bi-share-fill"></i></button>
|
||||||
><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
|
<button
|
||||||
class="btn ms-1 float-end"
|
class="btn ms-1 float-end"
|
||||||
class:btn-dark={exportview}
|
class:btn-dark={exportview_list}
|
||||||
class:btn-outline-dark={!exportview}
|
class:btn-outline-dark={!exportview_list}
|
||||||
title="Afficher les graphiques"
|
title="Traiter les listes comme des textes : ne pas chercher à les regrouper sous un diagramme"
|
||||||
on:click={() => { exportview = !exportview; } }
|
on:click={() => { exportview_list = !exportview_list; } }
|
||||||
><i class="bi bi-activity"></i></button>
|
><i class="bi bi-view-list"></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}
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="d-flex align-items-center">
|
{/if}
|
||||||
<h2>
|
<div class="d-flex align-items-center">
|
||||||
<a href="surveys/{survey.id}" class="text-muted" style="text-decoration: none"><</a>
|
<h2>
|
||||||
{survey.title}
|
<a href="surveys/{survey.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||||
<small class="text-muted">{#if exportview}Réponses{:else}Corrections{/if}</small>
|
{survey.title}
|
||||||
</h2>
|
<small class="text-muted">{#if exportview}Réponses{:else}Corrections{/if}</small>
|
||||||
<SurveyBadge class="ms-2" {survey} />
|
</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>
|
</div>
|
||||||
|
{:then questions}
|
||||||
{#if $user && $user.is_admin && edit}
|
{#if !exportview}
|
||||||
<SurveyAdmin {survey} on:saved={() => edit = false} />
|
<div class="card mt-3 mb-5">
|
||||||
{/if}
|
<table class="table table-hover table-striped mb-0">
|
||||||
|
<thead>
|
||||||
{#await getQuestions(survey.id)}
|
<tr>
|
||||||
<div class="text-center">
|
<th>Question</th>
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<th>Réponses</th>
|
||||||
<span>Chargement des questions …</span>
|
<th>Moyenne</th>
|
||||||
</div>
|
</tr>
|
||||||
{:then questions}
|
</thead>
|
||||||
{#if !exportview}
|
<tbody ng-controller="SurveyGradesController">
|
||||||
<div class="card mt-3 mb-5">
|
{#each questions as question (question.id)}
|
||||||
<table class="table table-hover table-striped mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Question</th>
|
<td><a href="surveys/{survey.id}/responses/{question.id}">{question.title}</a></td>
|
||||||
<th>Réponses</th>
|
{#await question.getResponses()}
|
||||||
<th>Moyenne</th>
|
<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>
|
</tr>
|
||||||
</thead>
|
{/each}
|
||||||
<tbody ng-controller="SurveyGradesController">
|
</tbody>
|
||||||
{#each questions as question (question.id)}
|
</table>
|
||||||
<tr>
|
</div>
|
||||||
<td><a href="surveys/{survey.id}/responses/{question.id}">{question.title}</a></td>
|
{:else}
|
||||||
{#await question.getResponses()}
|
{#each questions as question (question.id)}
|
||||||
<td colspan="2" class="text-center">
|
<h3>{question.title}</h3>
|
||||||
<div class="spinner-border mx-3" role="status"></div>
|
{#if question.kind == "text" || (exportview_list && question.kind.indexOf("list") == 0)}
|
||||||
<span>Chargement …</span>
|
{#await question.getResponses() then responses}
|
||||||
</td>
|
{#each responses as response (response.id)}
|
||||||
{:then responses}
|
<div class="card mb-2">
|
||||||
<td>
|
<div class="card-body">
|
||||||
{#if responses}
|
<p class="card-text" style:white-space="pre-line">
|
||||||
{responses.filter((r) => !r.time_scored || (r.time_reported && r.time_reported >= r.time_scored)).length} /
|
{response.value}
|
||||||
{responses.length}
|
</p>
|
||||||
{#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>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
</div>
|
||||||
{/await}
|
{/each}
|
||||||
{:else}
|
{/await}
|
||||||
<CorrectionPieChart {question} />
|
{:else}
|
||||||
{/if}
|
<CorrectionPieChart {question} />
|
||||||
<hr class="mb-3">
|
{/if}
|
||||||
{/each}
|
<hr class="mb-3">
|
||||||
{/if}
|
{/each}
|
||||||
{/await}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
<div class="modal fade" tabindex="-1" id="shareModal">
|
<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">
|
<script lang="ts">
|
||||||
import Correction from '$lib/components/Correction.svelte';
|
import Correction from '$lib/components/Correction.svelte';
|
||||||
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
import CorrectionPieChart from '$lib/components/CorrectionPieChart.svelte';
|
||||||
@ -20,8 +7,7 @@
|
|||||||
import { getCorrectionTemplates } from '$lib/correctionTemplates';
|
import { getCorrectionTemplates } from '$lib/correctionTemplates';
|
||||||
import { getQuestion } from '$lib/questions';
|
import { getQuestion } from '$lib/questions';
|
||||||
|
|
||||||
export let surveyP;
|
export let data;
|
||||||
export let rid;
|
|
||||||
|
|
||||||
let showChart = false;
|
let showChart = false;
|
||||||
let showResponses = false;
|
let showResponses = false;
|
||||||
@ -31,152 +17,156 @@
|
|||||||
|
|
||||||
let child;
|
let child;
|
||||||
let waitApply = false;
|
let waitApply = false;
|
||||||
let ctpls = getCorrectionTemplates(rid);
|
let ctpls;
|
||||||
let filter = "";
|
|
||||||
|
|
||||||
let cts = { };
|
let cts = { };
|
||||||
ctpls.then((ctpls) => {
|
$: {
|
||||||
for (const tpl of ctpls) {
|
ctpls = getCorrectionTemplates(data.rid);
|
||||||
cts[tpl.id] = { };
|
if (ctpls) {
|
||||||
tpl.getCorrections().then((c) => {
|
ctpls.then((ctpls) => {
|
||||||
if (c) {
|
for (const tpl of ctpls) {
|
||||||
for (const d of c) {
|
cts[tpl.id] = { };
|
||||||
cts[tpl.id][d.id_user] = d;
|
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 nodescription = false;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
$: nodescription = y > 10;
|
$: nodescription = y > 10;
|
||||||
|
|
||||||
|
let survey = null;
|
||||||
|
$: survey = data.survey;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:scrollY={y} />
|
<svelte:window bind:scrollY={y} />
|
||||||
|
|
||||||
{#await surveyP then survey}
|
{#await getQuestion(data.rid)}
|
||||||
{#await getQuestion(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="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<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>
|
</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">
|
<div class="card sticky-top" style="overflow-y: auto; max-height: 70vh">
|
||||||
<QuestionHeader
|
<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}
|
|
||||||
{question}
|
{question}
|
||||||
{showStudent}
|
nodescription={nodescription}
|
||||||
{notCorrected}
|
>
|
||||||
bind:child={child}
|
<button
|
||||||
templates={correctionTemplates}
|
class="btn btn-sm btn-link float-start"
|
||||||
on:nb_responses={(v) => { nb_responses = v.detail; } }
|
on:click={() => showResponses = !showResponses}
|
||||||
/>
|
>
|
||||||
{/await}
|
<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}
|
||||||
{/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">
|
<script lang="ts">
|
||||||
import UserKeys from '$lib/components/UserKeys.svelte';
|
import UserKeys from '$lib/components/UserKeys.svelte';
|
||||||
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
||||||
@ -15,13 +5,13 @@
|
|||||||
import { getSurveys } from '$lib/surveys';
|
import { getSurveys } from '$lib/surveys';
|
||||||
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
||||||
|
|
||||||
export let uid;
|
export let data;
|
||||||
|
|
||||||
let allPromos = false;
|
let allPromos = false;
|
||||||
|
|
||||||
let myuser = null;
|
let myuser = null;
|
||||||
let userP = null;
|
let userP = null;
|
||||||
$: userP = getUser(uid).then((u) => myuser = u)
|
$: userP = getUser(data.uid).then((u) => myuser = u)
|
||||||
|
|
||||||
function impersonate() {
|
function impersonate() {
|
||||||
fetch('api/auth/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">
|
<script lang="ts">
|
||||||
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
import UserSurveys from '$lib/components/UserSurveys.svelte';
|
||||||
import { user } from '$lib/stores/user';
|
import { user } from '$lib/stores/user';
|
||||||
import { getSurveys } from '$lib/surveys';
|
import { getSurveys } from '$lib/surveys';
|
||||||
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
import { getUser, getUserGrade, getUserScore } from '$lib/users';
|
||||||
|
|
||||||
export let uid;
|
export let data;
|
||||||
|
|
||||||
let allPromos = false;
|
let allPromos = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await getUser(uid)}
|
{#await getUser(data.uid)}
|
||||||
<h2>
|
<h2>
|
||||||
Étudiant
|
Étudiant
|
||||||
</h2>
|
</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">
|
<script lang="ts">
|
||||||
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
import SurveyBadge from '$lib/components/SurveyBadge.svelte';
|
||||||
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
import SurveyQuestions from '$lib/components/SurveyQuestions.svelte';
|
||||||
@ -16,11 +5,10 @@
|
|||||||
import { getQuestions } from '$lib/questions';
|
import { getQuestions } from '$lib/questions';
|
||||||
import { getUser } from '$lib/users';
|
import { getUser } from '$lib/users';
|
||||||
|
|
||||||
export let sid;
|
export let data;
|
||||||
export let uid;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await getUser(uid)}
|
{#await getUser(data.uid)}
|
||||||
<h2>
|
<h2>
|
||||||
Étudiant
|
Étudiant
|
||||||
</h2>
|
</h2>
|
||||||
@ -30,7 +18,7 @@
|
|||||||
Chargement des détails…
|
Chargement des détails…
|
||||||
</div>
|
</div>
|
||||||
{:then student}
|
{:then student}
|
||||||
{#await getSurvey(sid)}
|
{#await getSurvey(data.sid)}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||||
<span>Chargement du questionnaire …</span>
|
<span>Chargement du questionnaire …</span>
|
||||||
@ -50,7 +38,7 @@
|
|||||||
<span>Chargement des questions …</span>
|
<span>Chargement des questions …</span>
|
||||||
</div>
|
</div>
|
||||||
{:then questions}
|
{:then questions}
|
||||||
<SurveyQuestions {survey} {questions} id_user={uid} />
|
<SurveyQuestions {survey} {questions} id_user={data.uid} />
|
||||||
{/await}
|
{/await}
|
||||||
{:catch error}
|
{:catch error}
|
||||||
<div class="text-center">
|
<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">
|
<script lang="ts">
|
||||||
|
export let data;
|
||||||
export let work;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await work}
|
{#await data.work}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||||
<span>Chargement du rendu …</span>
|
<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">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
@ -21,222 +9,220 @@
|
|||||||
import WorkRepository from '$lib/components/WorkRepository.svelte';
|
import WorkRepository from '$lib/components/WorkRepository.svelte';
|
||||||
import { getScore } from '$lib/users';
|
import { getScore } from '$lib/users';
|
||||||
|
|
||||||
export let work = null;
|
export let data;
|
||||||
let edit = false;
|
let edit = false;
|
||||||
let my_submission = null;
|
let my_submission = null;
|
||||||
let warn_already_used = false;
|
let warn_already_used = false;
|
||||||
|
let w = null;
|
||||||
|
|
||||||
work.then((w) => {
|
$: w = data.work;
|
||||||
refresh_submission(w);
|
$: refresh_submission(data.work);
|
||||||
})
|
|
||||||
|
|
||||||
function refresh_submission(w) {
|
function refresh_submission(w) {
|
||||||
my_submission = w.getSubmission();
|
my_submission = w.getSubmission();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await work then w}
|
{#if $user && $user.is_admin}
|
||||||
{#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>
|
||||||
<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>
|
||||||
<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}
|
||||||
{/if}
|
<div class="d-flex align-items-center">
|
||||||
<div class="d-flex align-items-center">
|
<h2>
|
||||||
<h2>
|
<a href="works/" class="text-muted" style="text-decoration: none"><</a>
|
||||||
<a href="works/" class="text-muted" style="text-decoration: none"><</a>
|
{w.title}
|
||||||
{w.title}
|
</h2>
|
||||||
</h2>
|
<SurveyBadge class="ms-2" survey={w} />
|
||||||
<SurveyBadge class="ms-2" survey={w} />
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if $user && $user.is_admin && edit}
|
{#if $user && $user.is_admin && edit}
|
||||||
<WorkAdmin work={w} on:saved={() => edit = false} />
|
<WorkAdmin work={w} on:saved={() => edit = false} />
|
||||||
|
|
||||||
{#if w.description}
|
{#if w.description}
|
||||||
<hr>
|
<hr>
|
||||||
{@html w.description}
|
{@html w.description}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3 class="mt-3">Notes</h3>
|
<h3 class="mt-3">Notes</h3>
|
||||||
<div class="card mt-3 mb-5">
|
<div class="card mt-3 mb-5">
|
||||||
{#await w.getGrades()}
|
{#await w.getGrades()}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-primary mx-3" role="status"></div>
|
<div class="spinner-border text-primary mx-3" role="status"></div>
|
||||||
<span>Chargement des notes …</span>
|
<span>Chargement des notes …</span>
|
||||||
</div>
|
</div>
|
||||||
{:then grades}
|
{:then grades}
|
||||||
<table class="table table-hover table-striped mb-0">
|
<table class="table table-hover table-striped mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Login</th>
|
<th>Login</th>
|
||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
<th>Commentaire</th>
|
<th>Commentaire</th>
|
||||||
<th>Date de la note</th>
|
<th>Date de la note</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#if !grades}
|
{#if !grades}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
Aucune note n'a encore été envoyée pour ce travail.
|
Aucune note n'a encore été envoyée pour ce travail.
|
||||||
</div>
|
</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}
|
{: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}
|
{#await my_submission}
|
||||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
||||||
{:then submission}
|
{:then submission}
|
||||||
<i
|
<strong>{submission.commit} ({submission.tag})</strong> (taille : {submission.size} o, date : <DateFormat date={new Date(submission.date)} dateStyle="medium" timeStyle="medium" />)
|
||||||
class="bi bi-check-circle text-success"
|
|
||||||
title="Oui !"
|
|
||||||
></i>
|
|
||||||
<DateFormat date={new Date(submission.date)} dateStyle="medium" timeStyle="medium" />
|
|
||||||
{:catch}
|
{:catch}
|
||||||
<i
|
<strong>-</strong>
|
||||||
class="bi bi-x-circle text-danger"
|
|
||||||
title="Pas de rendu trouvé"
|
|
||||||
></i>
|
|
||||||
Non
|
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/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>
|
</div>
|
||||||
{/if}
|
<div class="d-flex flex-column justify-content-center">
|
||||||
{/if}
|
{#await my_submission then submission}
|
||||||
|
<a
|
||||||
{#if w.submission_url != "-"}
|
href="/api/works/{w.id}/download"
|
||||||
<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} />
|
class="btn btn-sm btn-dark"
|
||||||
<div class="card mb-3">
|
title="Voir la tarball de mon rendu"
|
||||||
<div class="card-body d-flex justify-content-between">
|
>
|
||||||
<div>
|
<i class="bi bi-download"></i>
|
||||||
{#if w.submission_url}
|
</a>
|
||||||
<strong>État du rendu :</strong> <SubmissionStatus work={w} user={$user} />
|
{:catch}
|
||||||
{:else}
|
<i
|
||||||
Rendu :
|
class="bi bi-x-circle text-danger"
|
||||||
{#await my_submission}
|
title="Pas de rendu trouvé"
|
||||||
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
|
></i>
|
||||||
{:then submission}
|
{/await}
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if w.corrected}
|
{#if w.corrected}
|
||||||
{#await getScore(w)}
|
{#await getScore(w)}
|
||||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||||
{:then grade}
|
{:then grade}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="bi bi-clipboard2-check-fill text-info me-1"></i>
|
<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}
|
<strong>Note finale :</strong> <span title="Établie le {grade.date}">{grade.score}</span> {#if grade.comment}– {grade.comment}{/if}
|
||||||
</div>
|
</div>
|
||||||
{:catch error}
|
{:catch error}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
<i class="bi text-warning bi-exclamation-triangle-fill" title={error}></i>
|
||||||
<strong>{error.message}</strong>
|
<strong>{error.message}</strong>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
{:else if w.submission_url != "-"}
|
{:else if w.submission_url != "-"}
|
||||||
{#if warn_already_used}
|
{#if warn_already_used}
|
||||||
<div class="alert alert-danger">
|
<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 :
|
<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">
|
<pre class="mx-2 mt-1 mb-2">
|
||||||
42sh$ git checkout --orphan renduX
|
42sh$ git checkout --orphan renduX
|
||||||
42sh$ git reset
|
42sh$ git reset
|
||||||
42sh$ rm -r *
|
42sh$ rm -r *
|
||||||
# Créez l'arborescence de rendu ensuite</pre>
|
# Créez l'arborescence de rendu ensuite</pre>
|
||||||
|
|
||||||
Pour retrouver ensuite vos rendus des travaux précédents :
|
Pour retrouver ensuite vos rendus des travaux précédents :
|
||||||
<pre class="mx-2 my-1">
|
<pre class="mx-2 my-1">
|
||||||
42sh$ git checkout renduY
|
42sh$ git checkout renduY
|
||||||
-- ou --
|
-- ou --
|
||||||
42sh$ git checkout master
|
42sh$ git checkout master
|
||||||
</pre>
|
</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>
|
</div>
|
||||||
{/if}
|
{/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}
|
{/if}
|
||||||
{/await}
|
{/if}
|
||||||
<div class="mb-5"></div>
|
<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">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
@ -23,11 +11,13 @@
|
|||||||
import { ToastsStore } from '$lib/stores/toasts';
|
import { ToastsStore } from '$lib/stores/toasts';
|
||||||
import { getUsers } from '$lib/users';
|
import { getUsers } from '$lib/users';
|
||||||
|
|
||||||
export let work = null;
|
export let data;
|
||||||
let usersP = null;
|
let usersP = null;
|
||||||
let show_dl_btn = { };
|
let show_dl_btn = { };
|
||||||
let repositoriesP = { };
|
let repositoriesP = { };
|
||||||
work.then((w) => {
|
let w = null;
|
||||||
|
$: {
|
||||||
|
w = data.work;
|
||||||
usersP = getUsers(w.promo, w.group);
|
usersP = getUsers(w.promo, w.group);
|
||||||
usersP.then((users) => {
|
usersP.then((users) => {
|
||||||
nb_users = users.length;
|
nb_users = users.length;
|
||||||
@ -36,7 +26,7 @@
|
|||||||
updateRepoUser(w.id, user.id);
|
updateRepoUser(w.id, user.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
function updateRepoUser(wid, userid) {
|
function updateRepoUser(wid, userid) {
|
||||||
repositoriesP[userid] = getRepositories(wid, userid);
|
repositoriesP[userid] = getRepositories(wid, userid);
|
||||||
@ -50,34 +40,33 @@
|
|||||||
let search_repo_for = {repo: null, user: null};
|
let search_repo_for = {repo: null, user: null};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await work then w}
|
<div class="d-flex align-items-center">
|
||||||
<div class="d-flex align-items-center">
|
<h2>
|
||||||
<h2>
|
<a href="works/{w.id}" class="text-muted" style="text-decoration: none"><</a>
|
||||||
<a href="works/{w.id}" class="text-muted" style="text-decoration: none"><</a>
|
{w.title}
|
||||||
{w.title}
|
<small class="text-muted">Rendus {Math.trunc(nb_rendus/nb_users*100)} % ({nb_rendus}/{nb_users})</small>
|
||||||
<small class="text-muted">Rendus {Math.trunc(nb_rendus/nb_users*100)} % ({nb_rendus}/{nb_users})</small>
|
</h2>
|
||||||
</h2>
|
<SurveyBadge class="ms-2" survey={w} />
|
||||||
<SurveyBadge class="ms-2" survey={w} />
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#await usersP then users}
|
{#await usersP then users}
|
||||||
<table class="w-100 mb-5">
|
<table class="w-100 mb-5">
|
||||||
<thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Login</th>
|
||||||
|
<th>Rendu</th>
|
||||||
|
<th>Dépôts</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each users as user (user.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Login</th>
|
<td><a href="users/{user.login}">{user.login}</a></td>
|
||||||
<th>Rendu</th>
|
<td>
|
||||||
<th>Dépôts</th>
|
<SubmissionStatus work={w} user={user} on:done={() => { nb_rendus += 1; show_dl_btn[user.id] = true; }} />
|
||||||
</tr>
|
</td>
|
||||||
</thead>
|
<td>
|
||||||
<tbody>
|
{#if repositoriesP[user.id]}
|
||||||
{#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]}
|
|
||||||
{#await repositoriesP[user.id] then repos}
|
{#await repositoriesP[user.id] then repos}
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
{#each repos as repo (repo.id)}
|
{#each repos as repo (repo.id)}
|
||||||
@ -90,7 +79,7 @@
|
|||||||
<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />
|
<DateFormat date={new Date(repo.last_check)} dateStyle="medium" timeStyle="medium" />
|
||||||
<BuildState
|
<BuildState
|
||||||
repo_pull_state={repo.getBuildState()}
|
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}
|
{:else}
|
||||||
-
|
-
|
||||||
@ -115,7 +104,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
-
|
-
|
||||||
<button
|
<button
|
||||||
@ -127,69 +116,68 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="/api/users/{user.id}/works/{w.id}/download"
|
href="/api/users/{user.id}/works/{w.id}/download"
|
||||||
class="btn btn-sm btn-dark"
|
class="btn btn-sm btn-dark"
|
||||||
class:disabled={!show_dl_btn[user.id]}
|
class:disabled={!show_dl_btn[user.id]}
|
||||||
title="Télécharger la tarball du rendu"
|
title="Télécharger la tarball du rendu"
|
||||||
>
|
>
|
||||||
<i class="bi bi-download"></i>
|
<i class="bi bi-download"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
|
||||||
{/await}
|
{/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 fade" tabindex="-1" id="logsModal">
|
||||||
<div class="modal-dialog modal-xl">
|
<div class="modal-dialog modal-xl">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
Reference in New Issue
Block a user