import { LitElement, html, nothing } from 'lit'; import { classMap } from 'lit/directives/class-map.js'; import { when } from 'lit/directives/when.js'; import { BaseView } from './baseView.js'; import { db } from './dataStorage.js'; import { t } from './translate.js'; import { client } from './hafasClient.js'; import { getIBNRbyDS100 } from './ds100.js'; import { formatPoint } from './formatters.js'; import { newJourneys } from './app_functions.js'; import { CustomDate, sleep, queryBackgroundColor, setThemeColor } from './helpers.js'; import { searchViewStyles } from './styles.js'; class SearchView extends BaseView { static properties = { date: { state: true }, numEnter: { state: true }, noTransfers: { state: true }, isArrival: { state: true }, showHistory: { state: true }, history: { state: true }, location: { state: true }, }; static styles = [ super.styles, searchViewStyles ]; constructor () { super(); this.date = new CustomDate(); this.numEnter = 0; this.noTransfers = false, this.isArrival = false, this.showHistory = false; this.history = []; this.location = { from: { value: '', suggestionsVisible: false, suggestionSelected: null, suggestion: null, suggestions: [], }, to: { value: '', suggestionsVisible: false, suggestionSelected: null, suggestion: null, suggestions: [], }, via: { value: '', suggestionsVisible: false, suggestionSelected: null, suggestion: null, suggestions: [], }, }; } async connectedCallback () { super.connectedCallback(); this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse(); await sleep(200); setThemeColor(queryBackgroundColor(document, 'body')); await sleep(200); this.renderRoot.querySelector('input[name=from]').focus(); } async willUpdate (previous) { if ( previous.has('settingsState') && previous.get('settingsState') !== undefined && previous.get('settingsState').profile !== this.settingsState.profile ) await this.profileChangeHandler(); } updated (previous) { super.updated(previous, 'SearchView'); } renderView = () => { return html`

${APP_NAME}

${['from', 'via', 'to'].map(name => html`
${when( name === 'from', () => html`
` )} ${when( name === 'via', () => html`` )} ${when( name === 'to', () => html`
` )}
${this.location[name].suggestions.map((suggestion, index) => html`

this.setSuggestion(name, index, event.pointerType)} @mouseover=${() => this.mouseOverHandler(name)} @mouseout=${() => this.mouseOutHandler(name)}>${formatPoint(suggestion)}

`)}
`)}
${t('now')}
${!this.settingsState.combineDateTime ? html` ` : html` `}
${client.profile.products.map(product => html` this.settingsState.toggleProduct(event.target.name)} .checked=${this.settingsState.products[product.id] ?? true}> `)}
${this.history.length !== 0 ? html`
` : nothing}
${when( this.showHistory, () => html`
${this.history.map((element, index) => html`
${t('from')}: ${formatPoint(element.fromPoint)} ${element.viaPoint ? html`
${t('via')} ${formatPoint(element.viaPoint)}
` : nothing}
${t('to')}: ${formatPoint(element.toPoint)}
`)}
` )}
`; }; swapFromTo = () => { this.location.from = [this.location.to, this.location.to = this.location.from][0]; this.requestUpdate(); }; resetDate = () => { this.date = new CustomDate(); this.requestUpdate(); }; showSettings = () => this.showDialogOverlay('settings', html``); toggleHistory = () => this.showHistory = !this.showHistory; journeysHistoryAction = num => { const element = this.history[num]; const options = [ {'label': t('setfromto'), 'action': () => { this.setFromHistory(element.key); this.hideOverlay(); }}, {'label': t('journeyoverview'), 'action': () => { window.location = `#/${element.slug}/${this.settingsState.journeysViewMode}`; this.hideOverlay(); }} ]; if (element.lastSelectedJourneyId !== undefined) { options.push({ 'label': t('lastSelectedJourney'), 'action': () => { window.location = `#/j/${this.settingsState.profile}/${element.lastSelectedJourneyId}`; this.hideOverlay(); } }); } this.showSelectOverlay(options); }; setFromHistory = async id => { const entry = await db.getHistoryEntry(id); if (!entry) return; [ 'from', 'via', 'to' ].forEach(mode => { if ( entry[`${mode}Point`] === null) return false; if (mode === 'via') this.settingsState.setShowVia(true); this.location[mode].value = formatPoint(entry[`${mode}Point`]); this.location[mode].suggestionSelected = entry[`${mode}Point`]; }); this.requestUpdate(); }; iconForProduct = id => { const productIcons = { // DB "nationalExpress": "icon-ice", "national": "icon-ic", "regionalExpress": "icon-dzug", // 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", // Rejseplanen "national-train": "icon-ic", "national-train-2": "icon-icl", "local-train": "icon-re", "o": "icon-o", "s-tog": "icon-suburban", }; return productIcons[id] || `icon-${id}`; }; focusNextElement = currentElementId => { switch (currentElementId) { case 'from': this.renderRoot.querySelector('input[name=to]').focus(); if (this.settingsState.showVia) this.renderRoot.querySelector('input[name=via]').focus(); break; case 'via': this.renderRoot.querySelector('input[name=to]').focus(); break; case 'to': this.renderRoot.querySelector('[type=submit]').focus(); break; } }; setSuggestion = (name, num, pointerType) => { this.location[name].value = formatPoint(this.location[name].suggestions[num]); this.location[name].suggestionSelected = this.location[name].suggestions[num]; this.location[name].suggestionsVisible = false; this.requestUpdate(); if (pointerType) this.focusNextElement(name); }; profileChangeHandler = async () => { [ 'from', 'via','to' ].forEach(name => { this.location[name] = { value: '', suggestionsVisible: false, suggestionSelected: null, suggestion: null, suggestions: [], }; }); this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse(); }; submitHandler = async event => { event.preventDefault(); if (this.isOffline !== false) { this.showAlertOverlay(t('offline')); return; } const params = { from: null, to: null, via: null, results: 6, products: {}, bike: this.settingsState.bikeFriendly, transferTime: this.settingsState.transferTime, }; await Promise.all([ 'from', 'via', 'to' ].map(async mode => { if (this.location[mode].value !== '') { if (mode === 'via' && !this.settingsState.showVia) return false; if (!this.location[mode].suggestionSelected) { if (this.location[mode].suggestions.length !== 0) { params[mode] = this.location[mode].suggestions[0] } else { const data = await client.locations(this.location[mode].value, {'results': 1}); if (!data[0]) return false; params[mode] = data[0]; } } else { params[mode] = this.location[mode].suggestionSelected; } } })); if (!params.from || !params.to) return false; if (formatPoint(params.from) === formatPoint(params.to) && params.via === null) { this.showAlertOverlay('From and To are the same place.'); return false; }; client.profile.products.forEach(product => { params.products[product.id] = this.settingsState.products[product.id] ?? true; }); if (this.noTransfers) params.transfers = 0; if (!this.isArrival) params.departure = this.date.getTime(); else params.arrival = this.date.getTime(); if (this.settingsState.profile !== 'db') { params.accessibility = this.settingsState.accessibility; params.walkingSpeed = this.settingsState.walkingSpeed; } if (isDevServer) console.info('SearchView(params):',params); try { this.showLoaderOverlay(); const responseData = await newJourneys(params); window.location = `#/${responseData.slug}/${this.settingsState.journeysViewMode}`; this.hideOverlay(); } catch(e) { this.showAlertOverlay(e.toString()); console.error(e); } }; mouseOverHandler = name => { this.location[name].suggestionsFocused = true; }; mouseOutHandler = name => { this.location[name].suggestionsFocused = false; }; focusHandler = event => { const name = event.target.name; this.location[name].suggestionsVisible = true; this.requestUpdate(); }; blurHandler = event => { const name = event.target.name; if (!this.location[name].suggestionsFocused) { this.location[name].suggestionsVisible = false; this.requestUpdate(); } }; keyupHandler = event => { const name = event.target.name; const value = event.target.value; if (event.key !== 'Enter') return true; if (this.numEnter === 2 && value === formatPoint(this.location[name].suggestionSelected)) { this.numEnter = 0; this.focusNextElement(name); } }; keydownHandler = event => { const name = event.target.name; if (this.location[name].suggestions.length === 0) return true; if (event.key === 'Enter') { event.preventDefault(); this.numEnter++; this.setSuggestion(name, this.location[name].suggestion); return true; }; if (!this.location[name].suggestionsVisible) { this.numEnter = 0; this.location[name].suggestionsVisible = true; this.requestUpdate(); return true; } if (['Escape', 'Tab'].includes(event.key)) { this.location[name].suggestionsVisible = false; } if (['ArrowUp', 'ArrowDown'].includes(event.key) && !event.shiftKey) { event.preventDefault(); const numSuggesttions = this.location[name].suggestions.length-1; if (event.key === 'ArrowUp') { if (this.location[name].suggestion === 0) { this.location[name].suggestion = numSuggesttions; } else { this.location[name].suggestion--; } } else { if (this.location[name].suggestion === numSuggesttions) { this.location[name].suggestion = 0; } else { this.location[name].suggestion++; } } } this.requestUpdate(); }; inputHandler = async event => { const name = event.target.name; const value = event.target.value.trim(); if (['from', 'via', 'to'].includes(name)) { if (this.isOffline !== false) return; if (value === '') return; this.location[name].value = value; let suggestions; const ds100Result = getIBNRbyDS100(value); if (ds100Result !== null) { suggestions = await client.locations(ds100Result, {'results': 1}); } else { suggestions = await client.locations(value, {'results': 10}); } this.location[name].suggestionSelected = null; this.location[name].suggestion = 0; this.location[name].suggestions = suggestions; this.requestUpdate(); }; }; changeHandler = event => { const name = event.target.name; const value = event.target.value; if (name === 'noTransfers') this.noTransfers = !this.noTransfers; if (name === 'isArrival') this.isArrival = !this.isArrival; if (name === 'dateTime') this.date.setDateTime(value); if (name === 'date') this.date.setDate(value); if (name === 'time') this.date.setTime(value); this.requestUpdate(); }; } customElements.define('search-view', SearchView);