Added button for switching to invite screen

-- | Loaded application ui logic
module Gonimo.UI.Loaded where

import Gonimo.UI.Html
import Data.Array as Arr
import Data.List as List
import Data.Map as Map
import Data.Tuple as Tuple
import Gonimo.Client.Effects as Gonimo
import Gonimo.Client.LocalStorage as Key
import Gonimo.Client.LocalStorage as Key
import Gonimo.Client.Router as Router
import Gonimo.Client.Types as Gonimo
import Gonimo.UI.AcceptInvitation as AcceptC
import Gonimo.UI.Error as Error
import Gonimo.UI.Home as HomeC
import Gonimo.UI.Invite as InviteC
import Gonimo.UI.Socket as SocketC
import Gonimo.UI.Socket.Channel as ChannelC
import Gonimo.UI.Socket.Lenses as SocketC
import Gonimo.UI.Socket.Types as SocketC
import Gonimo.WebAPI.MakeRequests as Reqs
import Gonimo.WebAPI.Subscriber as Sub
import Pux.Html as H
import Pux.Html.Attributes as A
import Pux.Html.Attributes.Bootstrap as A
import Pux.Html.Events as E
import Servant.Subscriber as Sub
import Servant.Subscriber.Connection as Sub
import Browser.LocalStorage (STORAGE, localStorage)
import Control.Alt ((<|>))
import Control.Monad.Aff (Aff)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Except.Trans (runExceptT)
import Control.Monad.Reader (runReader)
import Control.Monad.Reader.Class (class MonadReader)
import Control.Monad.Reader.Trans (runReaderT)
import Control.Monad.State.Class (get, modify, put)
import Data.Argonaut.Generic.Aeson (decodeJson)
import Data.Array (fromFoldable, concat, catMaybes, head)
import Data.Bifunctor (bimap)
import Data.Either (either, Either(Right, Left))
import Data.Foldable (foldl)
import Data.Generic (class Generic)
import Data.Generic (gShow)
import Data.Lens ((.=), to, (^.), (^?))
import Data.Map (Map)
import Data.Maybe (isNothing, maybe, Maybe(..))
import Data.Monoid (mempty)
import Data.Profunctor (lmap)
import Data.Semigroup (append)
import Data.String (takeWhile)
import Data.Traversable (traverse)
import Data.Tuple (uncurry, fst, Tuple(Tuple))
import Debug.Trace (trace)
import Gonimo.Client.Types (toIO, Settings, GonimoError, class ReportErrorAction, Gonimo)
import Gonimo.Pux (Component, toParent, runGonimo, liftChild, ComponentType, makeChildData, ToChild, noEffects, onlyModify, Update, wrapAction)
import Gonimo.Server.DbEntities (Device(Device), Family(Family))
import Gonimo.Server.DbEntities.Helpers (runFamily)
import Gonimo.Server.Error (ServerError(InvalidAuthToken))
import Gonimo.Server.Types (DeviceType(Baby, NoBaby), AuthToken, AuthToken(GonimoSecret))
import Gonimo.Types (dateToString, Key(Key), Secret(Secret))
import Gonimo.UI.Error (viewError, class ErrorAction, UserError(NoError, DeviceInvalid), handleSubscriber, handleError)
import Gonimo.UI.Loaded.Types (familyIds, authData, currentFamily, socketS, homeS, centralHome, Props, acceptS, inviteS, State, Action(..), Central(..))
import Gonimo.UI.Socket (viewParentChannels, getParentChannels)
import Gonimo.Util (toString, fromString)
import Gonimo.WebAPI (deleteOnlineStatusByFamilyIdByDeviceId, SPParams_(SPParams_), postAccounts)
import Gonimo.WebAPI.Types (DeviceInfo(DeviceInfo), AuthData(AuthData))
import Gonimo.WebAPI.Types.Helpers (runAuthData)
import Partial.Unsafe (unsafeCrashWith)
import Pux (renderToDOM, fromSimple, start)
import Pux.Html (text, small, script, li, a, nav, h3, h2, td, tbody, th, tr, thead, table, ul, p, button, input, h1, span, Html, img, div)
import Pux.Html.Attributes (letterSpacing, offset)
import Pux.Html.Events (FormEvent, FocusEvent)
import Pux.Router (navigateTo)
import Servant.PureScript.Affjax (AjaxError)
import Servant.PureScript.Settings (defaultSettings, SPSettings_(SPSettings_))
import Servant.Subscriber (Subscriber)
import Servant.Subscriber.Connection (Notification(HttpRequestFailed, ParseError, WebSocketClosed, WebSocketError))
import Servant.Subscriber.Internal (doDecode, coerceEffects)
import Servant.Subscriber.Request (HttpRequest(HttpRequest))
import Servant.Subscriber.Response (HttpResponse(HttpResponse))
import Servant.Subscriber.Subscriptions (Subscriptions)
import Servant.Subscriber.Util (toUserType)
import Signal (constant, Signal)
import Prelude hiding (div)

update :: Update Unit State Action
update (SetState state)                    = put state *> pure []
update (ReportError err)                   = handleError err
update (InviteA (InviteC.ReportError err)) = handleError err
update (InviteA action)                    = updateInvite action
update (AcceptA (AcceptC.ReportError err)) = handleError err
update (AcceptA action)                    = updateAccept action
update (SocketA (SocketC.ReportError err)) = handleError err
update (SocketA action)                    = updateSocket action
update (HomeA action)                      = updateHome action
update (SetFamilyIds ids)                  = handleSetFamilyIds ids
update (UpdateFamily familyId' family')    = onlyModify $ \state ->  state { families = Map.insert familyId' family' state.families }
update (SetCentral c)                      = onlyModify $ _ { central = c }
update (SetOnlineDevices devices)          = onlyModify $ _ { onlineDevices = devices }
update (SetDeviceInfos devices)            = onlyModify $ _ { deviceInfos = devices }
update (SetURL url)                        = handleSetURL url
update (HandleSubscriber msg)              = handleSubscriber msg
update ResetDevice                         = handleResetDevice
update ClearError                          = handleClearError
update Nop                                 = noEffects
update _                                   = noEffects

toInvite :: ToChild Unit State Props InviteC.State
toInvite = do
  props <- mkProps <$> get
  pure $ makeChildData inviteS props

toAccept :: ToChild Unit State Props AcceptC.State
toAccept = do
  props <- mkProps <$> get
  pure $ makeChildData acceptS props

toHome :: ToChild Unit State Props HomeC.State
toHome = do
  props <- mkProps <$> get
  pure $ makeChildData homeS props

toSocket :: ToChild Unit State Props SocketC.State
toSocket = do
  props <- mkProps <$> get
  pure $ makeChildData socketS props

updateInvite :: InviteC.Action -> ComponentType Unit State Action
updateInvite = toParent [] InviteA <<< liftChild toInvite <<< InviteC.update

updateAccept :: AcceptC.Action -> ComponentType Unit State Action
updateAccept = toParent [] AcceptA <<< liftChild toAccept <<< AcceptC.update

updateHome :: HomeC.Action -> ComponentType Unit State Action
updateHome action = case action of
    HomeC.SocketA socketA -> updateSocket socketA
    _                     -> toParent [] HomeA <<< liftChild toHome $ HomeC.update action

updateSocket :: SocketC.Action -> ComponentType Unit State Action
updateSocket action = do
  case action of
    SocketC.SetAuthData _ -> handleSetAuthData
    _ -> pure unit
  toParent [] SocketA <<< liftChild toSocket $ SocketC.update action

inviteEffect :: forall m. Monad m => Secret -> m Action
inviteEffect = pure <<< AcceptA <<< AcceptC.LoadInvitation

handleResetDevice :: ComponentType Unit State Action
handleResetDevice = do
  props <- mkProps <$> get :: Component Unit State State
  pure $ [ toIO props.settings $ do
              liftEff $ localStorage.removeItem Key.authData
              SocketA <<< SocketC.SetAuthData <$> getAuthData
         ]

handleSetAuthData :: Component Unit State Unit
handleSetAuthData = modify $ \state -> state
                                  {
                                   userError = case state.userError of
                                      DeviceInvalid -> NoError
                                      _ -> state.userError
                                  }
handleSetFamilyIds :: Array (Key Family) -> ComponentType Unit State Action
handleSetFamilyIds ids = do
  familyIds .= ids
  state <- get :: Component Unit State State
  if isNothing state.socketS.currentFamily
    then pure $ fromFoldable $ pure <<< SocketA <<< SocketC.SwitchFamily <$> Arr.head ids
    else noEffects

handleSetURL :: String -> ComponentType Unit State Action
handleSetURL url = do
  let
    route = Router.match url
    withoutQuery = takeWhile (_ /= '?') url
  case route of
    Router.Home -> onlyModify $ _ { url = url }
    Router.AcceptInvitation s -> do
      modify $ _ { url = withoutQuery
                 , central = CentralAccept
                 }
      pure [ do
              liftEff $ navigateTo withoutQuery
              inviteEffect s
            ]

handleClearError :: ComponentType Unit State Action
handleClearError = do
  state <- (get :: Component Unit State State)
  let
    newCentral = case state.central of
      CentralAccept -> case state.acceptS of
        Nothing  -> centralHome
        Just _ -> state.central
      _ -> state.central

  onlyModify $ _ { userError = NoError
                 , central = newCentral
                 }

--------------------------------------------------------------------------------

view :: State -> Html Action
view state =
  let
    numDevices = Arr.length state.onlineDevices
  in
    case state.userError of
      NoError ->
        div []
        [ viewHeader state
        , viewNavbar state
        , viewCentral state
        , div []
          [ h3 [] [ text "Videos - yeah:" ]
          , div [] $ map SocketA <$> viewParentChannels state.socketS
          ]
        , div []
          [ h3 [] [ text $ show numDevices <> " Device(s) currently online:" ]
          , div [] [ viewOnlineDevices state ]
          ]
        ]
      err -> viewError state

viewHeader :: State -> Html Action
viewHeader state =
      div [ A.className "page-header" ]
      [ h1 []
        [ text "gonimo.com "
        , small [] [ text "Good Night Monitor!" ]
        ]
      ]

viewNavbar :: State -> Html Action
viewNavbar state =
       nav [ A.className ".navbar .navbar-default" ]
        [ div [ A.className "container-fluid" ]
          [ -- Brand and toggle get grouped for better mobile display
            div [ A.className "navbar-header" ]
            [ button [ A.type_ "button"
                     , A.className "navbar-toggle collapsed"
                     , A.dataToggle "collapse"
                     , A.dataTarget "navbar-collapse-1"
                     ]
              [ span [ A.className "sr-only" ] [ text "Toggle navigation" ]
              , span [ A.className "icon-bar" ] []
              , span [ A.className "icon-bar" ] []
              , span [ A.className "icon-bar" ] []
              ]
            , a [ A.className "navbar-brand"
                , A.role "button"
                , E.onClick $ const $ SetCentral centralHome
                ]
              [ text "Home" ]
            , a [ A.className "navbar-brand"
                , A.role "button"
                , E.onClick $ const $ SetCentral CentralInvite
                ]
              [ text "Invite" ]
            ]
          , -- Collect the nav links, forms, and other content for toggling
            div [ A.className "collapse navbar-collapse", A.id_ "navbar-collapse-1" ]
            [
              ul [ A.className "nav navbar-nav" ]
              [ li []
                [ viewFamilyChooser state
                ]
              ]
            , ul [ A.className "nav navbar-nav navbar-right" ]
              [ li [ A.className "dropdown" ]
                [ a [ A.className "dropdown-toggle"
                         , A.dataToggle "dropdown"
                         , A.role "button"
                         ]
                  [ text "Account"
                  , span [ A.className "carret" ] []
                  ]
                  , ul [ A.className "dropdown-menu" ]
                    [ li []
                      [ a [ A.href "#" ] [ text "Configure User Details" ] ]
                    , li []
                      [ a [ A.href "#" ] [ text "change password" ] ]
                    , li [ A.role "separator",  A.className "divider" ] []
                    , li []
                      [ a [ A.href "#" ] [ text "Log out" ] ]
                    ]
                  ]
                ]
              ]
            ]
          ]

viewCentral :: State -> Html Action
viewCentral state =
  let
    props = mkProps state
  in
   case state.central of
    CentralInvite -> map InviteA $ InviteC.view props state.inviteS
    CentralAccept -> map AcceptA $ AcceptC.view state.acceptS
    CentralHome   -> map HomeA   $ HomeC.view props state.homeS

viewOnlineDevices :: State -> Html Action
viewOnlineDevices state = table [ A.className "table table-stripped"]
                          [ head
                          , body
                          ]
  where
    head = thead []
           [ tr []
             [ th [] [ text "Name" ]
             , th [] [ text "Last Accessed"]
             ]
           ]
    body = tbody []
           <<< Arr.mapMaybe viewOnlineDevice
           $ map fst state.onlineDevices
    viewOnlineDevice deviceId = do
        (DeviceInfo info) <- Tuple.lookup deviceId state.deviceInfos
        let name = info.deviceInfoName
        let lastAccessed = dateToString info.deviceInfoLastAccessed
        pure $ tr []
               [ td [] [ text name ]
               , td [] [ text lastAccessed ]
               ]

viewFamilyChooser :: State -> Html Action
viewFamilyChooser state = H.div []
                          [ H.label [ A.htmlFor "familySelect" ] [ text "Family: " ]
                          , H.select [ A.id_ "familySelect"
                                     , A.className "form-control"
                                     , E.onInput doSwitchFamily
                                     ]
                            $ fromFoldable
                            <<< map (uncurry makeOption)
                            <<< Map.toList $ state.families
                          ]
  where
    doSwitchFamily :: FormEvent -> Action
    doSwitchFamily ev = maybe Nop (SocketA <<< SocketC.SwitchFamily) $ fromString ev.target.value

    makeOption :: Key Family -> Family -> Html Action
    makeOption familyId (Family family) = H.option [ A.value (toString familyId) ]
                                          [ text family.familyName ]
--------------------------------------------------------------------------------
getSubscriptions :: State -> Subscriptions Action
getSubscriptions state =
  let
    familyId = state^?currentFamily

    --subscribeGetFamily :: forall m. MonadReader Settings m => Key Family -> m (Subscriptions Action)
    subscribeGetFamily familyId' =
      Sub.getFamiliesByFamilyId (maybe Nop (UpdateFamily familyId')) familyId'

    subArray :: Array (Subscriptions Action)
    subArray = map (flip runReader (mkSettings $ state^.authData)) <<< concat $
      [ [ Sub.getAccountsByAccountIdFamilies (maybe Nop SetFamilyIds)
                                          (runAuthData $ state^.authData).accountId
        ]
      , fromFoldable $
        Sub.getOnlineStatusByFamilyId (maybe Nop SetOnlineDevices) <$> familyId
      , fromFoldable $
        Sub.getFamiliesByFamilyIdDeviceInfos (maybe Nop SetDeviceInfos) <$> familyId
      , map subscribeGetFamily state.familyIds
      ]
  in
   case state.userError of
     NoError -> foldl append mempty subArray <> map SocketA (SocketC.getSubscriptions (mkProps state) state.socketS)
     _       -> mempty

getPongRequest :: State -> Maybe HttpRequest
getPongRequest state =
  let
    deviceId = (runAuthData $ state^.authData).deviceId
    onlineStatus' = state ^. socketS <<< SocketC.onlineStatus <<< to SocketC.toDeviceType
    deviceData = Tuple deviceId onlineStatus'
    familyId = state^?currentFamily
  in
   case state.userError of
     NoError -> flip runReader (mkSettings $ state^.authData) <<< Reqs.postOnlineStatusByFamilyId deviceData <$> familyId
     _ -> Nothing

getCloseRequest :: State -> Maybe HttpRequest
getCloseRequest state =
  let
    deviceId = (runAuthData $ state^.authData).deviceId
    familyId = state^?currentFamily
  in
   case state.userError of
     NoError -> flip runReader (mkSettings $ state^.authData) <<< flip Reqs.deleteOnlineStatusByFamilyIdByDeviceId deviceId <$> familyId
     _ -> Nothing

-- | Retrieve AuthData from local storage or if not present get new one from server
getAuthData :: Gonimo AuthData
getAuthData = do
  md <- liftEff $ localStorage.getItem Key.authData
  Gonimo.log $ "Got authdata from local storage: " <> gShow md
  case md of
    Nothing -> do
      auth <- postAccounts
      Gonimo.log $ "Got Nothing - called postAccounts and got: " <> gShow auth
      Gonimo.log $ "Calling setItem with : " <> gShow Key.authData
      liftEff $ localStorage.setItem Key.authData auth
      pure auth
    Just d  -> pure d

mkProps :: State -> Props
mkProps state = { settings : mkSettings $ state^.authData
                , deviceId : (runAuthData $ state^.authData).deviceId
                , familyId : state^?currentFamily
                , onlineStatus  : state ^. socketS <<< SocketC.onlineStatus <<< to SocketC.toDeviceType
                , family : flip Map.lookup state.families =<< state^?currentFamily
                , onlineDevices : state.onlineDevices
                , deviceInfos : state.deviceInfos
                , sendActionSocket : lmap SocketA state.sendAction
                }

mkSettings :: AuthData -> Settings
mkSettings (AuthData auth) = defaultSettings $ SPParams_ {
      authorization : auth.authToken
    , baseURL       : "http://localhost:8081/"
    }

-- | Loaded application ui logic
module Gonimo.UI.Loaded where

import Gonimo.UI.Html
import Data.Array as Arr
import Data.List as List
import Data.Map as Map
import Data.Tuple as Tuple
import Gonimo.Client.Effects as Gonimo
import Gonimo.Client.LocalStorage as Key
import Gonimo.Client.LocalStorage as Key
import Gonimo.Client.Router as Router
import Gonimo.Client.Types as Gonimo
import Gonimo.UI.AcceptInvitation as AcceptC
import Gonimo.UI.Error as Error
import Gonimo.UI.Home as HomeC
import Gonimo.UI.Invite as InviteC
import Gonimo.UI.Socket as SocketC
import Gonimo.UI.Socket.Channel as ChannelC
import Gonimo.UI.Socket.Lenses as SocketC
import Gonimo.UI.Socket.Types as SocketC
import Gonimo.WebAPI.MakeRequests as Reqs
import Gonimo.WebAPI.Subscriber as Sub
import Pux.Html as H
import Pux.Html.Attributes as A
import Pux.Html.Attributes.Bootstrap as A
import Pux.Html.Events as E
import Servant.Subscriber as Sub
import Servant.Subscriber.Connection as Sub
import Browser.LocalStorage (STORAGE, localStorage)
import Control.Alt ((<|>))
import Control.Monad.Aff (Aff)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Except.Trans (runExceptT)
import Control.Monad.Reader (runReader)
import Control.Monad.Reader.Class (class MonadReader)
import Control.Monad.Reader.Trans (runReaderT)
import Control.Monad.State.Class (get, modify, put)
import Data.Argonaut.Generic.Aeson (decodeJson)
import Data.Array (fromFoldable, concat, catMaybes, head)
import Data.Bifunctor (bimap)
import Data.Either (either, Either(Right, Left))
import Data.Foldable (foldl)
import Data.Generic (class Generic)
import Data.Generic (gShow)
import Data.Lens ((.=), to, (^.), (^?))
import Data.Map (Map)
import Data.Maybe (isNothing, maybe, Maybe(..))
import Data.Monoid (mempty)
import Data.Profunctor (lmap)
import Data.Semigroup (append)
import Data.String (takeWhile)
import Data.Traversable (traverse)
import Data.Tuple (uncurry, fst, Tuple(Tuple))
import Debug.Trace (trace)
import Gonimo.Client.Types (toIO, Settings, GonimoError, class ReportErrorAction, Gonimo)
import Gonimo.Pux (Component, toParent, runGonimo, liftChild, ComponentType, makeChildData, ToChild, noEffects, onlyModify, Update, wrapAction)
import Gonimo.Server.DbEntities (Device(Device), Family(Family))
import Gonimo.Server.DbEntities.Helpers (runFamily)
import Gonimo.Server.Error (ServerError(InvalidAuthToken))
import Gonimo.Server.Types (DeviceType(Baby, NoBaby), AuthToken, AuthToken(GonimoSecret))
import Gonimo.Types (dateToString, Key(Key), Secret(Secret))
import Gonimo.UI.Error (viewError, class ErrorAction, UserError(NoError, DeviceInvalid), handleSubscriber, handleError)
import Gonimo.UI.Loaded.Types (familyIds, authData, currentFamily, socketS, homeS, centralHome, Props, acceptS, inviteS, State, Action(..), Central(..))
import Gonimo.UI.Socket (viewParentChannels, getParentChannels)
import Gonimo.Util (toString, fromString)
import Gonimo.WebAPI (deleteOnlineStatusByFamilyIdByDeviceId, SPParams_(SPParams_), postAccounts)
import Gonimo.WebAPI.Types (DeviceInfo(DeviceInfo), AuthData(AuthData))
import Gonimo.WebAPI.Types.Helpers (runAuthData)
import Partial.Unsafe (unsafeCrashWith)
import Pux (renderToDOM, fromSimple, start)
import Pux.Html (text, small, script, li, a, nav, h3, h2, td, tbody, th, tr, thead, table, ul, p, button, input, h1, span, Html, img, div)
import Pux.Html.Attributes (letterSpacing, offset)
import Pux.Html.Events (FormEvent, FocusEvent)
import Pux.Router (navigateTo)
import Servant.PureScript.Affjax (AjaxError)
import Servant.PureScript.Settings (defaultSettings, SPSettings_(SPSettings_))
import Servant.Subscriber (Subscriber)
import Servant.Subscriber.Connection (Notification(HttpRequestFailed, ParseError, WebSocketClosed, WebSocketError))
import Servant.Subscriber.Internal (doDecode, coerceEffects)
import Servant.Subscriber.Request (HttpRequest(HttpRequest))
import Servant.Subscriber.Response (HttpResponse(HttpResponse))
import Servant.Subscriber.Subscriptions (Subscriptions)
import Servant.Subscriber.Util (toUserType)
import Signal (constant, Signal)
import Prelude hiding (div)

update :: Update Unit State Action
update (SetState state)                    = put state *> pure []
update (ReportError err)                   = handleError err
update (InviteA (InviteC.ReportError err)) = handleError err
update (InviteA action)                    = updateInvite action
update (AcceptA (AcceptC.ReportError err)) = handleError err
update (AcceptA action)                    = updateAccept action
update (SocketA (SocketC.ReportError err)) = handleError err
update (SocketA action)                    = updateSocket action
update (HomeA action)                      = updateHome action
update (SetFamilyIds ids)                  = handleSetFamilyIds ids
update (UpdateFamily familyId' family')    = onlyModify $ \state ->  state { families = Map.insert familyId' family' state.families }
update (SetCentral c)                      = onlyModify $ _ { central = c }
update (SetOnlineDevices devices)          = onlyModify $ _ { onlineDevices = devices }
update (SetDeviceInfos devices)            = onlyModify $ _ { deviceInfos = devices }
update (SetURL url)                        = handleSetURL url
update (HandleSubscriber msg)              = handleSubscriber msg
update ResetDevice                         = handleResetDevice
update ClearError                          = handleClearError
update Nop                                 = noEffects
update _                                   = noEffects

toInvite :: ToChild Unit State Props InviteC.State
toInvite = do
  props <- mkProps <$> get
  pure $ makeChildData inviteS props

toAccept :: ToChild Unit State Props AcceptC.State
toAccept = do
  props <- mkProps <$> get
  pure $ makeChildData acceptS props

toHome :: ToChild Unit State Props HomeC.State
toHome = do
  props <- mkProps <$> get
  pure $ makeChildData homeS props

toSocket :: ToChild Unit State Props SocketC.State
toSocket = do
  props <- mkProps <$> get
  pure $ makeChildData socketS props

updateInvite :: InviteC.Action -> ComponentType Unit State Action
updateInvite = toParent [] InviteA <<< liftChild toInvite <<< InviteC.update

updateAccept :: AcceptC.Action -> ComponentType Unit State Action
updateAccept = toParent [] AcceptA <<< liftChild toAccept <<< AcceptC.update

updateHome :: HomeC.Action -> ComponentType Unit State Action
updateHome action = case action of
    HomeC.SocketA socketA -> updateSocket socketA
    _                     -> toParent [] HomeA <<< liftChild toHome $ HomeC.update action

updateSocket :: SocketC.Action -> ComponentType Unit State Action
updateSocket action = do
  case action of
    SocketC.SetAuthData _ -> handleSetAuthData
    _ -> pure unit
  toParent [] SocketA <<< liftChild toSocket $ SocketC.update action

inviteEffect :: forall m. Monad m => Secret -> m Action
inviteEffect = pure <<< AcceptA <<< AcceptC.LoadInvitation

handleResetDevice :: ComponentType Unit State Action
handleResetDevice = do
  props <- mkProps <$> get :: Component Unit State State
  pure $ [ toIO props.settings $ do
              liftEff $ localStorage.removeItem Key.authData
              SocketA <<< SocketC.SetAuthData <$> getAuthData
         ]

handleSetAuthData :: Component Unit State Unit
handleSetAuthData = modify $ \state -> state
                                  {
                                   userError = case state.userError of
                                      DeviceInvalid -> NoError
                                      _ -> state.userError
                                  }
handleSetFamilyIds :: Array (Key Family) -> ComponentType Unit State Action
handleSetFamilyIds ids = do
  familyIds .= ids
  state <- get :: Component Unit State State
  if isNothing state.socketS.currentFamily
    then pure $ fromFoldable $ pure <<< SocketA <<< SocketC.SwitchFamily <$> Arr.head ids
    else noEffects

handleSetURL :: String -> ComponentType Unit State Action
handleSetURL url = do
  let
    route = Router.match url
    withoutQuery = takeWhile (_ /= '?') url
  case route of
    Router.Home -> onlyModify $ _ { url = url }
    Router.AcceptInvitation s -> do
      modify $ _ { url = withoutQuery
                 , central = CentralAccept
                 }
      pure [ do
              liftEff $ navigateTo withoutQuery
              inviteEffect s
            ]

handleClearError :: ComponentType Unit State Action
handleClearError = do
  state <- (get :: Component Unit State State)
  let
    newCentral = case state.central of
      CentralAccept -> case state.acceptS of
        Nothing  -> centralHome
        Just _ -> state.central
      _ -> state.central

  onlyModify $ _ { userError = NoError
                 , central = newCentral
                 }

--------------------------------------------------------------------------------

view :: State -> Html Action
view state =
  let
    numDevices = Arr.length state.onlineDevices
  in
    case state.userError of
      NoError ->
        div []
        [ viewHeader state
        , viewNavbar state
        , viewCentral state
        , div []
          [ h3 [] [ text "Videos - yeah:" ]
          , div [] $ map SocketA <$> viewParentChannels state.socketS
          ]
        , div []
          [ h3 [] [ text $ show numDevices <> " Device(s) currently online:" ]
          , div [] [ viewOnlineDevices state ]
          ]
        ]
      err -> viewError state

viewHeader :: State -> Html Action
viewHeader state =
      div [ A.className "page-header" ]
      [ h1 []
        [ text "gonimo.com "
        , small [] [ text "Good Night Monitor!" ]
        ]
      ]

viewNavbar :: State -> Html Action
viewNavbar state =
       nav [ A.className ".navbar .navbar-default" ]
        [ div [ A.className "container-fluid" ]
          [ -- Brand and toggle get grouped for better mobile display
            div [ A.className "navbar-header" ]
            [ button [ A.type_ "button"
                     , A.className "navbar-toggle collapsed"
                     , A.dataToggle "collapse"
                     , A.dataTarget "navbar-collapse-1"
                     ]
              [ span [ A.className "sr-only" ] [ text "Toggle navigation" ]
              , span [ A.className "icon-bar" ] []
              , span [ A.className "icon-bar" ] []
              , span [ A.className "icon-bar" ] []
              ]
            , a [ A.className "navbar-brand"
                , A.role "button"
                , E.onClick $ const $ SetCentral centralHome
                ]
              [ text "Home" ]
            ]
          , -- Collect the nav links, forms, and other content for toggling
            div [ A.className "collapse navbar-collapse", A.id_ "navbar-collapse-1" ]
            [
              ul [ A.className "nav navbar-nav" ]
              [ li []
                [ viewFamilyChooser state
                ]
              ]
            , ul [ A.className "nav navbar-nav navbar-right" ]
              [ li [ A.className "dropdown" ]
                [ a [ A.className "dropdown-toggle"
                         , A.dataToggle "dropdown"
                         , A.role "button"
                         ]
                  [ text "Account"
                  , span [ A.className "carret" ] []
                  ]
                  , ul [ A.className "dropdown-menu" ]
                    [ li []
                      [ a [ A.href "#" ] [ text "Configure User Details" ] ]
                    , li []
                      [ a [ A.href "#" ] [ text "change password" ] ]
                    , li [ A.role "separator",  A.className "divider" ] []
                    , li []
                      [ a [ A.href "#" ] [ text "Log out" ] ]
                    ]
                  ]
                ]
              ]
            ]
          ]

viewCentral :: State -> Html Action
viewCentral state =
  let
    props = mkProps state
  in
   case state.central of
    CentralInvite -> map InviteA $ InviteC.view props state.inviteS
    CentralAccept -> map AcceptA $ AcceptC.view state.acceptS
    CentralHome   -> map HomeA   $ HomeC.view props state.homeS

viewOnlineDevices :: State -> Html Action
viewOnlineDevices state = table [ A.className "table table-stripped"]
                          [ head
                          , body
                          ]
  where
    head = thead []
           [ tr []
             [ th [] [ text "Name" ]
             , th [] [ text "Last Accessed"]
             ]
           ]
    body = tbody []
           <<< Arr.mapMaybe viewOnlineDevice
           $ map fst state.onlineDevices
    viewOnlineDevice deviceId = do
        (DeviceInfo info) <- Tuple.lookup deviceId state.deviceInfos
        let name = info.deviceInfoName
        let lastAccessed = dateToString info.deviceInfoLastAccessed
        pure $ tr []
               [ td [] [ text name ]
               , td [] [ text lastAccessed ]
               ]

viewFamilyChooser :: State -> Html Action
viewFamilyChooser state = H.div []
                          [ H.label [ A.htmlFor "familySelect" ] [ text "Family: " ]
                          , H.select [ A.id_ "familySelect"
                                     , A.className "form-control"
                                     , E.onInput doSwitchFamily
                                     ]
                            $ fromFoldable
                            <<< map (uncurry makeOption)
                            <<< Map.toList $ state.families
                          ]
  where
    doSwitchFamily :: FormEvent -> Action
    doSwitchFamily ev = maybe Nop (SocketA <<< SocketC.SwitchFamily) $ fromString ev.target.value

    makeOption :: Key Family -> Family -> Html Action
    makeOption familyId (Family family) = H.option [ A.value (toString familyId) ]
                                          [ text family.familyName ]
--------------------------------------------------------------------------------
getSubscriptions :: State -> Subscriptions Action
getSubscriptions state =
  let
    familyId = state^?currentFamily

    --subscribeGetFamily :: forall m. MonadReader Settings m => Key Family -> m (Subscriptions Action)
    subscribeGetFamily familyId' =
      Sub.getFamiliesByFamilyId (maybe Nop (UpdateFamily familyId')) familyId'

    subArray :: Array (Subscriptions Action)
    subArray = map (flip runReader (mkSettings $ state^.authData)) <<< concat $
      [ [ Sub.getAccountsByAccountIdFamilies (maybe Nop SetFamilyIds)
                                          (runAuthData $ state^.authData).accountId
        ]
      , fromFoldable $
        Sub.getOnlineStatusByFamilyId (maybe Nop SetOnlineDevices) <$> familyId
      , fromFoldable $
        Sub.getFamiliesByFamilyIdDeviceInfos (maybe Nop SetDeviceInfos) <$> familyId
      , map subscribeGetFamily state.familyIds
      ]
  in
   case state.userError of
     NoError -> foldl append mempty subArray <> map SocketA (SocketC.getSubscriptions (mkProps state) state.socketS)
     _       -> mempty

getPongRequest :: State -> Maybe HttpRequest
getPongRequest state =
  let
    deviceId = (runAuthData $ state^.authData).deviceId
    onlineStatus' = state ^. socketS <<< SocketC.onlineStatus <<< to SocketC.toDeviceType
    deviceData = Tuple deviceId onlineStatus'
    familyId = state^?currentFamily
  in
   case state.userError of
     NoError -> flip runReader (mkSettings $ state^.authData) <<< Reqs.postOnlineStatusByFamilyId deviceData <$> familyId
     _ -> Nothing

getCloseRequest :: State -> Maybe HttpRequest
getCloseRequest state =
  let
    deviceId = (runAuthData $ state^.authData).deviceId
    familyId = state^?currentFamily
  in
   case state.userError of
     NoError -> flip runReader (mkSettings $ state^.authData) <<< flip Reqs.deleteOnlineStatusByFamilyIdByDeviceId deviceId <$> familyId
     _ -> Nothing

-- | Retrieve AuthData from local storage or if not present get new one from server
getAuthData :: Gonimo AuthData
getAuthData = do
  md <- liftEff $ localStorage.getItem Key.authData
  Gonimo.log $ "Got authdata from local storage: " <> gShow md
  case md of
    Nothing -> do
      auth <- postAccounts
      Gonimo.log $ "Got Nothing - called postAccounts and got: " <> gShow auth
      Gonimo.log $ "Calling setItem with : " <> gShow Key.authData
      liftEff $ localStorage.setItem Key.authData auth
      pure auth
    Just d  -> pure d

mkProps :: State -> Props
mkProps state = { settings : mkSettings $ state^.authData
                , deviceId : (runAuthData $ state^.authData).deviceId
                , familyId : state^?currentFamily
                , onlineStatus  : state ^. socketS <<< SocketC.onlineStatus <<< to SocketC.toDeviceType
                , family : flip Map.lookup state.families =<< state^?currentFamily
                , onlineDevices : state.onlineDevices
                , deviceInfos : state.deviceInfos
                , sendActionSocket : lmap SocketA state.sendAction
                }

mkSettings :: AuthData -> Settings
mkSettings (AuthData auth) = defaultSettings $ SPParams_ {
      authorization : auth.authToken
    , baseURL       : "http://localhost:8081/"
    }


Fix test

Fix test

module Hyper.FormSpec where

import Prelude
import Control.Monad.Aff (Aff)
import Control.Monad.Eff.Exception (Error)
import Control.Monad.Error.Class (throwError)
import Data.Either (Either(..))
import Data.StrMap (singleton)
import Data.Tuple (Tuple(Tuple))
import Hyper.Form (Form(Form), parseForm)
import Test.Spec (Spec, it, describe)
import Test.Spec.Assertions (shouldEqual)
import Test.Spec.Assertions.Aff (expectError)

liftEither ∷ ∀ e a. Either Error a → Aff e a
liftEither e =
  case e of
    Left err → throwError err
    Right x → pure x

spec :: forall e. Spec e Unit
spec =
  describe "Hyper.Form" do
    it "can parse the request body as a form" do
      form <- parseForm
              { request: { body: "foo=bar"
                         , headers: singleton "content-type" "application/x-www-form-urlencoded; charset=utf8"
                         }
              , response: {}
              , components: {}
              }
              # map _.request.body
              >>= liftEither
      form `shouldEqual` (Form [Tuple "foo" "bar"])

    it "fails to parse request body as a form when invalid" $ expectError do
      parseForm { request: { body: "foo=bar=baz"
                           , headers: singleton "content-type" "application/x-www-form-urlencoded; charset=utf8"
                           }
                , response: {}
                , components: {}
                }
        # map _.request.body
        >>= liftEither

module Hyper.FormSpec where

import Prelude
import Control.Monad.Aff (Aff)
import Control.Monad.Eff.Exception (Error)
import Control.Monad.Error.Class (throwError)
import Data.Either (Either(..))
import Data.Tuple (Tuple(Tuple))
import Hyper.Form (Form(Form), parseForm)
import Test.Spec (Spec, it, describe)
import Test.Spec.Assertions (shouldEqual)
import Test.Spec.Assertions.Aff (expectError)

liftEither ∷ ∀ e a. Either Error a → Aff e a
liftEither e =
  case e of
    Left err → throwError err
    Right x → pure x

spec :: forall e. Spec e Unit
spec =
  describe "Hyper.Form" do
    it "can parse the request body as a form" do
      conn <- parseForm
              { request: { body: "foo=bar"
                           -- Headers required by FormParser are 'content-type'
                           -- and 'content-length'.
                         , headers: { "content-type": "application/x-www-form-urlencoded; charset=utf8"
                                    , "content-length": "7"
                                      -- Other headers are OK too.
                                    , "host": "localhost"
                                    , "user-agent": "test"
                                    }
                         }
              , response: {}
              , components: {}
              }
              >>= liftEither
      conn.request.body `shouldEqual` (Form [Tuple "foo" "bar"])

    it "fails to parse request body as a form when invalid" $ expectError do
      parseForm { request: { body: "foo=bar=baz"
                             -- Headers required by formParser (content-type,
                             -- content-length):
                           , headers: { "content-type": "application/x-www-form-urlencoded; charset=utf8"
                                      , "content-length": "11"
                                      }
                           }
                , response: {}
                , components: {}
                }
        >>= liftEither


Swift deserializer helper for top-levels

Original commit 4633ccb78910b67dd39e3e9863943d466a8c5736

module Language.Swift
    ( renderer
    ) where

import Doc
import IRGraph
import Prelude

import Data.Array as A
import Data.Char.Unicode (isAlphaNum, isDigit)
import Data.Foldable (for_)
import Data.FoldableWithIndex (forWithIndex_)
import Data.Map (Map)
import Data.Map as M
import Data.Maybe (Maybe(..))
import Data.Set (Set)
import Data.Set as S
import Data.String.Util (camelCase, capitalize, decapitalize, legalizeCharacters, startWithLetter, stringEscape)
import Data.Tuple (Tuple(..))
import Utils (removeElement)

keywords :: Array String
keywords =
    [ "associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var"
    , "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while"
    , "as", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try"
    , "_"
    , "associativity", "convenience", "dynamic", "didSet", "final", "get", "infix", "indirect", "lazy", "left", "mutating", "none", "nonmutating", "optional", "override", "postfix", "precedence", "prefix", "Protocol", "required", "right", "set", "Type", "unowned", "weak", "willSet"
    , "String", "Int", "Double", "Bool"
    , "checkNull", "removeNSNull", "nilToNSNull", "convertArray", "convertOptional", "convertDict", "convertDouble"
    ]

renderer :: Renderer
renderer =
    { name: "Swift"
    , aceMode: "swift"
    , extension: "swift"
    , doc: swiftDoc
    , transforms:
        { nameForClass: simpleNamer nameForClass
        , nextName: \s -> "Other" <> s
        , forbiddenNames: keywords
        , topLevelName: noForbidNamer (swiftNameStyle true)
        , unions: Just
            { predicate: unionIsNotSimpleNullable
            , properName: simpleNamer (swiftNameStyle true <<< combineNames)
            , nameFromTypes: simpleNamer (unionNameIntercalated (swiftNameStyle true) "Or")
            }
        }
    }

swiftNameStyle :: Boolean -> String -> String
swiftNameStyle isUpper =
    legalizeCharacters isPartCharacter >>> camelCase >>> startWithLetter isStartCharacter isUpper
    where
        isStartCharacter :: Char -> Boolean
        isStartCharacter c = c == '_' || (isAlphaNum c && not (isDigit c))

        isPartCharacter :: Char -> Boolean
        isPartCharacter c = c == '_' || isAlphaNum c

nameForClass :: IRClassData -> String
nameForClass (IRClassData { names }) = swiftNameStyle true $ combineNames names

swiftDoc :: Doc Unit
swiftDoc = do
    line "import Foundation"
    forEachTopLevel_ \topLevelName topLevelType -> do
        blank
        rendered <- renderType topLevelType
        line $ "func " <> (decapitalize topLevelName) <> "(fromData data: Data) -> " <> rendered <> "? {"
        indent do
            line "if let json = try? JSONSerialization.jsonObject(with: data, options: []) {"
            indent do
                convertCode <- convertAny topLevelType "json"
                line $ "return " <> convertCode
            line "}"
            line "return nil"
        line "}"
    forEachClass_ \className properties -> do
        blank
        renderClassDefinition className properties
    forEachUnion_ \unionName unionTypes -> do
        blank
        renderUnionDefinition unionName unionTypes
    blank
    line """func convertArray<T>(converter: (Any) -> T?, json: Any) -> [T]? {
    guard let jsonArr = json as? [Any] else { return nil }
    var arr: [T] = []
    for v in jsonArr {
        if let converted = converter(v) {
            arr.append(converted)
        } else {
            return nil
        }
    }
    return arr
}

func convertOptional<T>(converter: (Any) -> T?, json: Any?) -> T?? {
    guard let v = json
    else {
        return Optional.some(nil)
    }
    return converter(v)
}

func convertDict<T>(converter: (Any) -> T?, json: Any?) -> [String: T]? {
    guard let jsonDict = json as? [String: Any] else { return nil }
    var dict: [String: T] = [:]
    for (k, v) in jsonDict {
        if let converted = converter(v) {
            dict[k] = converted
        } else {
            return nil
        }
    }
    return dict
}

func convertDouble(_ v: Any) -> Double? {
    if let i = v as? Int {
        return Double(i)
    }
    if let d = v as? Double {
        return d
    }
    return nil
}

func removeNSNull(_ v: Any?) -> Any? {
    if let w = v {
        if w is NSNull {
            return nil
        }
        return w
    }
    return nil
}

func checkNull(_ v: Any?) -> Any?? {
    if v != nil {
        return Optional.none
    }
    return Optional.some(nil)
}"""

renderUnion :: IRUnionRep -> Doc String
renderUnion ur =
    case nullableFromSet $ unionToSet ur of
    Just r -> do
        rendered <- renderType r
        pure $ rendered <> "?"
    Nothing -> lookupUnionName ur

renderType :: IRType -> Doc String
renderType = case _ of
    IRNothing -> pure "Any?"
    IRNull -> pure "Any?"
    IRInteger -> pure "Int"
    IRDouble -> pure "Double"
    IRBool -> pure "Bool"
    IRString -> pure "String"
    IRArray a -> do
        rendered <- renderType a
        pure $ "[" <> rendered <> "]"
    IRClass i -> lookupClassName i
    IRMap t -> do
        rendered <- renderType t
        pure $ "[String: " <> rendered <> "]"
    IRUnion ur -> renderUnion ur

convertAnyFunc :: IRType -> Doc String
convertAnyFunc (IRClass i) = do
    name <- lookupClassName i
    pure $ name <> ".init"
convertAnyFunc (IRUnion ur) = do
    name <- lookupUnionName ur
    pure $ name <> ".fromJson"
convertAnyFunc IRDouble =
    pure "convertDouble"
convertAnyFunc IRNull =
    pure "checkNull"
convertAnyFunc t = do
    converted <- convertAny t "$0"
    pure $ "{ " <> converted <> " }"

convertAny :: IRType -> String -> Doc String
convertAny (IRArray a) var = do
    converter <- convertAnyFunc a
    pure $ "convertArray(converter: " <> converter <> ", json: " <> var <> ")"
convertAny (IRMap m) var = do
    converter <- convertAnyFunc m
    pure $ "convertDict(converter: " <> converter <> ", json: " <> var <> ")"
convertAny (IRUnion ur) var =
    case nullableFromSet $ unionToSet ur of
    Just t -> do
        converter <- convertAnyFunc t
        pure $ "convertOptional(converter: " <> converter <> ", json: " <> var <> ")"
    Nothing -> do
        name <- lookupUnionName ur
        pure $ name <> ".fromJson(" <> var <> ")"
convertAny IRNothing var =
    pure var
convertAny IRBool var =
    pure $ var <> " as? Bool"
convertAny IRInteger var =
    pure $ var <> " as? Int"
convertAny IRString var =
    pure $ var <> " as? String"
convertAny t var = do
    converter <- convertAnyFunc t
    pure $ converter <> "(" <> var <> ")"

convertToAny :: IRType -> String -> Doc String
convertToAny (IRArray a) var = do
    convertCode <- convertToAny a "$0"
    pure $ var <> ".map({ " <> convertCode <> " })"
convertToAny (IRMap m) var = do
    convertCode <- convertToAny m "$1"
    pure $ var <> ".map({ " <> convertCode <> " })"
convertToAny (IRClass i) var =
    pure $ var <> ".toJson()"
convertToAny (IRUnion ur) var =
    case nullableFromSet $ unionToSet ur of
    Just t -> do
        convertCode <- convertToAny t "$0"
        pure $ var <> ".map({ " <> convertCode  <> " }) ?? NSNull()"
    Nothing ->
        pure $ var <> ".toJson()"
convertToAny IRNothing var =
    pure $ "nilToNSNull(" <> var <> ")"
convertToAny IRNull var =
    pure $ "NSNull() as Any"
convertToAny _ var =
    pure $ var <> " as Any"

renderClassDefinition :: String -> Map String IRType -> Doc Unit
renderClassDefinition className properties = do
    let forbidden = keywords <> ["jsonUntyped", "json"]
    let propertyNames = makePropertyNames "" forbidden
    line $ "struct " <> className <> " {"
    indent do
        forEachProperty_ properties propertyNames \_ ptype fieldName _ -> do
            rendered <- renderType ptype
            line $ "let " <> fieldName <> ": " <> rendered
        blank
        line $ "init?(_ jsonUntyped: Any) {"
        indent do
            line "guard let json = jsonUntyped as? [String: Any] else { return nil }"
            let forbiddenForUntyped = forbidden <> (A.fromFoldable $ M.keys propertyNames)
            let untypedNames = makePropertyNames "Untyped" forbiddenForUntyped
            let forbiddenForConverted = forbiddenForUntyped <> (A.fromFoldable $ M.keys untypedNames)
            let convertedNames = makePropertyNames "Converted" forbiddenForConverted
            forEachProperty_ properties untypedNames \pname ptype untypedName _ -> do
                when (canBeNull ptype) do
                    line $ "let " <> untypedName <> " = removeNSNull(json[\"" <> stringEscape pname <> "\"])"
            line "guard"
            indent do
                forEachProperty_ properties untypedNames \pname ptype untypedName isLast -> do
                    let convertedName = lookupName pname convertedNames
                    unless (canBeNull ptype) do
                        line $ "let " <> untypedName <> " = removeNSNull(json[\"" <> stringEscape pname <> "\"]),"
                    convertCode <- convertAny ptype untypedName
                    line $ "let " <> convertedName <> " = " <> convertCode <> (if isLast then "" else ",")
            line "else {"
            indent do
                line "return nil"
            line "}"
            forEachProperty_ properties propertyNames \pname _ fieldName _ -> do
                let convertedName = lookupName pname convertedNames
                line $ "self." <> fieldName <> " = " <> convertedName
        line "}"
        blank
        line "func toJson() -> Any {"
        indent do
            line "let dict: [String: Any] ="
            indent do
                line "["
                indent do
                    forEachProperty_ properties propertyNames \pname ptype fieldName _ -> do
                        convertCode <- convertToAny ptype ("self." <> fieldName)
                        line $ "\"" <> stringEscape pname <> "\": " <> convertCode <> ","
                line "]"
            line "return dict"
        line "}"
    line "}"
    where
        isSimpleNullable :: IRType -> Boolean
        isSimpleNullable (IRUnion ur) = not $ unionIsNotSimpleNullable ur
        isSimpleNullable _ = false

        maybeCast :: IRType -> String
        maybeCast (IRArray _) = " as? [Any]"
        maybeCast (IRClass _) = " as? [String: Any]"
        maybeCast (IRMap _) = " as? [String: Any]"
        maybeCast _ = ""

        isBuiltInType :: IRType -> Boolean
        isBuiltInType (IRArray a) = isBuiltInType a
        isBuiltInType (IRMap m) = isBuiltInType m
        isBuiltInType (IRClass _) = false
        isBuiltInType (IRUnion ur) =
            case nullableFromSet $ unionToSet ur of
            Just t -> isBuiltInType t
            Nothing -> false
        isBuiltInType _ = true

        forEachProperty_ :: Map String IRType -> Map String String -> (String -> IRType -> String -> Boolean -> Doc Unit) -> Doc Unit
        forEachProperty_ properties propertyNames f =
            let propertyArray = M.toUnfoldable properties :: Array _
                lastIndex = (A.length propertyArray) - 1
            in
                forWithIndex_ propertyArray \i (Tuple pname ptype) -> do
                    let fieldName = lookupName pname propertyNames
                    f pname ptype fieldName (i == lastIndex)

        makePropertyNames :: String -> Array String -> Map String String
        makePropertyNames suffix forbidden =
            transformPropertyNames (fieldNamer suffix) otherField forbidden properties

        fieldNamer :: String -> Namer String
        fieldNamer suffix =
            simpleNamer \name -> swiftNameStyle false name <> suffix

        otherField :: String -> String
        otherField name =
            "other" <> capitalize name

renderUnionDefinition :: String -> Set IRType -> Doc Unit
renderUnionDefinition unionName unionTypes = do
    let { element: emptyOrNull, rest: nonNullTypes } = removeElement (_ == IRNull) unionTypes
    line $ "enum " <> unionName <> " {"
    indent do
        for_ nonNullTypes \typ -> do
            name <- caseName typ
            rendered <- renderType typ
            line $ "case " <> name <> "(" <> rendered <> ")"
        case emptyOrNull of
            Just t -> do
                name <- caseName t
                line $ "case " <> name
            Nothing -> pure unit
        blank
        line $ "static func fromJson(_ v: Any) -> " <> unionName <> "? {"
        indent do
            case emptyOrNull of
                Just t -> do
                    name <- caseName t
                    line "guard let v = removeNSNull(v)"
                    line "else {"
                    indent do
                        line $ "return " <> unionName <> "." <> name
                    line "}"
                Nothing -> pure unit
            when (S.member IRInteger unionTypes) do
                renderCase IRInteger
            for_ (S.delete IRInteger nonNullTypes) \typ -> do
                renderCase typ
            line "return nil"
        line "}"
        blank
        line $ "func toJson() -> Any {"
        indent do
            line $ "switch self {"
            for_ unionTypes \typ -> do
                name <- caseName typ
                line $ "case ." <> name <> "(let x):"
                indent do
                    convertCode <- convertToAny typ "x"
                    line $ "return " <> convertCode
            line "}"
        line "}"
    line "}"
    where
        caseName :: IRType -> Doc String
        caseName = case _ of
            IRArray a -> do
                rendered <- renderType a
                pure $ "some" <> rendered <> "s"
            IRNull -> pure "none"
            t -> do
                rendered <- renderType t
                pure $ "some" <> rendered

        renderCase :: IRType -> Doc Unit
        renderCase t = do
            convertCode <- convertAny t "v"
            line $ "if let x = " <> convertCode <> " {"
            indent do
                name <- caseName t
                line $ "return " <> unionName <> "." <> name <> "(x)"
            line "}"

module Language.Swift
    ( renderer
    ) where

import Doc
import IRGraph
import Prelude

import Data.Array as A
import Data.Char.Unicode (isAlphaNum, isDigit)
import Data.Foldable (for_)
import Data.FoldableWithIndex (forWithIndex_)
import Data.Map (Map)
import Data.Map as M
import Data.Maybe (Maybe(..))
import Data.Set (Set)
import Data.Set as S
import Data.String.Util (camelCase, capitalize, legalizeCharacters, startWithLetter, stringEscape)
import Data.Tuple (Tuple(..))
import Utils (removeElement)

keywords :: Array String
keywords =
    [ "associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var"
    , "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while"
    , "as", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try"
    , "_"
    , "associativity", "convenience", "dynamic", "didSet", "final", "get", "infix", "indirect", "lazy", "left", "mutating", "none", "nonmutating", "optional", "override", "postfix", "precedence", "prefix", "Protocol", "required", "right", "set", "Type", "unowned", "weak", "willSet"
    , "String", "Int", "Double", "Bool"
    , "checkNull", "removeNSNull", "nilToNSNull", "convertArray", "convertOptional", "convertDict", "convertDouble"
    ]

renderer :: Renderer
renderer =
    { name: "Swift"
    , aceMode: "swift"
    , extension: "swift"
    , doc: swiftDoc
    , transforms:
        { nameForClass: simpleNamer nameForClass
        , nextName: \s -> "Other" <> s
        , forbiddenNames: keywords
        , topLevelName: noForbidNamer (swiftNameStyle true)
        , unions: Just
            { predicate: unionIsNotSimpleNullable
            , properName: simpleNamer (swiftNameStyle true <<< combineNames)
            , nameFromTypes: simpleNamer (unionNameIntercalated (swiftNameStyle true) "Or")
            }
        }
    }

swiftNameStyle :: Boolean -> String -> String
swiftNameStyle isUpper =
    legalizeCharacters isPartCharacter >>> camelCase >>> startWithLetter isStartCharacter isUpper
    where
        isStartCharacter :: Char -> Boolean
        isStartCharacter c = c == '_' || (isAlphaNum c && not (isDigit c))

        isPartCharacter :: Char -> Boolean
        isPartCharacter c = c == '_' || isAlphaNum c

nameForClass :: IRClassData -> String
nameForClass (IRClassData { names }) = swiftNameStyle true $ combineNames names

swiftDoc :: Doc Unit
swiftDoc = do
    line "import Foundation"
    forEachClass_ \className properties -> do
        blank
        renderClassDefinition className properties
    forEachUnion_ \unionName unionTypes -> do
        blank
        renderUnionDefinition unionName unionTypes
    blank
    line """func convertArray<T>(converter: (Any) -> T?, json: Any) -> [T]? {
    guard let jsonArr = json as? [Any] else { return nil }
    var arr: [T] = []
    for v in jsonArr {
        if let converted = converter(v) {
            arr.append(converted)
        } else {
            return nil
        }
    }
    return arr
}

func convertOptional<T>(converter: (Any) -> T?, json: Any?) -> T? {
    guard let v = json
    else {
        return nil
    }
    return converter(v)
}

func convertDict<T>(converter: (Any) -> T?, json: Any?) -> [String: T]? {
    guard let jsonDict = json as? [String: Any] else { return nil }
    var dict: [String: T] = [:]
    for (k, v) in jsonDict {
        if let converted = converter(v) {
            dict[k] = converted
        } else {
            return nil
        }
    }
    return dict
}

func convertDouble(_ v: Any) -> Double? {
    if let i = v as? Int {
        return Double(i)
    }
    if let d = v as? Double {
        return d
    }
    return nil
}

func removeNSNull(_ v: Any?) -> Any? {
    if let w = v {
        if w is NSNull {
            return nil
        }
        return w
    }
    return nil
}

func checkNull(_ v: Any?) -> Any?? {
    if v != nil {
        return Optional.none
    }
    return Optional.some(nil)
}"""

renderUnion :: IRUnionRep -> Doc String
renderUnion ur =
    case nullableFromSet $ unionToSet ur of
    Just r -> do
        rendered <- renderType r
        pure $ rendered <> "?"
    Nothing -> lookupUnionName ur

renderType :: IRType -> Doc String
renderType = case _ of
    IRNothing -> pure "Any?"
    IRNull -> pure "Any?"
    IRInteger -> pure "Int"
    IRDouble -> pure "Double"
    IRBool -> pure "Bool"
    IRString -> pure "String"
    IRArray a -> do
        rendered <- renderType a
        pure $ "[" <> rendered <> "]"
    IRClass i -> lookupClassName i
    IRMap t -> do
        rendered <- renderType t
        pure $ "[String: " <> rendered <> "]"
    IRUnion ur -> renderUnion ur

convertAnyFunc :: IRType -> Doc String
convertAnyFunc (IRClass i) = do
    name <- lookupClassName i
    pure $ name <> ".init"
convertAnyFunc (IRUnion ur) = do
    name <- lookupUnionName ur
    pure $ name <> ".fromJson"
convertAnyFunc IRDouble =
    pure "convertDouble"
convertAnyFunc IRNull =
    pure "checkNull"
convertAnyFunc t = do
    converted <- convertAny t "$0"
    pure $ "{ " <> converted <> " }"

convertAny :: IRType -> String -> Doc String
convertAny (IRArray a) var = do
    converter <- convertAnyFunc a
    pure $ "convertArray(converter: " <> converter <> ", json: " <> var <> ")"
convertAny (IRMap m) var = do
    converter <- convertAnyFunc m
    pure $ "convertDict(converter: " <> converter <> ", json: " <> var <> ")"
convertAny (IRUnion ur) var =
    case nullableFromSet $ unionToSet ur of
    Just t -> do
        converter <- convertAnyFunc t
        pure $ "convertOptional(converter: " <> converter <> ", json: " <> var <> ")"
    Nothing -> do
        name <- lookupUnionName ur
        pure $ name <> ".fromJson(" <> var <> ")"
convertAny IRNothing var =
    pure var
convertAny IRBool var =
    pure $ var <> " as? Bool"
convertAny IRInteger var =
    pure $ var <> " as? Int"
convertAny IRString var =
    pure $ var <> " as? String"
convertAny t var = do
    converter <- convertAnyFunc t
    pure $ converter <> "(" <> var <> ")"

convertToAny :: IRType -> String -> Doc String
convertToAny (IRArray a) var = do
    convertCode <- convertToAny a "$0"
    pure $ var <> ".map({ " <> convertCode <> " })"
convertToAny (IRMap m) var = do
    convertCode <- convertToAny m "$1"
    pure $ var <> ".map({ " <> convertCode <> " })"
convertToAny (IRClass i) var =
    pure $ var <> ".toJson()"
convertToAny (IRUnion ur) var =
    case nullableFromSet $ unionToSet ur of
    Just t -> do
        convertCode <- convertToAny t "$0"
        pure $ var <> ".map({ " <> convertCode  <> " }) ?? NSNull()"
    Nothing ->
        pure $ var <> ".toJson()"
convertToAny IRNothing var =
    pure $ "nilToNSNull(" <> var <> ")"
convertToAny IRNull var =
    pure $ "NSNull() as Any"
convertToAny _ var =
    pure $ var <> " as Any"

renderClassDefinition :: String -> Map String IRType -> Doc Unit
renderClassDefinition className properties = do
    let forbidden = keywords <> ["jsonUntyped", "json"]
    let propertyNames = makePropertyNames "" forbidden
    line $ "struct " <> className <> " {"
    indent do
        forEachProperty_ properties propertyNames \_ ptype fieldName _ -> do
            rendered <- renderType ptype
            line $ "let " <> fieldName <> ": " <> rendered
        blank
        line $ "init?(_ jsonUntyped: Any) {"
        indent do
            line "guard let json = jsonUntyped as? [String: Any] else { return nil }"
            let forbiddenForUntyped = forbidden <> (A.fromFoldable $ M.keys propertyNames)
            let untypedNames = makePropertyNames "Untyped" forbiddenForUntyped
            let forbiddenForConverted = forbiddenForUntyped <> (A.fromFoldable $ M.keys untypedNames)
            let convertedNames = makePropertyNames "Converted" forbiddenForConverted
            forEachProperty_ properties untypedNames \pname ptype untypedName _ -> do
                when (canBeNull ptype) do
                    line $ "let " <> untypedName <> " = removeNSNull(json[\"" <> stringEscape pname <> "\"])"
            line "guard"
            indent do
                forEachProperty_ properties untypedNames \pname ptype untypedName isLast -> do
                    let convertedName = lookupName pname convertedNames
                    unless (canBeNull ptype) do
                        line $ "let " <> untypedName <> " = removeNSNull(json[\"" <> stringEscape pname <> "\"]),"
                    convertCode <- convertAny ptype untypedName
                    line $ "let " <> convertedName <> " = " <> convertCode <> (if isLast then "" else ",")
            line "else {"
            indent do
                line "return nil"
            line "}"
            forEachProperty_ properties propertyNames \pname _ fieldName _ -> do
                let convertedName = lookupName pname convertedNames
                line $ "self." <> fieldName <> " = " <> convertedName
        line "}"
        blank
        line "func toJson() -> Any {"
        indent do
            line "let dict: [String: Any] ="
            indent do
                line "["
                indent do
                    forEachProperty_ properties propertyNames \pname ptype fieldName _ -> do
                        convertCode <- convertToAny ptype ("self." <> fieldName)
                        line $ "\"" <> stringEscape pname <> "\": " <> convertCode <> ","
                line "]"
            line "return dict"
        line "}"
    line "}"
    where
        isSimpleNullable :: IRType -> Boolean
        isSimpleNullable (IRUnion ur) = not $ unionIsNotSimpleNullable ur
        isSimpleNullable _ = false

        maybeCast :: IRType -> String
        maybeCast (IRArray _) = " as? [Any]"
        maybeCast (IRClass _) = " as? [String: Any]"
        maybeCast (IRMap _) = " as? [String: Any]"
        maybeCast _ = ""

        isBuiltInType :: IRType -> Boolean
        isBuiltInType (IRArray a) = isBuiltInType a
        isBuiltInType (IRMap m) = isBuiltInType m
        isBuiltInType (IRClass _) = false
        isBuiltInType (IRUnion ur) =
            case nullableFromSet $ unionToSet ur of
            Just t -> isBuiltInType t
            Nothing -> false
        isBuiltInType _ = true

        forEachProperty_ :: Map String IRType -> Map String String -> (String -> IRType -> String -> Boolean -> Doc Unit) -> Doc Unit
        forEachProperty_ properties propertyNames f =
            let propertyArray = M.toUnfoldable properties :: Array _
                lastIndex = (A.length propertyArray) - 1
            in
                forWithIndex_ propertyArray \i (Tuple pname ptype) -> do
                    let fieldName = lookupName pname propertyNames
                    f pname ptype fieldName (i == lastIndex)

        makePropertyNames :: String -> Array String -> Map String String
        makePropertyNames suffix forbidden =
            transformPropertyNames (fieldNamer suffix) otherField forbidden properties

        fieldNamer :: String -> Namer String
        fieldNamer suffix =
            simpleNamer \name -> swiftNameStyle false name <> suffix

        otherField :: String -> String
        otherField name =
            "other" <> capitalize name

renderUnionDefinition :: String -> Set IRType -> Doc Unit
renderUnionDefinition unionName unionTypes = do
    let { element: emptyOrNull, rest: nonNullTypes } = removeElement (_ == IRNull) unionTypes
    line $ "enum " <> unionName <> " {"
    indent do
        for_ nonNullTypes \typ -> do
            name <- caseName typ
            rendered <- renderType typ
            line $ "case " <> name <> "(" <> rendered <> ")"
        case emptyOrNull of
            Just t -> do
                name <- caseName t
                line $ "case " <> name
            Nothing -> pure unit
        blank
        line $ "static func fromJson(_ v: Any) -> " <> unionName <> "? {"
        indent do
            case emptyOrNull of
                Just t -> do
                    name <- caseName t
                    line "guard let v = removeNSNull(v)"
                    line "else {"
                    indent do
                        line $ "return " <> unionName <> "." <> name
                    line "}"
                Nothing -> pure unit
            when (S.member IRInteger unionTypes) do
                renderCase IRInteger
            for_ (S.delete IRInteger nonNullTypes) \typ -> do
                renderCase typ
            line "return nil"
        line "}"
        blank
        line $ "func toJson() -> Any {"
        indent do
            line $ "switch self {"
            for_ unionTypes \typ -> do
                name <- caseName typ
                line $ "case ." <> name <> "(let x):"
                indent do
                    convertCode <- convertToAny typ "x"
                    line $ "return " <> convertCode
            line "}"
        line "}"
    line "}"
    where
        caseName :: IRType -> Doc String
        caseName = case _ of
            IRArray a -> do
                rendered <- renderType a
                pure $ "some" <> rendered <> "s"
            IRNull -> pure "none"
            t -> do
                rendered <- renderType t
                pure $ "some" <> rendered

        renderCase :: IRType -> Doc Unit
        renderCase t = do
            convertCode <- convertAny t "v"
            line $ "if let x = " <> convertCode <> " {"
            indent do
                name <- caseName t
                line $ "return " <> unionName <> "." <> name <> "(x)"
            line "}"


Change disabled attribute type signature

Under the covers, React will do the right thing w/ the disabled attribute based on its truthy-ness or falsey-ness. Strings are always truthy so setting a string here will always disable the widget.

module Thermite.Html.Attributes where

import Thermite.Types
import Thermite.Internal

accept :: forall action. String -> Prop action
accept = unsafeAttribute "accept"

acceptCharset :: forall action. String -> Prop action
acceptCharset = unsafeAttribute "acceptCharset"

accessKey :: forall action. String -> Prop action
accessKey = unsafeAttribute "accessKey"

action :: forall action. String -> Prop action
action = unsafeAttribute "action"

allowFullScreen :: forall action. String -> Prop action
allowFullScreen = unsafeAttribute "allowFullScreen"

allowTransparency :: forall action. String -> Prop action
allowTransparency = unsafeAttribute "allowTransparency"

alt :: forall action. String -> Prop action
alt = unsafeAttribute "alt"

async :: forall action. String -> Prop action
async = unsafeAttribute "async"

autoComplete :: forall action. String -> Prop action
autoComplete = unsafeAttribute "autoComplete"

autoFocus :: forall action. Boolean -> Prop action
autoFocus = unsafeAttribute "autoFocus"

autoPlay :: forall action. String -> Prop action
autoPlay = unsafeAttribute "autoPlay"

cellPadding :: forall action. String -> Prop action
cellPadding = unsafeAttribute "cellPadding"

cellSpacing :: forall action. String -> Prop action
cellSpacing = unsafeAttribute "cellSpacing"

charSet :: forall action. String -> Prop action
charSet = unsafeAttribute "charSet"

checked :: forall action. String -> Prop action
checked = unsafeAttribute "checked"

classID :: forall action. String -> Prop action
classID = unsafeAttribute "classID"

className :: forall action. String -> Prop action
className = unsafeAttribute "className"

cols :: forall action. String -> Prop action
cols = unsafeAttribute "cols"

colSpan :: forall action. String -> Prop action
colSpan = unsafeAttribute "colSpan"

content :: forall action. String -> Prop action
content = unsafeAttribute "content"

contentEditable :: forall action. String -> Prop action
contentEditable = unsafeAttribute "contentEditable"

contextMenu :: forall action. String -> Prop action
contextMenu = unsafeAttribute "contextMenu"

controls :: forall action. String -> Prop action
controls = unsafeAttribute "controls"

coords :: forall action. String -> Prop action
coords = unsafeAttribute "coords"

crossOrigin :: forall action. String -> Prop action
crossOrigin = unsafeAttribute "crossOrigin"

dateTime :: forall action. String -> Prop action
dateTime = unsafeAttribute "dateTime"

defer :: forall action. String -> Prop action
defer = unsafeAttribute "defer"

dir :: forall action. String -> Prop action
dir = unsafeAttribute "dir"

disabled :: forall action. Boolean -> Prop action
disabled = unsafeAttribute "disabled"

download :: forall action. String -> Prop action
download = unsafeAttribute "download"

draggable :: forall action. String -> Prop action
draggable = unsafeAttribute "draggable"

encType :: forall action. String -> Prop action
encType = unsafeAttribute "encType"

form :: forall action. String -> Prop action
form = unsafeAttribute "form"

formAction :: forall action. String -> Prop action
formAction = unsafeAttribute "formAction"

formEncType :: forall action. String -> Prop action
formEncType = unsafeAttribute "formEncType"

formMethod :: forall action. String -> Prop action
formMethod = unsafeAttribute "formMethod"

formNoValidate :: forall action. String -> Prop action
formNoValidate = unsafeAttribute "formNoValidate"

formTarget :: forall action. String -> Prop action
formTarget = unsafeAttribute "formTarget"

frameBorder :: forall action. String -> Prop action
frameBorder = unsafeAttribute "frameBorder"

height :: forall action. String -> Prop action
height = unsafeAttribute "height"

hidden :: forall action. String -> Prop action
hidden = unsafeAttribute "hidden"

href :: forall action. String -> Prop action
href = unsafeAttribute "href"

hrefLang :: forall action. String -> Prop action
hrefLang = unsafeAttribute "hrefLang"

htmlFor :: forall action. String -> Prop action
htmlFor = unsafeAttribute "htmlFor"

httpEquiv :: forall action. String -> Prop action
httpEquiv = unsafeAttribute "httpEquiv"

icon :: forall action. String -> Prop action
icon = unsafeAttribute "icon"

_id :: forall action. String -> Prop action
_id = unsafeAttribute "id"

label :: forall action. String -> Prop action
label = unsafeAttribute "label"

lang :: forall action. String -> Prop action
lang = unsafeAttribute "lang"

list :: forall action. String -> Prop action
list = unsafeAttribute "list"

loop :: forall action. String -> Prop action
loop = unsafeAttribute "loop"

manifest :: forall action. String -> Prop action
manifest = unsafeAttribute "manifest"

marginHeight :: forall action. String -> Prop action
marginHeight = unsafeAttribute "marginHeight"

marginWidth :: forall action. String -> Prop action
marginWidth = unsafeAttribute "marginWidth"

max :: forall action. String -> Prop action
max = unsafeAttribute "max"

maxLength :: forall action. String -> Prop action
maxLength = unsafeAttribute "maxLength"

media :: forall action. String -> Prop action
media = unsafeAttribute "media"

mediaGroup :: forall action. String -> Prop action
mediaGroup = unsafeAttribute "mediaGroup"

method :: forall action. String -> Prop action
method = unsafeAttribute "method"

min :: forall action. String -> Prop action
min = unsafeAttribute "min"

multiple :: forall action. String -> Prop action
multiple = unsafeAttribute "multiple"

muted :: forall action. String -> Prop action
muted = unsafeAttribute "muted"

name :: forall action. String -> Prop action
name = unsafeAttribute "name"

noValidate :: forall action. String -> Prop action
noValidate = unsafeAttribute "noValidate"

open :: forall action. String -> Prop action
open = unsafeAttribute "open"

pattern :: forall action. String -> Prop action
pattern = unsafeAttribute "pattern"

placeholder :: forall action. String -> Prop action
placeholder = unsafeAttribute "placeholder"

poster :: forall action. String -> Prop action
poster = unsafeAttribute "poster"

preload :: forall action. String -> Prop action
preload = unsafeAttribute "preload"

radioGroup :: forall action. String -> Prop action
radioGroup = unsafeAttribute "radioGroup"

readOnly :: forall action. String -> Prop action
readOnly = unsafeAttribute "readOnly"

rel :: forall action. String -> Prop action
rel = unsafeAttribute "rel"

required :: forall action. String -> Prop action
required = unsafeAttribute "required"

role :: forall action. String -> Prop action
role = unsafeAttribute "role"

rows :: forall action. String -> Prop action
rows = unsafeAttribute "rows"

rowSpan :: forall action. String -> Prop action
rowSpan = unsafeAttribute "rowSpan"

sandbox :: forall action. String -> Prop action
sandbox = unsafeAttribute "sandbox"

scope :: forall action. String -> Prop action
scope = unsafeAttribute "scope"

scrolling :: forall action. String -> Prop action
scrolling = unsafeAttribute "scrolling"

seamless :: forall action. String -> Prop action
seamless = unsafeAttribute "seamless"

selected :: forall action. String -> Prop action
selected = unsafeAttribute "selected"

shape :: forall action. String -> Prop action
shape = unsafeAttribute "shape"

size :: forall action. String -> Prop action
size = unsafeAttribute "size"

sizes :: forall action. String -> Prop action
sizes = unsafeAttribute "sizes"

span :: forall action. String -> Prop action
span = unsafeAttribute "span"

spellCheck :: forall action. String -> Prop action
spellCheck = unsafeAttribute "spellCheck"

src :: forall action. String -> Prop action
src = unsafeAttribute "src"

srcDoc :: forall action. String -> Prop action
srcDoc = unsafeAttribute "srcDoc"

srcSet :: forall action. String -> Prop action
srcSet = unsafeAttribute "srcSet"

start :: forall action. String -> Prop action
start = unsafeAttribute "start"

step :: forall action. String -> Prop action
step = unsafeAttribute "step"

tabIndex :: forall action. String -> Prop action
tabIndex = unsafeAttribute "tabIndex"

target :: forall action. String -> Prop action
target = unsafeAttribute "target"

title :: forall action. String -> Prop action
title = unsafeAttribute "title"

_type :: forall action. String -> Prop action
_type = unsafeAttribute "type"

useMap :: forall action. String -> Prop action
useMap = unsafeAttribute "useMap"

value :: forall action. String -> Prop action
value = unsafeAttribute "value"

width :: forall action. String -> Prop action
width = unsafeAttribute "width"

wmode :: forall action. String -> Prop action
wmode = unsafeAttribute "wmode"

module Thermite.Html.Attributes where

import Thermite.Types
import Thermite.Internal

accept :: forall action. String -> Prop action
accept = unsafeAttribute "accept"

acceptCharset :: forall action. String -> Prop action
acceptCharset = unsafeAttribute "acceptCharset"

accessKey :: forall action. String -> Prop action
accessKey = unsafeAttribute "accessKey"

action :: forall action. String -> Prop action
action = unsafeAttribute "action"

allowFullScreen :: forall action. String -> Prop action
allowFullScreen = unsafeAttribute "allowFullScreen"

allowTransparency :: forall action. String -> Prop action
allowTransparency = unsafeAttribute "allowTransparency"

alt :: forall action. String -> Prop action
alt = unsafeAttribute "alt"

async :: forall action. String -> Prop action
async = unsafeAttribute "async"

autoComplete :: forall action. String -> Prop action
autoComplete = unsafeAttribute "autoComplete"

autoFocus :: forall action. Boolean -> Prop action
autoFocus = unsafeAttribute "autoFocus"

autoPlay :: forall action. String -> Prop action
autoPlay = unsafeAttribute "autoPlay"

cellPadding :: forall action. String -> Prop action
cellPadding = unsafeAttribute "cellPadding"

cellSpacing :: forall action. String -> Prop action
cellSpacing = unsafeAttribute "cellSpacing"

charSet :: forall action. String -> Prop action
charSet = unsafeAttribute "charSet"

checked :: forall action. String -> Prop action
checked = unsafeAttribute "checked"

classID :: forall action. String -> Prop action
classID = unsafeAttribute "classID"

className :: forall action. String -> Prop action
className = unsafeAttribute "className"

cols :: forall action. String -> Prop action
cols = unsafeAttribute "cols"

colSpan :: forall action. String -> Prop action
colSpan = unsafeAttribute "colSpan"

content :: forall action. String -> Prop action
content = unsafeAttribute "content"

contentEditable :: forall action. String -> Prop action
contentEditable = unsafeAttribute "contentEditable"

contextMenu :: forall action. String -> Prop action
contextMenu = unsafeAttribute "contextMenu"

controls :: forall action. String -> Prop action
controls = unsafeAttribute "controls"

coords :: forall action. String -> Prop action
coords = unsafeAttribute "coords"

crossOrigin :: forall action. String -> Prop action
crossOrigin = unsafeAttribute "crossOrigin"

dateTime :: forall action. String -> Prop action
dateTime = unsafeAttribute "dateTime"

defer :: forall action. String -> Prop action
defer = unsafeAttribute "defer"

dir :: forall action. String -> Prop action
dir = unsafeAttribute "dir"

disabled :: forall action. String -> Prop action
disabled = unsafeAttribute "disabled"

download :: forall action. String -> Prop action
download = unsafeAttribute "download"

draggable :: forall action. String -> Prop action
draggable = unsafeAttribute "draggable"

encType :: forall action. String -> Prop action
encType = unsafeAttribute "encType"

form :: forall action. String -> Prop action
form = unsafeAttribute "form"

formAction :: forall action. String -> Prop action
formAction = unsafeAttribute "formAction"

formEncType :: forall action. String -> Prop action
formEncType = unsafeAttribute "formEncType"

formMethod :: forall action. String -> Prop action
formMethod = unsafeAttribute "formMethod"

formNoValidate :: forall action. String -> Prop action
formNoValidate = unsafeAttribute "formNoValidate"

formTarget :: forall action. String -> Prop action
formTarget = unsafeAttribute "formTarget"

frameBorder :: forall action. String -> Prop action
frameBorder = unsafeAttribute "frameBorder"

height :: forall action. String -> Prop action
height = unsafeAttribute "height"

hidden :: forall action. String -> Prop action
hidden = unsafeAttribute "hidden"

href :: forall action. String -> Prop action
href = unsafeAttribute "href"

hrefLang :: forall action. String -> Prop action
hrefLang = unsafeAttribute "hrefLang"

htmlFor :: forall action. String -> Prop action
htmlFor = unsafeAttribute "htmlFor"

httpEquiv :: forall action. String -> Prop action
httpEquiv = unsafeAttribute "httpEquiv"

icon :: forall action. String -> Prop action
icon = unsafeAttribute "icon"

_id :: forall action. String -> Prop action
_id = unsafeAttribute "id"

label :: forall action. String -> Prop action
label = unsafeAttribute "label"

lang :: forall action. String -> Prop action
lang = unsafeAttribute "lang"

list :: forall action. String -> Prop action
list = unsafeAttribute "list"

loop :: forall action. String -> Prop action
loop = unsafeAttribute "loop"

manifest :: forall action. String -> Prop action
manifest = unsafeAttribute "manifest"

marginHeight :: forall action. String -> Prop action
marginHeight = unsafeAttribute "marginHeight"

marginWidth :: forall action. String -> Prop action
marginWidth = unsafeAttribute "marginWidth"

max :: forall action. String -> Prop action
max = unsafeAttribute "max"

maxLength :: forall action. String -> Prop action
maxLength = unsafeAttribute "maxLength"

media :: forall action. String -> Prop action
media = unsafeAttribute "media"

mediaGroup :: forall action. String -> Prop action
mediaGroup = unsafeAttribute "mediaGroup"

method :: forall action. String -> Prop action
method = unsafeAttribute "method"

min :: forall action. String -> Prop action
min = unsafeAttribute "min"

multiple :: forall action. String -> Prop action
multiple = unsafeAttribute "multiple"

muted :: forall action. String -> Prop action
muted = unsafeAttribute "muted"

name :: forall action. String -> Prop action
name = unsafeAttribute "name"

noValidate :: forall action. String -> Prop action
noValidate = unsafeAttribute "noValidate"

open :: forall action. String -> Prop action
open = unsafeAttribute "open"

pattern :: forall action. String -> Prop action
pattern = unsafeAttribute "pattern"

placeholder :: forall action. String -> Prop action
placeholder = unsafeAttribute "placeholder"

poster :: forall action. String -> Prop action
poster = unsafeAttribute "poster"

preload :: forall action. String -> Prop action
preload = unsafeAttribute "preload"

radioGroup :: forall action. String -> Prop action
radioGroup = unsafeAttribute "radioGroup"

readOnly :: forall action. String -> Prop action
readOnly = unsafeAttribute "readOnly"

rel :: forall action. String -> Prop action
rel = unsafeAttribute "rel"

required :: forall action. String -> Prop action
required = unsafeAttribute "required"

role :: forall action. String -> Prop action
role = unsafeAttribute "role"

rows :: forall action. String -> Prop action
rows = unsafeAttribute "rows"

rowSpan :: forall action. String -> Prop action
rowSpan = unsafeAttribute "rowSpan"

sandbox :: forall action. String -> Prop action
sandbox = unsafeAttribute "sandbox"

scope :: forall action. String -> Prop action
scope = unsafeAttribute "scope"

scrolling :: forall action. String -> Prop action
scrolling = unsafeAttribute "scrolling"

seamless :: forall action. String -> Prop action
seamless = unsafeAttribute "seamless"

selected :: forall action. String -> Prop action
selected = unsafeAttribute "selected"

shape :: forall action. String -> Prop action
shape = unsafeAttribute "shape"

size :: forall action. String -> Prop action
size = unsafeAttribute "size"

sizes :: forall action. String -> Prop action
sizes = unsafeAttribute "sizes"

span :: forall action. String -> Prop action
span = unsafeAttribute "span"

spellCheck :: forall action. String -> Prop action
spellCheck = unsafeAttribute "spellCheck"

src :: forall action. String -> Prop action
src = unsafeAttribute "src"

srcDoc :: forall action. String -> Prop action
srcDoc = unsafeAttribute "srcDoc"

srcSet :: forall action. String -> Prop action
srcSet = unsafeAttribute "srcSet"

start :: forall action. String -> Prop action
start = unsafeAttribute "start"

step :: forall action. String -> Prop action
step = unsafeAttribute "step"

tabIndex :: forall action. String -> Prop action
tabIndex = unsafeAttribute "tabIndex"

target :: forall action. String -> Prop action
target = unsafeAttribute "target"

title :: forall action. String -> Prop action
title = unsafeAttribute "title"

_type :: forall action. String -> Prop action
_type = unsafeAttribute "type"

useMap :: forall action. String -> Prop action
useMap = unsafeAttribute "useMap"

value :: forall action. String -> Prop action
value = unsafeAttribute "value"

width :: forall action. String -> Prop action
width = unsafeAttribute "width"

wmode :: forall action. String -> Prop action
wmode = unsafeAttribute "wmode"