Begin work on timeline component.
JXG3FCXYBDKMUD77DOM7RCIJYKB7BILC43OHHDZBE7YQRGAMUCCAC
QQXR7DTOQ3BDRAIYASTMTFQXAVIG4MFIRNUH4C5M72WZMHPFL2YAC
PGZJ736CG2E4HXIRYTZTGOMJRX2CHPIFG6H45PPO57EONOWJJ74QC
ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC
ARX7SHY5UXL5ZZDY4BJ6LVQSC2XCI5M6FFXQ35MBWDRUHNJNICHQC
RB2ETNIFLQUA6OA66DAEOXZ25ENMQGNKX5CZRSKEYHTD6BQ6NTFQC
EA5BFM5GMM7KNMDLTVOSUKVKMSIDD72TAFVHDVGEOUY5VELECU3QC
.psc-package
"purescript-console": "^0.1.0",
"purescript-halogen": "~0.5.14",
"purescript-affjax": "~0.10.1"
"purescript-halogen": "^1.2.1",
"purescript-css": "^2.1.0",
"purescript-halogen-css": "^5.0.0",
"purescript-dom": "^3.7.0",
"purescript-affjax": "^3.0.2",
"purescript-now": "^2.0.0"
.tl-wrapper {
height: 45px;
width: 962px;
margin-left: auto;
margin-right: auto;
}
{
"devDependencies": {
"pulp": "^10.0.0",
"purescript": "^0.10.7",
"purescript-psa": "^0.4.0",
"rimraf": "^2.5.4"
}
}
import Halogen
import Halogen.HTML.Core (className)
import qualified Halogen.HTML.Indexed as H
import qualified Halogen.HTML.Events.Indexed as E
import qualified Halogen.HTML.Properties.Indexed as P
import Halogen as H
import Halogen.Aff (HalogenEffects)
import Halogen.HTML.Core (ClassName(..))
import Halogen.HTML as HH
import Halogen.HTML.Events as E
import Halogen.HTML.Properties as P
ui :: forall eff. Component LoginState LoginAction (Aff (LoginEffects eff))
ui = component render eval
where
ui :: forall eff. H.Component HH.HTML LoginAction LoginState Void (Aff (LoginEffects eff))
ui = H.component
{ initialState: const initialState
, render
, eval
, receiver: const Nothing
} where
H.div
[ P.classes (className <$> ["container"]) ]
[ H.form
[ P.classes [ className "form-signin" ] ]
[ H.h2 [ P.classes [ className "form-signin-heading" ]] [ H.text "Aftok Login" ]
, H.label [ P.for "inputUsername", P.classes [ className "sr-only" ]] [ H.text "username" ]
, H.input
[ P.inputType P.InputText
HH.div
[ P.classes (ClassName <$> ["container"]) ]
[ HH.form
[ P.classes [ ClassName "form-signin" ] ]
[ HH.h2 [ P.classes [ ClassName "form-signin-heading" ]] [ HH.text "Aftok Login" ]
, HH.label [ P.for "inputUsername", P.classes [ ClassName "sr-only" ]] [ HH.text "username" ]
, HH.input
[ P.type_ P.InputText
, H.label [ P.for "inputPassword", P.classes [ className "sr-only" ]] [ H.text "username" ]
, H.input
[ P.inputType P.InputPassword
, HH.label [ P.for "inputPassword", P.classes [ ClassName "sr-only" ]] [ HH.text "username" ]
, HH.input
[ P.type_ P.InputPassword
eval :: Natural LoginAction (ComponentDSL LoginState LoginAction (Aff (LoginEffects eff)))
eval (SetUsername user next) = modify (_ { username = user }) $> next
eval (SetPassword pass next) = modify (_ { password = pass }) $> next
eval :: LoginAction ~> H.ComponentDSL LoginState LoginAction Void (Aff (LoginEffects eff))
eval (SetUsername user next) = H.modify (_ { username = user }) $> next
eval (SetPassword pass next) = H.modify (_ { password = pass }) $> next
module Aftok.Timeline where
import Prelude
import Control.Monad.Eff (Eff())
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Now (NOW, nowDateTime, now)
import Control.Monad.Aff (Aff())
import Control.Alt ((<|>))
import Data.Array (cons)
--import Data.Bounded (bottom)
--import Data.Date (Date(..), day, month, year)
import Data.DateTime (DateTime(..), date, adjust)
import Data.DateTime.Instant (Instant, unInstant, fromDateTime)
import Data.DateTime.Locale (LocalValue(..))
import Data.Int (toNumber)
import Data.Time.Duration (Milliseconds(..), Days(..))
import Data.Maybe (Maybe(..), maybe)
import Math (abs)
import CSS.Display (position, absolute)
import CSS.Geometry (left) --, width, top, height)
import CSS.Size (px)
import Halogen as H
import Halogen.Aff (HalogenEffects)
import Halogen.HTML.CSS as CSS
import Halogen.HTML as HH
-- import Halogen.HTML.Events as E
import Halogen.HTML.Properties as P
type Interval =
{ start :: Instant
, end :: Instant
}
type TimelineLimits =
{ start :: Instant
, current :: Instant
, end :: Instant
}
type TimelineConfig =
{ width :: Number
}
type TimelineState =
{ limits :: TimelineLimits
, history :: Array Interval
, active :: Maybe Interval
}
data TimelineAction a
= Start a
| Stop a
| Refresh a
-- | Open a
-- | Close a
type TimelineEffects eff = HalogenEffects (now :: NOW | eff)
initialState :: forall eff. Eff (now :: NOW | eff) TimelineState
initialState = do
LocalValue l t <- nowDateTime
let startOfDay = DateTime (date t) bottom
endOfDay = adjust (Days (toNumber 1)) startOfDay
startInstant = fromDateTime startOfDay
limits =
{ start: startInstant
, current: fromDateTime t
, end: maybe startInstant fromDateTime endOfDay
}
pure $ { limits : limits
, history : []
, active : Nothing
}
ui :: forall eff. TimelineConfig -> TimelineState -> H.Component HH.HTML TimelineAction TimelineState Void (Aff (TimelineEffects eff))
ui conf s = H.component
{ initialState: const s
, render
, eval
, receiver: const Nothing
} where
intervalHtml :: forall f. TimelineLimits -> Interval -> H.ComponentHTML f
intervalHtml limits i =
let offset = ((ilen limits.start i.start) / (ilen limits.start limits.end)) * conf.width
in HH.div
[ CSS.style do
position absolute
left (px offset)
]
[ HH.div
[ P.classes (H.ClassName <$> ["center"]) ]
[]
]
-- <div style="position: absolute; left: 582.268px; width: 92.4619px; top: 6px; height: 33px;" class="TimelineElement Element ui-resizable ui-draggable" data-original-title=""><div class="center" style="background-color: rgb(255, 0, 0);"></div><div class="ui-resizable-w ui-resizable-handle"></div><div class="ui-draggable-pad"></div><div class="ui-resizable-e ui-resizable-handle"></div></div>
render :: TimelineState -> H.ComponentHTML TimelineAction
render st =
HH.div
[ P.classes (H.ClassName <$> ["container"]) ]
[ HH.div
[ P.classes (H.ClassName <$> ["tl-wrapper"]) ]
(intervalHtml st.limits <$> st.history)
]
eval :: TimelineAction ~> H.ComponentDSL TimelineState TimelineAction Void (Aff (TimelineEffects eff))
-- The user has requested to start the clock.
eval (Start next) = do
t <- liftEff now
H.modify (start t)
pure next
-- The user has requested to stop the clock
eval (Stop next) = do
t <- liftEff now
H.modify (stop t)
pure next
-- The runtime system renders a clock tick
eval (Refresh next) = do
t <- liftEff now
H.modify (refresh t)
pure next
-- The user has requested to open a new interval beginning at a
-- specific point on the timeline (mouse down)
-- The user has requested to close the currently open interval
-- at a specific point on the timeline (mouse up)
--
start :: Instant -> TimelineState -> TimelineState
start t s =
{ limits: s.limits
, history: s.history
, active: s.active <|> Just { start: t, end: t }
}
stop :: Instant -> TimelineState -> TimelineState
stop t s =
{ limits: s.limits
, history: maybe s.history (\st -> cons { start: st.start, end: t } s.history) s.active
, active: Nothing
}
refresh :: Instant -> TimelineState -> TimelineState
refresh t s =
{ limits: s.limits
, history: s.history
, active: map (\a -> { start: a.start, end: t }) s.active
}
ilen :: Instant -> Instant -> Number
ilen d d' =
let n (Milliseconds x) = x
in abs $ n (unInstant d) - n (unInstant d')
import Halogen
import Halogen.Util (appendToBody, onLoad)
import Halogen.Util (appendToBody, onLoad)
import qualified Aftok.Login as L
import Halogen.Aff (HalogenEffects)
import Halogen.VDom.Driver (runUI)
import Halogen.Aff as HA
import Aftok.Login as L
main = runAff throwException (const (pure unit)) $ do
app <- runUI L.ui L.initialState
onLoad $ appendToBody app.node
main = HA.runHalogenAff $ do
body <- HA.awaitBody
runUI L.ui L.initialState body