Commit c7304e4d authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

[purty] purty-fied the whole src tree

Also, added 'yarn purty-format' command.
parent e2a6b681
......@@ -8,7 +8,8 @@
"sass": "sass dist/styles/",
"dev": "webpack-dev-server --env dev --mode development",
"repl": "pulp --psc-package repl",
"clean": "rm -Rf output"
"clean": "rm -Rf output",
"purty-format": "find src -iname '*.purs' -exec yarn run purty {} --write \\;"
},
"dependencies": {
"@babel/polyfill": "^7.0.0",
......@@ -17,8 +18,10 @@
"echarts": "^4.1.0",
"echarts-for-react": "^2.0.14",
"prop-types": "15.6.2",
"purty": "^4.5.1",
"react": "^16.10",
"react-dom": "^16.10",
"sass": "^1.22.9",
"sigma": "git://github.com/jjl/sigma.js#garg"
},
"eslintConfig": {
......
module Gargantext.BootstrapNative where
import Effect (Effect)
import Gargantext.Prelude
foreign import createDropdown :: String -> Effect Unit
......@@ -12,61 +12,64 @@
module Gargantext.Components.Annotation.AnnotatedField where
import Prelude
import Data.Maybe ( Maybe(..), maybe )
import Data.Tuple ( Tuple(..) )
import Data.Tuple.Nested ( (/\) )
import Data.Maybe (Maybe(..), maybe)
import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\))
import DOM.Simple.Event as DE
import Effect ( Effect )
import Effect.Uncurried ( mkEffectFn1 )
import Effect (Effect)
import Effect.Uncurried (mkEffectFn1)
import Reactix as R
import Reactix.DOM.HTML as HTML
import Reactix.SyntheticEvent as E
import Gargantext.Types (CTabNgramType(..), TermList)
import Gargantext.Components.Annotation.Utils ( termBootstrapClass )
import Gargantext.Components.Annotation.Utils (termBootstrapClass)
import Gargantext.Components.NgramsTable.Core (NgramsTable, NgramsTerm, findNgramTermList, highlightNgrams)
import Gargantext.Components.Annotation.Menu ( AnnotationMenu, annotationMenu, MenuType(..) )
import Gargantext.Components.Annotation.Menu (AnnotationMenu, annotationMenu, MenuType(..))
import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Selection as Sel
type Props =
( ngrams :: NgramsTable
type Props
= ( ngrams :: NgramsTable
, setTermList :: NgramsTerm -> Maybe TermList -> TermList -> Effect Unit
, text :: Maybe String
)
type MouseEvent = E.SyntheticEvent DE.MouseEvent
type MouseEvent
= E.SyntheticEvent DE.MouseEvent
-- UNUSED
-- defaultProps :: Record Props
-- defaultProps = { ngrams: NgramsTable Map.empty, text: Nothing, setTermList: \_ _ _ -> pure unit }
annotatedField :: Record Props -> R.Element
annotatedField p = R.createElement annotatedFieldComponent p []
annotatedFieldComponent :: R.Component Props
annotatedFieldComponent = R.hooksComponent "AnnotatedField" cpt
where
cpt {ngrams,setTermList,text} _ = do
cpt { ngrams, setTermList, text } _ = do
menu /\ setMenu <- R.useState $ const Nothing
let wrapperProps =
{ className: "annotated-field-wrapper" }
let
wrapperProps = { className: "annotated-field-wrapper" }
onSelect _ Nothing event = maybeShowMenu setMenu setTermList ngrams event
onSelect text' (Just list) event = do
let x = E.clientX event
let
x = E.clientX event
y = E.clientY event
setList t = do
setTermList text' (Just list) t
setMenu (const Nothing)
setMenu (const $ Just {x, y, list: Just list, menuType: SetTermListItem, setList} )
setMenu (const $ Just { x, y, list: Just list, menuType: SetTermListItem, setList })
mapCompile (Tuple t l) = {text: t, list: l, onSelect}
compiled = map mapCompile $ compile ngrams text
mapCompile (Tuple t l) = { text: t, list: l, onSelect }
runs =
HTML.div { className: "annotated-field-runs" } $ map annotateRun compiled
pure $ HTML.div wrapperProps [maybeAddMenu setMenu runs menu]
compiled = map mapCompile $ compile ngrams text
runs = HTML.div { className: "annotated-field-runs" } $ map annotateRun compiled
pure $ HTML.div wrapperProps [ maybeAddMenu setMenu runs menu ]
-- forall e. IsMouseEvent e => R2.Setter (Maybe AnnotationMenu) -> R2.Setter ? -> ? -> e -> Effect Unit
maybeShowMenu setMenu setTermList ngrams event = do
......@@ -76,9 +79,13 @@ maybeShowMenu setMenu setTermList ngrams event = do
case Sel.selectionToString sel of
"" -> pure unit
sel' -> do
let x = E.clientX event
let
x = E.clientX event
y = E.clientY event
list = findNgramTermList CTabTerms ngrams sel'
setList t = do
setTermList sel' list t
setMenu (const Nothing)
......@@ -86,21 +93,21 @@ maybeShowMenu setMenu setTermList ngrams event = do
setMenu (const $ Just { x, y, list, menuType: NewNgram, setList })
Nothing -> pure unit
maybeAddMenu
:: R2.Setter (Maybe AnnotationMenu)
-> R.Element
-> Maybe AnnotationMenu
-> R.Element
maybeAddMenu ::
R2.Setter (Maybe AnnotationMenu) ->
R.Element ->
Maybe AnnotationMenu ->
R.Element
maybeAddMenu setMenu e (Just props) = annotationMenu setMenu props <> e
maybeAddMenu _ e _ = e
compile :: NgramsTable -> Maybe String -> Array (Tuple String (Maybe TermList))
compile ngrams = maybe [] (highlightNgrams CTabTerms ngrams)
-- Runs
type Run =
( text :: String
type Run
= ( text :: String
, list :: (Maybe TermList)
, onSelect :: String -> Maybe TermList -> MouseEvent -> Effect Unit
)
......@@ -111,13 +118,13 @@ annotateRun p = R.createElement annotatedRunComponent p []
annotatedRunComponent :: R.Component Run
annotatedRunComponent = R.staticComponent "AnnotatedRun" cpt
where
cpt { text, list: Nothing, onSelect } _ =
HTML.span { onMouseUp: mkEffectFn1 $ \e -> onSelect text Nothing e } [ HTML.text text ]
cpt { text, list: Nothing, onSelect } _ = HTML.span { onMouseUp: mkEffectFn1 $ \e -> onSelect text Nothing e } [ HTML.text text ]
cpt { text, list: (Just list), onSelect } _ =
HTML.span { className: className list
, onClick: mkEffectFn1 $ \e -> onSelect text (Just list) e} [ HTML.text text ]
HTML.span
{ className: className list
, onClick: mkEffectFn1 $ \e -> onSelect text (Just list) e
}
[ HTML.text text ]
where
className list' = "annotation-run bg-" <> termBootstrapClass list'
-- | A ContextMenU that allows you to add terms to a list
module Gargantext.Components.Annotation.Menu where
import Prelude (Unit, pure, ($), (<>), (==))
import Data.Array as A
import Data.Maybe (Maybe(..))
......@@ -9,45 +8,52 @@ import Effect (Effect)
import Effect.Uncurried (mkEffectFn1)
import Reactix as R
import Reactix.DOM.HTML as HTML
import Gargantext.Types (TermList(..), termListName)
import Gargantext.Components.Annotation.Utils (termBootstrapClass)
import Gargantext.Components.ContextMenu.ContextMenu as CM
import Gargantext.Utils.Reactix as R2
data MenuType = NewNgram | SetTermListItem
data MenuType
= NewNgram
| SetTermListItem
type Props =
( list :: Maybe TermList
type Props
= ( list :: Maybe TermList
, menuType :: MenuType
, setList :: TermList -> Effect Unit -- not a state hook setter
)
type AnnotationMenu = { x :: Number, y :: Number | Props }
type AnnotationMenu
= { x :: Number, y :: Number | Props }
-- | An Annotation Menu is parameterised by a Maybe Termlist of the
-- | TermList the currently selected text belongs to
annotationMenu :: R2.Setter (Maybe AnnotationMenu) -> AnnotationMenu -> R.Element
annotationMenu setMenu { x,y,list,menuType,setList } =
CM.contextMenu { x,y,setMenu } [
R.createElement annotationMenuCpt {list,menuType,setList} []
annotationMenu setMenu { x, y, list, menuType, setList } =
CM.contextMenu { x, y, setMenu }
[ R.createElement annotationMenuCpt { list, menuType, setList } []
]
annotationMenuCpt :: R.Component Props
annotationMenuCpt = R.hooksComponent "Annotation.Menu" cpt
where
cpt props _ = pure $ R.fragment $ children props
children props = A.mapMaybe (addToList props) [ GraphTerm, CandidateTerm, StopTerm ]
-- | Given the TermList to render the item for zand the Maybe TermList the item may belong to, possibly render the menuItem
addToList :: Record Props -> TermList -> Maybe R.Element
addToList {list: Just t'} t
addToList { list: Just t' } t
| t == t' = Nothing
addToList {menuType, setList} t = Just $ CM.contextMenuItem [ link ]
addToList { menuType, setList } t = Just $ CM.contextMenuItem [ link ]
where
link = HTML.a { onClick: click, className: className } [ HTML.text (label menuType) ]
label NewNgram = "Add to " <> termListName t
label SetTermListItem = "Change to " <> termListName t
className = "list-group-item list-group-item-" <> (termBootstrapClass t)
click = mkEffectFn1 $ \_ -> setList t
module Gargantext.Components.Annotation.Utils where
import Gargantext.Types ( TermList(..) )
import Gargantext.Types (TermList(..))
termClass :: TermList -> String
termClass GraphTerm = "graph-term"
termClass StopTerm = "stop-term"
termClass CandidateTerm = "candidate-term"
termBootstrapClass :: TermList -> String
termBootstrapClass GraphTerm = "success"
termBootstrapClass StopTerm = "danger"
termBootstrapClass CandidateTerm = "warning"
This diff is collapsed.
......@@ -9,31 +9,38 @@ import Gargantext.Prelude
import Effect (Effect)
import Effect.Timer (IntervalId, setInterval, clearInterval)
data Action = Update
data Action
= Update
type PropsRow =
( duration :: Int
type PropsRow
= ( duration :: Int
, effect :: Effect Unit
)
type Props = { | PropsRow }
type Props
= { | PropsRow }
type State = { intervalId :: Maybe IntervalId }
type State
= { intervalId :: Maybe IntervalId }
autoUpdateClass :: ReactClass { children :: Children | PropsRow }
autoUpdateClass =
React.component "AutoUpdate"
(\this -> do
pure { state: {intervalId: Nothing}
( \this -> do
pure
{ state: { intervalId: Nothing }
, render: pure $ div' []
, componentDidMount: do
{duration,effect} <- React.getProps this
, componentDidMount:
do
{ duration, effect } <- React.getProps this
intervalId <- setInterval duration effect
React.setState this {intervalId: Just intervalId}
, componentWillUnmount: do
{intervalId} <- React.getState this
React.setState this { intervalId: Just intervalId }
, componentWillUnmount:
do
{ intervalId } <- React.getState this
traverse_ clearInterval intervalId
})
}
)
autoUpdateElt :: Props -> ReactElement
autoUpdateElt props = React.createElement autoUpdateClass props []
......@@ -13,7 +13,6 @@ module Gargantext.Components.Charts.Options.Color
) where
import Prelude
import CSS as CSS
import Color (rgba)
import Unsafe.Coerce (unsafeCoerce)
......@@ -34,8 +33,8 @@ red = stringColor "red"
blue :: Color
blue = cssColor $ rgba 100 150 200 0.0
-- stringColor "blue"
-- stringColor "blue"
magenta :: Color
magenta = stringColor "magenta"
......
......@@ -5,43 +5,48 @@ import Unsafe.Coerce (unsafeCoerce)
import Gargantext.Types (class Optional)
import Gargantext.Components.Charts.Options.Font (TextStyle, Icon, ItemStyle)
type DataLegend =
{ name :: String
type DataLegend
= { name :: String
, icon :: Icon
, textStyle :: TextStyle
}
type DataAxis = Array String
{- value :: String
type DataAxis
= Array String
{- value :: String
, textStyle :: TextStyle
-}
type RequiredData v o =
{ value :: v
type RequiredData v o
= { value :: v
| o
}
type OptionalData =
( name :: String
type OptionalData
= ( name :: String
, symbolSize :: Number
, itemStyle :: ItemStyle
-- ^ the style setting about single data point(bubble).
, label :: { show :: Boolean }
)
type DataSerie v = RequiredData v OptionalData
type DataSerie v
= RequiredData v OptionalData
dataSerie :: forall v o. Optional o OptionalData => RequiredData v o -> DataSerie v
dataSerie = unsafeCoerce
dataSerieV :: forall v. v -> DataSerie v
dataSerieV value = dataSerie {value}
dataSerieV value = dataSerie { value }
type DataD1
= DataSerie Number
type DataD1 = DataSerie Number
type DataD2 = DataSerie (Array Number)
type DataD2
= DataSerie (Array Number)
dataD1 :: forall o. Optional o OptionalData => Record o -> Number -> DataD1
dataD1 o x = unsafeCoerce (unsafeSet "value" x o)
dataD2 :: forall o. Optional o OptionalData => Record o -> Number -> Number -> DataD2
dataD2 o x y = unsafeCoerce (unsafeSet "value" [x,y] o)
dataD2 o x y = unsafeCoerce (unsafeSet "value" [ x, y ] o)
module Gargantext.Components.Charts.Options.ECharts where
import Prelude
import CSS (italic)
import CSS.Common (normal)
import Gargantext.Components.Charts.Options.Color (transparent, violet, black)
......@@ -24,62 +23,63 @@ chart = echarts <<< chartWith <<< opts
chartWith :: Option -> Echarts
chartWith option =
{ option
--, className : Nothing
--, style : Nothing
--, theme : Nothing
--, group : Nothing
--, initOpts : Nothing
--, notMerge : Nothing
--, lazyUpdate: Nothing
--, loading : Nothing
--, optsLoading: Nothing
--, onReady : Nothing
--, resizable : Nothing
--, onEvents : Nothing
--, className : Nothing
--, style : Nothing
--, theme : Nothing
--, group : Nothing
--, initOpts : Nothing
--, notMerge : Nothing
--, lazyUpdate: Nothing
--, loading : Nothing
--, optsLoading: Nothing
--, onReady : Nothing
--, resizable : Nothing
--, onEvents : Nothing
}
echarts :: Echarts -> R.Element
echarts c = R2.buff $ unsafeCreateElementDynamic (unsafeCoerce eChartsClass) c []
type MainTitle = String
type SubTitle = String
type MainTitle
= String
type SubTitle
= String
title :: MainTitle -> SubTitle -> Title
title mainTitle subTitle =
{
id: ""
,show: true
,text: mainTitle
,link: ""
,target: "blank"
,textStyle: textStyle
,subtext: subTitle
,sublink: ""
,subtarget: "blank"
,subtextStyle: textStyle2
,padding: 10.0
,itemGap: 0.0
,zlevel: 2.0
,z: 2.0
,left: relativePosition (Relative RightPos)
,top: relativePosition (Relative Top)
,right: numberPosition 60.0
,bottom: percentPosition 40.0
,backgroundColor: transparent
,borderColor: transparent
,borderWidth: 0.0
,borderRadius: 0.0
,shadowBlur: 0.0
,shadowColor: transparent
,shadowOffsetX: 0.0
,shadowOffsetY: 0.0
{ id: ""
, show: true
, text: mainTitle
, link: ""
, target: "blank"
, textStyle: textStyle
, subtext: subTitle
, sublink: ""
, subtarget: "blank"
, subtextStyle: textStyle2
, padding: 10.0
, itemGap: 0.0
, zlevel: 2.0
, z: 2.0
, left: relativePosition (Relative RightPos)
, top: relativePosition (Relative Top)
, right: numberPosition 60.0
, bottom: percentPosition 40.0
, backgroundColor: transparent
, borderColor: transparent
, borderWidth: 0.0
, borderRadius: 0.0
, shadowBlur: 0.0
, shadowColor: transparent
, shadowOffsetX: 0.0
, shadowOffsetY: 0.0
}
legend :: Legend
legend =
{
id: "Muda"
,"type": legendType Plain
{ id: "Muda"
, "type": legendType Plain
, show: true
, zlevel: 0.0
, z: 2.0
......@@ -100,54 +100,60 @@ legend =
, inactiveColor: violet
--- selected: Nothing
, textStyle: textStyle
, "data": [data1]
, "data": [ data1 ]
}
data1 :: DataLegend
data1 = {name: "Map terms coverage", icon: icon $ Shape Circle, textStyle: textStyle'}
data1 = { name: "Map terms coverage", icon: icon $ Shape Circle, textStyle: textStyle' }
data2 :: DataLegend
data2 = {name: "Favorites", icon: icon $ Shape Circle, textStyle: textStyle'}
data2 = { name: "Favorites", icon: icon $ Shape Circle, textStyle: textStyle' }
data3 :: DataLegend
data3 = {name: "Test", icon: icon $ Shape Diamond, textStyle: textStyle'}
data3 = { name: "Test", icon: icon $ Shape Diamond, textStyle: textStyle' }
yAxisVoid :: YAxis
yAxisVoid = yAxis
yAxisVoid =
yAxis
{ "type": ""
, name: ""
, min: 0
, position: ""
, axisLabel: {formatter: ""}
, axisLabel: { formatter: "" }
, show: false
}
xAxis' :: Array String -> XAxis
xAxis' [] = unsafeCoerce {show:false}
xAxis' xs = xAxis
xAxis' [] = unsafeCoerce { show: false }
xAxis' xs =
xAxis
{ "data": xs
, "type": "category"
, axisTick: {alignWithLabel: true}
, axisTick: { alignWithLabel: true }
, show: true
, axisLabel: {formatter: "{value}"}
, axisLabel: { formatter: "{value}" }
}
-- TODO try to use Optional
yAxis' :: { position :: String
yAxis' ::
{ position :: String
, show :: Boolean
, min :: Int
} -> YAxis
yAxis' {position, show, min} = yAxis
} ->
YAxis
yAxis' { position, show, min } =
yAxis
{ "type": "value"
, name: ""
, min: min
, axisLabel: {formatter: "{value}"}
, axisLabel: { formatter: "{value}" }
, position
, show
}
data Options = Options
data Options
= Options
{ mainTitle :: MainTitle
, subTitle :: SubTitle
, xAxis :: XAxis
......@@ -158,55 +164,60 @@ data Options = Options
}
tooltipTriggerAxis :: Tooltip
tooltipTriggerAxis = mkTooltip { trigger: "axis"}
tooltipTriggerAxis = mkTooltip { trigger: "axis" }
opts :: Options -> Option
opts (Options { mainTitle
opts ( Options
{ mainTitle
, subTitle
, xAxis
, yAxis
, series
, tooltip
, addZoom
}) =
}
) =
{ title: title mainTitle subTitle
, legend
, tooltip
, grid: {containLabel: true}
, grid: { containLabel: true }
, series
, xAxis
, yAxis
, dataZoom: if addZoom then [zoom Slider, zoom Inside] else []
, children : unsafeCoerce [] -- TODO
, dataZoom: if addZoom then [ zoom Slider, zoom Inside ] else []
, children: unsafeCoerce [] -- TODO
, toolbox: mkToolBox
}
data Zoom = Slider | Inside
data Zoom
= Slider
| Inside
instance showZoom :: Show Zoom where
show Slider = "slider"
show Inside = "inside"
zoom :: Zoom -> DataZoom
zoom z = {
"type": show z
,xAxisIndex: 0
,filterMode: "empty"
,start: 0
,end: 100
zoom z =
{ "type": show z
, xAxisIndex: 0
, filterMode: "empty"
, start: 0
, end: 100
}
seriesPie :: Series
seriesPie = seriesPieD1
seriesPie =
seriesPieD1
{ name: "Pie name" }
(dataSerie <$> [ {name: "t1", value: 50.0}
, {name: "t2", value: 45.0}
, {name: "t3", value: 65.0}
, {name: "t4", value: 15.0}
, {name: "t5", value: 23.0}
])
( dataSerie
<$> [ { name: "t1", value: 50.0 }
, { name: "t2", value: 45.0 }
, { name: "t3", value: 65.0 }
, { name: "t4", value: 15.0 }
, { name: "t5", value: 23.0 }
]
)
textStyle2 :: TextStyle
textStyle2 =
......
......@@ -28,7 +28,6 @@ module Gargantext.Components.Charts.Options.Font
) where
import Prelude (Unit, ($), (<<<), (<>))
import Data.Generic.Rep
import Data.Generic.Rep.Show (genericShow)
import CSS (FontStyle(..), FontWeight(..), Prefixed(..), Value(..))
......@@ -38,9 +37,8 @@ import Gargantext.Components.Charts.Options.Position (LeftRelativePosition, Posi
import Gargantext.Types (class Optional)
import Unsafe.Coerce (unsafeCoerce)
type TextStyle =
{ color :: Color
type TextStyle
= { color :: Color
, fontStyle :: ChartFontStyle
, fontWeight :: ChartFontWeight
, fontFamily :: String
......@@ -58,42 +56,58 @@ type TextStyle =
, textShadowOffsetY :: Number
}
newtype ChartFontStyle = ChartFontStyle String
newtype ChartFontStyle
= ChartFontStyle String
chartFontStyle :: FontStyle -> ChartFontStyle
chartFontStyle (FontStyle (Value (Plain "italic"))) = ChartFontStyle "italic"
chartFontStyle (FontStyle (Value (Plain "oblique"))) = ChartFontStyle "oblique"
chartFontStyle _ = ChartFontStyle "normal"
chartFontStyle _ = ChartFontStyle "normal"
newtype ChartFontWeight = ChartFontWeight String
newtype ChartFontWeight
= ChartFontWeight String
chartFontWeight :: FontWeight -> ChartFontWeight
chartFontWeight (FontWeight (Value (Plain "bold"))) = ChartFontWeight "bold"
chartFontWeight (FontWeight (Value (Plain "bolder"))) = ChartFontWeight "bolder"
chartFontWeight (FontWeight (Value (Plain "lighter"))) = ChartFontWeight "lighter"
chartFontWeight _ = ChartFontWeight "normal"
newtype Icon
= Icon String
newtype Icon = Icon String
newtype ImageURL
= ImageURL String
newtype ImageURL = ImageURL String
data Shape
= Circle
| Rect
| RoundRect
| Triangle
| Diamond
| Pin
| Arrow
data Shape = Circle | Rect | RoundRect | Triangle | Diamond | Pin | Arrow
derive instance genericShape :: Generic Shape _
data IconOptions = Shape Shape | Image ImageURL
data IconOptions
= Shape Shape
| Image ImageURL
icon :: IconOptions -> Icon
icon (Shape s) = Icon <<< toLower $ genericShow s
icon (Image (ImageURL url)) = Icon $ "image://" <> url
icon (Image (ImageURL url)) = Icon $ "image://" <> url
data ItemStyle
type ItemStyleOptional =
( color :: Color
type ItemStyleOptional
= ( color :: Color
)
itemStyle :: forall o. Optional o ItemStyleOptional => Record o -> ItemStyle
......@@ -105,11 +119,10 @@ templateFormatter :: String -> Formatter
templateFormatter = unsafeCoerce
-- TODO callbackFormatter :: (...) -> Formatter
data Tooltip
type TooltipOptional =
( trigger :: String
type TooltipOptional
= ( trigger :: String
-- ^ Not all tooltips support triggers.
-- Grid and legend tooltips : yes
-- Series : no
......@@ -120,20 +133,25 @@ type TooltipOptional =
-----------------------------------------------------------------
-- | ToolBox
mkToolBox :: ToolBox
mkToolBox = { feature: { dataView : { show: true, readOnly : false, title : "Data"}
, saveAsImage : { show : true, pixelRatio : 10, title : "Image"}
mkToolBox =
{ feature:
{ dataView: { show: true, readOnly: false, title: "Data" }
, saveAsImage: { show: true, pixelRatio: 10, title: "Image" }
--, magicType : { show : true, "type" : ["line", "bar", "pie", "stack", "tiled"], title : "Type"}
--, restore : {show : true, title : "Restore"}
--, brush : {"type" : ["rect", "polygon", "lineX", "lineY", "keep", "clear"]}
}
, orient : "vertical"
, orient: "vertical"
}
---------------------------------------
type ToolBox = { feature :: Feature
, orient :: String}
type Feature = { dataView :: DataView
type ToolBox
= { feature :: Feature
, orient :: String
}
type Feature
= { dataView :: DataView
, saveAsImage :: Save
--, magicType :: MagicType
--, restore :: Restore
......@@ -141,27 +159,33 @@ type Feature = { dataView :: DataView
}
---------------------------------------
type Save = { show :: Boolean
type Save
= { show :: Boolean
, pixelRatio :: Int
, title :: String
}
type Restore = { show :: Boolean
, title :: String}
type Restore
= { show :: Boolean
, title :: String
}
type MagicType = { show :: Boolean
type MagicType
= { show :: Boolean
, "type" :: Array String -- TODO use line bar types
, title :: String
}
---------------------------------------
type DataView = { show :: Boolean
---------------------------------------
type DataView
= { show :: Boolean
, readOnly :: Boolean
, title :: String
}
type Brush = { "type" :: Array String }
---------------------------------------
type Brush
= { "type" :: Array String }
---------------------------------------
mkTooltip :: forall o. Optional o TooltipOptional => Record o -> Tooltip
mkTooltip = unsafeCoerce
module Gargantext.Components.Charts.Options.Legend
(
LegendType(..),
PlainOrScroll(..),
legendType,
Orient(),
Orientation(..),
orient,
SelectedMode(),
LegendMode(..),
selectedMode
( LegendType(..)
, PlainOrScroll(..)
, legendType
, Orient()
, Orientation(..)
, orient
, SelectedMode()
, LegendMode(..)
, selectedMode
) where
import Prelude (class Show, show, (<<<))
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show
import Data.String (toLower)
import Unsafe.Coerce (unsafeCoerce)
newtype LegendType = LegendType String
newtype LegendType
= LegendType String
data PlainOrScroll
= Plain
| Scroll
data PlainOrScroll = Plain | Scroll
instance showPlainOrScroll :: Show PlainOrScroll where
show (Plain) = "plain"
show (Scroll) = "scroll"
......@@ -28,22 +30,30 @@ instance showPlainOrScroll :: Show PlainOrScroll where
legendType :: PlainOrScroll -> LegendType
legendType = LegendType <<< toLower <<< show
newtype Orient
= Orient String
newtype Orient = Orient String
data Orientation
= Horizontal
| Vertical
data Orientation = Horizontal | Vertical
derive instance genericOrientation :: Generic Orientation _
orient :: Orientation -> Orient
orient = Orient <<< toLower <<< genericShow
foreign import data SelectedMode :: Type
data LegendMode = Bool Boolean | Single | Multiple
data LegendMode
= Bool Boolean
| Single
| Multiple
derive instance genericLegendMode :: Generic LegendMode _
selectedMode :: LegendMode -> SelectedMode
selectedMode (Bool b) = unsafeCoerce b
selectedMode (Single) = unsafeCoerce "single"
selectedMode (Multiple) = unsafeCoerce "multiple"
module Gargantext.Components.Charts.Options.Position
(
Position(),
numberPosition,
percentPosition,
relativePosition,
Align(..),
TopRelativePosition(..),
LeftRelativePosition(..)
( Position()
, numberPosition
, percentPosition
, relativePosition
, Align(..)
, TopRelativePosition(..)
, LeftRelativePosition(..)
) where
import Prelude
import Unsafe.Coerce (unsafeCoerce)
-- | The type `Position` is made to render a css position.
......@@ -29,18 +27,29 @@ percentPosition n = unsafeCoerce $ (show n) <> "%"
-- | Smart constructor to build a JS String giving position's detail ("top", "left", ...)
relativePosition :: forall a. Show a => Align a -> Position a
relativePosition (Auto) = unsafeCoerce "auto"
relativePosition (Relative r) = unsafeCoerce $ show r
data Align p = Auto | Relative p
data Align p
= Auto
| Relative p
data TopRelativePosition = Top | Middle | Bottom
instance showTopRelativePosition :: Show TopRelativePosition
where show (Top) = "top"
data TopRelativePosition
= Top
| Middle
| Bottom
instance showTopRelativePosition :: Show TopRelativePosition where
show (Top) = "top"
show (Middle) = "middle"
show (Bottom) = "bottom"
data LeftRelativePosition = LeftPos | Center | RightPos
instance showLeftRelativePosition :: Show LeftRelativePosition
where show (LeftPos) = "left"
data LeftRelativePosition
= LeftPos
| Center
| RightPos
instance showLeftRelativePosition :: Show LeftRelativePosition where
show (LeftPos) = "left"
show (Center) = "center"
show (RightPos) = "right"
......@@ -10,15 +10,19 @@ import Gargantext.Types (class Optional)
import Gargantext.Components.Charts.Options.Font (ItemStyle, Tooltip)
import Gargantext.Components.Charts.Options.Data (DataD1, DataD2)
newtype SeriesType = SeriesType String
newtype SeriesType
= SeriesType String
type SeriesName = String
type SeriesName
= String
data Chart = Line
| Bar | PictorialBar
data Chart
= Line
| Bar
| PictorialBar
| Pie
| Scatter | EffectScatter
| Scatter
| EffectScatter
| Radar
| Trees
| Sunburst
......@@ -33,8 +37,8 @@ data Chart = Line
| Funnel
| Gauge
| ThemeRiver
-- Trees
-- Trees
instance showChart :: Show Chart where
show Bar = "bar"
show EffectScatter = "effectScatter" -- ^ https://ecomfe.github.io/echarts-examples/public/editor.html?c=scatter-effect
......@@ -50,16 +54,14 @@ instance showChart :: Show Chart where
seriesType :: Chart -> SeriesType
seriesType = SeriesType <<< show
-- | Scatter Dimension 2 data
type OptionalSeries =
( name :: String
type OptionalSeries
= ( name :: String
, symbolSize :: Number
, itemStyle :: ItemStyle
-- ^ Graphic style of, *emphasis* is the style when it is highlighted, like being hovered by mouse, or highlighted via legend connect.
-- https://ecomfe.github.io/echarts-doc/public/en/option.html#series-scatter.itemStyle
, tooltip :: Tooltip
-- many more...
)
......@@ -68,8 +70,8 @@ data Series
unsafeSeries :: forall o. Record o -> Series
unsafeSeries = unsafeCoerce
type RequiredSeriesD1 o =
{ "type" :: SeriesType
type RequiredSeriesD1 o
= { "type" :: SeriesType
, "data" :: Array DataD1
| o
}
......@@ -86,8 +88,8 @@ seriesBarD1 o ds = unsafeSeries (unsafeSet "data" ds (unsafeSet "type" (seriesTy
seriesPieD1 :: forall o. Optional o OptionalSeries => Record o -> Array DataD1 -> Series
seriesPieD1 o ds = unsafeSeries (unsafeSet "data" ds (unsafeSet "type" (seriesType Pie) o))
type RequiredSeriesD2 o =
{ "data" :: Array DataD2
type RequiredSeriesD2 o
= { "data" :: Array DataD2
, "type" :: SeriesType
| o
}
......@@ -96,25 +98,27 @@ seriesD2 :: forall o. Optional o OptionalSeries => RequiredSeriesD2 o -> Series
seriesD2 = unsafeSeries
seriesScatterD2 :: forall o. Optional o OptionalSeries => Record o -> Array DataD2 -> Series
seriesScatterD2 o ds =
unsafeCoerce (unsafeSet "data" ds (unsafeSet "type" (seriesType Scatter) o))
seriesScatterD2 o ds = unsafeCoerce (unsafeSet "data" ds (unsafeSet "type" (seriesType Scatter) o))
type Node = { name :: String}
type Link = { source :: String
type Node
= { name :: String }
type Link
= { source :: String
, target :: String
, value :: Number
}
-- | Sankey Chart
-- https://ecomfe.github.io/echarts-examples/public/editor.html?c=sankey-simple
type RequiredSankey o =
{ "data" :: Array Node
type RequiredSankey o
= { "data" :: Array Node
, links :: Array Link
| o
}
type OptionalSankey =
( layout :: String
type OptionalSankey
= ( layout :: String
)
seriesSankey :: forall o. Optional o OptionalSankey => RequiredSankey o -> Series
......@@ -123,43 +127,43 @@ seriesSankey o = unsafeSeries ((unsafeCoerce o) { "type" = seriesType Sankey })
-- | * Trees Chart
-- All these Trees are hierarchical Trees structure (or diagram)
-- https://en.wikipedia.org/wiki/Tree_structure
-- Tree types
data Trees = TreeLine | TreeRadial | TreeMap
data Trees
= TreeLine
| TreeRadial
| TreeMap
instance showTrees :: Show Trees where
show TreeLine = "tree" -- ^ https://ecomfe.github.io/echarts-examples/public/editor.html?c=tree-radial
show TreeRadial = "tree" -- ^ https://ecomfe.github.io/echarts-examples/public/editor.html?c=scatter-simple
show TreeMap = "treemap" -- ^ https://ecomfe.github.io/echarts-examples/public/editor.html?c=treemap-simple
-- TreeLine is a 1-Dimension horizontal hierchical Tree
-- TreeRadial is 1-Dimension radial (as circle) Tree with no surface meaning
-- https://en.wikipedia.org/wiki/Radial_tree
-- https://ecomfe.github.io/echarts-examples/public/editor.html?c=tree-radial
-- TreeMap is a is 2-Dimension Tree with surface meaning
-- TreeMap example implementation:
-- https://ecomfe.github.io/echarts-examples/public/editor.html?c=treemap-simple
type RequiredTree o =
{ "type" :: SeriesType
type RequiredTree o
= { "type" :: SeriesType
, "data" :: Array TreeNode
| o
}
type OptionalTree =
( layout :: String
type OptionalTree
= ( layout :: String
)
seriesTree :: forall o. Optional o OptionalTree => RequiredTree o -> Series
seriesTree = unsafeSeries
mkTree :: Trees -> Array TreeNode -> Series
mkTree t ts = seriesTree { "type" : SeriesType (show t)
, "data" : map (toJsTree Nothing) ts
, layout : layout
mkTree t ts =
seriesTree
{ "type": SeriesType (show t)
, "data": map (toJsTree Nothing) ts
, layout: layout
}
where
layout = case t of
......@@ -167,45 +171,40 @@ mkTree t ts = seriesTree { "type" : SeriesType (show t)
_ -> "none"
-- ** Data Structure of the Trees
data TreeData = Array TreeNode
data TreeData
= Array TreeNode
treeValue :: TreeNode -> Int
treeValue (TreeNode x) = foldl (+) 0 $ [x.value] <> map treeValue x.children
treeValue (TreeNode x) = foldl (+) 0 $ [ x.value ] <> map treeValue x.children
toJsTree :: Maybe String -> TreeNode -> TreeNode
toJsTree maybeSurname (TreeNode x) =
unsafeCoerce { name : name
, value : foldl (+) 0 $ [x.value] <> map treeValue x.children
, children : (map (toJsTree (Just name)) x.children)
unsafeCoerce
{ name: name
, value: foldl (+) 0 $ [ x.value ] <> map treeValue x.children
, children: (map (toJsTree (Just name)) x.children)
}
where
name = maybe "" (\x' -> x' <> ">") maybeSurname <> x.name
data TreeNode = TreeNode { name :: String
data TreeNode
= TreeNode
{ name :: String
, value :: Int
, children :: Array TreeNode
}
instance decodeTreeNode :: DecodeJson TreeNode where
decodeJson json = do
obj <- decodeJson json
name <- obj .: "label"
value <- obj .: "value"
children <- obj .: "children"
pure $ TreeNode {name, value, children}
pure $ TreeNode { name, value, children }
treeNode :: String -> Int -> Array TreeNode -> TreeNode
treeNode n v ts = TreeNode {name : n, value:v, children:ts}
treeNode n v ts = TreeNode { name: n, value: v, children: ts }
treeLeaf :: String -> Int -> TreeNode
treeLeaf n v = TreeNode { name : n, value : v, children : []}
-- | TODO
-- https://ecomfe.github.io/echarts-examples/public/data/asset/data/life-expectancy-table.json
-- https://ecomfe.github.io/echarts-examples/public/editor.html?c=scatter3D-dataset&gl=1
treeLeaf n v = TreeNode { name: n, value: v, children: [] }
-- | TODO -- https://ecomfe.github.io/echarts-examples/public/data/asset/data/life-expectancy-table.json -- https://ecomfe.github.io/echarts-examples/public/editor.html?c=scatter3D-dataset&gl=1
\ No newline at end of file
module Gargantext.Components.Charts.Options.Type where
import Prelude
import Gargantext.Components.Charts.Options.Color (Color)
import Gargantext.Components.Charts.Options.Data (DataLegend)
import Gargantext.Components.Charts.Options.Font (TextStyle, Tooltip, ToolBox)
......@@ -12,28 +11,28 @@ import Gargantext.Types (class Optional)
import React as R
import Unsafe.Coerce (unsafeCoerce)
newtype ChartAlign = ChartAlign String
newtype ChartAlign
= ChartAlign String
-- TODO: Maybe is not working here => use Optional
type Echarts =
{ option :: Option -- PropTypes.object.isRequired,
--, className :: Maybe String
--, style :: Maybe String -- objealect-black-altdarkmincnaquadahherry-blossomect,
--, theme :: Maybe String
--, group :: Maybe String
--, initOpts :: Maybe String -- PropTypes.object,
--, notMerge :: Maybe Boolean
--, lazyUpdate :: Maybe Boolean
--, loading :: Maybe Boolean
--, optsLoading :: Maybe OptsLoading -- PropTypes.object,
--, onReady :: Maybe String -- PropTypes.func,
--, resizable :: Maybe Boolean -- PropTypes.bool,
--, onEvents :: Maybe String -- PropTypes.object
type Echarts
= { option :: Option -- PropTypes.object.isRequired,
--, className :: Maybe String
--, style :: Maybe String -- objealect-black-altdarkmincnaquadahherry-blossomect,
--, theme :: Maybe String
--, group :: Maybe String
--, initOpts :: Maybe String -- PropTypes.object,
--, notMerge :: Maybe Boolean
--, lazyUpdate :: Maybe Boolean
--, loading :: Maybe Boolean
--, optsLoading :: Maybe OptsLoading -- PropTypes.object,
--, onReady :: Maybe String -- PropTypes.func,
--, resizable :: Maybe Boolean -- PropTypes.bool,
--, onEvents :: Maybe String -- PropTypes.object
}
type Option =
{ title :: Title
type Option
= { title :: Title
, legend :: Legend
, tooltip :: Tooltip
, grid :: Grid
......@@ -45,8 +44,8 @@ type Option =
, toolbox :: ToolBox
}
type Title =
{ id :: String -- None by default
type Title
= { id :: String -- None by default
, show :: Boolean -- default True
, text :: String -- default ''
, link :: String -- default ''
......@@ -74,29 +73,28 @@ type Title =
, shadowOffsetY :: Number
}
type OptsLoading =
{ text :: String
type OptsLoading
= { text :: String
, color :: Color --- color
, textColor :: Color --color
, maskColor :: Color --color
, zlevel :: Int
}
type DataZoom =
{"type" :: String
type DataZoom
= { "type" :: String
, xAxisIndex :: Int
, filterMode :: String
, start :: Int
, end :: Int
}
type Grid =
{containLabel :: Boolean
type Grid
= { containLabel :: Boolean
}
type Legend =
{
id :: String
type Legend
= { id :: String
, "type" :: LegendType
, show :: Boolean
, zlevel :: Number
......@@ -113,22 +111,22 @@ type Legend =
, itemGap :: Number
, itemWidth :: Number
, itemHeight :: Number
--, formatter :: Maybe String
--, formatter :: Maybe String
, selectedMode :: SelectedMode
, inactiveColor :: Color
--, selected :: Maybe String -- object
--, selected :: Maybe String -- object
, textStyle :: TextStyle
, "data" :: Array DataLegend
}
type AxisTick =
{ alignWithLabel :: Boolean
type AxisTick
= { alignWithLabel :: Boolean
}
data XAxis
type XAxisOptional =
( "data" :: Array String -- DataAxis
type XAxisOptional
= ( "data" :: Array String -- DataAxis
, "type" :: String
, axisTick :: AxisTick
, name :: String
......@@ -143,8 +141,8 @@ xAxis = unsafeCoerce
data YAxis
type YAxisOptional =
( "type" :: String
type YAxisOptional
= ( "type" :: String
, name :: String
, min :: Int
, position :: String
......@@ -155,8 +153,9 @@ type YAxisOptional =
yAxis :: forall o. Optional o YAxisOptional => Record o -> YAxis
yAxis = unsafeCoerce
type AxisLabel =
{ formatter :: String -- string or function
type AxisLabel
= { formatter :: String -- string or function
}
type Rich = {}
type Rich
= {}
-- | The ContextMenu component renders a generic context menu
module Gargantext.Components.ContextMenu.ContextMenu where
-- (MenuProps, Action(..), separator) where
-- (MenuProps, Action(..), separator) where
import Prelude hiding (div)
import Data.Maybe ( Maybe(..) )
import Data.Nullable ( Nullable, null, toMaybe )
import Data.Tuple.Nested ( (/\) )
import Data.Traversable ( traverse_ )
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple.Nested ((/\))
import Data.Traversable (traverse_)
import DOM.Simple as DOM
import DOM.Simple.Event as DE
import DOM.Simple.EventListener ( Callback, callback )
import DOM.Simple.EventListener (Callback, callback)
import DOM.Simple.Element as Element
import DOM.Simple.Window ( window )
import DOM.Simple.Document ( document )
import DOM.Simple.Types ( DOMRect )
import DOM.Simple.Window (window)
import DOM.Simple.Document (document)
import DOM.Simple.Types (DOMRect)
import Effect (Effect)
import FFI.Simple ((..))
import Reactix as R
import Reactix.DOM.HTML as HTML
import Gargantext.Utils.Reactix as R2
type Props t = ( x :: Number, y :: Number, setMenu :: R2.Setter (Maybe t) )
type Props t
= ( x :: Number, y :: Number, setMenu :: R2.Setter (Maybe t) )
contextMenu :: forall t. Record (Props t) -> Array R.Element -> R.Element
contextMenu = R.createElement contextMenuCpt
......@@ -33,14 +33,16 @@ contextMenuCpt = R.hooksComponent "ContextMenu" cpt
host <- R2.getPortalHost
root <- R.useRef null
rect /\ setRect <- R.useState $ \_ -> Nothing
R.useLayoutEffect1 (R.readRef root) $ do
R.useLayoutEffect1 (R.readRef root)
$ do
traverse_
(\r -> setRect (\_ -> Just (Element.boundingRect r)))
(toMaybe $ R.readRef root)
pure $ pure unit
R.useLayoutEffect2 root rect (contextMenuEffect menu.setMenu root)
let cs = [
HTML.div { className: "popover-content" }
let
cs =
[ HTML.div { className: "popover-content" }
[ HTML.div { className: "panel panel-default" }
[ HTML.ul { className: "list-group" }
children
......@@ -48,53 +50,66 @@ contextMenuCpt = R.hooksComponent "ContextMenu" cpt
]
]
pure $ R.createPortal [ elems root menu rect $ cs ] host
elems ref menu (Just rect) = HTML.div
elems ref menu (Just rect) =
HTML.div
{ ref
, className: "context-menu"
, style: position menu rect
, data: {toggle: "popover", placement: "right"}
, data: { toggle: "popover", placement: "right" }
}
elems ref _ _ = HTML.div
elems ref _ _ =
HTML.div
{ ref
, className: "context-menu"
, data: {toggle: "popover", placement: "right"}
, data: { toggle: "popover", placement: "right" }
}
contextMenuEffect
:: forall t
. R2.Setter (Maybe t)
-> R.Ref (Nullable DOM.Element)
-> Effect (Effect Unit)
contextMenuEffect setMenu rootRef =
case R.readNullableRef rootRef of
contextMenuEffect ::
forall t.
R2.Setter (Maybe t) ->
R.Ref (Nullable DOM.Element) ->
Effect (Effect Unit)
contextMenuEffect setMenu rootRef = case R.readNullableRef rootRef of
Just root -> do
let onClick = documentClickHandler setMenu root
let onScroll = documentScrollHandler setMenu
let
onClick = documentClickHandler setMenu root
let
onScroll = documentScrollHandler setMenu
DOM.addEventListener document "click" onClick
DOM.addEventListener document "scroll" onScroll
pure $ do
pure
$ do
DOM.removeEventListener document "click" onClick
DOM.removeEventListener document "scroll" onScroll
Nothing -> pure R.nothing
documentClickHandler :: forall t. R2.Setter (Maybe t) -> DOM.Element -> Callback DE.MouseEvent
documentClickHandler hide menu =
R2.named "hideMenuOnClickOutside" $ callback $ \e ->
if Element.contains menu (DE.target e)
then pure unit
else hide (const Nothing)
R2.named "hideMenuOnClickOutside" $ callback
$ \e ->
if Element.contains menu (DE.target e) then
pure unit
else
hide (const Nothing)
documentScrollHandler :: forall t. R2.Setter (Maybe t) -> Callback DE.MouseEvent
documentScrollHandler hide =
R2.named "hideMenuOnScroll" $ callback $ \e -> hide (const Nothing)
documentScrollHandler hide = R2.named "hideMenuOnScroll" $ callback $ \e -> hide (const Nothing)
position :: forall t. Record (Props t) -> DOMRect -> { left :: Number, top :: Number }
position mouse {width: menuWidth, height: menuHeight} = {left, top}
where left = if isRight then mouse.x else mouse.x - menuWidth
position mouse { width: menuWidth, height: menuHeight } = { left, top }
where
left = if isRight then mouse.x else mouse.x - menuWidth
top = if isAbove then mouse.y else mouse.y - menuHeight
isRight = screenWidth - mouse.x > menuWidth -- is there enough space to show above
isAbove = screenHeight - mouse.y > menuHeight -- is there enough space to show to the right?
screenWidth = window .. "innerWidth"
screenHeight = window .. "innerHeight"
contextMenuItem :: Array R.Element -> R.Element
......@@ -104,21 +119,4 @@ contextMenuItemCpt :: R.Component ()
contextMenuItemCpt = R.hooksComponent "ContextMenuItem" cpt
where
cpt _props children = pure $ HTML.li { className: "context-menu-item" } children
-- -- CSS Classes
-- menuClass :: String
-- menuClass = "context-menu"
-- menuShownClass :: String
-- menuShownClass = "context-menu-shown"
-- menuHiddenClass :: String
-- menuHiddenClass = "context-menu-hidden"
-- itemClass :: String
-- itemClass = "context-menu-item"
-- separatorClass :: String
-- separatorClass = "context-menu-item"
-- -- CSS Classes -- menuClass :: String -- menuClass = "context-menu" -- menuShownClass :: String -- menuShownClass = "context-menu-shown" -- menuHiddenClass :: String -- menuHiddenClass = "context-menu-hidden" -- itemClass :: String -- itemClass = "context-menu-item" -- separatorClass :: String -- separatorClass = "context-menu-item"
\ No newline at end of file
-- | The SimpleItem is a simple context menu item consisting of a link
-- | It handles automatically closing the context menu for you
module Gargantext.Component.ContextMenu.SimpleItem where
-- (MenuProps, Action(..), separator) where
-- (MenuProps, Action(..), separator) where
import Prelude hiding (div)
-- separator :: ReactElement
-- separator = div [ className "context-menu-separator" ] []
-- type State' = { open :: Boolean }
-- newtype State = State State'
-- defaultState :: State
-- defaultState = State { open: false }
-- type MenuProps = { classes :: String }
-- type ItemProps p = { hideMenu :: Effect () | p }
-- data Action = Show | Hide
-- menuClass :: String
-- menuClass = "context-menu"
-- menuShownClass :: String
-- menuShownClass = "context-menu-shown"
-- menuHiddenClass :: String
-- menuHiddenClass = "context-menu-hidden"
-- itemClass :: String
-- itemClass = "context-menu-item"
-- contextMenu :: MenuProps -> Array ReactElement -> ReactElement
-- contextMenu = createElement contextMenuClass
-- -- TODO: register callbacks
-- componentDidMount :: Effect Unit
-- componentDidMount = pure unit
-- -- TODO: unregister callbacks
-- componentWillUnmount :: Effect Unit
-- componentWillUnmount = pure unit
-- --
-- childRender :: forall s p a. Spec s p a -> Spec s p a
-- childRender = over _render (\c -> wrapItem <<< c)
-- -- | Wraps an item in an li tag with the item classname
-- wrapItem :: ReactElement -> ReactElement
-- wrapItem = wrap $ li [ className itemClass ]
-- renderMenuItem :: Render State MenuItem Action MenuItem
-- renderMenuItem _ Separator _ _ = li [ className "menu-item-separator" ]
-- renderMenuItem d (MenuItem i) _ _ = wrap outer inner
-- where outer = li [ className "context-menu-item" ]
-- inner = a [ onClick callback, style i.style ] [text i.label]
-- callback _ = d Hide *> i.callback
-- -- TODO: Aria and accessibility
-- renderMenu :: Render State MenuProps Action
-- renderMenu d m s c = pure $ wrap outer $ ul' inner
-- where outer = div [className (classes s.open m.classes)]
-- inner = map (\i -> renderMenuItem d i ) c
-- visibilityClass :: Boolean -> String
-- visibilityClass true = contextMenuShown
-- visibilityClass false = contextMenuHidden
-- classes :: Boolean -> String -> String
-- classes visible user = joinWith " " [menuClass, visibilityClass visible, user]
-- -- Class
-- contextMenuClass :: ReactClass (WithChildren State')
-- contextMenuClass = component "ContextMenu" createContextMenuClass
-- createContextMenuClass ::
-- forall given snapshot spec.
-- ReactComponentSpec MenuProps State snapshot given spec
-- => ReactClassConstructor MenuProps State given
-- -> ReactClass MenuProps
-- createContextMenuClass this = pure
-- { state: defaultState
-- , render: renderMenu
-- , componentDidMount: componentDidMount
-- , componentWillUnmount: componentWillUnmount
-- }
-- type Label = String
-- type ClassName = String
-- -- Items
-- simpleItem :: Label -> ClassName -> Effect Unit -> Effect Unit -> ReactElement
-- simpleItem label cls cb hide = a [ onClick (hide *> cb), className cls ] [ text label ]
-- separator :: ReactElement
-- separator = li [ className "menu-item-separator" ] []
-- separator :: ReactElement -- separator = div [ className "context-menu-separator" ] [] -- type State' = { open :: Boolean } -- newtype State = State State' -- defaultState :: State -- defaultState = State { open: false } -- type MenuProps = { classes :: String } -- type ItemProps p = { hideMenu :: Effect () | p } -- data Action = Show | Hide -- menuClass :: String -- menuClass = "context-menu" -- menuShownClass :: String -- menuShownClass = "context-menu-shown" -- menuHiddenClass :: String -- menuHiddenClass = "context-menu-hidden" -- itemClass :: String -- itemClass = "context-menu-item" -- contextMenu :: MenuProps -> Array ReactElement -> ReactElement -- contextMenu = createElement contextMenuClass -- -- TODO: register callbacks -- componentDidMount :: Effect Unit -- componentDidMount = pure unit -- -- TODO: unregister callbacks -- componentWillUnmount :: Effect Unit -- componentWillUnmount = pure unit -- -- -- childRender :: forall s p a. Spec s p a -> Spec s p a -- childRender = over _render (\c -> wrapItem <<< c) -- -- | Wraps an item in an li tag with the item classname -- wrapItem :: ReactElement -> ReactElement -- wrapItem = wrap $ li [ className itemClass ] -- renderMenuItem :: Render State MenuItem Action MenuItem -- renderMenuItem _ Separator _ _ = li [ className "menu-item-separator" ] -- renderMenuItem d (MenuItem i) _ _ = wrap outer inner -- where outer = li [ className "context-menu-item" ] -- inner = a [ onClick callback, style i.style ] [text i.label] -- callback _ = d Hide *> i.callback -- -- TODO: Aria and accessibility -- renderMenu :: Render State MenuProps Action -- renderMenu d m s c = pure $ wrap outer $ ul' inner -- where outer = div [className (classes s.open m.classes)] -- inner = map (\i -> renderMenuItem d i ) c -- visibilityClass :: Boolean -> String -- visibilityClass true = contextMenuShown -- visibilityClass false = contextMenuHidden -- classes :: Boolean -> String -> String -- classes visible user = joinWith " " [menuClass, visibilityClass visible, user] -- -- Class -- contextMenuClass :: ReactClass (WithChildren State') -- contextMenuClass = component "ContextMenu" createContextMenuClass -- createContextMenuClass :: -- forall given snapshot spec. -- ReactComponentSpec MenuProps State snapshot given spec -- => ReactClassConstructor MenuProps State given -- -> ReactClass MenuProps -- createContextMenuClass this = pure -- { state: defaultState -- , render: renderMenu -- , componentDidMount: componentDidMount -- , componentWillUnmount: componentWillUnmount -- } -- type Label = String -- type ClassName = String -- -- Items -- simpleItem :: Label -> ClassName -> Effect Unit -> Effect Unit -> ReactElement -- simpleItem label cls cb hide = a [ onClick (hide *> cb), className cls ] [ text label ] -- separator :: ReactElement -- separator = li [ className "menu-item-separator" ] []
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -4,7 +4,6 @@ import Reactix as R
import Reactix.DOM.HTML as H
-- TODO : get REST informations
folder :: {} -> R.Element
folder props = R.createElement folderCpt props []
......@@ -14,5 +13,5 @@ folderCpt = R.staticComponent "G.C.Folder.folder" cpt
cpt _ _ =
R.fragment
[ H.h1 {} [ H.text "Folder" ]
, H.text "Some description of the folder here" ]
, H.text "Some description of the folder here"
]
......@@ -10,8 +10,8 @@ import Gargantext.Sessions (Session(..), Sessions, unSessions)
import Gargantext.Components.Tree (treeView)
import Gargantext.Utils.Reactix as R2
type Props =
( sessions :: Sessions
type Props
= ( sessions :: Sessions
, route :: AppRoute
, frontends :: Frontends
, showLogin :: R2.Setter Boolean
......@@ -21,24 +21,26 @@ forest :: Record Props -> R.Element
forest props = R.createElement forestCpt props []
forestCpt :: R.Component Props
forestCpt = R.staticComponent "G.C.Forest.forest" cpt where
cpt {sessions, route, frontends, showLogin} _ =
R.fragment [ plus showLogin, trees ]
forestCpt = R.staticComponent "G.C.Forest.forest" cpt
where
trees =
case unSessions sessions of
cpt { sessions, route, frontends, showLogin } _ = R.fragment [ plus showLogin, trees ]
where
trees = case unSessions sessions of
Nothing -> R.fragment []
Just s@(Session {treeId}) ->
Just s@(Session { treeId }) ->
R.fragment
[ treeView { root: treeId
[ treeView
{ root: treeId
, frontends
, mCurrentRoute: Just route
, session: s }
, session: s
}
]
plus :: R2.Setter Boolean -> R.Element
plus showLogin = H.button {on: {click}}
[ H.i { className: "glyphicon glyphicon-log-in"}
plus showLogin =
H.button { on: { click } }
[ H.i { className: "glyphicon glyphicon-log-in" }
[]
]
where
......
......@@ -4,19 +4,19 @@ import Reactix as R
import Reactix.DOM.HTML as H
clearfix :: {} -> R.Element
clearfix _ = H.div {className: "clearfix"} []
clearfix _ = H.div { className: "clearfix" } []
formGroup :: Array R.Element -> R.Element
formGroup = H.div {className: "form-group"}
formGroup = H.div { className: "form-group" }
center :: Array R.Element -> R.Element
center = H.div {className: "center"}
center = H.div { className: "center" }
card :: Array R.Element -> R.Element
card = H.div {className: "card"}
card = H.div { className: "card" }
cardBlock :: Array R.Element -> R.Element
cardBlock = H.div {className: "card-block"}
cardBlock = H.div { className: "card-block" }
cardGroup :: Array R.Element -> R.Element
cardGroup = H.div {className: "card-group"}
cardGroup = H.div { className: "card-group" }
module Gargantext.Components.Graph
-- ( graph, graphCpt
module Gargantext.Components.Graph -- ( graph, graphCpt
-- , sigmaSettings, SigmaSettings, SigmaOptionalSettings
-- , forceAtlas2Settings, ForceAtlas2Settings, ForceAtlas2OptionalSettings
-- )
where
import Prelude (bind, discard, pure, ($))
import Data.Maybe (Maybe)
import Data.Nullable (null)
......@@ -12,22 +12,26 @@ import Reactix.DOM.HTML as RH
import Gargantext.Hooks.Sigmax
import Gargantext.Hooks.Sigmax.Types as Sigmax
type OnProps = ()
type OnProps
= ()
type Node =
( id :: String
type Node
= ( id :: String
, label :: String
, x :: Number
, y :: Number
, size :: Number
, color :: String )
, color :: String
)
type Edge = ( id :: String, source :: String, target :: String )
type Edge
= ( id :: String, source :: String, target :: String )
type Graph = Sigmax.Graph Node Edge
type Graph
= Sigmax.Graph Node Edge
type Props sigma forceatlas2 =
( graph :: Graph
type Props sigma forceatlas2
= ( graph :: Graph
, forceAtlas2Settings :: forceatlas2
, sigmaSettings :: sigma
, sigmaRef :: R.Ref (Maybe Sigma)
......@@ -42,11 +46,10 @@ graphCpt = R.hooksComponent "Graph" cpt
cpt props _ = do
ref <- R.useRef null
startSigma ref props.sigmaRef props.sigmaSettings props.forceAtlas2Settings props.graph
pure $ RH.div { ref, style: { height: "95%" } } []
pure $ RH.div { ref, style: {height: "95%"} } []
type SigmaSettings =
( animationsTime :: Number
type SigmaSettings
= ( animationsTime :: Number
, autoRescale :: Boolean
, autoResize :: Boolean
, batchEdgesDrawing :: Boolean
......@@ -135,9 +138,9 @@ type SigmaSettings =
, zoomingRatio :: Number
)
-- not selected <=> (1-greyness)
-- selected nodes <=> special label
sigmaSettings :: {|SigmaSettings}
-- not selected <=> (1-greyness)
-- selected nodes <=> special label
sigmaSettings :: { | SigmaSettings }
sigmaSettings =
{ animationsTime: 5500.0
, autoRescale: true
......@@ -160,7 +163,7 @@ sigmaSettings =
, font: "Droid Sans" -- font params
, fontStyle: "bold"
, hideEdgesOnMove: true
, labelSize : "fixed"
, labelSize: "fixed"
, labelSizeRatio: 2.0 -- label size in ratio of node size
, labelThreshold: 2.0 -- min node cam size to start showing label
, maxEdgeSize: 1.0
......@@ -181,14 +184,14 @@ sigmaSettings =
, twNodeRendBorderSize: 0.5 -- node borders (only iff ourRendering)
, twNodesGreyOpacity: 5.5 -- smaller value: more grey
, twSelectedColor: "default" -- "node" for a label bg like the node color, "default" for white background
, verbose : true
, verbose: true
, zoomMax: 1.7
, zoomMin: 0.0
, zoomingRatio: 3.2
}
type ForceAtlas2Settings =
( adjustSizes :: Boolean
type ForceAtlas2Settings
= ( adjustSizes :: Boolean
, barnesHutOptimize :: Boolean
-- , barnesHutTheta :: Number
, edgeWeightInfluence :: Number
......@@ -206,19 +209,19 @@ type ForceAtlas2Settings =
-- , worker :: Boolean
)
forceAtlas2Settings :: {|ForceAtlas2Settings}
forceAtlas2Settings :: { | ForceAtlas2Settings }
forceAtlas2Settings =
{ adjustSizes : false
, barnesHutOptimize : true
, edgeWeightInfluence : 0.0
{ adjustSizes: false
, barnesHutOptimize: true
, edgeWeightInfluence: 0.0
-- fixedY : false
, gravity : 1.0
, iterationsPerRender : 4.0
, linLogMode : true -- false
, gravity: 1.0
, iterationsPerRender: 4.0
, linLogMode: true -- false
, outboundAttractionDistribution: false
, scalingRatio : 4.0
, scalingRatio: 4.0
, skipHidden: false
, slowDown : 0.7
, startingIterations : 2.0
, strongGravityMode : false
, slowDown: 0.7
, startingIterations: 2.0
, strongGravityMode: false
}
module Gargantext.Components.GraphExplorer where
import Gargantext.Prelude hiding (max,min)
import Gargantext.Prelude hiding (max, min)
import Data.FoldableWithIndex (foldMapWithIndex)
import Data.Foldable (foldMap)
import Data.Int (toNumber)
import Data.Maybe (Maybe(..))
import Data.Sequence as Seq
import Data.Tuple (fst,snd)
import Data.Tuple (fst, snd)
import Data.Tuple.Nested ((/\))
import Effect.Aff (Aff)
import Reactix as R
import Reactix.DOM.HTML as RH
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Hooks.Sigmax (Sigma)
import Gargantext.Hooks.Sigmax.Types as Sigmax
......@@ -28,18 +26,19 @@ import Gargantext.Routes (SessionRoute(NodeAPI), AppRoute)
import Gargantext.Sessions (Session, Sessions(..))
import Gargantext.Types (NodeType(Graph))
type GraphId = Int
type GraphId
= Int
type LayoutProps =
( graphId :: GraphId
type LayoutProps
= ( graphId :: GraphId
, mCurrentRoute :: AppRoute
, treeId :: Maybe Int
, session :: Session
, frontends :: Frontends
)
type Props = ( graph :: Maybe Graph.Graph | LayoutProps )
type Props
= ( graph :: Maybe Graph.Graph | LayoutProps )
--------------------------------------------------------------
explorerLayout :: Record LayoutProps -> R.Element
......@@ -48,11 +47,11 @@ explorerLayout props = R.createElement explorerLayoutCpt props []
explorerLayoutCpt :: R.Component LayoutProps
explorerLayoutCpt = R.hooksComponent "G.C.GraphExplorer.explorerLayout" cpt
where
cpt {graphId, mCurrentRoute, treeId, session, frontends} _ =
useLoader graphId (getNodes session) handler
cpt { graphId, mCurrentRoute, treeId, session, frontends } _ = useLoader graphId (getNodes session) handler
where
handler loaded = explorer { graphId, mCurrentRoute, treeId, session, graph, frontends }
where
handler loaded = explorer {graphId, mCurrentRoute, treeId, session, graph, frontends}
where graph = Just (convert loaded)
graph = Just (convert loaded)
--------------------------------------------------------------
explorer :: Record Props -> R.Element
......@@ -61,12 +60,12 @@ explorer props = R.createElement explorerCpt props []
explorerCpt :: R.Component Props
explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt
where
cpt {session, graphId, mCurrentRoute, treeId, graph, frontends} _ = do
cpt { session, graphId, mCurrentRoute, treeId, graph, frontends } _ = do
controls <- Controls.useGraphControls
state <- useExplorerState
showLogin <- snd <$> R.useState' true
pure $
RH.div
pure
$ RH.div
{ id: "graph-explorer" }
[ row
[ outer
......@@ -77,35 +76,46 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt
, col [ pullRight [ Toggle.sidebarToggleButton controls.showSidePanel ] ]
]
, row [ Controls.controls controls ]
, row [ tree {mCurrentRoute, treeId} controls showLogin
, mGraph controls.sigmaRef {graphId, graph}
, Sidebar.sidebar {showSidePanel: fst controls.showSidePanel} ]
, row [ ]
, row
[ tree { mCurrentRoute, treeId } controls showLogin
, mGraph controls.sigmaRef { graphId, graph }
, Sidebar.sidebar { showSidePanel: fst controls.showSidePanel }
]
, row []
]
]
]
]
where
-- tree {treeId: Nothing} _ _ = RH.div { id: "tree" } []
tree _ {showTree: false /\ _} _ = RH.div { id: "tree" } []
tree {mCurrentRoute: m, treeId: root} _ showLogin=
RH.div {className: "col-md-2", style: {paddingTop: "60px"}}
[forest {sessions: Sessions (Just session), route:m, frontends, showLogin}]
tree _ { showTree: false /\ _ } _ = RH.div { id: "tree" } []
tree { mCurrentRoute: m, treeId: root } _ showLogin =
RH.div { className: "col-md-2", style: { paddingTop: "60px" } }
[ forest { sessions: Sessions (Just session), route: m, frontends, showLogin } ]
outer = RH.div { className: "col-md-12" }
inner = RH.div { className: "container-fluid", style: { paddingTop: "90px" } }
row1 = RH.div { className: "row", style: { paddingBottom: "10px", marginTop: "-24px" } }
row = RH.div { className: "row" }
col = RH.div { className: "col-md-4" }
pullLeft = RH.div { className: "pull-left" }
pullRight = RH.div { className: "pull-right" }
mGraph :: R.Ref (Maybe Sigma) -> { graphId :: GraphId, graph :: Maybe Graph.Graph } -> R.Element
mGraph _ { graph: Nothing } = RH.div {} []
mGraph :: R.Ref (Maybe Sigma) -> {graphId :: GraphId, graph :: Maybe Graph.Graph} -> R.Element
mGraph _ {graph: Nothing} = RH.div {} []
mGraph sigmaRef {graphId, graph: Just graph} = graphView sigmaRef {graphId, graph}
mGraph sigmaRef { graphId, graph: Just graph } = graphView sigmaRef { graphId, graph }
useExplorerState :: R.Hooks (Record GET.State)
useExplorerState = do pure {}
{- corpusId <- R.useState' 0
cursorSize <- R.useState' 0.0
filePath <- R.useState' ""
......@@ -119,13 +129,11 @@ useExplorerState = do pure {}
sigmaGraphData <- R.useState' (Nothing :: Maybe Graph.Graph)
sigmaSettings <- R.useState' Graph.sigmaSettings
treeId <- R.useState' (Nothing :: Maybe TreeId) -}
--treeId : Nothing
type GraphProps = (
graphId :: GraphId
--treeId : Nothing
type GraphProps
= ( graphId :: GraphId
, graph :: Graph.Graph
)
)
graphView :: R.Ref (Maybe Sigma) -> Record GraphProps -> R.Element
--graphView sigmaRef props = R.createElement (R.memo el memoCmp) props []
......@@ -133,12 +141,12 @@ graphView sigmaRef props = R.createElement el props []
where
--memoCmp props1 props2 = props1.graphId == props2.graphId
el = R.hooksComponent "GraphView" cpt
cpt {graphId, graph} _children = do
pure $
RH.div { id: "graph-view", className: "col-md-12" }
[
Graph.graph {
forceAtlas2Settings: Graph.forceAtlas2Settings
cpt { graphId, graph } _children = do
pure
$ RH.div { id: "graph-view", className: "col-md-12" }
[ Graph.graph
{ forceAtlas2Settings: Graph.forceAtlas2Settings
, graph
, sigmaSettings: Graph.sigmaSettings
, sigmaRef: sigmaRef
......@@ -146,29 +154,31 @@ graphView sigmaRef props = R.createElement el props []
]
convert :: GET.GraphData -> Graph.Graph
convert (GET.GraphData r) = Sigmax.Graph {nodes, edges}
convert (GET.GraphData r) = Sigmax.Graph { nodes, edges }
where
nodes = foldMapWithIndex nodeFn r.nodes
nodeFn i (GET.Node n) =
Seq.singleton
{ id : n.id_
, size : toNumber n.size
, label : n.label
, x : n.x -- cos (toNumber i)
, y : n.y -- sin (toNumber i)
, color : GET.intColor (cDef n.attributes)
{ id: n.id_
, size: toNumber n.size
, label: n.label
, x: n.x -- cos (toNumber i)
, y: n.y -- sin (toNumber i)
, color: GET.intColor (cDef n.attributes)
}
where
cDef (GET.Cluster {clustDefault}) = clustDefault
cDef (GET.Cluster { clustDefault }) = clustDefault
edges = foldMap edgeFn r.edges
edgeFn (GET.Edge e) = Seq.singleton {id : e.id_, source : e.source, target : e.target}
edgeFn (GET.Edge e) = Seq.singleton { id: e.id_, source: e.source, target: e.target }
defaultPalette :: Array String
defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff","#b399df","#ffdfed","#33c8f3","#739e9a","#caeca3","#f6f7e5","#f9bcca","#ccb069","#c9ffde","#c58683","#6c9eb0","#ffd3cf","#ccffc7","#52a1b0","#d2ecff","#99fffe","#9295ae","#5ea38b","#fff0b3","#d99e68"]
defaultPalette = [ "#5fa571", "#ab9ba2", "#da876d", "#bdd3ff", "#b399df", "#ffdfed", "#33c8f3", "#739e9a", "#caeca3", "#f6f7e5", "#f9bcca", "#ccb069", "#c9ffde", "#c58683", "#6c9eb0", "#ffd3cf", "#ccffc7", "#52a1b0", "#d2ecff", "#99fffe", "#9295ae", "#5ea38b", "#fff0b3", "#d99e68" ]
-- clusterColor :: Cluster -> Color
-- clusterColor (Cluster {clustDefault}) = unsafePartial $ fromJust $ defaultPalette !! (clustDefault `mod` length defaultPalette)
-- div [className "col-md-12", style {"padding-bottom" : "10px"}]
-- [ menu [_id "toolbar"]
-- [ ul'
......@@ -182,7 +192,6 @@ defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff","#b399df","#ffdfed","#
-- , name "file"
-- -- , onChange (\e -> d $ SetFile (getFile e) (unsafeCoerce $ d <<< SetProgress))
-- , className "btn btn-primary"]
-- -- , text $ show st.readyState
-- ]
-- ]
......@@ -209,7 +218,6 @@ defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff","#b399df","#ffdfed","#
-- , input [_type "text", className "form-control", placeholder "select topics"]
-- ]
-- ]
-- ]
-- ]
-- -}
......@@ -232,7 +240,6 @@ defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff","#b399df","#ffdfed","#
-- , onChange \e -> d $ ChangeLabelSize (numberTargetValue e)
-- ]
-- ]
-- , li [className "col-md-1"]
-- [ span [] [text "Nodes"],input [_type "range"
-- , _id "nodeSizeRange"
......@@ -286,7 +293,5 @@ defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff","#b399df","#ffdfed","#
-- -}
-- ]
-- ]
getNodes :: Session -> GraphId -> Aff GET.GraphData
getNodes session graphId = get $ url session $ NodeAPI Graph (Just graphId)
......@@ -10,12 +10,11 @@ import DOM.Simple.Console (log2)
import Effect (Effect)
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
type Props = (
onClick :: forall e. e -> Effect Unit
type Props
= ( onClick :: forall e. e -> Effect Unit
, text :: String
)
......@@ -25,22 +24,24 @@ simpleButton props = R.createElement simpleButtonCpt props []
simpleButtonCpt :: R.Component Props
simpleButtonCpt = R.hooksComponent "SimpleButton" cpt
where
cpt {onClick, text} _ = do
pure $
H.span {}
[
H.button
{ className: "btn btn-primary", on: {click: onClick} }
cpt { onClick, text } _ = do
pure
$ H.span {}
[ H.button
{ className: "btn btn-primary", on: { click: onClick } }
[ H.text text ]
]
centerButton :: R.Ref (Maybe Sigmax.Sigma) -> R.Element
centerButton sigmaRef = simpleButton {
onClick: \_ -> do
let mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
centerButton sigmaRef =
simpleButton
{ onClick:
\_ -> do
let
mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
log2 "[centerButton] mSigma" mSigma
case mSigma of
Just (Just s) -> Sigma.goToAllCameras s {x: 0.0, y: 0.0, ratio: 1.0, angle: 0.0}
Just (Just s) -> Sigma.goToAllCameras s { x: 0.0, y: 0.0, ratio: 1.0, angle: 0.0 }
_ -> pure unit
, text: "Center"
}
......@@ -4,11 +4,16 @@ module Gargantext.Components.GraphExplorer.Controls
, useGraphControls
, controls
, controlsCpt
, getShowTree, setShowTree
, getShowControls, setShowControls
, getShowSidePanel, setShowSidePanel
, getCursorSize, setCursorSize
, getMultiNodeSelect, setMultiNodeSelect
, getShowTree
, setShowTree
, getShowControls
, setShowControls
, getShowSidePanel
, setShowSidePanel
, getCursorSize
, setCursorSize
, getMultiNodeSelect
, setMultiNodeSelect
) where
import Data.Maybe (Maybe)
......@@ -17,7 +22,6 @@ import Effect (Effect)
import Prelude
import Reactix as R
import Reactix.DOM.HTML as RH
import Gargantext.Components.Graph as Graph
import Gargantext.Components.GraphExplorer.Button (centerButton)
import Gargantext.Components.GraphExplorer.RangeControl (edgeSizeControl, nodeSizeControl)
......@@ -27,9 +31,8 @@ import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Utils.Range as Range
import Gargantext.Utils.Reactix as R2
type Controls =
( cursorSize :: R.State Number
type Controls
= ( cursorSize :: R.State Number
, multiNodeSelect :: R.Ref Boolean
, showControls :: R.State Boolean
, showSidePanel :: R.State Boolean
......@@ -38,10 +41,10 @@ type Controls =
)
controlsToSigmaSettings :: Record Controls -> Record Graph.SigmaSettings
controlsToSigmaSettings { cursorSize: (cursorSize /\ _)} = Graph.sigmaSettings
controlsToSigmaSettings { cursorSize: (cursorSize /\ _) } = Graph.sigmaSettings
type LocalControls =
( edgeSize :: R.State Range.NumberRange
type LocalControls
= ( edgeSize :: R.State Range.NumberRange
, labelSize :: R.State Number
, nodeSize :: R.State Range.NumberRange
, pauseForceAtlas :: R.State Boolean
......@@ -55,9 +58,8 @@ initialLocalControls = do
nodeSize <- R.useState' $ Range.Closed { min: 5.0, max: 10.0 }
pauseForceAtlas <- R.useState' true
showEdges <- R.useState' true
pure $ {
edgeSize
pure
$ { edgeSize
, labelSize
, nodeSize
, pauseForceAtlas
......@@ -72,14 +74,14 @@ controlsCpt = R.hooksComponent "GraphControls" cpt
where
cpt props _ = do
localControls <- initialLocalControls
pure $ case getShowControls props of
pure
$ case getShowControls props of
false -> RH.div {} []
true -> RH.div { className: "col-md-12", style: { paddingBottom: "10px" } }
true ->
RH.div { className: "col-md-12", style: { paddingBottom: "10px" } }
[ R2.menu { id: "toolbar" }
[ RH.ul {}
[ -- change type button (?)
RH.li {} [ centerButton props.sigmaRef ]
[ -- change type button (?) RH.li {} [ centerButton props.sigmaRef ]
, RH.li {} [ pauseForceAtlasButton props.sigmaRef localControls.pauseForceAtlas ] -- spatialization (pause ForceAtlas2)
, RH.li {} [ edgesToggleButton props.sigmaRef localControls.showEdges ]
, RH.li {} [ edgeSizeControl props.sigmaRef localControls.edgeSize ] -- edge size : 0-3
......@@ -106,8 +108,8 @@ useGraphControls = do
showSidePanel <- R.useState' false
showTree <- R.useState' false
sigmaRef <- R2.nothingRef
pure { cursorSize
pure
{ cursorSize
, multiNodeSelect
, showControls
, showSidePanel
......@@ -116,31 +118,31 @@ useGraphControls = do
}
getShowControls :: Record Controls -> Boolean
getShowControls { showControls: ( should /\ _ ) } = should
getShowControls { showControls: (should /\ _) } = should
getShowSidePanel :: Record Controls -> Boolean
getShowSidePanel { showSidePanel: ( should /\ _ ) } = should
getShowSidePanel { showSidePanel: (should /\ _) } = should
getShowTree :: Record Controls -> Boolean
getShowTree { showTree: ( should /\ _ ) } = should
getShowTree { showTree: (should /\ _) } = should
getCursorSize :: Record Controls -> Number
getCursorSize { cursorSize: ( size /\ _ ) } = size
getCursorSize { cursorSize: (size /\ _) } = size
getMultiNodeSelect :: Record Controls -> Boolean
getMultiNodeSelect { multiNodeSelect } = R.readRef multiNodeSelect
setShowControls :: Record Controls -> Boolean -> Effect Unit
setShowControls { showControls: ( _ /\ set ) } v = set $ const v
setShowControls { showControls: (_ /\ set) } v = set $ const v
setShowSidePanel :: Record Controls -> Boolean -> Effect Unit
setShowSidePanel { showSidePanel: ( _ /\ set ) } v = set $ const v
setShowSidePanel { showSidePanel: (_ /\ set) } v = set $ const v
setShowTree :: Record Controls -> Boolean -> Effect Unit
setShowTree { showTree: ( _ /\ set ) } v = set $ not <<< const v
setShowTree { showTree: (_ /\ set) } v = set $ not <<< const v
setCursorSize :: Record Controls -> Number -> Effect Unit
setCursorSize { cursorSize: ( _ /\ setSize ) } v = setSize $ const v
setCursorSize { cursorSize: (_ /\ setSize) } v = setSize $ const v
setMultiNodeSelect :: Record Controls -> Boolean -> Effect Unit
setMultiNodeSelect { multiNodeSelect } = R.setRef multiNodeSelect
......@@ -7,7 +7,8 @@ import Prelude
import Reactix as R
import Reactix.DOM.HTML as H
type Props = ( state :: R.State Boolean )
type Props
= ( state :: R.State Boolean )
controlsToggleButton :: Record Props -> R.Element
controlsToggleButton props = R.createElement controlsToggleButtonCpt props []
......@@ -15,11 +16,14 @@ controlsToggleButton props = R.createElement controlsToggleButtonCpt props []
controlsToggleButtonCpt :: R.Component Props
controlsToggleButtonCpt = R.hooksComponent "GraphControlsToggleButton" cpt
where
cpt {state} _ = do
let (open /\ setOpen) = state
pure $
H.button
{ className: "btn btn-primary", on: {click: \_ -> setOpen not } }
cpt { state } _ = do
let
(open /\ setOpen) = state
pure
$ H.button
{ className: "btn btn-primary", on: { click: \_ -> setOpen not } }
[ H.text (text open) ]
text true = "Hide Controls"
text false = "Show Controls"
......@@ -3,14 +3,14 @@ module Gargantext.Components.GraphExplorer.Legend
) where
import Prelude hiding (map)
import Data.Sequence (Seq)
import Data.Traversable (foldMap)
import Reactix as R
import Reactix.DOM.HTML as RH
import Gargantext.Components.GraphExplorer.Types (Legend(..), intColor)
type Props = ( items :: Seq Legend )
type Props
= ( items :: Seq Legend )
legend :: Record Props -> R.Element
legend props = R.createElement legendCpt props []
......@@ -18,10 +18,10 @@ legend props = R.createElement legendCpt props []
legendCpt :: R.Component Props
legendCpt = R.hooksComponent "Legend" cpt
where
cpt {items} _ = pure $ RH.div {} [foldMap entry items]
cpt { items } _ = pure $ RH.div {} [ foldMap entry items ]
entry :: Legend -> R.Element
entry (Legend {id_, label}) =
entry (Legend { id_, label }) =
RH.p {}
[ RH.span { style: { width: 10, height: 10, backgroundColor: intColor id_, display: "inline-block" } } []
, RH.text $ " " <> label
......
......@@ -10,14 +10,13 @@ import Data.Maybe (Maybe(..))
import Data.Tuple.Nested ((/\))
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Components.RangeSlider as RS
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
import Gargantext.Utils.Range as Range
type Props = (
caption :: String
type Props
= ( caption :: String
, sliderProps :: Record RS.Props
)
......@@ -27,29 +26,32 @@ rangeControl props = R.createElement rangeControlCpt props []
rangeControlCpt :: R.Component Props
rangeControlCpt = R.hooksComponent "RangeButton" cpt
where
cpt {caption, sliderProps} _ = do
pure $
H.span {className: "range text-center"}
cpt { caption, sliderProps } _ = do
pure
$ H.span { className: "range text-center" }
[ H.label {} [ H.text caption ]
, RS.rangeSlider sliderProps
]
edgeSizeControl :: R.Ref (Maybe Sigmax.Sigma) -> R.State Range.NumberRange -> R.Element
edgeSizeControl sigmaRef (state /\ setState) =
rangeControl {
caption: "Edge Size"
, sliderProps: {
bounds: Range.Closed { min: 0.0, max: 3.0 }
rangeControl
{ caption: "Edge Size"
, sliderProps:
{ bounds: Range.Closed { min: 0.0, max: 3.0 }
, initialValue: state
, epsilon: 0.1
, step: 1.0
, width: 10.0
, height: 5.0
, onChange: \range@(Range.Closed {min, max}) -> do
let mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
, onChange:
\range@(Range.Closed { min, max }) -> do
let
mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
case mSigma of
Just (Just s) -> Sigma.setSettings s {
minEdgeSize: min
Just (Just s) ->
Sigma.setSettings s
{ minEdgeSize: min
, maxEdgeSize: max
}
_ -> pure unit
......@@ -59,20 +61,23 @@ edgeSizeControl sigmaRef (state /\ setState) =
nodeSizeControl :: R.Ref (Maybe Sigmax.Sigma) -> R.State Range.NumberRange -> R.Element
nodeSizeControl sigmaRef (state /\ setState) =
rangeControl {
caption: "Node Size"
, sliderProps: {
bounds: Range.Closed { min: 5.0, max: 15.0 }
rangeControl
{ caption: "Node Size"
, sliderProps:
{ bounds: Range.Closed { min: 5.0, max: 15.0 }
, initialValue: state
, epsilon: 0.1
, step: 1.0
, width: 10.0
, height: 5.0
, onChange: \range@(Range.Closed {min, max}) -> do
let mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
, onChange:
\range@(Range.Closed { min, max }) -> do
let
mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
case mSigma of
Just (Just s) -> Sigma.setSettings s {
minNodeSize: min
Just (Just s) ->
Sigma.setSettings s
{ minNodeSize: min
, maxNodeSize: max
}
_ -> pure unit
......
module Gargantext.Components.GraphExplorer.Sidebar
(Props, sidebar)
where
module Gargantext.Components.GraphExplorer.Sidebar (Props, sidebar) where
import Prelude
import Reactix as R
import Reactix.DOM.HTML as RH
type Props = ( showSidePanel :: Boolean )
type Props
= ( showSidePanel :: Boolean )
sidebar :: Record Props -> R.Element
sidebar props = R.createElement sidebarCpt props []
......@@ -14,41 +13,58 @@ sidebar props = R.createElement sidebarCpt props []
sidebarCpt :: R.Component Props
sidebarCpt = R.hooksComponent "Sidebar" cpt
where
cpt {showSidePanel: false} _children = do
cpt { showSidePanel: false } _children = do
pure $ RH.div {} []
cpt props _children = do
pure $
RH.div { id: "sp-container", className: "col-md-2" }
pure
$ RH.div { id: "sp-container", className: "col-md-2" }
[ RH.div {}
[ RH.div { className: "row" }
[ RH.div { className: "col-md-12" }
[ RH.ul { id: "myTab", className: "nav nav-tabs", role: "tablist"}
[ RH.ul { id: "myTab", className: "nav nav-tabs", role: "tablist" }
[ RH.li { className: "nav-item" }
[ RH.a { id: "home-tab"
[ RH.a
{ id: "home-tab"
, className: "nav-link active"
, data: {toggle: "tab"}
, data: { toggle: "tab" }
, href: "#home"
, role: "tab"
, aria: {controls: "home", selected: "true"}}
[ RH.text "Neighbours" ] ] ]
, aria: { controls: "home", selected: "true" }
}
[ RH.text "Neighbours" ]
]
]
, RH.div { className: "tab-content", id: "myTabContent" }
[ RH.div { className: "", id: "home", role: "tabpanel" }
(badge <$> badges) ] ]
(badge <$> badges)
]
]
, RH.div { className: "col-md-12", id: "horizontal-checkbox" }
[ RH.ul {}
[ checkbox "Pubs"
, checkbox "Projects"
, checkbox "Patents"
, checkbox "Others" ] ] ] ] ]
badge text =
RH.a { className: "badge badge-light" } [ RH.text text ]
, checkbox "Others"
]
]
]
]
]
badge text = RH.a { className: "badge badge-light" } [ RH.text text ]
checkbox text =
RH.li {}
[ RH.span {} [ RH.text text ]
, RH.input { type: "checkbox"
, RH.input
{ type: "checkbox"
, className: "checkbox"
, checked: true
, title: "Mark as completed" } ]
, title: "Mark as completed"
}
]
badges =
[ "objects"
, "evaluation"
......@@ -58,4 +74,5 @@ sidebarCpt = R.hooksComponent "Sidebar" cpt
, "performance analysis"
, "software engineering"
, "complex systems"
, "wireless communications" ]
, "wireless communications"
]
......@@ -13,13 +13,12 @@ import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
import Gargantext.Utils.Reactix as R2
type Props = (
state :: R.State Number
type Props
= ( state :: R.State Number
, caption :: String
, min :: Number
, max :: Number
......@@ -32,24 +31,26 @@ sizeButton props = R.createElement sizeButtonCpt props []
sizeButtonCpt :: R.Component Props
sizeButtonCpt = R.hooksComponent "SizeButton" cpt
where
cpt {state, caption, min, max, onChange} _ = do
let (value /\ setValue) = state
pure $
H.span {}
cpt { state, caption, min, max, onChange } _ = do
let
(value /\ setValue) = state
pure
$ H.span {}
[ H.label {} [ H.text caption ]
, H.input { type: "range"
, H.input
{ type: "range"
, className: "form-control"
, min: show min
, max: show max
, defaultValue: value
, on: {input: onChange}
, on: { input: onChange }
}
]
cursorSizeButton :: R.State Number -> R.Element
cursorSizeButton state =
sizeButton {
state: state
sizeButton
{ state: state
, caption: "Cursor Size"
, min: 1.0
, max: 4.0
......@@ -58,18 +59,23 @@ cursorSizeButton state =
labelSizeButton :: R.Ref (Maybe Sigmax.Sigma) -> R.State Number -> R.Element
labelSizeButton sigmaRef state =
sizeButton {
state: state
sizeButton
{ state: state
, caption: "Label Size"
, min: 5.0
, max: 30.0
, onChange: \e -> do
let mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
let newValue = readFloat $ R2.unsafeEventValue e
let (value /\ setValue) = state
, onChange:
\e -> do
let
mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
let
newValue = readFloat $ R2.unsafeEventValue e
let
(value /\ setValue) = state
case mSigma of
Just (Just s) -> Sigma.setSettings s {
defaultLabelSize: newValue
Just (Just s) ->
Sigma.setSettings s
{ defaultLabelSize: newValue
}
_ -> pure unit
setValue $ const newValue
......
module Gargantext.Components.GraphExplorer.ToggleButton
( Props, toggleButton, toggleButtonCpt
( Props
, toggleButton
, toggleButtonCpt
, controlsToggleButton
, edgesToggleButton
, sidebarToggleButton
......@@ -8,19 +10,17 @@ module Gargantext.Components.GraphExplorer.ToggleButton
) where
import Prelude
import Data.Maybe (Maybe(..))
import Data.Tuple (snd)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Sigma as Sigma
type Props = (
state :: R.State Boolean
type Props
= ( state :: R.State Boolean
, onMessage :: String
, offMessage :: String
, onClick :: forall e. e -> Effect Unit
......@@ -32,22 +32,24 @@ toggleButton props = R.createElement toggleButtonCpt props []
toggleButtonCpt :: R.Component Props
toggleButtonCpt = R.hooksComponent "ToggleButton" cpt
where
cpt {state, onMessage, offMessage, onClick} _ = do
let (toggled /\ _) = state
pure $
H.span {}
[
H.button
{ className: "btn btn-primary", on: {click: onClick} }
cpt { state, onMessage, offMessage, onClick } _ = do
let
(toggled /\ _) = state
pure
$ H.span {}
[ H.button
{ className: "btn btn-primary", on: { click: onClick } }
[ H.text (text onMessage offMessage toggled) ]
]
text on _off true = on
text _on off false = off
controlsToggleButton :: R.State Boolean -> R.Element
controlsToggleButton state =
toggleButton {
state: state
toggleButton
{ state: state
, onMessage: "Hide Controls"
, offMessage: "Show Controls"
, onClick: \_ -> snd state not
......@@ -55,17 +57,21 @@ controlsToggleButton state =
edgesToggleButton :: R.Ref (Maybe Sigmax.Sigma) -> R.State Boolean -> R.Element
edgesToggleButton sigmaRef state =
toggleButton {
state: state
toggleButton
{ state: state
, onMessage: "Hide Edges"
, offMessage: "Show Edges"
, onClick: \_ -> do
let mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
let (toggled /\ setToggled) = state
, onClick:
\_ -> do
let
mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
let
(toggled /\ setToggled) = state
case mSigma of
Just (Just s) -> do
let settings = {
drawEdges: not toggled
let
settings =
{ drawEdges: not toggled
, drawEdgeLabels: not toggled
, hideEdgesOnMove: toggled
}
......@@ -76,15 +82,19 @@ edgesToggleButton sigmaRef state =
pauseForceAtlasButton :: R.Ref (Maybe Sigmax.Sigma) -> R.State Boolean -> R.Element
pauseForceAtlasButton sigmaRef state =
toggleButton {
state: state
toggleButton
{ state: state
, onMessage: "Pause Force Atlas"
, offMessage: "Start Force Atlas"
, onClick: \_ -> do
let mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
let (toggled /\ setToggled) = state
, onClick:
\_ -> do
let
mSigma = Sigmax.readSigma <$> R.readRef sigmaRef
let
(toggled /\ setToggled) = state
case mSigma of
Just (Just s) -> if toggled then
Just (Just s) ->
if toggled then
Sigma.stopForceAtlas2 s
else
Sigma.restartForceAtlas2 s
......@@ -94,8 +104,8 @@ pauseForceAtlasButton sigmaRef state =
treeToggleButton :: R.State Boolean -> R.Element
treeToggleButton state =
toggleButton {
state: state
toggleButton
{ state: state
, onMessage: "Hide Tree"
, offMessage: "Show Tree"
, onClick: \_ -> snd state not
......@@ -103,8 +113,8 @@ treeToggleButton state =
sidebarToggleButton :: R.State Boolean -> R.Element
sidebarToggleButton state =
toggleButton {
state: state
toggleButton
{ state: state
, onMessage: "Hide Sidebar"
, offMessage: "Show Sidebar"
, onClick: \_ -> snd state not
......
......@@ -7,7 +7,8 @@ import Data.Maybe (Maybe(..), fromJust)
import Data.Newtype (class Newtype)
import Partial.Unsafe (unsafePartial)
newtype Node = Node
newtype Node
= Node
{ id_ :: String
, size :: Int
, type_ :: String
......@@ -19,11 +20,13 @@ newtype Node = Node
derive instance newtypeNode :: Newtype Node _
newtype Cluster = Cluster { clustDefault :: Int }
newtype Cluster
= Cluster { clustDefault :: Int }
derive instance newtypeCluster :: Newtype Cluster _
newtype Edge = Edge
newtype Edge
= Edge
{ id_ :: String
, source :: String
, target :: String
......@@ -34,19 +37,27 @@ newtype Edge = Edge
derive instance newtypeEdge :: Newtype Edge _
-- | A 'fully closed interval' in CS parlance
type InclusiveRange t = { min :: t, max :: t }
type InclusiveRange t
= { min :: t, max :: t }
type ListId = Int
type CorpusId = Int
type CorpusLabel = String
type ListId
= Int
newtype GraphSideCorpus = GraphSideCorpus
type CorpusId
= Int
type CorpusLabel
= String
newtype GraphSideCorpus
= GraphSideCorpus
{ corpusId :: CorpusId
, corpusLabel :: CorpusLabel
, listId :: ListId
}
newtype GraphData = GraphData
newtype GraphData
= GraphData
{ nodes :: Array Node
, edges :: Array Edge
, sides :: Array GraphSideCorpus
......@@ -55,28 +66,31 @@ newtype GraphData = GraphData
derive instance newtypeGraphData :: Newtype GraphData _
newtype MetaData = MetaData
{
title :: String
newtype MetaData
= MetaData
{ title :: String
, legend :: Array Legend
, corpusId :: Array Int
, listId :: ListId
}
getLegend :: GraphData -> Maybe (Array Legend)
getLegend (GraphData {metaData}) = (\(MetaData m) -> m.legend) <$> metaData
getLegend (GraphData { metaData }) = (\(MetaData m) -> m.legend) <$> metaData
newtype SelectedNode = SelectedNode {id :: String, label :: String}
newtype SelectedNode
= SelectedNode { id :: String, label :: String }
derive instance eqSelectedNode :: Eq SelectedNode
derive instance newtypeSelectedNode :: Newtype SelectedNode _
derive instance ordSelectedNode :: Ord SelectedNode
instance showSelectedNode :: Show SelectedNode where
show (SelectedNode node) = node.label
type State = (
type State
= (
-- corpusId :: R.State Int
--, cursorSize :: R.State Number
--, filePath :: R.State String
......@@ -93,11 +107,12 @@ type State = (
)
initialGraphData :: GraphData
initialGraphData = GraphData {
nodes: []
initialGraphData =
GraphData
{ nodes: []
, edges: []
, sides: []
, metaData : Just $ MetaData {title : "", legend : [], corpusId : [], listId : 0}
, metaData: Just $ MetaData { title: "", legend: [], corpusId: [], listId: 0 }
}
instance decodeJsonGraphData :: DecodeJson GraphData where
......@@ -110,8 +125,10 @@ instance decodeJsonGraphData :: DecodeJson GraphData where
corpusIds <- metadata .: "corpusId"
listId' <- metadata .: "listId"
metaData <- obj .: "metadata"
let side x = GraphSideCorpus { corpusId: x, corpusLabel: "Publications", listId : listId'}
let sides = side <$> corpusIds
let
side x = GraphSideCorpus { corpusId: x, corpusLabel: "Publications", listId: listId' }
let
sides = side <$> corpusIds
pure $ GraphData { nodes, edges, sides, metaData }
instance decodeJsonNode :: DecodeJson Node where
......@@ -126,7 +143,6 @@ instance decodeJsonNode :: DecodeJson Node where
y <- obj .: "y_coord"
pure $ Node { id_, type_, size, label, attributes, x, y }
instance decodeJsonMetaData :: DecodeJson MetaData where
decodeJson json = do
obj <- decodeJson json
......@@ -134,8 +150,7 @@ instance decodeJsonMetaData :: DecodeJson MetaData where
legend <- obj .: "legend"
corpusId <- obj .: "corpusId"
listId <- obj .: "listId"
pure $ MetaData { title, legend, corpusId, listId}
pure $ MetaData { title, legend, corpusId, listId }
instance decodeJsonLegend :: DecodeJson Legend where
decodeJson json = do
......@@ -145,7 +160,6 @@ instance decodeJsonLegend :: DecodeJson Legend where
label <- obj .: "label"
pure $ Legend { id_, color, label }
instance decodeJsonCluster :: DecodeJson Cluster where
decodeJson json = do
obj <- decodeJson json
......@@ -162,7 +176,8 @@ instance decodeJsonEdge :: DecodeJson Edge where
confluence <- obj .: "confluence"
pure $ Edge { id_, source, target, weight, confluence }
newtype Legend = Legend {id_ ::Int , color :: String, label :: String}
newtype Legend
= Legend { id_ :: Int, color :: String, label :: String }
instance eqLegend :: Eq Legend where
eq (Legend l1) (Legend l2) = eq l1.id_ l2.id_
......@@ -171,15 +186,14 @@ instance ordLegend :: Ord Legend where
compare (Legend l1) (Legend l2) = compare l1.id_ l2.id_
getLegendData :: GraphData -> Array Legend
getLegendData (GraphData {metaData: Just (MetaData {legend})}) = legend
getLegendData (GraphData { metaData: Just (MetaData { legend }) }) = legend
getLegendData _ = []
defaultPalette :: Array String
defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff","#b399df","#ffdfed","#33c8f3","#739e9a","#caeca3","#f6f7e5","#f9bcca","#ccb069","#c9ffde","#c58683","#6c9eb0","#ffd3cf","#ccffc7","#52a1b0","#d2ecff","#99fffe","#9295ae","#5ea38b","#fff0b3","#d99e68"]
defaultPalette = [ "#5fa571", "#ab9ba2", "#da876d", "#bdd3ff", "#b399df", "#ffdfed", "#33c8f3", "#739e9a", "#caeca3", "#f6f7e5", "#f9bcca", "#ccb069", "#c9ffde", "#c58683", "#6c9eb0", "#ffd3cf", "#ccffc7", "#52a1b0", "#d2ecff", "#99fffe", "#9295ae", "#5ea38b", "#fff0b3", "#d99e68" ]
-- clusterColor :: Cluster -> Color
-- clusterColor (Cluster {clustDefault}) = unsafePartial $ fromJust $ defaultPalette !! (clustDefault `mod` length defaultPalette)
intColor :: Int -> String
intColor i = unsafePartial $ fromJust $ defaultPalette !! (i `mod` length defaultPalette)
module Gargantext.Components.Data.Landing where
data LandingData = LandingData { name :: String
data LandingData
= LandingData
{ name :: String
, signature :: String
, logoTitle :: String
, imageTitle :: String
, blockTexts :: BlockTexts
}
data BlockTexts = BlockTexts { blocks :: Array BlockText }
data BlockTexts
= BlockTexts { blocks :: Array BlockText }
data BlockText = BlockText { title :: String
data BlockText
= BlockText
{ title :: String
, href :: String
, titleText :: String
, icon :: String
......@@ -18,8 +22,9 @@ data BlockText = BlockText { title :: String
, docButton :: Button
}
data Button = Button { title :: String
data Button
= Button
{ title :: String
, text :: String
, href :: String
}
module Gargantext.Components.Data.Lang where
data Lang = EN | FR
data Lang
= EN
| FR
......@@ -3,43 +3,54 @@ module Gargantext.Components.Lang.Landing.EnUS where
import Gargantext.Components.Data.Landing
landingData :: LandingData
landingData = LandingData { name : "Gargantext"
, signature : "search map share"
, logoTitle : "Project hosted by CNRS (France, Europa)"
landingData =
LandingData
{ name: "Gargantext"
, signature: "search map share"
, logoTitle: "Project hosted by CNRS (France, Europa)"
, imageTitle: "Click and test by yourself"
, blockTexts : BlockTexts { blocks : blockTexts}
, blockTexts: BlockTexts { blocks: blockTexts }
}
blockTexts :: Array BlockText
blockTexts = [ BlockText { title : "Random sentences in Gargantua's Books chapters, historically true"
, href : "#"
, icon : "glyphicon glyphicon-random"
, titleText : "Historic"
, text : "Chapter 1.XV. How Gargantua was put under other schoolmasters. Chapter 2.XXII. How Panurge served a Parisian lady a trick that pleased her not very well. Chapter 3.XXXVII. How Pantagruel persuaded Panurge to take counsel of a fool. Chapter 4.LXI. How Gaster invented means to get and preserve corn. Chapter 5.XXXVIII. Of the temple's admirable pavement."
, docButton : Button { title : "Your first map in less than 5 minutes"
, text : " Documentation"
, href : "https://iscpif.fr/gargantext/your-first-map/"
blockTexts =
[ BlockText
{ title: "Random sentences in Gargantua's Books chapters, historically true"
, href: "#"
, icon: "glyphicon glyphicon-random"
, titleText: "Historic"
, text: "Chapter 1.XV. How Gargantua was put under other schoolmasters. Chapter 2.XXII. How Panurge served a Parisian lady a trick that pleased her not very well. Chapter 3.XXXVII. How Pantagruel persuaded Panurge to take counsel of a fool. Chapter 4.LXI. How Gaster invented means to get and preserve corn. Chapter 5.XXXVIII. Of the temple's admirable pavement."
, docButton:
Button
{ title: "Your first map in less than 5 minutes"
, text: " Documentation"
, href: "https://iscpif.fr/gargantext/your-first-map/"
}
}
, BlockText { title : "Randomized words, semantically and syntaxically falses."
, href : "#"
, icon : "glyphicon glyphicon-random"
, titleText : "Presentation"
, text : "Autem nascetur iaculis, sedfusce enimsed cursus posuere consectetuer eu justo aliquammauris. Phasellus vero nisi porttitor elit quod, leo feliscras ultricies non tempor sagittis. Liberoduis facilisinam erat dapibusnam, lacus dui duis tristique volutpatut quis vestibulum magna. Nobis faucibusvestibulum dolores minim. Bibendumin malesuada adipiscing ante, mattis fames nequeetiam lorem. No diam id. Litora quisaenean commodo lobortisetiam neque, libero mollis scelerisque inceptos ullamcorper sea congue delenit possim."
, docButton : Button { title : "Your first map in less than 5 minutes"
, text : " Documentation"
, href : "https://iscpif.fr/gargantext/your-first-map/"
, BlockText
{ title: "Randomized words, semantically and syntaxically falses."
, href: "#"
, icon: "glyphicon glyphicon-random"
, titleText: "Presentation"
, text: "Autem nascetur iaculis, sedfusce enimsed cursus posuere consectetuer eu justo aliquammauris. Phasellus vero nisi porttitor elit quod, leo feliscras ultricies non tempor sagittis. Liberoduis facilisinam erat dapibusnam, lacus dui duis tristique volutpatut quis vestibulum magna. Nobis faucibusvestibulum dolores minim. Bibendumin malesuada adipiscing ante, mattis fames nequeetiam lorem. No diam id. Litora quisaenean commodo lobortisetiam neque, libero mollis scelerisque inceptos ullamcorper sea congue delenit possim."
, docButton:
Button
{ title: "Your first map in less than 5 minutes"
, text: " Documentation"
, href: "https://iscpif.fr/gargantext/your-first-map/"
}
}
, BlockText { title : "Randomized letters, true or false ?"
, href : "#"
, icon : "glyphicon glyphicon-random"
, titleText : "Tutoreil"
, text : "Il paraît que l'rdore des lettres dans un mot n'a pas d'imtraopnce. La première et la dernière lettre doeivnt être à la bonne place. Le reste peut être dans un désordre total et on peut touojurs lire sans prolèbme. On ne lit donc pas chaque lettre en ellêem-me, mais le mot comme un tout. Un chaegmnent de référentiel et nous tranpossons ce résultat au texte luimê-me: l'rdore des mots est failbement important copamré au contexte du texte qui, lui, est copmté: comptexter avec Gargantext."
, docButton : Button { title : "Your first map in less than 5 minutes"
, text : " Documentation"
, href : "https://iscpif.fr/gargantext/your-first-map/"
, BlockText
{ title: "Randomized letters, true or false ?"
, href: "#"
, icon: "glyphicon glyphicon-random"
, titleText: "Tutoreil"
, text: "Il paraît que l'rdore des lettres dans un mot n'a pas d'imtraopnce. La première et la dernière lettre doeivnt être à la bonne place. Le reste peut être dans un désordre total et on peut touojurs lire sans prolèbme. On ne lit donc pas chaque lettre en ellêem-me, mais le mot comme un tout. Un chaegmnent de référentiel et nous tranpossons ce résultat au texte luimê-me: l'rdore des mots est failbement important copamré au contexte du texte qui, lui, est copmté: comptexter avec Gargantext."
, docButton:
Button
{ title: "Your first map in less than 5 minutes"
, text: " Documentation"
, href: "https://iscpif.fr/gargantext/your-first-map/"
}
}
]
......@@ -3,43 +3,54 @@ module Gargantext.Components.Lang.Landing.FrFR where
import Gargantext.Components.Data.Landing
landingData :: LandingData
landingData = LandingData { name : "Gargantext"
, signature : "chercher cartographier partgager"
, logoTitle : "Projet développé par le CNRS (France, Europe)"
landingData =
LandingData
{ name: "Gargantext"
, signature: "chercher cartographier partgager"
, logoTitle: "Projet développé par le CNRS (France, Europe)"
, imageTitle: "Cliquez et testez vous-mêmes"
, blockTexts : BlockTexts { blocks : blockTexts}
, blockTexts: BlockTexts { blocks: blockTexts }
}
blockTexts :: Array BlockText
blockTexts = [ BlockText { title : "Phrases aléatoires issues de l'oeuvre de François Rabelais. L'ordre historique des chapitres est préservé."
, href : "#"
, icon : "glyphicon glyphicon-random"
, titleText : "Historique"
, text : "Chapitre 1"
, docButton : Button { title : "Your first map in less than 5 minutes"
, text : " Documentation"
, href : "https://iscpif.fr/gargantext/your-first-map/"
blockTexts =
[ BlockText
{ title: "Phrases aléatoires issues de l'oeuvre de François Rabelais. L'ordre historique des chapitres est préservé."
, href: "#"
, icon: "glyphicon glyphicon-random"
, titleText: "Historique"
, text: "Chapitre 1"
, docButton:
Button
{ title: "Your first map in less than 5 minutes"
, text: " Documentation"
, href: "https://iscpif.fr/gargantext/your-first-map/"
}
}
, BlockText { title : "Mots aléatoires."
, href : "#"
, icon : "glyphicon glyphicon-random"
, titleText : "Presentation"
, text : "Autem nascetur iaculis, sedfusce enimsed cursus posuere consectetuer eu justo aliquammauris. Phasellus vero nisi porttitor elit quod, leo feliscras ultricies non tempor sagittis. Liberoduis facilisinam erat dapibusnam, lacus dui duis tristique volutpatut quis vestibulum magna. Nobis faucibusvestibulum dolores minim. Bibendumin malesuada adipiscing ante, mattis fames nequeetiam lorem. No diam id. Litora quisaenean commodo lobortisetiam neque, libero mollis scelerisque inceptos ullamcorper sea congue delenit possim."
, docButton : Button { title : "Your first map in less than 5 minutes"
, text : " Documentation"
, href : "https://iscpif.fr/gargantext/your-first-map/"
, BlockText
{ title: "Mots aléatoires."
, href: "#"
, icon: "glyphicon glyphicon-random"
, titleText: "Presentation"
, text: "Autem nascetur iaculis, sedfusce enimsed cursus posuere consectetuer eu justo aliquammauris. Phasellus vero nisi porttitor elit quod, leo feliscras ultricies non tempor sagittis. Liberoduis facilisinam erat dapibusnam, lacus dui duis tristique volutpatut quis vestibulum magna. Nobis faucibusvestibulum dolores minim. Bibendumin malesuada adipiscing ante, mattis fames nequeetiam lorem. No diam id. Litora quisaenean commodo lobortisetiam neque, libero mollis scelerisque inceptos ullamcorper sea congue delenit possim."
, docButton:
Button
{ title: "Your first map in less than 5 minutes"
, text: " Documentation"
, href: "https://iscpif.fr/gargantext/your-first-map/"
}
}
, BlockText { title : "Lettres alétaoires, expérience"
, href : "#"
, icon : "glyphicon glyphicon-random"
, titleText : "Tutoreil"
, text : "Il paraît que l'rdore des lettres dans un mot n'a pas d'imtraopnce. La première et la dernière lettre doeivnt être à la bonne place. Le reste peut être dans un désordre total et on peut touojurs lire sans prolèbme. On ne lit donc pas chaque lettre en ellêem-me, mais le mot comme un tout. Un chaegmnent de référentiel et nous tranpossons ce résultat au texte luimê-me: l'rdore des mots est failbement important copamré au contexte du texte qui, lui, est copmté: comptexter avec Gargantext."
, docButton : Button { title : "Your first map in less than 5 minutes"
, text : " Documentation"
, href : "https://iscpif.fr/gargantext/your-first-map/"
, BlockText
{ title: "Lettres alétaoires, expérience"
, href: "#"
, icon: "glyphicon glyphicon-random"
, titleText: "Tutoreil"
, text: "Il paraît que l'rdore des lettres dans un mot n'a pas d'imtraopnce. La première et la dernière lettre doeivnt être à la bonne place. Le reste peut être dans un désordre total et on peut touojurs lire sans prolèbme. On ne lit donc pas chaque lettre en ellêem-me, mais le mot comme un tout. Un chaegmnent de référentiel et nous tranpossons ce résultat au texte luimê-me: l'rdore des mots est failbement important copamré au contexte du texte qui, lui, est copmté: comptexter avec Gargantext."
, docButton:
Button
{ title: "Your first map in less than 5 minutes"
, text: " Documentation"
, href: "https://iscpif.fr/gargantext/your-first-map/"
}
}
]
......@@ -7,54 +7,64 @@ import React (ReactClass, Children)
import Gargantext.Prelude
import Effect (Effect)
import Effect.Aff (Aff)
import Thermite (Render, PerformAction, simpleSpec, modifyState_, createReactSpec)
data Action path = ForceReload | SetPath path
data Action path
= ForceReload
| SetPath path
type InnerPropsRow path loaded row =
( path :: path
type InnerPropsRow path loaded row
= ( path :: path
, loaded :: loaded
, dispatch :: Action path -> Effect Unit
| row
)
type InnerProps path loaded row = Record (InnerPropsRow path loaded row)
type InnerProps path loaded row
= Record (InnerPropsRow path loaded row)
type InnerClass path loaded = ReactClass (InnerProps path loaded (children :: Children))
type InnerClass path loaded
= ReactClass (InnerProps path loaded ( children :: Children ))
type PropsRow path loaded row =
( path :: path
type PropsRow path loaded row
= ( path :: path
, component :: InnerClass path loaded
| row
)
type Props path loaded = Record (PropsRow path loaded (children :: Children))
type Props path loaded
= Record (PropsRow path loaded ( children :: Children ))
type Props' path loaded = Record (PropsRow path loaded ())
type Props' path loaded
= Record (PropsRow path loaded ())
type State path loaded = { currentPath :: path, loaded :: Maybe loaded }
type State path loaded
= { currentPath :: path, loaded :: Maybe loaded }
createLoaderClass' :: forall path loaded props
. Eq path
=> Show path
=> String
-> (path -> Aff loaded)
-> Render (State path loaded) {path :: path | props} (Action path)
-> ReactClass { path :: path, children :: Children | props }
createLoaderClass' ::
forall path loaded props.
Eq path =>
Show path =>
String ->
(path -> Aff loaded) ->
Render (State path loaded) { path :: path | props } (Action path) ->
ReactClass { path :: path, children :: Children | props }
createLoaderClass' name loader render =
React.component name
(\this -> do
( \this -> do
logs $ "createLoaderClass' " <> name
s <- spec this
pure { state: s.state
pure
{ state: s.state
, render: s.render
, componentDidMount: do
, componentDidMount:
do
logs $ name <> ".componentDidMount"
dispatcher this ForceReload
, componentDidUpdate: \{path: prevPath} {currentPath} _snapshot -> do
{path} <- React.getProps this
logs $ name <> ".componentDidUpdate " <> show {currentPath, path, prevPath}
, componentDidUpdate:
\{ path: prevPath } { currentPath } _snapshot -> do
{ path } <- React.getProps this
logs $ name <> ".componentDidUpdate " <> show { currentPath, path, prevPath }
-- This guard is the similar to the one in performAction (SetPath ...),
-- however we need it here to avoid potential infinite loops.
-- https://reactjs.org/docs/react-component.html#componentdidupdate
......@@ -65,43 +75,43 @@ createLoaderClass' name loader render =
-- previous value.
when (prevPath /= path && path /= currentPath) do
dispatcher this (SetPath path)
})
}
)
where
initialState {path} = {currentPath: path, loaded: Nothing}
initialState { path } = { currentPath: path, loaded: Nothing }
performAction :: PerformAction (State path loaded) {path :: path | props} (Action path)
performAction ForceReload _ {currentPath} = do
performAction :: PerformAction (State path loaded) { path :: path | props } (Action path)
performAction ForceReload _ { currentPath } = do
logs $ name <> ".ForceReload {currentPath: " <> show currentPath <> "}"
loaded <- lift $ loader currentPath
modifyState_ $ _ { loaded = Just loaded }
performAction (SetPath newPath) _ {currentPath} = do
logs $ name <> ".SetPath " <> show {newPath, currentPath}
performAction (SetPath newPath) _ { currentPath } = do
logs $ name <> ".SetPath " <> show { newPath, currentPath }
when (newPath /= currentPath) do
loaded <- lift $ loader newPath
modifyState_ $ _ { currentPath = newPath, loaded = Just loaded }
{spec, dispatcher} = createReactSpec (simpleSpec performAction render) initialState
{ spec, dispatcher } = createReactSpec (simpleSpec performAction render) initialState
type LoaderClass path loaded =
ReactClass (Record (PropsRow path loaded (children :: Children)))
type LoaderClass path loaded
= ReactClass (Record (PropsRow path loaded ( children :: Children )))
createLoaderClass :: forall path loaded
. Eq path
=> Show path
=> String
-> (path -> Aff loaded)
-> LoaderClass path loaded
createLoaderClass name loader =
createLoaderClass' name loader render
createLoaderClass ::
forall path loaded.
Eq path =>
Show path =>
String ->
(path -> Aff loaded) ->
LoaderClass path loaded
createLoaderClass name loader = createLoaderClass' name loader render
where
render :: Render (State path loaded) (Props' path loaded) (Action path)
render _ _ {loaded: Nothing} _ =
-- TODO load spinner
render _ _ { loaded: Nothing } _ = -- TODO load spinner
[]
render dispatch {component} {currentPath, loaded: Just loaded} c =
[React.createElement component {path: currentPath, loaded, dispatch} c]
{-
render dispatch { component } { currentPath, loaded: Just loaded } c = [ React.createElement component { path: currentPath, loaded, dispatch } c ]
{-
createLoaderClass :: forall path loaded
. String
-> (path -> Aff loaded)
......
......@@ -3,7 +3,8 @@ module Gargantext.Components.LoadingSpinner where
import Reactix as R
import Reactix.DOM.HTML as H
type Props = ()
type Props
= ()
loadingSpinner :: Record Props -> R.Element
loadingSpinner props = R.createElement loadingSpinnerCpt props []
......@@ -11,4 +12,4 @@ loadingSpinner props = R.createElement loadingSpinnerCpt props []
loadingSpinnerCpt :: R.Component Props
loadingSpinnerCpt = R.staticComponent "LoadingSpinner" cpt
where
cpt _ _ = H.span {} [H.text "[Loading]"]
cpt _ _ = H.span {} [ H.text "[Loading]" ]
This diff is collapsed.
module Gargantext.Components.Login.Types where
import Prelude
import Data.Argonaut ( class DecodeJson, class EncodeJson, decodeJson, jsonEmptyObject
, (.:), (.??), (:=), (~>)
import Data.Argonaut
( class DecodeJson
, class EncodeJson
, decodeJson
, jsonEmptyObject
, (.:)
, (.??)
, (:=)
, (~>)
)
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Eq (genericEq)
......@@ -10,25 +17,36 @@ import Data.Lens (Iso', iso)
import Data.Maybe (Maybe)
import Data.Newtype (class Newtype)
type Username = String
type Password = String
type Token = String
type TreeId = Int
type Username
= String
newtype AuthRequest = AuthRequest
type Password
= String
type Token
= String
type TreeId
= Int
newtype AuthRequest
= AuthRequest
{ username :: Username
, password :: Password
}
newtype AuthResponse = AuthResponse
newtype AuthResponse
= AuthResponse
{ valid :: Maybe AuthData
, inval :: Maybe AuthInvalid
}
newtype AuthInvalid = AuthInvalid
newtype AuthInvalid
= AuthInvalid
{ message :: String }
newtype AuthData = AuthData
newtype AuthData
= AuthData
{ token :: Token
, tree_id :: TreeId
}
......@@ -47,24 +65,25 @@ instance decodeAuthInvalid :: DecodeJson AuthInvalid where
decodeJson json = do
obj <- decodeJson json
message <- obj .: "message"
pure $ AuthInvalid {message}
pure $ AuthInvalid { message }
instance decodeAuthResponse :: DecodeJson AuthResponse where
decodeJson json = do
obj <- decodeJson json
valid <- obj .?? "valid"
inval <- obj .?? "inval"
pure $ AuthResponse {valid, inval}
pure $ AuthResponse { valid, inval }
instance decodeAuthData :: DecodeJson AuthData where
decodeJson json = do
obj <- decodeJson json
token <- obj .: "token"
tree_id <- obj .: "tree_id"
pure $ AuthData {token, tree_id}
pure $ AuthData { token, tree_id }
instance encodeAuthRequest :: EncodeJson AuthRequest where
encodeJson (AuthRequest {username, password}) =
encodeJson (AuthRequest { username, password }) =
"username" := username
~> "password" := password
~> "password"
:= password
~> jsonEmptyObject
......@@ -4,19 +4,20 @@
module Gargantext.Components.Modal where
import Prelude (Unit, bind, const, discard, pure, unit, ($))
import Data.Maybe ( maybe )
import Data.Nullable ( Nullable, null )
import Data.Maybe (maybe)
import Data.Nullable (Nullable, null)
import DOM.Simple as DOM
import DOM.Simple.EventListener ( callback )
import DOM.Simple.EventListener (callback)
import DOM.Simple.Element as Element
import DOM.Simple.Event (MouseEvent, target)
import DOM.Simple.Document ( document )
import DOM.Simple.Document (document)
import Effect (Effect)
import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Utils.Reactix as R2
type Props = ( setVisible :: R2.Setter Boolean )
type Props
= ( setVisible :: R2.Setter Boolean )
modal :: Record Props -> Array R.Element -> R.Element
modal = R.createElement modalCpt
......@@ -24,39 +25,47 @@ modal = R.createElement modalCpt
modalCpt :: R.Component Props
modalCpt = R.hooksComponent "Modal" cpt
where
cpt {setVisible} children = do
cpt { setVisible } children = do
host <- R2.getPortalHost
root <- R.useRef null -- used to close when user clicks outside
R2.useLayoutEffectOnce $ modalEffect root setVisible
pure $ R.createPortal
[ H.div { ref: root, className: "modal", data: {toggle: "popover", placement: "right"}}
pure
$ R.createPortal
[ H.div { ref: root, className: "modal", data: { toggle: "popover", placement: "right" } }
[ H.div { className: "popover-content" }
[ H.div { className: "panel panel-default" }
[ H.ul { className: "list-group" } children ]]]]
[ H.ul { className: "list-group" } children ]
]
]
]
host
modalEffect
:: R.Ref (Nullable DOM.Element)
-> R2.Setter Boolean
-> Effect (Effect Unit)
modalEffect ::
R.Ref (Nullable DOM.Element) ->
R2.Setter Boolean ->
Effect (Effect Unit)
modalEffect rootRef setVisible = maybe (pure R.nothing) withRoot (R.readNullableRef rootRef)
where
onScroll = R2.named "hideModalOnScroll" $ callback handler
where -- removing this type declaration will unleash the hounds, so don't
handler :: MouseEvent -> Effect Unit
handler _ = setVisible (const false)
withRoot root = do
let onClick = clickHandler root
let
onClick = clickHandler root
DOM.addEventListener document "click" onClick
DOM.addEventListener document "scroll" onScroll
pure $ do
pure
$ do
DOM.removeEventListener document "click" onClick
DOM.removeEventListener document "scroll" onScroll
clickHandler root =
R2.named "hideModalOnClickOutside" $ callback handler
clickHandler root = R2.named "hideModalOnClickOutside" $ callback handler
where -- removing this type declaration will unleash the hounds, so don't
handler :: MouseEvent -> Effect Unit
handler e =
if Element.contains root (target e)
then pure unit
else setVisible (const false)
if Element.contains root (target e) then
pure unit
else
setVisible (const false)
module Gargantext.Components.Modals.Modal where
import Prelude (Unit)
import Effect (Effect)
foreign import modalShow :: String -> Effect Unit
......
This diff is collapsed.
module Gargantext.Components.Node
where
module Gargantext.Components.Node where
import Gargantext.Prelude
import Data.Argonaut (class DecodeJson, decodeJson, (.:))
newtype NodePoly a =
NodePoly { id :: Int
newtype NodePoly a
= NodePoly
{ id :: Int
, typename :: Int
, userId :: Int
, parentId :: Int
......@@ -14,9 +14,9 @@ newtype NodePoly a =
, hyperdata :: a
}
instance decodeNodePoly :: (DecodeJson a)
=> DecodeJson (NodePoly a) where
instance decodeNodePoly ::
(DecodeJson a) =>
DecodeJson (NodePoly a) where
decodeJson json = do
obj <- decodeJson json
id <- obj .: "id"
......@@ -25,24 +25,24 @@ instance decodeNodePoly :: (DecodeJson a)
parentId <- obj .: "parentId"
name <- obj .: "name"
date <- obj .: "date"
hyperdata <- obj .: "hyperdata"
hyperdata' <- decodeJson hyperdata
pure $ NodePoly { id : id
, typename : typename
, userId : userId
, parentId : parentId
, name : name
, date : date
pure
$ NodePoly
{ id: id
, typename: typename
, userId: userId
, parentId: parentId
, name: name
, date: date
, hyperdata: hyperdata'
}
newtype HyperdataList = HyperdataList { preferences :: String}
newtype HyperdataList
= HyperdataList { preferences :: String }
instance decodeHyperdataList :: DecodeJson HyperdataList where
decodeJson json = do
obj <- decodeJson json
pref <- obj .: "preferences"
pure $ HyperdataList { preferences : pref}
pure $ HyperdataList { preferences: pref }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -7,7 +7,8 @@ import Data.Tuple.Nested ((/\))
import Reactix as R
import Reactix.DOM.HTML as H
type TabsProps = ( tabs :: Array (Tuple String R.Element), selected :: Int )
type TabsProps
= ( tabs :: Array (Tuple String R.Element), selected :: Int )
tabs :: Record TabsProps -> R.Element
tabs props = R.createElement tabsCpt props []
......@@ -18,20 +19,25 @@ tabsCpt = R.hooksComponent "Tabs" cpt
where
cpt props _ = do
(activeTab /\ setActiveTab) <- R.useState' props.selected
pure $
H.div { className: "tab-content" }
pure
$ H.div { className: "tab-content" }
[ H.nav {}
[ H.div { className: "nav nav-tabs" }
(mapWithIndex (item setActiveTab activeTab) props.tabs) ] ]
item setActiveTab selected index (name /\ _) =
H.a { className, on: { click } } [ H.text name ]
(mapWithIndex (item setActiveTab activeTab) props.tabs)
]
]
item setActiveTab selected index (name /\ _) = H.a { className, on: { click } } [ H.text name ]
where
eq = index == selected
className = "nav-item nav-link" <> (if eq then " active" else "")
click e = setActiveTab (const index)
-- TODO: document what these are (selection, item indices)
type TabProps = ( selected :: Int, index :: Int )
type TabProps
= ( selected :: Int, index :: Int )
tab :: Record TabProps -> Array R.Element -> R.Element
tab = R.createElement tabCpt
......@@ -43,6 +49,7 @@ tabCpt = R.staticComponent "Tab" cpt
cpt { selected, index } children = H.div { className } children'
where
same = selected == index
className = "tab-pane" <> (if same then "show active" else "fade")
children' = if same then children else []
children' = if same then children else []
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -13,4 +13,3 @@ useHashRouter routes init = do
route@(_ /\ setRoute) <- R.useState' init
R.useEffectOnce $ matches routes $ \_old new -> setRoute (const new)
pure route
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.
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.
......@@ -16,4 +16,5 @@ main = paint $ toMaybe (document ... "getElementById" $ [ "app" ])
paint :: Maybe Element -> Effect Unit
paint Nothing = log "[main] Container not found"
paint (Just c) = R2.render (app {}) c
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