import { html, nothing, render } from 'lit-html';
import { ElementById, hideElement, showElement, elementHidden, flipElement, unflipElement, setThemeColor, queryBackgroundColor, padZeros, isValidDate, loyaltyCardFromString } from './helpers.js';
import { db } from './dataStorage.js';
import { getJourneys, newJourneys, ds100Reverse } from './app_functions.js';
import { formatName, formatFromTo } from './formatters.js';
import { modifySettings, settings } from './settings.js';
import { go } from './router.js';
import { showAlertModal, showSelectModal, showLoader, hideOverlay} from './overlays.js';
import { footerTemplate } from './templates.js';
import { t } from './languages.js';
import { showSettings } from './settingsView.js';
import { client } from './hafasClient.js';
const viewState = {
currDate: new Date(),
numEnter: 0,
noTransfers: false,
isArrival: false,
dateValue: '',
timeValue: '',
dateTimeValue: '',
fromValue: '',
viaValue: '',
toValue: '',
suggestions: {
from: {},
via: {},
to: {},
},
};
viewState.dateValue = `${viewState.currDate.getFullYear()}-${padZeros(viewState.currDate.getMonth()+1)}-${padZeros(viewState.currDate.getDate())}`;
viewState.timeValue = `${padZeros(viewState.currDate.getHours())}:${padZeros(viewState.currDate.getMinutes())}`;
viewState.dateTimeValue = `${viewState.dateValue}T${viewState.timeValue}`;
const productIcons = {
// DB
"nationalExpress": "icon-ice",
"national": "icon-ic",
"regionalExpress": "icon-dzug",
// vbb, bvg
"express": "icon-icice",
// nahsh
"interregional": "icon-dzug",
"onCall": "icon-taxi",
// SNCB
"intercity-p": "icon-ic",
"s-train": "icon-suburban",
// RMV
"express-train": "icon-ice",
"long-distance-train": "icon-ic",
"regiona-train": "icon-regional",
"s-bahn": "icon-suburban",
"u-bahn": "icon-subway",
"watercraft": "icon-ferry",
"ast": "icon-taxi",
// VRN
"regional-train": "icon-regional",
"urban-train": "icon-suburban",
"dial-a-ride": "icon-taxi",
// "long-distance-train": "icon-icice",
};
const iconFor = id => {
return productIcons[id] || `icon-${id}`;
};
const searchTemplate = (journeysHistory) => html`
${APPNAME}
${journeysHistory.map(element => html`
{journeysHistoryAction(journeysHistory, element);}}">
${t('from')}:
${formatName(element.fromPoint)}
${element.viaPoint ? html`
${t('via')} ${formatName(element.viaPoint)}
` : nothing}
${t('to')}:
${formatName(element.toPoint)}
`)}
${footerTemplate}
`;
const journeysHistoryAction = (journeysHistory, element) => {
const options = [
{'label': t('setfromto'), 'action': () => { setFromHistory(element.key); hideOverlay(); }},
{'label': t('journeyoverview'), 'action': () => go(`/${element.slug}/${settings.journeysViewMode}`)}
];
if (element.lastSelectedJourneyId !== undefined)
options.push({'label': t('lastSelectedJourney'), 'action': () => go(`/j/${settings.profile}/${element.lastSelectedJourneyId}`)})
showSelectModal(options);
};
export const searchView = async (clearInputs) => {
const journeysHistory = (await db.getHistory(settings.profile)).slice().reverse();
render(searchTemplate(journeysHistory), ElementById('content'));
setThemeColor();
if (clearInputs === true) {
viewState.fromValue = '';
viewState.viaValue = '';
viewState.toValue = '';
viewState.suggestions.from = {};
viewState.suggestions.via = {};
viewState.suggestions.to = {};
ElementById('from').value = '';
ElementById('via').value = '';
ElementById('to').value = '';
ElementById('fromSuggestions').textContent = '';
ElementById('viaSuggestions').textContent = '';
ElementById('toSuggestions').textContent = '';
}
for (const [product, enabled] of Object.entries(settings.products)) {
const element = ElementById(product);
if (!element) continue;
element.checked = enabled;
}
ElementById('from').focus();
};
const submitHandler = async (event) => {
event.preventDefault();
await modifySettings(settings => {
settings.products = readProductSelection(settings);
settings.accessibility = document.querySelector('input[name="accessibility"]:checked').value;
settings.bikeFriendly = ElementById('bikeFriendly').checked
return settings;
});
viewState.fromValue = ElementById('from').value.trim();
viewState.viaValue = ElementById('via').value.trim();
viewState.toValue = ElementById('to').value.trim();
viewState.dateValue = ElementById('date').value.trim();
viewState.timeValue = ElementById('time').value.trim();
viewState.dateTimeValue = ElementById('datetime').value.trim();
viewState.isArrival = ElementById('arrival').checked;
viewState.noTransfers = ElementById('noTransfers').checked;
// check if From or To empty
if (viewState.fromValue === '' || viewState.toValue === '') {
showAlertModal('At least From and To need to be filled!');
return false;
}
// date and time
if (!settings.combineDateTime) {
viewState.dateTimeValue = `${viewState.dateValue}T${viewState.timeValue}`;
} else {
const splitedDateTimeValue = viewState.dateTimeValue.split('T');
viewState.dateValue = splitedDateTimeValue[0];
viewState.timeValue = splitedDateTimeValue[1];
};
if (viewState.dateValue !== '') {
if (!isValidDate(viewState.dateValue)) {
showAlertModal('Invalid date');
return false;
}
} else {
viewState.dateValue = `${viewState.currDate.getFullYear()}-${padZeros(viewState.currDate.getMonth()+1)}-${padZeros(viewState.currDate.getDate())}`;
}
if (viewState['timeValue'] !== '') {
if (!new RegExp('([0-1][0-9]|2[0-3]):([0-5][0-9])').test(viewState.timeValue)) {
showAlertModal('Invalid time');
return false;
}
} else {
viewState['timeValue'] = `${padZeros(viewState.currDate.getHours())}:${padZeros(viewState.currDate.getMinutes())}`;
}
const splitedDate = viewState.dateValue.split('-');
const splitedTime = viewState.timeValue.split(':');
const timestamp = Math.round(new Date(splitedDate[0], splitedDate[1]-1, splitedDate[2], splitedTime[0], splitedTime[1]).getTime()/1000);
// from
let from = '';
if (Object.entries(viewState.suggestions.from).length !== 0) {
from = viewState.suggestions.from;
} else {
const data = await client.locations(viewState.fromValue, {'results': 1});
if (!data[0]) {
showAlertModal('Invalid From');
return false;
}
from = data[0];
}
// via
let via = '';
if (viewState.viaValue == '') {
via = null;
} else if (Object.entries(viewState.suggestions.via).length !== 0) {
via = viewState.suggestions.via;
} else {
const data = await client.locations(viewState.viaValue, {'results': 1});
if (!data[0]) {
showAlertModal('Invalid Via');
return false;
}
via = data[0];
}
// to
let to = '';
if (Object.entries(viewState.suggestions.to).length !== 0) {
to = viewState.suggestions.to;
} else {
const data = await client.locations(viewState.toValue, {'results': 1});
if (!data[0]) {
showAlertModal('Invalid To');
return false;
}
to = data[0];
}
if (formatName(to) === formatName(from) && via === null) {
showAlertModal('From and To are the same place.');
return false;
};
const params = {
from,
to,
results: 6,
bike: settings.bikeFriendly,
products: settings.products,
};
if (via) params.via = via;
if (viewState.noTransfers) params.transfers = 0;
if (!viewState.isArrival) params.departure = timestamp * 1000;
else params.arrival = timestamp * 1000;
showLoader();
let responseData;
try {
responseData = await newJourneys(params);
} catch(e) {
showAlertModal(e.toString());
throw e;
}
hideOverlay();
go(`/${responseData.slug}/${settings.journeysViewMode}`);
};
const toggleHistory = () => {
const historyElement = ElementById('history');
const buttonElement = ElementById('historyButton');
if (!elementHidden(historyElement)) {
unflipElement(buttonElement);
hideElement(historyElement);
} else {
flipElement(buttonElement);
showElement(historyElement);
}
}
const toggleVia = async mode => {
const rowElement = ElementById('viaRow');
const buttonElement = ElementById('viaButton');
if (mode === 'show' || elementHidden(rowElement)) {
flipElement(buttonElement);
showElement(rowElement);
} else {
unflipElement(buttonElement);
hideElement(rowElement);
ElementById('via').value = '';
}
await modifySettings(settings => {
settings.showVia = !elementHidden(rowElement);
return settings;
});
}
const swapFromTo = () => {
viewState.suggestions.from = [viewState.suggestions.to, viewState.suggestions.to = viewState.suggestions.from][0];
const from = ElementById('from');
const to = ElementById('to');
from.value = [to.value, to.value = from.value][0];
};
const setFromHistory = async id => {
const entry = (await db.getHistoryEntry(id));
if (!entry) return;
setSuggestion(entry.fromPoint, 'from');
if (entry.viaPoint !== undefined) {
setSuggestion(entry.viaPoint, 'via');
toggleVia('show');
};
setSuggestion(entry.toPoint, 'to');
};
const setDateTimeNow = () => {
viewState.currDate = new Date();
viewState.dateValue = `${viewState.currDate.getFullYear()}-${padZeros(viewState.currDate.getMonth()+1)}-${padZeros(viewState.currDate.getDate())}`;
viewState.timeValue = `${padZeros(viewState.currDate.getHours())}:${padZeros(viewState.currDate.getMinutes())}`;
viewState.dateTimeValue = `${viewState.dateValue}T${viewState.timeValue}`;
ElementById('date').value = viewState.dateValue;
ElementById('time').value = viewState.timeValue;
ElementById('datetime').value = viewState.dateTimeValue;
};
const readProductSelection = settings => {
const productsMap = {};
for (const prod of client.profile.products) {
productsMap[prod.id] = ElementById(prod.id).checked;
}
return productsMap;
};
const showSuggestions = (id) => {
viewState.numEnter = 0;
showElement(ElementById(`${id}Suggestions`));
};
const hideSuggestions = (id) => {
const suggestionsElement = ElementById(`${id}Suggestions`);
hideElement(suggestionsElement);
suggestionsElement.classList.remove('mouseover');
};
const suggestionsTemplate = (data, inputId) => data.map((element, index) => html`
setSuggestion(encodeURI(JSON.stringify(element)), inputId, event.pointerType)}>${formatName(element)}
`);
const loadSuggestions = async (event) => {
const elementId = event.target.id;
const elementValue = event.target.value.trim();
viewState.suggestions[elementId] = {};
if (elementValue === '') return;
let results;
const ds100Result = ds100Reverse(elementValue);
if (ds100Result !== null) {
results = await client.locations(ds100Result, {'results': 1});
} else {
results = await client.locations(elementValue, {'results': 10})
}
render(suggestionsTemplate(results, elementId), ElementById(`${elementId}Suggestions`));
};
const focusNextElement = (currentElementId) => {
switch (currentElementId) {
case 'from':
ElementById('to').focus();
if (!elementHidden(ElementById('via'))) {
ElementById('via').focus();
}
break;
case 'via':
ElementById('to').focus();
break;
case 'to':
hideSuggestions(currentElementId);
ElementById('submit').focus();
break;
}
};
const setSuggestion = (data, inputId, pointerType) => {
if (typeof data === 'string') {
data = JSON.parse(decodeURI(data));
}
ElementById(inputId).value = formatName(data);
viewState.suggestions[inputId] = data;
hideSuggestions(inputId);
if (pointerType !== '') focusNextElement(inputId);
};
const mouseOverHandler = (event) => event.target.parentElement.classList.add('mouseover');
const mouseOutHandler = (event) => event.target.parentElement.classList.remove('mouseover');
const focusHandler = (event) => showSuggestions(event.target.id);
const blurHandler = (event) => {
if (!ElementById(`${event.target.id}Suggestions`).classList.contains('mouseover')) hideSuggestions(event.target.id);
};
const keyupHandler = (event) => {
const eventElement = event.target;
if (event.key !== 'Enter') return true;
if (viewState.numEnter === 2 && eventElement.value === formatName(viewState.suggestions[eventElement.id])) {
viewState.numEnter = 0;
focusNextElement(eventElement.id);
}
};
const keydownHandler = (event) => {
const eventElement = event.target;
const firstSuggestionElement = document.querySelector(`#${eventElement.id}Suggestions>p:first-child`);
const lastSuggestionElement = document.querySelector(`#${eventElement.id}Suggestions>p:last-child`);
const selectedElement = ElementById(`${eventElement.id}Selected`);
if (selectedElement === null) return true;
if (event.key === 'Enter') {
event.preventDefault();
selectedElement.click();
viewState.numEnter++;
return true;
};
if (elementHidden(ElementById(`${eventElement.id}Suggestions`))) {
showSuggestions(eventElement.id);
return true;
}
switch (event.key) {
case 'Escape':
case 'Tab':
hideSuggestions(eventElement.id);
break;
case 'ArrowUp':
case 'ArrowDown':
event.preventDefault();
if (event.shiftKey) break;
let nextElement = selectedElement.nextElementSibling ?? firstSuggestionElement;
if (event.key === 'ArrowUp') nextElement = selectedElement.previousElementSibling ?? lastSuggestionElement;
selectedElement.id = '';
nextElement.id = `${eventElement.id}Selected`;
break;
}
};