Skip to content

Commit

Permalink
add new frontend features
Browse files Browse the repository at this point in the history
  • Loading branch information
thotypous committed Oct 20, 2017
1 parent a63486b commit af18938
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 71 deletions.
30 changes: 30 additions & 0 deletions frontend/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,34 @@

.clickable {
cursor: pointer;
}

.btn-small {
width: 30px;
height: 30px;
}

.icon-header {
font-size: 20px !important;
line-height: 30px !important;
}

.input-header input {
font-weight: bold;
font-size: 15px !important;
padding-left: 0px !important;
width: 150px;
}

.team-selected {
background-color: #ccc !important;
}

.blue-grey.darken-1.is-solved {
background-color: green !important;
}

.country-flag {
width: 25px;
padding: 2px;
}
22 changes: 18 additions & 4 deletions frontend/js/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
$.ajaxSetup({ cache: false });

Vue.use(VueI18n);
if (!Cookies.get('lang')) {
Cookies.set('lang', 'En');
}
const routes = [
{
path: '/',
Expand All @@ -16,6 +18,10 @@ const routes = [
{
path: '/team/:name',
component: Team
},
{
path: '/settings',
component: Settings
}
];

Expand All @@ -27,7 +33,7 @@ const Title = Vue.component('app-title', {
template: `
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<h4 class="header center orange-text">{{title}}</h4>
<h4 class="header center orange-text">{{$t(title)}}</h4>
</div>
</div>
`,
Expand All @@ -42,5 +48,13 @@ const app = new Vue({
async mounted() {
settings = await getSettings();
this.loaded = true;
}
},
i18n: new VueI18n({
locale: Cookies.get('lang'),
fallbackLocale: 'En',
messages: {
En: enLocale,
Pt: ptLocale
}
})
}).$mount('#app');
79 changes: 63 additions & 16 deletions frontend/js/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,65 @@ const ChallengeModal = Vue.component('challenge-modal', {
<div id="modal1" class="modal">
<div class="modal-content">
<h4>{{challenge.title}}</h4>
<p>{{challenge.description}}</p>
<p>Total solves: {{challenge.solves}}</p>
<p>Points: {{challenge.points}}</p>
<p>Categories: {{challenge.tags.join(', ')}}</p>
<p v-html="challenge.description"></p>
<p><strong>{{$t('total-solves')}}:</strong> {{challenge.solves}}</p>
<p><strong>{{$t('score')}}:</strong> {{challenge.points}}</p>
<p><strong>{{$t('categories')}}:</strong> {{challenge.tags.join(', ')}}</p>
</div>
<div class="modal-footer">
<button class="modal-action modal-close waves-effect waves-green btn-flat">Close</button>
</div>
</div>
`,
props: ['challenge'],
mounted: function() {
data: () => ({
loaded: false,
descriptionMap: {}
}),
methods: {
loadDescription: async function(challenge) {
const lang = Cookies.get('lang').toLowerCase();

if (!this.descriptionMap[lang]) {
this.descriptionMap[lang] = {};
}

if (this.descriptionMap[lang][challenge.id]) {
this.challenge.description = this.descriptionMap[lang][challenge.id];
return;
}

const challengeMd = await getChallengeDescription(this.challenge.id, lang);
this.descriptionMap[lang][challenge.id] = converter.makeHtml(challengeMd);
this.challenge.description = this.descriptionMap[lang][challenge.id];
},
},
mounted: async function() {
$('.modal').modal();
}
this.loadDescription(this.challenge);
},
watch: {
challenge: function(challenge) {
thisloaded = false
this.loadDescription(challenge);
}
},
})

const ChallengeComponent = Vue.component('challenge-card', {
template: `
<div v-on:click="selectChallenge" class="col s12 m4">
<div class="clickable card blue-grey darken-1">
<div v-bind:class="{ 'is-solved': challenge.solved }" class="clickable card blue-grey darken-1">
<div class="card-content white-text">
<span class="card-title">{{challenge.title}}</span>
<div class="row"><strong>Total solves:</strong> {{challenge.solves}}</div>
<div class="row"><strong>{{$t('total-solves')}}:</strong> {{challenge.solves}}</div>
<div class="row"><strong>{{$t('score')}}:</strong> {{challenge.points}}</div>
</div>
<div class="card-action">
<div class="row"><span v-for="tag in challenge.tags" class="new badge" data-badge-caption="">{{tag}}</span></div>
<div class="row"><span class="new badge red" data-badge-caption="points">{{challenge.points}}</span></div>
<div class="row">
<span v-if="challenge.optional" class="new badge red" data-badge-caption="">{{$t(challenge.optional)}}</span>
<span v-for="tag in challenge.tags" class="new badge" data-badge-caption="">{{tag}}</span>
</div>
</div>
</div>
</div>
Expand All @@ -45,7 +77,7 @@ const ChallengeComponent = Vue.component('challenge-card', {
const Challenges = Vue.component('challenges', {
template: `
<div class="row">
<app-title v-if="!hideTitle" title="Challenges"></app-title>
<app-title v-if="!hideTitle" title="challenges"></app-title>
<div v-for="challenge in challenges">
<challenge-card :selectChallengeFunction="openModal" :challenge="challenge" />
</div>
Expand All @@ -61,7 +93,7 @@ const Challenges = Vue.component('challenges', {
props: ['hideTitle', 'submissions'],
watch: {
submissions: function(submissions) {
this.setChallengesSolves(submissions);
this.loadSubmissions(submissions);
}
},
methods: {
Expand All @@ -74,22 +106,37 @@ const Challenges = Vue.component('challenges', {
.map(mountChallPromise);

await Promise.all(challPromiseMap(challengeList));
this.challenges = this.challenges.sort((challA, challB) => challA.title.localeCompare(challB.title))
if (!this.submissions && !this.submissionsPolling.isStarted) {
this.submissionsPolling.start();
}
},
setChallengesSolves: function(acceptedSubmissions) {
solves = acceptedSubmissions.standings.reduce((reducer, { taskStats }) => {
loadSubmissions: function(acceptedSubmissions) {
const userTeam = Cookies.get('team');
let teamSolves = new Set([]);
solves = acceptedSubmissions.standings.reduce((reducer, { taskStats, team }) => {
Object.keys(taskStats).forEach(chall => {
reducer[chall]++ || (reducer[chall] = 1)
});

if (userTeam && userTeam === team) {
teamSolves = new Set(Object.keys(taskStats));
}
return reducer;
}, {});

this.challenges.forEach((challenge, index) => {
this.challenges.splice(index, 1, Object.assign({}, challenge, { solves: solves[challenge.id] || 0 }));
this.challenges.splice(index, 1, Object.assign({}, challenge, {
solves: solves[challenge.id] || 0,
points: this.calculatePoints(solves[challenge.id]),
solved: teamSolves.has(challenge.id)
}));
});
},
calculatePoints: function(solves) {
const { K, V, minpts, maxpts } = settings['dynamic_scoring'];
return parseInt(Math.max(minpts, Math.floor(maxpts - K * Math.log2(((solves + 1 || 1) + V)/(1 + V)))))
},
openModal: function(challenge) {
this.selectedChallenge = challenge;
Vue.nextTick(() => {
Expand All @@ -106,7 +153,7 @@ const Challenges = Vue.component('challenges', {

this.submissionsPolling = createPooling(
getSolvedChallenges,
this.setChallengesSolves
this.loadSubmissions
);
title = 'Challenges';
},
Expand Down
21 changes: 13 additions & 8 deletions frontend/js/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const createPooling = (promise, cb, intervalTime) => {
cb(await promise());
interval = setInterval(async () => {
cb(await promise());
}, intervalTime || 10000);
}, intervalTime || 60000);
},
stop () {
this.isStarted = false
Expand All @@ -21,16 +21,21 @@ const createPooling = (promise, cb, intervalTime) => {
}
};

const converter = new showdown.Converter();
const getSubmisionsPath = () => settings.submissions_project.split('/')[1];
const getTeamPath = teamName => sha256(teamName).splice(1, 0, '/').splice(5, 0, '/');
const mountUrl = (path, time = (1000 * 60 * 10)) => `${path}?_${Math.floor(+(new Date)/time)}`

const getSettings = () => $.getJSON('settings.json');
const getNews = () => $.getJSON('submissions/news.json');
const getChallenges = () => $.getJSON('challenges/index.json');
const getChallenge = (id) => $.getJSON(`challenges/${id}.json`);
const getSolvedChallenges = () => $.getJSON(`/${getSubmisionsPath()}/accepted-submissions.json`);
const getTeam = hash => $.getJSON(`/${getSubmisionsPath()}/${hash}/team.json`);
const getTeamMembers = hash => $.getJSON(`/${getSubmisionsPath()}/${hash}/members.json`);
const getNews = () => $.getJSON(mountUrl(`/${getSubmisionsPath()}/news.json`));
const getChallenges = () => $.getJSON(mountUrl('challenges/index.json'));
const getChallenge = id => $.getJSON(mountUrl(`challenges/${id}.json`));
const getChallengeDescription = (id, lang) => $.get(mountUrl(`challenges/${id}.${lang.toLowerCase()}.md`));
const getSolvedChallenges = () => $.getJSON(mountUrl(`/${getSubmisionsPath()}/accepted-submissions.json`, 1000 * 60));
const getTeam = teamName => $.getJSON(mountUrl(`/${getSubmisionsPath()}/${getTeamPath(teamName)}/team.json`));
const getTeamMembers = teamName => $.getJSON(mountUrl(`/${getSubmisionsPath()}/${getTeamPath(teamName)}/members.json`));
const getLocaleMessages = lang => $.getJSON(mountUrl(`frontend/locales/${lang}.json`));

String.prototype.splice = function(idx, rem, str) {
return this.slice(0, idx) + str + this.slice(idx + Math.abs(rem));
};
};
38 changes: 26 additions & 12 deletions frontend/js/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@ const News = Vue.component('news', {
<div class="news z-depth-1">
<div class="col s12">
<ul class="tabs">
<li class="tab col s3">
<li class="tab col s4">
<a class="active" href="#test1">
<div>Messages <div class="chip">{{news.length}}</div></div>
<div>{{$t("messages")}} <div class="chip">{{news.length}}</div></div>
</a>
</li>
<li class="tab col s3">
<li class="tab col s4">
<a href="#test2">
<div>Solves <div class="chip">{{solves.length}}</div></div>
<div>{{$t("solves")}} <div class="chip">{{solves.length}}</div></div>
</a>
</li>
</ul>
</div>
<div id="test1" class="col s12">
<div v-for="singleNews in news">[ {{formatDate(singleNews.time)}} ] admin: {{singleNews.msg}}</div>
<div v-for="singleNews in news">
<span v-if="!singleNews.to">[ {{formatDate(singleNews.time)}} ] admin: {{formatNews(singleNews)}}</span>
<span v-if="singleNews.to">[ {{formatDate(singleNews.time)}} ] admin: {{$t("private-message")}}</span>
</div>
</div>
<div id="test2" class="col s12">
<div v-for="solve in solves">[ {{formatDate(solve.time)}} ] {{solve.team}} solved {{solve.chall}}</div>
<div v-for="solve in solves">[ {{formatDate(solve.time)}} ] {{solve.team}} {{$t("solved")}} {{solve.chall}}</div>
</div>
</div>
`,
Expand All @@ -29,9 +32,20 @@ const News = Vue.component('news', {
solves: []
}),
methods: {
formatDate: date => moment(date).format('DD-MM-YYYY HH:mm:ss'),
formatDate: date => moment(date, "X").format('DD-MM-YYYY HH:mm:ss'),
formatNews: function(msg) {
if (msg.to) {
return
}

return msg.msg

},
loadNews: function(news) {
this.news = news;
this.news = news.filter(msg => {
console.log(!msg.to || (msg.to && msg.to === Cookies.get('team')));
return !msg.to || (msg.to && msg.to === Cookies.get('team'))
});
},
setChallengesSolves: function(acceptedSubmissions) {
this.solves = acceptedSubmissions.standings.reduce((reducer, { taskStats, team }) => {
Expand All @@ -44,7 +58,7 @@ const News = Vue.component('news', {
});
return reducer;
}, [])
.sort((solveA, solveB) => solveA <= solveB);
.sort((solveA, solveB) => solveB.time - solveA.time);
}
},
mounted: function() {
Expand Down Expand Up @@ -73,17 +87,17 @@ const Home = {
<app-title title="Home"></app-title>
<div class="row news-rank-row">
<div class="col s8">
<h5>News</h5>
<h5>{{$t('news')}}</h5>
<news></news>
</div>
<div class="col s4">
<h5>Rank</h5>
<h5>{{$t('rank')}}</h5>
<rank :hideTitle=true :limit=5></rank>
</div>
</div>
<div class="row">
<div class="col s12">
<h5>Challenges</h5>
<h5>{{$t('challenges')}}</h5>
<challenges :hideTitle=true></challenges>
</div>
</div>
Expand Down
Loading

0 comments on commit af18938

Please sign in to comment.