ui: Almost all interface done with Svelte
This commit is contained in:
parent
9fa1ede69c
commit
7e13cf28bd
54 changed files with 2809 additions and 16 deletions
161
frontend/ui/src/routes/[theme]/[exercice].svelte
Normal file
161
frontend/ui/src/routes/[theme]/[exercice].svelte
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
<script context="module">
|
||||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { themes } from '../../stores/themes.js';
|
||||
|
||||
export async function load({ page, fetch, session, context }) {
|
||||
let exercice = null;
|
||||
|
||||
for (let ex in context.theme.exercices) {
|
||||
if (context.theme.exercices[ex].urlid === page.params.exercice) {
|
||||
exercice = context.theme.exercices[ex];
|
||||
exercice.id = ex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
theme: context.theme,
|
||||
exercice: exercice,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Card,
|
||||
Col,
|
||||
Icon,
|
||||
Row,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import ExerciceDownloads from '../../components/ExerciceDownloads.svelte';
|
||||
import ExerciceFlags from '../../components/ExerciceFlags.svelte';
|
||||
import ExerciceHints from '../../components/ExerciceHints.svelte';
|
||||
import ExerciceSolved from '../../components/ExerciceSolved.svelte';
|
||||
import ExerciceVideo from '../../components/ExerciceVideo.svelte';
|
||||
import ThemeNav from '../../components/ThemeNav.svelte';
|
||||
|
||||
import { my } from '../../stores/my.js';
|
||||
import { settings } from '../../stores/settings.js';
|
||||
|
||||
export let theme;
|
||||
export let exercice;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{exercice.title} - {$settings.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if exercice}
|
||||
<ThemeNav {theme} {exercice} />
|
||||
{/if}
|
||||
|
||||
{#if !$my || !$my.exercices[exercice.id]}
|
||||
<Alert color="warning" class="mt-3" fade={false}>
|
||||
<Icon name="dash-circle-fill" />
|
||||
Vous n'avez pas encore accès à ce défi.
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
{#if exercice}
|
||||
<Card body class="niceborder text-indent my-3">
|
||||
<h3 class="display-4">{exercice.title}</h3>
|
||||
<div>
|
||||
{#each exercice.tags as tag, index}
|
||||
<Badge href="/tags/{tag}" pill color="secondary" class="mx-1 mb-2" >#{tag}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{#if !$my || !$my.exercices[exercice.id]}
|
||||
<p class="lead text-justify">{@html exercice.headline}</p>
|
||||
{:else}
|
||||
<p class="lead text-justify">{@html $my.exercices[exercice.id].statement}</p>
|
||||
{#if $my.exercices[exercice.id].issue}
|
||||
<Alert color="{$my.exercices[exercice.id].issuekind}">
|
||||
{@html $my.exercices[exercice.id].issue}
|
||||
</Alert>
|
||||
{/if}
|
||||
{/if}
|
||||
<hr class="mt-0 mb-4">
|
||||
<Row>
|
||||
<Col>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Gain :</strong>
|
||||
{exercice.gain} {exercice.gain==1?"point":"points"}
|
||||
{#if $settings.firstBlood && exercice.solved < 1}
|
||||
<em>+{$settings.firstBlood * 100}% (prem's)</em>
|
||||
{/if}
|
||||
{#if exercice.curcoeff != 1.0 || $settings.exerciceCurrentCoefficient != 1.0}
|
||||
<em>{#if exercice.curcoeff * $settings.exerciceCurrentCoefficient > 1}+{Math.round((exercice.curcoeff * $settings.exerciceCurrentCoefficient - 1) * 100)}{:else}-{Math.round((1-(exercice.curcoeff * $settings.exerciceCurrentCoefficient)) * 100)}{/if}% (bonus)</em>
|
||||
{/if}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tenté par :</strong>
|
||||
{#if !exercice.tried}
|
||||
aucune équipe
|
||||
{:else}
|
||||
{exercice.tried} {exercice.tried == 1?"équipe":"équipes"}
|
||||
{#if $my && $my.exercices[exercice.id].total_tries}
|
||||
(cumulant {$my.exercices[exercice.id].total_tries} {$my.exercices[exercice.id].total_tries == 1?"tentative":"tentatives"})
|
||||
{/if}
|
||||
{/if}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Résolu par :</strong>
|
||||
{#if !exercice.solved}
|
||||
aucune équipe
|
||||
{:else}
|
||||
{exercice.solved} {exercice.solved == 1?"équipe":"équipes"}
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
</Col>
|
||||
{#if $my && $my.team_id}
|
||||
<Col>
|
||||
{#if $settings.acceptNewIssue}
|
||||
<a href="/issues/?eid={exercice.id}" class="float-end btn btn-sm btn-warning">
|
||||
<Icon name="bug" />
|
||||
Rapporter une anomalie sur ce défi
|
||||
</a>
|
||||
{/if}
|
||||
{#if $settings.QAenabled}
|
||||
<a href="/qa/exercices/{exercice.id}" class="float-end btn btn-sm btn-info" target="_self">
|
||||
<Icon name="bug" />
|
||||
Voir les éléments QA sur ce défi
|
||||
</a>
|
||||
{/if}
|
||||
</Col>
|
||||
{/if}
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{#if $my && $my.exercices[exercice.id]}
|
||||
<Row class="mt-4">
|
||||
{#if $my.exercices[exercice.id].files || $my.exercices[exercice.id].hints}
|
||||
<Col lg class="mb-5">
|
||||
{#if $my.exercices[exercice.id].files}
|
||||
<ExerciceDownloads files={$my.exercices[exercice.id].files} />
|
||||
{/if}
|
||||
{#if $my.exercices[exercice.id].hints}
|
||||
<ExerciceHints hints={$my.exercices[exercice.id].hints} />
|
||||
{/if}
|
||||
</Col>
|
||||
{/if}
|
||||
<Col lg class="mb-5">
|
||||
{#if !$my.exercices[exercice.id].solved}
|
||||
<ExerciceFlags {exercice} flags={$my.exercices[exercice.id].flags} />
|
||||
{:else}
|
||||
<ExerciceSolved theme={theme} exercice={$my.exercices[exercice.id]} />
|
||||
{/if}
|
||||
{#if $my.exercices[exercice.id].video_uri}
|
||||
<ExerciceVideo uri={$my.exercices[exercice.id].video_uri} />
|
||||
{/if}
|
||||
</Col>
|
||||
</Row>
|
||||
{/if}
|
||||
{/if}
|
||||
94
frontend/ui/src/routes/[theme]/__layout.svelte
Normal file
94
frontend/ui/src/routes/[theme]/__layout.svelte
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<script context="module">
|
||||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { themes } from '../../stores/themes.js';
|
||||
|
||||
export async function load({ page, fetch, session, context }) {
|
||||
const thms = get_store_value(themes);
|
||||
|
||||
let theme = null;
|
||||
|
||||
for (let th in thms) {
|
||||
if (thms[th].urlid === page.params.theme) {
|
||||
theme = thms[th];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
context: {
|
||||
...context,
|
||||
theme: theme,
|
||||
}, props: {
|
||||
theme: theme,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Container,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { settings } from '../../stores/settings.js';
|
||||
|
||||
export let theme = null;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{theme.name} - {$settings.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if theme}
|
||||
<div style="background-image: url({theme.image})" class="page-header">
|
||||
<Container class="text-primary">
|
||||
<h1 class="display-2">
|
||||
<a href="/{theme.urlid}">{theme.name}</a>
|
||||
</h1>
|
||||
<h2>{@html theme.authors}</h2>
|
||||
</Container>
|
||||
<div class="headerfade"></div>
|
||||
</div>
|
||||
{/if}
|
||||
<Container>
|
||||
<slot></slot>
|
||||
</Container>
|
||||
|
||||
<style>
|
||||
.page-header {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
margin-bottom: -15rem;
|
||||
}
|
||||
.page-header h1 {
|
||||
text-shadow: 0 0 15px rgba(255,255,255,0.95), 0 0 5px rgb(255,255,255)
|
||||
}
|
||||
.page-header h1, .page-header h1 a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
.page-header h2 {
|
||||
font-size: 100%;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.9)
|
||||
}
|
||||
.page-header h2, .page-header h2 a {
|
||||
color: #4eaee6;
|
||||
}
|
||||
.page-header h2 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.page-header h1 {
|
||||
padding-top: 4rem;
|
||||
text-align: center;
|
||||
}
|
||||
.page-header h2 {
|
||||
padding-bottom: 14rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-header .headerfade {
|
||||
background: linear-gradient(transparent 0%, rgb(233,236,239) 100%);
|
||||
height: 3rem;
|
||||
}
|
||||
</style>
|
||||
103
frontend/ui/src/routes/[theme]/index.svelte
Normal file
103
frontend/ui/src/routes/[theme]/index.svelte
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<script context="module">
|
||||
export async function load({ page, fetch, session, context }) {
|
||||
return {
|
||||
props: {
|
||||
theme: context.theme,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Icon,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { my } from '../../stores/my.js';
|
||||
|
||||
export let theme = null;
|
||||
</script>
|
||||
|
||||
{#if theme && theme.exercices}
|
||||
<div class="card niceborder text-indent mt-2 mb-4">
|
||||
|
||||
<div class="card-body bg-dark text-light">
|
||||
<p class="mt-4 mx-3 card-text lead text-justify">{@html theme.headline}</p>
|
||||
<p class="mb-4 mx-3 card-text text-justify">{@html theme.intro}</p>
|
||||
</div>
|
||||
|
||||
<ul class="list-group">
|
||||
{#each Object.keys(theme.exercices) as k, index}
|
||||
<li
|
||||
class="list-group-item"
|
||||
class:list-group-item-action={$my && $my.exercices[k]}
|
||||
on:click={goto(`/${theme.urlid}/${theme.exercices[k].urlid}`)}
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-1" style="margin-top: -0.5rem; margin-bottom: -0.5rem; text-align: right; border-right: 5px solid #{$my && $my.exercices[k] && $my.exercices[k].solved ? '62c462' : 'aaa'}">
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<div style="position: absolute; margin-left: calc(var(--bs-gutter-x) * -.5 - 15px); margin-top: -0.5rem;">
|
||||
<svg style="height: 50px; width: 23px;">
|
||||
<rect
|
||||
style="fill:#{$my && $my.exercices[k] && (index < 1 || $my.exercices[Object.keys(theme.exercices)[index-1]].solved) ? '62c462' : 'aaa'}"
|
||||
width="5"
|
||||
height="30"
|
||||
x="10"
|
||||
y="0" />
|
||||
<path
|
||||
style="fill:#{$my && $my.exercices[k] ? ($my.exercices[k].solved ? '62c462' : (theme.exercices[k].curcoeff > 1.0 ? 'f89406' : '5bc0de')) : '555'}"
|
||||
d="m 22,20 a 9.5700617,9.5700617 0 0 1 -9.5690181,9.57006 9.5700617,9.5700617 0 0 1 -9.57110534,-9.56797 9.5700617,9.5700617 0 0 1 9.56692984,-9.57215 9.5700617,9.5700617 0 0 1 9.5731926,9.56588" />
|
||||
</svg>
|
||||
</div>
|
||||
{#each theme.exercices[k].tags as tag, idx}
|
||||
<Badge href="/tags/{tag}" pill color="secondary" class="mx-1 float-end">#{tag}</Badge>
|
||||
{/each}
|
||||
<h5 class="fw-bold">
|
||||
{#if $my && $my.exercices[k]}
|
||||
{theme.exercices[k].title}
|
||||
{:else}
|
||||
<span style="white-space: nowrap">
|
||||
<Icon name="lock-fill" aria-hidden="true" title="Vous n'avez pas encore accès à ce défi" />
|
||||
{theme.exercices[k].title}
|
||||
</span>
|
||||
{/if}
|
||||
{#if theme.exercices[k].curcoeff > 1.0}
|
||||
<Icon name="gift" aria-hidden="true" title="Un bonus est actuellement appliqué lors de la résolution de ce défi" />
|
||||
{/if}
|
||||
</h5>
|
||||
<p>{@html theme.exercices[k].headline}</p>
|
||||
</div>
|
||||
<div class="d-none d-md-block col-1">
|
||||
{#if $my && $my.exercices[k]}
|
||||
<a class="float-right" href="{theme.urlid}/{theme.exercices[k].urlid}" style="font-size: 3rem">
|
||||
<Icon name="chevron-right" aria-hidden="true" />
|
||||
</a>
|
||||
{:else}
|
||||
<span class="float-right" style="font-size: 3rem">
|
||||
<Icon name="chevron-right" aria-hidden="true" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{:else}
|
||||
<Alert color="danger" fade={false}>
|
||||
<Icon name="dash-circle-fill" />
|
||||
Ce scénario n'existe pas.
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.list-group-item-action {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in a new issue