diff --git a/base.css b/base.css index 5245f36..21405ac 100644 --- a/base.css +++ b/base.css @@ -351,6 +351,37 @@ pre { border-bottom-right-radius: 5%; } +.suggestion { + display: flex; + flex-direction: row; + align-items: center; +} + +.suggestion :first-child { + flex: 1; +} + +.suggestion .star { + flex: 0; + pointer-events: none; + width: 24px; + height: 24px; +} + +.suggestion .star.checked { + filter: invert(90%) sepia(49%) saturate(704%) hue-rotate(359deg) brightness(94%) contrast(99%); +} + +@media (prefers-color-scheme: dark) { + .suggestion .star { + filter: invert(100%); + } + + .suggestion .star.checked { + filter: invert(86%) sepia(79%) saturate(2126%) hue-rotate(357deg) brightness(108%) contrast(104%); + } +} + .product-suburban { color: green !important; } diff --git a/config-route.css b/config-route.css index f74e585..f953978 100644 --- a/config-route.css +++ b/config-route.css @@ -115,34 +115,3 @@ div.checkbox p { div.checkbox input { flex: 0; } - -.suggestion { - display: flex; - flex-direction: row; - align-items: center; -} - -.suggestion :first-child { - flex: 1; -} - -.suggestion .star { - flex: 0; - pointer-events: none; - width: 24px; - height: 24px; -} - -.suggestion .star.checked { - filter: invert(90%) sepia(49%) saturate(704%) hue-rotate(359deg) brightness(94%) contrast(99%); -} - -@media (prefers-color-scheme: dark) { - .suggestion .star { - filter: invert(100%); - } - - .suggestion .star.checked { - filter: invert(86%) sepia(79%) saturate(2126%) hue-rotate(357deg) brightness(108%) contrast(104%); - } -} diff --git a/station.html b/station.html index c5d2580..95f4dbd 100644 --- a/station.html +++ b/station.html @@ -15,6 +15,7 @@ + diff --git a/station.js b/station.js index 240e4a8..d552873 100755 --- a/station.js +++ b/station.js @@ -1,5 +1,12 @@ +var starred = [] var knownStations = [] +/** + * @type {'unavailable' | 'notRequested' | 'waiting' | 'gotData'} + */ +var nearbyStatus = 'notRequested' +var nearbyStations = [] + function goToStation(stationId) { var url = new URL(window.location.href) url.pathname = 'view-station.html' @@ -22,6 +29,94 @@ var focusedElement = null var _rebuildDebounce = null var _rebuildRequested = false +function createSuggestion(suggestion, index) { + delete suggestion['products'] + var suggestionDiv = document.createElement('div') + suggestionDiv.classList.add('suggestion') + + var suggestionLi = document.createElement('li') + suggestionDiv.appendChild(suggestionLi) + + suggestionLi.classList.add('items') + if (index) { + suggestionLi.tabIndex = index + 1 + } + suggestionLi.style.padding = '2px 0' + + function onAction(e) { + goToStation(JSON.stringify(suggestion)) + } + suggestionLi.addEventListener('click', onAction) + suggestionLi.addEventListener('keypress', function (e) { + if (e.key == 'Enter') { + onAction(e) + } + }) + suggestionLi.addEventListener('focus', function (e) { + focusedElement = suggestionLi + }) + + var stationNameP = document.createElement('p') + suggestionLi.appendChild(stationNameP) + + var stationNameSpan = document.createElement('span') + stationNameP.append(stationNameSpan) + stationNameSpan.textContent = suggestion.name || suggestion.address + stationNameSpan.classList.add('pri', 'stationName') + + if (suggestion.distance) { + stationNameP.append(' ') + var stationDistanceSpan = document.createElement('span') + stationNameP.append(stationDistanceSpan) + stationDistanceSpan.append('(', suggestion.distance.toString(), ' m)') + stationDistanceSpan.classList.add('stationDistance') + } + + + if (window.localStorage) { + var suggestionDistance = suggestion['distance'] + delete suggestion['distance'] + var suggestionLink = document.createElement('a') + suggestionLink.classList.add('no-custom-a') + + var suggestionStar = document.createElement('object') + suggestionLink.appendChild(suggestionStar) + suggestionStar.classList.add('star') + suggestionStar.type = 'image/svg+xml' + function setStar() { + if (starred.includes(JSON.stringify(suggestion))) { + suggestionStar.data = '/icons/star_full.svg' + suggestionStar.classList.add('checked') + } + else { + suggestionStar.data = '/icons/star_empty.svg' + suggestionStar.classList.remove('checked') + } + } + suggestionLink.addEventListener('click', function (event) { + event.preventDefault() + if (starred.includes(JSON.stringify(suggestion))) { + starred = starred.filter(function (s) { + return s !== suggestion + }) + } else { + starred.push(JSON.stringify(suggestion)) + } + setStar() + localStorage.setItem('stations/starred', JSON.stringify(starred)) + }) + setStar() + suggestionDiv.appendChild(suggestionLink) + suggestion['distance'] = suggestionDistance + } + + // var trainCompanyP = document.createElement('p') + // suggestionLi.appendChild(trainCompanyP) + + // trainCompanyP.textContent = suggestion.company + // trainCompanyP.classList.add('thi') + return suggestionDiv +} function rebuildSuggestions() { if (_rebuildDebounce !== null) { _rebuildRequested = true @@ -37,42 +132,66 @@ function rebuildSuggestions() { } var suggestions = knownStations.slice() + if (suggestions.length === 0) { + suggestions = starred.map(function (s) { return JSON.parse(s) }) + } suggestions.forEach(function (suggestion, index) { - var suggestionLi = document.createElement('li') - suggestionsArea.appendChild(suggestionLi) - - setTimeout(function () { - suggestionLi.classList.add('items') - suggestionLi.tabIndex = index + 1 - suggestionLi.style.padding = '2px 0' + suggestionsArea.append(createSuggestion(suggestion, index)) + }) - function onAction(e) { - goToStation(suggestion.id) - } - suggestionLi.addEventListener('click', onAction) - suggestionLi.addEventListener('keypress', function (e) { - if (e.key == 'Enter') { - onAction(e) - } - }) - suggestionLi.addEventListener('focus', function (e) { - focusedElement = suggestionLi + if (nearbyStatus !== 'unavailable') { + suggestionsArea.appendChild(h4('Nearby stations')) + if (nearbyStatus === 'notRequested') { + suggestionsArea.appendChild(a('', 'Load nearby stations').event$('click', function (event) { + event.preventDefault() + var watchId = navigator.geolocation.watchPosition( + function (data) { + var geoUrl = new URL('https://v6.db.transport.rest/locations/nearby') + geoUrl.searchParams.append('latitude', data.coords.latitude.toString()) + geoUrl.searchParams.append('longitude', data.coords.longitude.toString()) + geoUrl.searchParams.append('results', '10') + fetch(geoUrl) + .then(function (response) { + return response.json() + }) + .then(function (data) { + nearbyStatus = 'gotData' + nearbyStations = data + rebuildSuggestions() + }) + .catch(function () { + nearbyStatus = 'unavailable' + rebuildSuggestions() + }) + }, + function (error) { + if (nearbyStations.length === 0) { + nearbyStatus = 'unavailable' + rebuildSuggestions() + } + navigator.geolocation.clearWatch(watchId) + }, + { + enableHighAccuracy: true, + }, + ) + nearbyStatus = 'waiting' + rebuildSuggestions() + })) + } + else if (nearbyStatus === 'waiting') { + var waitingP = document.createElement('p') + suggestionsArea.append(waitingP) + waitingP.append('Loading...') + waitingP.classList.add('pri') + } + else if (nearbyStatus === 'gotData') { + nearbyStations.forEach(function (suggestion, index) { + suggestionsArea.appendChild(createSuggestion(suggestion, suggestions.length + index)) }) - - var stationNameP = document.createElement('p') - suggestionLi.appendChild(stationNameP) - - stationNameP.textContent = suggestion.name - stationNameP.classList.add('pri', 'stationName') - - // var trainCompanyP = document.createElement('p') - // suggestionLi.appendChild(trainCompanyP) - - // trainCompanyP.textContent = suggestion.company - // trainCompanyP.classList.add('thi') - }, 0) - }) + } + } setTimeout(function () { _rebuildDebounce = null @@ -97,7 +216,12 @@ function reloadSuggestions() { fetchAbortController = new AbortController() fetch(locationsUrl.toString(), { signal: fetchAbortController.signal }) .then(function (response) { - return response.json() + if (response.ok) { + return response.json() + } + else { + return {} + } }) .then(function (data) { if (data) { @@ -125,6 +249,13 @@ function csk() { } window.addEventListener('load', function (e) { + if (window.localStorage) { + var maybeStarred = JSON.parse(localStorage.getItem('stations/starred')) + if (maybeStarred) { + starred = maybeStarred + } + } + var stationName = document.getElementById('stationName') stationName.addEventListener('input', function (e) { reloadSuggestions()