f01d492d95
With this commit two calculation errors are fixed * the score in the case partyPosition = 'negative' while userPosition = 'neutral' shouldn't be MIN_POINTS but rather BASE_POINTS. * the totalScoredPoints (now "totalMaxPoints") should be independent of any party-positions which it wasn't. To minimize errors in this area in the future tests are added which are based on the official Rechenmodel of the bpb. To that end the score calculation logic was refactored and moved from results.vue to scoring.js, too. (cherry picked from commit 2d246fefbc4730ca5f7a4224325084a98f1c41f0)
444 lines
10 KiB
Vue
444 lines
10 KiB
Vue
<template>
|
|
<section>
|
|
<header class="results-header">
|
|
<h1>{{ $t('results.headline') }}</h1>
|
|
</header>
|
|
|
|
<div class="results-content">
|
|
<p>{{ $t('results.entry') }}</p>
|
|
<span>{{ $t('results.hint') }}</span>
|
|
</div>
|
|
|
|
<ul class="party-results">
|
|
<li v-for="party of parties" :key="party.token">
|
|
<router-link :to="{ path: getPartyPath(party.token) }">
|
|
<div class="result-party-info">
|
|
<div class="result-party-logo">
|
|
<img
|
|
v-if="hasPartyLogo(party.token)"
|
|
:src="getPartyLogo(party.token)"
|
|
width="50"
|
|
height="50"
|
|
:alt="party.token"
|
|
>
|
|
<span v-else>{{ party.token }}</span>
|
|
</div>
|
|
|
|
<h2>{{ getScorePercentage(party.score) }}%</h2>
|
|
</div>
|
|
|
|
<feather-zoom-in class="results-see-more" />
|
|
|
|
<v-progress
|
|
class="result-percentage"
|
|
:value="party.score"
|
|
:max="totalMaxPoints"
|
|
/>
|
|
</router-link>
|
|
|
|
<div v-if="party.nationalParty" class="party-results-national">
|
|
<feather-corner-down-right />
|
|
<span>
|
|
{{ $t('results.nationalParty') }}
|
|
<a
|
|
class="party-results-national-logo"
|
|
:href="party.nationalParty.program"
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
<div v-if="hasPartyLogo(party.nationalParty.token)">
|
|
<img
|
|
:src="getPartyLogo(party.nationalParty.token)"
|
|
:alt="party.nationalParty.name"
|
|
:title="party.nationalParty.name"
|
|
width="40"
|
|
height="40"
|
|
>
|
|
</div>
|
|
<span v-else>{{ party.nationalParty.token }}</span>
|
|
</a>
|
|
</span>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
|
|
<div v-if="!isEmbedded" class="results-ctrls">
|
|
<p>{{ $t('results.thanks') }}</p>
|
|
<router-link tag="a"
|
|
class="btn"
|
|
:to="{ path: `/${$i18n.locale}/` }"
|
|
>
|
|
{{ $t('results.indexBtn') }}
|
|
</router-link>
|
|
<router-link
|
|
tag="a"
|
|
class="btn btn-dark btn-small"
|
|
:to="{ path: startOverUrl }"
|
|
>
|
|
{{ $t('results.startoverBtn') }}
|
|
<feather-rotate-cw />
|
|
</router-link>
|
|
</div>
|
|
|
|
<div class="results-affiliation">
|
|
<a
|
|
href="https://www.talkingeurope.com/"
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
<img
|
|
:src="talkingEuropeBanner"
|
|
title="Talking Europe"
|
|
alt="Talking Europe Banner"
|
|
>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script>
|
|
import { IPDATA_URL } from '@/config/api'
|
|
import { getTranslatedUrl, getUserLanguage } from '@/i18n/helper'
|
|
import { getPartiesWithScores, getTotalMaxPoints } from '@/app/euromat/scoring'
|
|
import { parties } from '@/data'
|
|
|
|
export default {
|
|
name: 'Results',
|
|
|
|
components: {
|
|
'feather-zoom-in': () =>
|
|
import('vue-feather-icons/icons/ZoomInIcon' /* webpackChunkName: "icons" */),
|
|
'feather-rotate-cw': () =>
|
|
import('vue-feather-icons/icons/RotateCwIcon' /* webpackChunkName: "icons" */),
|
|
'feather-corner-down-right': () =>
|
|
import('vue-feather-icons/icons/CornerDownRightIcon' /* webpackChunkName: "icons" */)
|
|
},
|
|
|
|
data () {
|
|
return {
|
|
userCountry: getUserLanguage().country,
|
|
scoringGrid: [],
|
|
parties: [],
|
|
totalMaxPoints: 0
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
startOverUrl () {
|
|
return getTranslatedUrl('theses')
|
|
},
|
|
isEmbedded () {
|
|
return (
|
|
this.$route.query.embedded &&
|
|
this.$route.query.embedded === 'iframe'
|
|
)
|
|
},
|
|
talkingEuropeBanner () {
|
|
try {
|
|
return require(`@/assets/talkingeurope/talkingeurope-${this.$i18n.locale}.png`)
|
|
} catch (e) {
|
|
console.warn('TalkingEurope image not found, defaulting to "en". ', e)
|
|
return require(`@/assets/talkingeurope/talkingeurope-en.png`)
|
|
}
|
|
}
|
|
},
|
|
|
|
async created () {
|
|
let emphasized
|
|
let answers
|
|
|
|
if (this.$browser.supports('sessionStorage')) {
|
|
emphasized = JSON.parse(sessionStorage.getItem('euromat-emphasized'))
|
|
answers = JSON.parse(sessionStorage.getItem('euromat-answers'))
|
|
} else {
|
|
emphasized = JSON.parse(this.$root.$data.backupStorage.emphasized)
|
|
answers = JSON.parse(this.$root.$data.backupStorage.answers)
|
|
}
|
|
|
|
if (!emphasized) {
|
|
this.$router.push({ path: getTranslatedUrl('theses') })
|
|
}
|
|
|
|
try {
|
|
const ipResponse = await fetch(IPDATA_URL)
|
|
const ipData = await ipResponse.json()
|
|
if (ipData.country_code) {
|
|
this.userCountry = ipData.country_code.toLowerCase()
|
|
}
|
|
} catch (error) {
|
|
console.warn('Unable to fetch geo location:', error)
|
|
}
|
|
|
|
const partiesWithScores = getPartiesWithScores(answers, emphasized, parties)
|
|
this.parties = partiesWithScores.map(party => ({
|
|
token: party.token,
|
|
score: party.score,
|
|
nationalParty: party['national_parties'][this.userCountry]
|
|
}))
|
|
.sort((a, b) => a.score - b.score)
|
|
.reverse()
|
|
this.totalMaxPoints = getTotalMaxPoints(answers, emphasized)
|
|
},
|
|
|
|
methods: {
|
|
getPartyPath (token) {
|
|
return `${getTranslatedUrl('party')}/${token.toLowerCase()}`
|
|
},
|
|
getPartyLogo (token) {
|
|
try {
|
|
return require(`@/assets/svg/${token.toLowerCase().replace(/\s/g, '-')}-logo.svg`)
|
|
} catch (e) {
|
|
try {
|
|
return require(`@/assets/${token.toLowerCase().replace(/\s/g, '-')}-logo.png`)
|
|
} catch (error) {
|
|
console.warn(`No logo found for party "${token}", falling back to initials.`, error.message)
|
|
return false
|
|
}
|
|
}
|
|
},
|
|
hasPartyLogo (token) {
|
|
try {
|
|
require(`@/assets/svg/${token.toLowerCase().replace(/\s/g, '-')}-logo.svg`)
|
|
return true
|
|
} catch (e) {
|
|
try {
|
|
require(`@/assets/${token.toLowerCase().replace(/\s/g, '-')}-logo.png`)
|
|
return true
|
|
} catch (error) {
|
|
return false
|
|
}
|
|
}
|
|
},
|
|
getScorePercentage (score) {
|
|
return (score / this.totalMaxPoints * 100).toFixed(2)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "~@/styles/animations";
|
|
@import "~@/styles/colors";
|
|
@import "~@/styles/layout";
|
|
|
|
$result-bar-length: 92%;
|
|
|
|
section {
|
|
width: 95%;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.results-header {
|
|
margin-bottom: $base-gap;
|
|
|
|
h1 {
|
|
margin-bottom: $small-gap;
|
|
}
|
|
}
|
|
|
|
.results-content {
|
|
margin-bottom: $base-gap;
|
|
|
|
span {
|
|
margin-top: $small-gap;
|
|
color: $text-color-secondary;
|
|
font-size: $font-size-small;
|
|
}
|
|
}
|
|
|
|
.party-results {
|
|
list-style: none;
|
|
width: 100%;
|
|
counter-reset: result;
|
|
|
|
li {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
margin-bottom: $base-gap;
|
|
position: relative;
|
|
|
|
&:hover .results-see-more {
|
|
opacity: 1;
|
|
}
|
|
|
|
&::before {
|
|
counter-increment: result;
|
|
content: counter(result) ".";
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
transform: translateY(-50%);
|
|
color: $text-color-secondary;
|
|
font-size: $font-size-xlarge;
|
|
font-weight: 600;
|
|
}
|
|
|
|
@media (max-width: 650px) {
|
|
&::before {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
a:not(.party-results-national-logo) {
|
|
height: 80px;
|
|
width: $result-bar-length;
|
|
position: relative;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
@media (max-width: 650px) {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
h2,
|
|
.results-see-more {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
h2 {
|
|
color: $text-color-base;
|
|
font-weight: 600;
|
|
text-shadow: $text-shadow;
|
|
|
|
span {
|
|
font-weight: 400;
|
|
}
|
|
}
|
|
|
|
.results-see-more {
|
|
stroke: $text-color-base;
|
|
filter: drop-shadow($text-shadow);
|
|
height: 32px;
|
|
width: 32px;
|
|
opacity: 0;
|
|
transition: opacity 150ms $easeOutBack;
|
|
margin-right: $base-gap;
|
|
}
|
|
|
|
.result-percentage {
|
|
height: 100%;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
}
|
|
|
|
.party-results-national {
|
|
width: $result-bar-length;
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
padding-top: calc(#{$small-gap} / 2);
|
|
padding-left: $small-gap;
|
|
|
|
svg {
|
|
margin-right: calc(#{$small-gap} / 2);
|
|
}
|
|
|
|
> span {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.party-results-national-logo {
|
|
display: inline-block;
|
|
font-weight: 700;
|
|
margin-left: calc(#{$small-gap} / 2);
|
|
|
|
> div {
|
|
width: 70px;
|
|
height: auto;
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
img {
|
|
object-fit: contain;
|
|
width: 80%;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 650px) {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
.result-party-info {
|
|
display: flex;
|
|
height: calc(100% - 4px);
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.result-party-logo {
|
|
margin-right: $small-gap;
|
|
position: relative;
|
|
z-index: 1;
|
|
background: $background-secondary;
|
|
border-radius: $border-radius;
|
|
width: 80px;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-left: 2px;
|
|
|
|
img {
|
|
object-fit: contain;
|
|
}
|
|
|
|
span {
|
|
color: $text-color-invert;
|
|
font-weight: 700;
|
|
}
|
|
}
|
|
}
|
|
|
|
.results-ctrls {
|
|
margin-top: $base-gap * 2;
|
|
border-top: 4px solid $transparent-white;
|
|
padding-top: $small-gap;
|
|
|
|
p {
|
|
margin-bottom: $small-gap;
|
|
}
|
|
|
|
a:first-of-type {
|
|
margin-right: $small-gap;
|
|
}
|
|
}
|
|
|
|
.results-affiliation {
|
|
background: $medium-blue;
|
|
width: 100%;
|
|
margin-top: $base-gap * 2;
|
|
padding: calc(#{$small-gap} / 2);
|
|
border-radius: calc(#{$border-radius} / 3);
|
|
|
|
@media (max-width: 650px) {
|
|
padding: 0;
|
|
margin-top: $base-gap * 2;
|
|
border-top: 4px solid $transparent-white;
|
|
padding-top: $small-gap;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
}
|
|
|
|
a {
|
|
display: block;
|
|
}
|
|
|
|
img {
|
|
width: 100%;
|
|
height: auto;
|
|
vertical-align: middle;
|
|
}
|
|
}
|
|
</style>
|