ui: Almost all interface done with Svelte

This commit is contained in:
nemunaire 2021-08-30 12:46:18 +02:00
commit 7e13cf28bd
54 changed files with 2809 additions and 16 deletions

View file

@ -0,0 +1,57 @@
<script>
import {
Alert,
Card,
CardBody,
CardTitle,
Col,
Icon,
Row,
} from 'sveltestrap';
export { className as class };
export let theme = {};
export let exercice = null;
let className = '';
</script>
<div class="theme-card h-100">
<Card
class="text-light h-100 rounded-3 niceborder {className}"
color="dark"
on:click
>
{#if theme.image}
<div class="card-img-top" style="background-image: url({ theme.image.substr(0, theme.image.length-3) }thumb.jpg)"></div>
{/if}
<CardBody class="text-indent">
<CardTitle class="fw-bolder">
{#if exercice}{exercice.title}{:else}{theme.name}{/if}
</CardTitle>
<p class="card-text text-justify">{#if exercice}{@html exercice.headline}{:else}{@html theme.headline}{/if}</p>
</CardBody>
</Card>
</div>
<style>
.theme-card {
cursor: pointer;
transition: transform 250ms;
}
.theme-card:hover {
transform: scale(1.07);
}
.card-img-top {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.theme-card .card-img-top {
height: 10rem;
}
p {
color: #ccc;
}
</style>

View file

@ -0,0 +1,46 @@
<script>
import {
Card,
CardBody,
CardHeader,
CardText,
Icon,
ListGroup,
ListGroupItem,
} from 'sveltestrap';
export let files = [];
</script>
{#if files.length}
<Card class="mb-2">
<CardHeader>
<Icon name="download" />
Téléchargements
</CardHeader>
<CardBody class="text-indent">
<CardText class="text-danger text-justify">
<strong>Attention&nbsp;:</strong> puisqu'il s'agit de captures effectuées dans le but de découvrir si des actes malveillants ont été commis, les contenus qui sont téléchargeables <em>peuvent</em> contenir du contenu malveillant&nbsp;!
</CardText>
</CardBody>
<ListGroup>
{#each files as file (file.id)}
<ListGroupItem tag="a" href="{file.path}" target="_self" class="d-flex">
<h1><Icon name="arrow-down-circle" /></h1>
<div>
<h4 class="fw-bold"><samp>{file.name}</samp></h4>
<nobr>
Taille&nbsp;:
<span title="{file.size} octets">{file.size}</span>
</nobr>
&ndash;
<nobr>
<span title="blake2.net">b2sum</span>&nbsp;:
<samp class="cksum" title="{file.checksum}">{file.checksum}</samp>
</nobr>
</div>
</ListGroupItem>
{/each}
</ListGroup>
</Card>
{/if}

View file

@ -0,0 +1,74 @@
<script>
import {
Button,
Card,
CardBody,
CardHeader,
CardText,
Icon,
ListGroup,
ListGroupItem,
} from 'sveltestrap';
export let exercice = {};
export let flags = [];
function submitFlags(event) {
console.log(event);
}
let sberr = "";
let message = "";
</script>
<Card class="border-danger mb-2">
<CardHeader class="bg-danger">
<Icon name="flag-fill" />
Faire son rapport
</CardHeader>
{#if exercice.tries || exercice.submitted || sberr}
<ListGroup>
{#if exercice.solved_time && exercice.tries}
<ListGroupItem class="text-warning">
{exercice.tries} {exercice.tries==1?"tentative effectuée":"tentatives effectuées"}.
Dernière solution envoyée à {exercice.solved_time}.
</ListGroupItem>
{/if}
{#if exercice.solve_dist}
<ListGroupItem>
{exercice.solve_dist} {exercice.solve_dist == 1?"réponse erronée":"réponses erronées"}.
</ListGroupItem>
{/if}
{#if exercice.submitted || sberr}
<ListGroupItem>
{#if !sberr}
<strong>Votre solution a bien été envoyée !</strong>
{:else}
<strong>{sberr}</strong> {message}
{/if}
</ListGroupItem>
{/if}
{#if exercice.timeouted}
<ListGroupItem class="text-danger">
<strong>Oops</strong>
La requête a dépassé le délai d'attente. Vous devriez réessayer dans quelques instant&hellip;
</ListGroupItem>
{/if}
</ListGroup>
{/if}
{#if !exercice.submitted || sberr}
<CardBody>
<form on:submit|preventDefault={submitFlags}>
{JSON.stringify(flags)}
<div class="form-group mt-2">
<Button
type="submit"
color="danger"
>
Soumettre
</Button>
</div>
</form>
</CardBody>
{/if}
</Card>

View file

@ -0,0 +1,70 @@
<script>
import {
Card,
CardBody,
CardHeader,
CardText,
Icon,
ListGroup,
ListGroupItem,
} from 'sveltestrap';
import { settings } from '../stores/settings.js';
export let hints = [];
let hinterror = "";
</script>
{#if hints.length}
<Card class="mb-2">
<CardHeader class="bg-info">
<Icon name="lightbulb-fill" />
Indices
</CardHeader>
{#if hinterror}
<CardBody>
<CardText class="text-danger">
{hinterror}
</CardText>
</CardBody>
{/if}
<ListGroup>
{#each hints as hint (hint.id)}
<ListGroupItem tag="a" href="{hint.file}" target="_self" class="d-flex">
{#if hint.file}
<h1><Icon name="arrow-down-circle" /></h1>
{/if}
<div>
{#if !(hint.content || hint.file)}
<button type="button" ng-click="hsubmit(hint)" class="float-end btn btn-info" class:disabled={hint.submitted}>
<Icon name="lock" aria-hidden="true" />
Débloquer
</button>
{/if}
{#if !hint.file && hint.hidden}
<button type="button" ng-click="hint.hidden = false;" class="float-end btn btn-info">
<Icon name="lock" aria-hidden="true" />
Afficher
</button>
{/if}
<h4 class="fw-bold">{hint.name}</h4>
{#if hint.file}
<p>
Cliquez ici pour télécharger l'indice.<br>
b2sum&nbsp;:
<samp class="cksum" title="Somme de contrôle BLAKE2b : {hint.content}">{hint.content}</samp>
</p>
{:else if hint.content && !hint.hidden}
<p>{@html hint.content}</p>
{:else}
<p>
Débloquer cet indice vous fera perdre {hint.cost * settings.hintCurrentCoefficient} {hint.cost * settings.hintCurrentCoefficient==1?"point":"points"}.
</p>
{/if}
</div>
</ListGroupItem>
{/each}
</ListGroup>
</Card>
{/if}

View file

@ -0,0 +1,41 @@
<script>
import {
Card,
CardBody,
CardHeader,
CardText,
Icon,
ListGroup,
ListGroupItem,
} from 'sveltestrap';
export let theme = {};
export let exercice = {};
</script>
<Card class="border-success mb-2">
<CardHeader class="bg-success">
<Icon name="flag-fill" />
Défi réussi&nbsp;!
</CardHeader>
<CardBody class="text-indent">
<CardText>
{#if exercice.solved_rank}
Vous êtes la {exercice.solved_rank}<sup>{exercice.solved_rank==1?"re":"e"}</sup> équipe à avoir résolu ce défi à {exercice.solved_time}.
{:else}
Bravo, vous avez résolu ce défi à {exercice.solved_time}.
{/if}
Vous avez marqué {exercice.gain} {exercice.gain==1?"point":"points"}&nbsp;!
</CardText>
{#if exercice.finished}
<hr>
<CardText>{@html exercice.finished}</CardText>
{#if exercice.next}
<hr>
{/if}
{/if}
{#if theme.exercices[exercice.id].next}
<a href="/{theme.urlid}/{theme.exercices[theme.exercices[exercice.id].next].urlid}" class="btn btn-success">Passer au défi suivant</a>
{/if}
</CardBody>
</Card>

View file

@ -0,0 +1,26 @@
<script>
import {
Card,
CardBody,
CardHeader,
CardText,
Icon,
ListGroup,
ListGroupItem,
} from 'sveltestrap';
export let uri = "";
</script>
<Card class="border-success mb-2">
<CardHeader class="bg-success">
<Icon name="laptop-fill" />
Solution du défi
</CardHeader>
<CardBody class="text-indent">
<div class="embed-responsive embed-responsive-16by9">
<iframe type="text/html" src="{uri}" class="embed-responsive-item" title="Vidéo de résolution">
Regardez la vidéo de résolution de ce défi&nbsp;: <a href="{uri}">{uri}</a>.
</iframe>
</CardBody>
</Card>

View file

@ -0,0 +1,47 @@
<script>
import {
Button,
Icon,
} from 'sveltestrap';
import { issues, issues_idx } from '../stores/issues.js';
export let exercice = null;
export let issue = { };
</script>
<form on:submit|preventDefault>
{#if exercice || issue.id_exercice}
<div class="row mb-3">
<label for="idExercice" class="col-sm-2 col-form-label">Défi</label>
<div class="col-sm-10">
{#if exercice.id}
<input type="text" readonly class="form-control-plaintext" id="idExercice" value={exercice.title}>
{:else}
<input type="text" readonly class="form-control-plaintext" id="idExercice" value="{issue.id_exercice}">
{/if}
</div>
</div>
{/if}
<div class="row mb-3">
<label for="subject" class="col col-form-label">Objet</label>
<div class="col-sm-10">
{#if issue.id && $issues_idx[issue.id]}
<input type="text" readonly class="form-control-plaintext" id="subject" value="Re: {$issues_idx[issue.id].subject}">
{:else}
<input type="text" class="form-control" id="subject" bind:value={issue.subject} placeholder="Intitulé succinct">
{/if}
</div>
</div>
<div class="row mb-3">
<label for="description" class="col col-form-label">Description</label>
<div class="col-sm-10">
<textarea class="form-control" id="description" bind:value={issue.description} placeholder="Décrivez en détail votre problème ici. Si nécessaire, incluez un lien vers une capture d'écran montrant votre problème."></textarea>
</div>
</div>
<Button type="submit" color="warning" class="float-end">
Envoyer le rapport
</Button>
</form>

View file

@ -0,0 +1,112 @@
<script>
import {
Badge,
Button,
ButtonGroup,
Col,
Collapse,
Container,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
Icon,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem,
NavLink,
Progress,
Row,
} from 'sveltestrap';
import { my } from '../stores/my.js';
import { teams } from '../stores/teams.js';
import { settings, time } from '../stores/settings.js';
import HeaderClock from './HeaderClock.svelte';
import HeaderIssues from './HeaderIssues.svelte';
import HeaderPartners from './HeaderPartners.svelte';
import NavThemes from './NavThemes.svelte';
import NavTags from './NavTags.svelte';
let isOpen = false;
function handleUpdate(event) {
isOpen = event.detail.isOpen;
}
</script>
<div class="container-fluid bg-dark" style="max-height: 15vh;">
<div style="height: 100%; max-height: inherit; width: 98%; position: absolute">
<Container class="d-flex justify-content-center align-items-center text-light" style="height: 100%; max-height: inherit">
<HeaderClock />
</Container>
</div>
<Container class="d-flex justify-content-between p-1" style="max-height: inherit">
<a href="/">
<img src="/img/fic.png" alt="Forum International de la Cybersécurité" class="h-100">
</a>
<HeaderPartners />
</Container>
</div>
<Navbar class="sticky-top" color="dark" dark expand="md">
<NavbarToggler on:click={() => (isOpen = !isOpen)} />
<Collapse {isOpen} navbar expand="md" on:update={handleUpdate}>
<Nav navbar>
<NavItem>
<NavLink href="/">
<Icon name="box-seam" />
Accueil
</NavLink>
</NavItem>
<NavThemes />
<NavTags />
{#if $settings && $settings.end - $settings.start >= 0}
<NavItem>
<NavLink href="/rank">
<Icon name="sort-down" />
Classement
</NavLink>
</NavItem>
{/if}
<HeaderIssues />
<NavItem>
<NavLink href="/rules">
<Icon name="signpost-split" />
Aide
</NavLink>
</NavItem>
</Nav>
<Nav class="ms-auto text-light" navbar>
{#if $my && $my.team_id}
<NavItem>
{$my.score} {$my.score === 1 ? 'point' : 'points'}
{#if $teams[$my.team_id].rank}
&ndash; {$teams[$my.team_id].rank}<sup>e</sup> sur {Object.keys($teams).length}
{/if}
</NavItem>
{/if}
<NavItem class="ms-2">
{#if !$my}
<Badge href="/register" color="warning">
Inscription
</Badge>
{:else if $my.team_id}
<Badge href="/edit" style="background-color: {$teams[$my.team_id].color} !important; color: {$teams[$my.team_id].color};">
<span class="teamname">{$my.name}</span>
</Badge>
{/if}
</NavItem>
</Nav>
</Collapse>
</Navbar>
<Progress value={$time.progression * 100} color="info" style="height: 5px; border-radius: 0;" />
<style>
.teamname {
-webkit-filter: invert(100%);
filter: invert(100%);
}
</style>

View file

@ -0,0 +1,108 @@
<script>
import {
ButtonGroup,
Icon,
} from 'sveltestrap';
import { settings, time } from '../stores/settings.js';
</script>
{#if $settings}
{#if $settings.end - $settings.start > 0}
<div
class="clock display-2"
class:expired={$time.expired}
class:end={$time.end}
class:wait={$time.startIn}
>
{#if $time.seconds}
<span id="hours">
{$time.hours}
</span>
<span class="point">
:
</span>
<span id="minutes">
{$time.minutes}
</span>
<span class="point">
:
</span>
<span id="seconds">
{$time.seconds}
</span>
{:else}
Chargement
{/if}
</div>
{:else}
<div class="d-flex h-100 justify-content-center align-items-center">
<ButtonGroup size="lg">
<a
href="/"
class="btn btn-light"
>
<Icon name="ui-checks-grid" />
Accueil
</a>
<a
href="/rank"
class="btn btn-light"
>
<Icon name="sort-down" />
Classement
</a>
<a
href="{$settings.videoslink}"
class="btn btn-light"
class:disabled={$settings.videoslink === ''}
>
<Icon name="laptop-fill" />
Vidéos
</a>
</ButtonGroup>
</div>
{/if}
{:else}
<div class="d-flex h-100 justify-content-center align-items-center">
<h1 class="display-3 m-0">
Challenge forensic
</h1>
</div>
{/if}
<style>
.clock:not(.expired):not(.wait) .point, .clock.expired {
transition: color text-shadow 1s;
position: relative;
animation: clockanim 1s ease infinite;
-moz-animation: clockanim 1s ease infinite;
-webkit-animation: clockanim 1s ease infinite;
}
.clock.wait .point {
transition: color text-shadow 1s;
position: relative;
animation: clockwait 1s ease infinite;
-moz-animation: clockwait 1s ease infinite;
-webkit-animation: clockwait 1s ease infinite;
}
.end {
color: #e64143;
}
.point {
text-shadow: 0 0 20px #4eaee6;
}
.end .point {
text-shadow: 0 0 20px #e64143;
}
@keyframes clockanim {
0% { opacity: 1.0; }
50% { opacity: 0; }
100% { opacity: 1.0; }
}
@keyframes clockwait {
0% { text-shadow: 0 0 20px #A6D6F2; }
50% { text-shadow: 0 0 2px #A6D6F2; }
100% { text-shadow: 0 0 20px #A6D6F2; }
}
</style>

View file

@ -0,0 +1,34 @@
<script>
import {
Badge,
Icon,
NavItem,
NavLink,
} from 'sveltestrap';
import { issues, issues_need_info, issues_nb_responses, issues_known_responses } from '../stores/issues.js';
import { settings } from '../stores/settings.js';
let badge_color = 'secondary';
$: {
if ($issues_known_responses != $issues_nb_responses) {
if ($issues_need_info) {
badge_color = 'danger';
} else {
badge_color = 'warning';
}
} else {
badge_color = 'light';
}
}
</script>
{#if $issues.length}
<NavItem>
<NavLink href="/issues">
<Icon name="bug" />
Problèmes
<Badge color={badge_color}>{$issues_nb_responses}</Badge>
</NavLink>
</NavItem>
{/if}

View file

@ -0,0 +1,40 @@
<script>
import {
Carousel,
CarouselItem,
} from 'sveltestrap';
let partners = [
{
img: '/img/epita.png',
alt: 'Epita',
href: 'https://www.epita.fr/',
},
{
img: '/img/srs.png',
alt: 'Laboratoire SRS Épita',
href: 'https://srs.epita.fr/',
},
{
img: '/img/comcyber.png',
alt: 'Réserves de cyberdéfense',
},
];
let activePartner = 0;
</script>
<Carousel items={partners} bind:activeIndex={activePartner} ride="carousel" pause="hover" interval={25000}>
<div class="carousel-inner h-100">
{#each partners as partner, index}
<CarouselItem bind:activeIndex={activePartner} itemIndex={index} class="h-100 text-end">
{#if partner.href}
<a href="{partner.href}" target="_blank" class="h-100">
<img src={partner.img} class="h-100" alt={partner.alt}>
</a>
{:else}
<img src={partner.img} class="h-100" alt={partner.alt}>
{/if}
</CarouselItem>
{/each}
</div>
</Carousel>

View file

@ -0,0 +1,49 @@
<script>
import {
Badge,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
Icon,
} from 'sveltestrap';
import { my } from '../stores/my.js';
import { tags } from '../stores/mythemes.js';
let filter = "";
</script>
<Dropdown nav inNavbar>
<DropdownToggle nav caret>
<Icon name="tags" />
Tags
</DropdownToggle>
<DropdownMenu class="niceborder" end>
<input
type="text"
class="dropdown-item"
placeholder="Filtrer"
bind:value={filter}
>
<div>
{#each Object.keys($tags).sort() as itag, index}
{#if filter === "" || itag.toLowerCase().indexOf(filter.toLowerCase()) >= 0}
<DropdownItem href="/tags/{itag}">
#{itag}
<Badge>
{#if $my && $my.team_id}{$tags[itag].solved}/{/if}{$tags[itag].count}
</Badge>
</DropdownItem>
{/if}
{/each}
</div>
</DropdownMenu>
</Dropdown>
<style>
div {
overflow-y: auto;
max-height: calc(66vh - 100px);
}
</style>

View file

@ -0,0 +1,41 @@
<script>
import {
Badge,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
Icon,
} from 'sveltestrap';
import { my } from '../stores/my.js';
import { max_solved, themes } from '../stores/themes.js';
import { myThemes } from '../stores/mythemes.js';
</script>
<Dropdown nav inNavbar>
<DropdownToggle nav caret>
<Icon name="tv" />
Scénarii
</DropdownToggle>
<DropdownMenu class="niceborder" end>
{#each Object.keys($themes) as th, index}
<DropdownItem href="/{$themes[th].urlid}">
{$themes[th].name}
{#if $max_solved > 1 && $themes[th].solved == $max_solved}
<Badge color="danger">
<Icon name="heart-fill" />
</Badge>
{/if}
{#if $themes[th].exercice_coeff_max > 1}
<Badge color="success">
<Icon name="gift-fill" />
</Badge>
{/if}
<Badge>
{#if $my && $my.team_id}{$myThemes[th].exercice_solved}/{/if}{$themes[th].exercice_count}
</Badge>
</DropdownItem>
{/each}
</DropdownMenu>
</Dropdown>

View file

@ -0,0 +1,109 @@
<script>
import { createEventDispatcher } from 'svelte';
import {
Alert,
Badge,
Button,
Card,
Col,
Container,
Icon,
Row,
} from 'sveltestrap';
import { settings } from '../stores/settings.js';
import RegistrationRowMember from './RegistrationRowMember.svelte';
const dispatch = createEventDispatcher();
export let value = {members:[{}]};
function validateTeamName() {
if (typeof value.members !== 'Array') {
value.members = [{ }];
} else if (value.members.length <= 0) {
value.members.push({ });
}
partR = true;
}
function AddMember() {
value.members.push({ });
value = value;
}
function RemoveMember(mid) {
console.log(mid);
}
function submit(event) {
if (!partR) {
validateTeamName();
} else {
dispatch('submit', event);
}
}
const max_team_members = 3;
let jTeam = false;
export let partR = false;
let message = "";
let messageClass = "danger";
</script>
<form on:submit|preventDefault={submit}>
<Row>
<label for="teamName" class="col col-form-label">Nom d'équipe</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="teamName" bind:value={value.teamName} placeholder="" autofocus required>
<Button color="info" type="button" on:click={validateTeamName} disabled={jTeam}>Valider</Button>
<div class="invalid-feedback">
Veuillez indiquer un nom d'équipe valide.
</div>
</div>
</div>
</Row>
{#if partR}
<h4 class="mt-4">
{#if !$settings.canJoinTeam}
Membres d'équipe
<Button
color="success"
disabled={value.members.length >= max_team_members}
size="sm"
type="button"
on:click={AddMember}
>
<Icon name="person-plus-fill" />
Ajouter un membre
</Button>
{:else}
Chef d'équipe
{/if}
</h4>
{#if message}
<p class={messageClass}>{message}</p>
{/if}
{#each value.members as member, mid}
<RegistrationRowMember
canDelete={!$settings.canJoinTeam && value.members.length > 1}
bind:member={member}
on:delete={RemoveMember}
/>
{/each}
<Row>
<Col sm={{ size: 9, offset: 3 }} md={{ size: 8, offset: 4 }}>
<Button color="info" class="mt-4" type="submit" disabled={jTeam}>
C'est parti&nbsp;!
<Icon name="chevron-right" />
</Button>
</Col>
</Row>
{/if}
</form>

View file

@ -0,0 +1,99 @@
<script>
import { createEventDispatcher } from 'svelte';
import {
Alert,
Badge,
Button,
Card,
Col,
Container,
Icon,
Row,
} from 'sveltestrap';
import { settings } from '../stores/settings.js';
import { teams } from '../stores/teams.js';
import RegistrationRowMember from './RegistrationRowMember.svelte';
const dispatch = createEventDispatcher();
export let value = { };
function JvalidateTeam() {
if (!value.members || value.members.length == 0) {
value.members = [{ }];
}
value = value;
partJ = true;
}
function submit(event) {
if (!partJ) {
JvalidateTeam();
} else {
dispatch('submit', event);
}
}
export let partJ = false;
let message = "";
let messageClass = "danger";
</script>
{#if Object.keys($teams).length}
<form on:submit|preventDefault={submit}>
<Row>
<label for="jTeam" class="col col-form-label">Nom d'équipe</label>
<div class="col-sm-10">
<div class="input-group">
<select
class="form-select"
id="jTeam"
bind:value={value.jTeam}
required
disabled={partJ}
>
{#each Object.keys($teams) as tid, index}
<option value={$teams[tid].id}>
{$teams[tid].name}
</option>
{/each}
</select>
<Button color="info" type="button" on:click={JvalidateTeam} disabled={partJ}>Valider</Button>
<div class="invalid-feedback">
Veuillez indiquer une équipe valide.
</div>
</div>
</div>
</Row>
{#if partJ}
<h4 class="mt-4">
Vos informations
</h4>
{#if message}
<p class={messageClass}>{message}</p>
{/if}
<RegistrationRowMember
bind:member={value.members[0]}
/>
<Row>
<Col sm={{ size: 9, offset: 3 }} md={{ size: 8, offset: 4 }}>
<Button color="info" class="mt-4" type="submit" disabled={!value.jTeam}>
C'est parti&nbsp;!
<Icon name="chevron-right" />
</Button>
</Col>
</Row>
{/if}
</form>
{:else}
<p class="card-text">
Aucune équipe enregistrée pour l'instant.
</p>
{/if}

View file

@ -0,0 +1,36 @@
<script>
import { createEventDispatcher } from 'svelte';
import {
Button,
Icon,
Row,
} from 'sveltestrap';
const dispatch = createEventDispatcher();
export let member = {};
export let canDelete = false;
</script>
<Row class="form-group my-3">
<div class="col-sm">
<input type="text" class="form-control" bind:value={member.lastname} placeholder="Nom" autofocus>
</div>
<div class="col-sm">
<input type="text" class="form-control" bind:value={member.firstname} placeholder="Prénom">
</div>
<div class="col-sm">
<input type="text" class="form-control" bind:value={member.nickname} placeholder="Pseudo">
</div>
<div class="col-sm">
<input type="text" class="form-control" bind:value={member.company} placeholder="Entreprise">
</div>
{#if canDelete}
<div class="col-sm-auto">
<Button color="danger" type="button" on:click={dispatch('delete', member)}>
<Icon name="trash" />
</Button>
</div>
{/if}
</Row>

View file

@ -0,0 +1,109 @@
<script>
import {
Button,
Card,
CardHeader,
CardBody,
Icon,
} from 'sveltestrap';
import { my } from '../stores/my.js';
export let refresh_my;
let newTeamName = "";
function gotoHomeOnDiff(i) {
refresh_my((my) => {
if (my && my.name == newTeamName) {
newTeamName = "";
messageClass = "info";
sberr = "Votre nom d'équipe a été changé avec succès.";
message = "";
} else {
setTimeout(gotoHomeOnDiff, 850, i-1);
}
})
}
async function submitChangeName(event) {
message = "";
sberr = "";
if (newTeamName.length < 1) {
messageClass = "danger";
sberr = "Nom d'équipe invalide: pas d'entrée.";
return false;
}
else if (newTeamName.length > 32) {
messageClass = "danger";
sberr = "Nom d'équipe invalide: pas plus de 32 caractères.";
return false;
}
else if (!newTeamName.match(/^[A-Za-z0-9 àéèêëîïôùûü_-]+$/)) {
messageClass = "danger";
sberr = "Nom d'équipe invalide: seuls les caractères alpha-numériques sont autorisés.";
return false;
}
const response = await fetch('/chname', {
method: "POST",
body: JSON.stringify({newName: newTeamName}),
});
if (response.status < 300) {
const data = await response.json();
messageClass = 'success';
message = data.errmsg;
gotoHomeOnDiff(15);
} else {
messageClass = 'danger';
let data = "";
try {
data = await response.json();
} catch(e) {
data = null;
}
if (data && data.errmsg)
message = data.errmsg;
if (response.statys != 402)
sberr = "Une erreur est survenue lors de la demande de changement de nom. Veuillez réessayer dans quelques instants.";
}
}
let sberr = "";
let message = "";
let messageClass = "danger";
</script>
<Card class="mb-3 border-info">
<CardHeader class="bg-info">
<Icon name="input-cursor-text" />
Changer de nom d'équipe
</CardHeader>
<CardBody>
{#if sberr || message}
<p class="card-text text-{messageClass}">
{#if !sberr}
<strong>Votre demande a bien été envoyée !</strong>
{:else}
<strong>{sberr}</strong>
{/if}
{message}
</p>
{/if}
<form on:submit|preventDefault={submitChangeName}>
<div class="form-group row">
<label for="newName" class="col col-form-label">Nouveau nom</label>
<div class="col-sm-10">
<div class="input-group">
<input type="text" class="form-control" id="newName" bind:value={newTeamName} placeholder="{$my.name}">
<Button type="submit" class="btn btn-info">Valider</Button>
</div>
</div>
</div>
</form>
</CardBody>
</Card>

View file

@ -0,0 +1,37 @@
<script>
import {
Card,
CardHeader,
CardBody,
Icon,
ListGroup,
ListGroupItem,
} from 'sveltestrap';
export let members = [];
</script>
<Card class="mb-3">
<CardHeader>
<Icon name="people-fill" />
Membres de l'équipe
</CardHeader>
{#if members.length}
<ListGroup>
{#each members as member (member.id)}
<ListGroupItem class="list-group-item-action">
{member.firstname}
{#if member.nickname}
<span style="font-style: italic">{member.nickname}</span>
{/if}
<span style="font-variant: small-caps;">{member.lastname}</span>
{#if member.company}&ndash; {member.company}{/if}
</ListGroupItem>
{/each}
</ListGroup>
{:else}
<CardBody>
Passez voir l'équipe d'organisation pour compléter ces informations.
</CardBody>
{/if}
</Card>

View file

@ -0,0 +1,59 @@
<script>
import {
Breadcrumb,
BreadcrumbItem,
Card,
Icon,
} from 'sveltestrap';
import { my } from '../stores/my.js';
export let theme = {};
export let exercice = {};
</script>
<Card body class="mb-3" color="dark">
<Breadcrumb listClassName="mb-0">
{#each Object.keys(theme.exercices) as k, index}
<BreadcrumbItem active={k == exercice.id}>
{#if k == exercice.id}
<strong class="text-info">
{theme.exercices[k].title}
{#if theme.exercices[k].curcoeff > 1.0}
<Icon name="gift" aria-hidden="true" />
{/if}
{#if $my && $my.team_id && $my.exercices[k] && $my.exercices[k].solved}
<Icon name="check" class="text-success" aria-hidden="true" />
{/if}
</strong>
{:else if $my && $my.exercices[k]}
<a href="/{theme.urlid}/{theme.exercices[k].urlid}" class:text-success={$my.exercices[k].solved}>
{theme.exercices[k].title}
{#if theme.exercices[k].curcoeff > 1.0}
<Icon name="gift" aria-hidden="true" />
{/if}
{#if $my.team_id && $my.exercices[k].solved}
<Icon name="check" class="text-success" aria-hidden="true" />
{/if}
</a>
{:else}
<span class="text-muted">
{theme.exercices[k].title}
{#if theme.exercices[k].curcoeff > 1.0}
<Icon name="gift" aria-hidden="true" />
{/if}
</span>
{/if}
</BreadcrumbItem>
{/each}
</Breadcrumb>
</Card>
<style>
a {
text-decoration: none;
}
a[href]:hover {
text-decoration: underline;
}
</style>