Box.purs 16 KB
Newer Older
1
module Gargantext.Components.Forest.Tree.Node.Box where
2

3
import Data.Maybe (Maybe(..))
4
import Data.Tuple (Tuple(..))
5
import Data.Tuple.Nested ((/\))
6
import Effect.Aff (Aff, launchAff)
7 8
import Effect.Class (liftEffect)
import Effect.Uncurried (mkEffectFn1)
9 10 11 12
import Gargantext.Components.Forest.Tree.Node (NodeAction(..), SettingsBox(..), glyphiconNodeAction, settingsBox)
import Gargantext.Components.Forest.Tree.Node.Action (Action(..), DroppedFile(..), FileType(..), ID, Name, UploadFileContents(..))
import Gargantext.Components.Forest.Tree.Node.Action.Add (NodePopup(..), createNodeView)
import Gargantext.Components.Forest.Tree.Node.Action.Rename (renameBox)
13
import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadFileView, fileTypeView)
14
import Gargantext.Components.Forest.Tree.Node.ProgressBar (asyncProgressBar)
15 16
import Gargantext.Components.Search.Types (allLangs)
import Gargantext.Components.Search.SearchBar (searchBar)
17
import Gargantext.Components.Search.SearchField (Search, defaultSearch, isIsTex)
18

19
import Gargantext.Ends (Frontends, url)
20
import Gargantext.Routes (AppRoute)
21
import Gargantext.Routes as Routes
22
import Gargantext.Sessions (Session, sessionId)
23
import Gargantext.Types (AsyncTask, NodePath(..), NodeType(..), fldr)
24
import Gargantext.Utils (glyphicon, glyphiconActive)
25
import Gargantext.Utils.Reactix as R2
26
import Prelude (Unit, bind, const, discard, identity, map, pure, show, void, ($), (<>), (==), (-), (+))
27 28 29
import React.SyntheticEvent as E
import Reactix as R
import Reactix.DOM.HTML as H
30 31
import URI.Extra.QueryPairs as NQP
import URI.Query as Query
32 33 34 35 36 37
import Web.File.FileReader.Aff (readAsText)


-- Main Node
type NodeMainSpanProps =
  ( id            :: ID
38
  , asyncTasks    :: Array AsyncTask
39 40 41 42 43 44 45 46 47 48 49 50 51 52
  , name          :: Name
  , nodeType      :: NodeType
  , mCurrentRoute :: Maybe AppRoute
  )

nodeMainSpan :: (Action -> Aff Unit)
             -> Record NodeMainSpanProps
             -> R.State Boolean
             -> Session
             -> Frontends
             -> R.Element
nodeMainSpan d p folderOpen session frontends = R.createElement el p []
  where
    el = R.hooksComponent "NodeMainSpan" cpt
53
    cpt props@{id, asyncTasks, name, nodeType, mCurrentRoute} _ = do
54 55
      -- only 1 popup at a time is allowed to be opened
      popupOpen   <- R.useState' (Nothing :: Maybe NodePopup)
56
      popupPosition <- R.useState' (Nothing :: Maybe R2.Point)
57 58 59
      droppedFile <- R.useState' (Nothing :: Maybe DroppedFile)
      isDragOver  <- R.useState' false

60
      pure $ H.span (dropProps droppedFile isDragOver) $
61
        [ folderIcon nodeType folderOpen
62 63 64
        , H.a { href: (url frontends (NodePath (sessionId session) nodeType (Just id)))
              , style: {marginLeft: "22px"}
              }
65
          [ nodeText { isSelected: (mCorpusId mCurrentRoute) == (Just id)
66 67 68
                     , name: name' props} ]
        , popOverIcon showBox popupOpen popupPosition
        , mNodePopupView props showBox popupOpen popupPosition
69
        , fileTypeView   d {id, nodeType} droppedFile isDragOver
70
        , H.div {} (map (\t -> asyncProgressBar {corpusId: id, asyncTask: t}) asyncTasks)
71 72
        ]
          where
73
            SettingsBox {show: showBox} = settingsBox nodeType
74

75
    name' {name, nodeType} = if nodeType == NodeUser then show session else name
76

77
    folderIcon nodeType folderOpen'@(open /\ _) =
78
      H.a {onClick: R2.effToggler folderOpen'}
79
      [ H.i {className: fldr nodeType open} [] ]
80

81 82
    popOverIcon false _ _ = H.div {} []
    popOverIcon true (popOver /\ setPopOver) (_ /\ setPopupPosition) =
83 84
      H.a { className: "glyphicon glyphicon-cog"
          , id: "rename-leaf"
85
          , on: { click: toggle popOver }
86 87
          } []
      where
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
        toggle Nothing e = do
          setPopupPosition $ const $ Just $ R2.mousePosition e
          setPopOver $ const $ Just NodePopup
        toggle _ _ = do
          setPopupPosition $ const Nothing
          setPopOver $ const Nothing

    mNodePopupView _ false _ _ = H.div {} []
    mNodePopupView _ _ (Nothing /\ _) _ = H.div {} []
    mNodePopupView _ _ _ (Nothing /\ _) = H.div {} []
    mNodePopupView props@{id, nodeType} true popupOpen (Just position /\ _) =
      nodePopupView d { id
                      , action: Nothing
                      , name: name' props
                      , nodeType
                      , position
                      , session
                      } popupOpen
106 107 108 109 110 111 112 113 114 115

    dropProps droppedFile isDragOver =
      { className: dropClass droppedFile isDragOver
      , on: { drop: dropHandler droppedFile
            , dragOver: onDragOverHandler isDragOver
            , dragLeave: onDragLeave isDragOver } }
      where
        dropClass (Just _ /\ _)  _           = "file-dropped"
        dropClass _              (true /\ _) = "file-dropped"
        dropClass (Nothing /\ _) _           = ""
116
        dropHandler (_ /\ setDroppedFile) e = do
117 118 119
          -- prevent redirection when file is dropped
          E.preventDefault e
          E.stopPropagation e
120
          blob <- R2.dataTransferFileBlob e
121
          void $ launchAff do
122
            contents <- readAsText blob
123 124 125 126 127 128
            liftEffect $ setDroppedFile
                       $ const
                       $ Just
                       $ DroppedFile { contents: (UploadFileContents contents)
                                     , fileType: Just CSV
                                     }
129 130 131 132 133 134 135 136
    onDragOverHandler (_ /\ setIsDragOver) e = do
      -- prevent redirection when file is dropped
      -- https://stackoverflow.com/a/6756680/941471
      E.preventDefault e
      E.stopPropagation e
      setIsDragOver $ const true
    onDragLeave (_ /\ setIsDragOver) _ = setIsDragOver $ const false

137
{-
138
fldr nt open = if open
139 140 141
               then "fa fa-globe" -- <> color nt
               else "fa fa-folder-globe" -- <> color nt
               --else "glyphicon glyphicon-folder-close" <> color nt
142
                 where
143 144
                   color NodeUser     = ""
                   color FolderPublic = ""
145
                   color FolderShared = " text-warning"
146
                   color _            = " text-danger"
147
                   -}
148

149

150 151 152
-- START node text
type NodeTextProps =
  ( isSelected :: Boolean
153 154
  , name :: Name 
  )
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

nodeText :: Record NodeTextProps -> R.Element
nodeText p = R.createElement el p []
  where
    el = R.hooksComponent "NodeText" cpt
    cpt {isSelected: true, name} _ = do
      pure $ H.u {} [H.b {} [H.text ("| " <> name <> " |    ")]]
    cpt {isSelected: false, name} _ = do
      pure $ H.text (name <> "    ")
-- END node text

mCorpusId :: Maybe AppRoute -> Maybe Int
mCorpusId (Just (Routes.Corpus _ id)) = Just id
mCorpusId (Just (Routes.CorpusDocument _ id _ _)) = Just id
mCorpusId _ = Nothing


-- | START Popup View
type NodePopupProps =
  ( id       :: ID
175
  , action   :: Maybe NodeAction
176 177
  , name     :: Name
  , nodeType :: NodeType
178
  , position :: R2.Point
179
  , session  :: Session
180 181
  )

182
iconAStyle :: { color :: String, paddingTop :: String, paddingBottom :: String}
183 184
iconAStyle = { color         : "black"
             , paddingTop    : "6px"
185 186
             , paddingBottom : "6px"
             }
187 188 189 190 191

nodePopupView :: (Action -> Aff Unit)
              -> Record NodePopupProps
              -> R.State (Maybe NodePopup)
              -> R.Element
192
nodePopupView d p mPop@(Just NodePopup /\ setPopupOpen) = R.createElement el p []
193 194
  where
    el = R.hooksComponent "NodePopupView" cpt
195
    cpt {id, action, name, nodeType, position, session} _ = do
196
      renameBoxOpen <- R.useState' false
197
      nodePopupState@(nodePopup /\ setNodePopup) <- R.useState' {id, name, nodeType, action}
198
      search <- R.useState' $ defaultSearch { node_id = Just id }
199
      pure $ H.div (tooltipProps position) $
200
        [ H.div {id: "arrow"} []
201 202
        , H.div { className: "popup-container" }
          [ H.div { className: "panel panel-default" }
203 204
            [ H.div {className: ""}
              [ H.div { className : "col-md-11"}
Alexandre Delanoë's avatar
Alexandre Delanoë committed
205 206
                [ H.h3 { className: fldr nodeType true} [H.text $ show nodeType]
                , H.p {className: "text-primary center"} [H.text name]
207 208 209
                ]
              ]
            , panelHeading renameBoxOpen
210
            , panelBody    nodePopupState d
211
            , panelAction d {id, name, nodeType, action:nodePopup.action, session, search} mPop
212 213 214 215
            ]
          , if nodePopup.action == Just SearchBox then
              H.div {}
                [
216
                  searchIsTexIframe id session search
217 218 219
                ]
            else
              H.div {} []
220 221 222
          ]
        ]
      where
223 224 225 226 227 228
        tooltipProps (R2.Point {x, y}) = {
          className: ""
          , id: "node-popup-tooltip"
          , title: "Node settings"
          , data: { toggle: "tooltip"
                  , placement: "right"},
229
            style: { top: y - 65.0, left: x + 10.0 }
230
          }
231

232
        SettingsBox {edit, doc, buttons} = settingsBox nodeType
233

234 235 236 237 238 239 240
        removeCircleGeneral (Just _) setNodePopup = removeCircle setNodePopup
        removeCircleGeneral Nothing _ = H.div {} []
        removeCircle setNodePopup =
          H.div { className: glyphicon "remove-circle"
                , onClick : setNodePopup $ const {id, name, nodeType, action :Nothing}
                } []

241 242
        panelHeading renameBoxOpen@(open /\ _) =
          H.div {className: "panel-heading"}
243
                [ R2.row
Alexandre Delanoë's avatar
Alexandre Delanoë committed
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
                        [ H.div {className: "col-md-8"}
                                [ renameBox d {id, name, nodeType} renameBoxOpen ]

                        , H.div {className: "flex-end"}
                                [ if edit then editIcon renameBoxOpen else H.div {} []
                                , H.div {className: "col-md-1"}
                                        [ H.a { "type"   : "button"
                                              , className: glyphicon "remove-circle"
                                              , onClick  : mkEffectFn1
                                                         $ \_ -> setPopupOpen $ const Nothing
                                              , title    : "Close"} []
                                        ]
                                 ]
                        ]
                ]
259 260
          where
            editIcon (false /\ setRenameBoxOpen) =
Alexandre Delanoë's avatar
Alexandre Delanoë committed
261
              H.div {className : "col-md-1"}
262
              [ H.a { className: glyphicon "pencil"
Alexandre Delanoë's avatar
Alexandre Delanoë committed
263 264 265 266
                    , id       : "rename1"
                    , title    : "Rename"
                    , onClick  : mkEffectFn1
                               $ \_ -> setRenameBoxOpen $ const true
267 268 269 270 271
                    }
                []
              ]
            editIcon (true /\ _) = H.div {} []

272
        panelBody nodePopupState d' =
273 274 275 276 277
          H.div {className: "panel-body flex-space-between"}
                [ H.div {className: "flex-center"} [buttonClick nodePopupState d' doc]
                , H.div {className: "flex-center"}
                        $ map (buttonClick nodePopupState d') buttons
                ]
278

279
        searchIsTexIframe _id _session search@(search' /\ _) =
280
          if isIsTex search'.datafield then
281
            H.div { className: "istex-search panel panel-default" }
282 283 284 285 286 287 288 289
            [
              H.h3 { className: fldr nodeType true} []
            , componentIsTex search
            ]
          else
            H.div {} []

        componentIsTex (search /\ setSearch) =
290
          H.iframe { src: isTexTermUrl search.term , width: "100%", height: "100%"} []
291 292 293 294 295 296 297 298 299
        isTexUrl = "https://istex.gargantext.org"
        isTexLocalUrl = "http://localhost:8083"
        isTexTermUrl term = isTexUrl <> query
          where
            query = Query.print $ NQP.print identity identity qp

            qp = NQP.QueryPairs [
              Tuple (NQP.keyFromString "query") (Just (NQP.valueFromString term))
              ]
300

301 302 303 304 305 306
nodePopupView _ p _ = R.createElement el p []
  where
    el = R.hooksComponent "CreateNodeView" cpt
    cpt _ _ = pure $ H.div {} []


307 308 309 310 311 312 313 314
buttonClick :: R.State { id        :: ID
                        , name     :: Name
                        , nodeType :: NodeType
                        , action   :: Maybe NodeAction
                        }
            -> (Action -> Aff Unit)
            -> NodeAction
            -> R.Element
315
buttonClick (node@{action} /\ setNodePopup) _ todo = H.div {className: "col-md-1"}
316
            [ H.a { style: iconAStyle
317 318 319 320 321 322 323
                  , className: glyphiconActive (glyphiconNodeAction todo)
                                               (action == (Just todo)   )
                  , id: show todo
                  , title: show todo
                  , onClick : mkEffectFn1
                            $ \_ -> setNodePopup
                            $ const (node { action = action' })
324 325 326
                }
              []
            ]
327 328 329 330
              where
                action' = if action == (Just todo)
                             then Nothing
                             else (Just todo)
331

332

333 334
-- END Popup View

335 336 337 338 339 340 341 342
type NodeProps =
  ( id       :: ID
  , name     :: Name
  , nodeType :: NodeType
  )

type Open = Boolean

343 344 345 346 347 348 349 350 351
type PanelActionProps =
  ( id       :: ID
  , name     :: Name
  , nodeType :: NodeType
  , action   :: Maybe NodeAction
  , session  :: Session
  , search   :: R.State Search
  )

352
panelAction :: (Action -> Aff Unit)
353
            -> Record PanelActionProps
354 355
            -> R.State (Maybe NodePopup)
            -> R.Element
356
panelAction d {id, name, nodeType, action, session, search} p = case action of
Alexandre Delanoë's avatar
Alexandre Delanoë committed
357
    (Just (Documentation NodeUser))      -> R.fragment [H.div {style: {margin: "10px"}} [ infoTitle NodeUser
Alexandre Delanoë's avatar
Alexandre Delanoë committed
358
                                                                 , H.p {} [ H.text "This account is personal"]
Alexandre Delanoë's avatar
Alexandre Delanoë committed
359
                                                                 , H.p {} [ H.text "See the instances terms of uses."]
360 361
                                                                 ]
                                                        ]
362 363 364 365
    (Just (Documentation FolderPrivate)) -> fragmentPT "This folder and its children are private only!"
    (Just (Documentation FolderPublic))  -> fragmentPT "Soon, you will be able to build public folders to share your work with the world!"
    (Just (Documentation FolderShared))  -> fragmentPT "Soon, you will be able to build teams folders to share your work"
    (Just (Documentation x)) -> fragmentPT $ "More information on" <> show nodeType
366

367
    (Just (Link _))                      -> fragmentPT "Soon, you will be able to link the corpus with your Annuaire (and reciprocally)."
368
    (Just Upload)                        -> uploadFileView d {session, id}
369
    (Just Download)                      -> fragmentPT "Soon, you will be able to dowload your file here"
370

371
    (Just SearchBox)         -> R.fragment [ H.p {"style": {"margin" :"10px"}} [ H.text $ "Search and create a private corpus with the search query as corpus name." ]
372
                                           , searchBar {session, langs:allLangs, search}
373
                                           ]
374
    (Just Delete)            -> case nodeType of
Alexandre Delanoë's avatar
Alexandre Delanoë committed
375 376
        NodeUser -> R.fragment [ H.div {style: {margin: "10px"}} [H.text "Yes, we are RGPD compliant! But you can not delete User Node yet (we are still on development). Thanks for your comprehensin."]]
        _        -> R.fragment [ H.div {style: {margin: "10px"}} (map (\t -> H.p {} [H.text t]) ["Are your sure you want to delete it ?", "If yes, click again below."]), reallyDelete d]
377 378
    (Just (Add xs))          -> createNodeView d {id, name, nodeType} p xs
    _                        -> H.div {} []
379
  where
Alexandre Delanoë's avatar
Alexandre Delanoë committed
380
    fragmentPT text = H.div {style: {margin: "10px"}} [H.text text]
381 382


Alexandre Delanoë's avatar
Alexandre Delanoë committed
383
infoTitle :: NodeType -> R.Element
Alexandre Delanoë's avatar
Alexandre Delanoë committed
384
infoTitle nt = H.div {style: {margin: "10px"}} [ H.h3 {} [H.text "Documentation about " ]
Alexandre Delanoë's avatar
Alexandre Delanoë committed
385 386 387
                        , H.h3 {className: fldr nt true} [ H.text $ show nt ]
                        ]

388
reallyDelete :: (Action -> Aff Unit) -> R.Element
389 390 391 392 393 394
reallyDelete d = H.div {className: "panel-footer"}
            [ H.a { type: "button"
                  , className: "btn glyphicon glyphicon-trash"
                  , id: "delete"
                  , title: "Delete"
                  , onClick: mkEffectFn1 $ \_ -> launchAff $ d $ DeleteNode}
395
              [H.text " Yes, delete!"]
396 397
            ]