Commit 7cad1696 authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

Merge branch 'dev' into dev-arxiv

parents 2a29dcc9 9e3893bb
Pipeline #2699 failed with stage
in 0 seconds
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"name": "Gargantext",
"version": "0.0.5.8.4",
"version": "0.0.5.8.5",
"scripts": {
"generate-purs-packages-nix": "./nix/generate-purs-packages.nix",
"generate-psc-packages-nix": "./nix/generate-packages-json.bash",
......
......@@ -15,12 +15,14 @@ type Props =
)
type Options =
( className :: String
( className :: String
, contentClassName :: String
)
options :: Record Options
options =
{ className: ""
{ className : ""
, contentClassName : ""
}
-- | Component simulating a native <fieldset>
......@@ -36,12 +38,21 @@ component = R.hooksComponent componentName cpt where
cpt props@{ titleSlot
} children = do
-- Computed
className <- pure $ intercalate " "
-- provided custom className
[ props.className
-- BEM classNames
, componentName
]
let
className = intercalate " "
-- provided custom className
[ props.className
-- BEM classNames
, componentName
]
contentClassName = intercalate " "
-- provided custom className
[ props.contentClassName
-- BEM classNames
, componentName <> "__content"
]
-- Render
pure $
......@@ -53,6 +64,6 @@ component = R.hooksComponent componentName cpt where
[ titleSlot ]
,
H.div
{ className: componentName <> "__content" }
{ className: contentClassName}
children
]
......@@ -14,7 +14,9 @@ import Gargantext.Components.Bootstrap.Icon(icon) as Exports
import Gargantext.Components.Bootstrap.IconButton(iconButton) as Exports
import Gargantext.Components.Bootstrap.ProgressBar(progressBar) as Exports
import Gargantext.Components.Bootstrap.Spinner(spinner) as Exports
import Gargantext.Components.Bootstrap.Tooltip(tooltip, tooltipBind, tooltipContainer) as Exports
import Gargantext.Components.Bootstrap.Tabs(tabs) as Exports
import Gargantext.Components.Bootstrap.Tooltip(tooltip, TooltipBindingProps, tooltipBind, tooltipBind', tooltipContainer) as Exports
import Gargantext.Components.Bootstrap.Wad(wad, wad', wad_) as Exports
import Gargantext.Components.Bootstrap.Shortcut(
div', div_
......
module Gargantext.Components.Bootstrap.Tooltip
( tooltip
, tooltipBind
, TooltipBindingProps, tooltipBind, tooltipBind'
, tooltipContainer
) where
......@@ -90,6 +90,13 @@ tooltipBind =
, "data-tip": true
}
-- | Derived empty state
tooltipBind' :: Record TooltipBindingProps
tooltipBind' =
{ "data-for": ""
, "data-tip": false
}
-------------------------------------------------------------
type ContainerProps =
......
module Gargantext.Components.Bootstrap.Wad
( wad
, wad'
, wad_
) where
import Gargantext.Prelude
import Data.Foldable (intercalate)
import Reactix as R
import Reactix.DOM.HTML as H
componentName :: String
componentName = "b-wad"
-- | Structural Component for a simple Element only serving the purpose to add
-- | some classes in it
-- |
-- | Hence the name: Wad (noun): a small mass, lump, or ball of anything ;
-- | a roll of something
wad :: Array String -> Array R.Element -> R.Element
wad classes children = R.createDOMElement "div" cls children
where
cls = { className: intercalate " " $
[ componentName
] <> classes
}
-- | Shorthand for using <wad> Component without writing its text node
wad' :: Array String -> String -> R.Element
wad' classes text = R.createDOMElement "div" cls chd
where
cls = { className: intercalate " " $
[ componentName
] <> classes
}
chd = [ H.text text ]
-- | Shorthand for using <wad> Component without any child
wad_ :: Array String -> R.Element
wad_ classes = R.createDOMElement "div" cls []
where
cls = { className: intercalate " " $
[ componentName
] <> classes
}
module Gargantext.Components.Bootstrap.Tabs(tabs) where
import Gargantext.Prelude
import Data.Foldable (intercalate)
import Effect (Effect)
import Gargantext.Utils ((?))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
type Props a =
( value :: a
, callback :: a -> Effect Unit
, list :: Array a
| Options
)
type Options =
( className :: String
)
options :: Record Options
options =
{ className : ""
}
-- | Structural molecular component to the Bootstrap <nav-tabs> + <nav-item>
-- | simplifying a lot of the available UI/UX possibilites (type, disabled
-- | tabs, etc)
-- |
-- | https://getbootstrap.com/docs/4.6/components/navs/#tabs
tabs :: forall r a.
Show a
=> Eq a
=> R2.OptLeaf Options (Props a) r
tabs = R2.optLeaf component options
componentName :: String
componentName = "b-tabs"
component :: forall a.
Show a
=> Eq a
=> R.Component (Props a)
component = R.hooksComponent componentName cpt where
cpt props@{ list, value, callback } _ = do
-- Computed
let
className = intercalate " "
-- provided custom className
[ props.className
-- BEM classNames
, componentName
-- Bootstrap specific classNames
, "nav nav-tabs"
]
-- Render
pure $
H.ul
{ className } $
flip map list \item ->
H.li
{ className: "nav-item"
, on: { click: \_ -> callback item }
}
[
H.a
{ className: intercalate " "
[ "nav-link"
, value == item ? "active" $ ""
]
}
[
H.text $ show item
]
]
......@@ -16,7 +16,6 @@ import Gargantext.Ends (Frontends)
import Gargantext.Hooks.LinkHandler (useLinkHandler)
import Gargantext.Routes (AppRoute(..))
import Gargantext.Sessions (Session(..), unSessions)
import Gargantext.Utils (nbsp)
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
......@@ -132,9 +131,10 @@ plusCpt = here.component "plus" cpt where
, variant: ButtonVariant Light
}
[
B.icon { name: "universal-access" }
B.icon
{ name: "universal-access" }
,
H.text $ nbsp 1
B.wad_ [ "d-inline-block", "w-1" ]
,
H.text $ "Log in/out"
]
......@@ -150,7 +150,7 @@ forestLayout :: R2.Leaf Props
forestLayout = R2.leaf forestLayoutCpt
forestLayoutCpt :: R.Memo Props
forestLayoutCpt = R.memo' $ here.component "forestLayout" cpt where
cpt p children = pure $
cpt p _ = pure $
H.div
{ className: "forest-layout" }
......
exports.nodeUserRegexp = /(@{1}.*).gargantext.org$/;
......@@ -31,15 +31,15 @@ type NodeActionsGraphProps =
nodeActionsGraph :: R2.Component NodeActionsGraphProps
nodeActionsGraph = R.createElement nodeActionsGraphCpt
nodeActionsGraphCpt :: R.Component NodeActionsGraphProps
nodeActionsGraphCpt = here.component "nodeActionsGraph" cpt
where
cpt { id, graphVersions, session, refresh } _ = do
pure $ H.div { className: "node-actions" } [
if graphVersions.gv_graph == Just graphVersions.gv_repo then
H.div {} []
else
graphUpdateButton { id, session, refresh }
]
nodeActionsGraphCpt = here.component "nodeActionsGraph" cpt where
cpt { id, graphVersions, session, refresh } _ =
let sameVersions = (graphVersions.gv_graph == Just graphVersions.gv_repo)
in pure $
R2.if' (not sameVersions) $
graphUpdateButton { id, session, refresh }
type GraphUpdateButtonProps =
( id :: GT.ID
......@@ -99,12 +99,9 @@ type NodeActionsNodeListProps =
nodeActionsNodeList :: Record NodeActionsNodeListProps -> R.Element
nodeActionsNodeList p = R.createElement nodeActionsNodeListCpt p []
nodeActionsNodeListCpt :: R.Component NodeActionsNodeListProps
nodeActionsNodeListCpt = here.component "nodeActionsNodeList" cpt
where
cpt props _ = do
pure $ H.div { className: "node-actions" } [
nodeListUpdateButton props
]
nodeActionsNodeListCpt = here.component "nodeActionsNodeList" cpt where
cpt props _ = pure $ nodeListUpdateButton props
type NodeListUpdateButtonProps =
( listId :: GT.ListId
......
......@@ -132,9 +132,12 @@ graphCpt = here.component "graph" cpt where
Sigma.stopForceAtlas2 sig
case mCamera of
Nothing -> pure unit
Just (GET.Camera { ratio, x, y }) -> do
Sigma.updateCamera sig { ratio, x, y }
-- Default camera: slightly de-zoom the graph to avoid
-- nodes sticking to the container borders
Nothing ->
Sigma.updateCamera sig { ratio: 1.1, x: 0.0, y: 0.0 }
-- Reload Sigma on Theme changes
_ <- flip T.listen boxes.theme \{ old, new } ->
......
module Gargantext.Components.GraphExplorer.Button
( Props, centerButton, simpleButton, cameraButton ) where
module Gargantext.Components.GraphExplorer.Buttons
( Props
, centerButton
, simpleButton
, cameraButton
, edgesToggleButton
, louvainToggleButton
, pauseForceAtlasButton
, resetForceAtlasButton
, multiSelectEnabledButton
) where
import Prelude
import DOM.Simple.Console (log2)
import Data.DateTime as DDT
import Data.DateTime.Instant as DDI
import Data.Either (Either(..))
import Data.Enum (fromEnum)
import Data.Maybe (Maybe(..))
import Data.DateTime as DDT
import Data.DateTime.Instant as DDI
import Data.String as DS
import DOM.Simple.Console (log2)
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Effect.Now as EN
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), ComponentStatus(..), Variant(..))
import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadArbitraryData)
import Gargantext.Components.Forest.Tree.Node.Action.Upload.Types (FileFormat(..))
import Gargantext.Components.GraphExplorer.API (cloneGraph)
import Gargantext.Components.GraphExplorer.Resources as Graph
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Components.GraphExplorer.Utils as GEU
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
import Gargantext.Sessions (Session)
import Gargantext.Utils ((?))
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Button"
......@@ -36,9 +50,12 @@ type Props = (
, text :: String
)
-- @WIP
simpleButton :: Record Props -> R.Element
simpleButton props = R.createElement simpleButtonCpt props []
------------------------------------------------------
simpleButtonCpt :: R.Component Props
simpleButtonCpt = here.component "simpleButton" cpt
where
......@@ -48,14 +65,16 @@ simpleButtonCpt = here.component "simpleButton" cpt
} [ R2.small {} [ H.text text ] ]
centerButton :: R.Ref Sigmax.Sigma -> R.Element
centerButton sigmaRef = simpleButton {
onClick: \_ -> do
centerButton sigmaRef = B.button
{ variant: OutlinedButtonVariant Secondary
, callback: \_ -> do
let sigma = R.readRef sigmaRef
Sigmax.dependOnSigma sigma "[centerButton] sigma: Nothing" $ \s ->
Sigma.goToAllCameras s {x: 0.0, y: 0.0, ratio: 1.0, angle: 0.0}
, text: "Center"
}
[ H.text "Center" ]
------------------------------------------------------
type CameraButtonProps =
( id :: Int
......@@ -71,8 +90,10 @@ cameraButton { id
, hyperdataGraph: GET.HyperdataGraph { graph: GET.GraphData hyperdataGraph }
, session
, sigmaRef
, reloadForest } = simpleButton {
onClick: \_ -> do
, reloadForest } = B.button
{ variant: OutlinedButtonVariant Secondary
, callback: \_ -> do
let sigma = R.readRef sigmaRef
Sigmax.dependOnSigma sigma "[cameraButton] sigma: Nothing" $ \s -> do
screen <- Sigma.takeScreenshot s
......@@ -105,5 +126,171 @@ cameraButton { id
Left err -> liftEffect $ log2 "[cameraButton] RESTError" err
Right _ret -> do
liftEffect $ T2.reload reloadForest
, text: "Screenshot"
}
[ H.text "Screenshot" ]
------------------------------------------------------
type EdgesButtonProps =
( state :: T.Box SigmaxTypes.ShowEdgesState
, stateAtlas :: T.Box SigmaxTypes.ForceAtlasState
)
edgesToggleButton :: R2.Leaf EdgesButtonProps
edgesToggleButton = R2.leaf edgesToggleButtonCpt
edgesToggleButtonCpt :: R.Component EdgesButtonProps
edgesToggleButtonCpt = here.component "edgesToggleButton" cpt
where
cpt { state, stateAtlas } _ = do
-- States
state' <- R2.useLive' state
stateAtlas' <- R2.useLive' stateAtlas
-- Computed
let
cst SigmaxTypes.InitialRunning = Disabled
cst SigmaxTypes.Running = Disabled
cst _ = Enabled
-- Render
pure $
B.button
{ variant: state' == SigmaxTypes.EShow ?
ButtonVariant Secondary $
OutlinedButtonVariant Secondary
, status: cst stateAtlas'
-- TODO: Move this to Graph.purs to the R.useEffect handler which renders nodes/edges
, callback: \_ -> T.modify_ SigmaxTypes.toggleShowEdgesState state
}
[ H.text "Edges" ]
------------------------------------------------------
type LouvainToggleButtonProps =
( state :: T.Box Boolean
)
louvainToggleButton :: R2.Leaf LouvainToggleButtonProps
louvainToggleButton = R2.leaf louvainToggleButtonCpt
louvainToggleButtonCpt :: R.Component LouvainToggleButtonProps
louvainToggleButtonCpt = here.component "louvainToggleButton" cpt
where
cpt { state } _ = do
state' <- R2.useLive' state
pure $
B.button
{ variant: state' ?
ButtonVariant Secondary $
OutlinedButtonVariant Secondary
, callback: \_ -> T.modify_ (not) state
}
[ H.text "Louvain" ]
--------------------------------------------------------------
type ForceAtlasProps =
( state :: T.Box SigmaxTypes.ForceAtlasState
)
pauseForceAtlasButton :: R2.Leaf ForceAtlasProps
pauseForceAtlasButton = R2.leaf pauseForceAtlasButtonCpt
pauseForceAtlasButtonCpt :: R.Component ForceAtlasProps
pauseForceAtlasButtonCpt = here.component "forceAtlasToggleButton" cpt
where
cpt { state } _ = do
-- States
state' <- R2.useLive' state
-- Computed
let
cls SigmaxTypes.InitialRunning = "on-running-animation active"
cls SigmaxTypes.Running = "on-running-animation active"
cls _ = ""
vrt SigmaxTypes.InitialRunning = ButtonVariant Secondary
vrt SigmaxTypes.Running = ButtonVariant Secondary
vrt _ = OutlinedButtonVariant Secondary
icn SigmaxTypes.InitialRunning = "pause"
icn SigmaxTypes.InitialStopped = "play"
icn SigmaxTypes.Running = "pause"
icn SigmaxTypes.Paused = "play"
icn SigmaxTypes.Killed = "play"
-- Render
pure $
B.button
{ variant: vrt state'
, className: cls state'
, callback: \_ -> T.modify_ SigmaxTypes.toggleForceAtlasState state
}
[
B.icon
{ name: icn state'}
]
--------------------------------------------------------
type ResetForceAtlasProps =
( forceAtlasState :: T.Box SigmaxTypes.ForceAtlasState
, sigmaRef :: R.Ref Sigmax.Sigma
)
resetForceAtlasButton :: R2.Leaf ResetForceAtlasProps
resetForceAtlasButton = R2.leaf resetForceAtlasButtonCpt
resetForceAtlasButtonCpt :: R.Component ResetForceAtlasProps
resetForceAtlasButtonCpt = here.component "resetForceAtlasToggleButton" cpt
where
cpt { forceAtlasState, sigmaRef } _ = do
pure $ H.button { className: "btn btn-outline-secondary"
, on: { click: onClick forceAtlasState sigmaRef }
} [ R2.small {} [ H.text "Reset Force Atlas" ] ]
onClick forceAtlasState sigmaRef _ = do
-- TODO Sigma.killForceAtlas2 sigma
-- startForceAtlas2 sigma
Sigmax.dependOnSigma (R.readRef sigmaRef) "[resetForceAtlasButton] no sigma" $ \sigma -> do
Sigma.killForceAtlas2 sigma
Sigma.refreshForceAtlas sigma Graph.forceAtlas2Settings
T.write_ SigmaxTypes.Killed forceAtlasState
------------------------------------------------------------------
type MultiSelectEnabledButtonProps =
( state :: T.Box Boolean
)
multiSelectEnabledButton :: R2.Leaf MultiSelectEnabledButtonProps
multiSelectEnabledButton = R2.leaf multiSelectEnabledButtonCpt
multiSelectEnabledButtonCpt :: R.Component MultiSelectEnabledButtonProps
multiSelectEnabledButtonCpt = here.component "multiSelectEnabledButton" cpt
where
cpt { state } _ = do
state' <- R2.useLive' state
pure $
H.div
{ className: "btn-group"
, role: "group"
}
[
B.button
{ variant: state' ?
OutlinedButtonVariant Secondary $
ButtonVariant Secondary
, callback: \_ -> T.write_ false state
}
[ H.text "Single" ]
,
B.button
{ variant: state' ?
ButtonVariant Secondary $
OutlinedButtonVariant Secondary
, callback: \_ -> T.write_ true state
}
[ H.text "Multiple" ]
]
module Gargantext.Components.GraphExplorer.Legend
( Props, legend, legendCpt
( Props, legend
) where
import Prelude hiding (map)
......@@ -7,31 +7,39 @@ import Prelude hiding (map)
import Data.Sequence (Seq)
import Data.Traversable (foldMap)
import Reactix as R
import Reactix.DOM.HTML as RH
import Reactix.DOM.HTML as H
import Gargantext.Components.GraphExplorer.Types (Legend(..), intColor)
import Gargantext.Utils.Reactix as R2
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Legend"
type Props = ( items :: Seq Legend )
legend :: Record Props -> R.Element
legend props = R.createElement legendCpt props []
legend :: R2.Leaf Props
legend = R2.leaf legendCpt
legendCpt :: R.Component Props
legendCpt = here.component "legend" cpt
where
cpt {items} _ = pure $ RH.div {} [foldMap entry items]
entry :: Legend -> R.Element
entry (Legend {id_, label}) =
RH.p {}
[ RH.span { style: { width : 10
, height: 10
, backgroundColor: intColor id_
, display: "inline-block"
}
} []
, RH.text $ " " <> label
]
legendCpt = here.component "legend" cpt where
cpt { items } _ = pure $
H.ul
{ className: "graph-legend" }
[
flip foldMap items \(Legend { id_, label }) ->
H.li
{ className: "graph-legend__item" }
[
H.span
{ className: "graph-legend__code"
, style: { backgroundColor: intColor id_ }
}
[]
,
H.span
{ className: "graph-legend__caption" }
[ H.text label ]
]
]
......@@ -18,37 +18,45 @@ import Gargantext.Utils.Reactix as R2
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.RangeControl"
type Props = (
caption :: String
type Props =
( caption :: String
, sliderProps :: Record RS.Props
)
rangeControl :: R2.Component Props
rangeControl = R.createElement rangeControlCpt
rangeControl :: R2.Leaf Props
rangeControl = R2.leaf rangeControlCpt
rangeControlCpt :: R.Component Props
rangeControlCpt = here.component "rangeButton" cpt
where
cpt {caption, sliderProps} _ = do
pure $
H.span {className: "range text-center"}
[ H.label {} [ R2.small {} [ H.text caption ] ]
, RS.rangeSlider sliderProps
]
type EdgeConfluenceControlProps = (
range :: Range.NumberRange
cpt {caption, sliderProps} _ = pure $
H.span
{ className: "range-control" }
[
H.label
{ className: "range-control__label" }
[ H.text caption ]
,
RS.rangeSlider sliderProps
]
----------------------------------------
type EdgeConfluenceControlProps =
( range :: Range.NumberRange
, state :: T.Box Range.NumberRange
)
edgeConfluenceControl :: R2.Component EdgeConfluenceControlProps
edgeConfluenceControl = R.createElement edgeConfluenceControlCpt
edgeConfluenceControl :: R2.Leaf EdgeConfluenceControlProps
edgeConfluenceControl = R2.leaf edgeConfluenceControlCpt
edgeConfluenceControlCpt :: R.Component EdgeConfluenceControlProps
edgeConfluenceControlCpt = here.component "edgeConfluenceControl" cpt
where
cpt { range: Range.Closed { min, max }
, state } _ = do
, state
} _ = do
state' <- T.useLive T.unequal state
pure $ rangeControl {
......@@ -62,21 +70,24 @@ edgeConfluenceControlCpt = here.component "edgeConfluenceControl" cpt
, height: 5.0
, onChange: \rng -> T.write_ rng state
}
} []
}
type EdgeWeightControlProps = (
range :: Range.NumberRange
--------------------------------------
type EdgeWeightControlProps =
( range :: Range.NumberRange
, state :: T.Box Range.NumberRange
)
edgeWeightControl :: R2.Component EdgeWeightControlProps
edgeWeightControl = R.createElement edgeWeightControlCpt
edgeWeightControl :: R2.Leaf EdgeWeightControlProps
edgeWeightControl = R2.leaf edgeWeightControlCpt
edgeWeightControlCpt :: R.Component EdgeWeightControlProps
edgeWeightControlCpt = here.component "edgeWeightControl" cpt
where
cpt { range: Range.Closed { min, max }
, state } _ = do
, state
} _ = do
state' <- T.useLive T.unequal state
pure $ rangeControl {
......@@ -90,21 +101,24 @@ edgeWeightControlCpt = here.component "edgeWeightControl" cpt
, height: 5.0
, onChange: \rng -> T.write_ rng state
}
} []
}
--------------------------------------
type NodeSideControlProps = (
range :: Range.NumberRange
type NodeSideControlProps =
( range :: Range.NumberRange
, state :: T.Box Range.NumberRange
)
nodeSizeControl :: R2.Component NodeSideControlProps
nodeSizeControl = R.createElement nodeSizeControlCpt
nodeSizeControl :: R2.Leaf NodeSideControlProps
nodeSizeControl = R2.leaf nodeSizeControlCpt
nodeSizeControlCpt :: R.Component NodeSideControlProps
nodeSizeControlCpt = here.component "nodeSizeControl" cpt
where
cpt { range: Range.Closed { min, max }
, state } _ = do
, state
} _ = do
state' <- T.useLive T.unequal state
pure $ rangeControl {
......@@ -118,4 +132,4 @@ nodeSizeControlCpt = here.component "nodeSizeControl" cpt
, height: 5.0
, onChange: \rng -> T.write_ rng state
}
} []
}
This diff is collapsed.
......@@ -4,10 +4,11 @@ module Gargantext.Components.GraphExplorer.Search
import Prelude
import DOM.Simple.Console (log2)
import Data.Foldable (foldl)
import Data.Foldable (foldl, intercalate)
import Data.Sequence as Seq
import Data.Set as Set
import Effect (Effect)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.InputWithAutocomplete (inputWithAutocomplete)
import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Utils (queryMatchesLabel)
......@@ -23,6 +24,7 @@ type Props = (
graph :: SigmaxT.SGraph
, multiSelectEnabled :: T.Box Boolean
, selectedNodeIds :: T.Box SigmaxT.NodeIds
, className :: String
)
-- | Whether a node matches a search string
......@@ -37,28 +39,43 @@ searchNodes :: String -> Seq.Seq (Record SigmaxT.Node) -> Seq.Seq (Record Sigmax
searchNodes "" _ = Seq.empty
searchNodes s nodes = Seq.filter (nodeMatchesSearch s) nodes
nodeSearchControl :: R2.Component Props
nodeSearchControl = R.createElement nodeSearchControlCpt
nodeSearchControl :: R2.Leaf Props
nodeSearchControl = R2.leaf nodeSearchControlCpt
nodeSearchControlCpt :: R.Component Props
nodeSearchControlCpt = here.component "nodeSearchControl" cpt
where
cpt { graph, multiSelectEnabled, selectedNodeIds } _ = do
cpt props@{ graph, multiSelectEnabled, selectedNodeIds } _ = do
search <- T.useBox ""
search' <- T.useLive T.unequal search
multiSelectEnabled' <- T.useLive T.unequal multiSelectEnabled
let doSearch s = triggerSearch graph s multiSelectEnabled' selectedNodeIds
pure $ R.fragment
[ inputWithAutocomplete { autocompleteSearch: autocompleteSearch graph
, classes: "mx-2"
, onAutocompleteClick: doSearch
, onEnterPress: doSearch
, state: search } []
, H.div { className: "btn input-group-addon"
, on: { click: \_ -> doSearch search' }
}
[ H.span { className: "fa fa-search" } [] ]
pure $
H.form
{ className: intercalate " "
[ "graph-node-search"
, props.className
]
}
[
inputWithAutocomplete
{ autocompleteSearch: autocompleteSearch graph
, onAutocompleteClick: doSearch
, onEnterPress: doSearch
, classes: ""
, state: search
}
,
B.button
{ callback: \_ -> doSearch search'
, type: "submit"
, className: "graph-node-search__submit"
}
[
B.icon { name: "search"}
]
]
autocompleteSearch :: SigmaxT.SGraph -> String -> Array String
......
module Gargantext.Components.GraphExplorer.Sidebar.Types where
import Data.Maybe (Maybe(..), maybe)
import Data.Set as Set
import Reactix as R
import Toestand as T
import Gargantext.Prelude
import Data.Maybe (Maybe(..), maybe)
import Data.Set as Set
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Types as GT
import Reactix as R
import Toestand as T
type SidePanel =
(
......@@ -19,6 +19,7 @@ type SidePanel =
, selectedNodeIds :: SigmaxT.NodeIds
, showControls :: Boolean
, sideTab :: GET.SideTab
, showSidebar :: GT.SidePanelState
)
initialSidePanel :: Maybe (Record SidePanel)
......@@ -32,7 +33,9 @@ focusedSidePanel :: T.Box (Maybe (Record SidePanel))
, removedNodeIds :: T.Box SigmaxT.NodeIds
, selectedNodeIds :: T.Box SigmaxT.NodeIds
, showControls :: T.Box Boolean
, sideTab :: T.Box GET.SideTab }
, sideTab :: T.Box GET.SideTab
, showSidebar :: T.Box GT.SidePanelState
}
focusedSidePanel sidePanel = do
mGraph <- T.useFocused
(maybe Nothing _.mGraph)
......@@ -55,6 +58,9 @@ focusedSidePanel sidePanel = do
sideTab <- T.useFocused
(maybe GET.SideTabLegend _.sideTab)
(\val -> maybe Nothing (\sp -> Just $ sp { sideTab = val })) sidePanel
showSidebar <- T.useFocused
(maybe GT.InitialClosed _.showSidebar)
(\val -> maybe Nothing (\sp -> Just $ sp { showSidebar = val })) sidePanel
pure $ {
mGraph
......@@ -64,4 +70,5 @@ focusedSidePanel sidePanel = do
, selectedNodeIds
, showControls
, sideTab
, showSidebar
}
......@@ -34,20 +34,35 @@ sizeButtonCpt :: R.Component Props
sizeButtonCpt = here.component "sizeButton" cpt where
cpt { state, caption, min, max, onChange } _ = do
defaultValue <- T.useLive T.unequal state
pure $ H.span { className: "range-simple" }
[ H.label {} [ R2.small {} [ H.text caption ] ]
, H.input { type: "range"
, className: "form-control"
, min: show min
, max: show max
, defaultValue
, on: { input: onChange } }]
pure $
H.span
{ className: "range-simple" }
[
H.label
{ className: "range-simple__label" }
[ H.text caption ]
,
H.span
{ className: "range-simple__field" }
[
H.input
{ type: "range"
, min: show min
, max: show max
, defaultValue
, on: { input: onChange }
, className: "range-simple__input"
}
]
]
labelSizeButton :: R.Ref Sigmax.Sigma -> T.Box Number -> R.Element
labelSizeButton sigmaRef state =
sizeButton {
state
, caption: "Label Size"
, caption: "Label size"
, min: 1.0
, max: 30.0
, onChange: \e -> do
......@@ -67,7 +82,7 @@ mouseSelectorSizeButton :: R.Ref Sigmax.Sigma -> T.Box Number -> R.Element
mouseSelectorSizeButton sigmaRef state =
sizeButton {
state
, caption: "Selector Size"
, caption: "Selector size"
, min: 1.0
, max: 50.0
, onChange: \e -> do
......
......@@ -3,27 +3,18 @@ module Gargantext.Components.GraphExplorer.ToggleButton
, toggleButton
, toggleButtonCpt
, controlsToggleButton
, edgesToggleButton
, louvainToggleButton
, multiSelectEnabledButton
, sidebarToggleButton
, pauseForceAtlasButton
, resetForceAtlasButton
) where
import Prelude
import Effect (Effect)
import Gargantext.Components.Graph as Graph
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
import Gargantext.Types as GT
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
-- @WIP: used?
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.ToggleButton"
......@@ -56,6 +47,8 @@ toggleButtonCpt = here.component "toggleButton" cpt
text on _off true = on
text _on off false = off
----------------------------------------------------------------
type ControlsToggleButtonProps = (
state :: T.Box Boolean
)
......@@ -73,147 +66,3 @@ controlsToggleButtonCpt = here.component "controlsToggleButton" cpt
, onClick: \_ -> T.modify_ not state
, style: "light"
} []
type EdgesButtonProps = (
state :: T.Box SigmaxTypes.ShowEdgesState
)
edgesToggleButton :: R2.Component EdgesButtonProps
edgesToggleButton = R.createElement edgesToggleButtonCpt
edgesToggleButtonCpt :: R.Component EdgesButtonProps
edgesToggleButtonCpt = here.component "edgesToggleButton" cpt
where
cpt { state } _ = do
state' <- T.useLive T.unequal state
pure $ H.button { className: "btn btn-outline-secondary " <> cls state'
, on: { click: onClick state }
} [ R2.small {} [ H.text (text state') ] ]
text s = if SigmaxTypes.edgeStateHidden s then "Show edges" else "Hide edges"
cls SigmaxTypes.EShow = ""
cls _ = "active"
-- TODO: Move this to Graph.purs to the R.useEffect handler which renders nodes/edges
onClick state _ = T.modify_ SigmaxTypes.toggleShowEdgesState state
type LouvainToggleButtonProps = (
state :: T.Box Boolean
)
louvainToggleButton :: R2.Component LouvainToggleButtonProps
louvainToggleButton = R.createElement louvainToggleButtonCpt
louvainToggleButtonCpt :: R.Component LouvainToggleButtonProps
louvainToggleButtonCpt = here.component "louvainToggleButton" cpt
where
cpt { state } _ = do
pure $ toggleButton {
state: state
, onMessage: "Louvain off"
, offMessage: "Louvain on"
, onClick: \_ -> T.modify_ not state
, style: "secondary"
} []
type MultiSelectEnabledButtonProps = (
state :: T.Box Boolean
)
multiSelectEnabledButton :: R2.Component MultiSelectEnabledButtonProps
multiSelectEnabledButton = R.createElement multiSelectEnabledButtonCpt
multiSelectEnabledButtonCpt :: R.Component MultiSelectEnabledButtonProps
multiSelectEnabledButtonCpt = here.component "lmultiSelectEnabledButton" cpt
where
cpt { state } _ = do
pure $ toggleButton {
state: state
, onMessage: "Single-node"
, offMessage: "Multi-node"
, onClick: \_ -> T.modify_ not state
, style : "primary"
} []
type ForceAtlasProps = (
state :: T.Box SigmaxTypes.ForceAtlasState
)
pauseForceAtlasButton :: R2.Component ForceAtlasProps
pauseForceAtlasButton = R.createElement pauseForceAtlasButtonCpt
pauseForceAtlasButtonCpt :: R.Component ForceAtlasProps
pauseForceAtlasButtonCpt = here.component "forceAtlasToggleButton" cpt
where
cpt { state } _ = do
state' <- T.useLive T.unequal state
pure $ H.button { className: "btn btn-outline-secondary " <> cls state'
, on: { click: onClick state }
} [ R2.small {} [ H.text (text state') ] ]
cls SigmaxTypes.InitialRunning = "active"
cls SigmaxTypes.Running = "active"
cls _ = ""
text SigmaxTypes.InitialRunning = "Pause"
text SigmaxTypes.InitialStopped = "Start"
text SigmaxTypes.Running = "Pause"
text SigmaxTypes.Paused = "Start"
text SigmaxTypes.Killed = "Start"
onClick state _ = T.modify_ SigmaxTypes.toggleForceAtlasState state
type ResetForceAtlasProps = (
forceAtlasState :: T.Box SigmaxTypes.ForceAtlasState
, sigmaRef :: R.Ref Sigmax.Sigma
)
resetForceAtlasButton :: R2.Component ResetForceAtlasProps
resetForceAtlasButton = R.createElement resetForceAtlasButtonCpt
resetForceAtlasButtonCpt :: R.Component ResetForceAtlasProps
resetForceAtlasButtonCpt = here.component "resetForceAtlasToggleButton" cpt
where
cpt { forceAtlasState, sigmaRef } _ = do
pure $ H.button { className: "btn btn-outline-secondary"
, on: { click: onClick forceAtlasState sigmaRef }
} [ R2.small {} [ H.text "Reset Force Atlas" ] ]
onClick forceAtlasState sigmaRef _ = do
-- TODO Sigma.killForceAtlas2 sigma
-- startForceAtlas2 sigma
Sigmax.dependOnSigma (R.readRef sigmaRef) "[resetForceAtlasButton] no sigma" $ \sigma -> do
Sigma.killForceAtlas2 sigma
Sigma.refreshForceAtlas sigma Graph.forceAtlas2Settings
T.write_ SigmaxTypes.Killed forceAtlasState
type SidebarToggleButtonProps = (
state :: T.Box GT.SidePanelState
)
sidebarToggleButton :: R2.Component SidebarToggleButtonProps
sidebarToggleButton = R.createElement sidebarToggleButtonCpt
sidebarToggleButtonCpt :: R.Component SidebarToggleButtonProps
sidebarToggleButtonCpt = here.component "sidebarToggleButton" cpt
where
cpt { state } _ = do
state' <- T.useLive T.unequal state
pure $ H.div { className: "btn btn-outline-light " <> cls state'
, on: { click: onClick state }
} [ R2.small {} [ H.text (text onMessage offMessage state') ] ]
cls GT.Opened = "active"
cls _ = ""
onMessage = "Hide Sidebar"
offMessage = "Show Sidebar"
text on _off GT.Opened = on
text _on off GT.InitialClosed = off
text _on off GT.Closed = off
onClick state = \_ ->
T.modify_ GT.toggleSidePanelState state
-- case s of
-- GET.InitialClosed -> GET.Opened GET.SideTabLegend
-- GET.Closed -> GET.Opened GET.SideTabLegend
-- (GET.Opened _) -> GET.Closed) state
module Gargantext.Components.GraphExplorer.TopBar where
module Gargantext.Components.GraphExplorer.TopBar (topBar) where
import Data.Maybe (Maybe(..))
import Reactix as R
import Reactix.DOM.HTML as RH
import Toestand as T
import Gargantext.Prelude hiding (max, min)
import Gargantext.Prelude hiding (max,min)
import Gargantext.Components.App.Data (Boxes)
import Data.Maybe (Maybe)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), Variant(..))
import Gargantext.Components.GraphExplorer.Search (nodeSearchControl)
import Gargantext.Components.GraphExplorer.Sidebar.Types as GEST
import Gargantext.Components.GraphExplorer.ToggleButton as Toggle
import Gargantext.Types as GT
import Gargantext.Utils ((?))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
type Props =
( sidePanelGraph :: T.Box (Maybe (Record GEST.SidePanel))
)
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.TopBar"
type TopBar =
(
boxes :: Boxes
)
topBar :: R2.Leaf Props
topBar = R2.leaf component
component :: R.Component Props
component = here.component "topBar" cpt where
cpt { sidePanelGraph } _ = do
-- States
{ mGraph
, multiSelectEnabled
, selectedNodeIds
, showControls
, showSidebar
} <- GEST.focusedSidePanel sidePanelGraph
mGraph' <- R2.useLive' mGraph
showControls' <- R2.useLive' showControls
showSidebar' <- R2.useLive' showSidebar
-- Render
pure $
H.div
{ className: "graph-topbar" }
[
-- Toolbar toggle
B.button
{ className: "graph-topbar__toolbar"
, callback: \_ -> T.modify_ (not) showControls
, variant: showControls' ?
ButtonVariant Light $
OutlinedButtonVariant Light
}
[
H.text $ showControls' ? "Hide toolbar" $ "Show toolbar"
]
,
-- Sidebar toggle
B.button
{ className: "graph-topbar__sidebar"
, callback: \_ -> T.modify_ GT.toggleSidePanelState showSidebar
, variant: showSidebar' == GT.Opened ?
ButtonVariant Light $
OutlinedButtonVariant Light
}
[
H.text $ showSidebar' == GT.Opened ?
"Hide sidebar" $
"Show sidebar"
]
,
-- Search
R2.fromMaybe_ mGraph' \graph ->
topBar :: R2.Leaf TopBar
topBar = R2.leafComponent topBarCpt
topBarCpt :: R.Component TopBar
topBarCpt = here.component "topBar" cpt where
cpt { boxes: { sidePanelGraph
, sidePanelState } } _ = do
{ mGraph, multiSelectEnabled, selectedNodeIds, showControls } <- GEST.focusedSidePanel sidePanelGraph
mGraph' <- T.useLive T.unequal mGraph
let search = case mGraph' of
Just graph -> nodeSearchControl { graph
, multiSelectEnabled
, selectedNodeIds } []
Nothing -> RH.div {} []
pure $ RH.form { className: "graph-topbar d-flex" }
[ Toggle.controlsToggleButton { state: showControls } []
, Toggle.sidebarToggleButton { state: sidePanelState } []
, search
nodeSearchControl
{ graph
, multiSelectEnabled
, selectedNodeIds
, className: "graph-topbar__search"
}
]
......@@ -26,7 +26,8 @@ type UserInfo
, ui_cwTouchPhone :: Maybe String
, ui_cwTouchMail :: Maybe String }
type UserInfoM
= { ui_id :: NotNull Int
= { token :: NotNull String
, ui_id :: NotNull Int
, ui_username :: String
, ui_email :: String
, ui_title :: String
......
......@@ -29,8 +29,8 @@ type Props =
, state :: T.Box String
)
inputWithAutocomplete :: R2.Component Props
inputWithAutocomplete = R.createElement inputWithAutocompleteCpt
inputWithAutocomplete :: R2.Leaf Props
inputWithAutocomplete = R2.leaf inputWithAutocompleteCpt
inputWithAutocompleteCpt :: R.Component Props
inputWithAutocompleteCpt = here.component "inputWithAutocomplete" cpt
where
......
......@@ -7,9 +7,6 @@ module Gargantext.Components.Nodes.Annuaire.User.Contact
, saveUserInfo
) where
import Gargantext.Components.GraphQL.User (UserInfo, _ui_cwCity, _ui_cwCountry, _ui_cwFirstName, _ui_cwLabTeamDeptsFirst, _ui_cwLastName, _ui_cwOffice, _ui_cwOrganizationFirst, _ui_cwRole, _ui_cwTouchMail, _ui_cwTouchPhone)
import Gargantext.Prelude (Unit, bind, discard, pure, show, ($), (<$>), (<>))
import Data.Either (Either(..))
import Data.Lens as L
import Data.Maybe (Maybe(..), fromMaybe)
......@@ -19,6 +16,7 @@ import Effect.Class (liftEffect)
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.GraphQL (getClient)
import Gargantext.Components.GraphQL.Endpoints (getUserInfo)
import Gargantext.Components.GraphQL.User (UserInfo, _ui_cwCity, _ui_cwCountry, _ui_cwFirstName, _ui_cwLabTeamDeptsFirst, _ui_cwLastName, _ui_cwOffice, _ui_cwOrganizationFirst, _ui_cwRole, _ui_cwTouchMail, _ui_cwTouchPhone)
import Gargantext.Components.InputWithEnter (inputWithEnter)
import Gargantext.Components.Nodes.Annuaire.User.Contacts.Tabs as Tabs
import Gargantext.Components.Nodes.Annuaire.User.Contacts.Types (ContactData', HyperdataContact(..))
......@@ -26,8 +24,9 @@ import Gargantext.Components.Nodes.Lists.Types as LT
import Gargantext.Config.REST (AffRESTError, logRESTError)
import Gargantext.Ends (Frontends)
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Prelude (Unit, bind, discard, pure, show, ($), (<$>), (<>))
import Gargantext.Routes as Routes
import Gargantext.Sessions (Session, get, put, sessionId)
import Gargantext.Sessions (Session(..), get, put, sessionId)
import Gargantext.Types (NodeType(..))
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2
......@@ -196,11 +195,13 @@ saveContactHyperdata session id = put session (Routes.NodeAPI Node (Just id) "")
saveUserInfo :: Session -> Int -> UserInfo -> AffRESTError Int
saveUserInfo session id ui = do
let token = getToken session
client <- liftEffect $ getClient session
res <- mutation
client
"update user_info"
{ update_user_info: onlyArgs { ui_id: id
{ update_user_info: onlyArgs { token: token
, ui_id: id
, ui_cwFirstName: ga ui.ui_cwFirstName
, ui_cwLastName: ga ui.ui_cwLastName
, ui_cwOrganization: ui.ui_cwOrganization
......@@ -215,6 +216,7 @@ saveUserInfo session id ui = do
where
ga Nothing = ArgL IgnoreArg
ga (Just val) = ArgR val
getToken (Session { token }) = token
type AnnuaireLayoutProps = ( annuaireId :: Int, session :: Session | ReloadProps )
......
......@@ -5,19 +5,20 @@ module Gargantext.Components.Nodes.Corpus.Phylo
import Gargantext.Prelude
import DOM.Simple (document, querySelector)
import Data.Maybe (Maybe(..))
import FFI.Simple ((..), (.=))
import Data.Maybe (Maybe(..), isJust)
import Data.Tuple.Nested ((/\))
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.PhyloExplorer.API (get)
import Gargantext.Components.PhyloExplorer.Layout (layout)
import Gargantext.Components.PhyloExplorer.Types (PhyloDataSet)
import Gargantext.Config.REST (logRESTError)
import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Hooks.Loader (useLoaderEffect)
import Gargantext.Sessions (Session)
import Gargantext.Types (NodeID)
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
type MainProps =
( nodeId :: NodeID
......@@ -35,60 +36,93 @@ phyloLayoutCpt :: R.Component MainProps
phyloLayoutCpt = here.component "main" cpt where
cpt { nodeId, session } _ = do
-- | States
-- |
state' /\ state <- R2.useBox' Nothing
-- | Computed
-- |
let
errorHandler = logRESTError here "[phylo]"
handler (dataset :: PhyloDataSet) =
content
handler (phyloDataSet :: PhyloDataSet) =
layout
{ nodeId
, dataset
, phyloDataSet
}
useLoader
-- | Hooks
-- |
useLoaderEffect
{ errorHandler
, loader: get session
, path: nodeId
, render: handler
, state
}
-- @XXX: Runtime odd behavior
-- cannot use the `useEffect` + its cleanup function within the
-- same `Effect`, otherwise the below cleanup example will be
-- execute at mount
--------------------------------------------------------
-- @XXX: inopinent <div> (see Gargantext.Components.Router) (@TODO?)
R.useEffectOnce' do
mEl <- querySelector document ".main-page__main-route .container"
type ContentProps =
( nodeId :: NodeID
, dataset :: PhyloDataSet
)
case mEl of
Nothing -> R.nothing
Just el -> R2.addClass el [ "d-none" ]
content :: R2.Leaf ContentProps
content = R2.leaf contentCpt
R.useEffectOnce do
pure do
mEl <- querySelector document ".main-page__main-route .container"
contentCpt :: R.Component ContentProps
contentCpt = here.component "content" cpt where
cpt { nodeId, dataset } _ = do
-- Hooks
case mEl of
Nothing -> R.nothing
Just el -> R2.removeClass el [ "d-none" ]
-- @XXX: reset "main-page__main-route" wrapper margin
-- see Gargantext.Components.Router) (@TODO?)
R.useEffectOnce' do
mEl <- querySelector document ".main-page__main-route"
useFirstEffect' do
-- @XXX: inopinent <div> (see Gargantext.Components.Router) (@TODO?)
mEl <- querySelector document ".main-page__main-route .container"
case mEl of
Nothing -> pure unit
Just el -> do
style <- pure $ (el .. "style")
pure $ (style .= "display") "none"
-- @XXX: reset "main-page__main-route" wrapper margin
-- see Gargantext.Components.Router) (@TODO?)
mEl' <- querySelector document ".main-page__main-route"
case mEl' of
Nothing -> pure unit
Just el -> do
style <- pure $ (el .. "style")
pure $ (style .= "padding") "initial"
-- Render
Nothing -> R.nothing
Just el -> R2.addClass el [ "p-0" ]
R.useEffectOnce do
pure do
mEl <- querySelector document ".main-page__main-route"
case mEl of
Nothing -> R.nothing
Just el -> R2.removeClass el [ "p-0" ]
-- | Render
-- |
pure $
layout
{ nodeId
, phyloDataSet: dataset
B.cloak
{ isDisplayed: isJust state'
, idlingPhaseDuration: Just 150
, cloakSlot:
-- mimicking `PhyloExplorer.layout` preloading template
H.div
{ className: "phylo" }
[
H.div
{ className: "phylo__spinner-wrapper" }
[
B.spinner
{ className: "phylo__spinner" }
]
]
, defaultSlot:
R2.fromMaybe_ state' handler
}
module Gargantext.Components.Nodes.Corpus.Graph
( graphLayout
) where
import Gargantext.Prelude
import DOM.Simple (document, querySelector)
import Data.Maybe (Maybe(..), isJust)
import Data.Set as Set
import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\))
import Gargantext.Components.App.Data (Boxes)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.GraphExplorer.Layout (convert, layout)
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Config.REST (AffRESTError, logRESTError)
import Gargantext.Hooks.Loader (useLoaderEffect)
import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Routes (SessionRoute(NodeAPI))
import Gargantext.Sessions (Session, get)
import Gargantext.Types as Types
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
type Props =
( key :: String
, session :: Session
, boxes :: Boxes
, graphId :: GET.GraphId
)
here :: R2.Here
here = R2.here "Gargantext.Components.Nodes.Corpus.Graph"
graphLayout :: R2.Leaf Props
graphLayout = R2.leaf graphLayoutCpt
graphLayoutCpt :: R.Component Props
graphLayoutCpt = here.component "explorerLayout" cpt where
cpt props@{ boxes: { graphVersion }, graphId, session } _ = do
-- | States
-- |
graphVersion' <- T.useLive T.unequal graphVersion
state' /\ state <- R2.useBox' Nothing
-- | Hooks
-- |
useLoaderEffect
{ errorHandler
, loader: getNodes session graphVersion'
, path: graphId
, state
}
-- @XXX: Runtime odd behavior
-- cannot use the `useEffect` + its cleanup function within the
-- same `Effect`, otherwise the below cleanup example will be
-- execute at mount
-- @XXX: inopinent <div> (see Gargantext.Components.Router) (@TODO?)
R.useEffectOnce' do
mEl <- querySelector document ".main-page__main-route .container"
case mEl of
Nothing -> R.nothing
Just el -> R2.addClass el [ "d-none" ]
R.useEffectOnce do
pure do
mEl <- querySelector document ".main-page__main-route .container"
case mEl of
Nothing -> R.nothing
Just el -> R2.removeClass el [ "d-none" ]
-- @XXX: reset "main-page__main-route" wrapper margin
-- see Gargantext.Components.Router) (@TODO?)
R.useEffectOnce' do
mEl <- querySelector document ".main-page__main-route"
case mEl of
Nothing -> R.nothing
Just el -> R2.addClass el [ "p-0" ]
R.useEffectOnce do
pure do
mEl <- querySelector document ".main-page__main-route"
case mEl of
Nothing -> R.nothing
Just el -> R2.removeClass el [ "p-0" ]
-- | Render
-- |
pure $
B.cloak
{ isDisplayed: isJust state'
, idlingPhaseDuration: Just 150
, cloakSlot:
H.div
{ className: "graph-loader" }
[
B.spinner
{ className: "graph-loader__spinner" }
]
, defaultSlot:
R2.fromMaybe_ state' handler
}
where
errorHandler = logRESTError here "[explorerLayout]"
handler loaded@(GET.HyperdataGraph { graph: hyperdataGraph }) =
content { graph
, hyperdataGraph: loaded
, mMetaData'
, session
, boxes: props.boxes
, graphId
}
where
Tuple mMetaData' graph = convert hyperdataGraph
--------------------------------------------------------
type ContentProps =
( mMetaData' :: Maybe GET.MetaData
, graph :: SigmaxT.SGraph
, hyperdataGraph :: GET.HyperdataGraph
, session :: Session
, boxes :: Boxes
, graphId :: GET.GraphId
)
content :: R2.Leaf ContentProps
content = R2.leaf contentCpt
contentCpt :: R.Component ContentProps
contentCpt = here.component "content" cpt where
cpt props@{ boxes, mMetaData', graph } _ = do
-- Hooks
R.useEffectOnce' $
-- Hydrate Boxes
flip T.write_ boxes.sidePanelGraph $ Just
{ mGraph: Just graph
, mMetaData: mMetaData'
, multiSelectEnabled: false
, removedNodeIds: Set.empty
, selectedNodeIds: Set.empty
, showControls: false
, sideTab: GET.SideTabLegend
, showSidebar: Types.InitialClosed
}
-- Render
pure $
layout
props
--------------------------------------------------------------
getNodes :: Session -> T2.Reload -> GET.GraphId -> AffRESTError GET.HyperdataGraph
getNodes session graphVersion graphId =
get session $ NodeAPI Types.Graph
(Just graphId)
("?version=" <> (show graphVersion))
......@@ -21,7 +21,7 @@ import Gargantext.Components.PhyloExplorer.TopBar (topBar)
import Gargantext.Components.PhyloExplorer.Types (DisplayView(..), PhyloDataSet(..), ExtractedTerm, ExtractedCount, Source, Term, sortSources)
import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Hooks.UpdateEffect (useUpdateEffect1')
import Gargantext.Types (NodeID)
import Gargantext.Types (NodeID, SidePanelState(..))
import Gargantext.Utils (getter, (?))
import Gargantext.Utils.Reactix as R2
import Graphics.D3.Base (d3)
......@@ -46,6 +46,7 @@ layoutCpt = here.component "layout" cpt where
cpt { phyloDataSet: (PhyloDataSet o)
, nodeId
} _ = do
-- States
---------
......@@ -82,7 +83,7 @@ layoutCpt = here.component "layout" cpt where
R2.useBox' false
sideBarDisplayed /\ sideBarDisplayedBox <-
R2.useBox' false
R2.useBox' InitialClosed
extractedTerms /\ extractedTermsBox <-
R2.useBox' (mempty :: Array ExtractedTerm)
......@@ -256,7 +257,7 @@ layoutCpt = here.component "layout" cpt where
{ className: "phylo__sidebar"
-- @XXX: ReactJS lack of "keep-alive" feature workaround solution
-- @link https://github.com/facebook/react/issues/12039
, style: { display: sideBarDisplayed ? "block" $ "none" }
, style: { display: sideBarDisplayed == Opened? "block" $ "none" }
}
[
sideBar
......
......@@ -86,7 +86,7 @@ component = R.hooksComponent componentName cpt where
B.caveat
{ className: "phylo-selection-tab__nil" }
[
H.text "No selection has been made"
H.text "Select term, branch or source to get their informations"
]
,
-- Selected source
......@@ -212,8 +212,7 @@ component = R.hooksComponent componentName cpt where
{ className: "phylo-selection-tab__separator" }
[
B.icon
{ name: "angle-down"
}
{ name: "angle-down" }
]
,
-- No extracted result
......@@ -291,6 +290,7 @@ component = R.hooksComponent componentName cpt where
]
,
R2.if' (truncateResults) $
B.button
{ variant: ButtonVariant Light
, callback: \_ -> T.modify_ not showMoreBox
......
......@@ -4,15 +4,14 @@ module Gargantext.Components.PhyloExplorer.SideBar
import Gargantext.Prelude
import Data.Foldable (intercalate)
import Data.Maybe (Maybe)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.PhyloExplorer.DetailsTab (detailsTab)
import Gargantext.Components.PhyloExplorer.SelectionTab (selectionTab)
import Gargantext.Components.PhyloExplorer.Types (ExtractedCount, ExtractedTerm, TabView(..))
import Gargantext.Types (NodeID)
import Gargantext.Utils ((?))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
......@@ -48,84 +47,45 @@ component = R.hooksComponent componentName cpt where
-- States
tabView /\ tabViewBox <- R2.useBox' DetailsTab
-- Computed
let
tabList = [ DetailsTab, SelectionTab ]
-- Render
pure $
H.div
{ className: "phylo-sidebar" }
[
-- Teasers
H.div
{ className: "phylo-sidebar__top-teaser" }
[]
,
-- Menu
H.ul
{ className: intercalate " "
[ "nav nav-tabs"
, "phylo-sidebar__menu"
]
B.tabs
{ value: tabView
, list: tabList
, callback: flip T.write_ tabViewBox
}
[
H.li
{ className: "nav-item"
, on: { click: \_ -> T.write_ DetailsTab tabViewBox }
}
[
H.a
{ className: intercalate " "
[ "nav-link"
, tabView == DetailsTab ? "active" $ ""
]
,
-- Content
case tabView of
DetailsTab ->
detailsTab
{ key: (show props.nodeId) <> "-details"
, docCount: props.docCount
, foundationCount: props.foundationCount
, periodCount: props.periodCount
, termCount: props.termCount
, groupCount: props.groupCount
, branchCount: props.branchCount
}
[
H.text "Details"
]
]
,
H.li
{ className: "nav-item"
, on: { click: \_ -> T.write_ SelectionTab tabViewBox }
}
[
H.a
{ className: intercalate " "
[ "nav-link"
, tabView == SelectionTab ? "active" $ ""
]
SelectionTab ->
selectionTab
{ key: (show props.nodeId) <> "-selection"
, extractedTerms: props.extractedTerms
, extractedCount: props.extractedCount
, selectedTerm: props.selectedTerm
, selectedBranch: props.selectedBranch
, selectedSource: props.selectedSource
, selectTermCallback: props.selectTermCallback
}
[
H.text "Selection"
]
]
]
,
-- Details tab
R2.if' (tabView == DetailsTab) $
detailsTab
{ key: (show props.nodeId) <> "-details"
, docCount: props.docCount
, foundationCount: props.foundationCount
, periodCount: props.periodCount
, termCount: props.termCount
, groupCount: props.groupCount
, branchCount: props.branchCount
}
,
-- Selection tab
R2.if' (tabView == SelectionTab) $
selectionTab
{ key: (show props.nodeId) <> "-selection"
, extractedTerms: props.extractedTerms
, extractedCount: props.extractedCount
, selectedTerm: props.selectedTerm
, selectedBranch: props.selectedBranch
, selectedSource: props.selectedSource
, selectTermCallback: props.selectTermCallback
}
,
-- Teaser
H.div
{ className: "phylo-sidebar__bottom-teaser" }
[]
]
......@@ -9,6 +9,7 @@ import Effect (Effect)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), ComponentStatus(..), Variant(..))
import Gargantext.Components.PhyloExplorer.Types (Term(..), Source(..))
import Gargantext.Types (SidePanelState(..), toggleSidePanelState)
import Gargantext.Utils ((?))
import Gargantext.Utils.Reactix as R2
import Reactix (nothing)
......@@ -27,7 +28,7 @@ type Props =
, resultCallback :: Maybe Term -> Effect Unit
, toolBar :: T.Box (Boolean)
, sideBar :: T.Box (Boolean)
, sideBar :: T.Box (SidePanelState)
)
here :: R2.Here
......@@ -71,12 +72,12 @@ component = here.component "main" cpt where
-- Sidebar toggle
B.button
{ className: "phylo-topbar__sidebar"
, callback: \_ -> T.modify_ (not) sideBar
, variant: sideBar' ?
, callback: \_ -> T.modify_ (toggleSidePanelState) sideBar
, variant: sideBar' == Opened ?
ButtonVariant Light $
OutlinedButtonVariant Light
}
[ H.text $ sideBar' ? "Hide sidebar" $ "Show sidebar" ]
[ H.text $ sideBar' == Opened ? "Hide sidebar" $ "Show sidebar" ]
,
-- Source
H.div
......
......@@ -469,6 +469,9 @@ data TabView
derive instance Generic TabView _
derive instance Eq TabView
instance Show TabView where
show DetailsTab = "Legend"
show SelectionTab = "Data"
-----------------------------------------------------------
......
......@@ -158,14 +158,14 @@ renderScale :: R.Ref (Nullable DOM.Element) -> Record Props -> Range.NumberRange
renderScale ref {width,height} (Range.Closed {min, max}) =
H.div { ref, className, width, height, aria } []
where
className = "scale"
className = "range-slider__scale"
aria = { label: "Scale running from " <> show min <> " to " <> show max }
renderScaleSel :: R.Ref (Nullable DOM.Element) -> Record Props -> Range.NumberRange -> R.Element
renderScaleSel ref props (Range.Closed {min, max}) =
H.div { ref, className, style} []
where
className = "scale-sel"
className = "range-slider__scale-sel"
style = {left: computeLeft, width: computeWidth}
percOffsetMin = Range.normalise props.bounds min
percOffsetMax = Range.normalise props.bounds max
......@@ -176,7 +176,7 @@ renderScaleSel ref props (Range.Closed {min, max}) =
renderKnob :: Knob -> R.Ref (Nullable DOM.Element) -> Range.NumberRange -> Bounds -> T.Box (Maybe Knob) -> Int -> R.Element
renderKnob knob ref (Range.Closed value) bounds set precision =
H.div { ref, tabIndex, className, aria, on: { mouseDown: onMouseDown }, style } [
H.div { className: "button" }
H.div { className: "range-slider__placeholder" }
[
H.text $ text $ toFixed precision val
]
......@@ -185,7 +185,7 @@ renderKnob knob ref (Range.Closed value) bounds set precision =
text (Just num) = num
text Nothing = "error"
tabIndex = 0
className = "knob"
className = "range-slider__knob"
aria = { label: labelPrefix knob <> "value: " <> show val }
labelPrefix MinKnob = "Minimum "
labelPrefix MaxKnob = "Maximum "
......@@ -220,4 +220,3 @@ roundRange :: Epsilon -> Bounds -> Range.NumberRange -> Range.NumberRange
roundRange epsilon bounds (Range.Closed initial) = Range.Closed { min, max }
where min = round epsilon bounds initial.min
max = round epsilon bounds initial.max
This diff is collapsed.
......@@ -9,6 +9,7 @@ import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), Variant(..))
import Gargantext.Components.Lang (langSwitcher, allFeLangs)
import Gargantext.Components.Themes (themeSwitcher, allThemes)
import Gargantext.Types (Handed(..))
import Gargantext.Utils ((?))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
......@@ -64,7 +65,9 @@ topBarCpt = here.component "topBar" cpt
]
,
B.button
{ variant: ButtonVariant Light
{ variant: showTree' ?
ButtonVariant Light $
OutlinedButtonVariant Light
, callback: const $ T.modify_ (not) showTree
, className: "main-topbar__tree-switcher"
}
......
......@@ -10,6 +10,8 @@ import Reactix (nothing, thenNothing)
import Reactix as R
-- | Hook triggered on first mount event only
-- |
-- | /!\ @TODO cleanup function not working
useFirstMount :: R.Hooks (Boolean)
useFirstMount = do
firstMount <- R.useRef true
......
module Gargantext.Utils.Popover where
import Gargantext.Prelude
import DOM.Simple as DOM
import Data.Maybe (maybe)
import Data.Nullable (Nullable, toMaybe)
import DOM.Simple as DOM
import Effect (Effect)
import Effect.Uncurried (EffectFn2, runEffectFn2)
import Reactix as R
import Gargantext.Prelude
import Record as Record
type PopoverRef = R.Ref (Nullable DOM.Element)
......@@ -22,8 +23,11 @@ type Props =
foreign import popoverCpt :: R.Component Props
-- | https://github.com/vaheqelyan/react-awesome-popover
popover :: Record Props -> Array R.Element -> R.Element
popover = R.rawCreateElement popoverCpt
popover props children = R.rawCreateElement popoverCpt props' children
where
props' = Record.merge props { className: "awesome-popover" }
foreign import _setState :: forall a. EffectFn2 DOM.Element a Unit
......
@import "./base/_reset.scss"
@import "./base/_general.scss"
@import "./base/_form.scss"
@import "./base/_layout.scss"
@import "./base/_nav.scss"
@import "./base/_typography.scss"
@import "./base/_animations.scss"
@import "./base/_bootstrap.scss"
@import "./base/_placeholder.scss"
@import "./base/_utilities.scss"
@import "./base/_range_slider.sass"
......@@ -5,7 +5,6 @@
@import "./_legacy/_tree"
@import "./_legacy/_code_editor"
@import "./_legacy/_styles"
@import "./_legacy/_range_slider"
@import "./_legacy/_annotation"
@import "./_legacy/_folder_view"
@import "./_legacy/_phylo"
This diff is collapsed.
......@@ -92,9 +92,6 @@ li#rename
overflow: visible
height: auto
#graph-tree
.tree
margin-top: 27px
.nopadding
padding: 0 !important
......
......@@ -9,6 +9,11 @@
&__tree-switcher
margin-right: space-x(1)
&__tree-switcher
$fixed-width: 136px
width: $fixed-width
// add hovering effect
&.navbar-dark .navbar-text:hover
color: $navbar-dark-hover-color
......
......@@ -26,7 +26,6 @@
/* Grid constants */
$topbar-height: 56px; // ~ unworthy empirical value (@TODO topbar height calculation)
$graph-margin: 16px;
$layout-height: calc(100vh - #{ $topbar-height} );
......@@ -38,16 +37,6 @@ $left-column-width: 10%;
$center-column-width: 85%;
$right-column-width: 5%;
/* Topbar constants */
$topbar-input-width: 304px;
/* Sidebar constant */
$sidebar-width: 480px;
$sidebar-height: calc(100vh - #{ $topbar-height });
$tab-margin-x: space-x(2.5);
/* Colors */
$graph-background-color: $body-bg;
......@@ -448,34 +437,33 @@ $decreasing-color: #11638F;
////////////////////////////////////////////////////////////////
.phylo-topbar {
$margin: space-x(0.5);
$fixed-button-width: 136px;
@include aside-topbar();
padding-left: $margin;
padding-right: $margin;
padding-left: $topbar-item-margin;
padding-right: $topbar-item-margin;
display: flex;
&__toolbar,
&__sidebar {
width: $fixed-button-width;
margin-left: $margin;
margin-right: $margin;
width: $topbar-fixed-button-width;
margin-left: $topbar-item-margin;
margin-right: $topbar-item-margin;
}
&__source {
width: $topbar-input-width;
margin-left: $margin;
margin-right: $margin;
margin-left: $topbar-item-margin;
margin-right: $topbar-item-margin;
}
&__autocomplete {
display: flex;
width: $topbar-input-width;
position: relative;
margin-left: $margin;
margin-right: $margin;
margin-left: $topbar-item-margin;
margin-right: $topbar-item-margin;
}
&__suggestion {
......@@ -506,60 +494,11 @@ $decreasing-color: #11638F;
////////////////////////////////////////////////////////////////
.phylo-sidebar {
$teaser-height: 16px;
background-color: $body-bg;
height: 100%;
// avoiding ugly scrollbar
scrollbar-width: none;
overflow-y: scroll;
overflow-x: visible;
&::-webkit-scrollbar {
display: none;
}
// adjust nav menu gutter (@TODO: generic?)
&__menu .nav-item {
&:first-child {
margin-left: space-x(2);
}
&:last-child {
margin-right: space-x(2);
}
}
// UX best pratice: when a lengthy column is overflowy hidden
// (with a scroll), a teaser UI element shows to the user that a scroll
// is possible
&__top-teaser {
@include top-teaser;
z-index: z-index('phylo-sidebar', 'teaser');
pointer-events: none;
position: sticky;
top: 0;
height: $teaser-height;
width: 100%;
}
&__bottom-teaser {
@include bottom-teaser;
z-index: z-index('phylo-sidebar', 'teaser');
pointer-events: none;
position: sticky;
bottom: 0;
height: $teaser-height;
width: 100%;
}
@include sidebar;
}
.phylo-details-tab {
$margin-x: $tab-margin-x;
$margin-x: $sidebar-tab-margin-x;
$margin-y: space-x(2);
&__counter {
......@@ -585,7 +524,7 @@ $decreasing-color: #11638F;
}
.phylo-selection-tab {
$margin-x: $tab-margin-x;
$margin-x: $sidebar-tab-margin-x;
$margin-y: space-x(2);
&__highlight {
......@@ -609,7 +548,6 @@ $decreasing-color: #11638F;
&__item {
white-space: normal;
word-break: break-word;
// remove "_reboot.scss" line height
line-height: initial;
&:not(:last-child) {
......
.range
width: 400px
/* some space for the right knob */
padding-right: 30px
.range-slider
position: relative
width: 85%
.scale
position: absolute
width: 100%
height: 3px
margin-top: 2px
background-color: #d8d8d8
.scale-sel
position: absolute
background-color: rgb(39, 196, 112)
width: 100%
height: 7px
.knob
position: absolute
cursor: pointer
-moz-user-select: none
-webkit-user-select: none
-ms-user-select: none
user-select: none
-o-user-select: none
margin-top: -4px
z-index: 1
box-shadow: 1px 1px 3px grey
.button
margin-top: -3px
background: #eee
width: 30px
height: 20px
.range-simple
input
width: 85%
......@@ -69,12 +69,6 @@
/* */
.btn-primary
color: white
background-color: #005a9aff
border-color: black
.frame
height: 100vh
iframe
......
This diff is collapsed.
......@@ -38,7 +38,6 @@
}
/// Mixins
///--------------------------
......@@ -122,3 +121,13 @@
bottom: $value;
left: $value;
}
/// Hidden vertical scrollbar
@mixin hidden-scrollbar() {
scrollbar-width: none;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
}
This diff is collapsed.
......@@ -46,3 +46,7 @@
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse {
from { transform: scale(1); }
to { transform: scale(1.1); }
}
/* Bootstrap 4.x Generic Custom Styles */
// originally added to "table" tag on "_reboot.scss"
.table {
border-collapse: collapse;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment