Use local dates for display of intervals.
[?]
Aug 24, 2020, 4:59 AM
OUR4PAOTXXKXQPMAR5TIYX7MBRRJS2WVTZS7SN4SOGML7SPJIJGQCDependencies
- [2]
RSF6UAJKBreak out api module for timeline. - [3]
WRPIYG3EUse project listing functionality to check for whether we have a cookie. - [4]
ZIG57EE6Fix project selection, end log end on project switch. - [5]
QMEYU4MWAdd display for prior intervals. - [6]
BFZN4SUAMake timeline component work. - [7]
PT4276XCAdd logout functionality. - [8]
JXG3FCXYUpgrade ps + halogen versions. - [9]
QU5FW67RAdd project selection to time tracker. - [10]
NJNMO72SAdd zcash.com submodule and update client to modern halogen. - [11]
J6S23MDGUse server timestamps for interval start and end. - [*]
RB2ETNIFAdd skeletal PureScript client project.
Change contents
- edit in client/src/Aftok/Api/Timeline.purs at line 82
instance showInterval :: Show i => Show (Interval' i) whereshow (Interval i) = "Interval {start: " <> show i.start <> ", end: " <> show i.end <> "}" - edit in client/src/Aftok/Api/Timeline.purs at line 106
start :: forall i. Interval' i -> istart (Interval i) = i.startend :: forall i. Interval' i -> iend (Interval i) = i.end - edit in client/src/Aftok/Timeline.purs at line 7
import Control.Monad.State (State, put, get, evalState) - edit in client/src/Aftok/Timeline.purs at line 9
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT) - replacement in client/src/Aftok/Timeline.purs at line 11
import Data.Array (reverse, filter)import Data.Array (reverse)import Data.Date as D - replacement in client/src/Aftok/Timeline.purs at line 14
import Data.DateTime (DateTime(..), adjust, date)import Data.DateTime as DTimport Data.DateTime (DateTime(..), date) - replacement in client/src/Aftok/Timeline.purs at line 21
import Data.Maybe (Maybe(..), maybe, isJust, isNothing)import Data.Maybe (Maybe(..), maybe, isJust, isNothing, fromMaybe) - replacement in client/src/Aftok/Timeline.purs at line 25[3.2626]→[2.6641:6677](∅→∅),[2.6677]→[3.1144:1179](∅→∅),[3.553]→[3.1144:1179](∅→∅),[3.553]→[3.45:80](∅→∅),[3.1179]→[3.45:80](∅→∅),[3.2662]→[3.45:80](∅→∅),[3.2903]→[3.45:80](∅→∅),[3.2904]→[3.2904:2922](∅→∅)
import Data.Traversable (traverse_)import Data.Tuple (Tuple(..), fst)import Data.Unfoldable (fromMaybe)import Math (abs)import Data.Traversable (traverse_, traverse)import Data.Tuple (Tuple(..))import Data.Unfoldable as U - replacement in client/src/Aftok/Timeline.purs at line 46[3.301702]→[3.1319:1406](∅→∅),[3.183]→[3.301777:301817](∅→∅),[3.1406]→[3.301777:301817](∅→∅),[3.301777]→[3.301777:301817](∅→∅)
import CSS (backgroundColor, clear, clearBoth, border, rgb, solid, borderRadius, left)import CSS.Display (position, absolute)import CSS (backgroundColor, clear, clearBoth, border, rgb, solid, borderRadius, marginLeft)import CSS.Display (display, flex) - replacement in client/src/Aftok/Timeline.purs at line 52
import Aftok.Api.Timeline (TimelineError, Interval'(..), Interval, TimeSpan)import Aftok.Api.Timeline (TimelineError, Interval'(..), Interval, TimeSpan, start, end, interval) - replacement in client/src/Aftok/Timeline.purs at line 54
import Aftok.Project (Project, Project'(..), ProjectId)import Aftok.Project (Project, Project'(..), ProjectId, pidStr) - edit in client/src/Aftok/Timeline.purs at line 62
type DayIntervals ={ dayBounds :: Interval, loggedIntervals :: Array Interval}type History = M.Map Date DayIntervals - replacement in client/src/Aftok/Timeline.purs at line 70
{ limits :: TimelineLimits, history :: M.Map Date (Array Interval){ selectedProject :: Maybe Project, history :: M.Map Date DayIntervals - replacement in client/src/Aftok/Timeline.purs at line 73
, selectedProject :: Maybe Project, activeHistory :: M.Map Date DayIntervals - replacement in client/src/Aftok/Timeline.purs at line 115
{ limits: { bounds: TL.interval bottom bottom, current: bottom }{ selectedProject: Nothing - replacement in client/src/Aftok/Timeline.purs at line 118
, selectedProject: Nothing, activeHistory: M.empty - replacement in client/src/Aftok/Timeline.purs at line 151
, lineHtml $ intervalHtml st.limits.bounds <$> currentHistory st] <> ((\(Tuple d xs) -> dateLine st d xs) <$> priorHistory st))] <> (historyLine <$> reverse (M.toUnfoldable $ unionHistories st.history st.activeHistory))) - replacement in client/src/Aftok/Timeline.purs at line 157[3.5140]→[3.303693:303737](∅→∅),[3.5279]→[3.303693:303737](∅→∅),[3.303693]→[3.303693:303737](∅→∅),[3.303737]→[3.5280:5511](∅→∅),[3.5511]→[3.304192:304227](∅→∅),[3.304192]→[3.304192:304227](∅→∅),[3.304227]→[3.5141:5184](∅→∅),[3.5184]→[3.304227:304245](∅→∅),[3.304227]→[3.304227:304245](∅→∅),[3.304245]→[3.5512:5548](∅→∅),[3.5548]→[3.998:1016](∅→∅),[3.998]→[3.998:1016](∅→∅)
eval = case _ ofInitialize -> dodt@(DateTime today t) <- lift system.nowDateTimeH.put $ { limits : { bounds: dateBounds today, current: fromDateTime dt}, history : M.empty, active : Nothing, selectedProject: Nothing}_ <- H.subscribe caps.timerpure uniteval action = docase action ofInitialize -> dovoid $ H.subscribe caps.timer - replacement in client/src/Aftok/Timeline.purs at line 162[3.5215]→[3.2204:2334](∅→∅),[3.2506]→[3.2506:2652](∅→∅),[3.2652]→[2.6945:7045](∅→∅),[2.7045]→[3.5646:5920](∅→∅),[3.5646]→[3.5646:5920](∅→∅)
ProjectSelected p -> doactive <- isJust <$> H.gets (_.active)currentProject <- H.gets (_.selectedProject)when (active && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)(traverse_ logEnd currentProject)timeSpan <- TL.Before <$> lift system.nowDateTime -- FIXME, should come from a form controlintervals' <- lift $ caps.listIntervals (unwrap p).projectId timeSpanlet intervals = case intervals' ofLeft err -> [] -- FIXMERight ivals -> ivalsH.modify_ (_ { selectedProject = Just p, history = toHistory intervals })ProjectSelected p -> doactive <- isJust <$> H.gets (_.active)currentProject <- H.gets (_.selectedProject)when (active && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)(traverse_ logEnd currentProject)timeSpan <- TL.Before <$> lift system.nowDateTime -- FIXME, should come from a form controlintervals' <- lift $ caps.listIntervals (unwrap p).projectId timeSpanintervals <- lift $ case intervals' ofLeft err ->(system.log $ "Error occurred listing intervals") *>pure []Right ivals ->(system.log $ "Got " <> show (length ivals :: Int) <> " intervals for project " <> pidStr (unwrap p).projectId) *>pure ivalshistory' <- lift <<< runMaybeT $ toHistory system intervalshist <- case history' ofNothing -> lift $ system.log "Project history was empty." *> pure M.emptyJust h -> pure hH.modify_ (_ { selectedProject = Just p, history = hist }) - replacement in client/src/Aftok/Timeline.purs at line 182[3.5265]→[3.304246:304266](∅→∅),[3.5215]→[3.304246:304266](∅→∅),[3.5497]→[3.5497:5543](∅→∅),[3.5543]→[3.2653:2688](∅→∅)
Start -> doproject <- H.gets (_.selectedProject)traverse_ logStart projectStart -> doproject <- H.gets (_.selectedProject)traverse_ logStart projectStop -> docurrentProject <- H.gets (_.selectedProject)traverse_ logEnd currentProject - replacement in client/src/Aftok/Timeline.purs at line 190
Stop -> docurrentProject <- H.gets (_.selectedProject)traverse_ logEnd currentProjectRefresh -> dot <- lift $ system.nowH.modify_ (refresh t) - replacement in client/src/Aftok/Timeline.purs at line 194[3.304399]→[3.304399:304419](∅→∅),[3.304419]→[3.5921:5952](∅→∅),[3.5952]→[3.304447:304477](∅→∅),[3.304447]→[3.304447:304477](∅→∅)
Refresh -> dot <- lift $ system.nowH.modify_ (refresh t)-- common updates, irrespective of actionactive <- H.gets (_.active)activeHistory <- lift <<< map (fromMaybe M.empty) <<< runMaybeT $ toHistory system (U.fromMaybe active)H.modify_ (_ { activeHistory = activeHistory }) - replacement in client/src/Aftok/Timeline.purs at line 204
Right t -> H.modify_ (start t)Right t -> H.modify_ (updateStart t) - replacement in client/src/Aftok/Timeline.purs at line 211
Right t -> H.modify_ (stop t)Right t -> docurrentState <- H.getupdatedState <- lift $ updateStop system t currentStateH.put updatedState - replacement in client/src/Aftok/Timeline.purs at line 216
dateBounds :: Date -> IntervaldateBounds date =let startOfDay = DateTime date bottomendOfDay = adjust (Days 1.0) startOfDaystartInstant = fromDateTime startOfDayin TL.interval startInstant (maybe startInstant fromDateTime endOfDay)historyLine:: forall w i. Tuple Date DayIntervals-> HH.HTML w ihistoryLine (Tuple d xs) =datedLine d xs.dayBounds xs.loggedIntervals - replacement in client/src/Aftok/Timeline.purs at line 223
currentHistory:: TimelineStatedatedLine:: forall w i. Date-> Interval - replacement in client/src/Aftok/Timeline.purs at line 228
currentHistory st =let currentDate = date $ toDateTime st.limits.currentin maybe [] identity (M.lookup currentDate st.history) <> fromMaybe st.activepriorHistory:: TimelineState-> Array (Tuple Date (Array Interval))priorHistory st =let currentDate = date $ toDateTime st.limits.currentin reverse <<< filter (not <<< (currentDate == _) <<< fst) $ M.toUnfoldable st.historydateLine:: forall action slots m. TimelineState-> Date-> Array Interval-> H.ComponentHTML action slots mdateLine st d xs =-> HH.HTML w idatedLine d dateBounds xs = - replacement in client/src/Aftok/Timeline.purs at line 231
[][ CSS.style doclear clearBoth] - replacement in client/src/Aftok/Timeline.purs at line 235
, lineHtml (intervalHtml (dateBounds d) <$> xs), HH.div[ CSS.style doborder solid (px 3.0) (rgb 0x00 0xFF 0x00)borderRadius px5 px5 px5 px5height (px $ 44.0)display flex, P.classes (ClassName <$> ["my-2"])](evalState (traverse (intervalHtml dateBounds) xs) 0.0) - edit in client/src/Aftok/Timeline.purs at line 245
wherepx5 = px 5.0 - edit in client/src/Aftok/Timeline.purs at line 253[3.3329]→[3.3329:3338](∅→∅),[3.3338]→[3.304488:304615](∅→∅),[3.304488]→[3.304488:304615](∅→∅),[3.304615]→[3.3447:3466](∅→∅),[3.3466]→[3.304643:304675](∅→∅),[3.304643]→[3.304643:304675](∅→∅),[3.304675]→[3.7437:7461](∅→∅),[3.7461]→[3.3467:3543](∅→∅),[3.304675]→[3.3467:3543](∅→∅),[3.3543]→[3.304769:304866](∅→∅),[3.304769]→[3.304769:304866](∅→∅),[3.304866]→[3.5480:5481](∅→∅),[3.5480]→[3.5480:5481](∅→∅)
lineHtml:: forall action slots m. Array (H.ComponentHTML action slots m)-> H.ComponentHTML action slots mlineHtml contents =let px5 = px 5.0in HH.div[ CSS.style doclear clearBothborder solid (px 3.0) (rgb 0x00 0xFF 0x00)height (px 50.0)borderRadius px5 px5 px5 px5, P.classes (ClassName <$> ["my-2"])]contents - replacement in client/src/Aftok/Timeline.purs at line 254
:: forall action slots m:: forall w i - replacement in client/src/Aftok/Timeline.purs at line 257
-> H.ComponentHTML action slots mintervalHtml (Interval limits) (Interval i) =-> State Number (HH.HTML w i)intervalHtml (Interval limits) (Interval i) = dooffset <- get - replacement in client/src/Aftok/Timeline.purs at line 261
ileft = ilen limits.start i.startileft = ilen limits.start i.start - replacement in client/src/Aftok/Timeline.purs at line 265[3.973]→[3.305220:305233](∅→∅),[3.1255]→[3.305220:305233](∅→∅),[3.3608]→[3.305220:305233](∅→∅),[3.305220]→[3.305220:305233](∅→∅)
in HH.divput $ toPct (ilen limits.start i.end)pure $ HH.div - edit in client/src/Aftok/Timeline.purs at line 268
position absolute - replacement in client/src/Aftok/Timeline.purs at line 269
height (px $ 44.0)left (pct $ toPct ileft)marginLeft (pct $ toPct ileft - offset) - replacement in client/src/Aftok/Timeline.purs at line 285[3.6224]→[3.6224:6275](∅→∅),[3.6275]→[3.3508:3520](∅→∅),[3.3520]→[2.7121:7172](∅→∅),[2.7172]→[3.305818:305824](∅→∅),[3.7572]→[3.305818:305824](∅→∅),[3.305818]→[3.305818:305824](∅→∅)
start :: Instant -> TimelineState -> TimelineStatestart t s =s { active = s.active <|> Just (TL.interval t t)}updateStart :: Instant -> TimelineState -> TimelineStateupdateStart t s =s { active = s.active <|> Just (TL.interval t t) } - replacement in client/src/Aftok/Timeline.purs at line 289[3.6388]→[3.6388:6438](∅→∅),[3.6438]→[3.3521:3532](∅→∅),[3.3532]→[3.7573:7615](∅→∅),[3.7615]→[2.7173:7262](∅→∅),[2.7262]→[3.7701:7718](∅→∅),[3.7701]→[3.7701:7718](∅→∅),[3.7718]→[3.305918:305947](∅→∅),[3.305918]→[3.305918:305947](∅→∅)
stop :: Instant -> TimelineState -> TimelineStatestop t s =s { history = maybes.history(\i -> M.unionWith (<>) (toHistory [TL.interval (unwrap i).start t]) s.history)s.active, active = Nothing}updateStop:: forall m. Monad m=> System m-> Instant-> TimelineState-> m TimelineStateupdateStop system t st = donewHistory <- join <$> traverse (\i -> runMaybeT $ toHistory system [TL.interval (start i) t]) st.activepure { selectedProject: st.selectedProject, history: maybe st.history (unionHistories st.history) newHistory, active: Nothing, activeHistory: M.empty} - replacement in client/src/Aftok/Timeline.purs at line 306
s { limits = s.limits { current = t }, active = map (\(Interval i) -> TL.interval i.start t) s.actives { active = map (\(Interval i) -> TL.interval i.start t) s.active - replacement in client/src/Aftok/Timeline.purs at line 312
in abs $ n (unInstant _end) - n (unInstant _start)in n (unInstant _end) - n (unInstant _start) - replacement in client/src/Aftok/Timeline.purs at line 330
intervalDate :: Interval -> DateintervalDate = date <<< toDateTime <<< (_.end) <<< unwraputcDayBounds :: Instant -> IntervalutcDayBounds i =let startOfDay = DateTime (date $ toDateTime i) bottomendOfDay = DT.adjust (Days 1.0) startOfDaystartInstant = fromDateTime startOfDayin TL.interval startInstant (maybe startInstant fromDateTime endOfDay) - replacement in client/src/Aftok/Timeline.purs at line 337
toHistory :: Array Interval -> M.Map Date (Array Interval)toHistory = M.fromFoldableWith (<>) <<< map (\i -> Tuple (intervalDate i) [i])localDayBounds:: forall m. Monad m=> System m-> Instant-> MaybeT m (Tuple Date Interval)localDayBounds system t = doTuple date start <- MaybeT $ system.dateFFI.midnightLocal tend <- MaybeT <<< pure $ fromDateTime <$> DT.adjust (Days 1.0) (toDateTime start)pure $ Tuple date (interval start end)incrementDayBounds :: Tuple Date Interval -> Maybe (Tuple Date Interval)incrementDayBounds (Tuple d i) =let nextEnd = fromDateTime <$> (DT.adjust (Days 1.0) $ toDateTime (end i))in Tuple <$> D.adjust (Days 1.0) d<*> (interval (end i) <$> nextEnd)splitInterval:: forall m. Monad m=> System m-> Interval-> MaybeT m (Array (Tuple Date DayIntervals))splitInterval system i = dolift <<< system.log $ "Splitting interval " <> show idayBounds@(Tuple date bounds) <- localDayBounds system (start i)split <- if end i < (end bounds)then dopure [Tuple date { dayBounds: bounds, loggedIntervals: [i] }]else dolet firstFragment = [ Tuple date { dayBounds: bounds, loggedIntervals: [interval (start i) (end bounds)]} ]append firstFragment <$> splitInterval system (interval (end bounds) (end i))lift <<< system.log $ "Split result: " <> show splitpure splittoHistory:: forall m. Monad m=> System m-> Array Interval-> MaybeT m (M.Map Date DayIntervals)toHistory system xs = dosplitIntervals <- join <$> traverse (splitInterval system) xspure $ M.fromFoldableWith unionDayIntervals splitIntervals - edit in client/src/Aftok/Timeline.purs at line 384[3.10748]
unionDayIntervals :: DayIntervals -> DayIntervals -> DayIntervalsunionDayIntervals d1 d2 ={ dayBounds: d1.dayBounds -- FIXME, need to be sure these match, loggedIntervals: d1.loggedIntervals <> d2.loggedIntervals}unionHistories :: History -> History -> HistoryunionHistories = M.unionWith unionDayIntervals - edit in client/src/Aftok/Types.purs at line 11
import Data.Date (Date) - replacement in client/src/Aftok/Types.purs at line 17
import Data.JSDate as JSDateimport Data.JSDate as JD - edit in client/src/Aftok/Types.purs at line 21
import Data.Tuple (Tuple(..)) - edit in client/src/Aftok/Types.purs at line 40
, dateFFI :: DateFFI m - edit in client/src/Aftok/Types.purs at line 50
, dateFFI: hoistDateFFI liftEffect jsDateFFI}type DateFFI m ={ midnightLocal :: Instant -> m (Maybe (Tuple Date Instant)) - edit in client/src/Aftok/Types.purs at line 57
jsDateFFI :: DateFFI EffectjsDateFFI ={ midnightLocal}midnightLocal :: Instant -> Effect (Maybe (Tuple Date Instant))midnightLocal i = dolet jsDate = JD.fromInstant iyear <- JD.getFullYear jsDatemonth <- JD.getMonth jsDateday <- JD.getDate jsDatejsMidnight <- midnightLocalJS year month daylet date = JD.toDate jsMidnightpure $ Tuple <$> date <*> JD.toInstant jsMidnightmidnightLocalJS :: Number -> Number -> Number -> Effect JD.JSDatemidnightLocalJS year month day = JD.jsdateLocal{ year, month, day, hour: 0.0, minute: 0.0, second: 0.0, millisecond: 0.0}hoistDateFFI :: forall m n. (forall a. m a -> n a) -> DateFFI m -> DateFFI nhoistDateFFI nt ffi ={ midnightLocal: \i -> nt (ffi.midnightLocal i)} - replacement in client/src/Aftok/Types.purs at line 127
jsDate <- lift $ JSDate.parse strjsDate <- lift $ JD.parse str - replacement in client/src/Aftok/Types.purs at line 129
(JSDate.toDateTime jsDate)(JD.toDateTime jsDate) - replacement in client/src/Main.purs at line 72
initialState _ = LoggedOutinitialState _ = Loading - replacement in client/src/Main.purs at line 75
render s = case s ofrender = case _ of - replacement in client/src/Main.purs at line 95
LoginComplete (Login.LoginComplete xs) ->LoginComplete (Login.LoginComplete _) ->