@@ -137,11 +137,32 @@ type State =
137137 , currentPage :: Int
138138 -- | Whether there are more results beyond the current page.
139139 , hasNextPage :: Boolean
140- -- | Boundary timestamps for pages we have visited. `pageCursors !! 0` is
141- -- | the cursor that takes us from page 1 to page 2, etc.
142- , pageCursors :: Array DateTime
140+ -- | The cursor used to fetch the current page, if any.
141+ , pageCursor :: Maybe PageCursor
143142 }
144143
144+ -- | Direction of a pagination cursor relative to the sort order.
145+ data PaginationDir = Forward | Backward
146+
147+ derive instance Eq PaginationDir
148+
149+ -- | A pagination cursor: a boundary timestamp and the direction it was
150+ -- | computed from (forward = NextPage, backward = PrevPage).
151+ type PageCursor = { timestamp :: DateTime , dir :: PaginationDir }
152+
153+ printCursorParam :: PageCursor -> String
154+ printCursorParam { timestamp, dir } =
155+ (case dir of
156+ Forward -> " f:"
157+ Backward -> " b:" )
158+ <> Job .formatCursorTimestamp timestamp
159+
160+ parseCursorParam :: String -> Maybe PageCursor
161+ parseCursorParam s = case String .take 2 s of
162+ " f:" -> map (\dt -> { timestamp: dt, dir: Forward }) (Job .parseCursorTimestamp (String .drop 2 s))
163+ " b:" -> map (\dt -> { timestamp: dt, dir: Backward }) (Job .parseCursorTimestamp (String .drop 2 s))
164+ _ -> Nothing
165+
145166data Action
146167 = Initialize
147168 | FetchJobs
@@ -205,7 +226,9 @@ initialState input = do
205226 , untilStr: fromMaybe " " p.until
206227 , currentPage: fromMaybe 1 p.page
207228 , hasNextPage: true
208- , pageCursors: []
229+ , pageCursor: case p.page, p.cursor >>= parseCursorParam of
230+ Just pg, Just pc | pg > 1 -> Just pc
231+ _, _ -> Nothing
209232 }
210233
211234-- --------------------------------------------------------------------------
@@ -246,6 +269,9 @@ stateToParams s =
246269 , page:
247270 if s.currentPage <= 1 then Nothing
248271 else Just s.currentPage
272+ , cursor: case s.pageCursor of
273+ Just pc | s.currentPage > 1 -> Just (printCursorParam pc)
274+ _ -> Nothing
249275 }
250276
251277-- --------------------------------------------------------------------------
@@ -630,7 +656,9 @@ handleAction = case _ of
630656 , currentPage = fromMaybe 1 p.page
631657 , since = Nothing
632658 , until = Nothing
633- , pageCursors = []
659+ , pageCursor = case p.page, p.cursor >>= parseCursorParam of
660+ Just pg, Just pc | pg > 1 -> Just pc
661+ _, _ -> Nothing
634662 , hasNextPage = true
635663 }
636664 handleAction FetchJobs
@@ -663,15 +691,18 @@ handleAction = case _ of
663691 -- avoids VDOM diffing on every auto-refresh tick when nothing new
664692 -- has arrived.
665693 unless (not state.loading && newFingerprints == oldFingerprints) do
666- let hasNext = Array .length jobs >= pageSize
694+ let isBackward = case state.pageCursor of
695+ Just { dir: Backward } -> true
696+ _ -> false
697+ let hasNext = isBackward || Array .length jobs >= pageSize
667698 H .modify_ _ { loading = false , error = Nothing , jobs = summaries, hasNextPage = hasNext }
668699
669700 SetTimeRange range -> do
670701 when (range == Custom ) do
671702 now <- liftEffect Now .nowDateTime
672703 let sinceDefault = subtractHours 24.0 now
673704 H .modify_ _ { sinceStr = Job .formatDateTimeLocal sinceDefault, untilStr = Job .formatDateTimeLocal now }
674- H .modify_ _ { timeRange = range, since = Nothing , until = Nothing , currentPage = 1 , pageCursors = [] , hasNextPage = true }
705+ H .modify_ _ { timeRange = range, since = Nothing , until = Nothing , currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
675706 handleAction FetchJobs
676707 notifyFiltersChanged
677708
@@ -722,17 +753,17 @@ handleAction = case _ of
722753 -- Re-fetch when switching between Active and other modes, because
723754 -- Active excludes completed jobs server-side.
724755 when (needsRefetch state) do
725- H .modify_ _ { currentPage = 1 , pageCursors = [] , hasNextPage = true }
756+ H .modify_ _ { currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
726757 handleAction FetchJobs
727758
728759 ClearFilters -> do
729- H .modify_ _ { filters = emptyFilters, sortOrder = defaultSortOrder, currentPage = 1 , pageCursors = [] , hasNextPage = true }
760+ H .modify_ _ { filters = emptyFilters, sortOrder = defaultSortOrder, currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
730761 notifyFiltersChanged
731762
732763 SetSort field -> do
733764 H .modify_ \s -> do
734765 let newOrder = if s.sortField == field then (if s.sortOrder == DESC then ASC else DESC ) else DESC
735- s { sortField = field, sortOrder = newOrder, currentPage = 1 , pageCursors = [] , hasNextPage = true }
766+ s { sortField = field, sortOrder = newOrder, currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
736767 handleAction FetchJobs
737768 notifyFiltersChanged
738769
@@ -749,18 +780,30 @@ handleAction = case _ of
749780 case cursor of
750781 Nothing -> pure unit
751782 Just ts -> do
752- let newCursors = state.pageCursors <> [ ts ]
753- H .modify_ _ { currentPage = state.currentPage + 1 , pageCursors = newCursors }
783+ H .modify_ _ { currentPage = state.currentPage + 1 , pageCursor = Just { timestamp: ts, dir: Forward } }
754784 handleAction FetchJobs
755785 notifyFiltersChanged
756786
757787 PrevPage -> do
758788 state <- H .get
759789 when (state.currentPage > 1 ) do
760- let newCursors = fromMaybe [] (Array .init state.pageCursors)
761- H .modify_ _ { currentPage = state.currentPage - 1 , pageCursors = newCursors, hasNextPage = true }
762- handleAction FetchJobs
763- notifyFiltersChanged
790+ let targetPage = state.currentPage - 1
791+ if targetPage <= 1 then do
792+ -- Arriving at page 1: reset cursor for fresh data
793+ H .modify_ _ { currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
794+ handleAction FetchJobs
795+ notifyFiltersChanged
796+ else do
797+ let
798+ cursor = case state.sortOrder of
799+ DESC -> extremeCreatedAt max state.jobs
800+ ASC -> extremeCreatedAt min state.jobs
801+ case cursor of
802+ Nothing -> pure unit
803+ Just ts -> do
804+ H .modify_ _ { currentPage = targetPage, pageCursor = Just { timestamp: ts, dir: Backward }, hasNextPage = true }
805+ handleAction FetchJobs
806+ notifyFiltersChanged
764807
765808 Tick ->
766809 handleAction FetchJobsSilent
@@ -799,25 +842,24 @@ doFetchJobs = do
799842 Custom -> customUntil
800843 UntilNow -> Just now
801844 _ -> Nothing
802- pageCursor = Array .index state.pageCursors (state.currentPage - 2 )
803- since = case state.sortOrder of
804- DESC -> baseSince
805- ASC ->
806- if state.currentPage > 1 then pageCursor
807- else baseSince
808- until = case state.sortOrder of
809- DESC ->
810- if state.currentPage > 1 then pageCursor
811- else baseUntil
812- ASC -> baseUntil
845+ { since, until, fetchOrder, needsReverse } = case state.pageCursor of
846+ Nothing ->
847+ { since: baseSince, until: baseUntil, fetchOrder: state.sortOrder, needsReverse: false }
848+ Just { timestamp, dir: Forward } -> case state.sortOrder of
849+ DESC -> { since: baseSince, until: Just timestamp, fetchOrder: DESC , needsReverse: false }
850+ ASC -> { since: Just timestamp, until: baseUntil, fetchOrder: ASC , needsReverse: false }
851+ Just { timestamp, dir: Backward } -> case state.sortOrder of
852+ DESC -> { since: Just timestamp, until: baseUntil, fetchOrder: ASC , needsReverse: true }
853+ ASC -> { since: baseSince, until: Just timestamp, fetchOrder: DESC , needsReverse: true }
813854 includeCompleted = Just (state.filters.statusFilter /= ActiveOnly )
814- H .liftAff $ API .fetchJobs state.apiConfig { since, until, order: Just state.sortOrder, includeCompleted }
855+ result <- H .liftAff $ API .fetchJobs state.apiConfig { since, until, order: Just fetchOrder, includeCompleted }
856+ pure $ if needsReverse then map Array .reverse result else result
815857
816858-- | Update the combined sinceStr from a date or time part change, fetch if
817859-- | both endpoints parse, and sync the URL.
818860updateCustomSince :: forall m . MonadAff m => String -> H.HalogenM State Action () Output m Unit
819861updateCustomSince newSince = do
820- H .modify_ _ { sinceStr = newSince, since = Nothing , until = Nothing , currentPage = 1 , pageCursors = [] , hasNextPage = true }
862+ H .modify_ _ { sinceStr = newSince, since = Nothing , until = Nothing , currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
821863 state <- H .get
822864 case Job .parseDateTimeLocal newSince, Job .parseDateTimeLocal state.untilStr of
823865 Just _, Just _ -> handleAction FetchJobs
@@ -828,7 +870,7 @@ updateCustomSince newSince = do
828870-- | both endpoints parse, and sync the URL.
829871updateCustomUntil :: forall m . MonadAff m => String -> H.HalogenM State Action () Output m Unit
830872updateCustomUntil newUntil = do
831- H .modify_ _ { untilStr = newUntil, since = Nothing , until = Nothing , currentPage = 1 , pageCursors = [] , hasNextPage = true }
873+ H .modify_ _ { untilStr = newUntil, since = Nothing , until = Nothing , currentPage = 1 , pageCursor = Nothing , hasNextPage = true }
832874 state <- H .get
833875 case Job .parseDateTimeLocal state.sinceStr, Job .parseDateTimeLocal newUntil of
834876 Just _, Just _ -> handleAction FetchJobs
0 commit comments