FolderView.purs 15.9 KB
Newer Older
1 2
module Gargantext.Components.FolderView where

3 4 5
import Gargantext.Prelude

import DOM.Simple (window)
6 7
import Data.Array as A
import Data.Maybe (Maybe(..), fromMaybe)
8
import Data.Traversable (traverse_)
9
import Effect.Aff (Aff)
10 11
import Effect.Class (liftEffect)
import Gargantext.AsyncTasks as GAT
arturo's avatar
arturo committed
12
import Gargantext.Components.App.Store (Boxes)
arturo's avatar
arturo committed
13
import Gargantext.Components.App.Store as AppStore
14 15 16
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.BaseModal (hideModal)
import Gargantext.Components.Bootstrap.Types (Elevation(..), Variant(..))
17 18 19 20 21 22 23 24
import Gargantext.Components.Forest.Tree.Node.Action.Add (AddNodeValue(..), addNode)
import Gargantext.Components.Forest.Tree.Node.Action.Contact as Contact
import Gargantext.Components.Forest.Tree.Node.Action.Delete (deleteNode, unpublishNode)
import Gargantext.Components.Forest.Tree.Node.Action.Link (linkNodeReq)
import Gargantext.Components.Forest.Tree.Node.Action.Merge (mergeNodeReq)
import Gargantext.Components.Forest.Tree.Node.Action.Move (moveNodeReq)
import Gargantext.Components.Forest.Tree.Node.Action.Rename (RenameValue(..), rename)
import Gargantext.Components.Forest.Tree.Node.Action.Share as Share
25
import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..))
26 27 28 29
import Gargantext.Components.Forest.Tree.Node.Action.Update (updateRequest)
import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadArbitraryFile, uploadFile)
import Gargantext.Components.Forest.Tree.Node.Box (nodePopupView)
import Gargantext.Components.Forest.Tree.Node.Tools.SubTree.Types (SubTreeOut(..))
30 31
import Gargantext.Components.GraphQL.Endpoints (getNode, getTreeFirstLevel)
import Gargantext.Components.GraphQL.Node (Node)
32
import Gargantext.Components.GraphQL.Tree (TreeFirstLevel, TreeNode)
33
import Gargantext.Config.REST (AffRESTError, logRESTError)
34
import Gargantext.Config.Utils (handleRESTError)
35
import Gargantext.Hooks.LinkHandler (useLinkHandler)
36
import Gargantext.Hooks.Loader (useLoader)
arturo's avatar
arturo committed
37
import Gargantext.Routes (AppRoute(Home), appPath, nodeTypeAppRoute)
38
import Gargantext.Sessions (Session(..), sessionId)
39
import Gargantext.Types (NodeType(..), SessionId)
40
import Gargantext.Types as GT
41
import Gargantext.Utils.Reactix as R2
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 47 48 49 50

here :: R2.Here
here = R2.here "Gargantext.Components.FolderView"

type Props =
arturo's avatar
arturo committed
51
  ( nodeId     :: Int
52
  , session    :: Session
53 54 55 56
  )

data FolderStyle = FolderUp | FolderChild

57
folderView :: R2.Leaf Props
58
folderView = R2.leafComponent folderViewCpt
59 60
folderViewCpt :: R.Component Props
folderViewCpt = here.component "folderViewCpt" cpt where
arturo's avatar
arturo committed
61
  cpt { nodeId, session } _ = do
62 63
    reload <- T.useBox T2.newReload
    reload' <- T.useLive T.unequal reload
64 65 66
    useLoader { errorHandler
              , loader: loadFolders
              , path: { nodeId, session, reload: reload'}
arturo's avatar
arturo committed
67
              , render: \folders -> folderViewMain { folders
68 69
                                                   , nodeId
                                                   , reload
70
                                                   , session
71
                                                   } [] }
72
    where
73
      errorHandler = logRESTError here "[folderView]"
74

75
type FolderViewProps =
arturo's avatar
arturo committed
76
  ( folders       :: TreeFirstLevel
77 78 79
  , nodeId        :: Int
  , reload        :: T.Box T2.Reload
  , session       :: Session
80 81
  )

82 83
folderViewMain :: R2.Component FolderViewProps
folderViewMain = R.createElement folderViewMainCpt
84 85
folderViewMainCpt :: R.Component FolderViewProps
folderViewMainCpt = here.component "folderViewMainCpt" cpt where
86
  cpt props@{ folders: {parent: parentNode, children, root} } _ = do
87
    let folders' = A.sortBy sortFolders children
88 89
    let parent = makeParentFolder root parentNode props
    let childrenEl = makeFolderElements folders' props
90

arturo's avatar
arturo committed
91 92 93 94 95
    pure $

      H.div
      { className: "folder-view" } $
      parent <> childrenEl
96

97
  makeFolderElements :: Array TreeNode -> Record FolderViewProps -> Array R.Element
98
  makeFolderElements folders' props = makeFolderElementsMap <$> folders' where
99
    makeFolderElementsMap :: TreeNode -> R.Element
arturo's avatar
arturo committed
100
    makeFolderElementsMap node = folder { nodeId: node.id
101 102 103 104 105 106 107
                                        , linkId: node.id
                                        , nodeType: node.node_type
                                        , linkNodeType: node.node_type
                                        , parentId: props.nodeId
                                        , reload: props.reload
                                        , session: props.session
                                        , style: FolderChild
108
                                        , text: node.name
109
                                        , disabled: false
110
                                        }
111 112 113

  makeParentFolder :: TreeNode -> Maybe TreeNode -> Record FolderViewProps -> Array R.Element
  makeParentFolder root (Just parent) props =
114 115
    [
      folder
arturo's avatar
arturo committed
116
      { nodeId: root.id
117 118 119 120 121 122 123
      , linkId: parent.id
      , linkNodeType: parent.node_type
      , nodeType: root.node_type
      , parentId: parent.id
      , reload: props.reload
      , session: props.session
      , style: FolderUp
124 125
      , text: "..."
      , disabled: disabled parent
126 127
      }
    ]
128 129
    where
      disabled { node_type } = if node_type == GT.FolderShared then true else false
130
  makeParentFolder _ Nothing _ = []
131

132 133
  sortFolders :: TreeNode-> TreeNode -> Ordering
  sortFolders a b = compare a.id b.id
134

135
type FolderProps =
136 137 138 139 140 141 142
  ( style         :: FolderStyle
  , text          :: String
  , nodeType      :: GT.NodeType
  , nodeId        :: Int
  , linkNodeType  :: GT.NodeType
  , linkId        :: Int
  , session       :: Session
143 144
  , parentId      :: Int
  , reload        :: T.Box T2.Reload
145
  , disabled      :: Boolean
146 147
  )

148 149
folder :: R2.Leaf FolderProps
folder = R2.leaf folderCpt
150 151
folderCpt :: R.Component FolderProps
folderCpt = here.component "folderCpt" cpt where
arturo's avatar
arturo committed
152
  cpt props@{ nodeId
153
            , nodeType
154 155
            , linkId
            , linkNodeType
156 157 158 159
            , parentId
            , reload
            , session
            , style
160
            , text
161
            , disabled
162
            } _ = do
arturo's avatar
arturo committed
163 164 165 166
    -- | States
    -- |

    boxes <- AppStore.use
167 168
    isBoxVisible <- T.useBox false

arturo's avatar
arturo committed
169 170
    -- | Computed
    -- |
171

172 173 174
    let sid = sessionId session
    let rootId = treeId session
    let dispatch a = performAction a { boxes, nodeId, parentId, reload, session, isBoxVisible }
175

arturo's avatar
arturo committed
176 177
    -- | Render
    -- |
178
    pure $
179 180

      H.div
arturo's avatar
arturo committed
181
      { className: "folder-view-item" }
182
      [
arturo's avatar
arturo committed
183 184 185
        H.a
        { className: "folder-view-item__body"
        , href: "/#/" <> href linkId rootId linkNodeType sid
186
        }
arturo's avatar
arturo committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
        [
          B.ripple
          { variant: Dark }
          [
            B.icon
            { className: "folder-view-item__icon"
            , name: icon style nodeType
            }
          ,
            B.div'
            { className: "folder-view-item__text" }
            text
          ]
        ]
      ,
        H.div
        { className: "folder-view-item__settings" }
arturo's avatar
arturo committed
204 205 206 207 208 209 210 211
        [
          B.iconButton
          { name: "cog"
          , callback: \_ -> T.write_ true isBoxVisible
          , title:
                "Each node of the Tree can perform some actions.\n"
              <> "Click here to execute one of them."
          , variant: Secondary
arturo's avatar
arturo committed
212 213 214
          , elevation: Level1
          , overlay: true
          , focusRing: false
arturo's avatar
arturo committed
215 216
          }
        ]
217 218 219 220 221 222 223 224 225 226
      ,
        -- // Modals //
        B.baseModal
        { isVisibleBox: isBoxVisible
        , noBody: true
        , noHeader: true
        , modalClassName: "forest-tree-node-modal"
        }
        [
          nodePopupView
arturo's avatar
arturo committed
227
          { boxes
228 229 230 231 232 233 234
          , dispatch: dispatch
          , id: props.nodeId
          , nodeType: props.nodeType
          , name: props.text
          , session: props.session
          , closeCallback: \_ -> T.write_ false isBoxVisible
          }
235
        ]
236
      ]
237

arturo's avatar
arturo committed
238 239 240 241
  href :: Int -> Int -> NodeType -> SessionId -> String
  href lId rootId nType sId
    | rootId == lId  = appPath Home
    | otherwise      = appPath $ getFolderPath nType sId lId
242 243

  icon :: FolderStyle -> GT.NodeType -> String
arturo's avatar
arturo committed
244 245
  icon FolderUp _  = "folder-open"
  icon _        nt = GT.getIcon nt true
246 247 248

  getFolderPath :: GT.NodeType -> GT.SessionId -> Int -> AppRoute
  getFolderPath nodeType sid nodeId = fromMaybe Home $ nodeTypeAppRoute nodeType sid nodeId
249

250 251 252
backButton :: R2.Component ()
backButton = R.createElement backButtonCpt
backButtonCpt :: R.Component ()
253
backButtonCpt = here.component "backButton" cpt where
254 255 256
  cpt _ _ = do
    { goToPreviousPage } <- useLinkHandler

arturo's avatar
arturo committed
257
    pure $
258 259
      H.button {
        className: "btn btn-primary"
260
      , on: { click: \_ -> goToPreviousPage unit }
261 262 263
      } [
        H.i { className: "fa fa-arrow-left", title: "Previous view"} []
      ]
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
backButtonSmart :: R2.Component (nodeId :: Int, session :: Session)
backButtonSmart = R.createElement backButtonSmartCpt

backButtonSmartCpt :: R.Component (nodeId :: Int, session :: Session)
backButtonSmartCpt = here.component "backButtonSmart" cpt where
  cpt {nodeId, session} _ = do
    reload <- T.useBox T2.newReload
    reload' <- T.useLive T.unequal reload
    useLoader { errorHandler
              , loader: loadNode
              , path: { nodeId, session, reload: reload' }
              , render: \node -> backButtonSmartMain { node, session } []
    }
    where
      errorHandler = logRESTError here "[folderView]"

backButtonSmartMain :: R2.Component (node :: Node, session :: Session)
backButtonSmartMain = R.createElement backButtonSmartMainCpt

backButtonSmartMainCpt :: R.Component (node :: Node, session :: Session)
backButtonSmartMainCpt = here.component "backButtonSmartMain" cpt where
  cpt { node, session } _ = do
    handlers <- useLinkHandler
    let rootId = treeId session

290
    pure $
291 292 293 294 295 296 297 298 299 300 301
      H.button {
        className: "btn btn-primary"
      , on: { click: action rootId node.parent_id handlers }
      } [
        H.i { className: "fa fa-arrow-left", title: "Previous view"} []
      ]
    where
      action rootId pId handlers
        | rootId == pId = handlers.goToRoute Home
        | otherwise = handlers.goToPreviousPage unit

302 303 304
treeId :: Session -> Int
treeId (Session {treeId: tId}) = tId

305 306 307 308 309 310 311
type LoadProps =
  (
    session :: Session,
    nodeId :: Int,
    reload :: T2.Reload
  )

312 313
loadFolders :: Record LoadProps -> AffRESTError TreeFirstLevel
loadFolders {nodeId, session} = getTreeFirstLevel session nodeId
314 315 316

loadNode :: Record LoadProps -> AffRESTError Node
loadNode {nodeId, session} = getNode session nodeId
317 318

type PerformActionProps =
319
  ( boxes         :: Boxes
320 321 322 323
  , nodeId        :: Int
  , parentId      :: Int
  , reload        :: T.Box T2.Reload
  , session       :: Session
324
  , isBoxVisible  :: T.Box Boolean
325 326 327 328 329 330 331 332 333 334 335 336
  )

performAction :: Action -> Record PerformActionProps -> Aff Unit
performAction = performAction' where
  performAction' (DeleteNode nt) p = deleteNode' nt p
  performAction' (DoSearch task) p = doSearch task p
  performAction' (UpdateNode params) p = updateNode params p
  performAction' (RenameNode name) p = renameNode name p
  performAction' (ShareTeam username) p = shareTeam username p
  performAction' (SharePublic { params }) p = sharePublic params p
  performAction' (AddContact params) p = addContact params p
  performAction' (AddNode name nodeType) p = addNode' name nodeType p
337 338
  performAction' (UploadFile nodeType fileType fileFormat lang mName contents selection) p =
    uploadFile' nodeType fileType fileFormat lang mName contents p selection
339 340
  performAction' (UploadArbitraryFile fileFormat mName blob selection) p =
    uploadArbitraryFile' fileFormat mName blob p selection
341
  performAction' DownloadNode _ = liftEffect $ here.log "[performAction] DownloadNode"
342 343 344
  performAction' (MoveNode {params}) p = moveNode params p
  performAction' (MergeNode {params}) p = mergeNode params p
  performAction' (LinkNode { nodeType, params }) p = linkNode nodeType params p
345
  performAction' NoAction _ = liftEffect $ here.log "[performAction] NoAction"
346
  performAction' CloseBox p = closeBox p
347
  performAction' _ _ = liftEffect $ here.log "[performAction] unsupported action"
348

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
  closeBox { isBoxVisible, nodeId } =
    liftEffect $ do
      T.write_ false isBoxVisible
      -- @XXX ReactJS unreactive ref
      --
      -- /!\ extra care here:
      --
      --  - due to a ReactJS yet another flaw, we have to make an extra closing
      --    modal method call here (bc. even if the `T.Box` change its value
      --    no reactivity will be perfomed, for some unknown reason, and
      --    the modal would so partially close)
      --
      --  - also make an extra assumption here, as the `querySelector` used for
      --    modal close call should be the same as the selector qualifying the
      --    created <base-modal>)
      hideModal window $ "#" <> (show nodeId)
365

366
  refreshFolders p@{ boxes: { reloadForest }, reload } = do
367
    closeBox p
368 369
    liftEffect $ T2.reload reload
    liftEffect $ T2.reload reloadForest
370

371
  deleteNode' nt p@{ nodeId: id, parentId: parent_id, session } = do
372
    case nt of
373
      NodePublic FolderPublic  -> void $ deleteNode session id
374
      NodePublic _             -> void $ unpublishNode session (Just parent_id) id
375
      _                        -> void $ deleteNode session id
376 377
    refreshFolders p

378
  doSearch task { boxes: { tasks }, nodeId: id } = liftEffect $ do
379
    GAT.insert id task tasks
380
    here.log2 "[performAction] DoSearch task:" task
381

382 383
  updateNode params { boxes: { errors, tasks }, nodeId: id, session } = do
    eTask <- updateRequest params session id
384
    handleRESTError errors eTask $ \task -> liftEffect $ do
385
      GAT.insert id task tasks
386
      here.log2 "[performAction] UpdateNode task:" task
387

388 389
  shareTeam username { boxes: { errors }, nodeId: id, session } = do
    eTask <- Share.shareReq session id $ Share.ShareTeamParams { username }
390
    handleRESTError errors eTask $ \_task -> pure unit
391

392
  sharePublic params p@{ boxes: { errors }, session } = traverse_ f params where
393
    f (SubTreeOut { in: inId, out }) = do
394
      eTask <- Share.shareReq session inId $ Share.SharePublicParams { node_id: out }
395
      handleRESTError errors eTask $ \_task -> pure unit
396 397
      refreshFolders p

398 399
  addContact params { nodeId: id, session } =
    void $ Contact.contactReq session id params
400

401 402
  uploadFile' nodeType fileType fileFormat lang mName contents { boxes: { errors, tasks }, nodeId: id, session } selection = do
    eTask <- uploadFile { contents, fileType, fileFormat, lang, id, nodeType, mName, selection, session }
403
    handleRESTError errors eTask $ \task -> liftEffect $ do
404
      GAT.insert id task tasks
405
      here.log2 "[performAction] UploadFile, uploaded, task:" task
406

407 408
  uploadArbitraryFile' fileFormat mName blob { boxes: { errors, tasks }, nodeId: id, session } selection = do
    eTask <- uploadArbitraryFile session id { blob, fileFormat, mName } selection
409
    handleRESTError errors eTask $ \task -> liftEffect $ do
410
      GAT.insert id task tasks
411
      here.log2 "[performAction] UploadArbitraryFile, uploaded, task:" task
412

413
  moveNode params p@{ boxes: { errors }, session } = traverse_ f params where
414
    f (SubTreeOut { in: in', out }) = do
415
      eTask <- moveNodeReq session in' out
416
      handleRESTError errors eTask $ \_task -> pure unit
417 418
      refreshFolders p

419
  mergeNode params p@{ boxes: { errors }, session } = traverse_ f params where
420
    f (SubTreeOut { in: in', out }) = do
421
      eTask <- mergeNodeReq session in' out
422
      handleRESTError errors eTask $ \_task -> pure unit
423 424
      refreshFolders p

425
  linkNode nodeType params p@{ boxes: { errors }, session } = traverse_ f params where
426
    f (SubTreeOut { in: in', out }) = do
427
      eTask <- linkNodeReq session nodeType in' out
428
      handleRESTError errors eTask $ \_task -> pure unit
429 430
      refreshFolders p

431 432
  renameNode name p@{ boxes: { errors }, nodeId: id, session } = do
    eTask <- rename session id $ RenameValue { text: name }
433
    handleRESTError errors eTask $ \_task -> pure unit
434 435
    refreshFolders p

436 437
  addNode' name nodeType p@{ boxes: { errors }, nodeId: id, session } = do
    eTask <- addNode session id $ AddNodeValue {name, nodeType}
438
    handleRESTError errors eTask $ \_task -> pure unit
439
    refreshFolders p