svelte-migrate: updated files
This commit is contained in:
parent
ca12b3dde5
commit
3cf92b4798
37 changed files with 600 additions and 536 deletions
|
@ -16,6 +16,7 @@
|
|||
import { blake2b } from 'hash-wasm';
|
||||
|
||||
import { my } from '../stores/my.js';
|
||||
import { teams } from '../stores/teams.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
|
||||
import DateFormat from './DateFormat.svelte';
|
||||
|
@ -25,14 +26,11 @@
|
|||
export let exercice = { };
|
||||
export let flags = [];
|
||||
|
||||
export let refresh_my;
|
||||
export let refresh_teams;
|
||||
|
||||
function waitDiff(i) {
|
||||
refresh_my((my) => {
|
||||
my.refresh((my) => {
|
||||
if (my && (my.exercices[exercice.id].tries != exercice.tries || my.exercices[exercice.id].solved_rank != exercice.solved_rank || my.exercices[exercice.id].solved_time != exercice.solved_time)) {
|
||||
submitInProgress = false;
|
||||
refresh_teams();
|
||||
teams.refresh();
|
||||
} else if (i > 0) {
|
||||
setTimeout(waitDiff, (12-i)*50+440, i-1);
|
||||
} else {
|
||||
|
@ -208,7 +206,6 @@
|
|||
<FlagMCQ
|
||||
exercice_id={exercice.id}
|
||||
{flag}
|
||||
{refresh_my}
|
||||
bind:values={responses.mcqs}
|
||||
bind:justifications={responses.justifications}
|
||||
/>
|
||||
|
@ -216,7 +213,6 @@
|
|||
<FlagKey
|
||||
exercice_id={exercice.id}
|
||||
{flag}
|
||||
{refresh_my}
|
||||
bind:value={responses.flags[flag.id]}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -14,12 +14,11 @@
|
|||
|
||||
export let hints = [];
|
||||
export let exercice = {};
|
||||
export let refresh_my = null;
|
||||
|
||||
let hints_submitted = {};
|
||||
|
||||
function waitDiff(i, hint) {
|
||||
refresh_my((my) => {
|
||||
my.refresh((my) => {
|
||||
let openedHint = false;
|
||||
|
||||
if (my && my.exercices[exercice.id].hints) {
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
} from 'sveltestrap';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
import { my } from '../stores/my.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
|
||||
export let refresh_my = null;
|
||||
export let exercice_id = 0;
|
||||
export let flag = { };
|
||||
export let value = "";
|
||||
let values = [""];
|
||||
|
||||
function waitChoices(i) {
|
||||
refresh_my((my) => {
|
||||
my.refresh((my) => {
|
||||
let haveChoices = false;
|
||||
|
||||
if (my && my.exercices[exercice_id].flags) {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import FlagKey from './FlagKey.svelte';
|
||||
|
||||
export let refresh_my = null;
|
||||
export let exercice_id = 0;
|
||||
export let flag = { };
|
||||
export let values = { };
|
||||
|
@ -36,7 +35,6 @@
|
|||
<FlagKey
|
||||
{exercice_id}
|
||||
flag={flag.choices[cid].justification}
|
||||
{refresh_my}
|
||||
bind:values={justifications[flag.choices[cid].justification.id]}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -10,12 +10,10 @@
|
|||
import { my } from '../stores/my.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
|
||||
export let refresh_my;
|
||||
|
||||
let newTeamName = "";
|
||||
|
||||
function gotoHomeOnDiff(i) {
|
||||
refresh_my((my) => {
|
||||
my.refresh((my) => {
|
||||
if (my && my.name == newTeamName) {
|
||||
newTeamName = "";
|
||||
messageClass = "info";
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
export async function handle({ event, resolve }) {
|
||||
const response = await resolve(event, {
|
||||
ssr: false,
|
||||
});
|
||||
return response;
|
||||
}
|
|
@ -1,15 +1,5 @@
|
|||
<script context="module">
|
||||
export function load({ error, status }) {
|
||||
return {
|
||||
props: {
|
||||
title: `${status}: ${error.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let title;
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<h1>{title}</h1>
|
||||
<h1>{$page.status} : {$page.error.message}</h1>
|
||||
|
|
21
frontend/ui/src/routes/+layout.js
Normal file
21
frontend/ui/src/routes/+layout.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { issuesStore } from '../stores/issues.js';
|
||||
import { my } from '../stores/my.js';
|
||||
import { teamsStore } from '../stores/teams.js';
|
||||
import { themesStore } from '../stores/themes.js';
|
||||
import { settings, time } from '../stores/settings.js';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
export async function load() {
|
||||
await challengeInfo.refresh();
|
||||
await settings.refresh();
|
||||
await themes.refresh();
|
||||
teams.refresh();
|
||||
my.refresh((my) => {
|
||||
if (my && my.team_id === 0) {
|
||||
stop_refresh = true;
|
||||
}
|
||||
});
|
||||
issues.refresh();
|
||||
}
|
|
@ -1,137 +1,3 @@
|
|||
<script context="module">
|
||||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { issuesStore } from '../stores/issues.js';
|
||||
import { my } from '../stores/my.js';
|
||||
import { teamsStore } from '../stores/teams.js';
|
||||
import { themesStore } from '../stores/themes.js';
|
||||
import { settings, time } from '../stores/settings.js';
|
||||
|
||||
let stop_refresh = false;
|
||||
|
||||
let refresh_interval_settings = null;
|
||||
async function refresh_settings(cb=null, interval=null) {
|
||||
if (refresh_interval_settings)
|
||||
clearInterval(refresh_interval_settings);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_settings = setInterval(refresh_settings, interval);
|
||||
|
||||
if (!cb) {
|
||||
// Before we start, update settings more frequently.
|
||||
cb = function(stgs) {
|
||||
const srv_cur = new Date(Date.now() + (stgs.currentTime - stgs.recvTime));
|
||||
|
||||
if (settings.start > srv_cur) {
|
||||
const startIn = settings.start - srv_cur;
|
||||
if (startIn > 15000) {
|
||||
setTimeout(refresh_settings, Math.floor(Math.random() * 10000) + 2400)
|
||||
} else if (startIn > 1500) {
|
||||
setTimeout(refresh_settings, startIn - 1000 - Math.floor(Math.random() * 500))
|
||||
} else {
|
||||
// On scheduled start time, refresh my.json file
|
||||
setTimeout(refresh_my, startIn + Math.floor(Math.random() * 200))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
settings.update(await fetch('settings.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
async function refresh_challengeInfo(cb=null) {
|
||||
challengeInfo.update(await fetch('challenge.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
let refresh_interval_teams = null;
|
||||
async function refresh_teams(cb=null, interval=null) {
|
||||
if (refresh_interval_teams)
|
||||
clearInterval(refresh_interval_teams);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_teams = setInterval(refresh_teams, interval);
|
||||
|
||||
teamsStore.update(await fetch('teams.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
let refresh_interval_themes = null;
|
||||
async function refresh_themes(cb=null, interval=null) {
|
||||
if (refresh_interval_themes)
|
||||
clearInterval(refresh_interval_themes);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_themes = setInterval(refresh_themes, interval);
|
||||
|
||||
await themesStore.update(await fetch('themes.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
let refresh_interval_my = null;
|
||||
async function refresh_my(cb=null, interval=null) {
|
||||
if (refresh_interval_my)
|
||||
clearInterval(refresh_interval_my);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 24000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_my = setInterval(refresh_my, interval);
|
||||
|
||||
my.update(await fetch('my.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
let refresh_interval_issues = null;
|
||||
async function refresh_issues(cb=null, interval=null) {
|
||||
if (refresh_interval_issues)
|
||||
clearInterval(refresh_interval_issues);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_issues = setInterval(refresh_issues, interval);
|
||||
|
||||
issuesStore.update(await fetch('issues.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
}
|
||||
|
||||
export async function load({ stuff }) {
|
||||
await refresh_challengeInfo();
|
||||
await refresh_settings();
|
||||
await refresh_themes();
|
||||
refresh_teams();
|
||||
refresh_my((my) => {
|
||||
if (my && my.team_id === 0) {
|
||||
stop_refresh = true;
|
||||
}
|
||||
});
|
||||
refresh_issues();
|
||||
|
||||
return {
|
||||
stuff: {
|
||||
...stuff,
|
||||
refresh_challengeInfo,
|
||||
refresh_settings,
|
||||
refresh_teams,
|
||||
refresh_themes,
|
||||
refresh_my,
|
||||
refresh_issues,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import '../fic.scss'
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
|
|
19
frontend/ui/src/routes/[theme]/+layout.js
Normal file
19
frontend/ui/src/routes/[theme]/+layout.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { themes } from '../../stores/themes.js';
|
||||
|
||||
export async function load({ params }) {
|
||||
const thms = get_store_value(themes);
|
||||
|
||||
let theme = null;
|
||||
for (let th in thms) {
|
||||
if (thms[th] && thms[th].urlid === params.theme) {
|
||||
theme = thms[th];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
theme,
|
||||
};
|
||||
}
|
|
@ -1,30 +1,3 @@
|
|||
<script context="module">
|
||||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { themes } from '../../stores/themes.js';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
const thms = get_store_value(themes);
|
||||
|
||||
let theme = null;
|
||||
for (let th in thms) {
|
||||
if (thms[th] && thms[th].urlid === params.theme) {
|
||||
theme = thms[th];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
stuff: {
|
||||
...stuff,
|
||||
theme: theme,
|
||||
}, props: {
|
||||
theme: theme,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Container,
|
||||
|
|
7
frontend/ui/src/routes/[theme]/+page.js
Normal file
7
frontend/ui/src/routes/[theme]/+page.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export async function load({ parent }) {
|
||||
const data = await parent();
|
||||
|
||||
return {
|
||||
theme: data.theme,
|
||||
};
|
||||
}
|
|
@ -1,13 +1,3 @@
|
|||
<script context="module">
|
||||
export async function load({ stuff }) {
|
||||
return {
|
||||
props: {
|
||||
theme: stuff.theme,
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
|
@ -25,31 +15,31 @@
|
|||
|
||||
import { my } from '../../stores/my.js';
|
||||
|
||||
export let theme = null;
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
{#if theme && theme.exercices}
|
||||
{#if data.theme && data.theme.exercices}
|
||||
<Card class="niceborder text-indent mt-2 mb-4">
|
||||
|
||||
<CardBody class="bg-dark text-light">
|
||||
<Row>
|
||||
<Col>
|
||||
<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>
|
||||
<p class="mt-4 mx-3 card-text lead text-justify">{@html data.theme.headline}</p>
|
||||
<p class="mb-4 mx-3 card-text text-justify">{@html data.theme.intro}</p>
|
||||
</Col>
|
||||
{#if theme.partner_txt || theme.partner_img || theme.partner_href}
|
||||
{#if data.theme.partner_txt || data.theme.partner_img || data.theme.partner_href}
|
||||
<Col md="2" lg="3" class="d-none d-md-block">
|
||||
<Card class="pt-3 px-3">
|
||||
{#if theme.partner_img}
|
||||
<img src="{theme.partner_img}" class="card-img-top">
|
||||
{#if data.theme.partner_img}
|
||||
<img src="{data.theme.partner_img}" class="card-img-top">
|
||||
{/if}
|
||||
{#if theme.partner_txt || theme.partner_href}
|
||||
{#if data.theme.partner_txt || data.theme.partner_href}
|
||||
<CardBody class="p-0 mt-3">
|
||||
{#if theme.partner_txt}
|
||||
{@html theme.partner_txt}
|
||||
{#if data.theme.partner_txt}
|
||||
{@html data.theme.partner_txt}
|
||||
{/if}
|
||||
{#if theme.partner_href}
|
||||
<Button tag="a" color="primary" href="{theme.partner_href}">
|
||||
{#if data.theme.partner_href}
|
||||
<Button tag="a" color="primary" href="{data.theme.partner_href}">
|
||||
Visiter le site
|
||||
</Button>
|
||||
{/if}
|
||||
|
@ -62,11 +52,11 @@
|
|||
</CardBody>
|
||||
|
||||
<ul class="list-group">
|
||||
{#each Object.keys(theme.exercices) as k, index}
|
||||
{#each Object.keys(data.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}`)}
|
||||
on:click={goto(`${data.theme.urlid}/${data.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_rank ? '62c462' : 'bbb'}">
|
||||
|
@ -75,37 +65,37 @@
|
|||
<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]] && $my.exercices[Object.keys(theme.exercices)[index-1]].solved_rank)) ? '62c462' : 'bbb'}"
|
||||
style="fill:#{$my && $my.exercices[k] && (index < 1 || ($my.exercices[Object.keys(data.data.theme.exercices)[index-1]] && $my.exercices[Object.keys(data.theme.exercices)[index-1]].solved_rank)) ? '62c462' : 'bbb'}"
|
||||
width="5"
|
||||
height="30"
|
||||
x="10"
|
||||
y="0" />
|
||||
<path
|
||||
style="fill:#{$my && $my.exercices[k] ? ($my.exercices[k].solved_rank ? '62c462' : (theme.exercices[k].curcoeff > 1.0 ? 'f89406' : '5bc0de')) : '555'}"
|
||||
style="fill:#{$my && $my.exercices[k] ? ($my.exercices[k].solved_rank ? '62c462' : (data.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}
|
||||
{#each data.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}
|
||||
{data.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}
|
||||
{data.theme.exercices[k].title}
|
||||
</span>
|
||||
{/if}
|
||||
{#if theme.exercices[k].curcoeff > 1.0}
|
||||
{#if data.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>
|
||||
<p>{@html data.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">
|
||||
<a class="float-right" href="{data.theme.urlid}/{data.theme.exercices[k].urlid}" style="font-size: 3rem">
|
||||
<Icon name="chevron-right" aria-hidden="true" />
|
||||
</a>
|
||||
{:else}
|
||||
|
|
22
frontend/ui/src/routes/[theme]/[exercice]/+page.js
Normal file
22
frontend/ui/src/routes/[theme]/[exercice]/+page.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { themes } from '../../../stores/themes.js';
|
||||
|
||||
export async function load({ parent }) {
|
||||
const stuff = await parent();
|
||||
|
||||
let exercice = null;
|
||||
|
||||
for (let ex in stuff.theme.exercices) {
|
||||
if (stuff.theme.exercices[ex].urlid === params.exercice) {
|
||||
exercice = stuff.theme.exercices[ex];
|
||||
exercice.id = ex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
theme: stuff.theme,
|
||||
exercice: exercice,
|
||||
};
|
||||
}
|
|
@ -1,30 +1,3 @@
|
|||
<script context="module">
|
||||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { themes } from '../../stores/themes.js';
|
||||
|
||||
export async function load({ params, stuff }) {
|
||||
let exercice = null;
|
||||
|
||||
for (let ex in stuff.theme.exercices) {
|
||||
if (stuff.theme.exercices[ex].urlid === params.exercice) {
|
||||
exercice = stuff.theme.exercices[ex];
|
||||
exercice.id = ex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
theme: stuff.theme,
|
||||
exercice: exercice,
|
||||
refresh_my: stuff.refresh_my,
|
||||
refresh_teams: stuff.refresh_teams,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
|
@ -38,55 +11,51 @@
|
|||
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 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 { challengeInfo } from '../../stores/challengeinfo.js';
|
||||
import { my } from '../../stores/my.js';
|
||||
import { settings } from '../../stores/settings.js';
|
||||
import { challengeInfo } from '../../../stores/challengeinfo.js';
|
||||
import { my } from '../../../stores/my.js';
|
||||
import { settings } from '../../../stores/settings.js';
|
||||
|
||||
export let theme;
|
||||
export let exercice;
|
||||
export let data;
|
||||
let solved = {};
|
||||
|
||||
export let refresh_my;
|
||||
export let refresh_teams;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{exercice?exercice.title+" - ":""}{$challengeInfo.title}</title>
|
||||
<title>{data.exercice?data.exercice.title+" - ":""}{$challengeInfo.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if exercice}
|
||||
<ThemeNav {theme} {exercice} />
|
||||
{#if data.exercice}
|
||||
<ThemeNav theme={data.theme} exercice={data.exercice} />
|
||||
{/if}
|
||||
|
||||
{#if !$my || !exercice || !$my.exercices[exercice.id]}
|
||||
{#if !$my || !data.exercice || !$my.exercices[data.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}
|
||||
{#if data.exercice}
|
||||
<Card body class="niceborder text-indent my-3">
|
||||
<h3 class="display-4">{exercice.title}</h3>
|
||||
<h3 class="display-4">{data.exercice.title}</h3>
|
||||
<div>
|
||||
{#each exercice.tags as tag, index}
|
||||
{#each data.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>
|
||||
{#if !$my || !$my.exercices[data.exercice.id]}
|
||||
<p class="lead text-justify">{@html data.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}
|
||||
<p class="lead text-justify">{@html $my.exercices[data.exercice.id].statement}</p>
|
||||
{#if $my.exercices[data.exercice.id].issue}
|
||||
<Alert color="{$my.exercices[data.exercice.id].issuekind}">
|
||||
{@html $my.exercices[data.exercice.id].issue}
|
||||
</Alert>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -96,31 +65,31 @@
|
|||
<ul>
|
||||
<li>
|
||||
<strong>Gain :</strong>
|
||||
{exercice.gain} {exercice.gain==1?"point":"points"}
|
||||
{#if $settings.firstBlood && exercice.solved < 1}
|
||||
{data.exercice.gain} {data.exercice.gain==1?"point":"points"}
|
||||
{#if $settings.firstBlood && data.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 data.exercice.curcoeff != 1.0 || $settings.exerciceCurrentCoefficient != 1.0}
|
||||
<em>{#if data.exercice.curcoeff * $settings.exerciceCurrentCoefficient > 1}+{Math.round((data.exercice.curcoeff * $settings.exerciceCurrentCoefficient - 1) * 100)}{:else}-{Math.round((1-(data.exercice.curcoeff * $settings.exerciceCurrentCoefficient)) * 100)}{/if}% (bonus)</em>
|
||||
{/if}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tenté par :</strong>
|
||||
{#if !exercice.tried}
|
||||
{#if !data.exercice.tried}
|
||||
aucune équipe
|
||||
{:else}
|
||||
{exercice.tried} {exercice.tried == 1?"équipe":"équipes"}
|
||||
{#if $my && $my.exercices[exercice.id] && $my.exercices[exercice.id].total_tries}
|
||||
(cumulant {$my.exercices[exercice.id].total_tries} {$my.exercices[exercice.id].total_tries == 1?"tentative":"tentatives"})
|
||||
{data.exercice.tried} {data.exercice.tried == 1?"équipe":"équipes"}
|
||||
{#if $my && $my.exercices[data.exercice.id] && $my.exercices[data.exercice.id].total_tries}
|
||||
(cumulant {$my.exercices[data.exercice.id].total_tries} {$my.exercices[data.exercice.id].total_tries == 1?"tentative":"tentatives"})
|
||||
{/if}
|
||||
{/if}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Résolu par :</strong>
|
||||
{#if !exercice.solved}
|
||||
{#if !data.exercice.solved}
|
||||
aucune équipe
|
||||
{:else}
|
||||
{exercice.solved} {exercice.solved == 1?"équipe":"équipes"}
|
||||
{data.exercice.solved} {data.exercice.solved == 1?"équipe":"équipes"}
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -128,13 +97,13 @@
|
|||
{#if $my && $my.team_id}
|
||||
<Col>
|
||||
{#if $settings.acceptNewIssue}
|
||||
<a href="issues/?eid={exercice.id}" class="float-end btn btn-sm btn-warning">
|
||||
<a href="issues/?eid={data.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">
|
||||
<a href="qa/exercices/{data.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>
|
||||
|
@ -144,51 +113,48 @@
|
|||
</Row>
|
||||
</Card>
|
||||
|
||||
{#if $my && $my.exercices[exercice.id]}
|
||||
{#if $my && $my.exercices[data.exercice.id]}
|
||||
<Row class="mt-4">
|
||||
<Col lg="6" class="mb-5">
|
||||
{#if $my.exercices[exercice.id].files}
|
||||
{#if $my.exercices[data.exercice.id].files}
|
||||
<ExerciceDownloads
|
||||
files={$my.exercices[exercice.id].files}
|
||||
files={$my.exercices[data.exercice.id].files}
|
||||
/>
|
||||
{/if}
|
||||
{#if $my.exercices[exercice.id].hints}
|
||||
{#if $my.exercices[data.exercice.id].hints}
|
||||
<ExerciceHints
|
||||
{refresh_my}
|
||||
exercice={$my.exercices[exercice.id]}
|
||||
hints={$my.exercices[exercice.id].hints}
|
||||
exercice={$my.exercices[data.exercice.id]}
|
||||
hints={$my.exercices[data.exercice.id].hints}
|
||||
/>
|
||||
{/if}
|
||||
</Col>
|
||||
<Col lg="6" class="mb-5">
|
||||
{#if $my.exercices[exercice.id].flags && $my.exercices[exercice.id].non_found_flags > 0 && !solved[exercice.id]}
|
||||
{#if $my.exercices[data.exercice.id].flags && $my.exercices[data.exercice.id].non_found_flags > 0 && !solved[data.exercice.id]}
|
||||
<ExerciceFlags
|
||||
{refresh_my}
|
||||
{refresh_teams}
|
||||
exercice={$my.exercices[exercice.id]}
|
||||
bind:forcesolved={solved[exercice.id]}
|
||||
flags={$my.exercices[exercice.id].flags}
|
||||
exercice={$my.exercices[data.exercice.id]}
|
||||
bind:forcesolved={solved[data.exercice.id]}
|
||||
flags={$my.exercices[data.exercice.id].flags}
|
||||
/>
|
||||
{/if}
|
||||
{#if $my.exercices[exercice.id].solved_rank || solved[exercice.id]}
|
||||
{#if $my.exercices[data.exercice.id].solved_rank || solved[data.exercice.id]}
|
||||
<ExerciceSolved
|
||||
{theme}
|
||||
exercice={$my.exercices[exercice.id]}
|
||||
theme={data.theme}
|
||||
exercice={$my.exercices[data.exercice.id]}
|
||||
/>
|
||||
{/if}
|
||||
{#if $my.exercices[exercice.id].resolution || $my.exercices[exercice.id].video_uri}
|
||||
{#if $my.exercices[data.exercice.id].resolution || $my.exercices[data.exercice.id].video_uri}
|
||||
<Card class="border-success mb-2">
|
||||
<CardHeader class="bg-success text-light">
|
||||
<Icon name="laptop-fill" />
|
||||
Solution du défi
|
||||
</CardHeader>
|
||||
{#if $my.exercices[exercice.id].resolution}
|
||||
{#if $my.exercices[data.exercice.id].resolution}
|
||||
<CardBody>
|
||||
{@html $my.exercices[exercice.id].resolution}
|
||||
{@html $my.exercices[data.exercice.id].resolution}
|
||||
</CardBody>
|
||||
{/if}
|
||||
{#if $my.exercices[exercice.id].video_uri}
|
||||
<ExerciceVideo uri={$my.exercices[exercice.id].video_uri} />
|
||||
{#if $my.exercices[data.exercice.id].video_uri}
|
||||
<ExerciceVideo uri={$my.exercices[data.exercice.id].video_uri} />
|
||||
{/if}
|
||||
</Card>
|
||||
{/if}
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
<script context="module">
|
||||
export async function load({ stuff }) {
|
||||
return {
|
||||
props: {
|
||||
refresh_my: stuff.refresh_my,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
|
@ -20,14 +10,12 @@
|
|||
Row,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import ScoreGrid from '../components/ScoreGrid.svelte';
|
||||
import TeamChangeName from '../components/TeamChangeName.svelte';
|
||||
import TeamMembers from '../components/TeamMembers.svelte';
|
||||
import ScoreGrid from '../../components/ScoreGrid.svelte';
|
||||
import TeamChangeName from '../../components/TeamChangeName.svelte';
|
||||
import TeamMembers from '../../components/TeamMembers.svelte';
|
||||
|
||||
import { my } from '../stores/my.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
|
||||
export let refresh_my;
|
||||
import { my } from '../../stores/my.js';
|
||||
import { settings } from '../../stores/settings.js';
|
||||
</script>
|
||||
|
||||
<Container class="my-3">
|
||||
|
@ -43,7 +31,7 @@
|
|||
<Col md>
|
||||
<TeamMembers members={$my.members} />
|
||||
{#if !$settings.denyNameChange}
|
||||
<TeamChangeName {refresh_my} />
|
||||
<TeamChangeName />
|
||||
{/if}
|
||||
</Col>
|
||||
<Col md>
|
||||
|
|
14
frontend/ui/src/routes/issues/+page.js
Normal file
14
frontend/ui/src/routes/issues/+page.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { exercices_idx } from '../../stores/themes.js';
|
||||
|
||||
export async function load({ url }) {
|
||||
const eidx = get_store_value(exercices_idx);
|
||||
|
||||
const exercice = eidx[url.searchParams.get("eid")]?eidx[url.searchParams.get("eid")]:null;
|
||||
|
||||
return {
|
||||
exercice: exercice,
|
||||
fillIssue: exercice !== null || url.searchParams.get("fill-issue") !== null,
|
||||
};
|
||||
}
|
|
@ -1,23 +1,3 @@
|
|||
<script context="module">
|
||||
import { get_store_value } from 'svelte/internal';
|
||||
|
||||
import { exercices_idx } from '../stores/themes.js';
|
||||
|
||||
export async function load({ url, stuff }) {
|
||||
const eidx = get_store_value(exercices_idx);
|
||||
|
||||
const exercice = eidx[url.searchParams.get("eid")]?eidx[url.searchParams.get("eid")]:null;
|
||||
|
||||
return {
|
||||
props: {
|
||||
refresh_issues: stuff.refresh_issues,
|
||||
exercice: exercice,
|
||||
fillIssue: exercice !== null || url.searchParams.get("fill-issue") !== null,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
|
@ -30,22 +10,20 @@
|
|||
Table,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import DateFormat from '../components/DateFormat.svelte';
|
||||
import DateFormat from '../../components/DateFormat.svelte';
|
||||
|
||||
import { issues, issues_nb_responses, issues_known_responses } from '../stores/issues.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
import { issues, issues_nb_responses, issues_known_responses } from '../../stores/issues.js';
|
||||
import { settings } from '../../stores/settings.js';
|
||||
|
||||
import FormIssue from '../components/FormIssue.svelte';
|
||||
import FormIssue from '../../components/FormIssue.svelte';
|
||||
|
||||
export let refresh_issues = null;
|
||||
export let exercice = null;
|
||||
export let fillIssue = false;
|
||||
export let data;
|
||||
let issue = {};
|
||||
|
||||
issues_known_responses.set($issues_nb_responses);
|
||||
|
||||
function newIssue() {
|
||||
fillIssue = true;
|
||||
data.fillIssue = true;
|
||||
}
|
||||
|
||||
let sberr = "";
|
||||
|
@ -53,7 +31,7 @@
|
|||
let messageClass = "success";
|
||||
|
||||
function waitDiff(curissues, i) {
|
||||
refresh_issues((issues) => {
|
||||
issues.refresh((issues) => {
|
||||
if (i > 0 && (!issues || issues.length <= curissues)) {
|
||||
setTimeout(waitDiff, 850, curissues, i-1);
|
||||
}
|
||||
|
@ -61,9 +39,9 @@
|
|||
}
|
||||
|
||||
function respondTo(_issue) {
|
||||
exercice = null;
|
||||
data.exercice = null;
|
||||
issue = {id: _issue.id, description: ''};
|
||||
fillIssue = true;
|
||||
data.fillIssue = true;
|
||||
}
|
||||
|
||||
async function submit_issue(event) {
|
||||
|
@ -85,8 +63,8 @@
|
|||
messageClass = 'success';
|
||||
message = data.errmsg;
|
||||
issue = { };
|
||||
exercice = null;
|
||||
fillIssue = false;
|
||||
data.exercice = null;
|
||||
data.fillIssue = false;
|
||||
|
||||
const currentissues = get_store_value(issues);
|
||||
waitDiff(currentissues.length, 7);
|
||||
|
@ -120,7 +98,7 @@
|
|||
</Alert>
|
||||
{/if}
|
||||
|
||||
{#if fillIssue}
|
||||
{#if data.fillIssue}
|
||||
<Card class="border-warning mt-3 mb-5">
|
||||
<CardHeader class="bg-warning text-light">
|
||||
<Icon name="file-earmark-plus" />
|
||||
|
@ -135,7 +113,7 @@
|
|||
<p class="card-text">Rapprochez-vous d'un membre de l'équipe afin d'obtenir de l'aide.</p>
|
||||
{:else}
|
||||
<FormIssue
|
||||
{exercice}
|
||||
exercice={data.exercice}
|
||||
bind:issue={issue}
|
||||
on:submit={submit_issue}
|
||||
/>
|
||||
|
@ -153,7 +131,7 @@
|
|||
<th>Géré par</th>
|
||||
<th>Messages</th>
|
||||
<th>
|
||||
{#if !fillIssue}
|
||||
{#if !data.fillIssue}
|
||||
<Button sm color="warning" on:click={newIssue}>
|
||||
<Icon name="file-earmark-plus" />
|
||||
</Button>
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
Row,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { my } from '../stores/my.js';
|
||||
import { rank } from '../stores/teams.js';
|
||||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { my } from '../../stores/my.js';
|
||||
import { rank } from '../../stores/teams.js';
|
||||
import { challengeInfo } from '../../stores/challengeinfo.js';
|
||||
|
||||
import CardTheme from '../components/CardTheme.svelte';
|
||||
import CardTheme from '../../components/CardTheme.svelte';
|
||||
|
||||
let search = "";
|
||||
</script>
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
<script context="module">
|
||||
export async function load({ stuff }) {
|
||||
return {
|
||||
props: {
|
||||
refresh_my: stuff.refresh_my,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
|
@ -21,13 +11,11 @@
|
|||
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { my } from '../stores/my.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
import { my } from '../../stores/my.js';
|
||||
import { settings } from '../../stores/settings.js';
|
||||
|
||||
import RegistrationFormCreateTeam from '../components/RegistrationFormCreateTeam.svelte';
|
||||
import RegistrationFormJoinTeam from '../components/RegistrationFormJoinTeam.svelte';
|
||||
|
||||
export let refresh_my;
|
||||
import RegistrationFormCreateTeam from '../../components/RegistrationFormCreateTeam.svelte';
|
||||
import RegistrationFormJoinTeam from '../../components/RegistrationFormJoinTeam.svelte';
|
||||
|
||||
let form = { };
|
||||
let partR = false;
|
||||
|
@ -36,7 +24,7 @@
|
|||
let message;
|
||||
|
||||
function gotoHomeOnDiff(i) {
|
||||
refresh_my((my) => {
|
||||
my.refresh((my) => {
|
||||
if (my && my.team_id) {
|
||||
goto('.');
|
||||
} else if (i > 0) {
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
Icon,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { challengeInfo } from '../stores/challengeinfo.js';
|
||||
import { settings } from '../stores/settings.js';
|
||||
import { challengeInfo } from '../../stores/challengeinfo.js';
|
||||
import { settings } from '../../stores/settings.js';
|
||||
</script>
|
||||
|
||||
<Container class="my-3">
|
||||
|
|
5
frontend/ui/src/routes/tags/[tag]/+page.js
Normal file
5
frontend/ui/src/routes/tags/[tag]/+page.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export async function load({ params }) {
|
||||
return {
|
||||
tag: params.tag,
|
||||
};
|
||||
}
|
|
@ -1,13 +1,3 @@
|
|||
<script context="module">
|
||||
export async function load({ params }) {
|
||||
return {
|
||||
props: {
|
||||
tag: params.tag,
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
|
@ -22,11 +12,11 @@
|
|||
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { themes } from '../../stores/themes.js';
|
||||
import { themes } from '../../../stores/themes.js';
|
||||
|
||||
import CardTheme from '../../components/CardTheme.svelte';
|
||||
import CardTheme from '../../../components/CardTheme.svelte';
|
||||
|
||||
export let tag = "";
|
||||
export let data;
|
||||
|
||||
let exercices = [];
|
||||
$: {
|
||||
|
@ -34,7 +24,7 @@
|
|||
|
||||
for (let th in $themes) {
|
||||
for (let ex in $themes[th].exercices) {
|
||||
if ($themes[th].exercices[ex].tags.indexOf(tag) >= 0) {
|
||||
if ($themes[th].exercices[ex].tags.indexOf(data.tag) >= 0) {
|
||||
tmp_exercices.push({theme: $themes[th], exercice: $themes[th].exercices[ex], index: th + "," + ex});
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +36,7 @@
|
|||
|
||||
<Container class="mt-3">
|
||||
<h1 class="text-dark">
|
||||
Challenges <em>{tag}</em>
|
||||
Challenges <em>{data.tag}</em>
|
||||
</h1>
|
||||
|
||||
{#if exercices.length}
|
||||
|
|
|
@ -5,6 +5,11 @@ function createChallengeStore() {
|
|||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
refresh: async (cb) => {
|
||||
challengeInfo.update(await fetch('challenge.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
},
|
||||
|
||||
update: (res_challenge, cb) => {
|
||||
if (res_challenge.status === 200) {
|
||||
res_challenge.json().then((challenge) => {
|
||||
|
|
1
frontend/ui/src/stores/common.js
Normal file
1
frontend/ui/src/stores/common.js
Normal file
|
@ -0,0 +1 @@
|
|||
export let stop_refresh = false;
|
|
@ -1,32 +1,53 @@
|
|||
import { derived, writable } from 'svelte/store';
|
||||
|
||||
import { stop_refresh } from './common';
|
||||
|
||||
let refresh_interval_issues = null;
|
||||
|
||||
function createIssuesStore() {
|
||||
const { subscribe, set, update } = writable({issues: [], issues_idx: {}, issues_nb_responses: 0, issues_need_info: 0});
|
||||
|
||||
function updateFunc (res_issues, cb=null) {
|
||||
if (res_issues.status === 200) {
|
||||
res_issues.json().then((issues) => {
|
||||
const issues_idx = {};
|
||||
let issues_nb_responses = 0;
|
||||
let issues_need_info = 0;
|
||||
issues.forEach(function(issue, k) {
|
||||
issues_idx[issue.id] = issue;
|
||||
issues_nb_responses += issue.texts.length;
|
||||
if (issue.state == 'need-info') issues_need_info++;
|
||||
issues[k].texts.reverse();
|
||||
})
|
||||
update((i) => (Object.assign(i, {issues, issues_idx, issues_nb_responses, issues_need_info})));
|
||||
|
||||
if (cb) {
|
||||
cb(issues, issues_idx, issues_nb_responses, issues_need_info);
|
||||
}
|
||||
});
|
||||
} else if (res_issues.status === 404) {
|
||||
update((i) => ({issues: [], issues_idx: {}, issues_nb_responses: 0, issues_need_info: 0}));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
update: (res_issues, cb=null) => {
|
||||
if (res_issues.status === 200) {
|
||||
res_issues.json().then((issues) => {
|
||||
const issues_idx = {};
|
||||
let issues_nb_responses = 0;
|
||||
let issues_need_info = 0;
|
||||
issues.forEach(function(issue, k) {
|
||||
issues_idx[issue.id] = issue;
|
||||
issues_nb_responses += issue.texts.length;
|
||||
if (issue.state == 'need-info') issues_need_info++;
|
||||
issues[k].texts.reverse();
|
||||
})
|
||||
update((i) => (Object.assign(i, {issues, issues_idx, issues_nb_responses, issues_need_info})));
|
||||
|
||||
if (cb) {
|
||||
cb(issues, issues_idx, issues_nb_responses, issues_need_info);
|
||||
}
|
||||
});
|
||||
} else if (res_issues.status === 404) {
|
||||
update((i) => ({issues: [], issues_idx: {}, issues_nb_responses: 0, issues_need_info: 0}));
|
||||
refresh: async (cb=null, interval=null) => {
|
||||
if (refresh_interval_issues)
|
||||
clearInterval(refresh_interval_issues);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_issues = setInterval(refresh_issues, interval);
|
||||
|
||||
updateFunc(await fetch('issues.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
},
|
||||
|
||||
update: updateFunc,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +1,63 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
import { stop_refresh } from './common';
|
||||
|
||||
let refresh_interval_my = null;
|
||||
|
||||
function createMyStore() {
|
||||
const { subscribe, set, update } = writable(null);
|
||||
|
||||
function updateFunc(res_my, cb=null) {
|
||||
if (res_my.status === 200) {
|
||||
res_my.json().then((my) => {
|
||||
for (let k in my.exercices) {
|
||||
my.exercices[k].id = k;
|
||||
|
||||
if (my.exercices[k].flags) {
|
||||
let nb = 0;
|
||||
for (let j in my.exercices[k].flags) {
|
||||
if (!my.exercices[k].flags[j].found)
|
||||
nb += 1;
|
||||
}
|
||||
my.exercices[k].non_found_flags = nb;
|
||||
}
|
||||
|
||||
if (my.team_id === 0 && my.exercices[k].hints) {
|
||||
for (let j in my.exercices[k].hints) {
|
||||
my.exercices[k].hints[j].hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update((m) => (Object.assign(m?m:{}, my)));
|
||||
|
||||
if (cb) {
|
||||
cb(my);
|
||||
}
|
||||
});
|
||||
} else if (res_my.status === 404) {
|
||||
update((m) => (null));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
update: (res_my, cb=null) => {
|
||||
if (res_my.status === 200) {
|
||||
res_my.json().then((my) => {
|
||||
for (let k in my.exercices) {
|
||||
my.exercices[k].id = k;
|
||||
|
||||
if (my.exercices[k].flags) {
|
||||
let nb = 0;
|
||||
for (let j in my.exercices[k].flags) {
|
||||
if (!my.exercices[k].flags[j].found)
|
||||
nb += 1;
|
||||
}
|
||||
my.exercices[k].non_found_flags = nb;
|
||||
}
|
||||
|
||||
if (my.team_id === 0 && my.exercices[k].hints) {
|
||||
for (let j in my.exercices[k].hints) {
|
||||
my.exercices[k].hints[j].hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update((m) => (Object.assign(m?m:{}, my)));
|
||||
|
||||
if (cb) {
|
||||
cb(my);
|
||||
}
|
||||
});
|
||||
} else if (res_my.status === 404) {
|
||||
update((m) => (null));
|
||||
refresh: async (cb=null, interval=null) => {
|
||||
if (refresh_interval_my)
|
||||
clearInterval(refresh_interval_my);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 24000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_my = setInterval(refresh_my, interval);
|
||||
|
||||
updateFunc(await fetch('my.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
},
|
||||
|
||||
update: updateFunc,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
0
frontend/ui/src/stores/myresponses.js
Normal file
0
frontend/ui/src/stores/myresponses.js
Normal file
|
@ -1,42 +1,82 @@
|
|||
import { readable, writable } from 'svelte/store';
|
||||
|
||||
import { stop_refresh } from './common';
|
||||
|
||||
let refresh_interval_settings = null;
|
||||
|
||||
function createSettingsStore() {
|
||||
const { subscribe, set, update } = writable({});
|
||||
|
||||
function updateFunc(res_settings, cb) {
|
||||
const recvTime = (new Date()).getTime();
|
||||
|
||||
if (res_settings.status === 200) {
|
||||
res_settings.json().then((settings) => {
|
||||
if (settings.start)
|
||||
settings.start = new Date(settings.start);
|
||||
if (settings.end)
|
||||
settings.end = new Date(settings.end);
|
||||
if (settings.generation)
|
||||
settings.generation = new Date(settings.generation);
|
||||
if (settings.activateTime)
|
||||
settings.activateTime = new Date(settings.activateTime);
|
||||
if (!settings.disablesubmitbutton)
|
||||
settings.disablesubmitbutton = null;
|
||||
|
||||
settings.recvTime = recvTime;
|
||||
const x_fic_time = res_settings.headers.get("x-fic-time");
|
||||
if (x_fic_time) {
|
||||
settings.currentTime = Math.floor(x_fic_time * 1000);
|
||||
} else {
|
||||
settings.currentTime = settings.recvTime;
|
||||
}
|
||||
|
||||
update((s) => (Object.assign({}, settings)));
|
||||
|
||||
if (cb) {
|
||||
cb(settings);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
update: (res_settings, cb) => {
|
||||
const recvTime = (new Date()).getTime();
|
||||
|
||||
if (res_settings.status === 200) {
|
||||
res_settings.json().then((settings) => {
|
||||
if (settings.start)
|
||||
settings.start = new Date(settings.start);
|
||||
if (settings.end)
|
||||
settings.end = new Date(settings.end);
|
||||
if (settings.generation)
|
||||
settings.generation = new Date(settings.generation);
|
||||
if (settings.activateTime)
|
||||
settings.activateTime = new Date(settings.activateTime);
|
||||
if (!settings.disablesubmitbutton)
|
||||
settings.disablesubmitbutton = null;
|
||||
|
||||
settings.recvTime = recvTime;
|
||||
const x_fic_time = res_settings.headers.get("x-fic-time");
|
||||
if (x_fic_time) {
|
||||
settings.currentTime = Math.floor(x_fic_time * 1000);
|
||||
} else {
|
||||
settings.currentTime = settings.recvTime;
|
||||
}
|
||||
|
||||
update((s) => (Object.assign({}, settings)));
|
||||
|
||||
if (cb) {
|
||||
cb(settings);
|
||||
}
|
||||
});
|
||||
refresh: async (cb=null, interval=null) => {
|
||||
if (refresh_interval_settings)
|
||||
clearInterval(refresh_interval_settings);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_settings = setInterval(refresh_settings, interval);
|
||||
|
||||
if (!cb) {
|
||||
// Before we start, update settings more frequently.
|
||||
cb = function(stgs) {
|
||||
const srv_cur = new Date(Date.now() + (stgs.currentTime - stgs.recvTime));
|
||||
|
||||
if (settings.start > srv_cur) {
|
||||
const startIn = settings.start - srv_cur;
|
||||
if (startIn > 15000) {
|
||||
setTimeout(refresh_settings, Math.floor(Math.random() * 10000) + 2400)
|
||||
} else if (startIn > 1500) {
|
||||
setTimeout(refresh_settings, startIn - 1000 - Math.floor(Math.random() * 500))
|
||||
} else {
|
||||
// On scheduled start time, refresh my.json file
|
||||
setTimeout(refresh_my, startIn + Math.floor(Math.random() * 200))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
updateFunc(await fetch('settings.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
},
|
||||
|
||||
update: updateFunc,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,51 @@
|
|||
import { derived, writable } from 'svelte/store';
|
||||
|
||||
import { stop_refresh } from './common';
|
||||
|
||||
let refresh_interval_teams = null;
|
||||
|
||||
function createTeamsStore() {
|
||||
const { subscribe, set, update } = writable({teams:{}, teams_count: 0, rank: []});
|
||||
|
||||
function updateFunc(res_teams, cb=null) {
|
||||
if (res_teams.status === 200) {
|
||||
res_teams.json().then((teams) => {
|
||||
const teams_count = Object.keys(teams).length
|
||||
|
||||
const rank = [];
|
||||
for (const tid in teams) {
|
||||
teams[tid].id = Number(tid);
|
||||
rank.push(teams[tid]);
|
||||
}
|
||||
rank.sort((a, b) => (a.rank > b.rank ? 1 : (a.rank == b.rank ? 0 : -1)));
|
||||
|
||||
update((t) => (Object.assign(t, {teams, teams_count, rank})));
|
||||
|
||||
if (cb) {
|
||||
cb(teams, teams_count, rank);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
update: (res_teams, cb=null) => {
|
||||
if (res_teams.status === 200) {
|
||||
res_teams.json().then((teams) => {
|
||||
const teams_count = Object.keys(teams).length
|
||||
|
||||
const rank = [];
|
||||
for (const tid in teams) {
|
||||
teams[tid].id = Number(tid);
|
||||
rank.push(teams[tid]);
|
||||
}
|
||||
rank.sort((a, b) => (a.rank > b.rank ? 1 : (a.rank == b.rank ? 0 : -1)));
|
||||
|
||||
update((t) => (Object.assign(t, {teams, teams_count, rank})));
|
||||
|
||||
if (cb) {
|
||||
cb(teams, teams_count, rank);
|
||||
}
|
||||
});
|
||||
refresh: async (cb=null, interval=null) => {
|
||||
if (refresh_interval_teams)
|
||||
clearInterval(refresh_interval_teams);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_teams = setInterval(refresh_teams, interval);
|
||||
|
||||
updateFunc(await fetch('teams.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
},
|
||||
|
||||
update: updateFunc,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,52 +1,73 @@
|
|||
import { derived, writable } from 'svelte/store';
|
||||
|
||||
import { stop_refresh } from './common';
|
||||
|
||||
let refresh_interval_themes = null;
|
||||
|
||||
function createThemesStore() {
|
||||
const { subscribe, set, update } = writable({themes: null, exercices_idx: {}, max_solved: 0});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
update: async (res_themes, cb=null) => {
|
||||
if (res_themes.status === 200) {
|
||||
const themes = await res_themes.json();
|
||||
async function updateFunc (res_themes, cb=null) {
|
||||
if (res_themes.status === 200) {
|
||||
const themes = await res_themes.json();
|
||||
|
||||
let max_solved = 0;
|
||||
const exercices_idx = {};
|
||||
let max_solved = 0;
|
||||
const exercices_idx = {};
|
||||
|
||||
for (let key in themes) {
|
||||
const theme = themes[key];
|
||||
for (let key in themes) {
|
||||
const theme = themes[key];
|
||||
|
||||
if (theme.solved > max_solved) {
|
||||
max_solved = theme.solved;
|
||||
if (theme.solved > max_solved) {
|
||||
max_solved = theme.solved;
|
||||
}
|
||||
|
||||
themes[key].exercice_count = Object.keys(theme.exercices).length;
|
||||
themes[key].exercice_coeff_max = 0;
|
||||
themes[key].max_gain = 0;
|
||||
let last_exercice = null;
|
||||
for (let k in theme.exercices) {
|
||||
const exercice = theme.exercices[k];
|
||||
|
||||
themes[key].max_gain += exercice.gain;
|
||||
if (themes[key].exercice_coeff_max < exercice.curcoeff) {
|
||||
themes[key].exercice_coeff_max = exercice.curcoeff;
|
||||
}
|
||||
|
||||
themes[key].exercice_count = Object.keys(theme.exercices).length;
|
||||
themes[key].exercice_coeff_max = 0;
|
||||
themes[key].max_gain = 0;
|
||||
let last_exercice = null;
|
||||
for (let k in theme.exercices) {
|
||||
const exercice = theme.exercices[k];
|
||||
if (last_exercice != null)
|
||||
themes[key].exercices[last_exercice].next = k;
|
||||
last_exercice = k;
|
||||
|
||||
themes[key].max_gain += exercice.gain;
|
||||
if (themes[key].exercice_coeff_max < exercice.curcoeff) {
|
||||
themes[key].exercice_coeff_max = exercice.curcoeff;
|
||||
}
|
||||
|
||||
if (last_exercice != null)
|
||||
themes[key].exercices[last_exercice].next = k;
|
||||
last_exercice = k;
|
||||
|
||||
exercice.id = k;
|
||||
exercices_idx[k] = exercice;
|
||||
}
|
||||
}
|
||||
|
||||
update((t) => (Object.assign(t, {themes, exercices_idx, max_solved})));
|
||||
|
||||
if (cb) {
|
||||
cb(themes, exercices_idx, max_solved);
|
||||
}
|
||||
exercice.id = k;
|
||||
exercices_idx[k] = exercice;
|
||||
}
|
||||
}
|
||||
|
||||
update((t) => (Object.assign(t, {themes, exercices_idx, max_solved})));
|
||||
|
||||
if (cb) {
|
||||
cb(themes, exercices_idx, max_solved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
refresh: async (cb=null, interval=null) => {
|
||||
if (refresh_interval_themes)
|
||||
clearInterval(refresh_interval_themes);
|
||||
if (interval === null) {
|
||||
interval = Math.floor(Math.random() * 24000) + 32000;
|
||||
}
|
||||
if (stop_refresh) {
|
||||
return;
|
||||
}
|
||||
refresh_interval_themes = setInterval(refresh_themes, interval);
|
||||
|
||||
await updateFunc(await fetch('themes.json', {headers: {'Accept': 'application/json'}}), cb);
|
||||
},
|
||||
|
||||
update: updateFunc,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
44
frontend/ui/static/e404.html
Normal file
44
frontend/ui/static/e404.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Challenge Forensic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta name="author" content="EPITA Laboratoire SRS">
|
||||
<meta name="robots" content="all">
|
||||
<link href="../../static/main.css" rel="stylesheet">
|
||||
<link href="../../theme/styles.css" rel="stylesheet">
|
||||
<style>
|
||||
.niceborder {
|
||||
border-bottom-color: #ee5f5b !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-body">
|
||||
|
||||
<div class="theme-navbar niceborder">
|
||||
<div class="theme-navbar__logo-wrap">
|
||||
<img class="theme-navbar__logo" src="/img/fic.png" alt="Forum International de la Cybersécurité">
|
||||
</div>
|
||||
<div class="theme-navbar__logo-wrap">
|
||||
<img class="theme-navbar__logo" src="/img/epita.png" alt="Épita">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container dex-container" style="margin-top:20px;">
|
||||
<div class="jumbotron theme-panel niceborder">
|
||||
<h1>Page introuvable <small>Erreur 404</small></h1>
|
||||
<hr>
|
||||
<p class="lead">
|
||||
La page à laquelle vous tentez d'accéder n'existe pas ou l'adresse que vous avez tapée est incorrecte.
|
||||
</p>
|
||||
<p>
|
||||
Si le problème persiste, <a href="mailto:root@srs.epita.fr">contactez un administrateur</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
1
frontend/ui/static/e404.json
Normal file
1
frontend/ui/static/e404.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"errmsg": "La page à laquelle vous tentez d'accéder n'existe pas ou l'adresse que vous avez tapée est incorrecte."}
|
41
frontend/ui/static/e413.html
Normal file
41
frontend/ui/static/e413.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Challenge Forensic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta name="author" content="EPITA Laboratoire SRS">
|
||||
<meta name="robots" content="all">
|
||||
<link href="../../static/main.css" rel="stylesheet">
|
||||
<link href="../../theme/styles.css" rel="stylesheet">
|
||||
<style>
|
||||
.niceborder {
|
||||
border-bottom-color: #ee5f5b !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-body">
|
||||
|
||||
<div class="theme-navbar niceborder">
|
||||
<div class="theme-navbar__logo-wrap">
|
||||
<img class="theme-navbar__logo" src="/img/fic.png" alt="Forum International de la Cybersécurité">
|
||||
</div>
|
||||
<div class="theme-navbar__logo-wrap">
|
||||
<img class="theme-navbar__logo" src="/img/epita.png" alt="Épita">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container dex-container" style="margin-top:20px;">
|
||||
<div class="jumbotron theme-panel niceborder">
|
||||
<h1>Requête trop grosse <small>Erreur 413</small></h1>
|
||||
<hr>
|
||||
<p class="lead">
|
||||
La quantité de données que vous souhaitez envoyer au serveur est trop importante pour qu'il accepte de la traiter.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
1
frontend/ui/static/e413.json
Normal file
1
frontend/ui/static/e413.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"errmsg": "La quantité de données que vous souhaitez envoyer au serveur est trop importante pour qu'il accepte de la traiter."}
|
44
frontend/ui/static/e500.html
Normal file
44
frontend/ui/static/e500.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Challenge Forensic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||
<meta name="author" content="EPITA Laboratoire SRS">
|
||||
<meta name="robots" content="all">
|
||||
<link href="../../static/main.css" rel="stylesheet">
|
||||
<link href="../../theme/styles.css" rel="stylesheet">
|
||||
<style>
|
||||
.niceborder {
|
||||
border-bottom-color: #ee5f5b !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="theme-body">
|
||||
|
||||
<div class="theme-navbar niceborder">
|
||||
<div class="theme-navbar__logo-wrap">
|
||||
<img class="theme-navbar__logo" src="/img/fic.png" alt="Forum International de la Cybersécurité">
|
||||
</div>
|
||||
<div class="theme-navbar__logo-wrap">
|
||||
<img class="theme-navbar__logo" src="/img/epita.png" alt="Épita">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container dex-container" style="margin-top:20px;">
|
||||
<div class="jumbotron theme-panel niceborder">
|
||||
<h1>Erreur interne <small>Erreur 500</small></h1>
|
||||
<hr>
|
||||
<p class="lead">
|
||||
Notre serveur est actuellement dans l'incapacité de répondre à votre requête.<br>Veuillez recommencer dans quelques instants.
|
||||
</p>
|
||||
<p>
|
||||
Si le problème persiste, <a href="mailto:root@srs.epita.fr">contactez un administrateur</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
1
frontend/ui/static/e500.json
Normal file
1
frontend/ui/static/e500.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"errmsg": "Notre serveur est actuellement dans l'incapacité de répondre à votre requête. \nVeuillez recommencer dans quelques instants."}
|
Reference in a new issue