Commit bb0f1dfa authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

Merge branch 'dev' into 395-dev-ps-0.15-update

parents 3644779c 5cf74ca2
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{ {
"name": "Gargantext", "name": "Gargantext",
"version": "0.0.5.9.5", "version": "0.0.6",
"scripts": { "scripts": {
"generate-purs-packages-nix": "./nix/generate-purs-packages.nix", "generate-purs-packages-nix": "./nix/generate-purs-packages.nix",
"generate-psc-packages-nix": "./nix/generate-packages-json.bash", "generate-psc-packages-nix": "./nix/generate-packages-json.bash",
......
...@@ -74,15 +74,31 @@ annotatedFieldInner = R2.leafComponent annotatedFieldInnerCpt ...@@ -74,15 +74,31 @@ annotatedFieldInner = R2.leafComponent annotatedFieldInnerCpt
annotatedFieldInnerCpt :: R.Component InnerProps annotatedFieldInnerCpt :: R.Component InnerProps
annotatedFieldInnerCpt = here.component "annotatedFieldInner" cpt where annotatedFieldInnerCpt = here.component "annotatedFieldInner" cpt where
cpt { menuRef, ngrams, redrawMenu, setTermList, text: fieldText, mode } _ = do cpt { menuRef
, ngrams
, redrawMenu
, setTermList
, text: fieldText
, mode
} _ = do
-- | States
-- |
_redrawMenu' <- T.useLive T.unequal redrawMenu _redrawMenu' <- T.useLive T.unequal redrawMenu
-- menu <- T.useBox (Nothing :: Maybe (Record AnnotationMenu)) -- menu <- T.useBox (Nothing :: Maybe (Record AnnotationMenu))
let wrap (text /\ list) = { list -- | Computed
-- |
let
wrap :: Tuple String (List (Tuple NgramsTerm TermList)) -> Record RunProps
wrap (text /\ list)
= { list
, onSelect: onAnnotationSelect { menuRef, ngrams, redrawMenu, setTermList } , onSelect: onAnnotationSelect { menuRef, ngrams, redrawMenu, setTermList }
, text } , text
}
-- | Render
-- |
pure $ pure $
H.div H.div
......
...@@ -78,14 +78,14 @@ componentName = "b-modal" ...@@ -78,14 +78,14 @@ componentName = "b-modal"
-- | - a FFI fix has been added to remove left elements -- | - a FFI fix has been added to remove left elements
-- | - an overlay has been added to synchronise the close button -- | - an overlay has been added to synchronise the close button
-- | - the keyboard shortcut has been removed -- | - the keyboard shortcut has been removed
-- | @https://stackoverflow.com/questions/50168312/bootstrap-4-close-modal-backdrop-doesnt-disappear -- | @link https://stackoverflow.com/questions/50168312/bootstrap-4-close-modal-backdrop-doesnt-disappear
-- | -- |
-- | https://getbootstrap.com/docs/4.6/components/modal/ -- | @link https://getbootstrap.com/docs/4.6/components/modal/
baseModal :: forall r. R2.OptComponent Options Props r baseModal :: forall r. R2.OptComponent Options Props r
baseModal = R2.optComponent component options baseModal = R2.optComponent component options
component :: R.Component Props component :: R.Memo Props
component = R.hooksComponent componentName cpt where component = R.memo' $ R.hooksComponent componentName cpt where
cpt props@{ isVisibleBox cpt props@{ isVisibleBox
, title , title
, hasCollapsibleBackground , hasCollapsibleBackground
...@@ -94,12 +94,13 @@ component = R.hooksComponent componentName cpt where ...@@ -94,12 +94,13 @@ component = R.hooksComponent componentName cpt where
, noBody , noBody
, size , size
} children } children
= R.unsafeHooksEffect (UUID.genUUID >>= pure <<< UUID.toString) = do
>>= \uuid -> do
-- | States -- | States
-- | -- |
isVisible <- R2.useLive' isVisibleBox isVisible <- R2.useLive' isVisibleBox
uuid <- R.unsafeHooksEffect (UUID.genUUID >>= pure <<< UUID.toString)
-- | Computed -- | Computed
-- | -- |
let let
......
...@@ -93,7 +93,7 @@ component = R.hooksComponent componentName cpt where ...@@ -93,7 +93,7 @@ component = R.hooksComponent componentName cpt where
R2.select R2.select
{ className { className
, on: { change } , on: { change }
, disabled: elem status [ Disabled ] , disabled: elem status [ Disabled, Idled ]
, readOnly: elem status [ Idled ] , readOnly: elem status [ Idled ]
, type: props.type , type: props.type
, value: props.value , value: props.value
......
...@@ -5,18 +5,22 @@ module Gargantext.Components.Document.Layout ...@@ -5,18 +5,22 @@ module Gargantext.Components.Document.Layout
import Gargantext.Prelude import Gargantext.Prelude
import Data.Maybe (Maybe(..), fromMaybe, isJust, maybe) import Data.Maybe (Maybe(..), fromMaybe, isJust, maybe)
import Data.Ord (greaterThan)
import Data.String (length)
import Data.String as String import Data.String as String
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import Gargantext.Components.Annotation.Field as AnnotatedField import Gargantext.Components.Annotation.Field as AnnotatedField
import Gargantext.Components.Annotation.Types as AFT import Gargantext.Components.Annotation.Types as AFT
import Gargantext.Components.AutoUpdate (autoUpdate) import Gargantext.Components.AutoUpdate (autoUpdate)
import Gargantext.Components.Bootstrap as B import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (SpinnerTheme(..)) import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), SpinnerTheme(..))
import Gargantext.Components.Document.Types (DocPath, Document(..), LoadedData, initialState) import Gargantext.Components.Document.Types (DocPath, Document(..), LoadedData, initialState)
import Gargantext.Components.NgramsTable.AutoSync (useAutoSync) import Gargantext.Components.NgramsTable.AutoSync (useAutoSync)
import Gargantext.Components.Node (NodePoly(..)) import Gargantext.Components.Node (NodePoly(..))
import Gargantext.Core.NgramsTable.Functions (addNewNgramA, applyNgramsPatches, coreDispatch, findNgramRoot, setTermListA) import Gargantext.Core.NgramsTable.Functions (addNewNgramA, applyNgramsPatches, coreDispatch, findNgramRoot, setTermListA)
import Gargantext.Core.NgramsTable.Types (CoreAction(..), Versioned(..), replace) import Gargantext.Core.NgramsTable.Types (CoreAction(..), Versioned(..), replace)
import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Utils ((?))
import Gargantext.Utils as U import Gargantext.Utils as U
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
import Reactix as R import Reactix as R
...@@ -63,12 +67,11 @@ layoutCpt = here.component "main" cpt where ...@@ -63,12 +67,11 @@ layoutCpt = here.component "main" cpt where
state'@{ ngramsLocalPatch } /\ state <- state'@{ ngramsLocalPatch } /\ state <-
R2.useBox' $ initialState { loaded } R2.useBox' $ initialState { loaded }
mode' /\ mode <- R2.useBox' AFT.EditionMode mode' /\ mode <- R2.useBox' AFT.AdditionMode
-- | Hooks forceAdditionMode' /\ forceAdditionMode <- R2.useBox' false
-- |
let dispatch = coreDispatch path state
let dispatch = coreDispatch path state
{ onPending, result } <- useAutoSync { state, action: dispatch } { onPending, result } <- useAutoSync { state, action: dispatch }
onPending' <- R2.useLive' onPending onPending' <- R2.useLive' onPending
...@@ -77,6 +80,7 @@ layoutCpt = here.component "main" cpt where ...@@ -77,6 +80,7 @@ layoutCpt = here.component "main" cpt where
-- | Computed -- | Computed
-- | -- |
let let
withAutoUpdate = false withAutoUpdate = false
ngrams = applyNgramsPatches state' initTable ngrams = applyNgramsPatches state' initTable
...@@ -98,6 +102,21 @@ layoutCpt = here.component "main" cpt where ...@@ -98,6 +102,21 @@ layoutCpt = here.component "main" cpt where
hasAbstract = maybe false (not String.null) doc.abstract hasAbstract = maybe false (not String.null) doc.abstract
-- | Hooks
-- |
-- (?) Limit large document feature with empirical length value
-- see #423
useFirstEffect' do
let len = maybe 0 (length) doc.abstract
if (len `greaterThan` 4500)
then
T.write_ true forceAdditionMode
*> T.write_ AFT.AdditionMode mode
else
T.write_ false forceAdditionMode
*> T.write_ AFT.EditionMode mode
-- | Behaviors -- | Behaviors
-- | -- |
let let
...@@ -123,7 +142,7 @@ layoutCpt = here.component "main" cpt where ...@@ -123,7 +142,7 @@ layoutCpt = here.component "main" cpt where
[ [
-- Viewing mode -- Viewing mode
B.wad B.wad
[ "d-flex", "align-items-center" ] [ "d-flex", "align-items-center", "width-auto" ]
[ [
H.label H.label
{ className: "mr-1" { className: "mr-1"
...@@ -136,6 +155,9 @@ layoutCpt = here.component "main" cpt where ...@@ -136,6 +155,9 @@ layoutCpt = here.component "main" cpt where
B.formSelect B.formSelect
{ value: show mode' { value: show mode'
, callback: onModeChange , callback: onModeChange
, status: forceAdditionMode' ?
Idled $
Enabled
} }
[ [
H.option H.option
...@@ -147,6 +169,14 @@ layoutCpt = here.component "main" cpt where ...@@ -147,6 +169,14 @@ layoutCpt = here.component "main" cpt where
[ H.text "Add and edit terms" ] [ H.text "Add and edit terms" ]
] ]
] ]
,
R2.when forceAdditionMode' $
B.wad
[ "color-warning", "font-size-100", "mx-2", "inline-block" ]
[
H.text "limited term feature due to abstract length"
]
, ,
R2.when withAutoUpdate $ R2.when withAutoUpdate $
-- (?) purpose? would still working with current code? -- (?) purpose? would still working with current code?
...@@ -162,6 +192,7 @@ layoutCpt = here.component "main" cpt where ...@@ -162,6 +192,7 @@ layoutCpt = here.component "main" cpt where
-- , ngramsLocalPatch -- , ngramsLocalPatch
-- , performAction: dispatch -- , performAction: dispatch
-- } -- }
] ]
, ,
H.div H.div
......
...@@ -299,7 +299,7 @@ childLoaderCpt = here.component "childLoader" cpt where ...@@ -299,7 +299,7 @@ childLoaderCpt = here.component "childLoader" cpt where
closeBox { isBoxVisible } = closeBox { isBoxVisible } =
liftEffect $ T.write_ false isBoxVisible liftEffect $ T.write_ false isBoxVisible
refreshTree p@{ reloadTree } = liftEffect $ T2.reload reloadTree *> closeBox p refreshTree p@{ reloadTree } = liftEffect $ closeBox p *> T2.reload reloadTree
deleteNode' nt p@{ boxes: { forestOpen }, session, tree: (NTree (LNode {id, parent_id}) _) } = do deleteNode' nt p@{ boxes: { forestOpen }, session, tree: (NTree (LNode {id, parent_id}) _) } = do
case nt of case nt of
......
module Gargantext.Components.Forest.Tree.Node.Action.ManageTeam where
import Gargantext.Prelude
import Data.Array (filter, null)
import Data.Either (Either(..))
import Effect.Aff (runAff_)
import Effect.Class (liftEffect)
import Gargantext.Components.Forest.Tree.Node.Tools as Tools
import Gargantext.Components.GraphQL.Endpoints (deleteTeamMembership, getTeam)
import Gargantext.Components.GraphQL.Team (TeamMember)
import Gargantext.Config.REST (AffRESTError, logRESTError)
import Gargantext.Hooks.Loader (useLoader)
import Gargantext.Sessions (Session)
import Gargantext.Types (ID, NodeType)
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.Forest.Tree.Node.Action.ManageTeam"
type ActionManageTeam = (
id :: ID
, nodeType :: NodeType
, session :: Session
)
actionManageTeam :: R2.Component ActionManageTeam
actionManageTeam = R.createElement actionManageTeamCpt
actionManageTeamCpt :: R.Component ActionManageTeam
actionManageTeamCpt = here.component "actionManageTeam" cpt where
cpt {id, session} _ = do
useLoader { errorHandler
, loader: loadTeam
, path: { nodeId: id, session }
, render: \team -> teamLayoutWrapper { team
, nodeId: id
, session
} []
}
where
errorHandler = logRESTError here "teamLayout"
type TeamProps =
( nodeId :: ID
, session :: Session
, team :: (Array TeamMember)
)
teamLayoutWrapper :: R2.Component TeamProps
teamLayoutWrapper = R.createElement teamLayoutWrapperCpt
teamLayoutWrapperCpt :: R.Component TeamProps
teamLayoutWrapperCpt = here.component "teamLayoutWrapper" cpt where
cpt {nodeId, session, team} _ = do
teamS <- T.useBox team
team' <- T.useLive T.unequal teamS
error <- T.useBox ""
error' <- T.useLive T.unequal error
pure $ teamLayoutRows {nodeId, session, team: teamS, team', error, error'}
type TeamRowProps =
( nodeId :: ID
, session :: Session
, team :: T.Box (Array TeamMember)
, error :: T.Box String
, team' :: Array TeamMember
, error' :: String
)
teamLayoutRows :: R2.Leaf TeamRowProps
teamLayoutRows = R2.leafComponent teamLayoutRowsCpt
teamLayoutRowsCpt :: R.Component TeamRowProps
teamLayoutRowsCpt = here.component "teamLayoutRows" cpt where
cpt { team, nodeId, session, error, team', error' } _ = do
case null team' of
true -> pure $ H.div { style: {margin: "10px"}}
[ H.h4 {} [H.text "Your team is empty, you can send some invitations."]]
false -> pure $ Tools.panel (map makeTeam team') (H.div {} [H.text error'])
where
makeTeam :: TeamMember -> R.Element
makeTeam { username, shared_folder_id } = H.div {className: "from-group row"} [ H.div { className: "col-8" } [ H.text username ]
, H.a { className: "text-danger col-2 fa fa-times"
, title: "Remove user from team"
, type: "button"
, on: {click: submit shared_folder_id }
} []
]
submit sharedFolderId _ = do
runAff_ callback $ saveDeleteTeam { session, nodeId, sharedFolderId }
callback res =
case res of
Left _ -> do
_ <- liftEffect $ T.write "Only the Team owner can remove users" error
pure unit
Right val ->
case val of
Left _ -> do
pure unit
Right r -> do
T.write_ (filter (\tm -> tm.shared_folder_id /= r) team') team
-------------------------------------------------------------
type LoadProps =
(
session :: Session,
nodeId :: Int
)
loadTeam :: Record LoadProps -> AffRESTError (Array TeamMember)
loadTeam { session, nodeId } = getTeam session nodeId
type DeleteProps =
(
session :: Session,
nodeId :: Int,
sharedFolderId :: Int
)
saveDeleteTeam ∷ Record DeleteProps -> AffRESTError Int
saveDeleteTeam { session, nodeId, sharedFolderId } = deleteTeamMembership session sharedFolderId nodeId
...@@ -22,6 +22,7 @@ import Gargantext.Components.Forest.Tree.Node.Action.Types (Action) ...@@ -22,6 +22,7 @@ import Gargantext.Components.Forest.Tree.Node.Action.Types (Action)
import Gargantext.Components.Forest.Tree.Node.Action.Update (update) import Gargantext.Components.Forest.Tree.Node.Action.Update (update)
import Gargantext.Components.Forest.Tree.Node.Action.Upload (actionUpload) import Gargantext.Components.Forest.Tree.Node.Action.Upload (actionUpload)
import Gargantext.Components.Forest.Tree.Node.Action.WriteNodesDocuments (actionWriteNodesDocuments) import Gargantext.Components.Forest.Tree.Node.Action.WriteNodesDocuments (actionWriteNodesDocuments)
import Gargantext.Components.Forest.Tree.Node.Action.ManageTeam (actionManageTeam)
import Gargantext.Components.Forest.Tree.Node.Box.Types (NodePopupProps, NodePopupS) import Gargantext.Components.Forest.Tree.Node.Box.Types (NodePopupProps, NodePopupS)
import Gargantext.Components.Forest.Tree.Node.Settings (NodeAction(..), SettingsBox(..), glyphiconNodeAction, settingsBox) import Gargantext.Components.Forest.Tree.Node.Settings (NodeAction(..), SettingsBox(..), glyphiconNodeAction, settingsBox)
import Gargantext.Components.Forest.Tree.Node.Status (Status(..), hasStatus) import Gargantext.Components.Forest.Tree.Node.Status (Status(..), hasStatus)
...@@ -189,6 +190,7 @@ panelActionCpt = here.component "panelAction" cpt ...@@ -189,6 +190,7 @@ panelActionCpt = here.component "panelAction" cpt
cpt { action: Download, id, nodeType, session} _ = pure $ actionDownload { id, nodeType, session } [] cpt { action: Download, id, nodeType, session} _ = pure $ actionDownload { id, nodeType, session } []
cpt { action: Upload, dispatch, id, nodeType, session} _ = pure $ actionUpload { dispatch, id, nodeType, session } [] cpt { action: Upload, dispatch, id, nodeType, session} _ = pure $ actionUpload { dispatch, id, nodeType, session } []
cpt { action: Delete, nodeType, dispatch} _ = pure $ actionDelete { dispatch, nodeType } [] cpt { action: Delete, nodeType, dispatch} _ = pure $ actionDelete { dispatch, nodeType } []
cpt { action: ManageTeam, nodeType, id, session} _ = pure $ actionManageTeam { id, nodeType, session } []
cpt { action: Add xs, dispatch, id, name, nodeType} _ = cpt { action: Add xs, dispatch, id, name, nodeType} _ =
pure $ addNodeView {dispatch, id, name, nodeType, nodeTypes: xs} [] pure $ addNodeView {dispatch, id, name, nodeType, nodeTypes: xs} []
cpt { action: Refresh , dispatch, nodeType } _ = pure $ update { dispatch, nodeType } [] cpt { action: Refresh , dispatch, nodeType } _ = pure $ update { dispatch, nodeType } []
......
...@@ -16,6 +16,7 @@ data NodeAction = Documentation NodeType ...@@ -16,6 +16,7 @@ data NodeAction = Documentation NodeType
| Download | Upload | Refresh | Config | Download | Upload | Refresh | Config
| Delete | Delete
| Share | Share
| ManageTeam
| Publish { subTreeParams :: SubTreeParams } | Publish { subTreeParams :: SubTreeParams }
| Add (Array NodeType) | Add (Array NodeType)
| Merge { subTreeParams :: SubTreeParams } | Merge { subTreeParams :: SubTreeParams }
...@@ -37,6 +38,7 @@ instance Eq NodeAction where ...@@ -37,6 +38,7 @@ instance Eq NodeAction where
eq Clone Clone = true eq Clone Clone = true
eq Delete Delete = true eq Delete Delete = true
eq Share Share = true eq Share Share = true
eq ManageTeam ManageTeam = true
eq (Link x) (Link y) = x == y eq (Link x) (Link y) = x == y
eq (Add x) (Add y) = x == y eq (Add x) (Add y) = x == y
eq (Merge x) (Merge y) = x == y eq (Merge x) (Merge y) = x == y
...@@ -57,6 +59,7 @@ instance Show NodeAction where ...@@ -57,6 +59,7 @@ instance Show NodeAction where
show Clone = "Clone" show Clone = "Clone"
show Delete = "Delete" show Delete = "Delete"
show Share = "Share" show Share = "Share"
show ManageTeam = "Team"
show Config = "Config" show Config = "Config"
show (Link _) = "Link to " -- <> show x show (Link _) = "Link to " -- <> show x
show (Add _) = "Add Child" -- foldl (\a b -> a <> show b) "Add " xs show (Add _) = "Add Child" -- foldl (\a b -> a <> show b) "Add " xs
...@@ -78,6 +81,7 @@ glyphiconNodeAction (Merge _) = "random" ...@@ -78,6 +81,7 @@ glyphiconNodeAction (Merge _) = "random"
glyphiconNodeAction Refresh = "refresh" glyphiconNodeAction Refresh = "refresh"
glyphiconNodeAction Config = "wrench" glyphiconNodeAction Config = "wrench"
glyphiconNodeAction Share = "user-plus" glyphiconNodeAction Share = "user-plus"
glyphiconNodeAction ManageTeam = "users"
glyphiconNodeAction AddingContact = "user-plus" glyphiconNodeAction AddingContact = "user-plus"
glyphiconNodeAction (Move _) = "share-square-o" glyphiconNodeAction (Move _) = "share-square-o"
glyphiconNodeAction (Publish _) = fldr FolderPublic true glyphiconNodeAction (Publish _) = fldr FolderPublic true
...@@ -137,6 +141,7 @@ settingsBox Team = ...@@ -137,6 +141,7 @@ settingsBox Team =
, NodeFrameVisio , NodeFrameVisio
] ]
, Share , Share
, ManageTeam
, Delete , Delete
] ]
} }
......
...@@ -24,7 +24,10 @@ forgotPasswordLayoutCpt = here.component "forgotPasswordLayout" cpt where ...@@ -24,7 +24,10 @@ forgotPasswordLayoutCpt = here.component "forgotPasswordLayout" cpt where
, loader: loadPassword , loader: loadPassword
, path: { server, uuid } , path: { server, uuid }
, render: \{ password } -> , render: \{ password } ->
H.p {} [ H.text ("Your new password is: " <> password) ] } H.div { className:"container text-center justify-content-center" } [
H.div {className: "row"} [ H.div {className: "mx-auto"} [ H.img { src: "images/logo.png" } ] ]
, H.div {className: "row"} [ H.div {className: "col"} [ H.text ("Your new password is: " <> password) ] ]
]}
where where
errorHandler = logRESTError here "[forgotPasswordLayout]" errorHandler = logRESTError here "[forgotPasswordLayout]"
......
...@@ -28,9 +28,10 @@ import Gargantext.Components.GraphExplorer.Sidebar.DocList (docListWrapper) ...@@ -28,9 +28,10 @@ import Gargantext.Components.GraphExplorer.Sidebar.DocList (docListWrapper)
import Gargantext.Components.GraphExplorer.Sidebar.Legend as Legend import Gargantext.Components.GraphExplorer.Sidebar.Legend as Legend
import Gargantext.Components.GraphExplorer.Store as GraphStore import Gargantext.Components.GraphExplorer.Store as GraphStore
import Gargantext.Components.GraphExplorer.Types as GET import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Components.GraphExplorer.Utils as GEU
import Gargantext.Components.Lang (Lang(..)) import Gargantext.Components.Lang (Lang(..))
import Gargantext.Core.NgramsTable.Functions as NTC
import Gargantext.Config.REST (AffRESTError) import Gargantext.Config.REST (AffRESTError)
import Gargantext.Core.NgramsTable.Functions as NTC
import Gargantext.Core.NgramsTable.Types as CNT import Gargantext.Core.NgramsTable.Types as CNT
import Gargantext.Data.Array (mapMaybe) import Gargantext.Data.Array (mapMaybe)
import Gargantext.Ends (Frontends) import Gargantext.Ends (Frontends)
...@@ -38,7 +39,7 @@ import Gargantext.Hooks.FirstEffect (useFirstEffect') ...@@ -38,7 +39,7 @@ import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Hooks.Sigmax.Types as SigmaxT import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Sessions (Session) import Gargantext.Sessions (Session)
import Gargantext.Types (CTabNgramType, FrontendError(..), NodeID, TabSubType(..), TabType(..), TermList(..), modeTabType) import Gargantext.Types (CTabNgramType, FrontendError(..), NodeID, TabSubType(..), TabType(..), TermList(..), modeTabType)
import Gargantext.Utils (nbsp, setter, (?)) import Gargantext.Utils (getter, nbsp, setter, (?))
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
import Gargantext.Utils.Toestand as T2 import Gargantext.Utils.Toestand as T2
import Partial.Unsafe (unsafePartial) import Partial.Unsafe (unsafePartial)
...@@ -101,15 +102,53 @@ sideTabLegend :: R2.Leaf Props ...@@ -101,15 +102,53 @@ sideTabLegend :: R2.Leaf Props
sideTabLegend = R2.leaf sideTabLegendCpt sideTabLegend = R2.leaf sideTabLegendCpt
sideTabLegendCpt :: R.Component Props sideTabLegendCpt :: R.Component Props
sideTabLegendCpt = here.component "sideTabLegend" cpt sideTabLegendCpt = here.component "sideTabLegend" cpt where
where cpt { metaData: GET.MetaData { legend } } _ = do
cpt { metaData: GET.MetaData { legend } } _ = pure $ -- | States
-- |
store <- GraphStore.use
hyperdataGraph
<- R2.useLive' store.hyperdataGraph
-- | Computed
-- |
let
maxItemPerCluster = 4
-- | Hooks
-- |
-- For each provided Cluster (see Legend), extract the greatest nodes
extractedNodeList <- R.useMemo1 hyperdataGraph $ const $
flip A.foldMap legend
( getter _.id_
>>> GEU.takeGreatestNodeByCluster
hyperdataGraph
maxItemPerCluster
)
-- For each provided Cluster (see Legend), count the number of nodes
nodeCountList <- R.useMemo1 hyperdataGraph $ const $
flip A.foldMap legend
( getter _.id_
>>> GEU.countNodeByCluster hyperdataGraph
>>> A.singleton
)
-- | Render
-- |
pure $
H.div H.div
{ className: "graph-sidebar__legend-tab" } { className: "graph-sidebar__legend-tab" }
[ [
Legend.legend Legend.legend
{ items: Seq.fromFoldable legend } { legendSeq: Seq.fromFoldable legend
, extractedNodeList
, nodeCountList
, selectedNodeIds: store.selectedNodeIds
}
, ,
H.hr {} H.hr {}
, ,
......
...@@ -4,42 +4,196 @@ module Gargantext.Components.GraphExplorer.Sidebar.Legend ...@@ -4,42 +4,196 @@ module Gargantext.Components.GraphExplorer.Sidebar.Legend
import Prelude hiding (map) import Prelude hiding (map)
import Data.Array as A
import Data.Maybe (isJust, maybe)
import Data.Sequence (Seq) import Data.Sequence (Seq)
import Data.Traversable (foldMap) import Data.Set as Set
import Data.Traversable (foldMap, intercalate)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.Types as ST
import Gargantext.Utils (getter, nbsp, (?))
import Gargantext.Utils.Reactix as R2
import Reactix as R import Reactix as R
import Reactix.DOM.HTML as H import Reactix.DOM.HTML as H
import Toestand as T
import Gargantext.Components.GraphExplorer.Types (Legend(..), intColor)
import Gargantext.Utils.Reactix as R2
here :: R2.Here here :: R2.Here
here = R2.here "Gargantext.Components.GraphExplorer.Sidebar.Legend" here = R2.here "Gargantext.Components.GraphExplorer.Sidebar.Legend"
type Props = ( items :: Seq Legend ) type Props =
( legendSeq :: Seq GET.Legend
, extractedNodeList :: Array GET.Node
, nodeCountList :: Array GET.ClusterCount
, selectedNodeIds :: T.Box ST.NodeIds
)
legend :: R2.Leaf Props legend :: R2.Leaf Props
legend = R2.leaf legendCpt legend = R2.leaf legendCpt
legendCpt :: R.Component Props legendCpt :: R.Component Props
legendCpt = here.component "legend" cpt where legendCpt = here.component "legend" cpt where
cpt { items } _ = pure $ cpt { legendSeq
, extractedNodeList
, nodeCountList
, selectedNodeIds
} _ = do
-- | Hooks
-- |
R.useEffectOnce' $ here.info2 "legend" extractedNodeList
-- | Render
-- |
pure $
H.ul H.ul
{ className: "graph-legend" } { className: "graph-legend" }
[ [
flip foldMap items \(Legend { id_, label }) -> flip foldMap legendSeq \(GET.Legend { id_, label }) ->
H.li H.li
{ className: "graph-legend__item" } { className: "graph-legend__item" }
[ [
H.span H.div
{ className: "graph-legend__code" { className: "graph-legend__code"
, style: { backgroundColor: intColor id_ } , style: { backgroundColor: GET.intColor id_ }
} }
[] []
, ,
H.span B.wad
{ className: "graph-legend__caption" } [ "flex-grow-1" ]
[ H.text label ] [
B.div'
{ className: "graph-legend__title" }
label
,
selectedNodes
{ selectedNodeIds
, extractedNodeList
, clusterId: id_
, nodeCount: getClusterNodeCount nodeCountList id_
}
]
]
]
filterByCluster :: Int -> Array GET.Node -> Array GET.Node
filterByCluster id
= A.filter
( getter _.attributes
>>> getter _.clustDefault
>>> eq id
)
getClusterNodeCount :: Array GET.ClusterCount -> Int -> Int
getClusterNodeCount nodeCountList id
= nodeCountList
# A.find
( getter _.id
>>> eq id
)
>>> maybe 0
( getter _.count
)
---------------------------------------------------------
type SelectedNodesProps =
( extractedNodeList :: Array GET.Node
, selectedNodeIds :: T.Box ST.NodeIds
, clusterId :: Int
, nodeCount :: Int
)
selectedNodes :: R2.Leaf SelectedNodesProps
selectedNodes = R2.leaf selectedNodesCpt
selectedNodesCpt :: R.Component SelectedNodesProps
selectedNodesCpt = here.component "selectedNodes" cpt where
cpt { extractedNodeList
, selectedNodeIds
, clusterId
, nodeCount
} _ = do
-- | States
-- |
selectedNodeIds' <- R2.useLive' selectedNodeIds
-- | Computed
-- |
let
isSelected id
= selectedNodeIds'
# A.fromFoldable
# A.find
( eq id
)
# isJust
countValue
= extractedNodeList
# A.length
# (nodeCount - _)
-- | Behaviors
-- |
let
onBadgeClick id _ = T.write_ (Set.singleton id) selectedNodeIds
-- | Render
-- |
pure $
H.ul
{ className: "graph-legend-nodes" }
[
flip foldMap (filterByCluster clusterId extractedNodeList)
\(GET.Node { label: nodeLabel, id_: nodeId }) ->
H.li
{ className: "graph-legend-nodes__item" }
[
H.a
{ className: intercalate " "
[ "graph-legend-nodes__badge"
, (isSelected nodeId) ?
"graph-legend-nodes__badge--selected" $
""
, "badge badge-light"
]
, on: { click: onBadgeClick nodeId }
}
[ H.text nodeLabel ]
]
,
R2.when (eq countValue 0) $
H.li
{ className: intercalate " "
[ "graph-legend-nodes__item"
, "graph-legend-nodes__item--count"
]
}
[
H.text "0 node"
]
,
R2.when (not $ eq countValue 0) $
H.li
{ className: intercalate " "
[ "graph-legend-nodes__item"
, "graph-legend-nodes__item--count"
]
}
[
H.text "+"
,
H.text $ nbsp 1
,
H.text $ show countValue
,
H.text $ nbsp 1
,
H.text $ eq countValue 1 ? "node" $ "nodes"
] ]
] ]
...@@ -72,6 +72,12 @@ instance JSON.ReadForeign Cluster where ...@@ -72,6 +72,12 @@ instance JSON.ReadForeign Cluster where
instance JSON.WriteForeign Cluster where instance JSON.WriteForeign Cluster where
writeImpl (Cluster cl) = JSON.writeImpl $ Record.rename clustDefaultP clust_defaultP cl writeImpl (Cluster cl) = JSON.writeImpl $ Record.rename clustDefaultP clust_defaultP cl
newtype ClusterCount = ClusterCount
{ id :: Int
, count :: Int
}
derive instance Generic ClusterCount _
derive instance Newtype ClusterCount _
newtype Edge = Edge { newtype Edge = Edge {
confluence :: Number confluence :: Number
......
module Gargantext.Components.GraphExplorer.Utils where module Gargantext.Components.GraphExplorer.Utils
( stEdgeToGET, stNodeToGET
import Data.Maybe (Maybe(..)) , normalizeNodes
, takeGreatestNodeByCluster, countNodeByCluster
) where
import Gargantext.Prelude import Gargantext.Prelude
import Data.Array as A
import Data.Maybe (Maybe(..))
import Data.Newtype (wrap)
import Gargantext.Components.GraphExplorer.Types as GET import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Hooks.Sigmax.Types as ST import Gargantext.Hooks.Sigmax.Types as ST
import Gargantext.Utils (getter)
import Gargantext.Utils.Array as GUA import Gargantext.Utils.Array as GUA
stEdgeToGET :: Record ST.Edge -> GET.Edge stEdgeToGET :: Record ST.Edge -> GET.Edge
stEdgeToGET { _original } = _original stEdgeToGET { _original } = _original
...@@ -24,6 +29,8 @@ stNodeToGET { id, label, x, y, _original: GET.Node { attributes, size, type_ } } ...@@ -24,6 +29,8 @@ stNodeToGET { id, label, x, y, _original: GET.Node { attributes, size, type_ } }
, y , y
} }
-----------------------------------------------------------------------
normalizeNodes :: Array GET.Node -> Array GET.Node normalizeNodes :: Array GET.Node -> Array GET.Node
normalizeNodes ns = map normalizeNode ns normalizeNodes ns = map normalizeNode ns
where where
...@@ -49,3 +56,37 @@ normalizeNodes ns = map normalizeNode ns ...@@ -49,3 +56,37 @@ normalizeNodes ns = map normalizeNode ns
Just ydiv -> 1.0 / ydiv Just ydiv -> 1.0 / ydiv
normalizeNode (GET.Node n@{ x, y }) = GET.Node $ n { x = x * xdivisor normalizeNode (GET.Node n@{ x, y }) = GET.Node $ n { x = x * xdivisor
, y = y * ydivisor } , y = y * ydivisor }
------------------------------------------------------------------------
takeGreatestNodeByCluster :: GET.HyperdataGraph -> Int -> Int -> Array GET.Node
takeGreatestNodeByCluster graphData take clusterId
= graphData
# getter _.graph
>>> getter _.nodes
>>> A.filter
( getter _.attributes
>>> getter _.clustDefault
>>> eq clusterId
)
>>> A.sortWith
( getter _.size
)
>>> A.takeEnd take
>>> A.reverse
countNodeByCluster :: GET.HyperdataGraph -> Int -> GET.ClusterCount
countNodeByCluster graphData clusterId
= graphData
# getter _.graph
>>> getter _.nodes
>>> A.filter
( getter _.attributes
>>> getter _.clustDefault
>>> eq clusterId
)
>>> A.length
>>> { id: clusterId
, count: _
}
>>> wrap
...@@ -14,6 +14,7 @@ import Gargantext.Components.GraphQL.IMT as GQLIMT ...@@ -14,6 +14,7 @@ import Gargantext.Components.GraphQL.IMT as GQLIMT
import Gargantext.Components.GraphQL.Node (Node) import Gargantext.Components.GraphQL.Node (Node)
import Gargantext.Components.GraphQL.Tree (TreeFirstLevel) import Gargantext.Components.GraphQL.Tree (TreeFirstLevel)
import Gargantext.Components.GraphQL.User (User, UserInfo, UserInfoM) import Gargantext.Components.GraphQL.User (User, UserInfo, UserInfoM)
import Gargantext.Components.GraphQL.Team (TeamMember, TeamDeleteM)
import Gargantext.Ends (Backend(..)) import Gargantext.Ends (Backend(..))
import Gargantext.Sessions (Session(..)) import Gargantext.Sessions (Session(..))
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
...@@ -77,7 +78,9 @@ type Schema ...@@ -77,7 +78,9 @@ type Schema
, users :: { user_id :: Int } ==> Array User , users :: { user_id :: Int } ==> Array User
, tree :: { root_id :: Int } ==> TreeFirstLevel , tree :: { root_id :: Int } ==> TreeFirstLevel
, annuaire_contacts :: { contact_id :: Int } ==> Array AnnuaireContact , annuaire_contacts :: { contact_id :: Int } ==> Array AnnuaireContact
, team :: { team_node_id :: Int } ==> Array TeamMember
} }
type Mutation type Mutation
= { update_user_info :: UserInfoM ==> Int } = { update_user_info :: UserInfoM ==> Int
, delete_team_membership :: TeamDeleteM ==> Array Int }
...@@ -2,22 +2,24 @@ module Gargantext.Components.GraphQL.Endpoints where ...@@ -2,22 +2,24 @@ module Gargantext.Components.GraphQL.Endpoints where
import Gargantext.Prelude import Gargantext.Prelude
import Gargantext.Components.GraphQL.Node (Node, nodeParentQuery, nodesQuery)
import Gargantext.Components.GraphQL.Tree (TreeFirstLevel, treeFirstLevelQuery)
import Gargantext.Components.GraphQL.User (UserInfo, userInfoQuery)
import Data.Array as A import Data.Array as A
import Data.Either (Either(..)) import Data.Either (Either(..))
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Effect.Aff (Aff) import Effect.Aff (Aff)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Gargantext.Components.GraphQL (queryGql) import Gargantext.Components.GraphQL (getClient, queryGql)
import Gargantext.Components.GraphQL.IMT as GQLIMT import Gargantext.Components.GraphQL.IMT as GQLIMT
import Gargantext.Components.GraphQL.Node (Node, nodeParentQuery, nodesQuery)
import Gargantext.Components.GraphQL.Team (TeamMember, teamQuery)
import Gargantext.Components.GraphQL.Tree (TreeFirstLevel, treeFirstLevelQuery)
import Gargantext.Components.GraphQL.User (UserInfo, userInfoQuery)
import Gargantext.Config.REST (RESTError(..), AffRESTError) import Gargantext.Config.REST (RESTError(..), AffRESTError)
import Gargantext.Sessions (Session) import Gargantext.Sessions (Session(..))
import Gargantext.Types (NodeType) import Gargantext.Types (NodeType)
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
import Gargnatext.Components.GraphQL.Contact (AnnuaireContact, annuaireContactQuery) import Gargnatext.Components.GraphQL.Contact (AnnuaireContact, annuaireContactQuery)
import GraphQL.Client.Args (onlyArgs)
import GraphQL.Client.Query (mutation)
import GraphQL.Client.Variables (withVars) import GraphQL.Client.Variables (withVars)
here :: R2.Here here :: R2.Here
...@@ -70,3 +72,28 @@ getTreeFirstLevel session id = do ...@@ -70,3 +72,28 @@ getTreeFirstLevel session id = do
{ tree } <- queryGql session "get tree first level" $ treeFirstLevelQuery `withVars` { id } { tree } <- queryGql session "get tree first level" $ treeFirstLevelQuery `withVars` { id }
liftEffect $ here.log2 "[getTreeFirstLevel] tree first level" tree liftEffect $ here.log2 "[getTreeFirstLevel] tree first level" tree
pure $ Right tree -- TODO: error handling pure $ Right tree -- TODO: error handling
getTeam :: Session -> Int -> AffRESTError (Array TeamMember)
getTeam session id = do
{ team } <- queryGql session "get team" $ teamQuery `withVars` { id }
liftEffect $ here.log2 "[getTree] data" team
pure $ Right team
type SharedFolderId = Int
type TeamNodeId = Int
deleteTeamMembership :: Session -> SharedFolderId -> TeamNodeId -> AffRESTError Int
deleteTeamMembership session sharedFolderId teamNodeId = do
let token = getToken session
client <- liftEffect $ getClient session
{ delete_team_membership } <- mutation
client
"delete_team_membership"
{ delete_team_membership: onlyArgs { token: token
, shared_folder_id: sharedFolderId
, team_node_id: teamNodeId } }
pure $ case A.head delete_team_membership of
Nothing -> Left (CustomError $ "Failed to delete team membership. team node id=" <> show teamNodeId <> " shared folder id=" <> show sharedFolderId)
Just _ -> Right sharedFolderId
where
getToken (Session { token }) = token
module Gargantext.Components.GraphQL.Team where
import Gargantext.Prelude
import GraphQL.Client.Args (NotNull, (=>>))
import GraphQL.Client.Variable (Var(..))
type TeamMember
= { username :: String
, shared_folder_id :: Int
}
type TeamDeleteM
= { token :: NotNull String
, shared_folder_id :: Int
, team_node_id :: Int
}
teamQuery = { team: { team_node_id: Var :: _ "id" Int } =>>
{ username: unit
, shared_folder_id: unit }
}
\ No newline at end of file
...@@ -5,9 +5,11 @@ module Gargantext.Components.Login where ...@@ -5,9 +5,11 @@ module Gargantext.Components.Login where
import Gargantext.Prelude import Gargantext.Prelude
import DOM.Simple.Event as DE
import Data.Array (head) import Data.Array (head)
import Data.Maybe (Maybe(..), fromMaybe) import Data.Maybe (Maybe(..), fromMaybe)
import Data.String as DST import Data.String as DST
import Effect (Effect)
import Effect.Aff (launchAff_) import Effect.Aff (launchAff_)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Gargantext.Components.Bootstrap as B import Gargantext.Components.Bootstrap as B
...@@ -23,6 +25,7 @@ import Gargantext.Sessions as Sessions ...@@ -23,6 +25,7 @@ import Gargantext.Sessions as Sessions
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
import Reactix as R import Reactix as R
import Reactix.DOM.HTML as H import Reactix.DOM.HTML as H
import Reactix.SyntheticEvent as RE
import Toestand as T import Toestand as T
here :: R2.Here here :: R2.Here
...@@ -44,6 +47,27 @@ login :: R2.Leaf Props ...@@ -44,6 +47,27 @@ login :: R2.Leaf Props
login = R2.leaf loginCpt login = R2.leaf loginCpt
loginCpt :: R.Component Props loginCpt :: R.Component Props
loginCpt = here.component "login" cpt where loginCpt = here.component "login" cpt where
cpt props@{ visible } _ = do
-- Render
pure $
B.baseModal
{ isVisibleBox: visible
, title: Just "GarganText ecosystem explorer"
, size: ExtraLargeModalSize
}
[
loginContainer
props
]
-- | @XXX React re-rendering issue with `React.Portal`
-- | @link https://github.com/facebook/react/issues/12247
loginContainer :: R2.Leaf Props
loginContainer = R2.leaf loginContainerCpt
loginContainerCpt :: R.Component Props
loginContainerCpt = here.component "container" cpt where
cpt props@{ sessions, visible } _ = do cpt props@{ sessions, visible } _ = do
-- States -- States
mBackend <- R2.useLive' props.backend mBackend <- R2.useLive' props.backend
...@@ -53,49 +77,96 @@ loginCpt = here.component "login" cpt where ...@@ -53,49 +77,96 @@ loginCpt = here.component "login" cpt where
-- Render -- Render
pure $ pure $
B.baseModal H.div
{ isVisibleBox: visible {}
, title: Just "GarganText ecosystem explorer"
, size: ExtraLargeModalSize
}
[ [
case mBackend of case mBackend of
Nothing -> chooser props Nothing -> chooser props
Just backend -> case formType' of Just backend -> case formType' of
Login -> form { backend, formType, sessions, visible } Login ->
ForgotPassword -> forgotPassword { backend, sessions } form
{ backend
, formType
, sessions
, visible
}
ForgotPassword ->
forgotPassword
{ backend
, sessions
}
] ]
chooser :: R2.Leaf Props chooser :: R2.Leaf Props
chooser = R2.leafComponent chooserCpt chooser = R2.leaf chooserCpt
chooserCpt :: R.Component Props chooserCpt :: R.Component Props
chooserCpt = here.component "chooser" cpt where chooserCpt = here.component "chooser" cpt where
cpt { backend, backends, sessions } _ = do cpt { backend, backends, sessions } _ = do
sessions' <- T.useLive T.unequal sessions sessions' <- T.useLive T.unequal sessions
pure $ pure $
R.fragment $ H.div
[ H.h2 { className: "mx-auto" } [ H.text "Workspace manager" ]] {} $
<> activeConnections sessions sessions' <> [
[ H.h3 {} [ H.text "Existing places (click to login)" ] H.h2
, H.table { className : "table" } { className: "mx-auto" }
[ H.thead { className: "thead-light" } [ H.text "Workspace manager" ]
[ H.tr {} (map header headers) ] ]
, H.tbody {} (map (renderBackend backend) backends) <>
activeConnections sessions sessions'
<>
[
H.h3
{}
[ H.text "Existing places (click to login)" ]
,
H.table
{ className : "table" }
[
H.thead
{ className: "thead-light" }
[
H.tr
{}
(map header headers)
]
,
H.tbody
{}
(map (renderBackend backend) backends)
]
,
H.input
{ className: "form-control"
, type:"text"
, placeholder: "Search for your institute"
}
] ]
, H.input { className: "form-control", type:"text", placeholder } ]
placeholder = "Search for your institute"
headers = [ "", "GarganText places", "Fonction", "Garg protocol url" ] headers = [ "", "GarganText places", "Fonction", "Garg protocol url" ]
header label = H.th {} [ H.text label ] header label = H.th {} [ H.text label ]
-- Shown in the chooser -- Shown in the chooser
activeConnections :: forall s. T.ReadWrite s Sessions => s -> Sessions -> Array R.Element activeConnections :: forall s. T.ReadWrite s Sessions => s -> Sessions -> Array R.Element
activeConnections _ sessions' | Sessions.null sessions' = [] activeConnections _ sessions' | Sessions.null sessions' = mempty
activeConnections sessions sessions' = activeConnections sessions sessions' =
[ H.h3 {} [ H.text "Active place(s)" ] [
, H.table { className : "table" } H.h3
[ H.thead { className: "thead-light" } {}
[ H.tr {} (map header headers) ] [ H.text "Active place(s)" ]
, H.tbody {} [renderSessions sessions sessions'] ,
H.table
{ className : "table" }
[
H.thead
{ className: "thead-light" }
[
H.tr
{}
(map header headers)
]
,
H.tbody
{}
[ renderSessions sessions sessions' ]
] ]
] ]
where where
...@@ -144,7 +215,11 @@ renderBackend cursor backend@(Backend {name, baseUrl, backendType}) = ...@@ -144,7 +215,11 @@ renderBackend cursor backend@(Backend {name, baseUrl, backendType}) =
] ]
where where
className = "fa fa-hand-o-right" -- "glyphitem fa fa-log-in" className = "fa fa-hand-o-right" -- "glyphitem fa fa-log-in"
click _ = T.write_ (Just backend) cursor
click :: RE.SyntheticEvent DE.Event -> Effect Unit
click e = do
RE.preventDefault e
T.write_ (Just backend) cursor
backendLabel :: String -> String backendLabel :: String -> String
backendLabel = backendLabel =
......
...@@ -957,7 +957,7 @@ mainNgramsTableCacheOnCpt = here.component "mainNgramsTableCacheOn" cpt where ...@@ -957,7 +957,7 @@ mainNgramsTableCacheOnCpt = here.component "mainNgramsTableCacheOn" cpt where
, spinnerClass: Nothing , spinnerClass: Nothing
} }
versionEndpoint { defaultListId, path: { nodeId, tabType, session } } _ = get session $ Routes.GetNgramsTableVersion { listId: defaultListId, tabType } (Just nodeId) versionEndpoint { defaultListId, path: { nodeId, tabType, session } } _ = get session $ Routes.GetNgramsTableVersion { listId: defaultListId, tabType } (Just nodeId)
errorHandler = logRESTError here "[mainNgramsTable]" errorHandler = logRESTError here "[mainNgramsTableCacheOn]"
mkRequest :: PageParams -> GUC.Request mkRequest :: PageParams -> GUC.Request
mkRequest path@{ session } = GUC.makeGetRequest session $ url path mkRequest path@{ session } = GUC.makeGetRequest session $ url path
where where
...@@ -994,7 +994,7 @@ mainNgramsTableCacheOffCpt = here.component "mainNgramsTableCacheOff" cpt where ...@@ -994,7 +994,7 @@ mainNgramsTableCacheOffCpt = here.component "mainNgramsTableCacheOff" cpt where
, path , path
, render } , render }
errorHandler = logRESTError here "[mainNgramsTable]" errorHandler = logRESTError here "[mainNgramsTableCacheOff]"
-- NOTE With cache off -- NOTE With cache off
loader :: PageParams -> AffRESTError VersionedWithCountNgramsTable loader :: PageParams -> AffRESTError VersionedWithCountNgramsTable
......
...@@ -575,10 +575,12 @@ listsCpt = here.component "lists" cpt where ...@@ -575,10 +575,12 @@ listsCpt = here.component "lists" cpt where
login' :: Boxes -> R.Element login' :: Boxes -> R.Element
login' { backend, sessions, showLogin: visible } = login' { backend, sessions, showLogin: visible } =
login { backend login
{ backend
, backends: A.fromFoldable defaultBackends , backends: A.fromFoldable defaultBackends
, sessions , sessions
, visible } , visible
}
-------------------------------------------------------------- --------------------------------------------------------------
......
...@@ -84,6 +84,7 @@ useLoaderEffect { errorHandler, loader: loader', path, state } = do ...@@ -84,6 +84,7 @@ useLoaderEffect { errorHandler, loader: loader', path, state } = do
then pure $ R.nothing then pure $ R.nothing
else do else do
R.setRef oPath path R.setRef oPath path
liftEffect $ T.write_ Nothing state
R2.affEffect "G.H.Loader.useLoaderEffect" $ do R2.affEffect "G.H.Loader.useLoaderEffect" $ do
l <- loader' path l <- loader' path
case l of case l of
...@@ -102,31 +103,9 @@ useLoaderBox :: forall path st. Eq path => Eq st ...@@ -102,31 +103,9 @@ useLoaderBox :: forall path st. Eq path => Eq st
=> Record (UseLoaderBox path st) => Record (UseLoaderBox path st)
-> R.Hooks R.Element -> R.Hooks R.Element
useLoaderBox { errorHandler, loader: loader', path, render } = do useLoaderBox { errorHandler, loader: loader', path, render } = do
state <- T.useBox Nothing
useLoaderBoxEffect { errorHandler, loader: loader', path, state: state }
pure $ loader { render, state } []
type UseLoaderBoxEffect path state =
( errorHandler :: RESTError -> Effect Unit
, loader :: path -> AffRESTError state
, path :: T.Box path
, state :: T.Box (Maybe state)
)
useLoaderBoxEffect :: forall st path. Eq path => Eq st
=> Record (UseLoaderBoxEffect path st)
-> R.Hooks Unit
useLoaderBoxEffect { errorHandler, loader: loader', path, state } = do
path' <- T.useLive T.unequal path path' <- T.useLive T.unequal path
R.useEffect' $ do useLoader { errorHandler, loader: loader', path: path', render }
R2.affEffect "G.H.Loader.useLoaderBoxEffect" $ do
l <- loader' path'
case l of
Left err -> liftEffect $ errorHandler err
Right l' -> liftEffect $ T.write_ (Just l') state
newtype HashedResponse a = HashedResponse { hash :: Hash, value :: a } newtype HashedResponse a = HashedResponse { hash :: Hash, value :: a }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
exports._add = add; exports._add = add;
exports._remove = remove; exports._remove = remove;
/** /**
* @name add * @function add
* @param {Window} window * @param {Window} window
* @param {Document} document * @param {Document} document
* @param {String} sourceQuery * @param {String} sourceQuery
...@@ -22,13 +22,18 @@ function add(window, document, sourceQuery, targetQuery, type) { ...@@ -22,13 +22,18 @@ function add(window, document, sourceQuery, targetQuery, type) {
} }
function startResizing(e) { function startResizing(e) {
var height = e.clientY - target.offsetTop
var width = e.clientX - target.offsetLeft
if (type === 'both' || type === 'horizontal') if (type === 'both' || type === 'horizontal')
target.style.height = (e.clientY - target.offsetTop) + 'px'; target.style.height = height + 'px';
if (type === 'both' || type === 'vertical') if (type === 'both' || type === 'vertical')
target.style.width = (e.clientX - target.offsetLeft) + 'px'; target.style.width = width + 'px';
// prevent "user-select" highlights // prevent "user-select" highlights
document.body.classList.add('no-user-select'); document.body.classList.add('no-user-select');
// prevent event focus losing (eg. while hovering iframe, see #422)
document.body.classList.add('no-pointer-events');
} }
function stopResizing(e) { function stopResizing(e) {
...@@ -36,10 +41,11 @@ function add(window, document, sourceQuery, targetQuery, type) { ...@@ -36,10 +41,11 @@ function add(window, document, sourceQuery, targetQuery, type) {
window.removeEventListener('mouseup', stopResizing, false); window.removeEventListener('mouseup', stopResizing, false);
document.body.classList.remove('no-user-select'); document.body.classList.remove('no-user-select');
document.body.classList.remove('no-pointer-events');
} }
} }
/** /**
* @name remove * @function remove
* @param {Document} document * @param {Document} document
* @param {String} sourceQuery * @param {String} sourceQuery
*/ */
......
...@@ -17,6 +17,10 @@ $document-container-width: 780px ...@@ -17,6 +17,10 @@ $document-container-width: 780px
border-bottom: 1px solid $border-color border-bottom: 1px solid $border-color
margin-bottom: $card-spacer-y margin-bottom: $card-spacer-y
&__main-controls
display: flex
align-items: center
&__side-controls &__side-controls
display: flex display: flex
align-items: center align-items: center
......
...@@ -142,8 +142,19 @@ ...@@ -142,8 +142,19 @@
$legend-code-height: 12px $legend-code-height: 12px
&__item &__item
display: flex
align-items: baseline
list-style: none list-style: none
margin-bottom: space-x(0.75) position: relative
&:not(:first-child)
margin-top: space-x(3)
&__title
color: $gray-800
font-size: 15px
font-weight: bold
margin-bottom: space-x(0.25)
&__code &__code
width: $legend-code-width width: $legend-code-width
...@@ -152,8 +163,32 @@ ...@@ -152,8 +163,32 @@
margin-right: space-x(2.5) margin-right: space-x(2.5)
border: 1px solid $gray-500 border: 1px solid $gray-500
&__caption
vertical-align: top .graph-legend-nodes
&__item
display: inline-block
&:first-child
margin-top: space-x(0.75)
&:not(:last-child)
margin-bottom: space-x(0.5)
margin-right: space-x(0.5)
&--count
color: $gray-800
font-size: 12px
font-weight: bold
&__badge
font-size: 13px
white-space: normal
word-break: break-word
&--selected
background-color: darken($light, 10%) // from Bootstrap "_badge.scss"
.graph-documentation .graph-documentation
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
.no-user-select .no-user-select
user-select: none user-select: none
.no-pointer-events
pointer-events: none
.side-panel .side-panel
background-color: #fff background-color: #fff
padding: 0.3em padding: 0.3em
......
...@@ -74,12 +74,18 @@ ...@@ -74,12 +74,18 @@
} }
} }
// Browser cursor consistency
.custom-select:disabled,
.form-control:disabled {
@include unclickable();
}
// Rectify Bootstrap readonly UI // Rectify Bootstrap readonly UI
.custom-select:disabled[readonly], .custom-select:disabled[readonly],
.form-control[readonly] { .form-control[readonly] {
background-color: $input-bg; background-color: $input-bg;
// (opinionated rules) // (opinionated rules)
color: $input-placeholder-color; // color: $input-placeholder-color;
border-style: dashed; border-style: dashed;
} }
......
This diff is collapsed.
...@@ -312,7 +312,7 @@ ...@@ -312,7 +312,7 @@
transition: transform 0s, opacity 0s; transition: transform 0s, opacity 0s;
} }
@each $name, $value in $theme-colors { @each $name, $value in $palette-semantic {
&--#{ $name }:after { &--#{ $name }:after {
background-image: background-image:
......
...@@ -23,6 +23,20 @@ $warning :#0DE2EA; ...@@ -23,6 +23,20 @@ $warning :#0DE2EA;
$light :#e8e8e8; $light :#e8e8e8;
$dark :#000000; $dark :#000000;
$gray-50 : #121212; // (+)
$gray-100: #212529;
$gray-150: #2E2E2E; // (+)
$gray-175: #333333; // (+)
$gray-200: #343A40;
$gray-300: #495057;
$gray-400: #6C757D;
$gray-500: #ADB5BD;
$gray-600: #CED4DA;
$gray-700: #DEE2E6;
$gray-800: #E9ECEF;
$gray-900: #F8F9FA;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
...@@ -36,20 +50,35 @@ $theme-colors: map.merge( ...@@ -36,20 +50,35 @@ $theme-colors: map.merge(
'dark': $dark, 'dark': $dark,
) )
); );
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$gray-50 : #121212; // (+) $palette-pastel: (
$gray-100: #212529; 'green': $pastel-green,
$gray-150: #2E2E2E; // (+) 'blue': $pastel-blue,
$gray-175: #333333; // (+) 'yellow': $pastel-yellow,
$gray-200: #343A40; 'red': $pastel-red,
$gray-300: #495057; 'orange': $pastel-orange,
$gray-400: #6C757D; 'purple': $pastel-purple
$gray-500: #ADB5BD; );
$gray-600: #CED4DA;
$gray-700: #DEE2E6;
$gray-800: #E9ECEF;
$gray-900: #F8F9FA;
// Components
$annotation-text-color: $gray-600; $annotation-text-color: $gray-600;
$annotation-field-base-alpha: 15%; $annotation-field-base-alpha: 15%;
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
$primary: #005a9a; $primary: #005a9a;
$secondary: $blue; $secondary: $blue;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
...@@ -20,6 +21,33 @@ $theme-colors: map.merge( ...@@ -20,6 +21,33 @@ $theme-colors: map.merge(
"secondary": $secondary, "secondary": $secondary,
) )
); );
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
///========================================== ///==========================================
......
...@@ -28,6 +28,7 @@ $warning :#6e9fa5; ...@@ -28,6 +28,7 @@ $warning :#6e9fa5;
$light :#eceeec; $light :#eceeec;
$dark :#1e2b37; $dark :#1e2b37;
// Palettes
$theme-colors: map.merge( $theme-colors: map.merge(
$theme-colors, $theme-colors,
( (
...@@ -41,6 +42,33 @@ $theme-colors: map.merge( ...@@ -41,6 +42,33 @@ $theme-colors: map.merge(
'dark': $dark, 'dark': $dark,
) )
); );
$palette-semantic: $theme-colors;
$palette-gray: (
'0': $white,
'50': $gray-50,
'100': $gray-100,
'150': $gray-150,
'175': $gray-175,
'200': $gray-200,
'300': $gray-300,
'400': $gray-400,
'500': $gray-500,
'600': $gray-600,
'700': $gray-700,
'800': $gray-800,
'900': $gray-900,
'1000': $black
);
$palette-pastel: (
'green': $pastel-green,
'blue': $pastel-blue,
'yellow': $pastel-yellow,
'red': $pastel-red,
'orange': $pastel-orange,
'purple': $pastel-purple
);
// Misc // Misc
$enable-rounded: false; $enable-rounded: false;
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment