Tree.purs 11.7 KB
Newer Older
1
module Gargantext.Components.NgramsTable.Tree where
2

3 4
import Gargantext.Prelude

5
import Data.Array as A
6
import Data.Either (Either(..))
7 8 9
import Data.Lens ((^..), (^.), view)
import Data.Lens.Fold (folded)
import Data.Lens.Index (ix)
10
import Data.List (List, intercalate)
11
import Data.List as L
12
import Data.Maybe (Maybe(..), maybe)
13 14 15
import Data.Set (Set)
import Data.Set as Set
import Effect (Effect)
16 17 18
import Effect.Aff (Aff)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Variant(..))
19
import Gargantext.Components.Table as Tbl
20
import Gargantext.Config.REST (logRESTError)
21 22
import Gargantext.Core.NgramsTable.Functions (applyNgramsPatches, setTermListA, tablePatchHasNgrams)
import Gargantext.Core.NgramsTable.Types (Action(..), NgramsClick, NgramsDepth, NgramsElement, NgramsTable, NgramsTablePatch, NgramsTerm, _NgramsElement, _NgramsRepoElement, _children, _list, _ngrams, _occurrences, ngramsTermText, replace)
23
import Gargantext.Hooks.Loader (useLoader)
24
import Gargantext.Prelude (Unit, bind, const, map, mempty, not, otherwise, pure, show, unit, ($), (+), (<<<), (<>), (==), (>), (||))
25
import Gargantext.Types as GT
26
import Gargantext.Utils ((?))
27
import Gargantext.Utils.Reactix as R2
28 29 30 31
import React.DOM (a, span, text)
import React.DOM.Props as DOM
import Reactix as R
import Reactix.DOM.HTML as H
32
import Record as Record
33
import Toestand as T
34
import Type.Proxy (Proxy(..))
35

36
here :: R2.Here
37
here = R2.here "Gargantext.Components.NgramsTable.Tree"
38 39 40


type RenderNgramsTree =
41
  ( getNgramsChildren :: NgramsTerm -> Aff (Array NgramsTerm)
42
  --, ngramsChildren    :: List NgramsTerm
43 44 45 46
  , ngramsClick       :: NgramsClick
  , ngramsDepth       :: NgramsDepth
  , ngramsEdit        :: NgramsClick
  , ngramsStyle       :: Array DOM.Props
47
  --, ngramsTable    :: NgramsTable
48
  , key               :: String -- used to refresh the tree on diff change
49 50 51 52 53
  )

renderNgramsTree :: Record RenderNgramsTree -> R.Element
renderNgramsTree p = R.createElement renderNgramsTreeCpt p []
renderNgramsTreeCpt :: R.Component RenderNgramsTree
54
renderNgramsTreeCpt = here.component "renderNgramsTree" cpt
55
  where
56
    cpt { getNgramsChildren, ngramsClick, ngramsDepth, ngramsEdit, ngramsStyle } _ = do
57 58 59
      pure $
        H.ul
        { className: "render-ngrams-tree" }
60 61 62 63 64 65 66 67 68 69
        [ H.span { className: "tree" }
          [ H.span { className: "righthanded" }
            [ tree { getNgramsChildren
                     --, ngramsChildren
                   , ngramsClick
                   , ngramsDepth
                   , ngramsEdit
                   , ngramsStyle
                   }
            ]
70
          ]
71 72 73
        ]


74
type TagProps =
75
  ( ngramsClick :: NgramsClick
76 77
  , ngramsDepth :: NgramsDepth
  , ngramsStyle :: Array DOM.Props
78 79 80 81 82 83 84 85 86 87 88 89 90
  )

{- TODO refactor here
-- tag :: TagProps -> Array R.Element -> R.Element
tag tagProps =
  case tagProps.ngramsClick tagProps.ngramsDepth of
    Just effect ->
      a (tagProps.ngramsStyle <> [DOM.onClick $ const effect])
    Nothing ->
      span tagProps.ngramsStyle
-}

type TreeProps =
91 92
  ( getNgramsChildren :: NgramsTerm -> Aff (Array NgramsTerm)
  , ngramsEdit        :: NgramsClick
93
  --, ngramsTable :: NgramsTable
94
  | TagProps
95 96 97 98 99
  )

tree :: Record TreeProps -> R.Element
tree p = R.createElement treeCpt p []
treeCpt :: R.Component TreeProps
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
treeCpt = here.component "tree" cpt where
  cpt props@{ getNgramsChildren, ngramsDepth } _ = do
    let loader p = do
          res <- getNgramsChildren p
          pure $ Right res
    let render nc = treeLoaded (Record.merge props { ngramsChildren: L.fromFoldable nc })

    useLoader { errorHandler
              , loader
              , path: ngramsDepth.ngrams
              , render }
    where
      errorHandler = logRESTError here "[tree]"

type TreeLoaded =
  ( ngramsChildren    :: List NgramsTerm
  | TreeProps )
117

118 119 120 121
treeLoaded :: Record TreeLoaded -> R.Element
treeLoaded p = R.createElement treeLoadedCpt p []
treeLoadedCpt :: R.Component TreeLoaded
treeLoadedCpt = here.component "treeLoaded" cpt where
122 123 124 125 126 127
  cpt params@{ ngramsChildren
             , ngramsClick
             , ngramsDepth
             , ngramsEdit
             , ngramsStyle
             } _ = do
128
    pure $
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

      H.li
      -- { className: "ngrams-tree-loaded-node" }
      { className: intercalate " "
          [ "ngrams-tree-loaded-node"
          , ngramsDepth.depth == 1 ?
              "ngrams-tree-loaded-node--first-child" $
              ""
          , ngramsDepth.depth > 1 ?
              "ngrams-tree-loaded-node--grand-child" $
              ""
          ]
      }
      (
        -- @NOTE #414: currently commenting this, as the below icon is not
        --             a call-to-action, thus deceiving the user of possible
        --             yet-to-become reveal/collapse node children feature
        -- [ H.i { className, style } [] ]
      -- <>
        [ R2.buff $ tag [ text $ " " <> ngramsTermText ngramsDepth.ngrams ] ]
      <>
        maybe [] edit (ngramsEdit ngramsDepth)
      <>
        [ forest ngramsChildren ]
153 154 155 156 157 158 159 160
      )
    where
      tag =
        case ngramsClick ngramsDepth of
          Just effect ->
            a (ngramsStyle <> [DOM.onClick $ const effect])
          Nothing ->
            span ngramsStyle
161 162 163 164 165 166 167 168 169 170
      edit effect =
        [
          B.iconButton
          { name: "pencil"
          , className: "ml-1"
          , variant: Secondary
          , callback: const effect
          , overlay: false
          }
        ]
171 172 173 174 175 176
      leaf = L.null ngramsChildren
      className = "fa fa-chevron-" <> if open then "down" else "right"
      style = if leaf then {color: "#adb5bd"} else {color: ""}
      open = not leaf || false {- TODO -}
      --cs   = ngramsTable ^.. ix ngramsDepth.ngrams <<< _NgramsRepoElement <<< _children <<< folded
      -- cs has a list is ok, the length is the number of direct children of an ngram which is generally < 10.
177

178 179 180 181 182 183
      forest =
        let depth = ngramsDepth.depth + 1 in
        if depth > 10 then
          const $ H.text "ERROR DEPTH > 10"
        else
          H.ul {} <<< map (\ngrams -> tree ((Record.delete (Proxy :: Proxy "ngramsChildren") params) { ngramsDepth = {depth, ngrams} })) <<< L.toUnfoldable
184

185 186 187
type RenderNgramsItem =
  ( dispatch          :: Action -> Effect Unit
  , getNgramsChildren :: NgramsTerm -> Aff (Array NgramsTerm)
188
  , isEditing         :: T.Box Boolean
189 190 191 192 193
  , ngrams            :: NgramsTerm
  , ngramsElement     :: NgramsElement
  , ngramsLocalPatch  :: NgramsTablePatch
  , ngramsSelection   :: Set NgramsTerm
  , ngramsTable       :: NgramsTable
194 195
  )

196 197
renderNgramsItem :: R2.Component RenderNgramsItem
renderNgramsItem = R.createElement renderNgramsItemCpt
198
renderNgramsItemCpt :: R.Component RenderNgramsItem
199
renderNgramsItemCpt = here.component "renderNgramsItem" cpt
200 201
  where
    cpt { dispatch
202
        --, getNgramsChildren
203
        , isEditing
204 205 206 207
        , ngrams
        , ngramsElement
        , ngramsLocalPatch
        , ngramsSelection
208 209
        , ngramsTable
        } _ = do
210
      isEditing' <- T.useLive T.unequal isEditing
211

212
      pure $ Tbl.makeRow
213 214 215 216 217 218 219 220 221 222 223 224
        [
          H.div
          { className: "text-center"
          , style: { marginTop: "6px" }
          }
          [
            B.iconButton
            { name: "eye-slash"
            , status: Disabled -- see `onClick` behavior
            , callback: onClick
            , className: ""
            }
225
          ]
226
        , selected
227 228
        , checkbox GT.MapTerm
        , checkbox GT.StopTerm
229
        , H.div {}
230
          ( if isEditing'
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
            then
              [
                B.iconButton
                { name: "plus"
                , className: "mr-1 align-bottom"
                , overlay: false
                , variant: Primary
                , callback: const $ dispatch $ ToggleChild true ngrams
                }
              ,
                R2.buff $
                tag [ text $ " " <> ngramsTermText ngramsDepth.ngrams ]
              ]
            else
              [
                renderNgramsTree
                { getNgramsChildren: getNgramsChildren'
                , ngramsClick
                , ngramsDepth
                , ngramsEdit
                , ngramsStyle
                , key: ""
                }
              ]
255
          )
256 257 258 259
        ,
          B.wad'
          [ "pl-3" ] $
          show (ngramsElement ^. _NgramsElement <<< _occurrences)
260 261
      ]
      where
262
        ngramsDepth = { ngrams, depth: 0 }
263 264 265 266 267 268
        tag =
          case ngramsClick ngramsDepth of
            Just effect ->
              a (ngramsStyle <> [DOM.onClick $ const effect])
            Nothing ->
              span ngramsStyle
269 270 271
        onClick _ = pure unit :: Effect Unit
        -- onClick _ = do
        --   R2.callTrigger toggleSidePanel unit
272 273
        termList    = ngramsElement ^. _NgramsElement <<< _list
        ngramsStyle = [termStyle termList ngramsOpacity]
274
        ngramsEdit { ngrams: n } = Just $ dispatch $ SetParentResetChildren (Just n) (ngramsChildren n)
275 276 277 278 279
        tbl = applyNgramsPatches { ngramsLocalPatch
                                 , ngramsStagePatch: mempty
                                 , ngramsValidPatch: mempty
                                 , ngramsVersion: 0 } ngramsTable
        getNgramsChildren' :: NgramsTerm -> Aff (Array NgramsTerm)
280 281
        getNgramsChildren' n = pure $ A.fromFoldable $ ngramsChildren n
        ngramsChildren n = tbl ^.. ix n <<< _NgramsRepoElement <<< _children <<< folded
282 283
        ngramsClick =
          Just <<< dispatch <<< CoreAction <<< cycleTermListItem <<< view _ngrams
284 285 286 287 288 289 290 291
          -- ^ This is the old behavior it is nicer to use since one can
          --   rapidly change the ngram list without waiting for confirmation.
          --   However this might expose bugs. One of them can be reproduced
          --   by clicking a multiple times on the same ngram, sometimes it stays
          --   transient.
          -- | ngramsTransient = const Nothing
          -- | otherwise       = Just <<< dispatch <<< cycleTermListItem <<< view _ngrams
        selected    =
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
          B.wad
          [ "text-center" ]
          [
            H.input
            { checked: Set.member ngrams ngramsSelection
            , className: "checkbox"
            , on: { change: const $ dispatch $ ToggleSelect ngrams }
            , type: "checkbox"
            , style:
                { cursor: "pointer"
                , marginTop: "6px"
                }
            }
          ]

307 308
        checkbox termList' =
          let chkd = termList == termList'
309
              termList'' = if chkd then GT.CandidateTerm else termList'
310
          in
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
            B.wad
            [ "text-center" ]
            [
              H.input
              { checked: chkd
              , className: "checkbox"
              , on: { change: const $ dispatch $ CoreAction $
                      setTermListA ngrams (replace termList termList'') }
              , readOnly: ngramsTransient
              , type: "checkbox"
              , style:
                  { cursor: "pointer"
                  , marginTop: "6px"
                  }
              }
            ]

328 329 330 331 332 333 334 335 336
        ngramsTransient = tablePatchHasNgrams ngramsLocalPatch ngrams
          -- ^ TODO here we do not look at ngramsNewElems, shall we?
        ngramsOpacity
          | ngramsTransient = 0.5
          | otherwise       = 1.0

        cycleTermListItem n = setTermListA n (replace termList (nextTermList termList))


337
termStyle :: GT.TermList -> Number -> DOM.Props
arturo's avatar
arturo committed
338 339 340 341 342 343 344 345 346 347 348 349 350 351
termStyle GT.MapTerm       opacity = DOM.style
  { color: "#11AA11"
  , opacity
  }
termStyle GT.StopTerm      opacity = DOM.style
  { color: "#EE3311"
  , opacity
  , textDecoration: "line-through"
  }
termStyle GT.CandidateTerm opacity = DOM.style
  { color: "#5A90B6"
  , fontStyle: "italic"
  , opacity
  }
352 353


354 355 356 357
nextTermList :: GT.TermList -> GT.TermList
nextTermList GT.MapTerm       = GT.StopTerm
nextTermList GT.StopTerm      = GT.CandidateTerm
nextTermList GT.CandidateTerm = GT.MapTerm