Cloak.purs 5.8 KB
Newer Older
1 2 3 4 5 6 7
module Gargantext.Components.Bootstrap.Cloak
  ( cloak
  ) where

import Gargantext.Prelude

import Data.Foldable (elem)
arturo's avatar
arturo committed
8
import Data.Maybe (Maybe(..), fromMaybe)
9
import Data.Tuple.Nested ((/\))
arturo's avatar
arturo committed
10
import Effect (Effect)
11
import Effect.Timer (setTimeout)
arturo's avatar
arturo committed
12
import Gargantext.Hooks.FirstEffect (useFirstEffect')
13 14 15 16 17 18 19 20 21
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Toestand as T


type Props =
  ( defaultSlot             :: R.Element
  , cloakSlot               :: R.Element
  , isDisplayed             :: Boolean
arturo's avatar
arturo committed
22 23 24 25 26
  | Options
  )

type Options =
  ( idlingPhaseDuration     :: Maybe Int -- Milliseconds
27 28 29
  , sustainingPhaseDuration :: Maybe Int -- Milliseconds
  )

arturo's avatar
arturo committed
30 31 32 33 34 35
options :: Record Options
options =
  { idlingPhaseDuration     : Nothing
  , sustainingPhaseDuration : Nothing
  }

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
-- |  Abstract component type easing the transition display between a content
-- |  component and transitional (or cloak) component
-- |
-- |
-- |  Inputs:
-- |    * `defaultSlot :: Element` transclude pattern providing elements
-- |       to be finally displayed
-- |    * `cloakSlot :: Element` transclude pattern proviging elements
-- |       displayed during first phases (see lifecycle explanation below)
-- |    * `isDisplayed :: Boolean` flag defining if the main content is
-- |       ready to be displayed
-- |    * `idlingPhaseDuration :: Maybe Int` if defined, perform idling
-- |       phase (see lifecyle explanation below)
-- |    * `sustainingPhaseDuration :: Maybe Int` if defined, perform sustaining
-- |       phase (see lifecyle explanation below)
-- |
-- |
-- |  The lifecycle is structured into 4 phases:
-- |    1. OPTIONAL, a primary idle phase, where no component is being displayed
-- |      ↳ why? if the flag switched from "off" to "on" in a particularly fast
-- |        pace, it will jump the sustain and wait phases (ie. the display
-- |        of the transitional component, see below) which won't be
-- [        needed anymore
-- |    2. OPTIONAL a sustain phase, where the transitional component will be
-- |        displayed for a defined amount of time
-- |        ↳ why? same heuristic as above, if the switch is too fast, the cloak
-- |          will be displayed at such speed that it will create a flickering
-- |          effect, this phase will avoid it
-- |    3. a waiting phase, where the cloak will be displayed until the switch
-- |        will be set to "on"
-- |    4. a final display phase, where the main content component will be shown
-- |
-- |
-- |  Simple transition:
-- |
-- |    ```purescript
-- |      cloak
-- |      { isDisplayed             : onPendingFlag
-- |      , cloakSlot               : blankPlaceholder {}
-- |      , defaultSlot             : componentToBeDisplayed {} []
-- |      , idlingPhaseDuration     : Nothing
-- |      , sustainingPhaseDuration : Nothing
-- |      }
-- |    ```
-- |
-- |
-- |    Smart transition
-- |
-- |    ```purescript
-- |      cloak
-- |      { isDisplayed             : onPendingFlag
-- |      , cloakSlot               : blankPlaceholder {}
-- |      , defaultSlot             : componentToBeDisplayed {} []
-- |      -- Idling phase set up
-- |      --
-- |      -- * if the computation for display makes less than 20ms, no
-- |      --   transition at all will be displayed, and the content will arrive
-- |      --   as soon as the 20ms delay is consumed
-- |      -- * if the computation takes more, following phases will be set up
-- |      --   (according to the configuration provided)
-- |      , idlingPhaseDuration     : Just 20
-- |      -- Sustaining phase set up
-- |      --
-- |      -- * now let's say the computation talking above makes 35ms before
-- |      --   displaying its content, setting this parameter will avoid a:
-- |      --      35ms (computation) - 20ms (idling) = 15ms (sustaining)
-- |      -- * this very short delay will be considered as a UI flickering
-- |      --   effect for the user, which is a UX smelly design
-- |      -- * by setting the sustaining phase to 400ms, we ensure a period
-- |      --   of time that eliminates this effect
-- |      , sustainingPhaseDuration  : Just 400
-- |      }
-- |    ```
arturo's avatar
arturo committed
109 110
cloak :: forall r. R2.OptLeaf Options Props r
cloak = R2.optLeaf component options
111 112 113 114 115 116 117 118 119 120 121

cname :: String
cname = "b-cloak"

component :: R.Component Props
component = R.hooksComponent cname cpt where
  cpt props _ = do
    -- State
    phase /\ phaseBox <- R2.useBox' (Idle :: Phase)

    -- Computed
arturo's avatar
arturo committed
122 123 124 125 126 127 128 129 130 131 132 133 134
    let
      canCloakBeDisplayed   = elem phase [ Sustain, Wait ]
      canContentBeDisplayed = elem phase [ Display ]

    -- Behaviors
    let

      execDisplayingPhaseOr :: (Unit -> Effect Unit) -> Effect Unit
      execDisplayingPhaseOr thunk =

        if props.isDisplayed
        then T.write_ Display phaseBox
        else thunk unit
135

arturo's avatar
arturo committed
136 137
      execWaitingPhase :: Unit -> Effect Unit
      execWaitingPhase _ = execDisplayingPhaseOr $ const $
138

arturo's avatar
arturo committed
139
            T.write_ Wait phaseBox
140

arturo's avatar
arturo committed
141 142
      execSustainingPhase :: Unit -> Effect Unit
      execSustainingPhase _ = execDisplayingPhaseOr $ const $
143

arturo's avatar
arturo committed
144
            T.write_ Sustain phaseBox
145

arturo's avatar
arturo committed
146 147 148
        <*  setTimeout
              (fromMaybe 0 props.sustainingPhaseDuration)
              (execWaitingPhase unit)
149

arturo's avatar
arturo committed
150 151
      execIdlingPhase :: Unit -> Effect Unit
      execIdlingPhase _ = execDisplayingPhaseOr $ const $
152

arturo's avatar
arturo committed
153
            T.write_ Idle phaseBox
154

arturo's avatar
arturo committed
155 156 157
        <*  setTimeout
              (fromMaybe 0 props.idlingPhaseDuration)
              (execSustainingPhase unit)
158 159

    -- Effects
arturo's avatar
arturo committed
160
    useFirstEffect' $ execIdlingPhase unit
161

arturo's avatar
arturo committed
162
    R.useEffect2' props.isDisplayed phase $
163 164

      if (props.isDisplayed && phase == Wait)
arturo's avatar
arturo committed
165
      then T.write_ Display phaseBox
166 167 168 169 170 171 172
      else pure unit

    -- Render
    pure $

      R.fragment
      [
arturo's avatar
arturo committed
173
        R2.when canCloakBeDisplayed    props.cloakSlot
174
      ,
arturo's avatar
arturo committed
175
        R2.when canContentBeDisplayed  props.defaultSlot
176 177 178 179 180 181 182 183 184 185
      ]


data Phase =
    Idle
  | Sustain
  | Wait
  | Display

derive instance eqPhase :: Eq Phase