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

import Data.Array as A
4
import Data.Eq ((==))
5
import Data.Maybe (Maybe(..), fromMaybe)
6
import Data.Nullable (null)
7
import Data.Traversable (traverse_)
8
import Effect (Effect)
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)
13 14 15 16 17 18 19 20
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
21
import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..))
22 23 24 25
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(..))
26 27
import Gargantext.Components.GraphQL.Endpoints (getNode, getTreeFirstLevel)
import Gargantext.Components.GraphQL.Node (Node)
28
import Gargantext.Components.GraphQL.Tree (TreeFirstLevel, TreeNode)
29
import Gargantext.Config.REST (AffRESTError, logRESTError)
30
import Gargantext.Config.Utils (handleRESTError)
31
import Gargantext.Hooks.LinkHandler (useLinkHandler)
32
import Gargantext.Hooks.Loader (useLoader)
33
import Gargantext.Prelude (Ordering, Unit, bind, compare, discard, otherwise, pure, unit, void, ($), (<$>), (<>))
34
import Gargantext.Routes (AppRoute(Home), nodeTypeAppRoute)
35
import Gargantext.Sessions (Session(..), sessionId)
36
import Gargantext.Types (NodeType(..), SessionId)
37
import Gargantext.Types as GT
38
import Gargantext.Utils.Popover as Popover
39
import Gargantext.Utils.Reactix as R2
40
import Gargantext.Utils.Toestand as T2
41 42 43 44
import Reactix as R
import Reactix.DOM.HTML as H
import Record as Record
import Toestand as T
45 46 47 48 49

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

type Props =
50
  ( boxes      :: Boxes
51 52
  , nodeId     :: Int
  , 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
61
  cpt { boxes, nodeId, session } _ = do
62 63 64
    setPopoverRef <- R.useRef Nothing
    reload <- T.useBox T2.newReload
    reload' <- T.useLive T.unequal reload
65 66 67
    useLoader { errorHandler
              , loader: loadFolders
              , path: { nodeId, session, reload: reload'}
68
              , render: \folders -> folderViewMain { boxes
69 70 71
                                                   , folders
                                                   , nodeId
                                                   , reload
72
                                                   , session
73
                                                   , setPopoverRef } [] }
74
    where
75
      errorHandler = logRESTError here "[folderView]"
76

77
type FolderViewProps =
78
  ( boxes         :: Boxes
79
  , folders       :: TreeFirstLevel
80 81 82
  , nodeId        :: Int
  , reload        :: T.Box T2.Reload
  , session       :: Session
83
  , setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
84 85
  )

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

95
    pure $ H.div {className: "fv folders"} $ parent <> childrenEl
96

97
  makeFolderElements :: Array TreeNode -> Record FolderViewProps -> Array R.Element
98
  makeFolderElements folders' props = makeFolderElementsMap <$> folders' where
99 100
    makeFolderElementsMap :: TreeNode -> R.Element
    makeFolderElementsMap node = folder { boxes: props.boxes
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
                                        , nodeId: node.id
                                        , linkId: node.id
                                        , nodeType: node.node_type
                                        , linkNodeType: node.node_type
                                        , parentId: props.nodeId
                                        , reload: props.reload
                                        , session: props.session
                                        , setPopoverRef: props.setPopoverRef
                                        , style: FolderChild
                                        , text: node.name } []

  makeParentFolder :: TreeNode -> Maybe TreeNode -> Record FolderViewProps -> Array R.Element
  makeParentFolder root (Just parent) props =
    [ folder { boxes: props.boxes
             , nodeId: root.id
             , linkId: parent.id
             , linkNodeType: parent.node_type
             , nodeType: root.node_type
             , parentId: parent.id
             , reload: props.reload
             , session: props.session
             , setPopoverRef: props.setPopoverRef
             , style: FolderUp
             , text: root.name } [] ]
  makeParentFolder _ Nothing _ = []
126

127 128
  sortFolders :: TreeNode-> TreeNode -> Ordering
  sortFolders a b = compare a.id b.id
129

130
type FolderProps =
131 132 133 134 135 136 137 138
  ( style         :: FolderStyle
  , text          :: String
  , nodeType      :: GT.NodeType
  , nodeId        :: Int
  , linkNodeType  :: GT.NodeType
  , linkId        :: Int
  , session       :: Session
  , boxes         :: Boxes
139 140 141
  , parentId      :: Int
  , reload        :: T.Box T2.Reload
  , setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
142 143
  )

144 145 146 147
folder :: R2.Component FolderProps
folder = R.createElement folderCpt
folderCpt :: R.Component FolderProps
folderCpt = here.component "folderCpt" cpt where
148 149 150
  cpt props@{ boxes
            , nodeId
            , nodeType
151 152
            , linkId
            , linkNodeType
153 154 155 156 157 158
            , parentId
            , reload
            , session
            , setPopoverRef
            , style
            , text } _ = do
159
    let sid = sessionId session
160
    let rootId = treeId session
161
    let dispatch a = performAction a { boxes, nodeId, parentId, reload, session, setPopoverRef }
162
    popoverRef <- R.useRef null
163
    { goToRoute } <- useLinkHandler
164 165 166 167

    R.useEffect' $ do
        R.setRef setPopoverRef $ Just $ Popover.setOpen popoverRef

168
    pure $
169 170
        H.div {} [
        H.span{style: {position: "absolute"}} [ Popover.popover {
171 172 173 174
            arrow: false
          , open: false
          , onClose: \_ -> pure unit
          , onOpen:  \_ -> pure unit
175
          , ref: popoverRef
176
          } [
177
              popOverIcon
178
              , mNodePopupView (Record.merge props { dispatch }) (onPopoverClose popoverRef)
179
              ]]
180
      , H.button {on: {click: \_ -> goToRoute $ route linkId rootId linkNodeType sid }, className: "btn btn-primary fv btn" } [
181
          H.i {className: icon style nodeType} []
182
        , H.br {}
183
        , H.text text]]
184

185 186
  onPopoverClose popoverRef _ = Popover.setOpen popoverRef false

187
  popOverIcon = H.span { className: "fv action" } [
188
        H.a { className: "settings fa fa-cog"
189
          , title : "Each node of the Tree can perform some actions.\n"
190
            <> "Click here to execute one of them." } []
191 192
      ]

193
  mNodePopupView props opc = nodePopupView { boxes: props.boxes
194 195
                                           , dispatch: props.dispatch
                                           , id: props.nodeId
196 197 198
                                           , onPopoverClose: opc
                                           , nodeType: props.nodeType
                                           , name: props.text
199 200
                                           , session: props.session
                                           }
201 202 203 204 205 206 207 208 209 210 211
  route :: Int -> Int -> NodeType -> SessionId -> AppRoute
  route lId rootId nType sid
    | rootId == lId    = Home
    | otherwise        = getFolderPath nType sid lId

  icon :: FolderStyle -> GT.NodeType -> String
  icon FolderUp _ = "fa fa-folder-open"
  icon _ nodeType = GT.fldr nodeType false

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

213 214 215
backButton :: R2.Component ()
backButton = R.createElement backButtonCpt
backButtonCpt :: R.Component ()
216
backButtonCpt = here.component "backButton" cpt where
217 218 219
  cpt _ _ = do
    { goToPreviousPage } <- useLinkHandler

arturo's avatar
arturo committed
220
    pure $
221 222
      H.button {
        className: "btn btn-primary"
223
      , on: { click: \_ -> goToPreviousPage unit }
224 225 226
      } [
        H.i { className: "fa fa-arrow-left", title: "Previous view"} []
      ]
227

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
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

    pure $ 
      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

265 266 267
treeId :: Session -> Int
treeId (Session {treeId: tId}) = tId

268 269 270 271 272 273 274
type LoadProps =
  (
    session :: Session,
    nodeId :: Int,
    reload :: T2.Reload
  )

275 276
loadFolders :: Record LoadProps -> AffRESTError TreeFirstLevel
loadFolders {nodeId, session} = getTreeFirstLevel session nodeId
277 278 279

loadNode :: Record LoadProps -> AffRESTError Node
loadNode {nodeId, session} = getNode session nodeId
280 281

type PerformActionProps =
282
  ( boxes         :: Boxes
283 284 285 286 287
  , nodeId        :: Int
  , parentId      :: Int
  , reload        :: T.Box T2.Reload
  , setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
  , session       :: Session
288 289 290 291 292 293 294 295 296 297 298 299
  )

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
300 301 302 303
  performAction' (UploadFile nodeType fileType fileFormat mName contents selection) p =
    uploadFile' nodeType fileType fileFormat mName contents p selection
  performAction' (UploadArbitraryFile fileFormat mName blob selection) p =
    uploadArbitraryFile' fileFormat mName blob p selection
304
  performAction' DownloadNode _ = liftEffect $ here.log "[performAction] DownloadNode"
305 306 307
  performAction' (MoveNode {params}) p = moveNode params p
  performAction' (MergeNode {params}) p = mergeNode params p
  performAction' (LinkNode { nodeType, params }) p = linkNode nodeType params p
308
  performAction' NoAction _ = liftEffect $ here.log "[performAction] NoAction"
309
  performAction' ClosePopover p = closePopover p
310
  performAction' _ _ = liftEffect $ here.log "[performAction] unsupported action"
311 312 313 314

  closePopover { setPopoverRef } =
    liftEffect $ traverse_ (\set -> set false) (R.readRef setPopoverRef)

315
  refreshFolders p@{ boxes: { reloadForest }, reload } = do
316 317
    liftEffect $ T2.reload reload
    liftEffect $ T2.reload reloadForest
318 319
    closePopover p

320
  deleteNode' nt p@{ nodeId: id, parentId: parent_id, session } = do
321
    case nt of
322
      NodePublic FolderPublic  -> void $ deleteNode session id
323
      NodePublic _             -> void $ unpublishNode session (Just parent_id) id
324
      _                        -> void $ deleteNode session id
325 326
    refreshFolders p

327
  doSearch task { boxes: { tasks }, nodeId: id } = liftEffect $ do
328
    GAT.insert id task tasks
329
    here.log2 "[performAction] DoSearch task:" task
330

331 332
  updateNode params { boxes: { errors, tasks }, nodeId: id, session } = do
    eTask <- updateRequest params session id
333
    handleRESTError errors eTask $ \task -> liftEffect $ do
334
      GAT.insert id task tasks
335
      here.log2 "[performAction] UpdateNode task:" task
336

337 338
  shareTeam username { boxes: { errors }, nodeId: id, session } = do
    eTask <- Share.shareReq session id $ Share.ShareTeamParams { username }
339
    handleRESTError errors eTask $ \_task -> pure unit
340

341
  sharePublic params p@{ boxes: { errors }, session } = traverse_ f params where
342
    f (SubTreeOut { in: inId, out }) = do
343
      eTask <- Share.shareReq session inId $ Share.SharePublicParams { node_id: out }
344
      handleRESTError errors eTask $ \_task -> pure unit
345 346
      refreshFolders p

347 348
  addContact params { nodeId: id, session } =
    void $ Contact.contactReq session id params
349

350 351
  uploadFile' nodeType fileType fileFormat mName contents { boxes: { errors, tasks }, nodeId: id, session } selection = do
    eTask <- uploadFile { contents, fileType, fileFormat, id, nodeType, mName, selection, session }
352
    handleRESTError errors eTask $ \task -> liftEffect $ do
353
      GAT.insert id task tasks
354
      here.log2 "[performAction] UploadFile, uploaded, task:" task
355

356 357
  uploadArbitraryFile' fileFormat mName blob { boxes: { errors, tasks }, nodeId: id, session } selection = do
    eTask <- uploadArbitraryFile session id { blob, fileFormat, mName } selection
358
    handleRESTError errors eTask $ \task -> liftEffect $ do
359
      GAT.insert id task tasks
360
      here.log2 "[performAction] UploadArbitraryFile, uploaded, task:" task
361

362
  moveNode params p@{ boxes: { errors }, session } = traverse_ f params where
363
    f (SubTreeOut { in: in', out }) = do
364
      eTask <- moveNodeReq session in' out
365
      handleRESTError errors eTask $ \_task -> pure unit
366 367
      refreshFolders p

368
  mergeNode params p@{ boxes: { errors }, session } = traverse_ f params where
369
    f (SubTreeOut { in: in', out }) = do
370
      eTask <- mergeNodeReq session in' out
371
      handleRESTError errors eTask $ \_task -> pure unit
372 373
      refreshFolders p

374
  linkNode nodeType params p@{ boxes: { errors }, session } = traverse_ f params where
375
    f (SubTreeOut { in: in', out }) = do
376
      eTask <- linkNodeReq session nodeType in' out
377
      handleRESTError errors eTask $ \_task -> pure unit
378 379
      refreshFolders p

380 381
  renameNode name p@{ boxes: { errors }, nodeId: id, session } = do
    eTask <- rename session id $ RenameValue { text: name }
382
    handleRESTError errors eTask $ \_task -> pure unit
383 384
    refreshFolders p

385 386
  addNode' name nodeType p@{ boxes: { errors }, nodeId: id, session } = do
    eTask <- addNode session id $ AddNodeValue {name, nodeType}
387
    handleRESTError errors eTask $ \_task -> pure unit
388
    refreshFolders p