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

3
import DOM.Simple.Console (log, log2)
4 5
import Data.Array as A
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 12 13 14 15 16 17 18 19 20 21 22 23
import Effect.Class (liftEffect)
import Gargantext.AsyncTasks as GAT
import Gargantext.Components.Forest.Tree.Node.Action (Action(..))
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
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.Hooks.Loader (useLoader)
27
import Gargantext.Prelude (Ordering, Unit, bind, compare, discard, pure, unit, void, ($), (<$>), (<>))
28 29
import Gargantext.Routes (AppRoute(Home), SessionRoute(..), appPath, nodeTypeAppRoute)
import Gargantext.Sessions (Session, get, sessionId)
30
import Gargantext.Types (NodeType(..))
31
import Gargantext.Types as GT
32
import Gargantext.Utils.Popover as Popover
33
import Gargantext.Utils.Reactix as R2
34
import Gargantext.Utils.Toestand as T2
35 36
import Reactix as R
import Reactix.DOM.HTML as H
37 38
import Record as Record
import Toestand as T
39

40
foreign import back :: Effect Unit
41
foreign import link :: String -> Effect Unit
42

43 44 45 46 47 48
here :: R2.Here
here = R2.here "Gargantext.Components.FolderView"

type Props =
  ( nodeId  :: Int
  , session :: Session
49
  , backFolder :: Boolean
50
  , tasks :: T.Box GAT.Storage
51
  , reloadForest :: T.Box T2.Reload
52 53 54 55
  )

data FolderStyle = FolderUp | FolderChild

56 57
folderView :: R2.Leaf Props
folderView props = R.createElement folderViewCpt props []
58

59 60
folderViewCpt :: R.Component Props
folderViewCpt = here.component "folderViewCpt" cpt where
61
  cpt {nodeId, session, backFolder, tasks, reloadForest} _ = do
62 63 64 65
    setPopoverRef <- R.useRef Nothing
    reload <- T.useBox T2.newReload
    reload' <- T.useLive T.unequal reload
    useLoader { nodeId, session, reload: reload'} loadFolders $
66
      \folders -> folderViewMain {folders, nodeId, session, backFolder, tasks, reload, setPopoverRef, reloadForest}
67 68 69 70 71 72

type FolderViewProps = 
  ( 
    nodeId :: Int
  , folders:: FTree
  , session :: Session
73
  , backFolder :: Boolean
74 75
  , tasks :: T.Box GAT.Storage
  , reload :: T.Box T2.Reload
76
  , reloadForest :: T.Box T2.Reload
77
  , setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
78 79
  )

80 81
folderViewMain :: Record FolderViewProps -> R.Element
folderViewMain props = R.createElement folderViewMainCpt props []
82

83 84
folderViewMainCpt :: R.Component FolderViewProps
folderViewMainCpt = here.component "folderViewMainCpt" cpt where
85
  cpt {nodeId, session, backFolder, tasks, setPopoverRef, reload, reloadForest, folders: tree@(NTree (LNode {parent_id: parentId, nodeType}) (folders))} _ = do
86
    let foldersS = A.sortBy sortFolders folders
87
    let backHome = isBackHome nodeType
88
    let parent = makeParentFolder parentId session backFolder backHome
89
    let children = makeFolderElements foldersS {session, setPopoverRef, nodeId, tasks, reload, reloadForest}
90

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

93
  makeFolderElements foldersS props = makeFolderElementsMap <$> foldersS where
94
    makeFolderElementsMap :: NTree LNode -> R.Element
95 96 97 98 99 100 101 102
    makeFolderElementsMap (NTree (LNode node) _) = folder {style: FolderChild
                                                          , text: node.name
                                                          , nodeId: node.id
                                                          , nodeType: node.nodeType
                                                          , session: props.session
                                                          , setPopoverRef: props.setPopoverRef
                                                          , parentId: props.nodeId
                                                          , tasks: props.tasks
103 104
                                                          , reload: props.reload
                                                          , reloadForest: props.reloadForest} []
105 106 107

  makeParentFolder :: Maybe Int -> Session -> Boolean -> Boolean -> Array R.Element
  makeParentFolder (Just parentId) session _ _ =
108 109
    -- 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
110
    [ folderSimple {style: FolderUp, text: "..", nodeId: parentId, nodeType: GT.FolderPrivate, session: session} [] ]
111
  makeParentFolder Nothing _ _ true = [ H.a {className: "btn btn-primary", href: appPath Home} [ H.i { className: "fa fa-folder-open" } []
112 113
                                                                   , H.br {}
                                                                   , H.text ".."] ]
114 115 116 117
  makeParentFolder Nothing _ true _ = [ H.button {className: "btn btn-primary", on: { click: back } }  [ H.i { className: "fa fa-folder-open" } []
                                                                   , H.br {}
                                                                   , H.text ".."] ]
  makeParentFolder Nothing _ _ _ = []
118 119 120 121 122


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

123 124 125 126 127 128
  isBackHome :: GT.NodeType -> Boolean
  isBackHome GT.FolderPrivate = true
  isBackHome GT.FolderPublic = true
  isBackHome GT.FolderShared = true
  isBackHome _ = false

129

130
type FolderSimpleProps = 
131 132 133
  (
    style :: FolderStyle
  , text :: String
134
  , nodeType :: GT.NodeType
135
  , nodeId :: Int
136
  , session :: Session
137 138
  )

139 140 141 142 143
folderSimple :: R2.Component FolderSimpleProps
folderSimple = R.createElement folderSimpleCpt

folderSimpleCpt :: R.Component FolderSimpleProps
folderSimpleCpt = here.component "folderSimpleCpt" cpt where
144 145
  cpt {style, text, nodeId, session, nodeType} _ = do
    let sid = sessionId session
146 147 148 149 150
    pure $ H.a { className: "btn btn-primary"
               , href: "/#/" <> getFolderPath nodeType sid nodeId }
      [ H.i { className: icon style nodeType } []
      , H.br {}
      , H.text text ]
151
  
152
  icon :: FolderStyle -> GT.NodeType -> String
153
  icon FolderUp _ = "fa fa-folder-open"
154
  icon _ nodeType = GT.fldr nodeType false
155

156
  getFolderPath :: GT.NodeType -> GT.SessionId -> Int -> String
157 158 159 160 161
  getFolderPath nodeType sid nodeId = appPath $ fromMaybe Home $ nodeTypeAppRoute nodeType sid nodeId

type FolderProps = 
  (
    setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
162 163 164
  , parentId :: Int
  , tasks :: T.Box GAT.Storage
  , reload :: T.Box T2.Reload
165
  , reloadForest :: T.Box T2.Reload
166 167 168
  | FolderSimpleProps
  )

169 170 171 172 173
folder :: R2.Component FolderProps
folder = R.createElement folderCpt

folderCpt :: R.Component FolderProps
folderCpt = here.component "folderCpt" cpt where
174
  cpt props@{style, text, nodeId, session, nodeType, setPopoverRef, parentId, tasks, reload, reloadForest} _ = do
175
    let sid = sessionId session
176
    let dispatch a = performAction a {setPopoverRef, session, nodeId, parentId, tasks, reload, reloadForest}
177 178 179 180 181
    popoverRef <- R.useRef null

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

182
    pure $ 
183 184
        H.div {} [
        H.span{style: {position: "absolute"}} [ Popover.popover {
185 186 187 188 189 190
            arrow: false
          , open: false
          , onClose: \_ -> pure unit
          , onOpen:  \_ -> pure unit
          , ref: popoverRef 
          } [
191
              popOverIcon
192
              , mNodePopupView (Record.merge props {dispatch}) (onPopoverClose popoverRef)
193 194 195
              ]]
      , H.button {on: {click: link ("/#/" <> getFolderPath nodeType sid nodeId) }, className: "btn btn-primary fv btn" } [ 
          H.i {className: icon style nodeType} []
196
        , H.br {}
197
        , H.text text]]
198 199
    
    
200
  icon :: FolderStyle -> GT.NodeType -> String
201
  icon FolderUp _ = "fa fa-folder-open"
202
  icon _ nodeType = GT.fldr nodeType false
203

204
  getFolderPath :: GT.NodeType -> GT.SessionId -> Int -> String
205 206
  getFolderPath nodeType sid nodeId = appPath $ fromMaybe Home $ nodeTypeAppRoute nodeType sid nodeId

207 208
  onPopoverClose popoverRef _ = Popover.setOpen popoverRef false

209
  popOverIcon = H.span { className: "fv action" } [
210 211
        H.a { className: "settings fa fa-cog" 
          , title : "Each node of the Tree can perform some actions.\n"
212
            <> "Click here to execute one of them." } []
213 214
      ]

215 216 217 218 219 220 221 222
  mNodePopupView props opc = nodePopupView {onPopoverClose: opc
                                           ,nodeType: props.nodeType
                                           , name: props.text
                                           , id: props.nodeId
                                           , dispatch: props.dispatch
                                           , session: props.session
                                           , handed: GT.RightHanded
                                           }
223

224 225 226 227 228 229
backButton :: R.Element
backButton = 
  H.button {
    className: "btn btn-primary"
  , on: {click: back}
  } [
230
    H.i { className: "fa fa-arrow-left", title: "Previous view"} []
231 232
  ]

Karen Konou's avatar
Karen Konou committed
233 234 235 236 237 238
homeButton :: R.Element
homeButton =
  H.a {
    className: "btn btn-primary"
  , href: appPath Home
  } [
239
    H.i { className: "fa fa-home", title: "Back to home"} []
Karen Konou's avatar
Karen Konou committed
240 241
  ]

242 243 244 245 246 247 248 249
type LoadProps =
  (
    session :: Session,
    nodeId :: Int,
    reload :: T2.Reload
  )

loadFolders :: Record LoadProps -> Aff FTree
250
loadFolders {nodeId, session} = get session $ TreeFirstLevel (Just nodeId) ""
251 252 253 254 255 256 257 258 259

type PerformActionProps =
  (
    setPopoverRef :: R.Ref (Maybe (Boolean -> Effect Unit))
  , session :: Session
  , nodeId :: Int
  , parentId :: Int
  , tasks :: T.Box GAT.Storage
  , reload :: T.Box T2.Reload
260
  , reloadForest :: T.Box T2.Reload
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
  )

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
  performAction' (UploadFile nodeType fileType mName blob) p = uploadFile' nodeType fileType mName blob p
  performAction' (UploadArbitraryFile mName blob) p = uploadArbitraryFile' mName blob p
  performAction' DownloadNode _ = liftEffect $ log "[performAction] DownloadNode"
  performAction' (MoveNode {params}) p = moveNode params p
  performAction' (MergeNode {params}) p = mergeNode params p
  performAction' (LinkNode { nodeType, params }) p = linkNode nodeType params p
  performAction' NoAction _ = liftEffect $ log "[performAction] NoAction"
  performAction' ClosePopover p = closePopover p
  performAction' _ _ = liftEffect $ log "[performAction] unsupported action"

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

  refreshFolders p = do 
    liftEffect $ T2.reload p.reload
288
    liftEffect $ T2.reload p.reloadForest
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
    closePopover p

  deleteNode' nt p@{ nodeId: id, parentId: parent_id } = do
    case nt of
      NodePublic FolderPublic  -> void $ deleteNode p.session nt id
      NodePublic _             -> void $ unpublishNode p.session (Just parent_id) id
      _                        -> void $ deleteNode p.session nt id
    refreshFolders p

  doSearch task p@{ tasks, nodeId: id } = liftEffect $ do
    GAT.insert id task tasks
    log2 "[performAction] DoSearch task:" task

  updateNode params p@{ tasks, nodeId: id } = do
    task <- updateRequest params p.session id
    liftEffect $ do
      GAT.insert id task tasks
      log2 "[performAction] UpdateNode task:" task
  
  shareTeam username p@{ nodeId: id} =
    void $ Share.shareReq p.session id $ Share.ShareTeamParams {username}

  sharePublic params p = traverse_ f params where
    f (SubTreeOut { in: inId, out }) = do
      void $ Share.shareReq p.session inId $ Share.SharePublicParams { node_id: out }
      refreshFolders p

  addContact params p@{ nodeId: id } =
    void $ Contact.contactReq p.session id params

  uploadFile' nodeType fileType mName blob p@{ tasks, nodeId: id } = do
    task <- uploadFile p.session nodeType id fileType {mName, blob}
    liftEffect $ do
      GAT.insert id task tasks
      log2 "[performAction] UploadFile, uploaded, task:" task

  uploadArbitraryFile' mName blob p@{ tasks, nodeId: id } = do
    task <- uploadArbitraryFile p.session id { blob, mName }
    liftEffect $ do
      GAT.insert id task tasks
      log2 "[performAction] UploadArbitraryFile, uploaded, task:" task

  moveNode params p@{ session } = traverse_ f params where
    f (SubTreeOut { in: in', out }) = do
      void $ moveNodeReq p.session in' out
      refreshFolders p

  mergeNode params p = traverse_ f params where
    f (SubTreeOut { in: in', out }) = do
      void $ mergeNodeReq p.session in' out
      refreshFolders p

  linkNode nodeType params p = traverse_ f params where
    f (SubTreeOut { in: in', out }) = do
      void $ linkNodeReq p.session nodeType in' out
      refreshFolders p

  renameNode name p@{ nodeId: id } = do
    void $ rename p.session id $ RenameValue { text: name }
    refreshFolders p

  addNode' name nodeType p@{ nodeId: id } = do
    void $ addNode p.session id $ AddNodeValue {name, nodeType}
    refreshFolders p