diff --git a/app/component/StopPage.js b/app/component/StopPage.js new file mode 100644 index 0000000000..6cf9a736a8 --- /dev/null +++ b/app/component/StopPage.js @@ -0,0 +1,66 @@ +import React from 'react'; +import Relay, { Route } from 'react-relay/classic'; +import connectToStores from 'fluxible-addons-react/connectToStores'; +import withState from 'recompose/withState'; +import moment from 'moment'; +import StopPageContentContainer from './StopPageContentContainer'; + +const initialDate = moment().format('YYYYMMDD'); + +class StopPageContainerRoute extends Route { + static queries = { + stop: (RelayComponent, variables) => Relay.QL` + query { + stop(id: $stopId) { + ${RelayComponent.getFragment('stop', variables)} + } + } + `, + }; + static paramDefinitions = { + startTime: { required: true }, + timeRange: { required: true }, + numberOfDepartures: { required: true }, + }; + static routeName = 'StopPageContainerRoute'; +} + +const StopPageRootContainer = routeProps => ( + + done ? ( + + ) : ( + undefined + ) + } + /> +); + +const StopPageContainerWithState = withState('date', 'setDate', initialDate)( + StopPageRootContainer, +); + +export default connectToStores( + StopPageContainerWithState, + ['TimeStore', 'FavouriteStopsStore'], + ({ getStore }) => ({ + startTime: getStore('TimeStore') + .getCurrentTime() + .unix(), + timeRange: 3600 * 12, + numberOfDepartures: 100, + }), +); diff --git a/app/component/StopPageContentContainer.js b/app/component/StopPageContentContainer.js index a82e4dcd39..eda123a204 100644 --- a/app/component/StopPageContentContainer.js +++ b/app/component/StopPageContentContainer.js @@ -1,85 +1,159 @@ import PropTypes from 'prop-types'; import React from 'react'; import Relay from 'react-relay/classic'; +import some from 'lodash/some'; +import mapProps from 'recompose/mapProps'; import connectToStores from 'fluxible-addons-react/connectToStores'; +import StopPageTabContainer from './StopPageTabContainer'; import DepartureListHeader from './DepartureListHeader'; import DepartureListContainer from './DepartureListContainer'; +import TimetableContainer from './TimetableContainer'; import Error404 from './404'; +import withBreakpoint from '../util/withBreakpoint'; -class StopPageContent extends React.Component { +class StopPageContentOptions extends React.Component { static propTypes = { - params: PropTypes.oneOfType([ - PropTypes.shape({ stopId: PropTypes.string.isRequired }).isRequired, - PropTypes.shape({ terminalId: PropTypes.string.isRequired }).isRequired, - ]).isRequired, - stop: PropTypes.shape({ - stoptimes: PropTypes.array, + printUrl: PropTypes.string, + departureProps: PropTypes.shape({ + stop: PropTypes.shape({ + stoptimes: PropTypes.array, + }).isRequired, }).isRequired, relay: PropTypes.shape({ variables: PropTypes.shape({ - startTime: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, }).isRequired, setVariables: PropTypes.func.isRequired, }).isRequired, + initialDate: PropTypes.string.isRequired, + setDate: PropTypes.func.isRequired, currentTime: PropTypes.number.isRequired, }; + static defaultProps = { + printUrl: null, + }; + + constructor(props) { + super(props); + this.state = { + showTab: 'right-now', // Show right-now as default + }; + } + componentWillReceiveProps({ relay, currentTime }) { const currUnix = this.props.currentTime; if (currUnix !== currentTime) { - relay.setVariables({ startTime: String(currUnix) }); + relay.setVariables({ startTime: currUnix }); } } - render() { - if (!this.props.stop) { - return ; - } + onDateChange = ({ target }) => { + this.props.setDate(target.value); + }; + + setTab = val => { + this.setState({ + showTab: val, + }); + }; + render() { + // Currently shows only next departures, add Timetables return ( - - -
- +
+
+ +
+ {this.state.showTab === 'right-now' && }
- + {this.state.showTab === 'right-now' && ( +
+ +
+ )} + {this.state.showTab === 'timetable' && ( + + )} +
); } } +const DepartureListContainerWithProps = mapProps(props => ({ + stoptimes: props.stop.stoptimes, + key: 'departures', + className: 'stop-page momentum-scroll', + routeLinks: true, + infiniteScroll: true, + isTerminal: !props.params.stopId, + rowClasses: 'padding-normal border-bottom', + currentTime: props.relay.variables.startTime, + showPlatformCodes: true, +}))(DepartureListContainer); + +const StopPageContent = withBreakpoint( + props => + some(props.routes, 'fullscreenMap') && + props.breakpoint !== 'large' ? null : ( + + ), +); + +const StopPageContentOrEmpty = props => { + if (props.stop) { + return ; + } + return ; +}; + +StopPageContentOrEmpty.propTypes = { + stop: PropTypes.shape({ + url: PropTypes.string, + }).isRequired, +}; + export default Relay.createContainer( - connectToStores(StopPageContent, ['TimeStore'], ({ getStore }) => ({ + connectToStores(StopPageContentOrEmpty, ['TimeStore'], ({ getStore }) => ({ currentTime: getStore('TimeStore') .getCurrentTime() .unix(), })), { fragments: { - stop: () => Relay.QL` + stop: ({ date }) => Relay.QL` fragment on Stop { url stoptimes: stoptimesWithoutPatterns(startTime: $startTime, timeRange: $timeRange, numberOfDepartures: $numberOfDepartures) { ${DepartureListContainer.getFragment('stoptimes')} } + ${TimetableContainer.getFragment('stop', { date })} } `, }, initialVariables: { - startTime: String(0), + startTime: 0, timeRange: 3600 * 12, numberOfDepartures: 100, + date: null, }, }, ); diff --git a/app/component/StopPageTabContainer.js b/app/component/StopPageTabContainer.js index 37755698fc..c1e434e51d 100644 --- a/app/component/StopPageTabContainer.js +++ b/app/component/StopPageTabContainer.js @@ -1,102 +1,82 @@ import PropTypes from 'prop-types'; import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; -import some from 'lodash/some'; import Icon from './Icon'; -import withBreakpoint from '../util/withBreakpoint'; -function StopPageTabContainer({ - children, - params, - routes, - breakpoint, - location, -}) { - if (some(routes, 'fullscreenMap') && breakpoint !== 'large') { - return null; - } +class StopPageTabContainer extends React.Component { + static propTypes = { + selectedTab: PropTypes.func.isRequired, + }; - let activeTab; - if (location.pathname.indexOf('/aikataulu') > -1) { - activeTab = 'timetable'; - } else { - activeTab = 'right-now'; + constructor(props) { + super(props); + this.state = { + active: 'right-now', // show departures as the default + }; } - const isTerminal = params.terminalId != null; - const urlBase = `/${ - isTerminal ? 'terminaalit' : 'pysakit' - }/${encodeURIComponent( - params.terminalId ? params.terminalId : params.stopId, - )}`; + selectedTab = val => { + this.props.selectedTab(val); + }; - return ( -
-
-
- -
-
- - - - -
-
- -
+ render() { + return ( +
+ + + { + //
{ this.setState({ active: 'add-info' }); }} + // /> + }
- {children} -
- ); + ); + } } -StopPageTabContainer.propTypes = { - children: PropTypes.node.isRequired, - breakpoint: PropTypes.string.isRequired, - params: PropTypes.oneOfType([ - PropTypes.shape({ stopId: PropTypes.string.isRequired }).isRequired, - PropTypes.shape({ terminalId: PropTypes.string.isRequired }).isRequired, - ]).isRequired, - routes: PropTypes.arrayOf( - PropTypes.shape({ - fullscreenMap: PropTypes.bool, - }).isRequired, - ).isRequired, - location: PropTypes.shape({ - pathname: PropTypes.string.isRequired, - }).isRequired, -}; - -export default withBreakpoint(StopPageTabContainer); +export default StopPageTabContainer; diff --git a/app/component/TerminalPage.js b/app/component/TerminalPage.js new file mode 100644 index 0000000000..b7b954b93c --- /dev/null +++ b/app/component/TerminalPage.js @@ -0,0 +1,68 @@ +import React from 'react'; +import Relay, { Route } from 'react-relay/classic'; +import connectToStores from 'fluxible-addons-react/connectToStores'; +import withState from 'recompose/withState'; +import moment from 'moment'; +import StopPageContentContainer from './StopPageContentContainer'; + +const initialDate = moment().format('YYYYMMDD'); + +class TerminalPageContainerRoute extends Route { + static queries = { + stop: (RelayComponent, variables) => Relay.QL` + query { + station(id: $terminalId) { + ${RelayComponent.getFragment('stop', variables)} + } + } + `, + }; + static paramDefinitions = { + startTime: { required: true }, + timeRange: { required: true }, + numberOfDepartures: { required: true }, + }; + static routeName = 'StopPageContainerRoute'; +} + +const TerminalPageRootContainer = routeProps => ( + + done ? ( + + ) : ( + undefined + ) + } + /> +); + +const TerminalPageContainerWithState = withState( + 'date', + 'setDate', + initialDate, +)(TerminalPageRootContainer); + +export default connectToStores( + TerminalPageContainerWithState, + ['TimeStore', 'FavouriteStopsStore'], + ({ getStore }) => ({ + startTime: getStore('TimeStore') + .getCurrentTime() + .unix(), + timeRange: 3600, + numberOfDepartures: 100, + }), +); diff --git a/app/component/TimetablePage.js b/app/component/TimetablePage.js deleted file mode 100644 index d3eafc8d53..0000000000 --- a/app/component/TimetablePage.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Relay from 'react-relay/classic'; -import moment from 'moment'; - -import TimetableContainer from './TimetableContainer'; - -const initialDate = moment().format('YYYYMMDD'); - -class TimetablePage extends React.Component { - static propTypes = { - stop: PropTypes.shape({ - url: PropTypes.string, - }).isRequired, - relay: PropTypes.shape({ - variables: PropTypes.shape({ - date: PropTypes.string.isRequired, - }).isRequired, - setVariables: PropTypes.func.isRequired, - }).isRequired, - }; - - onDateChange = ({ target }) => { - this.props.relay.setVariables({ date: target.value }); - }; - - render() { - return ( - - ); - } -} - -export default Relay.createContainer(TimetablePage, { - fragments: { - stop: ({ date }) => Relay.QL` - fragment on Stop { - url - ${TimetableContainer.getFragment('stop', { date })} - } - `, - }, - initialVariables: { - date: initialDate, - }, -}); diff --git a/app/component/stop.scss b/app/component/stop.scss index bc24c3fb4b..626099e4a7 100644 --- a/app/component/stop.scss +++ b/app/component/stop.scss @@ -119,7 +119,7 @@ } } -.stop-tab-singletab, .stop-tab-singletab:hover { +button.stop-tab-singletab, button.stop-tab-singletab:hover { text-transform: uppercase; font-size: $font-size-xxsmall; width: 50%; @@ -135,7 +135,6 @@ padding-bottom: 0.8em; padding-top: 0; margin-bottom: 0; - text-decoration: none; svg { stroke: $primary-color; @@ -152,6 +151,12 @@ fill: $black; } } + //Dummy setting as a placeholder, remove when + //Additional info is done + &.add-info.inactive { + background-color: $background-color; + border-top: 4px solid $background-color; + } @media print { display: none; diff --git a/app/routeRoutes.js b/app/routeRoutes.js deleted file mode 100644 index 399b389267..0000000000 --- a/app/routeRoutes.js +++ /dev/null @@ -1,153 +0,0 @@ -import React from 'react'; -import { Route, IndexRoute, IndexRedirect } from 'react-router'; -import Relay from 'react-relay/classic'; - -import Error404 from './component/404'; -import { PREFIX_ROUTES } from './util/path'; -import { getDefault, ComponentLoading404Renderer } from './util/routerUtils'; - -const RouteQueries = { - route: () => Relay.QL` - query { - route(id: $routeId) - } - `, -}; - -const PatternQueries = { - pattern: () => Relay.QL` - query { - pattern(id: $patternId) - } - `, -}; - -const TripQueries = { - trip: () => Relay.QL` - query { - trip(id: $tripId) - } - `, - pattern: () => Relay.QL` - query { - pattern(id: $patternId) - } - `, -}; - -const componentPatternQueries = { - title: RouteQueries, - header: RouteQueries, - map: PatternQueries, - content: PatternQueries, - meta: RouteQueries, -}; - -const componentTripQueries = { - title: RouteQueries, - header: RouteQueries, - map: TripQueries, - content: TripQueries, - meta: RouteQueries, -}; - -const componentRouteQueries = { - title: RouteQueries, - header: RouteQueries, - content: RouteQueries, - meta: RouteQueries, -}; - -function getComponents(getContentComponent) { - return function getPageComponents(location, cb) { - return Promise.all([ - import(/* webpackChunkName: "route" */ './component/RouteTitle').then( - getDefault, - ), - import(/* webpackChunkName: "route" */ './component/RoutePage').then( - getDefault, - ), - import(/* webpackChunkName: "route" */ './component/RouteMapContainer').then( - getDefault, - ), - getContentComponent(), - import(/* webpackChunkName: "route" */ './component/RoutePageMeta').then( - getDefault, - ), - ]).then(([title, header, map, content, meta]) => - cb(null, { title, header, map, content, meta }), - ); - }; -} - -export default ( - - - {/* TODO: Should return list of all routes */} - - - - - {/* Redirect to first pattern of route */} - - - import(/* webpackChunkName: "route" */ './component/PatternStopsContainer').then( - getDefault, - ), - )} - queries={componentPatternQueries} - render={ComponentLoading404Renderer} - /> - - import(/* webpackChunkName: "route" */ './component/PatternStopsContainer').then( - getDefault, - ), - )} - queries={componentPatternQueries} - render={ComponentLoading404Renderer} - fullscreenMap - /> - - import(/* webpackChunkName: "route" */ './component/TripStopsContainer').then( - getDefault, - ), - )} - queries={componentTripQueries} - render={ComponentLoading404Renderer} - > - - - - - - - - import(/* webpackChunkName: "route" */ './component/RouteScheduleContainer').then( - getDefault, - ), - )} - queries={componentPatternQueries} - render={ComponentLoading404Renderer} - /> - - - import(/* webpackChunkName: "route" */ './component/RouteAlertsContainer').then( - getDefault, - ), - )} - queries={componentRouteQueries} - render={ComponentLoading404Renderer} - /> - - -); diff --git a/app/routes.js b/app/routes.js index 872acf7062..8f3e1180af 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,21 +1,97 @@ import PropTypes from 'prop-types'; import React from 'react'; import Relay from 'react-relay/classic'; -import { Route } from 'react-router'; - +import { Route, IndexRoute, IndexRedirect } from 'react-router'; import IndexPage from './component/IndexPage'; import Error404 from './component/404'; +import NetworkError from './component/NetworkError'; +import Loading from './component/LoadingPage'; import TopLevel from './component/TopLevel'; import Title from './component/Title'; - import scrollTop from './util/scroll'; -import { PREFIX_ITINERARY_SUMMARY } from './util/path'; +import { + PREFIX_ROUTES, + PREFIX_STOPS, + PREFIX_ITINERARY_SUMMARY, +} from './util/path'; import { preparePlanParams } from './util/planParamUtil'; import { validateServiceTimeRange } from './util/timeUtils'; -import { errorLoading, getDefault, loadRoute } from './util/routerUtils'; -import getStopRoutes from './stopRoutes'; -import routeRoutes from './routeRoutes'; +const ComponentLoading404Renderer = { + /* eslint-disable react/prop-types */ + header: ({ error, props, element, retry }) => { + if (error) { + if ( + error.message === 'Failed to fetch' || // Chrome + error.message === 'Network request failed' // Safari && FF && IE + ) { + return ; + } + return ; + } else if (props) { + return React.cloneElement(element, props); + } + return ; + }, + map: ({ error, props, element }) => { + if (error) { + return null; + } else if (props) { + return React.cloneElement(element, props); + } + return undefined; + }, + title: ({ props, element }) => + React.cloneElement(element, { route: null, ...props }), + content: ({ props, element }) => + props ? React.cloneElement(element, props) :
, + /* eslint-enable react/prop-types */ +}; + +const StopQueries = { + stop: () => Relay.QL` + query { + stop(id: $stopId) + } + `, +}; + +const RouteQueries = { + route: () => Relay.QL` + query { + route(id: $routeId) + } + `, +}; + +const PatternQueries = { + pattern: () => Relay.QL` + query { + pattern(id: $patternId) + } + `, +}; + +const TripQueries = { + trip: () => Relay.QL` + query { + trip(id: $tripId) + } + `, + pattern: () => Relay.QL` + query { + pattern(id: $patternId) + } + `, +}; + +const terminalQueries = { + stop: () => Relay.QL` + query { + station(id: $terminalId) + } + `, +}; const planQueries = { plan: (Component, variables) => Relay.QL` @@ -27,6 +103,18 @@ const planQueries = { serviceTimeRange: () => Relay.QL`query { serviceTimeRange }`, }; +function errorLoading(err) { + console.error('Dynamic page loading failed', err); +} + +function loadRoute(cb) { + return module => cb(null, module.default); +} + +function getDefault(module) { + return module.default; +} + export default config => { const SummaryPageWrapper = ({ props, routerProps, element }) => props @@ -70,9 +158,253 @@ export default config => { .catch(errorLoading); }} /> - {getStopRoutes()} - {getStopRoutes(true) /* terminals */} - {routeRoutes} + + {' '} + {/* TODO: Should return list of all routes */} + { + Promise.all([ + import(/* webpackChunkName: "stop" */ './component/StopTitle').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPageHeaderContainer').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPage').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPageMap').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPageMeta').then( + getDefault, + ), + ]).then(([title, header, content, map, meta]) => + cb(null, { title, header, content, map, meta }), + ); + }} + queries={{ + header: StopQueries, + map: StopQueries, + meta: StopQueries, + }} + render={ComponentLoading404Renderer} + > + + + + + {' '} + {/* TODO: Should return list of all terminals */} + { + Promise.all([ + import(/* webpackChunkName: "stop" */ './component/TerminalTitle').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPageHeaderContainer').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/TerminalPage').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPageMap').then( + getDefault, + ), + import(/* webpackChunkName: "stop" */ './component/StopPageMeta').then( + getDefault, + ), + ]).then(([title, header, content, map, meta]) => + cb(null, { title, header, content, map, meta }), + ); + }} + queries={{ + header: terminalQueries, + map: terminalQueries, + meta: terminalQueries, + }} + render={ComponentLoading404Renderer} + > + + + + + {' '} + {/* TODO: Should return list of all routes */} + + + + {' '} + {/* Redirect to first pattern of route */} + + { + Promise.all([ + import(/* webpackChunkName: "route" */ './component/RouteTitle').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePage').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RouteMapContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/PatternStopsContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePageMeta').then( + getDefault, + ), + ]).then(([title, header, map, content, meta]) => + cb(null, { title, header, map, content, meta }), + ); + }} + queries={{ + title: RouteQueries, + header: RouteQueries, + map: PatternQueries, + content: PatternQueries, + meta: RouteQueries, + }} + render={ComponentLoading404Renderer} + /> + { + Promise.all([ + import(/* webpackChunkName: "route" */ './component/RouteTitle').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePage').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RouteMapContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/PatternStopsContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePageMeta').then( + getDefault, + ), + ]).then(([title, header, map, content, meta]) => + cb(null, { title, header, map, content, meta }), + ); + }} + queries={{ + title: RouteQueries, + header: RouteQueries, + map: PatternQueries, + content: PatternQueries, + meta: RouteQueries, + }} + render={ComponentLoading404Renderer} + fullscreenMap + /> + { + Promise.all([ + import(/* webpackChunkName: "route" */ './component/RouteTitle').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePage').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RouteMapContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/TripStopsContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePageMeta').then( + getDefault, + ), + ]).then(([title, header, map, content, meta]) => + cb(null, { title, header, map, content, meta }), + ); + }} + queries={{ + title: RouteQueries, + header: RouteQueries, + map: TripQueries, + content: TripQueries, + meta: RouteQueries, + }} + render={ComponentLoading404Renderer} + > + + + + + + + { + Promise.all([ + import(/* webpackChunkName: "route" */ './component/RouteTitle').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePage').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RouteMapContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RouteScheduleContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePageMeta').then( + getDefault, + ), + ]).then(([title, header, map, content, meta]) => + cb(null, { title, header, map, content, meta }), + ); + }} + queries={{ + title: RouteQueries, + header: RouteQueries, + map: PatternQueries, + content: PatternQueries, + meta: RouteQueries, + }} + render={ComponentLoading404Renderer} + /> + + { + Promise.all([ + import(/* webpackChunkName: "route" */ './component/RouteTitle').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePage').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RouteAlertsContainer').then( + getDefault, + ), + import(/* webpackChunkName: "route" */ './component/RoutePageMeta').then( + getDefault, + ), + ]).then(([title, header, content, meta]) => + cb(null, { title, header, content, meta }), + ); + }} + queries={{ + title: RouteQueries, + header: RouteQueries, + content: RouteQueries, + meta: RouteQueries, + }} + render={ComponentLoading404Renderer} + /> + + { diff --git a/app/server.js b/app/server.js index 0e323c2bda..5444c895d8 100644 --- a/app/server.js +++ b/app/server.js @@ -396,7 +396,7 @@ export default function(req, res, next) { res.write('\n\n'); if (process.env.NODE_ENV === 'development') { - res.write('\n'); + res.write('\n'); } else { res.write('