transit-departures-widget 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +343 -0
- package/dist/app/index.js.map +1 -0
- package/dist/bin/transit-departures-widget.d.ts +1 -0
- package/dist/bin/transit-departures-widget.js +478 -0
- package/dist/bin/transit-departures-widget.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +420 -0
- package/dist/index.js.map +1 -0
- package/package.json +25 -6
- package/.eslintrc.json +0 -28
- package/.husky/pre-commit +0 -4
- package/CHANGELOG.md +0 -206
- package/app/index.js +0 -95
- package/bin/transit-departures-widget.js +0 -38
- package/config-sample.json +0 -15
- package/docs/images/transit-departures-widget-logo.svg +0 -18
- package/index.js +0 -1
- package/lib/file-utils.js +0 -110
- package/lib/log-utils.js +0 -77
- package/lib/transit-departures-widget.js +0 -97
- package/lib/utils.js +0 -329
- package/locales/en.json +0 -20
- package/locales/pl.json +0 -20
- package/public/css/transit-departures-widget-styles.css +0 -200
- package/public/img/refresh.svg +0 -1
- package/public/js/transit-departures-widget.js +0 -687
- package/views/widget/layout.pug +0 -19
- package/views/widget/widget.pug +0 -60
- package/views/widget/widget_full.pug +0 -3
package/lib/utils.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { fileURLToPath } from 'url'
|
|
2
|
-
import { dirname, join } from 'path'
|
|
3
|
-
import { openDb, getDirections, getRoutes, getStops, getTrips } from 'gtfs'
|
|
4
|
-
import { groupBy, last, maxBy, size, sortBy, uniqBy } from 'lodash-es'
|
|
5
|
-
import { renderFile } from './file-utils.js'
|
|
6
|
-
import sqlString from 'sqlstring-sqlite'
|
|
7
|
-
import toposort from 'toposort'
|
|
8
|
-
import i18n from 'i18n'
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
* Get calendars for a specified date range
|
|
12
|
-
*/
|
|
13
|
-
const getCalendarsForDateRange = (config) => {
|
|
14
|
-
const db = openDb(config)
|
|
15
|
-
let whereClause = ''
|
|
16
|
-
const whereClauses = []
|
|
17
|
-
|
|
18
|
-
if (config.endDate) {
|
|
19
|
-
whereClauses.push(`start_date <= ${sqlString.escape(config.endDate)}`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (config.startDate) {
|
|
23
|
-
whereClauses.push(`end_date >= ${sqlString.escape(config.startDate)}`)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (whereClauses.length > 0) {
|
|
27
|
-
whereClause = `WHERE ${whereClauses.join(' AND ')}`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return db.prepare(`SELECT * FROM calendar ${whereClause}`).all()
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/*
|
|
34
|
-
* Format a route name.
|
|
35
|
-
*/
|
|
36
|
-
function formatRouteName(route) {
|
|
37
|
-
let routeName = ''
|
|
38
|
-
|
|
39
|
-
if (route.route_short_name !== null) {
|
|
40
|
-
routeName += route.route_short_name
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (route.route_short_name !== null && route.route_long_name !== null) {
|
|
44
|
-
routeName += ' - '
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (route.route_long_name !== null) {
|
|
48
|
-
routeName += route.route_long_name
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return routeName
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/*
|
|
55
|
-
* Get directions for a route
|
|
56
|
-
*/
|
|
57
|
-
function getDirectionsForRoute(route, config) {
|
|
58
|
-
const db = openDb(config)
|
|
59
|
-
|
|
60
|
-
// Lookup direction names from non-standard directions.txt file
|
|
61
|
-
const directions = getDirections({ route_id: route.route_id }, [
|
|
62
|
-
'direction_id',
|
|
63
|
-
'direction',
|
|
64
|
-
])
|
|
65
|
-
|
|
66
|
-
const calendars = getCalendarsForDateRange(config)
|
|
67
|
-
|
|
68
|
-
// Else use the most common headsigns as directions from trips.txt file
|
|
69
|
-
if (directions.length === 0) {
|
|
70
|
-
const headsigns = db
|
|
71
|
-
.prepare(
|
|
72
|
-
`SELECT direction_id, trip_headsign, count(*) AS count FROM trips WHERE route_id = ? AND service_id IN (${calendars
|
|
73
|
-
.map((calendar) => `'${calendar.service_id}'`)
|
|
74
|
-
.join(', ')}) GROUP BY direction_id, trip_headsign`,
|
|
75
|
-
)
|
|
76
|
-
.all(route.route_id)
|
|
77
|
-
|
|
78
|
-
for (const group of Object.values(groupBy(headsigns, 'direction_id'))) {
|
|
79
|
-
const mostCommonHeadsign = maxBy(group, 'count')
|
|
80
|
-
directions.push({
|
|
81
|
-
direction_id: mostCommonHeadsign.direction_id,
|
|
82
|
-
direction: i18n.__('To {{{headsign}}}', {
|
|
83
|
-
headsign: mostCommonHeadsign.trip_headsign,
|
|
84
|
-
}),
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return directions
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/*
|
|
93
|
-
* Sort an array of stoptimes by stop_sequence using a directed graph
|
|
94
|
-
*/
|
|
95
|
-
function sortStopIdsBySequence(stoptimes) {
|
|
96
|
-
const stoptimesGroupedByTrip = groupBy(stoptimes, 'trip_id')
|
|
97
|
-
|
|
98
|
-
// First, try using a directed graph to determine stop order.
|
|
99
|
-
try {
|
|
100
|
-
const stopGraph = []
|
|
101
|
-
|
|
102
|
-
for (const tripStoptimes of Object.values(stoptimesGroupedByTrip)) {
|
|
103
|
-
const sortedStopIds = sortBy(tripStoptimes, 'stop_sequence').map(
|
|
104
|
-
(stoptime) => stoptime.stop_id,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
for (const [index, stopId] of sortedStopIds.entries()) {
|
|
108
|
-
if (index === sortedStopIds.length - 1) {
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
stopGraph.push([stopId, sortedStopIds[index + 1]])
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return toposort(stopGraph)
|
|
117
|
-
} catch {
|
|
118
|
-
// Ignore errors and move to next strategy.
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Finally, fall back to using the stop order from the trip with the most stoptimes.
|
|
122
|
-
const longestTripStoptimes = maxBy(
|
|
123
|
-
Object.values(stoptimesGroupedByTrip),
|
|
124
|
-
(stoptimes) => size(stoptimes),
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
return longestTripStoptimes.map((stoptime) => stoptime.stop_id)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/*
|
|
131
|
-
* Get stops in order for a route and direction
|
|
132
|
-
*/
|
|
133
|
-
function getStopsForDirection(route, direction, config) {
|
|
134
|
-
const db = openDb(config)
|
|
135
|
-
const calendars = getCalendarsForDateRange(config)
|
|
136
|
-
const whereClause = formatWhereClauses({
|
|
137
|
-
direction_id: direction.direction_id,
|
|
138
|
-
route_id: route.route_id,
|
|
139
|
-
service_id: calendars.map((calendar) => calendar.service_id),
|
|
140
|
-
})
|
|
141
|
-
const stoptimes = db
|
|
142
|
-
.prepare(
|
|
143
|
-
`SELECT stop_id, stop_sequence, trip_id FROM stop_times WHERE trip_id IN (SELECT trip_id FROM trips ${whereClause}) ORDER BY stop_sequence ASC`,
|
|
144
|
-
)
|
|
145
|
-
.all()
|
|
146
|
-
|
|
147
|
-
const sortedStopIds = sortStopIdsBySequence(stoptimes)
|
|
148
|
-
|
|
149
|
-
const deduplicatedStopIds = sortedStopIds.reduce((memo, stopId) => {
|
|
150
|
-
// Remove duplicated stop_ids in a row
|
|
151
|
-
if (last(memo) !== stopId) {
|
|
152
|
-
memo.push(stopId)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return memo
|
|
156
|
-
}, [])
|
|
157
|
-
|
|
158
|
-
// Remove last stop of route since boarding is not allowed
|
|
159
|
-
deduplicatedStopIds.pop()
|
|
160
|
-
|
|
161
|
-
// Fetch stop details
|
|
162
|
-
const stops = getStops({ stop_id: deduplicatedStopIds }, [
|
|
163
|
-
'stop_id',
|
|
164
|
-
'stop_name',
|
|
165
|
-
'stop_code',
|
|
166
|
-
'parent_station',
|
|
167
|
-
])
|
|
168
|
-
|
|
169
|
-
return deduplicatedStopIds.map((stopId) =>
|
|
170
|
-
stops.find((stop) => stop.stop_id === stopId),
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/*
|
|
175
|
-
* Generate HTML for transit departures widget.
|
|
176
|
-
*/
|
|
177
|
-
export function generateTransitDeparturesWidgetHtml(config) {
|
|
178
|
-
i18n.configure({
|
|
179
|
-
directory: join(dirname(fileURLToPath(import.meta.url)), '..', 'locales'),
|
|
180
|
-
defaultLocale: config.locale,
|
|
181
|
-
updateFiles: false,
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
const templateVars = {
|
|
185
|
-
__: i18n.__,
|
|
186
|
-
config,
|
|
187
|
-
}
|
|
188
|
-
return renderFile('widget', templateVars, config)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/*
|
|
192
|
-
* Generate JSON of routes and stops for transit departures widget.
|
|
193
|
-
*/
|
|
194
|
-
export function generateTransitDeparturesWidgetJson(config) {
|
|
195
|
-
const routes = getRoutes()
|
|
196
|
-
const stops = []
|
|
197
|
-
const filteredRoutes = []
|
|
198
|
-
const calendars = getCalendarsForDateRange(config)
|
|
199
|
-
|
|
200
|
-
for (const route of routes) {
|
|
201
|
-
route.route_full_name = formatRouteName(route)
|
|
202
|
-
|
|
203
|
-
const directions = getDirectionsForRoute(route, config)
|
|
204
|
-
|
|
205
|
-
// Filter out routes with no directions
|
|
206
|
-
if (directions.length === 0) {
|
|
207
|
-
config.logWarning(
|
|
208
|
-
`route_id ${route.route_id} has no directions - skipping`,
|
|
209
|
-
)
|
|
210
|
-
continue
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
for (const direction of directions) {
|
|
214
|
-
const directionStops = getStopsForDirection(route, direction, config)
|
|
215
|
-
stops.push(...directionStops)
|
|
216
|
-
direction.stopIds = directionStops.map((stop) => stop.stop_id)
|
|
217
|
-
|
|
218
|
-
const trips = getTrips(
|
|
219
|
-
{
|
|
220
|
-
route_id: route.route_id,
|
|
221
|
-
direction_id: direction.direction_id,
|
|
222
|
-
service_id: calendars.map((calendar) => calendar.service_id),
|
|
223
|
-
},
|
|
224
|
-
['trip_id'],
|
|
225
|
-
)
|
|
226
|
-
direction.tripIds = trips.map((trip) => trip.trip_id)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
route.directions = directions
|
|
230
|
-
filteredRoutes.push(route)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Sort routes twice to handle integers with alphabetical characters, such as ['14', '14L', '14X']
|
|
234
|
-
const sortedRoutes = sortBy(
|
|
235
|
-
sortBy(filteredRoutes, (route) => route.route_short_name?.toLowerCase()),
|
|
236
|
-
(route) => Number.parseInt(route.route_short_name, 10),
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
// Get Parent Station Stops
|
|
240
|
-
const parentStationIds = new Set(stops.map((stop) => stop.parent_station))
|
|
241
|
-
|
|
242
|
-
const parentStationStops = getStops(
|
|
243
|
-
{ stop_id: Array.from(parentStationIds) },
|
|
244
|
-
['stop_id', 'stop_name', 'stop_code', 'parent_station'],
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
stops.push(
|
|
248
|
-
...parentStationStops.map((stop) => {
|
|
249
|
-
stop.is_parent_station = true
|
|
250
|
-
return stop
|
|
251
|
-
}),
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
// Sort unique list of stops
|
|
255
|
-
const sortedStops = sortBy(uniqBy(stops, 'stop_id'), 'stop_name')
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
routes: removeNulls(sortedRoutes),
|
|
259
|
-
stops: removeNulls(sortedStops),
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/*
|
|
264
|
-
* Remove null values from array or object
|
|
265
|
-
*/
|
|
266
|
-
function removeNulls(data) {
|
|
267
|
-
if (Array.isArray(data)) {
|
|
268
|
-
return data
|
|
269
|
-
.map(removeNulls)
|
|
270
|
-
.filter((item) => item !== null && item !== undefined)
|
|
271
|
-
} else if (typeof data === 'object' && data !== null) {
|
|
272
|
-
return Object.entries(data).reduce((acc, [key, value]) => {
|
|
273
|
-
const cleanedValue = removeNulls(value)
|
|
274
|
-
if (cleanedValue !== null && cleanedValue !== undefined) {
|
|
275
|
-
acc[key] = cleanedValue
|
|
276
|
-
}
|
|
277
|
-
return acc
|
|
278
|
-
}, {})
|
|
279
|
-
} else {
|
|
280
|
-
return data
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/*
|
|
285
|
-
* Initialize configuration with defaults.
|
|
286
|
-
*/
|
|
287
|
-
export function setDefaultConfig(initialConfig) {
|
|
288
|
-
const defaults = {
|
|
289
|
-
beautify: false,
|
|
290
|
-
noHead: false,
|
|
291
|
-
refreshIntervalSeconds: 20,
|
|
292
|
-
skipImport: false,
|
|
293
|
-
timeFormat: '12hour',
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return Object.assign(defaults, initialConfig)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export function formatWhereClause(key, value) {
|
|
300
|
-
if (Array.isArray(value)) {
|
|
301
|
-
let whereClause = `${sqlString.escapeId(key)} IN (${value
|
|
302
|
-
.filter((v) => v !== null)
|
|
303
|
-
.map((v) => sqlString.escape(v))
|
|
304
|
-
.join(', ')})`
|
|
305
|
-
|
|
306
|
-
if (value.includes(null)) {
|
|
307
|
-
whereClause = `(${whereClause} OR ${sqlString.escapeId(key)} IS NULL)`
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return whereClause
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (value === null) {
|
|
314
|
-
return `${sqlString.escapeId(key)} IS NULL`
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return `${sqlString.escapeId(key)} = ${sqlString.escape(value)}`
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export function formatWhereClauses(query) {
|
|
321
|
-
if (Object.keys(query).length === 0) {
|
|
322
|
-
return ''
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const whereClauses = Object.entries(query).map(([key, value]) =>
|
|
326
|
-
formatWhereClause(key, value),
|
|
327
|
-
)
|
|
328
|
-
return `WHERE ${whereClauses.join(' AND ')}`
|
|
329
|
-
}
|
package/locales/en.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"Realtime Departures": "Realtime Departures",
|
|
3
|
-
"By Stop": "By Stop",
|
|
4
|
-
"By Route": "By Route",
|
|
5
|
-
"Search by stop name or stop code": "Search by stop name or stop code",
|
|
6
|
-
"all": "all",
|
|
7
|
-
"Invalid stop code": "Invalid stop code",
|
|
8
|
-
"Get Departures": "Get Departures",
|
|
9
|
-
"Choose a route": "Choose a route",
|
|
10
|
-
"Choose a direction": "Choose a direction",
|
|
11
|
-
"Choose a stop": "Choose a stop",
|
|
12
|
-
"Loading": "Loading",
|
|
13
|
-
"Unknown Stop": "Unknown Stop",
|
|
14
|
-
"As of": "As of",
|
|
15
|
-
"Stop Code": "Stop Code",
|
|
16
|
-
"min": "min",
|
|
17
|
-
"No upcoming departures": "No upcoming departures",
|
|
18
|
-
"Unable to fetch departures": "Unable to fetch departures",
|
|
19
|
-
"To {{{headsign}}}": "To {{{headsign}}}"
|
|
20
|
-
}
|
package/locales/pl.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"Realtime Departures": "Przyjazdy w czasie rzeczywistym",
|
|
3
|
-
"By Stop": "Wg przystanku",
|
|
4
|
-
"By Route": "Wg linii",
|
|
5
|
-
"Search by stop name or stop code": "Wyszukaj po nazwie przystanku lub jego identyfikatorze",
|
|
6
|
-
"all": "wszystkie",
|
|
7
|
-
"Invalid stop code": "Nieprawidłowy identyfikator przystanku",
|
|
8
|
-
"Get Departures": "Uzyskaj przyjazdy",
|
|
9
|
-
"Choose a route": "Wybierz linię",
|
|
10
|
-
"Choose a direction": "Wybierz kierunek",
|
|
11
|
-
"Choose a stop": "Wybierz przystanek",
|
|
12
|
-
"Loading": "Ładowanie",
|
|
13
|
-
"Unknown Stop": "Nieznany przystanek",
|
|
14
|
-
"As of": "Aktualizacja",
|
|
15
|
-
"Stop Code": "Przystanek",
|
|
16
|
-
"min": "min",
|
|
17
|
-
"No upcoming departures": "Brak nadchodzących przyjazdów",
|
|
18
|
-
"Unable to fetch departures": "Nie można było pobrać przyjazdów",
|
|
19
|
-
"To {{{headsign}}}": "Do {{{headsign}}}"
|
|
20
|
-
}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/* General Styles */
|
|
2
|
-
|
|
3
|
-
body {
|
|
4
|
-
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
5
|
-
font-size: 14px;
|
|
6
|
-
line-height: 1.4;
|
|
7
|
-
color: #333333;
|
|
8
|
-
background-color: #ffffff;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.transit-departures-widget {
|
|
12
|
-
max-width: 600px;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.transit-departures-widget .hidden-form {
|
|
16
|
-
display: none;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.transit-departures-widget .stop-code-invalid {
|
|
20
|
-
display: none;
|
|
21
|
-
color: #821515;
|
|
22
|
-
padding-bottom: 10px;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.transit-departures-widget .departure-results {
|
|
26
|
-
display: none;
|
|
27
|
-
margin-top: 25px;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.transit-departures-widget .departure-results-none {
|
|
31
|
-
display: none;
|
|
32
|
-
margin-top: 10px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.transit-departures-widget .departure-results-error {
|
|
36
|
-
display: none;
|
|
37
|
-
margin-top: 10px;
|
|
38
|
-
color: #821515;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.transit-departures-widget .departure-results-stop,
|
|
42
|
-
.transit-departures-widget .departure-results-stop-unknown {
|
|
43
|
-
font-size: 24px;
|
|
44
|
-
line-height: 1;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.transit-departures-widget .departure-result {
|
|
48
|
-
background-color: #eee;
|
|
49
|
-
display: flex;
|
|
50
|
-
align-items: center;
|
|
51
|
-
justify-content: space-between;
|
|
52
|
-
margin-top: 8px;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.transit-departures-widget .departure-results-header {
|
|
56
|
-
display: flex;
|
|
57
|
-
justify-content: space-between;
|
|
58
|
-
align-items: flex-end;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.transit-departures-widget .departure-results-fetchtime {
|
|
62
|
-
flex-shrink: 0;
|
|
63
|
-
font-size: 12px;
|
|
64
|
-
margin-left: 8px;
|
|
65
|
-
padding: 0 0 0 15px;
|
|
66
|
-
border: none;
|
|
67
|
-
background-color: transparent;
|
|
68
|
-
background-image: url('../img/refresh.svg');
|
|
69
|
-
background-repeat: no-repeat;
|
|
70
|
-
background-size: 12px 12px;
|
|
71
|
-
background-position-y: 1px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.transit-departures-widget .departure-results-fetchtime:hover {
|
|
75
|
-
text-decoration: underline;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.transit-departures-widget .departure-result-route-name {
|
|
79
|
-
display: flex;
|
|
80
|
-
align-items: center;
|
|
81
|
-
line-height: 1;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.transit-departures-widget .departure-result-route-direction {
|
|
85
|
-
font-size: 22px;
|
|
86
|
-
margin-top: 2px;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.transit-departures-widget .departure-result-route-circle {
|
|
90
|
-
margin: 5px 9px 5px;
|
|
91
|
-
width: 30px;
|
|
92
|
-
height: 30px;
|
|
93
|
-
border-radius: 50%;
|
|
94
|
-
line-height: 30px;
|
|
95
|
-
text-align: center;
|
|
96
|
-
flex-shrink: 0;
|
|
97
|
-
overflow: hidden;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.transit-departures-widget .departure-result-times {
|
|
101
|
-
display: flex;
|
|
102
|
-
align-items: stretch;
|
|
103
|
-
flex-shrink: 0;
|
|
104
|
-
align-self: stretch;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.transit-departures-widget .departure-result-time-container {
|
|
108
|
-
padding: 0 5px;
|
|
109
|
-
width: 76px;
|
|
110
|
-
border-left: 1px solid #ddd;
|
|
111
|
-
align-self: stretch;
|
|
112
|
-
display: flex;
|
|
113
|
-
align-items: center;
|
|
114
|
-
justify-content: center;
|
|
115
|
-
flex-shrink: 0;
|
|
116
|
-
flex-grow: 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.transit-departures-widget .departure-result-time {
|
|
120
|
-
font-size: 24px;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.transit-departures-widget .departure-result-time-label {
|
|
124
|
-
font-size: 14px;
|
|
125
|
-
padding-left: 2px;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.transit-departures-widget .loader,
|
|
129
|
-
.transit-departures-widget .loader:after {
|
|
130
|
-
border-radius: 50%;
|
|
131
|
-
width: 10em;
|
|
132
|
-
height: 10em;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.transit-departures-widget .loader {
|
|
136
|
-
margin: 20px auto;
|
|
137
|
-
font-size: 10px;
|
|
138
|
-
position: relative;
|
|
139
|
-
text-indent: -9999em;
|
|
140
|
-
border-top: 1.1em solid rgba(79, 79, 79, 0.2);
|
|
141
|
-
border-right: 1.1em solid rgba(79, 79, 79, 0.2);
|
|
142
|
-
border-bottom: 1.1em solid rgba(79, 79, 79, 0.2);
|
|
143
|
-
border-left: 1.1em solid #4f4f4f;
|
|
144
|
-
-webkit-transform: translateZ(0);
|
|
145
|
-
-ms-transform: translateZ(0);
|
|
146
|
-
transform: translateZ(0);
|
|
147
|
-
-webkit-animation: transitDeparturesWidgetLoader 1.1s infinite linear;
|
|
148
|
-
animation: transitDeparturesWidgetLoader 1.1s infinite linear;
|
|
149
|
-
display: none;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@-webkit-keyframes transitDeparturesWidgetLoader {
|
|
153
|
-
0% {
|
|
154
|
-
-webkit-transform: rotate(0deg);
|
|
155
|
-
transform: rotate(0deg);
|
|
156
|
-
}
|
|
157
|
-
100% {
|
|
158
|
-
-webkit-transform: rotate(360deg);
|
|
159
|
-
transform: rotate(360deg);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
@keyframes transitDeparturesWidgetLoader {
|
|
164
|
-
0% {
|
|
165
|
-
-webkit-transform: rotate(0deg);
|
|
166
|
-
transform: rotate(0deg);
|
|
167
|
-
}
|
|
168
|
-
100% {
|
|
169
|
-
-webkit-transform: rotate(360deg);
|
|
170
|
-
transform: rotate(360deg);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.autocomplete {
|
|
175
|
-
background: white;
|
|
176
|
-
z-index: 1000;
|
|
177
|
-
overflow: auto;
|
|
178
|
-
box-sizing: border-box;
|
|
179
|
-
border: 1px solid rgba(0, 0, 0, 0.125);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.autocomplete * {
|
|
183
|
-
font: inherit;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.autocomplete > div {
|
|
187
|
-
padding: 4px 4px;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.autocomplete .group {
|
|
191
|
-
background: #eee;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.autocomplete > div:hover:not(.group),
|
|
195
|
-
.autocomplete > div.selected {
|
|
196
|
-
background: #007bff;
|
|
197
|
-
color: #fff;
|
|
198
|
-
font-weight: bold;
|
|
199
|
-
cursor: pointer;
|
|
200
|
-
}
|
package/public/img/refresh.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve"><path d="M226.74,199.28c-0.76,0.48-1.68,0.49-2.46,0.04l-16.82-9.7l-1.08,1.29c-19.26,23.11-47.55,36.37-77.61,36.37 c-48.57,0-90.32-34.6-99.26-82.27c-0.43-2.31,0.18-4.68,1.69-6.5c1.53-1.85,3.78-2.91,6.18-2.91H50.9c3.77,0,6.98,2.59,7.81,6.3 c3.49,15.69,12.32,29.94,24.87,40.12c12.73,10.33,28.78,16.02,45.21,16.02c19.4,0,37.57-7.62,51.17-21.45l1.81-1.84l-15.56-8.98 c-0.78-0.45-1.22-1.25-1.19-2.15c0.03-0.9,0.54-1.67,1.35-2.05l53.59-25.73c0.71-0.35,1.52-0.32,2.21,0.08 c0.69,0.4,1.12,1.08,1.18,1.88l4.51,59.27C227.92,197.99,227.5,198.81,226.74,199.28z"></path><path d="M226.03,119.33c-1.53,1.85-3.78,2.91-6.18,2.91h-13.51c-3.77,0-6.98-2.59-7.81-6.3c-3.49-15.69-12.32-29.94-24.87-40.12 c-12.73-10.33-28.78-16.02-45.21-16.02c-19.4,0-37.57,7.62-51.17,21.45l-1.81,1.84l15.56,8.98c0.78,0.45,1.22,1.25,1.19,2.15 c-0.03,0.9-0.54,1.67-1.35,2.05L37.29,122c-0.71,0.35-1.52,0.32-2.21-0.08c-0.69-0.4-1.12-1.08-1.18-1.88l-4.51-59.27 c-0.07-0.9,0.35-1.72,1.11-2.2s1.68-0.49,2.46-0.04l16.82,9.7l1.08-1.29c19.26-23.11,47.55-36.37,77.61-36.37 c48.57,0,90.32,34.6,99.26,82.27C228.15,115.14,227.54,117.51,226.03,119.33z"></path></svg>
|