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>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
@ -20,19 +20,19 @@
|
||||
import { ToastsStore } from '$lib/stores/toasts';
|
||||
import { viewIdx } from '$lib/stores/todo';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let query_selected;
|
||||
export let theme = null;
|
||||
export let exercice = null;
|
||||
export let query;
|
||||
|
||||
let query_commentsP;
|
||||
let thumbs = [];
|
||||
let thumb_me = [];
|
||||
let has_comments = false;
|
||||
$: updateComments(query_selected)
|
||||
$: updateComments(query)
|
||||
|
||||
function updateComments(query_selected) {
|
||||
if (query_selected) {
|
||||
query_commentsP = getQAComments(query_selected.id);
|
||||
function updateComments(query) {
|
||||
if (query) {
|
||||
query_commentsP = getQAComments(query.id);
|
||||
query_commentsP.then((comments) => {
|
||||
thumbs = [];
|
||||
thumb_me = [];
|
||||
@ -65,8 +65,8 @@
|
||||
let submissionInProgress = false;
|
||||
function addComment() {
|
||||
submissionInProgress = true;
|
||||
newComment.save(query_selected.id).then(() => {
|
||||
query_commentsP = getQAComments(query_selected.id);
|
||||
newComment.save(query.id).then(() => {
|
||||
query_commentsP = getQAComments(query.id);
|
||||
newComment = new QAComment();
|
||||
submissionInProgress = false;
|
||||
}).catch((err) => {
|
||||
@ -78,8 +78,8 @@
|
||||
}
|
||||
|
||||
function updateQA() {
|
||||
query_selected.save().then(() => {
|
||||
dispatch("update-queries");
|
||||
query.save().then(() => {
|
||||
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||
}).catch((err) => {
|
||||
ToastsStore.addErrorToast({
|
||||
msg: err,
|
||||
@ -88,9 +88,9 @@
|
||||
}
|
||||
|
||||
function solveQA() {
|
||||
query_selected.solved = new Date();
|
||||
query_selected.save().then(() => {
|
||||
dispatch("update-queries");
|
||||
query.solved = new Date();
|
||||
query.save().then(() => {
|
||||
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||
}).catch((err) => {
|
||||
ToastsStore.addErrorToast({
|
||||
msg: err,
|
||||
@ -99,9 +99,9 @@
|
||||
}
|
||||
|
||||
function reopenQA() {
|
||||
query_selected.solved = null;
|
||||
query_selected.save().then(() => {
|
||||
dispatch("update-queries");
|
||||
query.solved = null;
|
||||
query.save().then(() => {
|
||||
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||
}).catch((err) => {
|
||||
ToastsStore.addErrorToast({
|
||||
msg: err,
|
||||
@ -110,9 +110,9 @@
|
||||
}
|
||||
|
||||
function closeQA() {
|
||||
query_selected.closed = new Date();
|
||||
query_selected.save().then(() => {
|
||||
dispatch("update-queries");
|
||||
query.closed = new Date();
|
||||
query.save().then(() => {
|
||||
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||
}).catch((err) => {
|
||||
ToastsStore.addErrorToast({
|
||||
msg: err,
|
||||
@ -120,10 +120,10 @@
|
||||
})
|
||||
}
|
||||
|
||||
function deleteQA() {
|
||||
query_selected.delete().then(() => {
|
||||
query_selected = null;
|
||||
dispatch("update-queries");
|
||||
async function deleteQA() {
|
||||
query.delete().then(async () => {
|
||||
await invalidate(`api/exercices/${exercice.id}/qa`);
|
||||
goto(theme?`themes/${theme.id}/${exercice.id}`:`exercices/${exercice.id}`);
|
||||
}).catch((err) => {
|
||||
ToastsStore.addErrorToast({
|
||||
msg: err,
|
||||
@ -134,15 +134,15 @@
|
||||
function deleteMyThumbs() {
|
||||
if (thumb_me.length) {
|
||||
for (const c of thumb_me) {
|
||||
c.delete(query_selected.id);
|
||||
c.delete(query.id);
|
||||
}
|
||||
dispatch("update-queries");
|
||||
updateComments(query);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteComment(comment) {
|
||||
comment.delete(query_selected.id).then(() => {
|
||||
dispatch("update-queries");
|
||||
comment.delete(query.id).then(() => {
|
||||
updateComments(query);
|
||||
}).catch((err) => {
|
||||
ToastsStore.addErrorToast({
|
||||
msg: err,
|
||||
@ -151,20 +151,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if query_selected}
|
||||
{#if query}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<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>
|
||||
{#if $auth && !query_selected.solved && $viewIdx[query_selected.id_exercice]}
|
||||
{#if $auth && !query.solved && $viewIdx[query.id_exercice]}
|
||||
<Button on:click={solveQA} color="success">
|
||||
<Icon name="check" />
|
||||
Marquer comme résolu
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $auth && $auth.id_team == query_selected.id_team}
|
||||
{#if query_selected.solved && !query_selected.closed && (query_selected.subject != "RAS" || query_selected.state != "ok")}
|
||||
{#if $auth && $auth.id_team == query.id_team}
|
||||
{#if query.solved && !query.closed && (query.subject != "RAS" || query.state != "ok")}
|
||||
<Button on:click={closeQA} color="success">
|
||||
<Icon name="check" />
|
||||
Valider la résolution
|
||||
@ -174,7 +174,7 @@
|
||||
Réouvrir
|
||||
</Button>
|
||||
{/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">
|
||||
<Icon name="trash-fill" />
|
||||
Supprimer
|
||||
@ -192,10 +192,10 @@
|
||||
Qui ?
|
||||
</div>
|
||||
<div class="value">
|
||||
{query_selected.user.split("@")[0]}
|
||||
{query.user.split("@")[0]}
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
(team #{query_selected.id_team})
|
||||
(team #{query.id_team})
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
@ -205,7 +205,7 @@
|
||||
<div class="heading">
|
||||
État
|
||||
</div>
|
||||
<BadgeState class="value" state={query_selected.state} />
|
||||
<BadgeState class="value" state={query.state} />
|
||||
<div
|
||||
class="text-muted"
|
||||
title={thumbs.join(', ')}
|
||||
@ -227,7 +227,7 @@
|
||||
Date de création
|
||||
</div>
|
||||
<div class="value">
|
||||
<DateFormat date={query_selected.creation} dateStyle="long" timeStyle="medium" />
|
||||
<DateFormat date={query.creation} dateStyle="long" timeStyle="medium" />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
@ -238,8 +238,8 @@
|
||||
Date de résolution
|
||||
</div>
|
||||
<div class="value">
|
||||
{#if query_selected.solved}
|
||||
<DateFormat date={query_selected.solved} dateStyle="long" timeStyle="medium" />
|
||||
{#if query.solved}
|
||||
<DateFormat date={query.solved} dateStyle="long" timeStyle="medium" />
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
@ -253,8 +253,8 @@
|
||||
Date de clôture
|
||||
</div>
|
||||
<div class="value">
|
||||
{#if query_selected.closed}
|
||||
<DateFormat date={query_selected.closed} dateStyle="long" timeStyle="medium" />
|
||||
{#if query.closed}
|
||||
<DateFormat date={query.closed} dateStyle="long" timeStyle="medium" />
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
|
@ -1,4 +1,6 @@
|
||||
<script>
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
@ -11,11 +13,10 @@
|
||||
export { className as class };
|
||||
let className = '';
|
||||
|
||||
export let theme = null;
|
||||
export let exercice = { };
|
||||
export let query_selected = null;
|
||||
|
||||
let queriesP;
|
||||
$: queriesP = getExerciceQA(exercice.id);
|
||||
export let queries = [];
|
||||
|
||||
let thumbInProgress = null;
|
||||
function thumbUp(qid) {
|
||||
@ -25,56 +26,50 @@
|
||||
});
|
||||
thumb.save(qid).then(() => {
|
||||
thumbInProgress = null;
|
||||
invalidate(`api/exercices/${exercice.id}/qa`);
|
||||
})
|
||||
}
|
||||
|
||||
function updateQueries() {
|
||||
queriesP = getExerciceQA(exercice.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await queriesP}
|
||||
{:then queries}
|
||||
<div class="list-group {className}" class:list-group-flush={true}>
|
||||
{#if queries.length}
|
||||
{#each queries as q (q.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||||
class:active={query_selected && q.id == query_selected.id}
|
||||
aria-current="true"
|
||||
on:click={() => query_selected = q}
|
||||
>
|
||||
<div class="text-truncate">
|
||||
<BadgeState state={q.state} />
|
||||
{#if !q.solved}
|
||||
<strong>{q.subject}</strong>
|
||||
{:else if !q.closed}
|
||||
{q.subject}
|
||||
{: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>
|
||||
<div class="list-group {className}" class:list-group-flush={true}>
|
||||
{#if queries.length}
|
||||
{#each queries as q (q.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||||
class:active={query_selected && q.id == query_selected}
|
||||
aria-current="true"
|
||||
on:click={() => goto(theme?`themes/${theme.id}/${exercice.id}/${q.id}`:`exercices/${exercice.id}/${q.id}`)}
|
||||
>
|
||||
<div class="text-truncate">
|
||||
<BadgeState state={q.state} />
|
||||
{#if !q.solved}
|
||||
<strong>{q.subject}</strong>
|
||||
{:else if !q.closed}
|
||||
{q.subject}
|
||||
{:else}
|
||||
<s>{q.subject}</s>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="fw-bold text-center">
|
||||
Aucune requête enregistrée
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/await}
|
||||
</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}
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="fw-bold text-center">
|
||||
Aucune requête enregistrée
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { invalidate, goto } from '$app/navigation';
|
||||
|
||||
import {
|
||||
Button,
|
||||
@ -9,8 +9,7 @@
|
||||
import { QAQuery, QAStates } from '$lib/qa';
|
||||
import { ToastsStore } from '$lib/stores/toasts';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let theme = null;
|
||||
export let exercice = {};
|
||||
|
||||
let newQuery = new QAQuery();
|
||||
@ -25,10 +24,11 @@
|
||||
newQuery.content = "Difficulté : " + newQuery.difficulty + "\n" + newQuery.content;
|
||||
if (newQuery.timecount)
|
||||
newQuery.content = "Temps passé : " + newQuery.timecount + "\n" + newQuery.content;
|
||||
newQuery.save().then((res) => {
|
||||
dispatch("new-query");
|
||||
newQuery.save().then(async (res) => {
|
||||
newQuery = new QAQuery();
|
||||
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) => {
|
||||
creationInProgress = false;
|
||||
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>
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Container,
|
||||
Icon,
|
||||
Spinner,
|
||||
Row,
|
||||
Table,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { getExercice } from '$lib/exercices';
|
||||
import ExerciceQA from '$lib/components/ExerciceQA.svelte';
|
||||
import QANewItem from '$lib/components/QANewItem.svelte';
|
||||
|
||||
let exerciceP = getExercice($page.params.eid);
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await exerciceP}
|
||||
<Container class="mt-2 mb-5">
|
||||
<div class="d-flex justify-content-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
</Container>
|
||||
{:then exercice}
|
||||
<ExerciceQA {exercice} />
|
||||
{/await}
|
||||
<Row class="mb-3">
|
||||
<div
|
||||
class="col-md-6"
|
||||
style="overflow-y: auto; max-height: 40vh;"
|
||||
>
|
||||
{@html data.exercice.statement.replace("$FILES$", "../files")}
|
||||
</div>
|
||||
<div
|
||||
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,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { getTheme } from '$lib/themes';
|
||||
import { fieldsExercices, getThemedExercices } from '$lib/exercices';
|
||||
|
||||
export let data;
|
||||
let query = "";
|
||||
|
||||
function show(id) {
|
||||
@ -21,67 +21,61 @@
|
||||
</script>
|
||||
|
||||
<Container class="mt-2 mb-5">
|
||||
{#await getTheme($page.params.tid)}
|
||||
<div class="d-flex justify-content-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
{:then theme}
|
||||
<div class="d-flex align-items-end">
|
||||
<Button
|
||||
class="align-self-center"
|
||||
color="link"
|
||||
on:click={() => goto('themes/')}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
<h2>
|
||||
{theme.name}
|
||||
</h2>
|
||||
<small class="m-2 mb-3 text-muted text-truncate">{@html theme.authors}</small>
|
||||
</div>
|
||||
<div class="d-flex align-items-end">
|
||||
<Button
|
||||
class="align-self-center"
|
||||
color="link"
|
||||
on:click={() => goto('themes/')}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
<h2>
|
||||
{data.theme.name}
|
||||
</h2>
|
||||
<small class="m-2 mb-3 text-muted text-truncate">{@html data.theme.authors}</small>
|
||||
</div>
|
||||
|
||||
{#if theme.intro}
|
||||
<Container class="text-muted" style="overflow-y: auto; max-height: 34vh">
|
||||
{@html theme.intro.replace("$FILES$", "../files")}
|
||||
</Container>
|
||||
{/if}
|
||||
{#if data.theme.intro}
|
||||
<Container class="text-muted" style="overflow-y: auto; max-height: 34vh">
|
||||
{@html data.theme.intro.replace("$FILES$", "../files")}
|
||||
</Container>
|
||||
{/if}
|
||||
|
||||
{#await getThemedExercices($page.params.tid)}
|
||||
{:then exercices}
|
||||
<h3 class="mt-2">
|
||||
Défis ({exercices.length})
|
||||
</h3>
|
||||
{#await getThemedExercices($page.params.tid)}
|
||||
{:then exercices}
|
||||
<h3 class="mt-2">
|
||||
Défis ({exercices.length})
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<input type="search" class="form-control form-control-sm" placeholder="Search" bind:value={query} autofocus>
|
||||
</p>
|
||||
<Table class="table-hover table-bordered table-striped table-sm">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
{#each fieldsExercices as field}
|
||||
<th>
|
||||
{field}
|
||||
</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}
|
||||
<p>
|
||||
<input type="search" class="form-control form-control-sm" placeholder="Search" bind:value={query} autofocus>
|
||||
</p>
|
||||
<Table class="table-hover table-bordered table-striped table-sm">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
{#each fieldsExercices as field}
|
||||
<th>
|
||||
{field}
|
||||
</th>
|
||||
{/each}
|
||||
</tbody>
|
||||
</Table>
|
||||
{/await}
|
||||
</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}
|
||||
</tbody>
|
||||
</Table>
|
||||
{/await}
|
||||
</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>
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Container,
|
||||
Icon,
|
||||
Spinner,
|
||||
Row,
|
||||
Table,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { getThemedExercice } from '$lib/exercices';
|
||||
import ExerciceQA from '$lib/components/ExerciceQA.svelte';
|
||||
import QANewItem from '$lib/components/QANewItem.svelte';
|
||||
|
||||
let exerciceP = getThemedExercice($page.params.tid, $page.params.eid);
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#await exerciceP}
|
||||
<Container class="mt-2 mb-5">
|
||||
<div class="d-flex justify-content-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
</Container>
|
||||
{:then exercice}
|
||||
<ExerciceQA theme_id={$page.params.tid} {exercice} />
|
||||
{/await}
|
||||
<Row class="mb-3">
|
||||
<div
|
||||
class="col-md-6"
|
||||
style="overflow-y: auto; max-height: 40vh;"
|
||||
>
|
||||
{@html data.exercice.statement.replace("$FILES$", "../files")}
|
||||
</div>
|
||||
<div
|
||||
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