rowt/frontend/main.ts
2023-11-04 20:16:20 +01:00

650 lines
21 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Choices from "choices.js";
import { Sidebar } from './js/sidebar';
import './scss/app.scss'
export interface choiceMap {
[details: string]: Choices;
}
let choiceObjects: choiceMap = {};
document.addEventListener('DOMContentLoaded', function() {
changeTheme();
initcolorTheme();
initSearch();
initSidebar();
initToggle();
replaceStrings();
initChoices();
initBoatActions();
selectBoatChange();
addRelationMagic(<HTMLElement>document.querySelector('body'));
reloadPage();
setCurrentdate(<HTMLInputElement>document.querySelector('#departure'));
});
function changeTheme() {
let toggleBtn = <HTMLElement>document.querySelector('#theme-toggle-js');
if(toggleBtn) {
toggleBtn.addEventListener('click', function() {
if(toggleBtn.dataset.theme === 'light') {
setTheme('dark', true);
} else {
setTheme('light', true);
}
});
}
}
/***
* init javascript
* 1) detect native color scheme or use set theme in local storage
* 2) detect view (desktop or responsive) if on mobile device with touch screen
* 3) set base font size to 112.5% -> 18px
*/
function initcolorTheme() {
colorThemeWatcher();
let theme = localStorage.getItem('theme');
if (theme == null || theme === 'auto') {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
setTheme('dark', false);
} else {
setTheme('light', false);
}
} else {
setTheme(theme)
}
}
/***
* Listener operating system native color configuration
*/
function colorThemeWatcher() {
try {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
setTheme(e.matches ? 'dark' : 'light');
});
} catch {
console.warn('color theme watcher not supported');
}
}
/**
* Define color scheme, colors without losing the base font size configuration
* and add data-theme to html tag
* @param theme
*/
function setTheme(theme: string, setLocalStorage = true) {
let toggleBtn = document.querySelector('#theme-toggle-js');
if(toggleBtn) {
if (setLocalStorage) {
localStorage.setItem('theme', theme);
}
toggleBtn.setAttribute('data-theme', theme);
if (document.documentElement.classList.contains('dark') && theme === 'light') {
document.documentElement.classList.remove('dark');
} else if(theme === 'dark'){
document.documentElement.classList.add('dark');
}
}
}
function setCurrentdate(input: HTMLInputElement) {
if(input) {
const now = new Date();
const formattedDateTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}T${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
input.value = formattedDateTime;
}
}
interface ChoiceBoatEvent extends Event{
detail: {
value: string;
label: string,
customProperties: {
amount_seats: number,
owner: number,
}
};
}
function selectBoatChange() {
const boatSelect = <HTMLSelectElement>document.querySelector('#boat_id');
if(boatSelect) {
const boatChoice = new Choices(boatSelect, {
loadingText: 'Wird geladen...',
noResultsText: 'Keine Ergebnisse gefunden',
noChoicesText: 'Keine Ergebnisse gefunden',
itemSelectText: 'Zum Auswählen klicken',
} as any);
boatSelect.addEventListener('addItem', function(e) {
const event = e as ChoiceBoatEvent;
const amount_seats = event.detail.customProperties.amount_seats;
setMaxAmountRowers("newrower", amount_seats);
if (event.detail.customProperties.owner){
choiceObjects["newrower"].setChoiceByValue(event.detail.customProperties.owner+"");
}
const inputElement = document.getElementById("departure") as HTMLInputElement;
const now = new Date();
const formattedDateTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}T${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
inputElement.value = formattedDateTime;
},false);
choiceObjects[boatSelect.id] = boatChoice;
choiceObjects["boat_id"] = boatChoice;
}
}
function reloadPage() {
if (!window.location.href.includes("ergo")){
let pageTitle = document.title;
let attentionMessage = 'Riemen- und Dollenbruch';
document.addEventListener('visibilitychange', function() {
let isPageActive = !document.hidden;
if(!isPageActive){
document.title = attentionMessage;
} else {
document.title = pageTitle;
location.reload();
}
});
}
}
function setMaxAmountRowers(name: string, rowers: number) {
if(choiceObjects[name]) {
choiceObjects[name].removeActiveItems(-1);
//let curSelection = choiceObjects[name].getValue(true);
//let amount_to_delete = (<any>curSelection).length - rowers;
//if (amount_to_delete > 0){
// let to_delete = (<any>curSelection).slice(-amount_to_delete);
// for (let del of to_delete) {
// choiceObjects[name].removeActiveItemsByValue(del);
// }
//}
let input = <HTMLElement>document.querySelector('#'+name);
if(input) {
choiceObjects[name].config.maxItemCount = rowers;
if (rowers === 0) {
choiceObjects[name].disable()
input.parentElement?.parentElement?.parentElement?.classList.add('hidden');
input.parentElement?.parentElement?.parentElement?.classList.add('md:block');
input.parentElement?.parentElement?.parentElement?.classList.add('opacity-50');
} else{
choiceObjects[name].enable();
input.parentElement?.parentElement?.parentElement?.classList.remove('hidden');
input.parentElement?.parentElement?.parentElement?.classList.remove('md:block');
input.parentElement?.parentElement?.parentElement?.classList.remove('opacity-50');
}
}
//let only_steering = <HTMLSelectElement>document.querySelector('#shipmaster_only_steering');
//if(only_steering) {
// if(isShipmasterSteering == 'true') {
// only_steering.removeAttribute('disabled');
// only_steering.setAttribute('checked', 'true');
// only_steering.parentElement?.parentElement?.parentElement?.classList.remove('hidden');
// only_steering.parentElement?.parentElement?.parentElement?.classList.remove('md:block');
// only_steering.parentElement?.parentElement?.parentElement?.classList.remove('opacity-50');
// } else {
// only_steering.setAttribute('disabled', 'disabled');
// only_steering.removeAttribute('checked');
// only_steering.parentElement?.parentElement?.parentElement?.classList.add('hidden');
// only_steering.parentElement?.parentElement?.parentElement?.classList.add('md:block');
// only_steering.parentElement?.parentElement?.parentElement?.classList.add('opacity-50');
// }
//}
let shipmaster = <HTMLElement>document.querySelector('#shipmaster-'+name+'js');
let steering_person = <HTMLElement>document.querySelector('#steering_person-'+name+'js');
if (rowers == 1){
if (shipmaster.parentNode) {
(<HTMLElement>shipmaster.parentNode).classList.add('hidden');
}
shipmaster.removeAttribute('required');
if (steering_person.parentNode){
(<HTMLElement>steering_person.parentNode).classList.add('hidden');
}
steering_person.removeAttribute('required');
}else{
if (shipmaster.parentNode){
(<HTMLElement>shipmaster.parentNode).classList.remove('hidden');
}
shipmaster.setAttribute('required', 'required');
if (steering_person.parentNode){
(<HTMLElement>steering_person.parentNode).classList.remove('hidden');
}
steering_person.setAttribute('required', 'required');
}
}
}
function initBoatActions() {
const boatSelects = document.querySelectorAll('.boats-js[data-onclick="true"]');
if(boatSelects) {
Array.prototype.forEach.call(boatSelects, (select: HTMLInputElement) => {
select.addEventListener('click', function() {
if(select.dataset.seats) {
if (select.dataset.id) {
choiceObjects['boat_id'].setChoiceByValue(select.dataset.id);
}
window.scrollTo(0, 0);
}
});
});
}
}
function initChoices() {
const selects = document.querySelectorAll('select[data-init="true"]');
if(selects) {
Array.prototype.forEach.call(selects, (select: HTMLInputElement) => {
initNewChoice(select);
});
}
}
interface ChoiceEvent extends Event{
detail: {
value: string;
label: string,
customProperties: {
is_cox: boolean,
steers: boolean,
cox_on_boat: boolean,
}
};
}
function initNewChoice(select: HTMLInputElement) {
let seats = 0;
if (select.dataset && select.dataset.seats) {
seats = +select.dataset.seats;
}
let shipmaster = <HTMLElement>document.querySelector('#shipmaster-'+select.id+'js');
let steering_person = <HTMLElement>document.querySelector('#steering_person-'+select.id+'js');
if (seats == 1){
if (shipmaster.parentNode) {
(<HTMLElement>shipmaster.parentNode).classList.add('hidden');
}
shipmaster.removeAttribute('required');
if (steering_person.parentNode){
(<HTMLElement>steering_person.parentNode).classList.add('hidden');
}
steering_person.removeAttribute('required');
}else{
if (shipmaster.parentNode){
(<HTMLElement>shipmaster.parentNode).classList.remove('hidden');
}
shipmaster.setAttribute('required', 'required');
if (steering_person.parentNode){
(<HTMLElement>steering_person.parentNode).classList.remove('hidden');
}
steering_person.setAttribute('required', 'required');
}
const choice = new Choices(select, {
removeItemButton: true,
loadingText: 'Wird geladen...',
noResultsText: 'Keine Ergebnisse gefunden',
noChoicesText: 'Keine Ergebnisse gefunden',
itemSelectText: 'Zum Auswählen klicken',
placeholderValue: 'Ruderer auswählen',
maxItemCount: seats,
maxItemText: (maxItemCount) => {
return `Nur ${maxItemCount} Ruderer können hinzugefügt werden`;
},
callbackOnInit: function() {
this._currentState.items.forEach(function(obj){
if (obj.customProperties && obj.customProperties.is_cox){
const coxSelect = <HTMLSelectElement>document.querySelector('#shipmaster-' + select.id + 'js');
var new_option = new Option(obj.label, obj.value);
if (obj.customProperties.cox_on_boat){
new_option.selected = true;
}
coxSelect.add(new_option);
}
const steeringSelect = <HTMLSelectElement>document.querySelector('#steering_person-' + select.id + 'js');
if (steeringSelect) {
var new_option = new Option(obj.label, obj.value);
if (obj.customProperties && obj.customProperties.steers){
new_option.selected = true;
}
steeringSelect.add(new_option);
}
});
}
});
choiceObjects[select.id] = choice;
select.addEventListener('addItem', function(e) {
const event = e as ChoiceEvent;
const user_id = event.detail.value;
const name = event.detail.label;
if (event.detail.customProperties.is_cox) {
const coxSelect = <HTMLSelectElement>document.querySelector('#shipmaster-' + select.id + 'js');
if (coxSelect){
coxSelect.add(new Option(name, user_id));
}
}
const steeringSelect = <HTMLSelectElement>document.querySelector('#steering_person-' + select.id + 'js');
if (steeringSelect) {
steeringSelect.add(new Option(name, user_id));
}
},false);
select.addEventListener('removeItem', function(e) {
const event = e as ChoiceEvent;
const user_id = event.detail.value;
const coxSelect = <HTMLSelectElement>document.querySelector('#shipmaster-' + select.id + 'js');
if (coxSelect) {
for (var i=0; i<coxSelect.length; i++) {
if (coxSelect.options[i].value == user_id)
coxSelect.remove(i);
}
}
const steeringSelect = <HTMLSelectElement>document.querySelector('#steering_person-' + select.id + 'js');
if (steeringSelect) {
for (var i=0; i<steeringSelect.length; i++) {
if (steeringSelect.options[i].value == user_id)
steeringSelect.remove(i);
}
}
},false);
choiceObjects[select.id] = choice;
}
function initToggle() {
// get filter btns & set object sessionStorage
const btns = <NodeListOf<HTMLButtonElement>>document.querySelectorAll('.filter-trips-js');
let filterObject = new Map();
if(btns) {
Array.prototype.forEach.call(btns, (btn: HTMLButtonElement) => {
filterObject.set(btn.dataset.action, btn.ariaPressed);
btn.addEventListener('click', () => {
let filter = sessionStorage.getItem('tripsFilter');
if(filter) {
let filterMap = new Map(JSON.parse(filter));
for (let entry of filterMap.entries()) {
if(entry[0] === btn.dataset.action && entry[1] !== 'true') {
filterMap.set(entry[0],'true');
} else {
filterMap.set(entry[0],'false');
}
}
sessionStorage.setItem('tripsFilter', JSON.stringify( Array.from(filterMap.entries())));
}
resetFilteredElements();
if(btn.getAttribute('aria-pressed') === 'false'){
Array.prototype.forEach.call(btns, (b: HTMLButtonElement) => {
b.setAttribute('aria-pressed', 'false');
});
triggerFilterAction(btn.dataset.action);
} else {
btn.setAttribute('aria-pressed', 'false');
}
});
});
}
let filter = sessionStorage.getItem('tripsFilter');
if(filter) {
let filterMap = new Map(JSON.parse(filter));
for (let entry of filterMap.entries()) {
if(entry[1] === 'true') {
triggerFilterAction(entry[0]);
}
}
} else {
sessionStorage.setItem('tripsFilter', JSON.stringify( Array.from(filterObject.entries())));
}
}
function resetFilteredElements() {
const hiddenElements = document.querySelectorAll('.reset-js.hidden');
if(hiddenElements) {
Array.prototype.forEach.call(hiddenElements, (hiddenElement: HTMLButtonElement) => {
hiddenElement.classList.remove('hidden');
});
}
}
function triggerFilterAction(activeFilter: any) {
const activeBtn = document.querySelector('button[data-action="' + activeFilter + '"]');
if(activeBtn) {
activeBtn.setAttribute('aria-pressed', 'true');
filterAction(activeFilter);
}
}
function filterAction(activeFilter: string) {
switch(activeFilter) {
case 'filter-days': {
filterDays();
break;
}
case 'filter-coxs': {
filterCoxs();
break;
}
case 'filter-months': {
filterMonths(activeFilter)
break;
}
}
}
function filterDays() {
const daysNoTrips = document.querySelectorAll('div[data-trips="0"]');
Array.prototype.forEach.call(daysNoTrips, (day: HTMLElement) => {
day.classList.toggle('hidden');
});
}
function filterCoxs() {
const noCoxNeeded = document.querySelectorAll('div[data-coxneeded="false"]');
Array.prototype.forEach.call(noCoxNeeded, (notNeeded: HTMLElement) => {
notNeeded.classList.toggle('hidden');
});
}
function filterMonths(action: string) {
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
const monthToggle = <HTMLButtonElement>document.querySelector('button[data-action="' + action + '"]');
if(monthToggle) {
const currentMonth = monthToggle.dataset.month;
if(currentMonth) {
const index = months.indexOf(currentMonth);
if (index > -1) { // only splice array when item is found
months.splice(index, 1); // 2nd parameter means remove one item only
}
Array.prototype.forEach.call(months, (month: HTMLElement) => {
const notThisMonth = document.querySelectorAll('div[data-month="' + month + '"]');
Array.prototype.forEach.call(notThisMonth, (notThisMonth: HTMLElement) => {
notThisMonth.classList.toggle('hidden');
});
});
}
}
}
function initSearch() {
const input = <HTMLInputElement>document.querySelector('#filter-js');
if(input) {
filterElements(input.value);
input.addEventListener('input', () => {
filterElements(input.value);
});
}
}
function filterElements(input: string) {
const elements = document.querySelectorAll('div[data-filterable="true"]');
let resultWrapper = <HTMLElement>document.querySelector('#filter-result-js'),
amountShownElements = 0;
Array.prototype.forEach.call(elements, (element: HTMLElement) => {
// set both strings (input & dataset filter) to lowercase to not be case sensitive
let filterString = element.dataset.filter?.toLocaleLowerCase();
// bulk hide all elements
element.style.display = 'none';
// show if input matches
if(filterString?.includes(input.toLocaleLowerCase())) {
element.style.display = 'flex';
amountShownElements ++;
}
});
if(resultWrapper) {
resultWrapper.innerHTML = (amountShownElements === 0 ? 'Kein Ergebnis gefunden' : '<strong>' + amountShownElements + '</strong>' + (amountShownElements > 1 ? ' Ergebnisse' : ' Ergebnis') + ' gefunden');
}
}
function initSidebar() {
const sidebarTrigger = <NodeListOf<HTMLElement>>document.querySelectorAll('[data-trigger]');
if(sidebarTrigger) {
Array.prototype.forEach.call(sidebarTrigger, (triggerElement: HTMLElement) => {
if(triggerElement.dataset.trigger) {
const sidebar = new Sidebar(triggerElement.dataset.trigger);
triggerElement.addEventListener('click', (e) => {
e.preventDefault();
if(triggerElement.dataset.trigger === 'sidebar') {
initTripSidebar(triggerElement);
}
sidebar.toggle();
});
}
});
}
}
function initTripSidebar(triggerElement: HTMLElement) {
const sidebarElement = <HTMLElement>document.querySelector('#sidebar');
if(sidebarElement && triggerElement.dataset.body && triggerElement.dataset.header) {
let body = <HTMLElement>document.querySelector(triggerElement.dataset.body);
let bodyElement = <HTMLElement>body.cloneNode(true);
let bodyContainerElement = <HTMLElement>sidebarElement.querySelector('.body-js');
/* Quickfix duplicate ids checkboxes */
const checkboxes = <NodeListOf<HTMLElement>>bodyElement.querySelectorAll('input[type="checkbox"]');
Array.prototype.forEach.call(checkboxes, (checkbox: HTMLElement) => {
if(checkbox) {
checkbox.parentElement?.setAttribute('for', checkbox.id + 'js');
checkbox.id += 'js';
}
});
const prefixedContent = <NodeListOf<HTMLElement>>bodyElement.querySelectorAll('.change-id-js');
Array.prototype.forEach.call(prefixedContent, (content: HTMLElement) => {
if(content) {
content.id += 'js';
if(content.dataset.relation){
content.dataset.relation += 'js';
}
}
});
if(bodyContainerElement) {
bodyContainerElement.innerHTML = '';
bodyContainerElement.append(bodyElement);
addRelationMagic(bodyElement);
}
if(triggerElement.dataset.day) {
let hiddenElement = <HTMLInputElement>bodyElement.querySelector('.day-js');
if(hiddenElement) {
hiddenElement.value = triggerElement.dataset.day;
}
}
let headerElement = sidebarElement.querySelector('.header-js');
if(headerElement) {
headerElement.innerHTML = triggerElement.dataset.header;
}
const selects = bodyElement.querySelectorAll('select[multiple]');
if(selects) {
Array.prototype.forEach.call(selects, (select: HTMLInputElement) => {
initNewChoice(select);
});
}
}
const defaultDateTimes = <NodeListOf<HTMLInputElement>>document.querySelectorAll('.current-date-time');
defaultDateTimes.forEach((defaultDateTime) => {
setCurrentdate(defaultDateTime);
});
}
function addRelationMagic(bodyElement: HTMLElement) {
const fields = bodyElement.querySelectorAll('.set-distance-js');
if(fields) {
Array.prototype.forEach.call(fields, (field: HTMLInputElement) => {
if(field.dataset.relation){
const relatedField = <HTMLInputElement>bodyElement.querySelector('#' + field.dataset.relation);
if(relatedField) {
field.addEventListener('input', (e) => {
e.preventDefault();
const dataList = <HTMLDataListElement>document.querySelector('#destinations');
if(dataList) {
var option = Array.prototype.find.call(dataList.options, function(option) {
return option.value === field.value;
});
// Get distance
const distance = option.getAttribute('distance');
if(distance) relatedField.value = distance;
}
});
}
}
});
}
}
function replaceStrings() {
const weekdays = document.querySelectorAll('.weekday-js');
Array.prototype.forEach.call(weekdays, (weekday: HTMLElement) => {
weekday.innerHTML = weekday.innerHTML.replace('Freitag', 'Markttag');
});
}