module Gargantext.Components.Router (router) where import Gargantext.Prelude import DOM.Simple as DOM import Data.Array (filter, length) import Data.Array as A import Data.Foldable (intercalate) import Data.Map as M import Data.Maybe (Maybe(..), fromMaybe, maybe) import Data.UUID (UUID) import Data.UUID as UUID import Effect (Effect) import Gargantext.Components.App.Store (Boxes) import Gargantext.Components.ErrorsView (errorsView) import Gargantext.Components.Forest (forestLayout) import Gargantext.Components.ForgotPassword (forgotPasswordLayout) import Gargantext.Components.Login (login) import Gargantext.Components.Nodes.Annuaire (annuaireLayout) import Gargantext.Components.Nodes.Annuaire.User (userLayout) import Gargantext.Components.Nodes.Annuaire.User.Contact (contactLayout) import Gargantext.Components.Nodes.Corpus (corpusLayout) import Gargantext.Components.Nodes.Corpus.Code (corpusCodeLayout) import Gargantext.Components.Nodes.Corpus.Dashboard (dashboardLayout) import Gargantext.Components.Nodes.Corpus.Document as Document import Gargantext.Components.Nodes.Corpus.Graph as Graph import Gargantext.Components.Nodes.Corpus.Phylo as Phylo import Gargantext.Components.Nodes.File (fileLayout) import Gargantext.Components.Nodes.Frame as Frame import Gargantext.Components.Nodes.Home (homeLayout) import Gargantext.Components.Nodes.Lists as Lists import Gargantext.Components.Nodes.Texts as Texts import Gargantext.Components.Tile (tileBlock) import Gargantext.Components.TopBar as TopBar import Gargantext.Config (defaultFrontends, defaultBackends) import Gargantext.Context.Session as SessionContext import Gargantext.Ends (Backend) import Gargantext.Hooks.Resize (ResizeType(..), useResizeHandler) import Gargantext.Hooks.Session (useSession) import Gargantext.Routes (AppRoute, Tile) import Gargantext.Routes as GR import Gargantext.Sessions (Session, sessionId) import Gargantext.Sessions as Sessions import Gargantext.Types (CorpusId, Handed(..), ListId, NodeID, NodeType(..), SessionId, SidePanelState(..)) import Gargantext.Utils ((?)) import Gargantext.Utils.Reactix (getElementById) import Gargantext.Utils.Reactix as R2 import Reactix as R import Reactix.DOM.HTML as H import Record (get) import Record as Record import Record.Extra as RE import Toestand as T import Type.Proxy (Proxy(..)) here :: R2.Here here = R2.here "Gargantext.Components.Router" type Props = ( boxes :: Boxes ) type SessionProps = ( sessionId :: SessionId | Props ) type SessionNodeProps = ( nodeId :: NodeID | SessionProps ) type Props' = ( backend :: Backend, route' :: AppRoute | Props ) router :: R2.Leaf Props router = R2.leafComponent routerCpt routerCpt :: R.Component Props routerCpt = here.component "router" cpt where cpt { boxes: boxes@{ handed } } _ = do -- States handed' <- R2.useLive' handed -- Computed let handedClassName :: Handed -> String handedClassName = case _ of LeftHanded -> "left-handed" RightHanded -> "right-handed" toggleHandedClass :: Handed -> DOM.Element -> Effect Unit toggleHandedClass new el = do R2.removeClass el [ handedClassName LeftHanded , handedClassName RightHanded ] R2.addClass el [ handedClassName new ] -- Effects R.useLayoutEffect1' handed' do getElementById "app" >>= maybe R.nothing (toggleHandedClass handed') getElementById "portal" >>= maybe R.nothing (toggleHandedClass handed') -- Render pure $ H.div { className: "router" } [ login' boxes , TopBar.topBar { boxes } , errorsView { errors: boxes.errors } [] , H.div { className: "router__inner" } [ forest { boxes } , mainPage { boxes } , sidePanel { boxes } ] ] -------------------------------------------------------------- mainPage :: R2.Leaf Props mainPage = R2.leafComponent mainPageCpt mainPageCpt :: R.Component Props mainPageCpt = here.component "mainPage" cpt where cpt { boxes } _ = do -- States route <- R2.useLive' boxes.route tileAxisXList <- R2.useLive' boxes.tileAxisXList tileAxisYList <- R2.useLive' boxes.tileAxisYList -- Computed let findTile :: UUID -> Record Tile -> Boolean findTile id tile = eq id $ get (Proxy :: Proxy "id") tile deleteTile :: Record Tile -> T.Box (Array (Record Tile)) -> (Unit -> Effect Unit) deleteTile tile listBox = const do list <- T.read listBox newList <- pure $ filter (not $ findTile $ tile.id) list T.write_ newList listBox let hasHorizontalTiles = not $ eq 0 $ length tileAxisXList let hasVerticalTiles = not $ eq 0 $ length tileAxisYList -- Render pure $ H.div { className: "router__body main-page" } [ H.div { className: intercalate " " [ "main-page__main-row" , if (hasVerticalTiles) then "main-page__main-row--with-y-tiles" else "" , if (hasVerticalTiles && not hasHorizontalTiles) then "main-page__main-row--only-y-tiles" else "" ] } [ -- main render route H.div { className: "main-page__main-route" } [ renderRoute { boxes, route } ] , -- optional tile render route [Y Axis ~ aside vertical column] case tileAxisYList of [] -> mempty _ -> H.div { className: intercalate " " [ "main-page__vertical-tiles" , "main-page__vertical-tiles--" <> (show $ length tileAxisYList) ] } $ tileAxisYList <#> \tile -> tileBlock { boxes , tile , key: UUID.toString tile.id , closeCallback: deleteTile tile boxes.tileAxisYList } [ renderRoute { boxes, route: tile.route } ] ] , -- optional tile render route [X Axis ~ bottom horizontal row] case tileAxisXList of [] -> mempty _ -> H.div { className: intercalate " " [ "main-page__horizontal-tiles" , "main-page__horizontal-tiles--" <> (show $ length tileAxisXList) ] } $ tileAxisXList <#> \tile -> tileBlock { boxes , tile , key: UUID.toString tile.id , closeCallback: deleteTile tile boxes.tileAxisXList } [ renderRoute { boxes, route: tile.route } ] ] -------------------------------------------------------------- forest :: R2.Leaf Props forest = R2.leaf forestCpt forestCpt :: R.Memo Props forestCpt = R.memo' $ here.component "forest" cpt where cpt { boxes } _ = do -- States showTree' <- R2.useLive' boxes.showTree -- Hooks resizeHandler <- useResizeHandler -- Effects R.useLayoutEffect1' [] do resizeHandler.add ".router__aside__handle__action" ".router__aside" Vertical pure $ resizeHandler.remove ".router__aside__handle__action" -- Render pure $ H.div { className: "router__aside" -- @XXX: ReactJS lack of "keep-alive" feature workaround solution -- @link https://github.com/facebook/react/issues/12039 , style: { display: showTree' ? "block" $ "none" } } [ H.div { className: "router__aside__inner" } [ forestLayout { boxes , frontends: defaultFrontends } ] , H.div { className: "router__aside__handle" } [ H.div { className: "router__aside__handle__action" } [] ] ] -------------------------------------------------------------- sidePanel :: R2.Leaf Props sidePanel = R2.leafComponent sidePanelCpt sidePanelCpt :: R.Component Props sidePanelCpt = here.component "sidePanel" cpt where cpt props@{ boxes: { session , sidePanelState } } _ = do session' <- T.useLive T.unequal session sidePanelState' <- T.useLive T.unequal sidePanelState case session' of Nothing -> pure $ H.div {} [] Just _ -> case sidePanelState' of Opened -> pure $ R.provideContext SessionContext.context session' [ openedSidePanel props ] _ -> pure $ H.div {} [] -------------------------------------------------------------- type RenderRouteProps = ( route :: AppRoute | Props ) renderRoute :: R2.Leaf RenderRouteProps renderRoute = R2.leaf renderRouteCpt renderRouteCpt :: R.Memo RenderRouteProps renderRouteCpt = R.memo' $ here.component "renderRoute" cpt where cpt { boxes, route } _ = do let sessionNodeProps sId nId = { nodeId: nId , sessionId: sId , boxes } pure $ R.fragment [ case route of GR.Annuaire s n -> annuaire (sessionNodeProps s n) [] GR.ContactPage s a n -> contact (Record.merge { annuaireId: a } $ sessionNodeProps s n) [] GR.Corpus s n -> corpus (sessionNodeProps s n) [] GR.CorpusCode s n -> corpusCode (sessionNodeProps s n) [] GR.CorpusDocument s c l n -> corpusDocument (Record.merge { corpusId: c, listId: l } $ sessionNodeProps s n) [] GR.Dashboard s n -> dashboard (sessionNodeProps s n) [] GR.Document s l n -> document (Record.merge { listId: l } $ sessionNodeProps s n) [] GR.Folder s n -> corpus (sessionNodeProps s n) [] GR.FolderPrivate s n -> corpus (sessionNodeProps s n) [] GR.FolderPublic s n -> corpus (sessionNodeProps s n) [] GR.FolderShared s n -> corpus (sessionNodeProps s n) [] GR.Home -> home { boxes } [] GR.Lists s n -> lists (sessionNodeProps s n) [] GR.Login -> login' boxes GR.PGraphExplorer s g -> graphExplorer (sessionNodeProps s g) [] GR.PhyloExplorer s g -> phyloExplorer (sessionNodeProps s g) [] GR.RouteFile s n -> routeFile (sessionNodeProps s n) [] GR.RouteFrameWrite s n -> routeFrame (Record.merge { nodeType: NodeFrameWrite } $ sessionNodeProps s n) [] GR.RouteFrameCalc s n -> routeFrame (Record.merge { nodeType: NodeFrameCalc } $ sessionNodeProps s n) [] GR.RouteFrameCode s n -> routeFrame (Record.merge { nodeType: NodeFrameNotebook } $ sessionNodeProps s n) [] GR.RouteFrameVisio s n -> routeFrame (Record.merge { nodeType: NodeFrameVisio } $ sessionNodeProps s n) [] GR.Team s n -> team (sessionNodeProps s n) [] GR.NodeTexts s n -> texts (sessionNodeProps s n) [] GR.UserPage s n -> user (sessionNodeProps s n) [] GR.ForgotPassword p -> forgotPassword {boxes, params: p} [] ] -------------------------------------------------------------- type AuthedProps = ( content :: Session -> R.Element | SessionProps ) authed :: R2.Component AuthedProps authed = R.createElement authedCpt authedCpt :: R.Component AuthedProps authedCpt = here.component "authed" cpt where cpt props@{ boxes: { session, sessions } , content , sessionId } _ = do sessions' <- T.useLive T.unequal sessions let session' = Sessions.lookup sessionId sessions' R.useEffect' $ do T.write_ session' session case session' of Nothing -> pure $ home homeProps [] Just s -> pure $ R.provideContext SessionContext.context session' [ content s ] where homeProps = RE.pick props :: Record Props -------------------------------------------------------------- openedSidePanel :: R2.Leaf Props openedSidePanel = R2.leaf openedSidePanelCpt openedSidePanelCpt :: R.Component Props openedSidePanelCpt = here.component "openedSidePanel" cpt where cpt { boxes: { route , sidePanelState } } _ = do session <- useSession route' <- T.useLive T.unequal route let wrapper = H.div { className: "side-panel" } case route' of GR.Lists _s _n -> do pure $ wrapper [ Lists.sidePanel { session , sidePanelState } [] ] GR.NodeTexts _s _n -> pure $ wrapper [ Texts.textsSidePanel {} ] _ -> pure $ wrapper [] -------------------------------------------------------------- annuaire :: R2.Component SessionNodeProps annuaire = R.createElement annuaireCpt annuaireCpt :: R.Component SessionNodeProps annuaireCpt = here.component "annuaire" cpt where cpt props@{ nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> annuaireLayout { frontends: defaultFrontends , nodeId , session } } sessionProps) [] -------------------------------------------------------------- corpus :: R2.Component SessionNodeProps corpus = R.createElement corpusCpt corpusCpt :: R.Component SessionNodeProps corpusCpt = here.component "corpus" cpt where cpt props@{ boxes, nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> corpusLayout { boxes , nodeId , session } } sessionProps) [] -------------------------------------------------------------- corpusCode :: R2.Component SessionNodeProps corpusCode = R.createElement corpusCodeCpt corpusCodeCpt :: R.Component SessionNodeProps corpusCodeCpt = here.component "corpusCode" cpt where cpt props@{ boxes, nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps authedProps = Record.merge { content: \session -> corpusCodeLayout { nodeId , session , boxes } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- type CorpusDocumentProps = ( corpusId :: CorpusId , listId :: ListId | SessionNodeProps ) corpusDocument :: R2.Component CorpusDocumentProps corpusDocument = R.createElement corpusDocumentCpt corpusDocumentCpt :: R.Component CorpusDocumentProps corpusDocumentCpt = here.component "corpusDocument" cpt where cpt props@{ corpusId, listId, nodeId } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = Record.merge { content: \session -> Document.node { mCorpusId: Just corpusId , listId , nodeId , key: show (sessionId session) <> "-" <> show nodeId } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- dashboard :: R2.Component SessionNodeProps dashboard = R.createElement dashboardCpt dashboardCpt :: R.Component SessionNodeProps dashboardCpt = here.component "dashboard" cpt where cpt props@{ boxes, nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> dashboardLayout { boxes, nodeId, session } [] } sessionProps) [] -------------------------------------------------------------- type DocumentProps = ( listId :: ListId | SessionNodeProps ) document :: R2.Component DocumentProps document = R.createElement documentCpt documentCpt :: R.Component DocumentProps documentCpt = here.component "document" cpt where cpt props@{ listId, nodeId } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = Record.merge { content: \session -> Document.node { mCorpusId: Nothing , listId , nodeId , key: show (sessionId session) <> "-" <> show nodeId } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- graphExplorer :: R2.Component SessionNodeProps graphExplorer = R.createElement graphExplorerCpt graphExplorerCpt :: R.Component SessionNodeProps graphExplorerCpt = here.component "graphExplorer" cpt where cpt props@{ nodeId } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = Record.merge { content: \_ -> Graph.node { graphId: nodeId , key: "graphId-" <> show nodeId } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- phyloExplorer :: R2.Component SessionNodeProps phyloExplorer = R.createElement phyloExplorerCpt phyloExplorerCpt :: R.Component SessionNodeProps phyloExplorerCpt = here.component "phylo" cpt where cpt props@{ nodeId } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = Record.merge { content: \_ -> Phylo.node { nodeId } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- home :: R2.Component Props home = R.createElement homeCpt homeCpt :: R.Component Props homeCpt = here.component "home" cpt where cpt { boxes } _ = do pure $ homeLayout { boxes } -------------------------------------------------------------- lists :: R2.Component SessionNodeProps lists = R.createElement listsCpt listsCpt :: R.Component SessionNodeProps listsCpt = here.component "lists" cpt where cpt props@{ boxes , nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> Lists.listsLayout { boxes , nodeId , session , sessionUpdate: \_ -> pure unit } [] } sessionProps) [] -------------------------------------------------------------- login' :: Boxes -> R.Element login' { backend, sessions, showLogin: visible } = login { backend , backends: A.fromFoldable defaultBackends , sessions , visible } -------------------------------------------------------------- routeFile :: R2.Component SessionNodeProps routeFile = R.createElement routeFileCpt routeFileCpt :: R.Component SessionNodeProps routeFileCpt = here.component "routeFile" cpt where cpt props@{ nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> fileLayout { nodeId, session } } sessionProps) [] -------------------------------------------------------------- type RouteFrameProps = ( nodeType :: NodeType | SessionNodeProps ) routeFrame :: R2.Component RouteFrameProps routeFrame = R.createElement routeFrameCpt routeFrameCpt :: R.Component RouteFrameProps routeFrameCpt = here.component "routeFrame" cpt where cpt props@{ nodeId, nodeType } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = Record.merge { content: \session -> Frame.node { nodeId , nodeType , key: show (sessionId session) <> "-" <> show nodeId } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- team :: R2.Component SessionNodeProps team = R.createElement teamCpt teamCpt :: R.Component SessionNodeProps teamCpt = here.component "team" cpt where cpt props@{ boxes, nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> corpusLayout { boxes , nodeId , session } } sessionProps) [] -------------------------------------------------------------- texts :: R2.Component SessionNodeProps texts = R.createElement textsCpt textsCpt :: R.Component SessionNodeProps textsCpt = here.component "texts" cpt where cpt props@{ nodeId } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = Record.merge { content: \_ -> Texts.textsLayout { frontends: defaultFrontends , nodeId } } sessionProps pure $ authed authedProps [] -------------------------------------------------------------- user :: R2.Component SessionNodeProps user = R.createElement userCpt userCpt :: R.Component SessionNodeProps userCpt = here.component "user" cpt where cpt props@{ boxes , nodeId } _ = do let sessionProps = RE.pick props :: Record SessionProps pure $ authed (Record.merge { content: \session -> userLayout { boxes , frontends: defaultFrontends , nodeId , session } [] } sessionProps) [] -------------------------------------------------------------- type ContactProps = ( annuaireId :: NodeID | SessionNodeProps ) contact :: R2.Component ContactProps contact = R.createElement contactCpt contactCpt :: R.Component ContactProps contactCpt = here.component "contact" cpt where cpt props@{ annuaireId , nodeId } _ = do let sessionProps = (RE.pick props :: Record SessionProps) authedProps = { content: \_ -> contactLayout { nodeId , annuaireId , key: "annuaireId-" <> show annuaireId } } `Record.merge` sessionProps pure $ authed authedProps [] -------------------------------------------------------------- type ForgotPasswordProps = ( params :: (M.Map String String) | Props) forgotPassword :: R2.Component ForgotPasswordProps forgotPassword = R.createElement forgotPasswordCpt forgotPasswordCpt :: R.Component ForgotPasswordProps forgotPasswordCpt = here.component "forgotPassword" cpt where cpt { params } _ = do let server = fromMaybe "" $ M.lookup "server" params let uuid = fromMaybe "" $ M.lookup "uuid" params pure $ forgotPasswordLayout { server, uuid } []