Texts.purs 18.3 KB
Newer Older
1
module Gargantext.Components.Nodes.Texts where
2

arturo's avatar
arturo committed
3 4
import Gargantext.Prelude

5
import Data.Generic.Rep (class Generic)
6
import Data.Maybe (Maybe(..), isJust)
7
import Data.Show.Generic (genericShow)
8
import Data.Tuple.Nested ((/\))
9
import Effect.Aff (launchAff_)
arturo's avatar
arturo committed
10
import Gargantext.Components.App.Store (Boxes)
arturo's avatar
arturo committed
11 12 13
import Gargantext.Components.App.Store as AppStore
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (Elevation(..))
14
import Gargantext.Components.Charts.Options.ECharts (dispatchAction)
arturo's avatar
arturo committed
15
import Gargantext.Components.Charts.Options.Type (EChartsInstance, EChartActionData)
16
import Gargantext.Components.DocsTable as DT
17
import Gargantext.Components.DocsTable.Types (Year)
18 19 20
import Gargantext.Components.Document.API (loadData)
import Gargantext.Components.Document.Layout (layout)
import Gargantext.Components.Document.Types (LoadedData, DocPath)
21
import Gargantext.Components.NgramsTable.Loader (clearCache)
22
import Gargantext.Components.Node (NodePoly(..))
23
import Gargantext.Components.Nodes.Corpus (loadCorpusWithChild)
24
import Gargantext.Components.Nodes.Corpus.Chart.Histo (histo)
25
import Gargantext.Components.Nodes.Corpus.Chart.Types as CTypes
26
import Gargantext.Components.Nodes.Corpus.Document as D
27
import Gargantext.Components.Nodes.Corpus.Types (CorpusData)
28 29
import Gargantext.Components.Nodes.Lists.Types as LT
import Gargantext.Components.Nodes.Texts.Types as TT
30
import Gargantext.Components.Nodes.Texts.Types as TextsT
arturo's avatar
arturo committed
31
import Gargantext.Components.Reload (textsReloadContext)
32
import Gargantext.Components.Tab as Tab
33
import Gargantext.Components.Table as Table
34
import Gargantext.Config.REST (logRESTError)
James Laver's avatar
James Laver committed
35
import Gargantext.Ends (Frontends)
36
import Gargantext.Hooks.Loader (useLoader, useLoaderEffect)
arturo's avatar
arturo committed
37 38
import Gargantext.Hooks.Session (useSession)
import Gargantext.Sessions (Session, getCacheState, sessionId)
39
import Gargantext.Types (CTabNgramType(..), ListId, NodeID, SidePanelState(..), TabSubType(..), TabType(..))
arturo's avatar
arturo committed
40
import Gargantext.Utils ((?))
41
import Gargantext.Utils.Reactix as R2
arturo's avatar
arturo committed
42
import Gargantext.Utils.Toestand as T2
43 44 45
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
46

James Laver's avatar
James Laver committed
47 48
here :: R2.Here
here = R2.here "Gargantext.Components.Nodes.Texts"
49

arturo's avatar
arturo committed
50 51
type Props =
  ( frontends :: Frontends
52
  , nodeId    :: NodeID
53 54
  )

arturo's avatar
arturo committed
55 56
textsLayout :: R2.Leaf Props
textsLayout = R2.leaf textsLayoutCpt
57

58
textsLayoutCpt :: R.Component Props
James Laver's avatar
James Laver committed
59
textsLayoutCpt = here.component "textsLayout" cpt where
arturo's avatar
arturo committed
60
  cpt { frontends, nodeId } _ = do
arturo's avatar
arturo committed
61 62 63 64 65 66 67

    _ /\ reloadBox <- R2.useBox' T2.newReload

    pure $
      R.provideContext textsReloadContext (Just reloadBox)
      [
        textsLayoutWithKey
arturo's avatar
arturo committed
68
          { key: show nodeId
arturo's avatar
arturo committed
69 70
          , frontends
          , nodeId
arturo's avatar
arturo committed
71
          }
arturo's avatar
arturo committed
72 73
      ]

arturo's avatar
arturo committed
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
---------------------------------------------------------------

textsLayoutWithKey :: R2.Leaf ( key :: String | Props )
textsLayoutWithKey = R2.leaf textsLayoutWithKeyCpt

textsLayoutWithKeyCpt :: R.Component ( key :: String | Props )
textsLayoutWithKeyCpt = here.component "textsLayoutWithKey" cpt where
  cpt { frontends
      , nodeId
      } _ = do
    session <- useSession

    boxes@{ sidePanelTexts } <- AppStore.use

    cacheState <- T.useBox $ getCacheState LT.CacheOff session nodeId
    cacheState' <- T.useLive T.unequal cacheState

    yearFilter <- T.useBox (Nothing :: Maybe Year)

    eChartsInstance <- T.useBox (Nothing :: Maybe EChartsInstance)

    R.useEffectOnce' $ do
      T.listen (\{ new } -> afterCacheStateChange new) cacheState

    useLoader { errorHandler
              , loader: loadCorpusWithChild
              , path: { nodeId, session }
              , render: \corpusData@{ corpusId, corpusNode } ->
                  let
                    NodePoly { name, date, hyperdata } = corpusNode

                  in
                    H.div
                    { className: "texts-layout" }
                    [
                      Table.tableHeaderWithRenameLayout
                      { cacheState
                      , name
                      , date
                      , hyperdata
                      , nodeId: corpusId
                      , session
                      , key: "textsLayoutWithKey-" <> (show cacheState')
                      }
                    ,
                      tabs
                      { boxes
                      , cacheState
                      , corpusData
                      , corpusId
                      , eChartsInstance
                      , frontends
                      , session
                      , sidePanel: sidePanelTexts
                      , yearFilter
                      }
                    ]
              }
    where
      errorHandler = logRESTError here "[textsLayoutWithKey]"
134
      afterCacheStateChange _cacheState = do
arturo's avatar
arturo committed
135 136 137 138 139 140
        launchAff_ $ clearCache unit
        -- TODO
        --sessionUpdate $ setCacheState session nodeId cacheState
        --_ <- setCacheState session nodeId cacheState

-----------------------------------------------------
141 142 143

data Mode = MoreLikeFav | MoreLikeTrash

144
derive instance Generic Mode _
145

146
instance Show Mode where
147 148
  show = genericShow

149
derive instance Eq Mode
150 151 152 153 154

modeTabType :: Mode -> CTabNgramType
modeTabType MoreLikeFav    = CTabAuthors  -- TODO
modeTabType MoreLikeTrash  = CTabSources  -- TODO

James Laver's avatar
James Laver committed
155
type TabsProps =
156 157
  ( boxes           :: Boxes
  , cacheState      :: T.Box LT.CacheState
158 159
  , corpusData      :: CorpusData
  , corpusId        :: NodeID
160
  , eChartsInstance :: T.Box (Maybe EChartsInstance)
161 162 163 164
  , frontends       :: Frontends
  , session         :: Session
  , sidePanel       :: T.Box (Maybe (Record TT.SidePanel))
  , yearFilter      :: T.Box (Maybe Year)
165
  )
166 167 168 169

tabs :: Record TabsProps -> R.Element
tabs props = R.createElement tabsCpt props []
tabsCpt :: R.Component TabsProps
170
tabsCpt = here.component "tabs" cpt
171
  where
172 173
    cpt { boxes
        , cacheState
174 175 176 177 178 179 180
        , corpusId
        , corpusData
        , eChartsInstance
        , frontends
        , session
        , sidePanel
        , yearFilter } _ = do
181 182 183 184 185 186 187 188 189 190 191 192 193 194

      let
        path = initialPath

        onInit = Just \i -> T.write_ (Just i) eChartsInstance

        onClick = Just \opts@{ name } -> do
          T.write_ (Just name) yearFilter
          T.read eChartsInstance >>= case _ of
            Nothing -> pure unit
            Just i  -> do
              -- @XXX due to lack of support for "echart.select" action,
              --      have to manually rely on a set/unset selection
              --      targeting the "echart.emphasis" action
arturo's avatar
arturo committed
195 196 197 198 199 200 201 202 203 204
              let
                opts' :: Record EChartActionData
                opts' =
                  { dataIndex   : opts.dataIndex
                  , name        : opts.name
                  , seriesId    : opts.seriesId
                  , seriesIndex : opts.seriesIndex
                  , seriesName  : opts.seriesName
                  , type        : "highlight"
                  }
205
              dispatchAction i { type: "downplay" }
arturo's avatar
arturo committed
206
              dispatchAction i opts'
207

208 209
      activeTab <- T.useBox 0

210 211
      chartReload <- T.useBox T2.newReload

212 213 214 215
      pure $
        Tab.tabs
        { className: "nodes-texts-layout-tabs"
        , activeTab
216 217
        , tabs: [
            "Documents"       /\ R.fragment [
218 219
                histoRender { boxes, path, onClick, onInit, reload: chartReload, session } []
              , docView' path chartReload TabDocs
220
              ]
221
          , "Trash"           /\ docView' path chartReload TabTrash
222 223
          -- , "More like fav"   /\ docView' path TabMoreLikeFav
          -- , "More like trash" /\ docView' path TabMoreLikeTrash
224 225 226
          ]
        }

227
      where
228 229 230 231
        initialPath = { corpusId
                      , listId: corpusData.defaultListId
                      , limit: Nothing
                      , tabType: TabCorpus TabDocs }
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
        docView' path chartReload tabType = docView { boxes
                                                    , cacheState
                                                    , chartReload
                                                    , corpusData
                                                    , corpusId
                                                    , frontends
                                                    , listId: path.listId
                                                      -- , path
                                                    , session
                                                    , tabType
                                                    , sidePanel
                                                    , yearFilter
                                                    } []

type HistoProps =
  ( reload  :: T2.ReloadS
  | CTypes.Props
  )

histoRender :: R2.Component HistoProps
histoRender = R.createElement histoRenderCpt
histoRenderCpt :: R.Component HistoProps
histoRenderCpt = here.component "histoRender" cpt where
  cpt { boxes, path, onClick, onInit, reload, session } _ = do
256
    _ <- T.useLive T.unequal reload
257 258

    pure $ histo { boxes, path, onClick, onInit, session }
259

260
type DocViewProps a =
261 262 263 264 265 266 267 268 269 270 271 272
  ( boxes       :: Boxes
  , cacheState  :: T.Box LT.CacheState
  , chartReload :: T2.ReloadS
  , corpusData  :: CorpusData
  , corpusId    :: NodeID
  , frontends   :: Frontends
  , listId      :: ListId
  -- , path     :: Record DT.Path
  , session     :: Session
  , tabType     :: TabSubType a
  , sidePanel   :: T.Box (Maybe (Record TT.SidePanel))
  , yearFilter  :: T.Box (Maybe Year)
273
  )
274

275 276
docView :: forall a. R2.Component (DocViewProps a)
docView = R.createElement docViewCpt
277
docViewCpt :: forall a. R.Component (DocViewProps a)
278
docViewCpt = here.component "docView" cpt
279
  where
280 281 282 283
    cpt props _children = do
      pure $ DT.docViewLayout $ docViewLayoutRec props

-- docViewLayoutRec :: forall a. DocViewProps a -> Record DT.LayoutProps
284 285
docViewLayoutRec { boxes
                 , cacheState
286
                 , chartReload
287 288
                 , corpusId
                 , frontends
289
                 , listId
290 291
                 , session
                 , tabType: TabDocs
292
                 , sidePanel
293 294
                 , yearFilter
                 } =
295 296
  { boxes
  , cacheState
297
  , chart  : H.div {} []
298
  , chartReload
299
  , frontends
300
  , listId
301
  , mCorpusId: Just corpusId
302 303 304
  , nodeId: corpusId
    -- ^ TODO merge nodeId and corpusId in DT
  , session
305
  , showSearch: true
306
  , sidePanel
307 308
  , tabType: TabCorpus TabDocs
  , totalRecords: 4737
309
  , yearFilter
310
  }
311 312
docViewLayoutRec { boxes
                 , cacheState
313
                 , chartReload
314 315
                 , corpusId
                 , frontends
316
                 , listId
317 318
                 , session
                 , tabType: TabMoreLikeFav
319
                 , sidePanel
320 321
                 , yearFilter
                 } =
322 323
  { boxes
  , cacheState
324
  , chart  : H.div {} []
325
  , chartReload
326
  , frontends
327
  , listId
328
  , mCorpusId: Just corpusId
329 330 331
  , nodeId: corpusId
    -- ^ TODO merge nodeId and corpusId in DT
  , session
332
  , showSearch: false
333
  , sidePanel
334 335
  , tabType: TabCorpus TabMoreLikeFav
  , totalRecords: 4737
336
  , yearFilter
337
  }
338 339
docViewLayoutRec { boxes
                 , cacheState
340
                 , chartReload
341 342
                 , corpusId
                 , frontends
343
                 , listId
344 345
                 , session
                 , tabType: TabMoreLikeTrash
346
                 , sidePanel
347 348
                 , yearFilter
                 } =
349 350
  { boxes
  , cacheState
351
  , chart  : H.div {} []
352
  , chartReload
353
  , frontends
354
  , listId
355
  , mCorpusId: Just corpusId
356 357 358
  , nodeId: corpusId
  -- ^ TODO merge nodeId and corpusId in DT
  , session
359
  , showSearch: false
360
  , sidePanel
361 362
  , tabType: TabCorpus TabMoreLikeTrash
  , totalRecords: 4737
363
  , yearFilter
364
  }
365 366
docViewLayoutRec { boxes
                 , cacheState
367
                 , chartReload
368 369
                 , corpusId
                 , frontends
370
                 , listId
371 372
                 , session
                 , tabType: TabTrash
373
                 , sidePanel
374 375
                 , yearFilter
                 } =
376 377
  { boxes
  , cacheState
378
  , chart  : H.div {} []
379
  , chartReload
380
  , frontends
381
  , listId
382
  , mCorpusId: Just corpusId
383 384 385
  , nodeId: corpusId
  -- ^ TODO merge nodeId and corpusId in DT
  , session
386
  , showSearch: true
387
  , sidePanel
388 389
  , tabType: TabCorpus TabTrash
  , totalRecords: 4737
390
  , yearFilter
391
  }
392
-- DUMMY
393 394
docViewLayoutRec { boxes
                 , cacheState
395
                 , chartReload
396 397
                 , corpusId
                 , frontends
398
                 , listId
399
                 , session
400
                 , sidePanel
401
                 , tabType
402 403
                 , yearFilter
                 } =
404 405
  { boxes
  , cacheState
406
  , chart  : H.div {} []
407
  , chartReload
408
  , frontends
409
  , listId
410
  , mCorpusId: Just corpusId
411 412 413
  , nodeId: corpusId
  -- ^ TODO merge nodeId and corpusId in DT
  , session
414
  , showSearch: true
415
  , sidePanel
416 417
  , tabType: TabCorpus TabTrash
  , totalRecords: 4737
418
  , yearFilter
419
  }
420 421 422 423


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

arturo's avatar
arturo committed
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
textsSidePanel :: R2.Leaf ()
textsSidePanel = R2.leaf textsSidePanelCpt

textsSidePanelCpt :: R.Component ()
textsSidePanelCpt = here.component "sidePanel" cpt where
  cpt _ _ = do

    { sidePanelState
    , sidePanelTexts
    } <- AppStore.use

    session <- useSession

    sidePanelState' <- R2.useLive' sidePanelState
    sidePanelTexts' <- R2.useLive' sidePanelTexts

    -- R.useEffect' $ do
    --   let toggleSidePanel' _  = snd sidePanelState toggleSidePanelState
    --       triggerSidePanel' _ = snd sidePanelState $ const Opened
    --   R2.setTrigger toggleSidePanel  toggleSidePanel'
    --   R2.setTrigger triggerSidePanel triggerSidePanel'

    -- (mCorpusId /\ setMCorpusId) <- R.useState' Nothing
    -- (mListId /\ setMListId) <- R.useState' Nothing
    -- (mNodeId /\ setMNodeId) <- R.useState' Nothing

    -- R.useEffect3 mCorpusId mListId mNodeId $ do
    --   if mCorpusId == Just corpusId && mListId == Just listId && mNodeId == Just nodeId && mCurrentDocId == Just nodeId then do
    --     T.modify_ (\sp -> sp { mCurrentDocId = Nothing }) sidePanelTexts
    --   else do
    --     T.modify_ (\sp -> sp { mCorpusId = Just corpusId
    --                         , mCurrentDocId = Just nodeId
    --                         , mListId = Just listId
    --                         , mNodeId = Just nodeId }) sidePanelTexts
      -- let trigger :: Record TriggerAnnotatedDocIdChangeParams -> Effect Unit
      --     trigger { corpusId, listId, nodeId } = do
            -- log2 "[sidePanel trigger] trigger corpusId change" corpusId
            -- log2 "[sidePanel trigger] trigger listId change" listId
            -- log2 "[sidePanel trigger] trigger nodeId change" nodeId
            -- if mCorpusId == Just corpusId && mListId == Just listId && mNodeId == Just nodeId && mCurrentDocId == Just nodeId then do
              -- R.setRef currentDocIdRef Nothing
              -- T.modify_ (\sp -> sp { mCurrentDocId = Nothing }) sidePanelTexts
              -- R2.callTrigger toggleSidePanel unit
            -- else do
              -- setMCorpusId $ const $ Just corpusId
              -- setMListId $ const $ Just listId
              -- setMNodeId $ const $ Just nodeId
              -- R.setRef currentDocIdRef $ Just nodeId
              -- R2.callTrigger triggerSidePanel unit
              -- T.modify_ (\sp -> sp { mCorpusId = Just corpusId
              --                     , mCurrentDocId = Just nodeId
              --                     , mListId = Just listId
              --                     , mNodeId = Just nodeId }) sidePanelTexts
      -- log2 "[sidePanel] trigger" trigger
      -- R2.setTrigger triggerAnnotatedDocIdChange trigger
      -- pure unit

      -- pure $ do
      --   -- log "[sidePanel] clearing triggerAnnotatedDocIdChange"
      --   R2.clearTrigger triggerAnnotatedDocIdChange

    let closeSidePanel _ = do
          -- T.modify_ (\sp -> sp { mCurrentDocId = Nothing
          --                     , state = Closed }) sidePanelTexts
          T.write_ Closed sidePanelState
          T.write_ Nothing sidePanelTexts


    pure $

      H.div
      -- @XXX: ReactJS lack of "keep-alive" feature workaround solution
      -- @link https://github.com/facebook/react/issues/12039
      { className: "texts-sidepanel"
      , style: { display: sidePanelState' == Opened ? "block" $ "none" }
      }
      [
        H.div
        { className: "texts-sidepanel__inner" }
        [
          H.div
          { className: "texts-sidepanel__header" }
          [
            B.iconButton
            { name: "times"
            , elevation: Level2
            , callback: closeSidePanel
            }
          ]
        ,
          H.div
          { className: "texts-sidepanel__body" }
          [
            case sidePanelTexts' of
              Nothing ->
                B.caveat
                {}
                [ H.text $ "You can select a document to see its content" ]

523 524 525 526 527
              Just (sidePanelTexts_ :: Record TextsT.SidePanel) ->
                sideText
                { session
                , sidePanelText: sidePanelTexts_
                , key: show $ sidePanelTexts_.nodeId
arturo's avatar
arturo committed
528
                }
529 530
          ]
        ]
531
      ]
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602

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

type SideText =
  ( sidePanelText  :: Record TextsT.SidePanel
  , session       :: Session
  -- @TODO handling `closeCallback` when SidePanelText will be extracted from
  -- application layout business (and put inside the Node Document business)
  -- , closeCallback :: Unit -> Effect Unit
  )

sideText :: R2.Leaf ( key :: String | SideText )
sideText = R2.leaf sideTextCpt

sideTextCpt :: R.Component ( key :: String | SideText )
sideTextCpt = here.component "sideText" cpt where
  cpt { sidePanelText: { corpusId, listId, nodeId }
      , session
      } _ = do
    -- | States
    -- |
    state' /\ state <- R2.useBox' (Nothing :: Maybe LoadedData)

    -- | Computed
    -- |
    let

      tabType :: TabType
      tabType = TabDocument (TabNgramType CTabTerms)

      path :: DocPath
      path =
        { listIds: [listId]
        , mCorpusId: Just corpusId
        , nodeId
        , session
        , tabType
        }

    -- | Hooks
    -- |
    useLoaderEffect
      { errorHandler: logRESTError here "[sidePanelText]"
      , loader: loadData
      , path
      , state
      }

    -- | Render
    -- |
    pure $


      H.div
      { className: "graph-doc-focus" }
      [
        B.cloak
        { isDisplayed: isJust state'
        , idlingPhaseDuration: Just 150
        , cloakSlot:
            B.preloader
            {}

        , defaultSlot:
            R2.fromMaybe state' \loaded ->
              layout
              { loaded
              , path
              }
        }
      ]