diff --git a/frontend/src/Sidebar/ResultsContainer.jsx b/frontend/src/Sidebar/ResultsContainer.jsx new file mode 100644 index 0000000..1e97309 --- /dev/null +++ b/frontend/src/Sidebar/ResultsContainer.jsx @@ -0,0 +1,55 @@ +import { useContext, useState, useEffect } from 'react' +import { DataContext } from '../Layout' +import BigButton from './BigButton' + +export default function ResultsContainer(){ + const [ isFetchingData, setIsFetchingData ] = useState(false) + const [ progress, setProgress ] = useState(-1) + const { data } = useContext(DataContext) + useEffect(()=>{ + data.queue.on('active',()=>{ + setProgress( 100 * data.queryCountFinished / data.queryCount ) + }) + },[]) + return ( +
+ {!isFetchingData && <> + {data.queryCount} travel time{data.queryCount == 1 ? '' : 's'} to be queried + } + {data.queryCount > 0 && !isFetchingData && !data.allQueriesHaveData && + { + setIsFetchingData(true) + data.fetchAllResults().then( () => { + setIsFetchingData(false) + } ) + }}>Submit Query + } + {isFetchingData && <> +

Finished fetching {data.queryCountFinished}/{data.queryCount} results

+ + } + {data.allQueriesHaveData && <> + r.resultsRecord('json'))))}`} + > + Download results as JSON + + r.resultsRecord('csv')).join('\n'))}`} + > + Download results as CSV + + + } +
+ ) +} + +function ProgressBar({percentDone}){ + return ( + + + + + ) +} \ No newline at end of file diff --git a/frontend/src/Sidebar/index.jsx b/frontend/src/Sidebar/index.jsx index 642a6d4..7e7ab91 100644 --- a/frontend/src/Sidebar/index.jsx +++ b/frontend/src/Sidebar/index.jsx @@ -1,8 +1,9 @@ -import { useContext, useState } from 'react' +import { useContext } from 'react' import { DataContext } from '../Layout' import FactorContainer from './FactorContainer' import BigButton from './BigButton' +import ResultsContainer from './ResultsContainer' import FactorList from './FactorList' import { restoreStateFromFile } from './restoreStateFromFile.js' import './sidebar.css' @@ -29,51 +30,7 @@ export default function SidebarContent(){
×
=
- - - ) -} - -function Results(){ - const [ results, setResults ] = useState(undefined) - const [ isFetchingData, setIsFetchingData ] = useState(false) - const { data } = useContext(DataContext) - const numResults = data.travelTimeQueries.length - return ( -
- {numResults} travel time{numResults == 1 ? '' : 's'} to estimate currently - {numResults > 0 && ! isFetchingData && - { - setResults(undefined) - setIsFetchingData(true) - data.fetchAllResults().then( () => { - setIsFetchingData(false) - setResults(data.travelTimeQueries) - } ) - }}> - Submit Query - - } - {isFetchingData && -

Please wait while your request is being processed

- } - {results && <> - r.resultsRecord('json'))))}`} - > - - Download results as JSON - - - r.resultsRecord('csv')).join('\n'))}`} - > - - Download results as CSV - - - - } +
) } diff --git a/frontend/src/corridor.js b/frontend/src/corridor.js index 47ba0f8..431efe4 100644 --- a/frontend/src/corridor.js +++ b/frontend/src/corridor.js @@ -29,6 +29,7 @@ export class Corridor extends Factor { .then( () => { // notify the layout that the path is ready to be rendered logActivity('shortest path returned') + this.hasUpdated() } ) } get intersections(){ return [...this.#intersections.values()] } diff --git a/frontend/src/dateRange.js b/frontend/src/dateRange.js index bff9576..96b90ed 100644 --- a/frontend/src/dateRange.js +++ b/frontend/src/dateRange.js @@ -26,10 +26,12 @@ export class DateRange extends Factor { } setStartDate(inputDate){ this.#startDate = inputDate + this.hasUpdated() return this.#startDate } setEndDate(inputDate){ this.#endDate = inputDate + this.hasUpdated() return this.#endDate } static dateFormatted(datetime){ diff --git a/frontend/src/days.js b/frontend/src/days.js index 933f921..b798cc2 100644 --- a/frontend/src/days.js +++ b/frontend/src/days.js @@ -30,10 +30,15 @@ export class Days extends Factor { addDay(number){ if( daylist.map(d=>d.iso).includes(parseInt(number)) ){ this.#days.add(parseInt(number)) + this.hasUpdated() } } removeDay(number){ - this.#days.delete(parseInt(number)) + let dayNum = parseInt(number) + if(this.#days.has(dayNum)){ + this.#days.delete(dayNum) + this.hasUpdated() + } } hasDay(number){ return this.#days.has(parseInt(number)) diff --git a/frontend/src/factor.js b/frontend/src/factor.js index c9d76d9..e46b1d2 100644 --- a/frontend/src/factor.js +++ b/frontend/src/factor.js @@ -27,4 +27,8 @@ export class Factor { render(){ // this will be overwritten but must be implemented return <> } + hasUpdated(){ + // should be called when a factor has been updated + this.#dataContext.updateQueries() + } } \ No newline at end of file diff --git a/frontend/src/holidayOption.js b/frontend/src/holidayOption.js index f9c9d35..8a4773d 100644 --- a/frontend/src/holidayOption.js +++ b/frontend/src/holidayOption.js @@ -3,7 +3,7 @@ import { Factor } from './factor.js' export class HolidayOption extends Factor { #includeHolidays #dataContext - constructor(dataContext,includeHolidays){ + constructor(dataContext, includeHolidays){ super(dataContext) // store this here too to actually access the holiday data this.#dataContext = dataContext diff --git a/frontend/src/spatialData.js b/frontend/src/spatialData.js index 93d1650..1533af4 100644 --- a/frontend/src/spatialData.js +++ b/frontend/src/spatialData.js @@ -53,6 +53,7 @@ export class SpatialData { let days = new Days(this) this.#factors.push(days) days.activate() + this.updateQueries() // this is the only factor that starts out complete return days } get segments(){ @@ -63,6 +64,7 @@ export class SpatialData { } dropFactor(factor){ this.#factors = this.#factors.filter(f => f != factor) + this.updateQueries() } deactivateOtherFactors(factor){ this.#factors.forEach( f => { @@ -70,20 +72,37 @@ export class SpatialData { } ) } includeHolidays(){ - this.holidayOptions.forEach(f => this.dropFactor(f)) - this.#factors.push(new HolidayOption(this,true)) + this.holidayOptions.forEach( factor => { + if(!factor.holidaysIncluded) this.dropFactor(factor) + } ) + if(this.holidayOptions.length == 0){ + this.#factors.push(new HolidayOption(this,true)) + } + this.updateQueries() } excludeHolidays(){ - this.holidayOptions.forEach(f => this.dropFactor(f)) - this.#factors.push(new HolidayOption(this,false)) + this.holidayOptions.forEach( factor => { + if(factor.holidaysIncluded) this.dropFactor(factor) + } ) + if(this.holidayOptions.length == 0){ + this.#factors.push(new HolidayOption(this,false)) + } + this.updateQueries() } includeAndExcludeHolidays(){ - this.holidayOptions.forEach(f => this.dropFactor(f)) - this.#factors.push(new HolidayOption(this,true)) - this.#factors.push(new HolidayOption(this,false)) + console.assert(this.holidayOptions.length == 1) + // add a factor for whatever the opposite of the existing one is + this.#factors.push( + new HolidayOption( + this, + ! this.holidayOptions[0].holidaysIncluded + ) + ) + this.updateQueries() } - get travelTimeQueries(){ - // is the crossproduct of all complete/valid factors + updateQueries(){ + // this should be run any time the inputs change to keep the list fresh + // queries are the crossproduct of all complete/valid factors const crossProduct = [] this.corridors.filter(c=>c.isComplete).forEach( corridor => { this.timeRanges.filter(tr=>tr.isComplete).forEach( timeRange => { @@ -104,17 +123,19 @@ export class SpatialData { } ) } ) }) - // add new travelTimeRequests + // add any new travelTimeRequests crossProduct.forEach( TTQ => { if( ! this.#queries.has(TTQ.URI) ){ this.#queries.set(TTQ.URI,TTQ) } } ) - // remove old/modified travelTimeRequests + // remove any old/modified travelTimeRequests let currentURIs = new Set(crossProduct.map(TTI=>TTI.URI)) let currentKeys = [...this.#queries.keys()] currentKeys.filter( key => ! currentURIs.has(key) ) .forEach( key => this.#queries.delete(key) ) + } + get travelTimeQueries(){ return [...this.#queries.values()].sort((a,b)=> a.URI < b.URI ? -1 : 1) } fetchAllResults(){ @@ -124,4 +145,15 @@ export class SpatialData { .map( TTQ => () => TTQ.fetchData() ) ) } + get queue(){ return this.#queue } + get allQueriesHaveData(){ + return ( // some queries, all with data + this.queryCount > 0 + && this.queryCount == this.queryCountFinished + ) + } + get queryCount(){ return this.#queries.size } + get queryCountFinished(){ + return [...this.#queries.values()].filter(q=>q.hasData).length + } } diff --git a/frontend/src/timeRange.js b/frontend/src/timeRange.js index ba0a628..22ef535 100644 --- a/frontend/src/timeRange.js +++ b/frontend/src/timeRange.js @@ -45,10 +45,12 @@ export class TimeRange extends Factor { } setStartTime(input){ this.#startTime = TimeRange.parseTime(input) + this.hasUpdated() return this.#startTime } setEndTime(input){ this.#endTime = TimeRange.parseTime(input) + this.hasUpdated() return this.#endTime } static timeFormatted(datetime){