qa: Refactor layout

This commit is contained in:
nemunaire 2023-07-26 15:15:49 +02:00
parent 859b6a318e
commit c13da8b574
19 changed files with 426 additions and 296 deletions

View 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>

View 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>

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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;

View 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 };
}

View 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>

View File

@ -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}
/>

View 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 };
}

View 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}
/>

View File

@ -1 +0,0 @@
<slot></slot>

View 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 };
}

View File

@ -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>

View 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 };
}

View 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>

View File

@ -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}
/>

View 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 };
}

View 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}
/>