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}

${t('now')}
${client.profile.products.map(prod => html` `)}
${journeysHistory.length ? html`
` : nothing}
${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; } };