Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
purescript-gargantext
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Christian Merten
purescript-gargantext
Commits
08bd5c94
Commit
08bd5c94
authored
May 13, 2024
by
Alexandre Delanoë
Browse files
Options
Browse Files
Download
Plain Diff
[WIP] merging
parents
61118987
abebff62
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
212 additions
and
104 deletions
+212
-104
package.json
package.json
+4
-0
spago.lock
spago.lock
+28
-6
spago.yaml
spago.yaml
+7
-7
Layout.purs
src/Gargantext/Components/GraphExplorer/Layout.purs
+8
-2
Store.purs
src/Gargantext/Components/GraphExplorer/Store.purs
+4
-0
Controls.purs
...Gargantext/Components/GraphExplorer/Toolbar/Controls.purs
+14
-14
RangeControl.purs
...antext/Components/GraphExplorer/Toolbar/RangeControl.purs
+29
-11
SlideButton.purs
...gantext/Components/GraphExplorer/Toolbar/SlideButton.purs
+58
-53
Graph.purs
src/Gargantext/Components/Nodes/Graph.purs
+16
-7
RangeSlider.purs
src/Gargantext/Components/RangeSlider.purs
+9
-1
Reactix.js
src/Gargantext/Utils/Reactix.js
+10
-1
Reactix.purs
src/Gargantext/Utils/Reactix.purs
+21
-0
Main.purs
src/Main.purs
+4
-2
No files found.
package.json
View file @
08bd5c94
...
@@ -36,6 +36,8 @@
...
@@ -36,6 +36,8 @@
"
create-react-class
"
:
"
~15.6.3
"
,
"
create-react-class
"
:
"
~15.6.3
"
,
"
crypto
"
:
"
~1.0.1
"
,
"
crypto
"
:
"
~1.0.1
"
,
"
d3
"
:
"
~7.6.1
"
,
"
d3
"
:
"
~7.6.1
"
,
"
debounce
"
:
"
^2.0.0
"
,
"
debouncing
"
:
"
^22.7.25
"
,
"
echarts
"
:
"
~5.1.2
"
,
"
echarts
"
:
"
~5.1.2
"
,
"
echarts-for-react
"
:
"
~3.0.1
"
,
"
echarts-for-react
"
:
"
~3.0.1
"
,
"
graphology
"
:
"
~0.25.1
"
,
"
graphology
"
:
"
~0.25.1
"
,
...
@@ -50,6 +52,7 @@
...
@@ -50,6 +52,7 @@
"
isomorphic-unfetch
"
:
"
~3.1.0
"
,
"
isomorphic-unfetch
"
:
"
~3.1.0
"
,
"
markdown-it
"
:
"
~13.0.1
"
,
"
markdown-it
"
:
"
~13.0.1
"
,
"
prop-types
"
:
"
~15.6.2
"
,
"
prop-types
"
:
"
~15.6.2
"
,
"
pullstate
"
:
"
~1.20.6
"
,
"
react
"
:
"
~18.2.0
"
,
"
react
"
:
"
~18.2.0
"
,
"
react-awesome-popover
"
:
"
~6.1.1
"
,
"
react-awesome-popover
"
:
"
~6.1.1
"
,
"
react-bootstrap
"
:
"
~1.5.2
"
,
"
react-bootstrap
"
:
"
~1.5.2
"
,
...
@@ -58,6 +61,7 @@
...
@@ -58,6 +61,7 @@
"
secp256k1
"
:
"
~4.0.2
"
,
"
secp256k1
"
:
"
~4.0.2
"
,
"
sigma
"
:
"
~2.4.0
"
,
"
sigma
"
:
"
~2.4.0
"
,
"
twgl.js
"
:
"
~5.0.4
"
,
"
twgl.js
"
:
"
~5.0.4
"
,
"
use-debounce
"
:
"
^10.0.0
"
,
"
uuid
"
:
"
8.3.2
"
"
uuid
"
:
"
8.3.2
"
},
},
"devDependencies"
:
{
"devDependencies"
:
{
...
...
spago.lock
View file @
08bd5c94
...
@@ -3,7 +3,7 @@ workspace:
...
@@ -3,7 +3,7 @@ workspace:
gargantext:
gargantext:
path: ./
path: ./
dependencies:
dependencies:
- aff
- aff
: ">=7.1.0 <8.0.0"
- aff-promise: ">=4.0.0 <5.0.0"
- aff-promise: ">=4.0.0 <5.0.0"
- affjax: ">=13.0.0 <14.0.0"
- affjax: ">=13.0.0 <14.0.0"
- affjax-web: ">=1.0.0 <2.0.0"
- affjax-web: ">=1.0.0 <2.0.0"
...
@@ -21,6 +21,8 @@ workspace:
...
@@ -21,6 +21,8 @@ workspace:
- d3: "*"
- d3: "*"
- data-default: "*"
- data-default: "*"
- datetime: ">=6.1.0 <7.0.0"
- datetime: ">=6.1.0 <7.0.0"
- debouncing: ">=0.1.0 <0.2.0"
- debug: ">=6.0.2 <7.0.0"
- dom-filereader: ">=7.0.0 <8.0.0"
- dom-filereader: ">=7.0.0 <8.0.0"
- dom-simple: ">=0.4.0 <0.5.0"
- dom-simple: ">=0.4.0 <0.5.0"
- effect: ">=4.0.0 <5.0.0"
- effect: ">=4.0.0 <5.0.0"
...
@@ -57,7 +59,7 @@ workspace:
...
@@ -57,7 +59,7 @@ workspace:
- profunctor-lenses: ">=8.0.0 <9.0.0"
- profunctor-lenses: ">=8.0.0 <9.0.0"
- random: ">=6.0.0 <7.0.0"
- random: ">=6.0.0 <7.0.0"
- react: ">=11.0.0 <12.0.0"
- react: ">=11.0.0 <12.0.0"
- reactix: ">=0.6.
0
<0.7.0"
- reactix: ">=0.6.
1
<0.7.0"
- record: ">=4.0.0 <5.0.0"
- record: ">=4.0.0 <5.0.0"
- record-extra: ">=5.0.1 <6.0.0"
- record-extra: ">=5.0.1 <6.0.0"
- routing: ">=11.0.0 <12.0.0"
- routing: ">=11.0.0 <12.0.0"
...
@@ -117,6 +119,8 @@ workspace:
...
@@ -117,6 +119,8 @@ workspace:
- d3
- d3
- data-default
- data-default
- datetime
- datetime
- debouncing
- debug
- distributive
- distributive
- dom-filereader
- dom-filereader
- dom-simple
- dom-simple
...
@@ -232,7 +236,7 @@ workspace:
...
@@ -232,7 +236,7 @@ workspace:
- web-xhr
- web-xhr
package_set:
package_set:
address:
address:
registry: 50.1
1.0
registry: 50.1
3.1
compiler: ">=0.15.15 <0.16.0"
compiler: ">=0.15.15 <0.16.0"
content:
content:
abc-parser: 2.0.1
abc-parser: 2.0.1
...
@@ -247,6 +251,7 @@ workspace:
...
@@ -247,6 +251,7 @@ workspace:
affjax-node: 1.0.0
affjax-node: 1.0.0
affjax-web: 1.0.0
affjax-web: 1.0.0
ansi: 7.0.0
ansi: 7.0.0
apexcharts: 0.5.0
applicative-phases: 1.0.0
applicative-phases: 1.0.0
argonaut: 9.0.0
argonaut: 9.0.0
argonaut-aeson-generic: 0.4.1
argonaut-aeson-generic: 0.4.1
...
@@ -308,6 +313,7 @@ workspace:
...
@@ -308,6 +313,7 @@ workspace:
coroutines: 7.0.0
coroutines: 7.0.0
css: 6.0.0
css: 6.0.0
css-frameworks: 1.0.1
css-frameworks: 1.0.1
csv-stream: 1.1.7
data-mvc: 0.0.2
data-mvc: 0.0.2
datetime: 6.1.0
datetime: 6.1.0
datetime-parsing: 0.2.0
datetime-parsing: 0.2.0
...
@@ -545,7 +551,7 @@ workspace:
...
@@ -545,7 +551,7 @@ workspace:
pointed-list: 0.5.1
pointed-list: 0.5.1
polymorphic-vectors: 4.0.0
polymorphic-vectors: 4.0.0
posix-types: 6.0.0
posix-types: 6.0.0
postgresql: 1.
3.0
postgresql: 1.
5.1
precise: 6.0.0
precise: 6.0.0
precise-datetime: 7.0.0
precise-datetime: 7.0.0
prelude: 6.0.1
prelude: 6.0.1
...
@@ -726,12 +732,14 @@ workspace:
...
@@ -726,12 +732,14 @@ workspace:
data-default:
data-default:
git: https://github.com/garganscript/purescript-data-default.git
git: https://github.com/garganscript/purescript-data-default.git
ref: v0.4.0
ref: v0.4.0
debouncing: 0.1.2
graphql-client:
graphql-client:
git: https://github.com/garganscript/purescript-graphql-client.git
git: https://github.com/garganscript/purescript-graphql-client.git
ref: spago-next-9.3.2
ref: spago-next-9.3.2
markdown-it:
markdown-it:
git: https://github.com/garganscript/purescript-markdown-it.git
git: https://github.com/garganscript/purescript-markdown-it.git
ref: spago-next
ref: spago-next
reactix: 0.6.1
sequences:
sequences:
git: https://github.com/garganscript/purescript-sequences.git
git: https://github.com/garganscript/purescript-sequences.git
ref: v3.0.2-spago-next
ref: v3.0.2-spago-next
...
@@ -1048,6 +1056,20 @@ packages:
...
@@ -1048,6 +1056,20 @@ packages:
- partial
- partial
- prelude
- prelude
- tuples
- tuples
debouncing:
type: registry
version: 0.1.2
integrity: sha256-2Ajskjmc+r3gnpcv6L8U4BfiSKj4N7sCINiBD99RjO8=
dependencies:
- effect
- prelude
debug:
type: registry
version: 6.0.2
integrity: sha256-vmkYFuXYuELBzeauvgHG6E6Kf/Hp1dAnxwE9ByHfwSg=
dependencies:
- functions
- prelude
distributive:
distributive:
type: registry
type: registry
version: 6.0.0
version: 6.0.0
...
@@ -1822,8 +1844,8 @@ packages:
...
@@ -1822,8 +1844,8 @@ packages:
- unsafe-coerce
- unsafe-coerce
reactix:
reactix:
type: registry
type: registry
version: 0.6.
0
version: 0.6.
1
integrity: sha256-
gaF3igbCsTmaGxfbykWaBcyv4n7AHVmQRstZ/msNfJw
=
integrity: sha256-
mM6JZFWfeMhgMJa9oGdzNchkp/Xcnv2e/oGc0nZp6EQ
=
dependencies:
dependencies:
- dom-simple
- dom-simple
- effect
- effect
...
...
spago.yaml
View file @
08bd5c94
workspace
:
workspace
:
packageSet
:
packageSet
:
registry
:
50.1
1.0
registry
:
50.1
3.1
extraPackages
:
extraPackages
:
# garganscript packages
# garganscript packages
d3
:
d3
:
git
:
https://github.com/garganscript/purescript-d3.git
git
:
https://github.com/garganscript/purescript-d3.git
ref
:
v0.11.0
ref
:
v0.11.0
debouncing
:
0.1.2
reactix
:
0.6.1
string-search
:
string-search
:
git
:
https://gitlab.iscpif.fr/gargantext/purescript-string-search.git
git
:
https://gitlab.iscpif.fr/gargantext/purescript-string-search.git
ref
:
spago-next
ref
:
spago-next
...
@@ -37,10 +39,7 @@ package:
...
@@ -37,10 +39,7 @@ package:
name
:
gargantext
name
:
gargantext
dependencies
:
dependencies
:
# debugging
# debugging
# - debug: ">=6.0.2 <7.0.0"
-
aff
:
"
>=7.1.0
<8.0.0"
# - psci-support: ">=6.0.0 <7.0.0"
-
aff
-
aff-promise
:
"
>=4.0.0
<5.0.0"
-
aff-promise
:
"
>=4.0.0
<5.0.0"
-
affjax
:
"
>=13.0.0
<14.0.0"
-
affjax
:
"
>=13.0.0
<14.0.0"
-
affjax-web
:
"
>=1.0.0
<2.0.0"
-
affjax-web
:
"
>=1.0.0
<2.0.0"
...
@@ -58,6 +57,8 @@ package:
...
@@ -58,6 +57,8 @@ package:
-
d3
:
"
*"
-
d3
:
"
*"
-
data-default
:
"
*"
-
data-default
:
"
*"
-
datetime
:
"
>=6.1.0
<7.0.0"
-
datetime
:
"
>=6.1.0
<7.0.0"
-
debouncing
:
"
>=0.1.0
<0.2.0"
-
debug
:
"
>=6.0.2
<7.0.0"
-
dom-filereader
:
"
>=7.0.0
<8.0.0"
-
dom-filereader
:
"
>=7.0.0
<8.0.0"
-
dom-simple
:
"
>=0.4.0
<0.5.0"
-
dom-simple
:
"
>=0.4.0
<0.5.0"
-
effect
:
"
>=4.0.0
<5.0.0"
-
effect
:
"
>=4.0.0
<5.0.0"
...
@@ -94,7 +95,7 @@ package:
...
@@ -94,7 +95,7 @@ package:
-
profunctor-lenses
:
"
>=8.0.0
<9.0.0"
-
profunctor-lenses
:
"
>=8.0.0
<9.0.0"
-
random
:
"
>=6.0.0
<7.0.0"
-
random
:
"
>=6.0.0
<7.0.0"
-
react
:
"
>=11.0.0
<12.0.0"
-
react
:
"
>=11.0.0
<12.0.0"
-
reactix
:
"
>=0.6.
0
<0.7.0"
-
reactix
:
"
>=0.6.
1
<0.7.0"
-
record
:
"
>=4.0.0
<5.0.0"
-
record
:
"
>=4.0.0
<5.0.0"
-
record-extra
:
"
>=5.0.1
<6.0.0"
-
record-extra
:
"
>=5.0.1
<6.0.0"
-
routing
:
"
>=11.0.0
<12.0.0"
-
routing
:
"
>=11.0.0
<12.0.0"
...
@@ -140,7 +141,6 @@ package:
...
@@ -140,7 +141,6 @@ package:
-
UnusedName
-
UnusedName
-
UnusedTypeVar
-
UnusedTypeVar
test
:
test
:
main
:
Test.Main
main
:
Test.Main
dependencies
:
dependencies
:
...
...
src/Gargantext/Components/GraphExplorer/Layout.purs
View file @
08bd5c94
...
@@ -235,6 +235,8 @@ graphViewCpt = R.memo' $ here.component "graphView" cpt where
...
@@ -235,6 +235,8 @@ graphViewCpt = R.memo' $ here.component "graphView" cpt where
, sigmaRef
, sigmaRef
} _ = do
} _ = do
-- { edgeConfluence, edgeWeight } <- GraphStore.use
-- edgeConfluence' <- R2.useLive' edgeConfluence
-- edgeConfluence' <- R2.useLive' edgeConfluence
-- edgeWeight' <- R2.useLive' edgeWeight
-- edgeWeight' <- R2.useLive' edgeWeight
-- nodeSize' <- R2.useLive' nodeSize
-- nodeSize' <- R2.useLive' nodeSize
...
@@ -410,7 +412,11 @@ hooksTransformGraph = do
...
@@ -410,7 +412,11 @@ hooksTransformGraph = do
params <- transformGraphStoreParams
params <- transformGraphStoreParams
graph' <- R2.useLive' store.graph
graph' <- R2.useLive' store.graph
-- R.useEffect' $ do
-- here.log2 "[hooksTransformGraph] hashed" $ hashLiveProps params
R.useEffect2' (hashLiveProps params) graph' $ do
R.useEffect2' (hashLiveProps params) graph' $ do
-- here.log2 "[hooksTransformGraph] transformed" $ transformGraph graph' params
T.write_ (transformGraph graph' params) store.transformedGraph
T.write_ (transformGraph graph' params) store.transformedGraph
transformGraph :: SigmaxT.SGraph -> Record LiveProps -> SigmaxT.SGraph
transformGraph :: SigmaxT.SGraph -> Record LiveProps -> SigmaxT.SGraph
...
@@ -450,8 +456,8 @@ transformGraph graph { edgeConfluence'
...
@@ -450,8 +456,8 @@ transformGraph graph { edgeConfluence'
edge { hidden = true }
edge { hidden = true }
edgeHideWeight :: Record SigmaxT.Edge -> Record SigmaxT.Edge
edgeHideWeight :: Record SigmaxT.Edge -> Record SigmaxT.Edge
edgeHideWeight edge@{ weight
Idx
} =
edgeHideWeight edge@{ weight } =
if Range.within edgeWeight'
$ toNumber weightIdx
then
if Range.within edgeWeight'
weight
then
edge
edge
else
else
edge { hidden = true }
edge { hidden = true }
...
...
src/Gargantext/Components/GraphExplorer/Store.purs
View file @
08bd5c94
...
@@ -44,6 +44,7 @@ type Store =
...
@@ -44,6 +44,7 @@ type Store =
, edgeConfluence :: T.Box Range.NumberRange
, edgeConfluence :: T.Box Range.NumberRange
, edgeConfluenceRange :: T.Box Range.NumberRange
, edgeConfluenceRange :: T.Box Range.NumberRange
, edgeWeight :: T.Box Range.NumberRange
, edgeWeight :: T.Box Range.NumberRange
, edgeWeightRange :: T.Box Range.NumberRange
, forceAtlasState :: T.Box SigmaxT.ForceAtlasState
, forceAtlasState :: T.Box SigmaxT.ForceAtlasState
, noverlapState :: T.Box SigmaxT.NoverlapState
, noverlapState :: T.Box SigmaxT.NoverlapState
, graphStage :: T.Box GET.Stage
, graphStage :: T.Box GET.Stage
...
@@ -79,6 +80,7 @@ type State =
...
@@ -79,6 +80,7 @@ type State =
, edgeConfluence :: Range.NumberRange
, edgeConfluence :: Range.NumberRange
, edgeConfluenceRange :: Range.NumberRange
, edgeConfluenceRange :: Range.NumberRange
, edgeWeight :: Range.NumberRange
, edgeWeight :: Range.NumberRange
, edgeWeightRange :: Range.NumberRange
, forceAtlasState :: SigmaxT.ForceAtlasState
, forceAtlasState :: SigmaxT.ForceAtlasState
, noverlapState :: SigmaxT.NoverlapState
, noverlapState :: SigmaxT.NoverlapState
, graphStage :: GET.Stage
, graphStage :: GET.Stage
...
@@ -109,6 +111,7 @@ options ::
...
@@ -109,6 +111,7 @@ options ::
, mouseSelectorSize :: Number
, mouseSelectorSize :: Number
, multiSelectEnabled :: Boolean
, multiSelectEnabled :: Boolean
, edgeConfluence :: Range.NumberRange
, edgeConfluence :: Range.NumberRange
, edgeWeight :: Range.NumberRange
, graphStage :: GET.Stage
, graphStage :: GET.Stage
, nodeSize :: Range.NumberRange
, nodeSize :: Range.NumberRange
--, showLouvain :: Boolean
--, showLouvain :: Boolean
...
@@ -131,6 +134,7 @@ options =
...
@@ -131,6 +134,7 @@ options =
, labelRenderedSizeThreshold : 2.0
, labelRenderedSizeThreshold : 2.0
, mouseSelectorSize : 15.0
, mouseSelectorSize : 15.0
, edgeConfluence : Range.Closed { min: 0.0, max: 1.0 }
, edgeConfluence : Range.Closed { min: 0.0, max: 1.0 }
, edgeWeight : Range.Closed { min: 0.0, max: 1.0 }
, graphStage : GET.Init
, graphStage : GET.Init
, nodeSize : Range.Closed { min: 0.0, max: 100.0 }
, nodeSize : Range.Closed { min: 0.0, max: 100.0 }
--, showLouvain : false
--, showLouvain : false
...
...
src/Gargantext/Components/GraphExplorer/Toolbar/Controls.purs
View file @
08bd5c94
...
@@ -11,7 +11,7 @@ import Effect.Timer (setTimeout)
...
@@ -11,7 +11,7 @@ import Effect.Timer (setTimeout)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.GraphExplorer.Store as GraphStore
import Gargantext.Components.GraphExplorer.Store as GraphStore
import Gargantext.Components.GraphExplorer.Toolbar.Buttons (cameraButton, centerButton, edgesToggleButton, louvainButton, pauseForceAtlasButton, pauseNoverlapButton, multiSelectEnabledButton)
import Gargantext.Components.GraphExplorer.Toolbar.Buttons (cameraButton, centerButton, edgesToggleButton, louvainButton, pauseForceAtlasButton, pauseNoverlapButton, multiSelectEnabledButton)
import Gargantext.Components.GraphExplorer.Toolbar.RangeControl (edgeConfluenceControl, nodeSizeControl)
import Gargantext.Components.GraphExplorer.Toolbar.RangeControl (edgeConfluenceControl,
edgeWeightControl,
nodeSizeControl)
import Gargantext.Components.GraphExplorer.Toolbar.SlideButton (labelSizeButton, labelRenderedSizeThresholdButton, mouseSelectorSizeSlider)
import Gargantext.Components.GraphExplorer.Toolbar.SlideButton (labelSizeButton, labelRenderedSizeThresholdButton, mouseSelectorSizeSlider)
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.ForceAtlas2 as ForceAtlas
import Gargantext.Hooks.Sigmax.ForceAtlas2 as ForceAtlas
...
@@ -52,7 +52,8 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
...
@@ -52,7 +52,8 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
-- |
-- |
{ edgeConfluence
{ edgeConfluence
, edgeConfluenceRange
, edgeConfluenceRange
-- , edgeWeight
, edgeWeight
, edgeWeightRange
, forceAtlasState
, forceAtlasState
, noverlapState
, noverlapState
, graph
, graph
...
@@ -80,6 +81,7 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
...
@@ -80,6 +81,7 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
selectedNodeIds' <- R2.useLive' selectedNodeIds
selectedNodeIds' <- R2.useLive' selectedNodeIds
showSidebar' <- R2.useLive' showSidebar
showSidebar' <- R2.useLive' showSidebar
edgeConfluenceRange' <- R2.useLive' edgeConfluenceRange
edgeConfluenceRange' <- R2.useLive' edgeConfluenceRange
edgeWeightRange' <- R2.useLive' edgeWeightRange
nodeSizeRange' <- R2.useLive' nodeSizeRange
nodeSizeRange' <- R2.useLive' nodeSizeRange
-- session <- useSession
-- session <- useSession
...
@@ -207,12 +209,10 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
...
@@ -207,12 +209,10 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
}
}
,
,
gap, gap
gap, gap
,
, edgeWeightControl
labelRenderedSizeThresholdButton
{ forceAtlasState
{ forceAtlasState
, range: edgeWeightRange'
, sigmaRef
, state: edgeWeight }
, state: labelRenderedSizeThreshold
}
]
]
,
,
-- Run spatialization
-- Run spatialization
...
@@ -321,12 +321,12 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
...
@@ -321,12 +321,12 @@ controlsCpt = R.memo' $ here.component "controls" cpt where
{ forceAtlasState
{ forceAtlasState
, range: edgeConfluenceRange'
, range: edgeConfluenceRange'
, state: edgeConfluence }
, state: edgeConfluence }
{- ,
, gap
edgeWeightControl
, labelRenderedSizeThresholdButton
{ forceAtlasState
{ forceAtlasState
, range: edgeWeightRange
, sigmaRef
, state: edgeWeight }
, state: labelRenderedSizeThreshold
-
}
}
,
,
gap, gap
gap, gap
,
,
...
...
src/Gargantext/Components/GraphExplorer/Toolbar/RangeControl.purs
View file @
08bd5c94
...
@@ -6,19 +6,29 @@ module Gargantext.Components.GraphExplorer.Toolbar.RangeControl
...
@@ -6,19 +6,29 @@ module Gargantext.Components.GraphExplorer.Toolbar.RangeControl
, nodeSizeControl
, nodeSizeControl
) where
) where
import Data.Maybe (Maybe(..))
import Data.Tuple.Nested((/\))
import Debug (spy)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Effect.Debouncing as Debounce
import FFI.Simple (delay)
import Gargantext.Components.RangeSlider as RS
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
import Gargantext.Utils.Range as Range
import Gargantext.Utils.Reactix as R2
import Prelude
import Prelude
import Reactix as R
import Reactix as R
import Reactix.DOM.HTML as H
import Reactix.DOM.HTML as H
import Toestand as T
import Toestand as T
import Gargantext.Components.RangeSlider as RS
import Gargantext.Hooks.Sigmax.Types as SigmaxTypes
import Gargantext.Utils.Range as Range
import Gargantext.Utils.Reactix as R2
here :: R2.Here
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Toolbar.RangeControl"
here = R2.here "Gargantext.Components.GraphExplorer.Toolbar.RangeControl"
defaultThrottleInterval :: Int
defaultThrottleInterval = 500
type Props =
type Props =
( caption :: String
( caption :: String
, sliderProps :: Record RS.Props
, sliderProps :: Record RS.Props
...
@@ -54,22 +64,24 @@ edgeConfluenceControlCpt :: R.Component EdgeConfluenceControlProps
...
@@ -54,22 +64,24 @@ edgeConfluenceControlCpt :: R.Component EdgeConfluenceControlProps
edgeConfluenceControlCpt = here.component "edgeConfluenceControl" cpt
edgeConfluenceControlCpt = here.component "edgeConfluenceControl" cpt
where
where
cpt { forceAtlasState
cpt { forceAtlasState
, range
: Range.Closed { min, max }
, range
, state
, state
} _ = do
} _ = do
forceAtlasState' <- R2.useLive' forceAtlasState
forceAtlasState' <- R2.useLive' forceAtlasState
state' <- T.useLive T.unequal state
state' <- T.useLive T.unequal state
pure $ rangeControl {
pure $ rangeControl {
caption: "Edge Confluence Weight"
caption: "Edge Confluence Weight"
, sliderProps: {
, sliderProps: {
bounds:
Range.Closed { min, max }
bounds:
range
, epsilon: 0.01
, epsilon: 0.01
, height: 5.0
, height: 5.0
, initialValue: state'
, initialValue: state'
, onChange: \rng -> T.write_ rng state
, onChange: \rng -> T.write_ rng state
, status: SigmaxTypes.forceAtlasComponentStatus forceAtlasState'
, status: SigmaxTypes.forceAtlasComponentStatus forceAtlasState'
, step: 1.0
, step: 1.0
, throttleInterval: Just defaultThrottleInterval
, width: 10.0
, width: 10.0
}
}
}
}
...
@@ -88,22 +100,27 @@ edgeWeightControlCpt :: R.Component EdgeWeightControlProps
...
@@ -88,22 +100,27 @@ edgeWeightControlCpt :: R.Component EdgeWeightControlProps
edgeWeightControlCpt = here.component "edgeWeightControl" cpt
edgeWeightControlCpt = here.component "edgeWeightControl" cpt
where
where
cpt { forceAtlasState
cpt { forceAtlasState
, range:
Range.Closed { min, max }
, range:
range@(Range.Closed { min, max })
, state
, state
} _ = do
} _ = do
forceAtlasState' <- R2.useLive' forceAtlasState
forceAtlasState' <- R2.useLive' forceAtlasState
state' <- T.useLive T.unequal state
state' <- T.useLive T.unequal state
pure $ rangeControl {
pure $ rangeControl {
caption: "Edge Weight"
caption: "Edge Weight"
, sliderProps: {
, sliderProps: {
bounds: Range.Closed { min, max }
bounds: range
, initialValue: state'
, initialValue: state'
, epsilon:
1
.0
, epsilon:
(max - min) / 100
.0
, height: 5.0
, height: 5.0
, onChange: \rng -> T.write_ rng state
, onChange: \rng -> T.write_ rng state
-- , onChange: \rng -> Debounce.call onChange rng
-- , onChange: \rng -> RD.callDebouncedCallback rd rng
-- , onChange: onChange'
, status: SigmaxTypes.forceAtlasComponentStatus forceAtlasState'
, status: SigmaxTypes.forceAtlasComponentStatus forceAtlasState'
, step: 1.0
, step: 1.0
, throttleInterval: Just defaultThrottleInterval
, width: 10.0
, width: 10.0
}
}
}
}
...
@@ -138,6 +155,7 @@ nodeSizeControlCpt = here.component "nodeSizeControl" cpt
...
@@ -138,6 +155,7 @@ nodeSizeControlCpt = here.component "nodeSizeControl" cpt
, onChange: \rng -> T.write_ rng state
, onChange: \rng -> T.write_ rng state
, status: SigmaxTypes.forceAtlasComponentStatus forceAtlasState'
, status: SigmaxTypes.forceAtlasComponentStatus forceAtlasState'
, step: 1.0
, step: 1.0
, throttleInterval: Just defaultThrottleInterval
, width: 10.0
, width: 10.0
}
}
}
}
src/Gargantext/Components/GraphExplorer/Toolbar/SlideButton.purs
View file @
08bd5c94
...
@@ -7,10 +7,11 @@ module Gargantext.Components.GraphExplorer.Toolbar.SlideButton
...
@@ -7,10 +7,11 @@ module Gargantext.Components.GraphExplorer.Toolbar.SlideButton
) where
) where
import Data.Map as Map
import Data.Map as Map
import Data.Maybe (Maybe(..))
import Data.Maybe (Maybe(..)
, fromMaybe
)
import Data.Number as DN
import Data.Number as DN
import Prelude
import Effect (Effect)
import Effect (Effect)
import Effect.Debouncing as Debounce
import Prelude
import Reactix as R
import Reactix as R
import Reactix.DOM.HTML as H
import Reactix.DOM.HTML as H
import Toestand as T
import Toestand as T
...
@@ -25,20 +26,29 @@ import Gargantext.Utils.Reactix as R2
...
@@ -25,20 +26,29 @@ import Gargantext.Utils.Reactix as R2
here :: R2.Here
here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Toolbar.SlideButton"
here = R2.here "Gargantext.Components.GraphExplorer.Toolbar.SlideButton"
defaultThrottleInterval :: Int
defaultThrottleInterval = 500
type Props =
type Props =
( caption :: String
( caption :: String
, forceAtlasState :: T.Box SigmaxTypes.ForceAtlasState
, forceAtlasState :: T.Box SigmaxTypes.ForceAtlasState
, min :: Number
, min :: Number
, max :: Number
, max :: Number
, onChange :: forall e. e -> Effect Unit
, onChange :: Number -> Effect Unit
, state :: T.Box Number
, state :: T.Box Number
, throttleInterval :: Maybe Int -- then Nothing, no throttling is done
)
)
sizeButton :: Record Props -> R.Element
sizeButton :: Record Props -> R.Element
sizeButton props = R.createElement sizeButtonCpt props []
sizeButton props = R.createElement sizeButtonCpt props []
sizeButtonCpt :: R.Component Props
sizeButtonCpt :: R.Component Props
sizeButtonCpt = here.component "sizeButton" cpt where
sizeButtonCpt = here.component "sizeButton" cpt where
cpt { state, caption, forceAtlasState, min, max, onChange } _ = do
cpt { state, caption, forceAtlasState, min, max, onChange, throttleInterval } _ = do
let throttled = Debounce.throttleWithDebounceAtEnd onChange (fromMaybe 0 throttleInterval)
let onChangeThrottled = case throttleInterval of
Nothing -> onChange
Just ti -> \rng -> Debounce.call throttled rng
defaultValue <- T.useLive T.unequal state
defaultValue <- T.useLive T.unequal state
forceAtlasState' <- R2.useLive' forceAtlasState
forceAtlasState' <- R2.useLive' forceAtlasState
...
@@ -61,7 +71,11 @@ sizeButtonCpt = here.component "sizeButton" cpt where
...
@@ -61,7 +71,11 @@ sizeButtonCpt = here.component "sizeButton" cpt where
, min: show min
, min: show min
, max: show max
, max: show max
, defaultValue
, defaultValue
, on: { input: onChange }
, on: { input: \e -> do
let v = DN.fromString $ R.unsafeEventValue e
case v of
Nothing -> pure unit
Just vv -> onChangeThrottled vv }
, className: "range-simple__input"
, className: "range-simple__input"
, disabled: status == Disabled
, disabled: status == Disabled
}
}
...
@@ -92,30 +106,27 @@ labelSizeButtonCpt = here.component "labelSizeButton" cpt
...
@@ -92,30 +106,27 @@ labelSizeButtonCpt = here.component "labelSizeButton" cpt
, forceAtlasState
, forceAtlasState
, min: minLabelSize
, min: minLabelSize
, max: maxLabelSize
, max: maxLabelSize
, onChange: \e -> do
, onChange: \
newValu
e -> do
let sigma = R.readRef sigmaRef
let sigma = R.readRef sigmaRef
let newValue' = DN.fromString $ R.unsafeEventValue e
Sigmax.dependOnSigma sigma "[labelSizeButton] sigma: Nothing" $ \s -> do
case newValue' of
let ratio = (newValue - minLabelSize) / (defaultLabelSize - minLabelSize)
Nothing -> pure unit
let nodes = SigmaxTypes.graphNodes graph'
Just newValue ->
let nodesResized = (\n@{ size } -> n { size = size * ratio }) <$> nodes
Sigmax.dependOnSigma sigma "[labelSizeButton] sigma: Nothing" $ \s -> do
let nodesMap = SigmaxTypes.idMap nodesResized
let ratio = (newValue - minLabelSize) / (defaultLabelSize - minLabelSize)
Graphology.forEachNode (Sigma.graph s) $ \{ id } -> do
let nodes = SigmaxTypes.graphNodes graph'
case Map.lookup id nodesMap of
let nodesResized = (\n@{ size } -> n { size = size * ratio }) <$> nodes
Nothing -> pure unit
let nodesMap = SigmaxTypes.idMap nodesResized
Just { size } -> Graphology.mergeNodeAttributes (Sigma.graph s) id { size }
Graphology.forEachNode (Sigma.graph s) $ \{ id } -> do
case Map.lookup id nodesMap of
Sigma.setSettings s {
Nothing -> pure unit
defaultLabelSize: newValue
Just { size } -> Graphology.mergeNodeAttributes (Sigma.graph s) id { size }
, drawLabels: true
, labelSize: newValue
Sigma.setSettings s {
-- , maxNodeSize: newValue / 2.5
defaultLabelSize: newValue
--, labelSizeRatio: newValue / 2.5
, drawLabels: true
}
, labelSize: newValue
T.write_ newValue state
-- , maxNodeSize: newValue / 2.5
, throttleInterval: Just defaultThrottleInterval
--, labelSizeRatio: newValue / 2.5
}
T.write_ newValue state
}
}
type LabelRenderedSizeThresholdButtonProps =
type LabelRenderedSizeThresholdButtonProps =
...
@@ -135,17 +146,14 @@ labelRenderedSizeThresholdButtonCpt = here.component "labelRenderedSizeThreshold
...
@@ -135,17 +146,14 @@ labelRenderedSizeThresholdButtonCpt = here.component "labelRenderedSizeThreshold
, forceAtlasState
, forceAtlasState
, min: 0.0
, min: 0.0
, max: 10.0
, max: 10.0
, onChange: \e -> do
, onChange: \
newValu
e -> do
let sigma = R.readRef sigmaRef
let sigma = R.readRef sigmaRef
let newValue' = DN.fromString $ R.unsafeEventValue e
Sigmax.dependOnSigma sigma "[labelRenderdSizeThresholdButton] sigma: Nothing" $ \s -> do
case newValue' of
Sigma.setSettings s {
Nothing -> pure unit
labelRenderedSizeThreshold: newValue
Just newValue ->
}
Sigmax.dependOnSigma sigma "[labelRenderdSizeThresholdButton] sigma: Nothing" $ \s -> do
T.write_ newValue state
Sigma.setSettings s {
, throttleInterval: Just defaultThrottleInterval
labelRenderedSizeThreshold: newValue
}
T.write_ newValue state
}
}
type MouseSelectorSizeSliderProps =
type MouseSelectorSizeSliderProps =
...
@@ -164,16 +172,13 @@ mouseSelectorSizeSliderCpt = here.component "mouseSelectorSizeSlider" cpt
...
@@ -164,16 +172,13 @@ mouseSelectorSizeSliderCpt = here.component "mouseSelectorSizeSlider" cpt
, forceAtlasState
, forceAtlasState
, min: 1.0
, min: 1.0
, max: 100.0
, max: 100.0
, onChange: \e -> do
, onChange: \
newValu
e -> do
let sigma = R.readRef sigmaRef
let sigma = R.readRef sigmaRef
let newValue' = DN.fromString $ R.unsafeEventValue e
Sigmax.dependOnSigma sigma "[mouseSelectorSizeButton] sigma: Nothing" $ \s -> do
case newValue' of
Sigma.setSettings s {
Nothing -> pure unit
mouseSelectorSize: newValue
Just newValue ->
}
Sigmax.dependOnSigma sigma "[mouseSelectorSizeButton] sigma: Nothing" $ \s -> do
T.write_ newValue state
Sigma.setSettings s {
mouseSelectorSize: newValue
}
T.write_ newValue state
, state
, state
, throttleInterval: Just defaultThrottleInterval
}
}
src/Gargantext/Components/Nodes/Graph.purs
View file @
08bd5c94
...
@@ -5,7 +5,6 @@ module Gargantext.Components.Nodes.Graph
...
@@ -5,7 +5,6 @@ module Gargantext.Components.Nodes.Graph
import Gargantext.Prelude
import Gargantext.Prelude
import Data.Array as A
import Data.Array as A
import Data.Int as I
import Data.Maybe (Maybe(..), isJust, maybe)
import Data.Maybe (Maybe(..), isJust, maybe)
import Data.Sequence as Seq
import Data.Sequence as Seq
import Data.Tuple (Tuple(..))
import Data.Tuple (Tuple(..))
...
@@ -165,6 +164,11 @@ hydrateStoreCpt = here.component "hydrateStore" cpt where
...
@@ -165,6 +164,11 @@ hydrateStoreCpt = here.component "hydrateStore" cpt where
-- | Precompute some values
-- | Precompute some values
-- |
-- |
let edgesWeightSorted = A.sortWith (_.weight) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
let edgeWeightMin = maybe 0.0 _.weight $ A.head edgesWeightSorted
let edgeWeightMax = maybe 100.0 _.weight $ A.last edgesWeightSorted
let edgeWeightRange = Range.Closed { min: edgeWeightMin, max: edgeWeightMax }
let edgesConfluenceSorted = A.sortWith (_.confluence) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
let edgesConfluenceSorted = A.sortWith (_.confluence) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
let edgeConfluenceMin = maybe 0.0 _.confluence $ A.head edgesConfluenceSorted
let edgeConfluenceMin = maybe 0.0 _.confluence $ A.head edgesConfluenceSorted
let edgeConfluenceMax = maybe 100.0 _.confluence $ A.last edgesConfluenceSorted
let edgeConfluenceMax = maybe 100.0 _.confluence $ A.last edgesConfluenceSorted
...
@@ -184,13 +188,17 @@ hydrateStoreCpt = here.component "hydrateStore" cpt where
...
@@ -184,13 +188,17 @@ hydrateStoreCpt = here.component "hydrateStore" cpt where
let nodeSizeMax = maybe 100.0 _.size $ A.last nodesSorted
let nodeSizeMax = maybe 100.0 _.size $ A.last nodesSorted
let nodeSizeRange = Range.Closed { min: nodeSizeMin, max: nodeSizeMax }
let nodeSizeRange = Range.Closed { min: nodeSizeMin, max: nodeSizeMax }
let edgeWeight = Range.Closed
-- let edgeWeight = Range.Closed
{ min: 0.0
-- { min: 0.0
, max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph
-- , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph
}
-- }
-- let weightsSeq = Seq.map _.weight $ SigmaxT.graphEdges graph
-- let edgeWeight = Range.Closed
-- { min: fromMaybe 0.0 $ minimum weightsSeq
-- , max: fromMaybe 1.0 $ maximum weightsSeq } :: Range.Closed Number
let transformedGraph = transformGraph graph { edgeConfluence': GraphStore.options.edgeConfluence
let transformedGraph = transformGraph graph { edgeConfluence': GraphStore.options.edgeConfluence
, edgeWeight': edgeWeight
, edgeWeight':
GraphStore.options.
edgeWeight
, nodeSize': GraphStore.options.nodeSize
, nodeSize': GraphStore.options.nodeSize
, removedNodeIds': GraphStore.options.removedNodeIds
, removedNodeIds': GraphStore.options.removedNodeIds
, selectedNodeIds': GraphStore.options.selectedNodeIds
, selectedNodeIds': GraphStore.options.selectedNodeIds
...
@@ -208,7 +216,8 @@ hydrateStoreCpt = here.component "hydrateStore" cpt where
...
@@ -208,7 +216,8 @@ hydrateStoreCpt = here.component "hydrateStore" cpt where
, startForceAtlas
, startForceAtlas
, forceAtlasState
, forceAtlasState
, noverlapState: SigmaxT.NoverlapPaused
, noverlapState: SigmaxT.NoverlapPaused
, edgeWeight
-- , edgeWeight
, edgeWeightRange
, edgeConfluenceRange
, edgeConfluenceRange
, nodeSizeRange
, nodeSizeRange
-- (cache options)
-- (cache options)
...
...
src/Gargantext/Components/RangeSlider.purs
View file @
08bd5c94
...
@@ -21,6 +21,7 @@ import DOM.Simple.Event as Event
...
@@ -21,6 +21,7 @@ import DOM.Simple.Event as Event
import DOM.Simple.EventListener as EL
import DOM.Simple.EventListener as EL
import DOM.Simple (DOMRect)
import DOM.Simple (DOMRect)
import Effect (Effect)
import Effect (Effect)
import Effect.Debouncing as Debounce
import Reactix as R
import Reactix as R
import Reactix.DOM.HTML as H
import Reactix.DOM.HTML as H
import Toestand as T
import Toestand as T
...
@@ -51,6 +52,7 @@ type Props =
...
@@ -51,6 +52,7 @@ type Props =
, width :: Number
, width :: Number
, height :: Number
, height :: Number
, onChange :: Range.NumberRange -> Effect Unit
, onChange :: Range.NumberRange -> Effect Unit
, throttleInterval :: Maybe Int -- then Nothing, no throttling is done
, status :: ComponentStatus )
, status :: ComponentStatus )
data Knob = MinKnob | MaxKnob
data Knob = MinKnob | MaxKnob
...
@@ -65,6 +67,11 @@ rangeSlider props = R.createElement rangeSliderCpt props []
...
@@ -65,6 +67,11 @@ rangeSlider props = R.createElement rangeSliderCpt props []
rangeSliderCpt :: R.Component Props
rangeSliderCpt :: R.Component Props
rangeSliderCpt = here.component "rangeSlider" cpt where
rangeSliderCpt = here.component "rangeSlider" cpt where
cpt props _ = do
cpt props _ = do
let throttled = Debounce.throttleWithDebounceAtEnd props.onChange (fromMaybe 0 props.throttleInterval)
let onChangeThrottled = case props.throttleInterval of
Nothing -> props.onChange
Just ti -> \rng -> Debounce.call throttled rng
-- rounding precision (i.e. how many decimal digits are in epsilon)
-- rounding precision (i.e. how many decimal digits are in epsilon)
let (Range.Closed { min: minR, max: maxR }) = props.initialValue
let (Range.Closed { min: minR, max: maxR }) = props.initialValue
let decPrecision num =
let decPrecision num =
...
@@ -116,7 +123,8 @@ rangeSliderCpt = here.component "rangeSlider" cpt where
...
@@ -116,7 +123,8 @@ rangeSliderCpt = here.component "rangeSlider" cpt where
case reproject drag scalePos props.bounds props.epsilon (R2.domMousePosition event) of
case reproject drag scalePos props.bounds props.epsilon (R2.domMousePosition event) of
Just val -> do
Just val -> do
setKnob knob value value' val
setKnob knob value value' val
props.onChange $ knobSetter knob value' val
-- props.onChange $ knobSetter knob value' val
onChangeThrottled $ knobSetter knob value' val
Nothing -> destroy unit
Nothing -> destroy unit
let onMouseUp = EL.callback $ \(_event :: Event.MouseEvent) -> do
let onMouseUp = EL.callback $ \(_event :: Event.MouseEvent) -> do
--props.onChange $ knobSetter knob value val
--props.onChange $ knobSetter knob value val
...
...
src/Gargantext/Utils/Reactix.js
View file @
08bd5c94
...
@@ -61,4 +61,13 @@ export function _scrollIntoView(el) {
...
@@ -61,4 +61,13 @@ export function _scrollIntoView(el) {
inline
:
'center'
inline
:
'center'
});
});
}
}
\ No newline at end of file
export
const
isPendingTransitionImpl
=
(
t
)
=>
()
=>
{
return
t
[
0
]();
}
export
const
startTransitionImpl
=
(
t
)
=>
(
f
)
=>
()
=>
{
console
.
log
(
'starting transition'
,
t
,
f
);
return
t
[
1
](()
=>
{
f
()()
});
}
src/Gargantext/Utils/Reactix.purs
View file @
08bd5c94
...
@@ -648,3 +648,24 @@ setInputValue elNullableRef val = case toMaybe (R.readRef elNullableRef) of
...
@@ -648,3 +648,24 @@ setInputValue elNullableRef val = case toMaybe (R.readRef elNullableRef) of
_ <- pure $ (el .= "value") val
_ <- pure $ (el .= "value") val
triggerEvent el "change"
triggerEvent el "change"
triggerEvent el "input"
triggerEvent el "input"
-- TODO useTransition?
foreign import data Transition :: Type
-- foreign import useTransitionImpl :: R.Hooks Transition
-- useTransition :: R.Hooks Transition
-- useTransition = useTransitionImpl
useTransition :: R.Hooks Transition
useTransition = hook $ \_ -> pure $ react ... "useTransition" $ []
foreign import isPendingTransitionImpl :: Transition -> R.Hooks Boolean
isPendingTransition :: Transition -> R.Hooks Boolean
isPendingTransition = isPendingTransitionImpl
foreign import startTransitionImpl :: Transition -> (Unit -> Effect Unit) -> Effect Unit
startTransition :: Transition -> (Unit -> Effect Unit) -> Effect Unit
startTransition = startTransitionImpl
src/Main.purs
View file @
08bd5c94
...
@@ -8,7 +8,7 @@ import Effect (Effect)
...
@@ -8,7 +8,7 @@ import Effect (Effect)
import FFI.Simple ((...))
import FFI.Simple ((...))
import Gargantext.Components.App as App
import Gargantext.Components.App as App
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Reactix as R2
import Prelude (Unit, ($))
import Prelude (Unit, ($)
, bind
)
import Reactix as R
import Reactix as R
...
@@ -21,6 +21,8 @@ main = paint $ toMaybe (document ... "getElementById" $ [ "app" ])
...
@@ -21,6 +21,8 @@ main = paint $ toMaybe (document ... "getElementById" $ [ "app" ])
paint :: Maybe Element -> Effect Unit
paint :: Maybe Element -> Effect Unit
paint Nothing = here.error "[main] Container not found"
paint Nothing = here.error "[main] Container not found"
paint (Just c) = do
paint (Just c) = do
R.render app c
-- R.render app c
let r = R.createRoot c
R.renderRoot r app
where
where
app = App.app {}
app = App.app {}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment