qa: Refactor layout
This commit is contained in:
parent
859b6a318e
commit
c13da8b574
52
qa/ui/src/lib/components/ExerciceHeader.svelte
Normal file
52
qa/ui/src/lib/components/ExerciceHeader.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import { themes, themesIdx } from '$lib/stores/themes';
|
||||||
|
|
||||||
|
export let theme = null;
|
||||||
|
export let exercice = {};
|
||||||
|
export let query_selected = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
{#if query_selected}
|
||||||
|
<Button
|
||||||
|
class="float-start"
|
||||||
|
color="link"
|
||||||
|
on:click={() => goto(theme?`themes/${theme.id}/${exercice.id}`:`exercices/${exercice.id}`)}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-left" />
|
||||||
|
</Button>
|
||||||
|
{:else if theme}
|
||||||
|
<Button
|
||||||
|
class="float-start"
|
||||||
|
color="link"
|
||||||
|
on:click={() => goto('themes/' + theme.id)}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-left" />
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
class="float-start"
|
||||||
|
color="link"
|
||||||
|
on:click={() => goto('exercices/')}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-left" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{exercice.title}
|
||||||
|
{#if exercice.wip}
|
||||||
|
<Icon name="cone-striped" />
|
||||||
|
{/if}
|
||||||
|
{#if $themes.length && $themesIdx[exercice.id_theme]}
|
||||||
|
<small>
|
||||||
|
<a href="themes/{exercice.id_theme}" title={$themesIdx[exercice.id_theme].authors}>{$themesIdx[exercice.id_theme].name}</a>
|
||||||
|
</small>
|
||||||
|
<a href="../{$themesIdx[exercice.id_theme].urlid}/{exercice.urlid}" target="_self" class="float-right ml-2 btn btn-sm btn-info"><Icon name="play-fill" /> Site du challenge</a>
|
||||||
|
{/if}
|
||||||
|
</h2>
|
38
qa/ui/src/lib/components/ExerciceLayout.svelte
Normal file
38
qa/ui/src/lib/components/ExerciceLayout.svelte
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import {
|
||||||
|
Col,
|
||||||
|
Container,
|
||||||
|
Row,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import ExerciceHeader from '$lib/components/ExerciceHeader.svelte';
|
||||||
|
import QAItems from '$lib/components/QAItems.svelte';
|
||||||
|
|
||||||
|
export let theme = null;
|
||||||
|
export let exercice = {};
|
||||||
|
export let qaitems = null;
|
||||||
|
export let query_selected = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ExerciceHeader
|
||||||
|
{exercice}
|
||||||
|
{query_selected}
|
||||||
|
{theme}
|
||||||
|
/>
|
||||||
|
<Container fluid class="flex-fill d-flex">
|
||||||
|
<Row class="flex-fill">
|
||||||
|
<Col md={3} class="px-0 py-2" style="background: #e7e8e9">
|
||||||
|
{#key countCreation}
|
||||||
|
<QAItems
|
||||||
|
{exercice}
|
||||||
|
queries={qaitems}
|
||||||
|
{query_selected}
|
||||||
|
{theme}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
</Col>
|
||||||
|
<Col md={9} class="d-flex flex-column py-2">
|
||||||
|
<slot></slot>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
@ -1,105 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
Container,
|
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Table,
|
|
||||||
} from 'sveltestrap';
|
|
||||||
|
|
||||||
import QAItems from '$lib/components/QAItems.svelte';
|
|
||||||
import QAItem from '$lib/components/QAItem.svelte';
|
|
||||||
import QANewItem from '$lib/components/QANewItem.svelte';
|
|
||||||
import { themes, themesIdx } from '$lib/stores/themes';
|
|
||||||
|
|
||||||
if ($themes.length == 0) {
|
|
||||||
themes.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
export let theme_id = null;
|
|
||||||
export let exercice = {};
|
|
||||||
let query_selected = null;
|
|
||||||
let countCreation = 0;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h2>
|
|
||||||
{#if query_selected}
|
|
||||||
<Button
|
|
||||||
class="float-start"
|
|
||||||
color="link"
|
|
||||||
on:click={() => query_selected = null}
|
|
||||||
>
|
|
||||||
<Icon name="chevron-left" />
|
|
||||||
</Button>
|
|
||||||
{:else if theme_id}
|
|
||||||
<Button
|
|
||||||
class="float-start"
|
|
||||||
color="link"
|
|
||||||
on:click={() => goto('themes/' + theme_id)}
|
|
||||||
>
|
|
||||||
<Icon name="chevron-left" />
|
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<Button
|
|
||||||
class="float-start"
|
|
||||||
color="link"
|
|
||||||
on:click={() => goto('exercices/')}
|
|
||||||
>
|
|
||||||
<Icon name="chevron-left" />
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
{exercice.title}
|
|
||||||
{#if exercice.wip}
|
|
||||||
<Icon name="cone-striped" />
|
|
||||||
{/if}
|
|
||||||
{#if $themes.length && $themesIdx[exercice.id_theme]}
|
|
||||||
<small>
|
|
||||||
<a href="themes/{exercice.id_theme}" title={$themesIdx[exercice.id_theme].authors}>{$themesIdx[exercice.id_theme].name}</a>
|
|
||||||
</small>
|
|
||||||
<a href="../{$themesIdx[exercice.id_theme].urlid}/{exercice.urlid}" target="_self" class="float-right ml-2 btn btn-sm btn-info"><Icon name="play-fill" /> Site du challenge</a>
|
|
||||||
{/if}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<Container fluid class="flex-fill d-flex">
|
|
||||||
<Row class="flex-fill">
|
|
||||||
<Col md={3} class="px-0 py-2" style="background: #e7e8e9">
|
|
||||||
{#key countCreation}
|
|
||||||
<QAItems
|
|
||||||
bind:query_selected={query_selected}
|
|
||||||
{exercice}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
<Col md={9} class="d-flex flex-column py-2">
|
|
||||||
{#if query_selected}
|
|
||||||
<QAItem
|
|
||||||
bind:query_selected={query_selected}
|
|
||||||
on:update-queries={() => countCreation++}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<Row class="mb-3">
|
|
||||||
<div
|
|
||||||
class="col-md-6"
|
|
||||||
style="overflow-y: auto; max-height: 40vh;"
|
|
||||||
>
|
|
||||||
{@html exercice.statement.replace("$FILES$", "../files")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col-md-6"
|
|
||||||
style="overflow-y: auto; max-height: 40vh;"
|
|
||||||
>
|
|
||||||
{@html exercice.overview.replace("$FILES$", "../files")}
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<QANewItem
|
|
||||||
{exercice}
|
|
||||||
on:new-query={() => countCreation++}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { goto, invalidate } from '$app/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@ -20,19 +20,19 @@
|
|||||||
import { ToastsStore } from '$lib/stores/toasts';
|
import { ToastsStore } from '$lib/stores/toasts';
|
||||||
import { viewIdx } from '$lib/stores/todo';
|
import { viewIdx } from '$lib/stores/todo';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
export let theme = null;
|
||||||
|
export let exercice = null;
|
||||||
export let query_selected;
|
export let query;
|
||||||
|
|
||||||
let query_commentsP;
|
let query_commentsP;
|
||||||
let thumbs = [];
|
let thumbs = [];
|
||||||
let thumb_me = [];
|
let thumb_me = [];
|
||||||
let has_comments = false;
|
let has_comments = false;
|
||||||
$: updateComments(query_selected)
|
$: updateComments(query)
|
||||||
|
|
||||||
function updateComments(query_selected) {
|
function updateComments(query) {
|
||||||
if (query_selected) {
|
if (query) {
|
||||||
query_commentsP = getQAComments(query_selected.id);
|
query_commentsP = getQAComments(query.id);
|
||||||
query_commentsP.then((comments) => {
|
query_commentsP.then((comments) => {
|
||||||
thumbs = [];
|
thumbs = [];
|
||||||
thumb_me = [];
|
thumb_me = [];
|
||||||
@ -65,8 +65,8 @@
|
|||||||
let submissionInProgress = false;
|
let submissionInProgress = false;
|
||||||
function addComment() {
|
function addComment() {
|
||||||
submissionInProgress = true;
|
submissionInProgress = true;
|
||||||
newComment.save(query_selected.id).then(() => {
|
newComment.save(query.id).then(() => {
|
||||||
query_commentsP = getQAComments(query_selected.id);
|
query_commentsP = getQAComments(query.id);
|
||||||
newComment = new QAComment();
|
newComment = new QAComment();
|
||||||
submissionInProgress = false;
|
submissionInProgress = false;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -78,8 +78,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateQA() {
|
function updateQA() {
|
||||||
query_selected.save().then(() => {
|
query.save().then(() => {
|
||||||
dispatch("update-queries");
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ToastsStore.addErrorToast({
|
ToastsStore.addErrorToast({
|
||||||
msg: err,
|
msg: err,
|
||||||
@ -88,9 +88,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function solveQA() {
|
function solveQA() {
|
||||||
query_selected.solved = new Date();
|
query.solved = new Date();
|
||||||
query_selected.save().then(() => {
|
query.save().then(() => {
|
||||||
dispatch("update-queries");
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ToastsStore.addErrorToast({
|
ToastsStore.addErrorToast({
|
||||||
msg: err,
|
msg: err,
|
||||||
@ -99,9 +99,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reopenQA() {
|
function reopenQA() {
|
||||||
query_selected.solved = null;
|
query.solved = null;
|
||||||
query_selected.save().then(() => {
|
query.save().then(() => {
|
||||||
dispatch("update-queries");
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ToastsStore.addErrorToast({
|
ToastsStore.addErrorToast({
|
||||||
msg: err,
|
msg: err,
|
||||||
@ -110,9 +110,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeQA() {
|
function closeQA() {
|
||||||
query_selected.closed = new Date();
|
query.closed = new Date();
|
||||||
query_selected.save().then(() => {
|
query.save().then(() => {
|
||||||
dispatch("update-queries");
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ToastsStore.addErrorToast({
|
ToastsStore.addErrorToast({
|
||||||
msg: err,
|
msg: err,
|
||||||
@ -120,10 +120,10 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteQA() {
|
async function deleteQA() {
|
||||||
query_selected.delete().then(() => {
|
query.delete().then(async () => {
|
||||||
query_selected = null;
|
await invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
dispatch("update-queries");
|
goto(theme?`themes/${theme.id}/${exercice.id}`:`exercices/${exercice.id}`);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ToastsStore.addErrorToast({
|
ToastsStore.addErrorToast({
|
||||||
msg: err,
|
msg: err,
|
||||||
@ -134,15 +134,15 @@
|
|||||||
function deleteMyThumbs() {
|
function deleteMyThumbs() {
|
||||||
if (thumb_me.length) {
|
if (thumb_me.length) {
|
||||||
for (const c of thumb_me) {
|
for (const c of thumb_me) {
|
||||||
c.delete(query_selected.id);
|
c.delete(query.id);
|
||||||
}
|
}
|
||||||
dispatch("update-queries");
|
updateComments(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteComment(comment) {
|
function deleteComment(comment) {
|
||||||
comment.delete(query_selected.id).then(() => {
|
comment.delete(query.id).then(() => {
|
||||||
dispatch("update-queries");
|
updateComments(query);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
ToastsStore.addErrorToast({
|
ToastsStore.addErrorToast({
|
||||||
msg: err,
|
msg: err,
|
||||||
@ -151,20 +151,20 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if query_selected}
|
{#if query}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h4 class="card-title fw-bold mb-0">{query_selected.subject}</h4>
|
<h4 class="card-title fw-bold mb-0">{query.subject}</h4>
|
||||||
<div>
|
<div>
|
||||||
{#if $auth && !query_selected.solved && $viewIdx[query_selected.id_exercice]}
|
{#if $auth && !query.solved && $viewIdx[query.id_exercice]}
|
||||||
<Button on:click={solveQA} color="success">
|
<Button on:click={solveQA} color="success">
|
||||||
<Icon name="check" />
|
<Icon name="check" />
|
||||||
Marquer comme résolu
|
Marquer comme résolu
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $auth && $auth.id_team == query_selected.id_team}
|
{#if $auth && $auth.id_team == query.id_team}
|
||||||
{#if query_selected.solved && !query_selected.closed && (query_selected.subject != "RAS" || query_selected.state != "ok")}
|
{#if query.solved && !query.closed && (query.subject != "RAS" || query.state != "ok")}
|
||||||
<Button on:click={closeQA} color="success">
|
<Button on:click={closeQA} color="success">
|
||||||
<Icon name="check" />
|
<Icon name="check" />
|
||||||
Valider la résolution
|
Valider la résolution
|
||||||
@ -174,7 +174,7 @@
|
|||||||
Réouvrir
|
Réouvrir
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if (!query_selected.solved && !has_comments) || (query_selected.subject == "RAS" && query_selected.state == "ok" && !has_comments)}
|
{#if (!query.solved && !has_comments) || (query.subject == "RAS" && query.state == "ok" && !has_comments)}
|
||||||
<Button on:click={deleteQA} color="danger">
|
<Button on:click={deleteQA} color="danger">
|
||||||
<Icon name="trash-fill" />
|
<Icon name="trash-fill" />
|
||||||
Supprimer
|
Supprimer
|
||||||
@ -192,10 +192,10 @@
|
|||||||
Qui ?
|
Qui ?
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{query_selected.user.split("@")[0]}
|
{query.user.split("@")[0]}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
(team #{query_selected.id_team})
|
(team #{query.id_team})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
@ -205,7 +205,7 @@
|
|||||||
<div class="heading">
|
<div class="heading">
|
||||||
État
|
État
|
||||||
</div>
|
</div>
|
||||||
<BadgeState class="value" state={query_selected.state} />
|
<BadgeState class="value" state={query.state} />
|
||||||
<div
|
<div
|
||||||
class="text-muted"
|
class="text-muted"
|
||||||
title={thumbs.join(', ')}
|
title={thumbs.join(', ')}
|
||||||
@ -227,7 +227,7 @@
|
|||||||
Date de création
|
Date de création
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<DateFormat date={query_selected.creation} dateStyle="long" timeStyle="medium" />
|
<DateFormat date={query.creation} dateStyle="long" timeStyle="medium" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
@ -238,8 +238,8 @@
|
|||||||
Date de résolution
|
Date de résolution
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{#if query_selected.solved}
|
{#if query.solved}
|
||||||
<DateFormat date={query_selected.solved} dateStyle="long" timeStyle="medium" />
|
<DateFormat date={query.solved} dateStyle="long" timeStyle="medium" />
|
||||||
{:else}
|
{:else}
|
||||||
-
|
-
|
||||||
{/if}
|
{/if}
|
||||||
@ -253,8 +253,8 @@
|
|||||||
Date de clôture
|
Date de clôture
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{#if query_selected.closed}
|
{#if query.closed}
|
||||||
<DateFormat date={query_selected.closed} dateStyle="long" timeStyle="medium" />
|
<DateFormat date={query.closed} dateStyle="long" timeStyle="medium" />
|
||||||
{:else}
|
{:else}
|
||||||
-
|
-
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { goto, invalidate } from '$app/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Icon,
|
Icon,
|
||||||
@ -11,11 +13,10 @@
|
|||||||
export { className as class };
|
export { className as class };
|
||||||
let className = '';
|
let className = '';
|
||||||
|
|
||||||
|
export let theme = null;
|
||||||
export let exercice = { };
|
export let exercice = { };
|
||||||
export let query_selected = null;
|
export let query_selected = null;
|
||||||
|
export let queries = [];
|
||||||
let queriesP;
|
|
||||||
$: queriesP = getExerciceQA(exercice.id);
|
|
||||||
|
|
||||||
let thumbInProgress = null;
|
let thumbInProgress = null;
|
||||||
function thumbUp(qid) {
|
function thumbUp(qid) {
|
||||||
@ -25,56 +26,50 @@
|
|||||||
});
|
});
|
||||||
thumb.save(qid).then(() => {
|
thumb.save(qid).then(() => {
|
||||||
thumbInProgress = null;
|
thumbInProgress = null;
|
||||||
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQueries() {
|
|
||||||
queriesP = getExerciceQA(exercice.id);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await queriesP}
|
<div class="list-group {className}" class:list-group-flush={true}>
|
||||||
{:then queries}
|
{#if queries.length}
|
||||||
<div class="list-group {className}" class:list-group-flush={true}>
|
{#each queries as q (q.id)}
|
||||||
{#if queries.length}
|
<button
|
||||||
{#each queries as q (q.id)}
|
type="button"
|
||||||
<button
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||||||
type="button"
|
class:active={query_selected && q.id == query_selected}
|
||||||
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
aria-current="true"
|
||||||
class:active={query_selected && q.id == query_selected.id}
|
on:click={() => goto(theme?`themes/${theme.id}/${exercice.id}/${q.id}`:`exercices/${exercice.id}/${q.id}`)}
|
||||||
aria-current="true"
|
>
|
||||||
on:click={() => query_selected = q}
|
<div class="text-truncate">
|
||||||
>
|
<BadgeState state={q.state} />
|
||||||
<div class="text-truncate">
|
{#if !q.solved}
|
||||||
<BadgeState state={q.state} />
|
<strong>{q.subject}</strong>
|
||||||
{#if !q.solved}
|
{:else if !q.closed}
|
||||||
<strong>{q.subject}</strong>
|
{q.subject}
|
||||||
{:else if !q.closed}
|
{:else}
|
||||||
{q.subject}
|
<s>{q.subject}</s>
|
||||||
{:else}
|
|
||||||
<s>{q.subject}</s>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if !q.closed}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-info"
|
|
||||||
disabled={thumbInProgress !== null}
|
|
||||||
on:click|preventDefault={() => thumbUp(q.id)}
|
|
||||||
>
|
|
||||||
{#if thumbInProgress == q.id}
|
|
||||||
<Spinner size="sm" />
|
|
||||||
{:else}
|
|
||||||
<Icon name="hand-thumbs-up-fill" />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</div>
|
||||||
{/each}
|
{#if !q.closed}
|
||||||
{:else}
|
<button
|
||||||
<div class="fw-bold text-center">
|
type="button"
|
||||||
Aucune requête enregistrée
|
class="btn btn-sm btn-info"
|
||||||
</div>
|
disabled={thumbInProgress !== null}
|
||||||
{/if}
|
on:click|preventDefault={() => thumbUp(q.id)}
|
||||||
</div>
|
>
|
||||||
{/await}
|
{#if thumbInProgress == q.id}
|
||||||
|
<Spinner size="sm" />
|
||||||
|
{:else}
|
||||||
|
<Icon name="hand-thumbs-up-fill" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="fw-bold text-center">
|
||||||
|
Aucune requête enregistrée
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { invalidate, goto } from '$app/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -9,8 +9,7 @@
|
|||||||
import { QAQuery, QAStates } from '$lib/qa';
|
import { QAQuery, QAStates } from '$lib/qa';
|
||||||
import { ToastsStore } from '$lib/stores/toasts';
|
import { ToastsStore } from '$lib/stores/toasts';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
export let theme = null;
|
||||||
|
|
||||||
export let exercice = {};
|
export let exercice = {};
|
||||||
|
|
||||||
let newQuery = new QAQuery();
|
let newQuery = new QAQuery();
|
||||||
@ -25,10 +24,11 @@
|
|||||||
newQuery.content = "Difficulté : " + newQuery.difficulty + "\n" + newQuery.content;
|
newQuery.content = "Difficulté : " + newQuery.difficulty + "\n" + newQuery.content;
|
||||||
if (newQuery.timecount)
|
if (newQuery.timecount)
|
||||||
newQuery.content = "Temps passé : " + newQuery.timecount + "\n" + newQuery.content;
|
newQuery.content = "Temps passé : " + newQuery.timecount + "\n" + newQuery.content;
|
||||||
newQuery.save().then((res) => {
|
newQuery.save().then(async (res) => {
|
||||||
dispatch("new-query");
|
|
||||||
newQuery = new QAQuery();
|
newQuery = new QAQuery();
|
||||||
creationInProgress = false;
|
creationInProgress = false;
|
||||||
|
await invalidate(`api/exercices/${exercice.id}/qa`);
|
||||||
|
goto(theme?`themes/${theme.id}/${exercice.id}/${res.id}`:`exercices/${exercice.id}/${res.id}`)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
creationInProgress = false;
|
creationInProgress = false;
|
||||||
newQuery.content = myContent;
|
newQuery.content = myContent;
|
||||||
|
13
qa/ui/src/routes/exercices/[eid]/+layout.js
Normal file
13
qa/ui/src/routes/exercices/[eid]/+layout.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { getExercice } from '$lib/exercices';
|
||||||
|
import { getExerciceQA } from '$lib/qa.js';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export async function load({ depends, params }) {
|
||||||
|
const exercice = getExercice(params.eid)
|
||||||
|
depends(`api/exercices/${params.eid}`);
|
||||||
|
|
||||||
|
const qaitems = getExerciceQA(params.eid);
|
||||||
|
depends(`api/exercices/${params.eid}/qa`);
|
||||||
|
|
||||||
|
return { exercice, qaitems };
|
||||||
|
}
|
20
qa/ui/src/routes/exercices/[eid]/+layout.svelte
Normal file
20
qa/ui/src/routes/exercices/[eid]/+layout.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import ExerciceLayout from '$lib/components/ExerciceLayout.svelte';
|
||||||
|
import { themes } from '$lib/stores/themes';
|
||||||
|
|
||||||
|
if ($themes.length == 0) {
|
||||||
|
themes.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ExerciceLayout
|
||||||
|
exercice={data.exercice}
|
||||||
|
qaitems={data.qaitems}
|
||||||
|
query_selected={$page.params.qid}
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</ExerciceLayout>
|
@ -1,26 +1,34 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Col,
|
||||||
Container,
|
Container,
|
||||||
Icon,
|
Icon,
|
||||||
Spinner,
|
Row,
|
||||||
|
Table,
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import { getExercice } from '$lib/exercices';
|
import QANewItem from '$lib/components/QANewItem.svelte';
|
||||||
import ExerciceQA from '$lib/components/ExerciceQA.svelte';
|
|
||||||
|
|
||||||
let exerciceP = getExercice($page.params.eid);
|
export let data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await exerciceP}
|
<Row class="mb-3">
|
||||||
<Container class="mt-2 mb-5">
|
<div
|
||||||
<div class="d-flex justify-content-center">
|
class="col-md-6"
|
||||||
<Spinner size="lg" />
|
style="overflow-y: auto; max-height: 40vh;"
|
||||||
</div>
|
>
|
||||||
</Container>
|
{@html data.exercice.statement.replace("$FILES$", "../files")}
|
||||||
{:then exercice}
|
</div>
|
||||||
<ExerciceQA {exercice} />
|
<div
|
||||||
{/await}
|
class="col-md-6"
|
||||||
|
style="overflow-y: auto; max-height: 40vh;"
|
||||||
|
>
|
||||||
|
{@html data.exercice.overview.replace("$FILES$", "../files")}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<QANewItem
|
||||||
|
theme={data.theme}
|
||||||
|
exercice={data.exercice}
|
||||||
|
/>
|
||||||
|
21
qa/ui/src/routes/exercices/[eid]/[qid]/+layout.js
Normal file
21
qa/ui/src/routes/exercices/[eid]/[qid]/+layout.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export async function load({ params, parent }) {
|
||||||
|
const { exercice, qaitems } = await parent();
|
||||||
|
|
||||||
|
let query_selected = null;
|
||||||
|
for (const qaitem of qaitems) {
|
||||||
|
if (qaitem.id == params.qid) {
|
||||||
|
query_selected = qaitem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query_selected) {
|
||||||
|
throw error(404, {
|
||||||
|
message: 'Not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { exercice, qaitems, query_selected };
|
||||||
|
}
|
10
qa/ui/src/routes/exercices/[eid]/[qid]/+page.svelte
Normal file
10
qa/ui/src/routes/exercices/[eid]/[qid]/+page.svelte
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
import QAItem from '$lib/components/QAItem.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<QAItem
|
||||||
|
exercice={data.exercice}
|
||||||
|
query={data.query_selected}
|
||||||
|
/>
|
@ -1 +0,0 @@
|
|||||||
<slot></slot>
|
|
9
qa/ui/src/routes/themes/[tid]/+layout.js
Normal file
9
qa/ui/src/routes/themes/[tid]/+layout.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { getTheme } from '$lib/themes';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export async function load({ depends, params }) {
|
||||||
|
const theme = getTheme(params.tid)
|
||||||
|
depends(`api/themes/${params.tid}`);
|
||||||
|
|
||||||
|
return { theme };
|
||||||
|
}
|
@ -10,9 +10,9 @@
|
|||||||
Spinner,
|
Spinner,
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import { getTheme } from '$lib/themes';
|
|
||||||
import { fieldsExercices, getThemedExercices } from '$lib/exercices';
|
import { fieldsExercices, getThemedExercices } from '$lib/exercices';
|
||||||
|
|
||||||
|
export let data;
|
||||||
let query = "";
|
let query = "";
|
||||||
|
|
||||||
function show(id) {
|
function show(id) {
|
||||||
@ -21,67 +21,61 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container class="mt-2 mb-5">
|
<Container class="mt-2 mb-5">
|
||||||
{#await getTheme($page.params.tid)}
|
<div class="d-flex align-items-end">
|
||||||
<div class="d-flex justify-content-center">
|
<Button
|
||||||
<Spinner size="lg" />
|
class="align-self-center"
|
||||||
</div>
|
color="link"
|
||||||
{:then theme}
|
on:click={() => goto('themes/')}
|
||||||
<div class="d-flex align-items-end">
|
>
|
||||||
<Button
|
<Icon name="chevron-left" />
|
||||||
class="align-self-center"
|
</Button>
|
||||||
color="link"
|
<h2>
|
||||||
on:click={() => goto('themes/')}
|
{data.theme.name}
|
||||||
>
|
</h2>
|
||||||
<Icon name="chevron-left" />
|
<small class="m-2 mb-3 text-muted text-truncate">{@html data.theme.authors}</small>
|
||||||
</Button>
|
</div>
|
||||||
<h2>
|
|
||||||
{theme.name}
|
|
||||||
</h2>
|
|
||||||
<small class="m-2 mb-3 text-muted text-truncate">{@html theme.authors}</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if theme.intro}
|
{#if data.theme.intro}
|
||||||
<Container class="text-muted" style="overflow-y: auto; max-height: 34vh">
|
<Container class="text-muted" style="overflow-y: auto; max-height: 34vh">
|
||||||
{@html theme.intro.replace("$FILES$", "../files")}
|
{@html data.theme.intro.replace("$FILES$", "../files")}
|
||||||
</Container>
|
</Container>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#await getThemedExercices($page.params.tid)}
|
{#await getThemedExercices($page.params.tid)}
|
||||||
{:then exercices}
|
{:then exercices}
|
||||||
<h3 class="mt-2">
|
<h3 class="mt-2">
|
||||||
Défis ({exercices.length})
|
Défis ({exercices.length})
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="search" class="form-control form-control-sm" placeholder="Search" bind:value={query} autofocus>
|
<input type="search" class="form-control form-control-sm" placeholder="Search" bind:value={query} autofocus>
|
||||||
</p>
|
</p>
|
||||||
<Table class="table-hover table-bordered table-striped table-sm">
|
<Table class="table-hover table-bordered table-striped table-sm">
|
||||||
<thead class="thead-dark">
|
<thead class="thead-dark">
|
||||||
<tr>
|
<tr>
|
||||||
{#each fieldsExercices as field}
|
{#each fieldsExercices as field}
|
||||||
<th>
|
<th>
|
||||||
{field}
|
{field}
|
||||||
</th>
|
</th>
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{#each exercices as exercice (exercice.id)}
|
|
||||||
{#if exercice.title.indexOf(query) >= 0}
|
|
||||||
<tr on:click={() => show(exercice.id)}>
|
|
||||||
{#each fieldsExercices as field}
|
|
||||||
<td>
|
|
||||||
{@html exercice[field]}
|
|
||||||
{#if field == "title" && exercice.wip}
|
|
||||||
<Icon name="cone-striped" />
|
|
||||||
{/if}
|
|
||||||
</td>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tr>
|
||||||
</Table>
|
</thead>
|
||||||
{/await}
|
<tbody>
|
||||||
|
{#each exercices as exercice (exercice.id)}
|
||||||
|
{#if exercice.title.indexOf(query) >= 0}
|
||||||
|
<tr on:click={() => show(exercice.id)}>
|
||||||
|
{#each fieldsExercices as field}
|
||||||
|
<td>
|
||||||
|
{@html exercice[field]}
|
||||||
|
{#if field == "title" && exercice.wip}
|
||||||
|
<Icon name="cone-striped" />
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
{/await}
|
{/await}
|
||||||
</Container>
|
</Container>
|
||||||
|
15
qa/ui/src/routes/themes/[tid]/[eid]/+layout.js
Normal file
15
qa/ui/src/routes/themes/[tid]/[eid]/+layout.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { getExercice } from '$lib/exercices';
|
||||||
|
import { getExerciceQA } from '$lib/qa.js';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export async function load({ depends, params, parent }) {
|
||||||
|
const { theme } = await parent();
|
||||||
|
|
||||||
|
const exercice = getExercice(params.eid)
|
||||||
|
depends(`api/exercices/${params.eid}`);
|
||||||
|
|
||||||
|
const qaitems = getExerciceQA(params.eid);
|
||||||
|
depends(`api/exercices/${params.eid}/qa`);
|
||||||
|
|
||||||
|
return { exercice, qaitems, theme };
|
||||||
|
}
|
21
qa/ui/src/routes/themes/[tid]/[eid]/+layout.svelte
Normal file
21
qa/ui/src/routes/themes/[tid]/[eid]/+layout.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script>
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import ExerciceLayout from '$lib/components/ExerciceLayout.svelte';
|
||||||
|
import { themes } from '$lib/stores/themes';
|
||||||
|
|
||||||
|
if ($themes.length == 0) {
|
||||||
|
themes.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ExerciceLayout
|
||||||
|
exercice={data.exercice}
|
||||||
|
qaitems={data.qaitems}
|
||||||
|
query_selected={$page.params.qid}
|
||||||
|
theme={data.theme}
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</ExerciceLayout>
|
@ -1,26 +1,34 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Col,
|
||||||
Container,
|
Container,
|
||||||
Icon,
|
Icon,
|
||||||
Spinner,
|
Row,
|
||||||
|
Table,
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import { getThemedExercice } from '$lib/exercices';
|
import QANewItem from '$lib/components/QANewItem.svelte';
|
||||||
import ExerciceQA from '$lib/components/ExerciceQA.svelte';
|
|
||||||
|
|
||||||
let exerciceP = getThemedExercice($page.params.tid, $page.params.eid);
|
export let data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await exerciceP}
|
<Row class="mb-3">
|
||||||
<Container class="mt-2 mb-5">
|
<div
|
||||||
<div class="d-flex justify-content-center">
|
class="col-md-6"
|
||||||
<Spinner size="lg" />
|
style="overflow-y: auto; max-height: 40vh;"
|
||||||
</div>
|
>
|
||||||
</Container>
|
{@html data.exercice.statement.replace("$FILES$", "../files")}
|
||||||
{:then exercice}
|
</div>
|
||||||
<ExerciceQA theme_id={$page.params.tid} {exercice} />
|
<div
|
||||||
{/await}
|
class="col-md-6"
|
||||||
|
style="overflow-y: auto; max-height: 40vh;"
|
||||||
|
>
|
||||||
|
{@html data.exercice.overview.replace("$FILES$", "../files")}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<QANewItem
|
||||||
|
theme={data.theme}
|
||||||
|
exercice={data.exercice}
|
||||||
|
/>
|
||||||
|
21
qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+layout.js
Normal file
21
qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+layout.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export async function load({ params, parent }) {
|
||||||
|
const { exercice, qaitems, theme } = await parent();
|
||||||
|
|
||||||
|
let query_selected = null;
|
||||||
|
for (const qaitem of qaitems) {
|
||||||
|
if (qaitem.id == params.qid) {
|
||||||
|
query_selected = qaitem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query_selected) {
|
||||||
|
throw error(404, {
|
||||||
|
message: 'Not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { exercice, qaitems, query_selected, theme };
|
||||||
|
}
|
11
qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+page.svelte
Normal file
11
qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+page.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import QAItem from '$lib/components/QAItem.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<QAItem
|
||||||
|
exercice={data.exercice}
|
||||||
|
query={data.query_selected}
|
||||||
|
theme={data.theme}
|
||||||
|
/>
|
Loading…
x
Reference in New Issue
Block a user