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

import Data.Array as A
import Data.Maybe (Maybe(..), fromMaybe)
5
import Data.Nullable (null)
6
import Data.Traversable (traverse_)
7
import Effect (Effect)
8
import Effect.Aff (Aff)
9 10
import Effect.Class (liftEffect)
import Gargantext.AsyncTasks as GAT
11
import Gargantext.Components.App.Data (Boxes)
12 13 14 15 16 17 18 19
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
20
import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..))
21 22 23
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)
24
import Gargantext.Components.Forest.Tree.Node.Tools.FTree (FTree, LNode(..), NTree(..), fTreeID)
25
import Gargantext.Components.Forest.Tree.Node.Tools.SubTree.Types (SubTreeOut(..))
26
import Gargantext.Config.REST (AffRESTError, logRESTError)
27
import Gargantext.Config.Utils (handleRESTError)
28
import Gargantext.Hooks.LinkHandler (Methods, useLinkHandler)
29
import Gargantext.Hooks.Loader (useLoader)
30
import Gargantext.Prelude (Ordering, Unit, bind, compare, discard, pure, unit, void, ($), (<$>), (<>))
31
import Gargantext.Routes (AppRoute(Home), SessionRoute(..), nodeTypeAppRoute)
32
import Gargantext.Sessions (Session, get, sessionId)
33
import Gargantext.Types (NodeType(..))
34
import Gargantext.Types as GT
35
import Gargantext.Utils.Popover as Popover
36
import Gargantext.Utils.Reactix as R2
37
import Gargantext.Utils.Toestand as T2
38 39 40 41
import Reactix as R
import Reactix.DOM.HTML as H
import Record as Record
import Toestand as T
42 43 44 45 46

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

type Props =
47 48 49 50
  ( backFolder :: Boolean
  , boxes      :: Boxes
  , nodeId     :: Int
  , session    :: Session
51 52 53 54
  )

data FolderStyle = FolderUp | FolderChild

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

76
type FolderViewProps =
77
  ( backFolder    :: Boolean
78
  , boxes         :: Boxes
79 80 81 82
  , folders       :: FTree
  , 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 { backFolder
91
      , boxes
92 93 94 95
      , folders: NTree (LNode {parent_id: parentId, nodeType}) (folders)
      , nodeId
      , reload
      , session
96
      , setPopoverRef } _ = do
97
    linkHandlers <- useLinkHandler
98
    let foldersS = A.sortBy sortFolders folders
99
    let backHome = isBackHome nodeType
100
    let parent = makeParentFolder linkHandlers parentId session backFolder backHome
101
    let children = makeFolderElements foldersS { boxes, nodeId, reload, session, setPopoverRef }
102

Karen Konou's avatar
Karen Konou committed
103
    pure $ H.div {className: "fv folders"} $ parent <> children
104

105
  makeFolderElements foldersS props = makeFolderElementsMap <$> foldersS where
106
    makeFolderElementsMap :: NTree LNode -> R.Element
107
    makeFolderElementsMap (NTree (LNode node) _) = folder { boxes: props.boxes
108 109 110
                                                          , nodeId: node.id
                                                          , nodeType: node.nodeType
                                                          , parentId: props.nodeId
111
                                                          , reload: props.reload
112 113 114 115
                                                          , session: props.session
                                                          , setPopoverRef: props.setPopoverRef
                                                          , style: FolderChild
                                                          , text: node.name } []
116

117 118
  makeParentFolder :: Record Methods -> Maybe Int -> Session -> Boolean -> Boolean -> Array R.Element
  makeParentFolder _ (Just parentId) session _ _ =
119 120
    -- FIXME: The NodeType here should not be hardcoded to FolderPrivate but we currently can't get the actual NodeType
    -- without performing another API call. Also parentId is never being returned by this API even when it clearly exists
121
    [ folderSimple {style: FolderUp, text: "..", nodeId: parentId, nodeType: GT.FolderPrivate, session: session} [] ]
122
  makeParentFolder linkHandlers Nothing _ _ true = [ H.button {className: "btn btn-primary", on: { click: \_ -> linkHandlers.goToRoute Home}} [ H.i { className: "fa fa-folder-open" } []
123 124
                                                                   , H.br {}
                                                                   , H.text ".."] ]
125
  makeParentFolder linkHandlers Nothing _ true _ = [ H.button {className: "btn btn-primary", on: { click: \_ -> linkHandlers.goToPreviousPage unit } }  [ H.i { className: "fa fa-folder-open" } []
126 127
                                                                   , H.br {}
                                                                   , H.text ".."] ]
128
  makeParentFolder _ Nothing _ _ _ = []
129 130 131 132 133


  sortFolders :: FTree -> FTree -> Ordering
  sortFolders a b = compare (fTreeID a) (fTreeID b)

134 135 136 137 138 139
  isBackHome :: GT.NodeType -> Boolean
  isBackHome GT.FolderPrivate = true
  isBackHome GT.FolderPublic = true
  isBackHome GT.FolderShared = true
  isBackHome _ = false

140

141
type FolderSimpleProps =
142 143 144
  (
    style :: FolderStyle
  , text :: String
145
  , nodeType :: GT.NodeType
146
  , nodeId :: Int
147
  , session :: Session
148 149
  )

150 151 152 153 154
folderSimple :: R2.Component FolderSimpleProps
folderSimple = R.createElement folderSimpleCpt

folderSimpleCpt :: R.Component FolderSimpleProps
folderSimpleCpt = here.component "folderSimpleCpt" cpt where
155
  cpt {style, text, nodeId, session, nodeType} _ = do
156
    { goToRoute } <- useLinkHandler
157
    let sid = sessionId session
158 159
    pure $ H.button { className: "btn btn-primary"
               , on: {click: \_ -> goToRoute $ getFolderPath nodeType sid nodeId} }
160 161 162
      [ H.i { className: icon style nodeType } []
      , H.br {}
      , H.text text ]
163

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

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

171
type FolderProps =
172
  ( boxes         :: Boxes
173 174 175
  , parentId      :: Int
  , reload        :: T.Box T2.Reload
  , setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
176 177 178
  | FolderSimpleProps
  )

179 180 181 182
folder :: R2.Component FolderProps
folder = R.createElement folderCpt
folderCpt :: R.Component FolderProps
folderCpt = here.component "folderCpt" cpt where
183 184 185 186 187 188 189 190 191
  cpt props@{ boxes
            , nodeId
            , nodeType
            , parentId
            , reload
            , session
            , setPopoverRef
            , style
            , text } _ = do
192
    let sid = sessionId session
193
    let dispatch a = performAction a { boxes, nodeId, parentId, reload, session, setPopoverRef }
194
    popoverRef <- R.useRef null
195
    { goToRoute } <- useLinkHandler
196 197 198 199

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

200
    pure $
201 202
        H.div {} [
        H.span{style: {position: "absolute"}} [ Popover.popover {
203 204 205 206
            arrow: false
          , open: false
          , onClose: \_ -> pure unit
          , onOpen:  \_ -> pure unit
207
          , ref: popoverRef
208
          } [
209
              popOverIcon
210
              , mNodePopupView (Record.merge props { dispatch }) (onPopoverClose popoverRef)
211
              ]]
212
      , H.button {on: {click: \_ -> goToRoute $ getFolderPath nodeType sid nodeId }, className: "btn btn-primary fv btn" } [
213
          H.i {className: icon style nodeType} []
214
        , H.br {}
215
        , H.text text]]
216 217


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

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

225 226
  onPopoverClose popoverRef _ = Popover.setOpen popoverRef false

227
  popOverIcon = H.span { className: "fv action" } [
228
        H.a { className: "settings fa fa-cog"
229
          , title : "Each node of the Tree can perform some actions.\n"
230
            <> "Click here to execute one of them." } []
231 232
      ]

233
  mNodePopupView props opc = nodePopupView { boxes: props.boxes
234 235
                                           , dispatch: props.dispatch
                                           , id: props.nodeId
236 237 238
                                           , onPopoverClose: opc
                                           , nodeType: props.nodeType
                                           , name: props.text
239 240
                                           , session: props.session
                                           }
241

242 243 244 245 246 247 248 249 250 251
backButton :: R2.Component ()
backButton = R.createElement backButtonCpt
backButtonCpt :: R.Component ()
backButtonCpt = R.hooksComponent "backButton" cpt where
  cpt _ _ = do
    { goToPreviousPage } <- useLinkHandler

    pure $ 
      H.button {
        className: "btn btn-primary"
252
      , on: { click: \_ -> goToPreviousPage unit }
253 254 255
      } [
        H.i { className: "fa fa-arrow-left", title: "Previous view"} []
      ]
256

257 258 259 260 261 262 263
type LoadProps =
  (
    session :: Session,
    nodeId :: Int,
    reload :: T2.Reload
  )

264
loadFolders :: Record LoadProps -> AffRESTError FTree
265
loadFolders {nodeId, session} = get session $ TreeFirstLevel (Just nodeId) ""
266 267

type PerformActionProps =
268
  ( boxes         :: Boxes
269 270 271 272 273
  , nodeId        :: Int
  , parentId      :: Int
  , reload        :: T.Box T2.Reload
  , setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
  , session       :: Session
274 275 276 277 278 279 280 281 282 283 284 285
  )

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
286 287 288 289
  performAction' (UploadFile nodeType fileType mName contents selection) p =
    uploadFile' nodeType fileType mName contents p selection
  performAction' (UploadArbitraryFile mName blob selection) p =
    uploadArbitraryFile' mName blob p selection
290
  performAction' DownloadNode _ = liftEffect $ here.log "[performAction] DownloadNode"
291 292 293
  performAction' (MoveNode {params}) p = moveNode params p
  performAction' (MergeNode {params}) p = mergeNode params p
  performAction' (LinkNode { nodeType, params }) p = linkNode nodeType params p
294
  performAction' NoAction _ = liftEffect $ here.log "[performAction] NoAction"
295
  performAction' ClosePopover p = closePopover p
296
  performAction' _ _ = liftEffect $ here.log "[performAction] unsupported action"
297 298 299 300

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

301
  refreshFolders p@{ boxes: { reloadForest }, reload } = do
302 303
    liftEffect $ T2.reload reload
    liftEffect $ T2.reload reloadForest
304 305
    closePopover p

306
  deleteNode' nt p@{ nodeId: id, parentId: parent_id, session } = do
307
    case nt of
308
      NodePublic FolderPublic  -> void $ deleteNode session id
309
      NodePublic _             -> void $ unpublishNode session (Just parent_id) id
310
      _                        -> void $ deleteNode session id
311 312
    refreshFolders p

313
  doSearch task { boxes: { tasks }, nodeId: id } = liftEffect $ do
314
    GAT.insert id task tasks
315
    here.log2 "[performAction] DoSearch task:" task
316

317 318
  updateNode params { boxes: { errors, tasks }, nodeId: id, session } = do
    eTask <- updateRequest params session id
319
    handleRESTError errors eTask $ \task -> liftEffect $ do
320
      GAT.insert id task tasks
321
      here.log2 "[performAction] UpdateNode task:" task
322

323 324
  shareTeam username { boxes: { errors }, nodeId: id, session } = do
    eTask <- Share.shareReq session id $ Share.ShareTeamParams { username }
325
    handleRESTError errors eTask $ \_task -> pure unit
326

327
  sharePublic params p@{ boxes: { errors }, session } = traverse_ f params where
328
    f (SubTreeOut { in: inId, out }) = do
329
      eTask <- Share.shareReq session inId $ Share.SharePublicParams { node_id: out }
330
      handleRESTError errors eTask $ \_task -> pure unit
331 332
      refreshFolders p

333 334
  addContact params { nodeId: id, session } =
    void $ Contact.contactReq session id params
335

336 337
  uploadFile' nodeType fileType mName contents { boxes: { errors, tasks }, nodeId: id, session } selection = do
    eTask <- uploadFile { contents, fileType, id, nodeType, mName, selection, session }
338
    handleRESTError errors eTask $ \task -> liftEffect $ do
339
      GAT.insert id task tasks
340
      here.log2 "[performAction] UploadFile, uploaded, task:" task
341

342 343
  uploadArbitraryFile' mName blob { boxes: { errors, tasks }, nodeId: id, session } selection = do
    eTask <- uploadArbitraryFile session id { blob, mName } selection
344
    handleRESTError errors eTask $ \task -> liftEffect $ do
345
      GAT.insert id task tasks
346
      here.log2 "[performAction] UploadArbitraryFile, uploaded, task:" task
347

348
  moveNode params p@{ boxes: { errors }, session } = traverse_ f params where
349
    f (SubTreeOut { in: in', out }) = do
350
      eTask <- moveNodeReq session in' out
351
      handleRESTError errors eTask $ \_task -> pure unit
352 353
      refreshFolders p

354
  mergeNode params p@{ boxes: { errors }, session } = traverse_ f params where
355
    f (SubTreeOut { in: in', out }) = do
356
      eTask <- mergeNodeReq session in' out
357
      handleRESTError errors eTask $ \_task -> pure unit
358 359
      refreshFolders p

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

366 367
  renameNode name p@{ boxes: { errors }, nodeId: id, session } = do
    eTask <- rename session id $ RenameValue { text: name }
368
    handleRESTError errors eTask $ \_task -> pure unit
369 370
    refreshFolders p

371 372
  addNode' name nodeType p@{ boxes: { errors }, nodeId: id, session } = do
    eTask <- addNode session id $ AddNodeValue {name, nodeType}
373
    handleRESTError errors eTask $ \_task -> pure unit
374
    refreshFolders p