commit 5422ec49e86f1cf211be3f614decd1e0e6a4015f
parent 69c2f311e75919dbfd667f4db93f33a1a8a2ff50
Author: Yureka <yuka@yuka.dev>
Date: Thu, 3 Feb 2022 12:57:54 +0100
parent 69c2f311e75919dbfd667f4db93f33a1a8a2ff50
Author: Yureka <yuka@yuka.dev>
Date: Thu, 3 Feb 2022 12:57:54 +0100
stateless server
9 files changed, 162 insertions(+), 81 deletions(-)
M
|
143
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
diff --git a/client/src/app_functions.js b/client/src/app_functions.js @@ -21,7 +21,29 @@ const addJourneys = async data => { ConsoleLog(data); - await db.put('journeys', data); + const historyEntry = { + fromPoint: getFrom(data.journeys), + //viaPoint: data.params.via, + toPoint: getTo(data.journeys), + slug: data.slug, + journeyId: '' + }; + + const tx = db.transaction(['journey', 'journeysOverview', 'journeysHistory'], 'readwrite'); + const journeyStore = tx.objectStore('journey'); + const journeysOverviewStore = tx.objectStore('journeysOverview'); + const journeysHistoryStore = tx.objectStore('journeysHistory'); + let proms = data.journeys.map(j => { + j.settings = data.settings; + journeyStore.put(j); + }); + proms.push(journeysOverviewStore.put({ + ...data, + journeys: data.journeys.map(j => j.refreshToken), + })); + proms.push(journeysHistoryStore.put(historyEntry)); + await Promise.all(proms); + await tx.done; //const lastHistoryEntry = (await getJourneysHistory()).slice(-1); //if (lastHistoryEntry[0] @@ -30,17 +52,10 @@ const addJourneys = async data => { // dataStorage.journeysHistory[dataStorage.journeysHistory.length-1].slug = data.slug; // dataStorage.journeysHistory[dataStorage.journeysHistory.length-1].journeyId = ''; //} else { - await addHistoryEntry({ - fromPoint: getFrom(data.journeys), - //viaPoint: data.params.via, - toPoint: getTo(data.journeys), - slug: data.slug, - journeyId: '' - }); //} }; -const mkParams = () => { +const mkSettings = () => { return { stopovers: true, polylines: settings.showMap || false, @@ -49,52 +64,106 @@ const mkParams = () => { }; }; -const processData = data => { - for (const journey of data.journeys) { - for (const leg of journey.legs) { - if (leg.plannedDeparture) leg.plannedDeparture = new Date(leg.plannedDeparture); - if (leg.plannedArrival) leg.plannedArrival = new Date(leg.plannedArrival); - if (leg.departure) leg.departure = new Date(leg.departure); - if (leg.arrival) leg.arrival = new Date(leg.arrival); - for (const stopover of (leg.stopovers || [])) { - if (stopover.plannedDeparture) stopover.plannedDeparture = new Date(stopover.plannedDeparture); - if (stopover.plannedArrival) stopover.plannedArrival = new Date(stopover.plannedArrival); - if (stopover.departure) stopover.departure = new Date(stopover.departure); - if (stopover.arrival) stopover.arrival = new Date(stopover.arrival); - } +const processJourneys = data => { + for (const journey of data.journeys) processJourney(journey); + return data; +} + +const processJourney = journey => { + for (const leg of journey.legs) { + if (leg.plannedDeparture) leg.plannedDeparture = new Date(leg.plannedDeparture); + if (leg.plannedArrival) leg.plannedArrival = new Date(leg.plannedArrival); + if (leg.departure) leg.departure = new Date(leg.departure); + if (leg.arrival) leg.arrival = new Date(leg.arrival); + for (const stopover of (leg.stopovers || [])) { + if (stopover.plannedDeparture) stopover.plannedDeparture = new Date(stopover.plannedDeparture); + if (stopover.plannedArrival) stopover.plannedArrival = new Date(stopover.plannedArrival); + if (stopover.departure) stopover.departure = new Date(stopover.departure); + if (stopover.arrival) stopover.arrival = new Date(stopover.arrival); } } - return data; + return journey; }; export const getJourneys = async slug => { if (!slug) return; - let data = await db.get('journeys', slug); - if (!data) { - data = await get(`savedJourneys/${slug}`, mkParams()); - await addJourneys(data); + let data = await db.get('journeysOverview', slug); + data.journeys = await Promise.all(data.journeys.map(getJourney)); + + return processJourneys(data); +}; + +export const getJourney = async refreshToken => { + if (!refreshToken) return; + + let data = await db.get('journey', refreshToken); + const settings = mkSettings(); + if (!data || JSON.stringify(data.settings) != JSON.stringify(settings)) { + data = await get(`journeys/${encodeURIComponent(refreshToken)}`, settings); + data.settings = settings; + await db.put('journey', data); } - return processData(data); + return processJourney(data); }; export const getMoreJourneys = async (slug, mode, hideLoader) => { - const data = await post(`savedJourneys/${slug}/${mode}`, mkParams(), hideLoader); - await addJourneys(data); - return processData(data); -}; + if (!slug) return; + const saved = await db.get('journeysOverview', slug); + const params = { ...saved.params, ...mkSettings() }; + params[mode+'Than'] = saved[mode+'Ref']; + let { departure, arrival, ...moreOpt } = params; + console.log(moreOpt); + const [newData, ...existingJourneys] = await Promise.all( + [ get('journeys', moreOpt) ] + .concat(saved.journeys.map(getJourney)) + ); + console.log(newData); + + const res = { + ...saved, + ...newData, + }; + if (mode === 'earlier') { + res.journeys = newData.journeys.concat(existingJourneys); + res.indexOffset += newData.journeys.length; + // keep old laterRef + res.laterRef = saved.laterRef; + } else { + res.journeys = existingJourneys.concat(newData.journeys); + // keep old earlierRef + res.earlierRef = saved.earlierRef; + } + console.log(res); + + await addJourneys(res); + return processJourneys(res); +}; export const refreshJourneys = async (reqId, hideLoader) => { - const data = await get(`savedJourneys/${reqId}`, mkParams(), hideLoader); - await addJourneys(data); - return processData(data); +}; +export const refreshJourney = async (refreshToken, hideLoader) => { +}; + +const generateSlug = () => { + const len = 8; + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < len; i++) + result += characters.charAt(Math.floor(Math.random() * characters.length)); + return result; }; export const newJourneys = async (params, hideLoader) => { - const data = await post('savedJourneys', { ...mkParams(), ...params }, hideLoader); + const settings = mkSettings(); + const data = await get('journeys', { ...settings, ...params }, hideLoader); + data.slug = generateSlug(); + data.indexOffset = 0; + data.params = params; + data.settings = settings; await addJourneys(data); - return processData(data); + return processJourneys(data); }; export const t = (key, ...params) => {
diff --git a/client/src/canvas.js b/client/src/canvas.js @@ -321,7 +321,8 @@ const mouseUpHandler = (evt) => { const num = Math.floor((x - canvasState.offsetX + 2 * padding) / rectWidthWithPadding) + canvasState.indexOffset; if (num >= 0) { if (num < canvasState.journeys.length) { - go(`/${canvasState.slug}/${num - canvasState.indexOffset}`); + const j = canvasState.journeys[num - canvasState.indexOffset]; + go(`/j/${j.refreshToken}`); } else { moreJourneys(canvasState.slug, 'later'); }
diff --git a/client/src/dataStorage.js b/client/src/dataStorage.js @@ -6,19 +6,38 @@ const dbName = devMode ? 'trainsearch_dev' : 'trainsearch'; export let db; export const initDataStorage = async () => { - db = await openDB(dbName, 1, { + db = await openDB(dbName, 2, { upgrade: (db, oldVersion, newVersion, transaction) => { console.log(`upgrading database from ${oldVersion} to ${newVersion}`); switch (oldVersion) { case 0: - if (!db.objectStoreNames.contains('journeys')) - db.createObjectStore('journeys', {keyPath: 'slug'}); - if (!db.objectStoreNames.contains('journeysHistory')) - db.createObjectStore('journeysHistory', {autoIncrement: true}); - if (!db.objectStoreNames.contains('settings')) - db.createObjectStore('settings'); + /* + * in database scheme v1, the `journeys` object store stores whole searches, including journey data + */ + db.createObjectStore('journeys', {keyPath: 'slug'}); + db.createObjectStore('journeysHistory', {autoIncrement: true}); + db.createObjectStore('settings'); case 1: - //... add migrations for 1->2 here + /* + * in database scheme v2, there are the following data stores: + * `journey` for storing individual journeys with their refreshToken as index + * `journeysOverview`t contains the search results with the `.journeys` field being an array of refreshTokens. + * + * in comparison to v1, there are these additional changes: + * `journeys` object store is deleted. + * `journeysHistory` object store is cleared/recreated. + */ + + /* clean up old stuff */ + db.deleteObjectStore('journeys'); + db.deleteObjectStore('journeysHistory'); + + /* create new stores */ + db.createObjectStore('journey', {keyPath: 'refreshToken'}); + db.createObjectStore('journeysOverview', {keyPath: 'slug'}); + db.createObjectStore('journeysHistory', {autoIncrement: true}); + case 2: + //... add migrations for 2->3 here } }, blocking: async () => {
diff --git a/client/src/helpers.js b/client/src/helpers.js @@ -93,3 +93,13 @@ export const getTo = journeys => { return journeys[0].legs[journeys[0].legs.length-1].destination; }; +export const formatPrice = price => { + if (!price) return '-'; + const currencies = { USD: '$', EUR: '€', GBP: '£' }; + let ret = currencies[price.currency] || price.currency; + ret += Math.floor(price.amount); + ret += '.'; + ret += padZeros(price.amount * 100 % 100, 2); + return ret; +}; +
diff --git a/client/src/journeyView.js b/client/src/journeyView.js @@ -1,6 +1,6 @@ import { settings } from './settings.js'; -import { showDiv, hideDiv, ElementById, formatDateTime, formatDuration } from './helpers.js'; -import { ConsoleLog, parseName, ds100Names, t, timeTemplate, getJourneys, refreshJourneys } from './app_functions.js'; +import { showDiv, hideDiv, ElementById, formatDateTime, formatDuration, formatPrice } from './helpers.js'; +import { ConsoleLog, parseName, ds100Names, t, timeTemplate, getJourney, refreshJourneys } from './app_functions.js'; import { showModal } from './overlays.js'; import { get } from './api.js'; import { go } from './router.js'; @@ -71,9 +71,9 @@ const legTemplate = leg => { <tr> <td colspan="4"> <span>${marudorUrl ? html` - <a href="${marudorUrl}">${leg.line.name} → ${leg.direction}</a> + <a href="${marudorUrl}">${leg.line.name}${leg.direction ? html` → ${leg.direction}` : ''}</a> ` : html ` - ${leg.line.name} → ${leg.direction} + ${leg.line.name}${leg.direction ? html` → ${leg.direction}` : ''} `} ${leg.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : ''} ${Object.entries(remarks).map(remarksTemplate)} @@ -127,7 +127,7 @@ const legTemplate = leg => { `; }; -const journeyTemplate = (data, requestId, journeyId) => { +const journeyTemplate = (data) => { const duration = data.legs[data.legs.length - 1].arrival - data.legs[0].departure; const legs = []; @@ -160,12 +160,12 @@ const journeyTemplate = (data, requestId, journeyId) => { return html` <div class="journey"> <header> - <a class="back icon-back" href="#/${requestId}"></a> + <a class="back icon-back" title="${t("back")}" href="#/">${t("back")}</a> <div class="header-content"> <h3>${parseName(data.legs[0].origin)} → ${parseName(data.legs[data.legs.length - 1].destination)}</h3> - <p><b>${t('duration')}: ${formatDuration(duration)} | ${t('changes')}: ${changes-1} | ${t('date')}: ${formatDateTime(data.legs[0].plannedDeparture, 'date')}</b></p> + <p><b>${t('duration')}: ${formatDuration(duration)} | ${t('changes')}: ${changes-1} | ${t('date')}: ${formatDateTime(data.legs[0].plannedDeparture, 'date')}${settings.showPrices && data.price ? html` | ${t('price')}: <td><span>${formatPrice(data.price)}</span></td>` : ''}</b></p> </div> - <a class="reload icon-reload" title="{{LABEL_RELOAD}}" @click=${() => refreshJourneyView(requestId, journeyId)}>{{LABEL_RELOAD}}</a> + <a class="reload icon-reload" title="${t("reload")}" @click=${() => refreshJourneyView(requestId, journeyId)}>${t("reload")}</a> </header> ${legs.map(legTemplate)} @@ -198,15 +198,12 @@ const stopPlatformTemplate = (data) => { }; export const journeyView = async (match) => { - const reqId = match[0]; - const journeyId = Number(match[1]); + const refreshToken = decodeURIComponent(match[0]); - let all = await getJourneys(reqId); - if (!((journeyId + all.indexOffset) in all.journeys)) all = await refreshJourneys(reqId, false); - const data = all.journeys[journeyId + all.indexOffset]; + let data = await getJourney(refreshToken); ConsoleLog(data); - render(journeyTemplate(data, reqId, journeyId), ElementById('content')); + render(journeyTemplate(data), ElementById('content')); /*const history_id = dataStorage.journeysHistory.findIndex(obj => obj.reqId === reqId);
diff --git a/client/src/journeysView.js b/client/src/journeysView.js @@ -1,4 +1,4 @@ -import { showDiv, hideDiv, ElementById, formatDuration, formatFromTo, getFrom, getTo, padZeros } from './helpers.js'; +import { showDiv, hideDiv, ElementById, formatDuration, formatFromTo, getFrom, getTo, padZeros, formatPrice } from './helpers.js'; import { parseName, ConsoleLog, t, timeTemplate, getJourneys, getMoreJourneys } from './app_functions.js'; import { settings, modifySettings } from './settings.js'; import { setupCanvas } from './canvas.js'; @@ -85,7 +85,7 @@ const journeyOverviewTemplate = (entry, slug, key) => { for (const leg of entry.legs) { if (leg.cancelled) cancelled = true; - if (leg.isWalking || leg.isTransfer) continue; + if (leg.walking || leg.transfer) continue; changes = changes+1; @@ -106,7 +106,7 @@ const journeyOverviewTemplate = (entry, slug, key) => { }).join(', '); return html` - <tr @click=${() => go('/'+slug + '/' + key)}"> + <tr @click=${() => go('/j/'+entry.refreshToken)}"> <td class="${cancelled ? 'cancelled' : ''}"><span>${timeTemplate(firstLeg, 'departure')}</span></td> ${cancelled ? html` <td><span class="cancelled-text">${t('cancelled-ride')}</span></td> @@ -121,20 +121,11 @@ const journeyOverviewTemplate = (entry, slug, key) => { </tr>`; }; -const formatPrice = price => { - if (!price) return '-'; - const currencies = { USD: '$', EUR: '€', GBP: '£' }; - let ret = currencies[price.currency] || price.currency; - ret += Math.floor(price.amount); - ret += '.'; - ret += padZeros(price.amount * 100 % 100, 2); - return ret; -}; - export const journeysView = async (match, isUpdate) => { const slug = match[0]; const data = await getJourneys(slug); + console.log(data); render(journeysTemplate(data), ElementById('content'));
diff --git a/client/src/main.js b/client/src/main.js @@ -13,7 +13,7 @@ import { showDiv, hideDiv, ElementById } from './helpers.js'; route(/^\/$/, searchView); route(/^\/([a-zA-Z0-9]+)$/, journeysView); - route(/^\/([a-zA-Z0-9]+)\/([-0-9]+)$/, journeyView); + route(/^\/j\/(.+)$/, journeyView); hideDiv('overlay'); if (!window.location.hash.length) go('/');
diff --git a/client/src/searchView.js b/client/src/searchView.js @@ -273,7 +273,7 @@ export const search = async (requestId) => { from: formatFromTo(from), to: formatFromTo(to), results: 6, - //"accessibility": settings.accessibility, + accessibility: settings.accessibility, ...settings.products, };
diff --git a/client/src/settingsView.js b/client/src/settingsView.js @@ -46,8 +46,6 @@ const newAll = () => { }; const saveSettings = async () => { - let clearJourneys = false; - const clearJourneysHistory = false; await modifySettings(settings => { settings.showRIL100Names = ElementById('ril100').checked; settings.writeDebugLog = ElementById('debug-messages').checked; @@ -55,20 +53,16 @@ const saveSettings = async () => { settings.advancedSelection = ElementById('advancedSelection').checked; const language = document.querySelector('input[name="language"]:checked').value; - if (settings.language != language) clearJourneys = true; settings.language = language; const showMap = ElementById('feature-map').checked; - if (settings.showMap != showMap) clearJourneys = clearJourneys || showMap; settings.showMap = showMap; const showPrices = ElementById('feature-prices').checked; - if (settings.showPrices != showPrices) clearJourneys = clearJourneys || showPrices; settings.showPrices = showPrices; return settings; }); - if (clearJourneys) await db.clear('journeys'); searchView(); hideDiv('overlay'); };