1
0
Fork 0
mirror of https://github.com/voltbonn/diversity.volt.link.git synced 2024-06-24 23:10:57 +00:00
diversity.volt.link/frontend/index.js
2022-11-22 01:20:59 +01:00

778 lines
24 KiB
JavaScript

// Just to make the console a bit queer.
console.info('%c \n Be yourself! \n\n', `
font-family: Ubuntu, sans-serif;
font-size: 5rem;
font-weight: bold;
color: white;
text-shadow:
1px 1px #ff69b5,
2px 2px #ff0000,
3px 3px #ff8f00,
4px 4px #ffff00,
5px 5px #008f00,
6px 6px #00c1c1,
7px 7px #3e0099,
8px 8px #8f008f;
`)
let teams = []
let teams_selected = new Set()
function selectTeam(team) {
teams_selected.add(team)
renderTeamSuggestions()
renderTeamSelected()
renderTeamSelectedAutomatically()
}
function deselectTeam(team) {
teams_selected = new Set([...teams_selected].filter(team_selected => team_selected.id !== team))
renderTeamSuggestions()
renderTeamSelected()
renderTeamSelectedAutomatically()
}
function renderTeamSuggestions() {
const team_suggestions_input = document.getElementById('team_suggestions_input')
const team_suggestions = document.getElementById('team_suggestions')
team_suggestions.innerHTML = ''
const team_suggestions_input_value = team_suggestions_input.value.toLowerCase()
if (team_suggestions_input_value.length > 0) {
// filter out parent_teams that are in the teams array
const teams_selected_ids = [...teams_selected]
.flatMap(team => [
team.id,
// ...team.parent_team_ids,
])
const filtered_teams = teams
.filter(team => !teams_selected_ids.includes(team.id))
.filter(team => team.name.toLowerCase().includes(team_suggestions_input_value))
.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)
for (const team of filtered_teams) {
const team_chip = document.createElement('div')
team_chip.classList.add('chip')
team_chip.classList.add('add')
team_chip.innerHTML = team.name
team_chip.addEventListener('click', () => {
selectTeam(team)
})
team_suggestions.appendChild(team_chip)
}
}
}
function renderTeamSelected() {
const teams_selected_node = document.getElementById('teams_selected')
teams_selected_node.innerHTML = ''
const filtered_teams = [...teams_selected]
.sort((a, b) => {
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
})
const selected_teams_wrapper_node = document.getElementById('selected_teams_wrapper')
if (filtered_teams.length === 0) {
selected_teams_wrapper_node.style.display = 'none'
} else {
selected_teams_wrapper_node.style.display = 'block'
}
for (const team of filtered_teams) {
const team_chip = document.createElement('div')
team_chip.classList.add('chip')
team_chip.classList.add('remove')
team_chip.innerHTML = team.name
team_chip.addEventListener('click', () => {
deselectTeam(team.id)
})
teams_selected_node.appendChild(team_chip)
}
}
function getParentTeams(parent_team_ids) {
let parent_teams = []
for (const parent_team_id of parent_team_ids) {
const team = teams.find(team => team.id === parent_team_id)
if (team) {
parent_teams.push(team)
}
}
// filter out parent_teams that are in the teams array
const teams_selected_ids = [...teams_selected].map(team => team.id)
parent_teams = parent_teams.filter(parent_team => !teams_selected_ids.includes(parent_team.id))
return parent_teams
}
function renderTeamSelectedAutomatically() {
const teams_selected_node = document.getElementById('teams_selected_automatically')
teams_selected_node.innerHTML = ''
const parent_teams = getParentTeams([...new Set([...teams_selected].flatMap(team => team.parent_team_ids))])
.sort((a, b) => {
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
})
for (const team of parent_teams) {
const team_chip = document.createElement('div')
team_chip.classList.add('chip')
team_chip.classList.add('automatic')
team_chip.innerHTML = team.name
teams_selected_node.appendChild(team_chip)
}
}
function loadTeams() {
// load teams from ./teams.json
fetch('./teams.json')
.then(response => response.json())
.then(data => {
teams = data.teams
renderTeamSuggestions()
})
.catch(error => {
console.error(error)
})
}
function initTeams() {
loadTeams()
renderTeamSuggestions()
renderTeamSelected()
renderTeamSelectedAutomatically()
}
initTeams()
const CloudFunctionsPrefix = 'https://us-central1-volt-4eca0.cloudfunctions.net/save_formdata'
function resetBodyClasses() {
body.classList.remove('error')
body.classList.remove('success')
body.classList.remove('saving')
}
function getByLanguage(objectWithValuesByLanguage) {
let value = '[NO TRANSLATION FOUND!]'
if (objectWithValuesByLanguage) {
if (objectWithValuesByLanguage[_language_]) {
value = objectWithValuesByLanguage[_language_]
} else if (objectWithValuesByLanguage['en']) {
value = objectWithValuesByLanguage['en']
} else if (objectWithValuesByLanguage['de']) {
value = objectWithValuesByLanguage['de']
}
}
// if (value.indexOf(' ') > -1) {
// value = value.substr(0,value.lastIndexOf(' '))+' '+value.substr(value.lastIndexOf(' ')+1)
// }
return value
}
function generateForm(data) {
// websiteTitle.innerHTML = 'Volt — '+getByLanguage(_DATA_.translation_texts.website_title).replace(/\n+/g, ' ')
for (const section of _DATA_.sections) {
var sectionEle = document.createElement('section')
if (!!section.heading) {
var sectionHeading = document.createElement('h2')
sectionHeading.innerHTML = getByLanguage(section.heading)
sectionEle.appendChild(sectionHeading)
}
if (!!section.intro) {
var sectionIntro = document.createElement('p')
sectionIntro.classList.add('intro')
sectionIntro.innerHTML = getByLanguage(section.intro)
sectionEle.appendChild(sectionIntro)
}
// display questions
for (const questionKey of Object.keys(section.questions)) {
const question = section.questions[questionKey]
var questionEle = document.createElement('div')
questionEle.classList.add('question')
if (!!question.question) {
var questionHeading = document.createElement('h3')
questionHeading.innerHTML = getByLanguage(question.question)
questionEle.appendChild(questionHeading)
}
if (!!question.why) {
var question_why_text = document.createElement('p')
question_why_text.classList.add('why_text')
if (!question.info) {
question_why_text.classList.add('no_info_text')
}
question_why_text.innerHTML = getByLanguage(question.why)
questionEle.appendChild(question_why_text)
}
if (!!question.info) {
var question_info_text = document.createElement('p')
// question_info_text.classList.add('info_text')
question_info_text.innerHTML = getByLanguage(question.info)
questionEle.appendChild(question_info_text)
}
if (question.type == 'number') {
let newInput = document.createElement('input')
newInput.addEventListener('click', resetBodyClasses)
newInput.setAttribute('name', questionKey)
newInput.setAttribute('type', 'number')
newInput.setAttribute('placeholder', '…')
newInput.addEventListener('click', resetBodyClasses)
// newInput.value = '123'
questionEle.appendChild(newInput)
} else if (question.type == 'checkbox') {
for (const key of Object.keys(question.options)) {
let labelEle = document.createElement('label')
labelEle.classList.add('radio_or_checkbox_label')
labelEle.addEventListener('click', resetBodyClasses)
let checkboxEle = document.createElement('input')
checkboxEle.setAttribute('type', 'checkbox')
checkboxEle.setAttribute('name', questionKey)
checkboxEle.setAttribute('value', key)
labelEle.appendChild(checkboxEle)
let spanEle = document.createElement('span')
spanEle.innerHTML = getByLanguage(question.options[key])
labelEle.appendChild(spanEle)
questionEle.appendChild(labelEle)
}
} else if (question.type == 'radio') {
for (const key of Object.keys(question.options)) {
let labelEle = document.createElement('label')
labelEle.classList.add('radio_or_checkbox_label')
labelEle.addEventListener('click', resetBodyClasses)
let checkboxEle = document.createElement('input')
checkboxEle.setAttribute('type', 'radio')
checkboxEle.setAttribute('name', questionKey)
checkboxEle.setAttribute('value', key)
labelEle.appendChild(checkboxEle)
let spanEle = document.createElement('span')
spanEle.innerHTML = getByLanguage(question.options[key])
labelEle.appendChild(spanEle)
questionEle.appendChild(labelEle)
}
} else if (question.type == 'chooser') {
let newInput = document.createElement('select')
newInput.addEventListener('click', resetBodyClasses)
if (questionKey == 'metadata_country') {
newInput.setAttribute('selectedValue', window.volt_country_party)
for (const country_data of _DATA_.countries) {
const disabled = country_data.disabled
const value = (country_data.value ? country_data.value : '')
let title = ''
if (country_data.title) {
title = country_data.title
} else if (country_data.en && country_data.local_name && country_data.en != country_data.local_name) {
title = country_data.en + ' (' + country_data.local_name + ')'
} else if (country_data.en) {
title = country_data.en
} else if (country_data.local_name) {
title = country_data.local_name
}
const optionEle = document.createElement('option')
optionEle.setAttribute('name', questionKey)
optionEle.setAttribute('value', value)
optionEle.innerHTML = title
if (window.volt_country_party == value) {
optionEle.selected = true
}
if (disabled == true) {
optionEle.disabled = true
}
newInput.appendChild(optionEle)
}
} else {
// question.options = {
// '': {de:'…'},
// ...question.options
// }
question.options = Object.assign({}, { '': { de: '…' } }, question.options)
newInput.setAttribute('selectedValue', '')
// let optionEle = document.createElement('option')
// optionEle.setAttribute('value', '')
// optionEle.innerHTML = '…'
// newInput.appendChild(optionEle)
// var c = 0
for (const key of Object.keys(question.options)) {
let optionEle = document.createElement('option')
optionEle.setAttribute('name', questionKey)
optionEle.setAttribute('value', key)
optionEle.innerHTML = getByLanguage(question.options[key])
// if (c == 1) {
// optionEle.selected = true
// }
newInput.appendChild(optionEle)
// c += 1
}
}
newInput.addEventListener('change', e => {
const optionEles = e.target.querySelectorAll('option')
for (let optionEle of optionEles) {
if (optionEle.selected) {
newInput.setAttribute('selectedValue', optionEle.value)
break
}
}
})
if (question.multiSelect) {
newInput.setAttribute('multiple', 'multiple')
question.showAllOptions = true
}
if (question.showAllOptions) {
newInput.classList.add('showingAllOptions')
newInput.setAttribute('size', Object.keys(question.options).length)
} else {
newInput.classList.add('not_showingAllOptions')
}
questionEle.appendChild(newInput)
} else if (question.type == 'one_line_text') {
let newInput = document.createElement('input')
newInput.addEventListener('click', resetBodyClasses)
newInput.setAttribute('name', questionKey)
newInput.setAttribute('type', 'text')
newInput.setAttribute('placeholder', '…')
// newInput.value = 'some text'
questionEle.appendChild(newInput)
if (questionKey == 'metadata_city') {
newInput.value = window.volt_city
}
} else {
let newInput = document.createElement('textarea')
newInput.addEventListener('click', resetBodyClasses)
newInput.setAttribute('name', questionKey)
// newInput.setAttribute('type', 'text')
newInput.setAttribute('placeholder', '…')
// newInput.innerHTML = 'some text'
questionEle.appendChild(newInput)
}
sectionEle.appendChild(questionEle)
}
inputs.appendChild(sectionEle)
}
}
function getMetadata(country, city) {
// const now = new Date()
let metadata = {
// year and quater is calculated on the server
// year: now.getFullYear(),
// quater: Math.floor((now.getMonth() + 3) / 3),
isFirstCompletion: !!metadataIsFirstCompletion.checked,
// country: metadataCountries.getAttribute('selectedValue'),
// city: metadataCity.value,
country: country,
city: city,
}
return metadata
}
function getTimelessButAnonymousTrackingCode(questionKey, answerKey) {
// tatc = Timeless but Anonymous Tracking Code
const shaObj = new jsSHA('SHA3-512', 'TEXT')
shaObj.update(window.pre_tatc + '' + questionKey) // +' '+answerKey
return shaObj.getHash('HEX')
}
function submitForm() {
if (navigator.onLine) {
var answers = []
const elements = [
...inputs.querySelectorAll('input[type="text"]'),
...inputs.querySelectorAll('textarea'),
...inputs.querySelectorAll('input[type="number"]'),
]
for (const ele of elements) {
const questionKey = ele.getAttribute('name')
const value = ele.value
if (value != '' && questionKey != '') {
answers.push({
questionKey: questionKey,
answerKey: null,
value: value,
// metadata: metadata,
// tatc: getTimelessButAnonymousTrackingCode(questionKey, null),
})
}
}
for (const ele of inputs.querySelectorAll('option')) {
const questionKey = ele.getAttribute('name')
const answerKey = ele.value
if (ele.selected && questionKey != '' && answerKey != '') {
answers.push({
questionKey: questionKey,
answerKey: answerKey,
value: true, // (ele.selected ? true : false),
// metadata: metadata,
// tatc: getTimelessButAnonymousTrackingCode(questionKey, null),
})
}
}
for (const ele of inputs.querySelectorAll('input[type="checkbox"]')) {
const questionKey = ele.getAttribute('name')
const answerKey = ele.value
if (ele.checked && questionKey != '' && answerKey != '') {
answers.push({
questionKey: questionKey,
answerKey: answerKey,
value: true, // (ele.checked ? true : false),
// metadata: metadata,
// tatc: getTimelessButAnonymousTrackingCode(questionKey, answerKey),
})
}
}
for (const ele of inputs.querySelectorAll('input[type="radio"]')) {
const questionKey = ele.getAttribute('name')
const answerKey = ele.value
if (ele.checked && questionKey != '' && answerKey != '') {
answers.push({
questionKey: questionKey,
answerKey: answerKey,
value: true, // (ele.checked ? true : false),
// metadata: metadata,
// tatc: getTimelessButAnonymousTrackingCode(questionKey, answerKey),
})
}
}
if (answers.length > 0) {
body.classList.add('saving')
// add the metadata
const metadata_country_value = answers.filter(a => a.questionKey == 'metadata_country').map(a => a.answerKey)[0] || ''
const metadata_city_value = answers.filter(a => a.questionKey == 'metadata_city').map(a => a.value)[0] || ''
const metadata = getMetadata(metadata_country_value, metadata_city_value)
answers = answers.filter(answer => answer.questionKey != 'metadata_country' && answer.questionKey != 'metadata_city')
// answers = answers.map(answer=> ({
// ...answer,
// metadata: metadata,
// tatc: getTimelessButAnonymousTrackingCode(answer.questionKey, answer.answerKey),
// }) )
answers = answers.map(answer => Object.assign({}, answer, {
metadata: metadata,
tatc: getTimelessButAnonymousTrackingCode(answer.questionKey, answer.answerKey),
}))
console.log('answers:', answers)
let startTS = new Date() * 1
console.info('START', 0)
async.each(answers, (answer, callback) => {
const xmlReq = new XMLHttpRequest()
xmlReq.addEventListener('load', event => {
const json_res = JSON.parse(event.target.responseText)
if (!!json_res && !json_res.error) {
callback()
} else {
callback('error')
}
})
xmlReq.open('GET', CloudFunctionsPrefix + '?data=' + encodeURIComponent(JSON.stringify({ answers: [answer] })))
xmlReq.send()
}, error => {
console.info('END', (new Date() * 1) - startTS)
if (!!error) {
body.classList.add('error')
body.classList.remove('saving')
} else {
body.classList.add('success')
body.classList.remove('saving')
clearForm()
}
})
}
}
}
function clearForm() {
for (const ele of inputs.querySelectorAll('input[type="text"]')) {
ele.value = ''
}
for (const ele of inputs.querySelectorAll('input[type="number"]')) {
ele.value = ''
}
for (const ele of inputs.querySelectorAll('input[type="checkbox"]')) {
ele.checked = false
}
for (const ele of inputs.querySelectorAll('option')) {
ele.selected = false
}
}
function getIdentifier() {
// a got a few things from https://github.com/Valve/fingerprintjs2/blob/master/fingerprint2.js
// var getDoNotTrack = function() {
// if (navigator.doNotTrack) {
// return navigator.doNotTrack
// } else if (navigator.msDoNotTrack) {
// return navigator.msDoNotTrack
// } else if (window.doNotTrack) {
// return window.doNotTrack
// } else {
// return false
// }
// }
var getTouchSupport = function () {
var maxTouchPoints = 0
var touchEvent;
if (typeof navigator.maxTouchPoints !== 'undefined') {
maxTouchPoints = navigator.maxTouchPoints
} else if (typeof navigator.msMaxTouchPoints !== 'undefined') {
maxTouchPoints = navigator.msMaxTouchPoints
}
try {
document.createEvent('TouchEvent')
touchEvent = true
} catch (_) {
touchEvent = false
}
var touchStart = 'ontouchstart' in window
return [maxTouchPoints, touchEvent, touchStart]
}
var timezone = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
return new window.Intl.DateTimeFormat().resolvedOptions().timeZone
}
return false
}
var relativly_unique_information = {
browserPlatform: navigator.platform,
javaEnabled: navigator.javaEnabled(),
dataCookiesEnabled: navigator.cookieEnabled,
sizeScreenAvailWidth: screen.availWidth,
sizeScreenAvailHeight: screen.availHeight,
sizeScreenWidth: screen.width,
sizeScreenHeight: screen.height,
scrColorDepth: screen.colorDepth,
scrPixelDepth: screen.pixelDepth,
hardwareConcurrency: navigator.hardwareConcurrency,
// doNotTrack: getDoNotTrack(),
isTouchCapable: getTouchSupport().join('|'),
// isTouchCapable: 'ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
timezoneOffset: new Date().getTimezoneOffset(),
timezone: timezone(),
browserLanguage: (navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage),
languages: [...(navigator.languages || [])].sort().join('|'),
}
return Object.values(relativly_unique_information).join('|')
}
function parseQuery(queryString) {
var query = {}
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&')
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=')
if (pair[0] != '') {
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '')
}
}
return query
}
function obj2searchQuery(obj) {
var query_parts = []
for (const key of Object.keys(obj)) {
query_parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
}
return '?' + query_parts.join('&')
}
function setMetadata() {
// const now = new Date()
// metaDataCurrentYear.innerHTML = now.getFullYear()
// metadataCurrentRangeInYear.innerHTML = getByLanguage(_DATA_.months[now.getMonth()+1]) // quater = Math.floor((now.getMonth() + 3) / 3)
const locationSearchObj = parseQuery(window.location.search)
window.volt_country_party = (!!locationSearchObj.country ? locationSearchObj.country.toUpperCase() : 'DEU')
window.volt_city = (!!locationSearchObj.city ? locationSearchObj.city : '')
window.pre_tatc = (!!locationSearchObj.tatc ? locationSearchObj.tatc : getIdentifier()) // tatc from get OR a relatively good unique idetifier
// tatc = Timeless but Anonymous Tracking Code
}
function updateLanguageTexts() {
const translationNodes = document.querySelectorAll('[data-translation-key]')
for (let translationNode of translationNodes) {
const translationKey = translationNode.getAttribute('data-translation-key')
if (_DATA_.translation_texts[translationKey]) {
let text = ''
if (_DATA_.translation_texts[translationKey].use_instead && _DATA_.translation_texts[_DATA_.translation_texts[translationKey].use_instead]) {
text = getByLanguage(_DATA_.translation_texts[_DATA_.translation_texts[translationKey].use_instead])
} else {
text = getByLanguage(_DATA_.translation_texts[translationKey])
}
if (_DATA_.translation_texts[translationKey].prefix) {
text = _DATA_.translation_texts[translationKey].prefix + text
}
let translationDestination = ''
if (translationNode.hasAttribute('data-translation-dest')) {
translationDestination = translationNode.getAttribute('data-translation-dest')
}
if (translationDestination == 'attr-content') {
const temp_node = document.createElement("div")
temp_node.innerHTML = text
translationNode.setAttribute('content', temp_node.textContent || temp_node.innerText || "")
delete temp_node
} else {
text = text.replace(/\t+/g, '')
text = text.replace(/(^\n+|\n+$)/g, '')
if (!_DATA_.translation_texts[translationKey].is_plain) {
text = text.replace(/\n/g, '<br>')
}
translationNode.innerHTML = text
}
}
}
}
const body = document.getElementsByTagName('body')[0]
const websiteTitle = document.getElementById('websiteTitle')
const languageChooserSelect = document.getElementById('languageChooserSelect')
const metadataIsFirstCompletion = document.getElementById('metadataIsFirstCompletion')
const inputs = document.getElementById('inputs')
const submitButton = document.getElementById('submitButton')
const buttons_to_page = document.querySelectorAll('button[to-page]')
for (const button of buttons_to_page) {
button.addEventListener('click', () => {
const toPage = button.getAttribute('to-page')
if (!['', 'intro', 'privacy', 'metadata', 'questions'].includes(toPage)) {
toPage = ''
}
body.setAttribute('show', toPage)
window.scrollTo(0, 0)
history.pushState(null, websiteTitle.innerText, '#' + toPage)
})
}
languageChooserSelect.addEventListener('change', e => {
const optionEles = e.target.querySelectorAll('option')
for (let optionEle of optionEles) {
if (optionEle.selected) {
window._language_ = optionEle.value
updateLanguageTexts()
var locationSearchObj = parseQuery(window.location.search)
locationSearchObj.lang = optionEle.value
history.pushState(null, websiteTitle.innerText, window.location.protocol + '//' + window.location.host + window.location.pathname + obj2searchQuery(locationSearchObj) + window.location.hash)
break
}
}
})
function selectLanguageInSelector() {
const html_node = languageChooserSelect.querySelector('html')
html_node.setAttribute('lang', window._language_)
const optionEles = languageChooserSelect.querySelectorAll('option')
for (let optionEle of optionEles) {
if (optionEle.value === window._language_) {
optionEle.selected = true
} else {
optionEle.selected = false
}
}
}
function checkUrl() {
// check hash
let hash = document.location.hash
if (hash.charAt(0) === '#') {
hash = hash.slice(1)
}
body.setAttribute('show', hash)
// check search
const locationSearchObj = parseQuery(window.location.search)
// set language
window._language_ = locationSearchObj.lang || 'en'
selectLanguageInSelector()
updateLanguageTexts()
}
window.addEventListener('popstate', checkUrl)
submitButton.addEventListener('click', () => {
submitForm()
})
function _DATA_GOT_LOADED() {
if (typeof _DATA_ !== 'undefined') {
checkUrl()
setMetadata()
generateForm(_DATA_)
}
}
_DATA_GOT_LOADED()