import { html, css, nothing } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { when } from 'lit/directives/when.js';
import { BaseView } from './baseView.js';
import { createEvents } from 'ics';
import { sleep, queryBackgroundColor, setThemeColor } from './helpers.js';
import { db } from './dataStorage.js';
import { t } from './translate.js';
import { getJourney, refreshJourney } from './app_functions.js';
import { remarksModal, platformTemplate, stopTemplate, timeTemplate } from './templates.js';
import { formatPoint, formatDuration, formatPrice, formatTrainTypes, formatLineAdditionalName, formatLineDisplayName } from './formatters.js';
import { cachedCoachSequence } from './coach-sequence/index.js';
import { headerStyles, tableStyles, journeyViewStyles } from './styles.js';
class JourneyView extends BaseView {
static styles = [
super.styles,
headerStyles,
tableStyles,
journeyViewStyles
];
static properties = {
profile: { attribute: true },
refreshToken: { attribute: true, converter: (value) => decodeURIComponent(value)},
viewState: { state: true },
};
constructor () {
super();
this.viewState = null;
}
async connectedCallback () {
super.connectedCallback();
await sleep(200);
setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
}
disconnectedCallback () {
super.disconnectedCallback();
this.viewState = null;
}
async willUpdate (previous) {
if (previous.has('refreshToken')) this.viewState = null;
if (!this.viewState && !this.overlayState.visible) await this.updateViewState();
}
updated (previous) {
super.updated(previous, 'JourneyView');
if (isDevServer && previous.has('viewState')) console.info('JourneyView(viewState): ', this.viewState);
}
renderView = () => [
html`
`,
when(!this.viewState,
() => html``,
() => html`
${this.viewState.legs.map(leg => this.legTemplate(leg))}
`
)
];
legTemplate = leg => {
if (leg.walking) {
return html`${t(leg.distance === null ? 'walkinfo' : 'walkinfoMeters', formatPoint(leg.destination), leg.distance)}
`;
} else if (leg.transfer) {
return html`${t('transferinfo', formatPoint(leg.destination))}
`;
} else if (leg.change) {
return html`${t('changeinfo', formatDuration(leg.duration))}
`;
} else {
return html`
${when(
leg.cancelled,
() => html`${t('cancelled')}`
)}
${when(
formatLineAdditionalName(leg.line) !== null,
() => html`Trip: ${formatLineAdditionalName(leg.line)}`
)}
${when(
leg.line.trainType,
() => html`${t('trainType')}: ${leg.line.trainType}`
)}
${when(
leg.arrival && leg.departure,
() => html`${t('duration')}: ${formatDuration(leg.arrival - leg.departure)}`
)}
${when(
leg.loadFactor,
() => html`${t(`load-${leg.loadFactor}`)}`
)}
${t('arrival')}
${t('departure')}
${t('station')}
${t('platform')}
${(leg.stopovers || []).map(stop => html`
${timeTemplate(stop, 'arrival')}
${timeTemplate(stop, 'departure')}
${stopTemplate(this.profile, stop.stop)}
${platformTemplate(stop)}
`)}
`;
}
};
updateViewState = async viewState => {
const wasUpdating = this.isUpdating;
this.isUpdating = true;
try {
if (viewState === undefined) viewState = await getJourney(this.profile, this.refreshToken);
if (viewState.slug) {
let overviewObject = await db.getJourneysOverview(viewState.slug);
let historyObject = await db.getHistoryEntry(overviewObject.historyEntryId);
historyObject.lastSelectedJourneyId = viewState.refreshToken;
await db.updateHistoryEntry(overviewObject.historyEntryId, historyObject);
}
viewState.changes = 0;
viewState.duration = null;
if (viewState.legs[viewState.legs.length - 1].arrival && viewState.legs[0].departure)
viewState.duration = viewState.legs[viewState.legs.length - 1].arrival - viewState.legs[0].departure;
let lastArrival;
viewState.legs = viewState.legs.flatMap(leg => {
if (!leg.remarks) leg.remarks = [];
let legs = [];
const remarksStatus = leg.remarks.some((obj) => obj.type === 'status');
const remarksWarning = leg.remarks.some((obj) => obj.type === 'warning');
leg.remarksIcon = remarksWarning ? 'icon-warning' : (remarksStatus ? 'icon-status' : 'icon-hint');
if (!leg.walking && !leg.transfer) {
// add change
if (lastArrival) {
let duration = null;
if (leg.departure && lastArrival) duration = leg.departure - lastArrival;
legs.push({
change: true,
duration,
});
}
viewState.changes++;
lastArrival = leg.arrival;
} else if (legs.length) {
// if this is a walking leg and it is the first one, we don't want to
// insert a 0 minutes change entry for this
lastArrival = leg.arrival;
}
legs.push(leg);
return legs
});
this.viewState = viewState;
if (isDevServer) console.info('JourneyView(updateViewState): coach sequence fetch start');
//fetch train types after setting the viewState
if (!this.isOffline) {
for (const leg of this.viewState.legs) {
if (leg.line && leg.line.name) {
const [category, number] = leg.line.name.split(' ');
const info = cachedCoachSequence(category, leg.line.fahrtNr || number, leg.origin.id, leg.plannedDeparture);
if (info) leg.line.trainType = formatTrainTypes(info);
this.requestUpdate();
}
}
}
if (isDevServer) console.info('JourneyView(updateViewState): coach sequence fetch end');
} catch(e) {
this.showAlertOverlay(
e.toString(),
() => {
window.location = '#/';
}
);
console.error(e);
}
if (!wasUpdating) this.isUpdating = false;
};
refreshJourney = async () => {
if (this.isOffline !== false) return;
if (this.isUpdating !== false) return false;
this.isUpdating = true;
try {
const data = await refreshJourney(this.profile, this.refreshToken);
await this.updateViewState(data);
} catch(e) {
this.showAlertOverlay(e.toString());
console.error(e);
}
this.isUpdating = false;
};
moreModal = async () => {
const options = [
{ 'label': !navigator.canShare ? 'copyURL' : 'shareURL', 'action': async () => await this.shareAction() },
];
if (isDevServer) options.push({ 'label': 'addCalendar', 'action': async () => await this.calendarAction() });
if (this.profile === 'db') options.push({ 'label': 'tickets', 'action': async () => await this.ticketsAction() });
this.showSelectOverlay(options);
};
shareAction = async () => {
try {
await navigator.share({ url: window.location });
} catch (error) {
await navigator.clipboard.writeText(window.location);
this.showAlertOverlay('URL has been copied to clipboard.');
}
};
ticketsAction = async () => {
try {
this.showLoaderOverlay();
if (!this.isOffline) await this.refreshJourney();
if (this.viewState.tickets === undefined) {
await this.showAlertOverlay('No ticket data available');
return false;
}
this.showDialogOverlay('tickets', html`
${this.viewState.tickets.map(ticket => html`
${ticket.name} ${ticket.firstClass ? `1. ${t('class')}` : nothing}
${ticket.addDataTicketDetails}
${ticket.priceObj.amount / 100}
`)}
`);
} catch(e) {
this.showAlertOverlay(e.toString());
console.error(e);
}
};
calendarAction = async () => {
let events = [];
this.viewState.legs.forEach(leg => {
if (!leg.line) return;
const departureStop = leg.origin.location
events.push({
productId: APP_NAME,
created: (new Date).getTime(),
start: leg.plannedDeparture.getTime(),
end: leg.plannedArrival.getTime(),
title: `${leg.line.name} → ${formatPoint(leg.destination)}`,
url: window.location.href,
geo: { lat: departureStop.latitude , lon: departureStop.longitude },
description: `${leg.line.name} → ${leg.direction}\n
dep ${timeTemplate(departureStop, 'departure')} ${formatPoint(departureStop)}`,
});
});
const file = await new Promise((resolve, reject) => {
const { error, value } = createEvents(events);
if (error) reject(error);
resolve(new File([value], 'event.ics', { type: 'text/calendar' }));
});
window.location = URL.createObjectURL(file);
};
}
customElements.define('journey-view', JourneyView);