Skip to content

Commit f9a6b35

Browse files
authored
Merge pull request #83 from danielhep/new_table
New Stop Schedule Table
2 parents ac7c122 + 381915d commit f9a6b35

File tree

11 files changed

+635
-467
lines changed

11 files changed

+635
-467
lines changed

Diff for: package.json

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"vue-element-loading": "^1.1.5",
3232
"vue-input-autowidth": "^1.0.10",
3333
"vue-multipane": "^0.9.5",
34+
"vue-select": "^3.10.3",
3435
"vue-multiselect": "^2.1.6",
3536
"vue-router": "^3.3.4",
3637
"vue-slider-component": "^3.1.1",
@@ -56,7 +57,9 @@
5657
"eslint-plugin-vue": "^6.2.2",
5758
"graphql-tag": "^2.10.3",
5859
"lint-staged": "^10.0.7",
60+
"node-sass": "^4.14.1",
5961
"postcss": "^7.0.26",
62+
"sass-loader": "^8.0.2",
6063
"tailwindcss": "^1.4.4",
6164
"vue-cli-plugin-apollo": "^0.21.3",
6265
"vue-template-compiler": "^2.6.10"

Diff for: postcss.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const purgecss = postcssPurgecss({
1818
whitelistPatterns: [
1919
/-(leave|enter|appear)(|-(to|from|active))$/, /^(?!(|.*?:)cursor-move).+-move$/, /^router-link(|-exact)-active$/,
2020
/.*multiselect.*/,
21-
/.*vue-slider.*/
21+
/.*vue-slider.*/,
22+
/vs__.*/
2223
]
2324
})
2425

Diff for: src/assets/styles/index.css renamed to src/assets/styles/index.scss

+3-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
@tailwind base;
22
@tailwind components;
33

4+
$vs-border-color: #4B5354;
5+
46
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
7+
@import "vue-select/src/scss/vue-select.scss";
58

69
html {
710
line-height: 1;
@@ -34,28 +37,5 @@ input::-webkit-inner-spin-button {
3437
margin: 0;
3538
}
3639

37-
.vue-slider-process {
38-
@apply bg-purple-700 !important;
39-
}
40-
41-
.multiselect__tag {
42-
@apply bg-purple-700 !important;
43-
}
44-
45-
.multiselect__tag-icon::after {
46-
@apply text-white !important;
47-
}
48-
49-
.multiselect__tag-icon:hover {
50-
@apply bg-purple-900 !important;
51-
}
52-
53-
.multiselect__tags {
54-
@apply bg-transparent !important;
55-
}
56-
57-
.multiselect__option--highlight {
58-
@apply bg-purple-700 font-bold !important;
59-
}
6040

6141
@tailwind utilities;

Diff for: src/components/StopTimesTable.vue

+50-207
Original file line numberDiff line numberDiff line change
@@ -1,223 +1,66 @@
11
<template>
2-
<div class="w-full flex flex-row">
3-
<vue-element-loading
4-
background-color="rgba(0, 0, 0, .6)"
5-
:active="!stopSchedule.length"
6-
color="white"
7-
spinner="bar-fade-scale"
8-
/>
9-
<table class="w-full self-grow">
10-
<thead class="main-table-header">
11-
<tr>
12-
<th class="p-0">
13-
<div
14-
class="px-4 text-left text-lg bg-purple-900 h-16 border-b border-white flex flex-col justify-center"
15-
>
16-
<p>Time</p>
17-
</div>
18-
</th>
19-
<th class="p-0">
20-
<div
21-
class="px-4 text-left text-lg bg-purple-900 h-16 border-b border-white flex flex-col justify-center"
22-
>
23-
<p>Gap</p>
24-
</div>
25-
</th>
26-
<th class="p-0">
27-
<div
28-
class="px-4 text-left text-lg bg-purple-900 h-16 border-b border-white flex flex-col justify-center"
29-
>
30-
<p>Route</p>
31-
</div>
32-
</th>
33-
<th class="p-0">
34-
<div
35-
class="px-4 text-left text-lg bg-purple-900 h-16 border-b border-white flex flex-col justify-center"
36-
>
37-
<p>Headsign</p>
38-
</div>
39-
</th>
40-
<th class="p-0">
41-
<div
42-
class="text-left text-lg bg-purple-900 h-16 border-b border-white flex flex-col justify-center"
43-
>
44-
<p>
45-
<font-awesome-icon
46-
v-if="startInd || endInd"
47-
@click="clear()"
48-
class="m-auto cursor-pointer block p-1"
49-
size="2x"
50-
icon="times"
51-
fixed-width
52-
/>
53-
<font-awesome-icon
54-
v-else
55-
@click="selectAll()"
56-
class="m-auto cursor-pointer block p-1"
57-
size="2x"
58-
icon="check-double"
59-
fixed-width
60-
/>
61-
</p>
62-
</div>
63-
</th>
64-
</tr>
65-
</thead>
66-
<tbody>
67-
<tr
68-
:class="{'bg-gray-800': time.is_even_hour, 'bg-gray-850': !time.is_even_hour}"
69-
class="border-solid border-gray-700 border-b py-2"
70-
v-for="(time, i) in stopSchedule"
71-
:key="i"
72-
:ref="i"
73-
@click="selectRow(i)"
2+
<table class="w-full">
3+
<thead>
4+
<tr>
5+
<th
6+
v-for="column in columns"
7+
:key="column.name"
8+
class="py-3 text-left pl-1 border border-gray-700"
749
>
75-
<td class="px-4">
76-
<div>
77-
<p>{{time.departure_time.toFormat('hh:mm')}}</p>
78-
</div>
79-
</td>
80-
<td class="px-3 whitespace-no-wrap px-2">
81-
<div class="flex flex-row p-0 h-full content-center">
82-
<p class="flex-grow" v-if="time.time_since_last.invalid">-</p>
83-
<p class="flex-grow" v-else>{{time.time_since_last.as('minutes').toFixed(1)}}</p>
84-
<div class="self-end inline">
85-
<font-awesome-icon
86-
:class="{'opacity-0': !isFrequent(time.time_since_last)}"
87-
class="ml-2 inline"
88-
icon="check-square"
89-
/>
90-
</div>
91-
</div>
92-
</td>
93-
<td class="px-3" @click="$emit('selectRoute', time.trip.route)">
94-
<div>
95-
<p>{{time.trip.route.route_short_name}}</p>
96-
</div>
97-
</td>
98-
<td class="px-3 w-full">
99-
<p>{{time.trip.trip_headsign}}</p>
100-
</td>
101-
<td class="relative">
102-
<div
103-
class="absolute h-full top-0"
104-
v-if="i === 0"
105-
style="left: 50%; margin-left: -15px;"
106-
>
107-
<svg style="stroke: white; width: 30px" id="i-bar" />
108-
</div>
109-
</td>
110-
</tr>
111-
</tbody>
112-
</table>
113-
</div>
10+
{{ column.label }}
11+
</th>
12+
</tr>
13+
</thead>
14+
<tbody>
15+
<tr
16+
v-for="(row, i) in rows"
17+
:key="i"
18+
>
19+
<td class=" table-cell">
20+
{{ row.trip.route.route_short_name }}
21+
</td>
22+
<td class=" table-cell">
23+
{{ durationToTime(row.departure_time) }} <span
24+
v-if="row.time_since_last"
25+
class="text-gray-400 text-sm ml-1"
26+
> {{ durationToMin(row.time_since_last) }} minutes </span>
27+
</td>
28+
<!-- TODO: Add next service day indicator if over 24 hours -->
29+
<td class=" table-cell">
30+
{{ row.trip.trip_headsign }}
31+
</td>
32+
<td class=" table-cell">
33+
<font-awesome-icon
34+
v-if="row.time_since_last"
35+
:icon="durationToMin(row.time_since_last) <= fsThreshold ? 'check-circle' : 'times'"
36+
:class="durationToMin(row.time_since_last) <= fsThreshold ? 'text-green-400' : ''"
37+
/>
38+
</td>
39+
</tr>
40+
</tbody>
41+
</table>
11442
</template>
11543

11644
<script>
117-
import * as d3 from 'd3'
118-
45+
import { Duration } from 'luxon'
11946
export default {
120-
props: ['stopSchedule', 'fsThreshold', 'date'],
121-
data: function () {
122-
return {
123-
startInd: null,
124-
endInd: null,
125-
selectEnd: false
126-
}
127-
},
47+
props: ['columns', 'rows', 'fsThreshold'],
12848
methods: {
129-
isFrequent (dur) {
130-
return dur.as('minutes') <= this.fsThreshold
131-
},
132-
clear () {
133-
this.startInd = null
134-
this.endInd = null
135-
this.$emit('selectedItems', [])
136-
this.redraw()
137-
},
138-
selectAll () {
139-
this.startInd = 0
140-
this.endInd = this.stopSchedule.length - 1
141-
this.$emit('selectedItems', this.selectedItems)
142-
this.redraw()
143-
},
144-
redraw () {
145-
// Set height of SVG to height of table
146-
const tableHeight = d3.select('tbody').node()
147-
.getBoundingClientRect().height
148-
d3.select('#i-bar')
149-
.style('height', `${tableHeight}px`)
150-
151-
// filter out blank ones (because endInd is null to start)
152-
const data = [this.startInd, this.endInd].filter(d => d !== null)
153-
// get the ref for each indici
154-
const dataRefs = data.map((d) => this.$refs[d][0])
155-
156-
const headerHeight = d3.select('thead').node().getBoundingClientRect().height
157-
// generate horizontal bars
158-
d3
159-
.select('#i-bar')
160-
.selectAll('line.mark')
161-
.data(dataRefs)
162-
.join('line')
163-
.attr('class', 'mark')
164-
.attr('x1', 5)
165-
.attr('y1', (d) => d.offsetTop + (d.getBoundingClientRect().height / 2) - headerHeight)
166-
.attr('x2', 25)
167-
.attr('y2', (d) => d.offsetTop + (d.getBoundingClientRect().height / 2) - headerHeight)
168-
if (dataRefs.length === 2) {
169-
d3.select('#i-bar')
170-
.selectAll('line#connector')
171-
.data([{ start: dataRefs[0], end: dataRefs[1] }])
172-
.join('line')
173-
.attr('id', 'connector')
174-
.attr('x1', 15)
175-
.attr('y1', (d) => d.start.offsetTop + (d.start.getBoundingClientRect().height / 2) - headerHeight)
176-
.attr('x2', 15)
177-
.attr('y2', (d) => d.end.offsetTop + (d.end.getBoundingClientRect().height / 2) - headerHeight)
178-
} else if (dataRefs.length <= 1) {
179-
d3.select('#i-bar')
180-
.selectAll('line#connector')
181-
.remove()
182-
}
183-
},
184-
selectRow (i) {
185-
// if no end point has been selected
186-
if (!this.selectEnd) {
187-
this.startInd = i
188-
this.endInd = null
189-
} else if (i < this.startInd) { // we are selecting end point, but end point < start point
190-
this.endInd = this.startInd
191-
this.startInd = i
192-
this.$emit('selectedItems', this.selectedItems)
49+
durationToTime: (dur) => Duration.fromISO(dur).toFormat('hh:mm'),
50+
durationToMin: (dur) => Duration.fromISO(dur).as('minutes'),
51+
freqIconFromDur (dur) {
52+
if (dur <= this.fsThreshold) {
53+
return 'check-circle'
19354
} else {
194-
this.endInd = i
195-
this.$emit('selectedItems', this.selectedItems)
55+
return 'times'
19656
}
197-
this.selectEnd = !this.selectEnd
198-
this.redraw()
199-
}
200-
},
201-
computed: {
202-
selectedItems () {
203-
return this.stopSchedule.slice(this.startInd, this.endInd + 1)
20457
}
20558
}
20659
}
20760
</script>
20861

209-
<style lang="postcss">
210-
tbody {
211-
@apply items-center justify-between overflow-y-scroll w-full;
212-
height: 50vh;
213-
}
214-
215-
tbody tr td,
216-
tbody tr {
217-
/* height: 1px !important; */
218-
height: 1.5rem;
219-
}
220-
thead th {
221-
@apply sticky top-0 z-10;
62+
<style scoped>
63+
.table-cell {
64+
@apply border py-2 pl-1 border-gray-700;
22265
}
22366
</style>

Diff for: src/main.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import App from './App.vue'
33
import router from './router'
44
import store from './store'
55

6-
import './assets/styles/index.css'
6+
import './assets/styles/index.scss'
77
import 'vue-slider-component/theme/default.css'
88

99
import { library } from '@fortawesome/fontawesome-svg-core'
10-
import { faMapMarkerAlt, faRoute, faClock, faMapMarker, faMap, faTimes, faCaretLeft, faCheckSquare, faCheckDouble, faColumns, faWindowMaximize, faWindowMinimize } from '@fortawesome/free-solid-svg-icons'
10+
import { faMapMarkerAlt, faRoute, faClock, faMapMarker, faMap, faTimes, faCaretLeft, faCheckSquare, faCheckDouble, faColumns, faWindowMaximize, faWindowMinimize, faCheckCircle } from '@fortawesome/free-solid-svg-icons'
1111
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
1212
import { gqlProvider } from './vue-apollo'
1313
import { sync } from 'vuex-router-sync'
@@ -25,7 +25,7 @@ Vue.component('vue-element-loading', VueElementLoading)
2525

2626
Vue.use(VueInputAutowidth)
2727

28-
library.add(faCheckDouble, faMapMarkerAlt, faRoute, faClock, faMapMarker, faMap, faColumns, faTimes, faCaretLeft, faCheckSquare, faWindowMaximize, faWindowMinimize)
28+
library.add(faCheckDouble, faMapMarkerAlt, faRoute, faClock, faMapMarker, faMap, faColumns, faCheckCircle, faTimes, faCaretLeft, faCheckSquare, faWindowMaximize, faWindowMinimize)
2929

3030
Vue.config.productionTip = false
3131

Diff for: src/router/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const routes = [
3030
path: 'stop',
3131
name: 'Stop Schedule',
3232
icon: 'map-marker-alt',
33-
component: () => import('../views/StopExplorer.vue')
33+
component: () => import('../views/Stop.vue')
3434
}
3535
]
3636
}

Diff for: src/views/Map.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default {
7777
shapes
7878
}
7979
}`,
80-
update (data) { console.log(data); return data.routes_by_id[0].shapes },
80+
update (data) { return data.routes_by_id[0].shapes },
8181
variables () {
8282
return {
8383
feedIndex: this.feedIndex,

0 commit comments

Comments
 (0)