Verified Commit a8b823d3 authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

Merge branch 'dev' into 362-dev-sqlite

parents d75a1f93 d49a33a6
Pipeline #7508 passed with stages
in 21 minutes and 56 seconds
## Version 0.0.7.4.7
* [BACK][FIX][Adjust the output of the UpdateList tests (#460)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/460)
* [BACK][FIX][Import/export in SQLite format (#362)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/362)
* [FRONT][FIX][When the progress bar is empty: say: waiting task (#503)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/503)
## Version 0.0.7.4.6
* [BACK][FIX][project in `docker-compose` (#450)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/450)
* [BACK][FIX][Upgrade GHC to 9.6.x (#436)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/436) and [Merge Request](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/merge_requests/395)
* [BACK][FIX][Error during import of term in TSV format (#381)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/381)
* [BACK][FIX][Loading a terms file with empty terms gives an undecipherable and inconsistent error (#395)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/395) and [Merge Request](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/merge_requests/399)
## Version 0.0.7.4.5.1
* [FRONT][FIX][[Corpus import/upload] The error message has disappeared on version 0.0.7.4.2 at least (#728)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/728)
## Version 0.0.7.4.5
* [BACK][FIX][Error when uploading a specific TSV file (#433)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/433)
## Version 0.0.7.4.4
* [BACK][FIX][Order 1 advanced distance (#445)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/445)
* [FRONT][FIX][Frontend for bridgeness method choice (#730)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/730)
* [FRONT][FIX][Unify CSS files to a single syntax format (#712)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/712)
* [FRONT][FIX][Upgrade sigma.js (#705)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/705)
* [FRONT][FIX][Subcorpus frontend (#718)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/718)
* [FRONT][FIX][[Corpus upload] Fix an error on form select "NoList" option (#729)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/729)
* [FRONT][FIX][Basic feature flag hook (#721)](https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/721)
## Version 0.0.7.4.3
* [BACK][UPGRADE][Remove obsolete GHC option (#388)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/388)
* [BACK][REFACT][Error in corpus upload / construction are not reflected in the overall JobStatus (#390)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/390)
* [BACK][FIX][Write test(s) for "ngrams scores do not account for trashed documents" (#391)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/391)
## Version 0.0.7.4.2 ## Version 0.0.7.4.2
* [BACK][FIX][Let users create a Subcorpus (#384)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/384) * [BACK][FIX][Let users create a Subcorpus (#384)](https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/384)
......
#!/bin/bash
nix develop
purs-tidy format-in-place "src"
{ {
description = "Gargantext PureScript fronetnd"; description = "Gargantext PureScript frontend";
inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.flake-utils.url = "github:numtide/flake-utils";
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
# compile # compile
echo "Bundling" echo "Bundling"
npm run bundle npm run bundle
''; '';
}; };
test-ps = pkgs.writeShellApplication { test-ps = pkgs.writeShellApplication {
......
{ {
"name": "Gargantext", "name": "GarganText",
"version": "0.0.7.4.2", "version": "0.0.7.4.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "Gargantext", "name": "GarganText",
"version": "0.0.7.4.2", "version": "0.0.7.4.7",
"dependencies": { "dependencies": {
"@fontsource/crete-round": "~5.0.12", "@fontsource/crete-round": "~5.0.12",
"@fontsource/montserrat": "~5.0.17", "@fontsource/montserrat": "~5.0.17",
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
"debouncing": "^22.7.25", "debouncing": "^22.7.25",
"echarts": "~5.1.2", "echarts": "~5.1.2",
"echarts-for-react": "~3.0.1", "echarts-for-react": "~3.0.1",
"graphology": "~0.25.1", "graphology": "~0.25.4",
"graphology-communities-louvain": "~2.0.1", "graphology-communities-louvain": "~2.0.1",
"graphology-layout-forceatlas2": "~0.9.2", "graphology-layout-forceatlas2": "~0.9.2",
"graphology-layout-noverlap": "~0.4.2", "graphology-layout-noverlap": "~0.4.2",
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
"http-proxy-middleware": "^3.0.3", "http-proxy-middleware": "^3.0.3",
"immer": "~9.0.5", "immer": "~9.0.5",
"isomorphic-unfetch": "~3.1.0", "isomorphic-unfetch": "~3.1.0",
"js-cookie": "^3.0.5",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"markdown-it": "~13.0.1", "markdown-it": "~13.0.1",
"minify": "^11.3.0", "minify": "^11.3.0",
...@@ -51,7 +52,7 @@ ...@@ -51,7 +52,7 @@
"react-dom": "~18.2.0", "react-dom": "~18.2.0",
"react-tooltip": "~4.2.8", "react-tooltip": "~4.2.8",
"secp256k1": "~4.0.2", "secp256k1": "~4.0.2",
"sigma": "~2.4.0", "sigma": "~3.0.0",
"twgl.js": "~5.0.4", "twgl.js": "~5.0.4",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.0",
"uuid": "8.3.2" "uuid": "8.3.2"
...@@ -3991,10 +3992,6 @@ ...@@ -3991,10 +3992,6 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/@yomguithereal/helpers": {
"version": "1.1.1",
"license": "MIT"
},
"node_modules/@zeit/schemas": { "node_modules/@zeit/schemas": {
"version": "2.36.0", "version": "2.36.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
...@@ -7815,6 +7812,14 @@ ...@@ -7815,6 +7812,14 @@
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"license": "MIT" "license": "MIT"
...@@ -11997,12 +12002,12 @@ ...@@ -11997,12 +12002,12 @@
} }
}, },
"node_modules/sigma": { "node_modules/sigma": {
"version": "2.4.0", "version": "3.0.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/sigma/-/sigma-3.0.0.tgz",
"integrity": "sha512-O3w+sfyarnurPuxcBtkQ6wp5E9d0OctwMiXhQ8YUCiUX5G8Yz5XM6Vo6lsAGtMm/XvQR77+0JOzAIEeo4LpdjA==",
"dependencies": { "dependencies": {
"@yomguithereal/helpers": "^1.1.1",
"events": "^3.3.0", "events": "^3.3.0",
"graphology-utils": "^2.5.0" "graphology-utils": "^2.5.2"
} }
}, },
"node_modules/signal-exit": { "node_modules/signal-exit": {
......
{ {
"name": "Gargantext", "name": "GarganText",
"version": "0.0.7.4.2", "version": "0.0.7.4.7",
"scripts": { "scripts": {
"build": "spago build", "build": "spago build",
"bundle": "spago bundle --module Main --outfile dist/bundle.min.js --minify --source-maps", "bundle": "spago bundle --module Main --outfile dist/bundle.min.js --minify --source-maps",
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
"debouncing": "^22.7.25", "debouncing": "^22.7.25",
"echarts": "~5.1.2", "echarts": "~5.1.2",
"echarts-for-react": "~3.0.1", "echarts-for-react": "~3.0.1",
"graphology": "~0.25.1", "graphology": "~0.25.4",
"graphology-communities-louvain": "~2.0.1", "graphology-communities-louvain": "~2.0.1",
"graphology-layout-forceatlas2": "~0.9.2", "graphology-layout-forceatlas2": "~0.9.2",
"graphology-layout-noverlap": "~0.4.2", "graphology-layout-noverlap": "~0.4.2",
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"immer": "~9.0.5", "immer": "~9.0.5",
"isomorphic-unfetch": "~3.1.0", "isomorphic-unfetch": "~3.1.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"js-cookie": "^3.0.5",
"markdown-it": "~13.0.1", "markdown-it": "~13.0.1",
"minify": "^11.3.0", "minify": "^11.3.0",
"prop-types": "~15.6.2", "prop-types": "~15.6.2",
...@@ -67,7 +68,7 @@ ...@@ -67,7 +68,7 @@
"react-dom": "~18.2.0", "react-dom": "~18.2.0",
"react-tooltip": "~4.2.8", "react-tooltip": "~4.2.8",
"secp256k1": "~4.0.2", "secp256k1": "~4.0.2",
"sigma": "~2.4.0", "sigma": "~3.0.0",
"twgl.js": "~5.0.4", "twgl.js": "~5.0.4",
"use-debounce": "^10.0.0", "use-debounce": "^10.0.0",
"uuid": "8.3.2" "uuid": "8.3.2"
......
(builtins.getFlake ("git+file://" + toString ./.)).devShells.${builtins.currentSystem}.default
\ No newline at end of file
...@@ -3,6 +3,7 @@ module Gargantext.AsyncTasks ...@@ -3,6 +3,7 @@ module Gargantext.AsyncTasks
, TaskList , TaskList
, Storage(..) , Storage(..)
, insert , insert
, hasTask
, finish , finish
, focus , focus
-- , asyncTaskTTriggersAppReload -- , asyncTaskTTriggersAppReload
...@@ -78,6 +79,14 @@ insert id task storageBox = modifyTaskBox newStorage storageBox ...@@ -78,6 +79,14 @@ insert id task storageBox = modifyTaskBox newStorage storageBox
where where
newStorage (Storage s) = Storage $ Map.alter (maybe (Just [ task ]) (\ts -> Just $ A.nub $ A.cons task ts)) id s newStorage (Storage s) = Storage $ Map.alter (maybe (Just [ task ]) (\ts -> Just $ A.nub $ A.cons task ts)) id s
hasTask :: GT.NodeID -> Task -> T.Box Storage -> Effect Boolean
hasTask id (GT.WorkerTask { message_id }) storage = do
Storage storage' <- T.read storage
case Map.lookup id storage' of
Nothing -> pure false
Just taskList ->
pure $ A.any (\(GT.WorkerTask { message_id: mId }) -> mId == message_id) taskList
finish :: GT.NodeID -> Task -> T.Box Storage -> Effect Unit finish :: GT.NodeID -> Task -> T.Box Storage -> Effect Unit
finish id task storage = remove id task storage finish id task storage = remove id task storage
......
...@@ -3,11 +3,12 @@ module Gargantext.Components.DocsTable where ...@@ -3,11 +3,12 @@ module Gargantext.Components.DocsTable where
import Gargantext.Prelude import Gargantext.Prelude
import CSS (query)
import DOM.Simple.Event as DE import DOM.Simple.Event as DE
import Data.Array (any)
import Data.Array as A import Data.Array as A
import Data.Either (Either(..))
import Data.Generic.Rep (class Generic) import Data.Generic.Rep (class Generic)
import Data.Lens ((^.)) import Data.Lens (is, re, (^.))
import Data.Lens.At (at) import Data.Lens.At (at)
import Data.Lens.Record (prop) import Data.Lens.Record (prop)
import Data.Map as Map import Data.Map as Map
...@@ -20,32 +21,30 @@ import Data.String as Str ...@@ -20,32 +21,30 @@ import Data.String as Str
import Data.Tuple (Tuple(..)) import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import Effect (Effect) import Effect (Effect)
import Effect.Aff (Aff, launchAff_) import Effect.Aff (launchAff_)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Effect.Timer (setTimeout)
import Gargantext.AsyncTasks as GAT
import Gargantext.Components.App.Store as Store import Gargantext.Components.App.Store as Store
import Gargantext.Components.Bootstrap as B import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), ModalSizing(..), Variant(..)) import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), ComponentStatus(..), ModalSizing(..), Variant(..), SpinnerTheme(..))
import Gargantext.Components.Category (ratingSimple) import Gargantext.Components.Category (ratingSimple)
import Gargantext.Components.Category.Types (Category(..), cat2score, markCategoryChecked) import Gargantext.Components.Category.Types (Category(..), cat2score, markCategoryChecked)
import Gargantext.Components.DocsTable.DocumentFormCreation as DFC import Gargantext.Components.DocsTable.DocumentFormCreation as DFC
import Gargantext.Components.DocsTable.Types (DocumentsView(..), Hyperdata(..), LocalCategories, Query, Response(..), Year, sampleData, showSource) import Gargantext.Components.DocsTable.SubcorpusCreation (subcorpusCreation)
import Gargantext.Components.DocsTable.Types (DocumentsView(..), Hyperdata(..), LocalCategories, Query, Response(..), SubcorpusParams(..), Year, createSubCorpus, sampleData, showSource)
import Gargantext.Components.GraphQL.Endpoints (updateNodeContextCategory) import Gargantext.Components.GraphQL.Endpoints (updateNodeContextCategory)
import Gargantext.Components.Modal (modal)
import Gargantext.Components.Nodes.Lists.Types as NT import Gargantext.Components.Nodes.Lists.Types as NT
import Gargantext.Components.Reload (textsReloadContext) import Gargantext.Components.Reload (textsReloadContext)
import Gargantext.Components.Table as TT import Gargantext.Components.Table as TT
import Gargantext.Components.Table.Types as TT import Gargantext.Components.Table.Types as TT
import Gargantext.Config.REST (AffRESTError) import Gargantext.Config.REST (AffRESTError)
import Gargantext.Config.Utils (handleRESTError)
import Gargantext.Ends (Frontends, url) import Gargantext.Ends (Frontends, url)
import Gargantext.Hooks.LinkHandler (useLinkHandler) import Gargantext.Hooks.LinkHandler (useLinkHandler)
import Gargantext.Hooks.Loader (useLoader, useLoaderWithCacheAPI, HashedResponse(..)) import Gargantext.Hooks.Loader (useLoader, useLoaderWithCacheAPI, HashedResponse(..))
import Gargantext.Routes (SessionRoute(NodeAPI)) import Gargantext.Routes (SessionRoute(..))
import Gargantext.Routes as Routes import Gargantext.Routes as Routes
import Gargantext.Sessions (Session, sessionId, get, delete) import Gargantext.Sessions (Session, delete, get, sessionId)
import Gargantext.Types (ListId, NodeID, NodeType(..), OrderBy(..), SidePanelState(..), TabSubType, TabType, TableResult, showTabType') import Gargantext.Types (ListId, NodeID, NodeType(..), OrderBy(..), SidePanelState(..), TabSubType, TabType, TableResult, showTabType')
import Gargantext.Types as GT
import Gargantext.Utils (sortWith, (?)) import Gargantext.Utils (sortWith, (?))
import Gargantext.Utils.CacheAPI as GUC import Gargantext.Utils.CacheAPI as GUC
import Gargantext.Utils.QueryString (joinQueryStrings, mQueryParamS, mQueryParamS', queryParam, queryParamS) import Gargantext.Utils.QueryString (joinQueryStrings, mQueryParamS, mQueryParamS', queryParam, queryParamS)
...@@ -147,12 +146,15 @@ docViewCpt = R2.hereComponent here "docView" hCpt ...@@ -147,12 +146,15 @@ docViewCpt = R2.hereComponent here "docView" hCpt
} }
_ = do _ = do
-- State -- State
{ errors } <- Store.use { errors, reloadForest } <- Store.use
cacheState' <- T.useLive T.unequal cacheState cacheState' <- T.useLive T.unequal cacheState
query' <- T.useLive T.unequal query query' <- T.useLive T.unequal query
isDocumentModalVisibleBox <- T.useBox false isDocumentModalVisibleBox <- T.useBox false
isSubcorpusModalVisibleBox <- T.useBox false
onDocumentCreationPending /\ onDocumentCreationPendingBox <- onDocumentCreationPending /\ onDocumentCreationPendingBox <-
R2.useBox' false R2.useBox' false
onSubcorpusCreationPending' /\ onSubcorpusCreationPending <- R2.useBox' false
{ goToRoute } <- useLinkHandler
-- Context -- Context
mReloadContext <- R.useContext textsReloadContext mReloadContext <- R.useContext textsReloadContext
...@@ -181,6 +183,22 @@ docViewCpt = R2.hereComponent here "docView" hCpt ...@@ -181,6 +183,22 @@ docViewCpt = R2.hereComponent here "docView" hCpt
liftEffect $ here.log "[docView] TODO onCreateDocumentEnd handler" liftEffect $ here.log "[docView] TODO onCreateDocumentEnd handler"
createSubcorpusCallback <- pure $ \q p -> launchAff_ do
liftEffect $
T.write_ true onSubcorpusCreationPending
case mCorpusId of
Nothing -> liftEffect $ here.warn2 "[docsTable subCorpusButton RESTError]" mCorpusId
Just cId -> do
res <- createSubCorpus session cId $ SubcorpusParams { query: q, reuseParentList: p }
liftEffect $ do
case res of
Left err -> here.warn2 "[docsTable subCorpusButton RESTError]" err
Right id -> do
T2.reload reloadForest
goToRoute $ Routes.Corpus (sessionId session) id
-- handleRESTError hp errors eTask -- handleRESTError hp errors eTask
-- \t -> liftEffect $ launchDocumentCreationProgress -- \t -> liftEffect $ launchDocumentCreationProgress
-- errors -- errors
...@@ -208,7 +226,7 @@ docViewCpt = R2.hereComponent here "docView" hCpt ...@@ -208,7 +226,7 @@ docViewCpt = R2.hereComponent here "docView" hCpt
] ]
] ]
, H.div { className: "form-group" } , H.div { className: "form-group" }
[ if showSearch then searchBar { query } [] else H.div {} [] ] [ if showSearch then searchBar { query, isSubcorpusModalVisibleBox, onSubcorpusCreationPending' } [] else H.div {} [] ]
] ]
, R2.row , R2.row
...@@ -245,6 +263,20 @@ docViewCpt = R2.hereComponent here "docView" hCpt ...@@ -245,6 +263,20 @@ docViewCpt = R2.hereComponent here "docView" hCpt
, status: onDocumentCreationPending ? Deferred $ Enabled , status: onDocumentCreationPending ? Deferred $ Enabled
} }
] ]
,
-- Subcorpus Creation Modal
B.baseModal
{ isVisibleBox: isSubcorpusModalVisibleBox
, title: Just "Create a subcorpus"
, hasCollapsibleBackground: false
, size: MediumModalSize
}
[ subcorpusCreation
{ callback: createSubcorpusCallback
, query'
, onSubcorpusCreationPending'
}
]
] ]
-- launchDocumentCreationProgress :: -- launchDocumentCreationProgress ::
...@@ -289,7 +321,7 @@ docViewCpt = R2.hereComponent here "docView" hCpt ...@@ -289,7 +321,7 @@ docViewCpt = R2.hereComponent here "docView" hCpt
--------------------------------------------------- ---------------------------------------------------
type SearchBarProps = type SearchBarProps =
(query :: T.Box Query) (query :: T.Box Query, isSubcorpusModalVisibleBox :: T.Box Boolean, onSubcorpusCreationPending' :: Boolean)
searchBar :: R2.Component SearchBarProps searchBar :: R2.Component SearchBarProps
searchBar = R.createElement searchBarCpt searchBar = R.createElement searchBarCpt
...@@ -297,33 +329,45 @@ searchBar = R.createElement searchBarCpt ...@@ -297,33 +329,45 @@ searchBar = R.createElement searchBarCpt
searchBarCpt :: R.Component SearchBarProps searchBarCpt :: R.Component SearchBarProps
searchBarCpt = here.component "searchBar" cpt searchBarCpt = here.component "searchBar" cpt
where where
cpt { query } _children = do cpt { query, isSubcorpusModalVisibleBox, onSubcorpusCreationPending' } _children = do
query' <- T.useLive T.unequal query query' <- T.useLive T.unequal query
queryText <- T.useBox query' queryText <- T.useBox query'
queryText' <- T.useLive T.unequal queryText queryText' <- T.useLive T.unequal queryText
pure $ H.div { className: "input-group px-5" } pure $ R.fragment
[ H.input [ H.div { className: "input-group px-5" }
{ className: "form-control" [ H.input
, id: "docs-input-search" { className: "form-control"
, defaultValue: query' , id: "docs-input-search"
, on: , defaultValue: query'
{ change: onSearchChange queryText , on:
, keyUp: onSearchKeyup query queryText' { change: onSearchChange queryText
, keyUp: onSearchKeyup query queryText'
}
, placeholder: "Search in documents"
, type: "text"
} }
, placeholder: "Search in documents" , H.div { className: "input-group-append" }
, type: "text" [ if query' /= "" then
} R.fragment
, H.div { className: "input-group-append" } [ clearButton query
[ if query' /= "" then , searchButton query queryText'
R.fragment , subCorpusButton isSubcorpusModalVisibleBox queryText' query
[ clearButton query ]
, searchButton query queryText' else
] R.fragment
else [ searchButton query queryText'
searchButton query queryText' , subCorpusButton isSubcorpusModalVisibleBox queryText' query
]
]
, H.div { className: "input-group-append" }
[ R2.when' onSubcorpusCreationPending'
[ B.spinner
{ theme: BorderTheme }
]
]
-- , H.div {className: "col-md-1"} [ searchButton query queryText' ]
] ]
-- , H.div {className: "col-md-1"} [ searchButton query queryText' ]
] ]
onSearchChange :: forall e. T.Box Query -> e -> Effect Unit onSearchChange :: forall e. T.Box Query -> e -> Effect Unit
...@@ -352,6 +396,19 @@ searchBarCpt = here.component "searchBar" cpt ...@@ -352,6 +396,19 @@ searchBarCpt = here.component "searchBar" cpt
} }
[ H.span { className: "text-danger fa fa-times" } [] ] [ H.span { className: "text-danger fa fa-times" } [] ]
subCorpusButton modalVisible queryText' query =
H.button
{ className: "input-group-text btn btn-light text-secondary"
, on:
{ click: \_ -> do
T.write_ queryText' query
T.write_ true modalVisible
}
, type: "submit"
, title: "Create a subcorpus"
}
[ H.span { className: "fa fa-filter" } [] ]
mock :: Boolean mock :: Boolean
mock = false mock = false
......
module Gargantext.Components.DocsTable.SubcorpusCreation where
import Gargantext.Prelude
import Effect (Effect)
import Gargantext.Hooks.StateRecord (useStateRecord)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), ComponentStatus(..), Variant(..))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T
type Props =
( callback :: String -> Boolean -> Effect Unit
, query' :: String
, onSubcorpusCreationPending' :: Boolean
)
subcorpusCreation :: R2.Leaf Props
subcorpusCreation = R2.leaf component
component :: R.Component Props
component = R.hooksComponent "subcorpusCreation" cpt
where
cpt { query', onSubcorpusCreationPending', callback } _ = do
{ state, stateBox } <- useStateRecord (defaultData :: FormData)
let
onParentListCheckboxChange :: Boolean -> Effect Unit
onParentListCheckboxChange value = T.modify_
(\prev -> prev { reuseParentList = value })
stateBox
pure $ H.div {}
[ H.div { className: "form-group" }
[ H.label {} [ H.text $ "Creating subcorpus from query: " <> query' ]
]
, H.div { className: "form-check" }
[ B.formCheckbox
{ value: state.reuseParentList
, callback: onParentListCheckboxChange
}
, H.label { className: "form-check-label" } [ H.text "Reuse parent list?" ]
]
, B.button
{ callback: \_ -> callback query' state.reuseParentList
, type: "submit"
, variant: ButtonVariant Primary
, status: if query' == "" then Disabled else if onSubcorpusCreationPending' then Deferred else Enabled
}
[ H.text "Create!" ]
]
type FormData = { reuseParentList :: Boolean }
defaultData :: FormData
defaultData =
{ reuseParentList: true
}
module Gargantext.Components.DocsTable.Types where module Gargantext.Components.DocsTable.Types where
import Gargantext.Prelude
import Data.Eq.Generic (genericEq) import Data.Eq.Generic (genericEq)
import Data.Generic.Rep (class Generic) import Data.Generic.Rep (class Generic)
import Data.Map (Map) import Data.Map (Map)
import Data.Maybe (Maybe(..), fromMaybe) import Data.Maybe (Maybe(..), fromMaybe)
import Data.Tuple (Tuple(..)) import Data.Tuple (Tuple(..))
import Gargantext.Components.Category.Types (Category(..), Star, decodeCategory) import Gargantext.Components.Category.Types (Category(..), Star, decodeCategory)
import Gargantext.Prelude import Gargantext.Config.REST (AffRESTError)
import Gargantext.Routes (SessionRoute(..))
import Gargantext.Sessions (Session, post)
import Gargantext.Types (NodeID)
import Simple.JSON as JSON import Simple.JSON as JSON
data Action = MarkCategory Int Category data Action = MarkCategory Int Category
...@@ -115,6 +120,19 @@ type LocalUserScore = Map Int Star ...@@ -115,6 +120,19 @@ type LocalUserScore = Map Int Star
type Query = String type Query = String
type Year = String type Year = String
newtype SubcorpusParams = SubcorpusParams
{ query :: Query
, reuseParentList :: Boolean
}
derive instance Eq SubcorpusParams
derive instance Generic SubcorpusParams _
derive newtype instance JSON.ReadForeign SubcorpusParams
derive newtype instance JSON.WriteForeign SubcorpusParams
createSubCorpus :: Session -> Int -> SubcorpusParams -> AffRESTError NodeID
createSubCorpus session parentId = post session (SubCorpus parentId)
--------------------------------------------------------- ---------------------------------------------------------
sampleData' :: DocumentsView sampleData' :: DocumentsView
sampleData' = DocumentsView sampleData' = DocumentsView
......
...@@ -31,6 +31,7 @@ import Gargantext.Components.Lang (Lang(EN)) ...@@ -31,6 +31,7 @@ import Gargantext.Components.Lang (Lang(EN))
import Gargantext.Components.Nodes.Corpus.Types (CorpusData) import Gargantext.Components.Nodes.Corpus.Types (CorpusData)
import Gargantext.Components.Notifications as Notifications import Gargantext.Components.Notifications as Notifications
import Gargantext.Components.Notifications.Types as NT import Gargantext.Components.Notifications.Types as NT
import Gargantext.Config.Utils (handleErrorInAsyncTaskLog)
import Gargantext.Context.Progress (asyncContext, asyncProgress) import Gargantext.Context.Progress (asyncContext, asyncProgress)
import Gargantext.Ends (Frontends, url) import Gargantext.Ends (Frontends, url)
import Gargantext.Hooks.Loader (useLoader) import Gargantext.Hooks.Loader (useLoader)
...@@ -258,9 +259,16 @@ nodeSpanCpt = here.component "nodeSpan" cpt ...@@ -258,9 +259,16 @@ nodeSpanCpt = here.component "nodeSpan" cpt
void $ setTimeout 400 $ T2.reload reload void $ setTimeout 400 $ T2.reload reload
NT.NUpdateWorkerProgress ji atl -> do NT.NUpdateWorkerProgress ji atl -> do
-- TODO Fire this only once! -- TODO Fire this only once!
-- here.log3 "[nodeSpan] update job progress" ji atl here.log3 "[nodeSpan] update job progress" ji atl
if GT.asyncTaskLogIsFinished atl then pure unit if GT.asyncTaskLogIsFinished atl then do
else -- Handle error but only when the task is not already in the storage
-- (we want to avoid reporting the error multiple times, from different places).
-- See e.g. https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/728
-- (error is thrown before the task marks any progress)
hasTask <- GAT.hasTask props.id ji boxes.tasks
unless hasTask $ do
handleErrorInAsyncTaskLog boxes.errors atl
else do
GAT.insert props.id ji boxes.tasks GAT.insert props.id ji boxes.tasks
_ -> pure unit _ -> pure unit
ws <- T.read boxes.wsNotification ws <- T.read boxes.wsNotification
......
...@@ -2,8 +2,6 @@ module Gargantext.Components.Forest.Tree.Node.Action.Update where ...@@ -2,8 +2,6 @@ module Gargantext.Components.Forest.Tree.Node.Action.Update where
import Gargantext.Prelude import Gargantext.Prelude
import Gargantext.Components.Forest.Tree.Node.Action.Update.Types (Charts(..), Granularity(..), GraphMetric(..), Method(..), PartitionMethod(..), UpdateNodeParams(..), Strength(..), BridgenessMethod(..), UpdateNodeConfigGraph(..))
import DOM.Simple.Console (log3) import DOM.Simple.Console (log3)
import Data.Either (Either(..)) import Data.Either (Either(..))
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
...@@ -13,11 +11,12 @@ import Gargantext.AsyncTasks as GAT ...@@ -13,11 +11,12 @@ import Gargantext.AsyncTasks as GAT
import Gargantext.Components.Bootstrap as B import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..)) import Gargantext.Components.Bootstrap.Types (ComponentStatus(..))
import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..)) import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..))
import Gargantext.Components.Forest.Tree.Node.Action.Update.Types (BridgenessMethod(..), Charts(..), Granularity(..), GraphMetric(..), Method(..), Strength(..), UpdateNodeConfigGraph(..), UpdateNodeParams(..))
import Gargantext.Components.Forest.Tree.Node.Tools as Tools import Gargantext.Components.Forest.Tree.Node.Tools as Tools
import Gargantext.Components.PhyloExplorer.API as Phylo import Gargantext.Components.PhyloExplorer.API as Phylo
import Gargantext.Components.PhyloExplorer.Config.ConfigForm as PhyloForm import Gargantext.Components.PhyloExplorer.Config.ConfigForm as PhyloForm
import Gargantext.Components.PhyloExplorer.ConfigFormParser as PhyloHook import Gargantext.Components.PhyloExplorer.ConfigFormParser as PhyloHook
import Gargantext.Config.REST (RESTError, AffRESTError) import Gargantext.Config.REST (AffRESTError)
import Gargantext.Routes as GR import Gargantext.Routes as GR
import Gargantext.Sessions (Session, post) import Gargantext.Sessions (Session, post)
import Gargantext.Types (ID, NodeType(..)) import Gargantext.Types (ID, NodeType(..))
...@@ -104,10 +103,7 @@ updateGraphCpt = here.component "updateGraph" cpt ...@@ -104,10 +103,7 @@ updateGraphCpt = here.component "updateGraph" cpt
methodGraphNodeType2 <- T.useBox GT.CTabTerms methodGraphNodeType2 <- T.useBox GT.CTabTerms
methodGraphNodeType2' <- T.useLive T.unequal methodGraphNodeType2 methodGraphNodeType2' <- T.useLive T.unequal methodGraphNodeType2
methodGraphClustering <- T.useBox Spinglass methodGraphBridgeness <- T.useBox BridgenessBasic
methodGraphClustering' <- T.useLive T.unequal methodGraphClustering
methodGraphBridgeness <- T.useBox BridgenessMethod_Basic
methodGraphBridgeness' <- T.useLive T.unequal methodGraphBridgeness methodGraphBridgeness' <- T.useLive T.unequal methodGraphBridgeness
let let
...@@ -117,7 +113,6 @@ updateGraphCpt = here.component "updateGraph" cpt ...@@ -117,7 +113,6 @@ updateGraphCpt = here.component "updateGraph" cpt
let let
config = UpdateNodeConfigGraph config = UpdateNodeConfigGraph
{ methodGraphMetric: methodGraphMetric' { methodGraphMetric: methodGraphMetric'
, methodGraphClustering: methodGraphClustering'
, methodGraphBridgeness: methodGraphBridgeness' , methodGraphBridgeness: methodGraphBridgeness'
, methodGraphEdgesStrength: methodGraphEdgesStrength' , methodGraphEdgesStrength: methodGraphEdgesStrength'
, methodGraphNodeType1: methodGraphNodeType1' , methodGraphNodeType1: methodGraphNodeType1'
...@@ -141,43 +136,16 @@ updateGraphCpt = here.component "updateGraph" cpt ...@@ -141,43 +136,16 @@ updateGraphCpt = here.component "updateGraph" cpt
, label: "Show subjects with Order1 or concepts with Order2 ?" , label: "Show subjects with Order1 or concepts with Order2 ?"
} }
[] []
, Tools.formChoiceSafe
{ items: [ BridgenessBasic, BridgenessAdvanced ]
, default: methodGraphBridgeness'
, callback: \val -> T.write_ val methodGraphBridgeness
, print: show
, label: "Intercluster link filtering method"
}
[]
] ]
{-
, H.text "Bridgness Method : Basic is ok, Advanced in Development"
, formChoiceSafe { items: [BridgenessMethod_Basic, BridgenessMethod_Advanced]
, default: methodGraphBridgeness'
, callback: \val -> T.write_ val methodGraphBridgeness
, print: show } []
, H.text "NodeType 1 ?"
, formChoiceSafe { items: [GT.CTabTerms, GT.CTabSources, GT.CTabAuthors, GT.CTabInstitutes]
, default: methodGraphNodeType1'
, callback: \val -> T.write_ val methodGraphNodeType1
, print: show } []
, H.text "Ngrams ?"
, formChoiceSafe { items: [GT.CTabTerms, GT.CTabSources, GT.CTabAuthors, GT.CTabInstitutes]
, default: methodGraphNodeType2'
, callback: \val -> T.write_ val methodGraphNodeType2
, print: show } []
--}
{-
, H.text "Show Strong (expected) links or weak (maybe unexpected) links?"
, formChoiceSafe { items: [Strong, Weak]
, default: methodGraphEdgesStrength'
, callback: \val -> T.write_ val methodGraphEdgesStrength
, print: show } []
, formChoiceSafe { items: [Spinglass, Infomap, Confluence]
, default: methodGraphClustering'
, callback: \val -> T.write_ val methodGraphClustering
, print: show } []
-}
updatePhylo :: R2.Leaf UpdateProps updatePhylo :: R2.Leaf UpdateProps
updatePhylo = R2.leaf updatePhyloCpt updatePhylo = R2.leaf updatePhyloCpt
...@@ -246,37 +214,6 @@ updateCorpusCpt = here.component "updateTexts" cpt ...@@ -246,37 +214,6 @@ updateCorpusCpt = here.component "updateTexts" cpt
methodList <- T.useBox Basic methodList <- T.useBox Basic
methodList' <- T.useLive T.unequal methodList methodList' <- T.useLive T.unequal methodList
-- method for graph metric
methodGraphMetric <- T.useBox Order1
methodGraphMetric' <- T.useLive T.unequal methodGraphMetric
let
graphConfig = UpdateNodeConfigGraph
{ methodGraphMetric: methodGraphMetric'
, methodGraphEdgesStrength: Strong
, methodGraphClustering: Spinglass
, methodGraphBridgeness: BridgenessMethod_Basic
, methodGraphNodeType1: GT.CTabTerms
, methodGraphNodeType2: GT.CTabTerms
}
let
phyloConfig = Phylo.UpdateData
{ defaultMode: true
, proximity: 0.5
, synchrony: 0.5
, quality: 0.8
, exportFilter: 3.0
, timeUnit: Phylo.Year $ Phylo.TimeUnitCriteria
{ period: 3
, step: 1
, matchingFrame: 5
}
, clique: Phylo.MaxClique
{ size: 5
, threshold: 0.0001
, filter: Phylo.ByThreshold
}
}
let methodTexts = Both let methodTexts = Both
pure $ pure $
...@@ -354,13 +291,7 @@ updateTextsCpt = here.component "updateTexts" cpt ...@@ -354,13 +291,7 @@ updateTextsCpt = here.component "updateTexts" cpt
, iconName: "reload-with-settings" , iconName: "reload-with-settings"
, textTitle: "Update documents (contexts)" , textTitle: "Update documents (contexts)"
} }
[] -- H.text "Update with" []
-- formChoiceSafe { items: [NewNgrams, NewTexts, Both]
-- , default: methodTexts'
-- , callback: \val -> T.write_ val methodTexts
-- , print: show } []
-- (submitButton (UpdateNode $ UpdateNodeParamsTexts { methodTexts: methodTexts' }) dispatch)
updateOther :: R2.Component UpdateProps updateOther :: R2.Component UpdateProps
updateOther = R.createElement updateOtherCpt updateOther = R.createElement updateOtherCpt
...@@ -370,5 +301,3 @@ updateOtherCpt = here.component "updateOther" cpt ...@@ -370,5 +301,3 @@ updateOtherCpt = here.component "updateOther" cpt
where where
cpt _ _ = do cpt _ _ = do
pure $ H.div {} [] pure $ H.div {} []
-- fragmentPT $ "Update " <> show nodeType
...@@ -13,7 +13,6 @@ import Simple.JSON.Generics as JSONG ...@@ -13,7 +13,6 @@ import Simple.JSON.Generics as JSONG
newtype UpdateNodeConfigGraph = UpdateNodeConfigGraph newtype UpdateNodeConfigGraph = UpdateNodeConfigGraph
{ methodGraphMetric :: GraphMetric { methodGraphMetric :: GraphMetric
, methodGraphEdgesStrength :: Strength , methodGraphEdgesStrength :: Strength
, methodGraphClustering :: PartitionMethod
, methodGraphBridgeness :: BridgenessMethod , methodGraphBridgeness :: BridgenessMethod
, methodGraphNodeType1 :: GT.CTabNgramType , methodGraphNodeType1 :: GT.CTabNgramType
, methodGraphNodeType2 :: GT.CTabNgramType , methodGraphNodeType2 :: GT.CTabNgramType
...@@ -143,28 +142,9 @@ instance JSON.ReadForeign Strength where ...@@ -143,28 +142,9 @@ instance JSON.ReadForeign Strength where
instance JSON.WriteForeign Strength where instance JSON.WriteForeign Strength where
writeImpl = JSON.writeImpl <<< show writeImpl = JSON.writeImpl <<< show
data PartitionMethod = Spinglass | Infomap | Confluence
derive instance Generic PartitionMethod _
derive instance Eq PartitionMethod
instance Show PartitionMethod where
show = genericShow
instance Read PartitionMethod where
read "Spinglass" = Just Spinglass
read "Confluence" = Just Confluence
read "Infomap" = Just Infomap
read _ = Nothing
instance JSON.ReadForeign PartitionMethod where
readImpl = JSONG.enumSumRep
instance JSON.WriteForeign PartitionMethod where
writeImpl = JSON.writeImpl <<< show
data BridgenessMethod data BridgenessMethod
= BridgenessMethod_Basic = BridgenessBasic
| BridgenessMethod_Advanced | BridgenessAdvanced
derive instance Generic BridgenessMethod _ derive instance Generic BridgenessMethod _
derive instance Eq BridgenessMethod derive instance Eq BridgenessMethod
...@@ -172,8 +152,8 @@ instance Show BridgenessMethod where ...@@ -172,8 +152,8 @@ instance Show BridgenessMethod where
show = genericShow show = genericShow
instance Read BridgenessMethod where instance Read BridgenessMethod where
read "BridgenessMethod_Basic" = Just BridgenessMethod_Basic read "BridgenessBasic" = Just BridgenessBasic
read "BridgenessMethod_Advanced" = Just BridgenessMethod_Advanced read "BridgenessAdvanced" = Just BridgenessAdvanced
read _ = Nothing read _ = Nothing
instance JSON.ReadForeign BridgenessMethod where instance JSON.ReadForeign BridgenessMethod where
......
...@@ -300,6 +300,14 @@ uploadFileViewWithLangsCpt = here.component "uploadFileViewWithLangs" cpt ...@@ -300,6 +300,14 @@ uploadFileViewWithLangsCpt = here.component "uploadFileViewWithLangs" cpt
, R2.row , R2.row
[ H.div { className: "col-12 form-group" } [ H.div { className: "col-12 form-group" }
[ H.label {} [ H.text "Please choose the list of terms to use:" ] [ H.label {} [ H.text "Please choose the list of terms to use:" ]
, H.details { className: "small p-1 pl-3 pb-2 alert-light" }
[ H.summary {} [ H.text "Infos about this choice:" ]
, H.p {}
[ H.text "- My list first: the analysis will generate a list of terms based on your previous lists of terms"
, H.br {}
, H.text "- NoList: the analysis will not generate any terms"
]
]
, ListSelection.selection { selection, session } [] , ListSelection.selection { selection, session } []
] ]
] ]
......
...@@ -307,11 +307,12 @@ buttonClickCpt = here.component "buttonClick" cpt ...@@ -307,11 +307,12 @@ buttonClickCpt = here.component "buttonClick" cpt
[ B.iconButton [ B.iconButton
{ className: intercalate " " { className: intercalate " "
[ "popup-container__cta__button" [ "popup-container__cta__button"
, action == Just todo ? "popup-container__cta__button--active" $ "" , if action == Just todo || (action == Nothing && todo == Documentation nodeType) then "popup-container__cta__button--active" else ""
] ]
, name: glyphiconNodeAction todo , name: glyphiconNodeAction todo
, title: show todo , title: show todo
, callback: action == Just todo ? pure $ click , callback: click
-- , callback: action == Just todo ? pure $ click
, elevation: Level2 , elevation: Level2
-- , status: action == Just todo ? -- , status: action == Just todo ?
-- Disabled $ -- Disabled $
......
...@@ -225,7 +225,7 @@ settingsBoxLens FolderPublic = ...@@ -225,7 +225,7 @@ settingsBoxLens FolderPublic =
] ]
settingsBoxLens FolderShared = settingsBoxLens FolderShared =
_buttons .~ _buttons .~
[ Add [ Team, FolderShared ] [ Add [ Team ]
] ]
settingsBoxLens Graph = settingsBoxLens Graph =
_buttons .~ _buttons .~
......
...@@ -242,7 +242,7 @@ drawGraphCpt = here.component "drawGraph" cpt ...@@ -242,7 +242,7 @@ drawGraphCpt = here.component "drawGraph" cpt
-- When we change state, we make it empty though. -- When we change state, we make it empty though.
--pure $ RH.div { ref: elRef, style: {height: "95%"} } [] --pure $ RH.div { ref: elRef, style: {height: "95%"} } []
-- based on https://github.com/jacomyal/sigma.js/blob/v2.4.0/src/settings.ts -- based on https://github.com/jacomyal/sigma.js/blob/v3.0.0/src/settings.ts
-- TODO: check some of the types -- TODO: check some of the types
type SigmaSettings = type SigmaSettings =
( -- Performance ( -- Performance
...@@ -250,9 +250,7 @@ type SigmaSettings = ...@@ -250,9 +250,7 @@ type SigmaSettings =
--, hideLablesOnMove :: Boolean --, hideLablesOnMove :: Boolean
renderLabels :: Boolean renderLabels :: Boolean
--, renderEdgeLabels :: Boolean --, renderEdgeLabels :: Boolean
--, enableEdgeClickEvents :: Boolean , enableEdgeEvents :: Boolean
--, enableEdgeWheelEvents :: Boolean
, enableEdgeHoverEvents :: Boolean
-- Component rendering -- Component rendering
, defaultNodeColor :: String , defaultNodeColor :: String
--, defaultNodeType :: String --, defaultNodeType :: String
...@@ -279,9 +277,9 @@ type SigmaSettings = ...@@ -279,9 +277,9 @@ type SigmaSettings =
--, minCameraRatio :: Number --, minCameraRatio :: Number
--, maxCameraRatio :: Number --, maxCameraRatio :: Number
-- Renderers -- Renderers
--, labelRenderer :: DrawLabel --, defaultDrawNodeLabel :: DrawLabel
--, hoverRenderer :: DrawHover --, defaultDrawNodeHover :: DrawHover
--, edgeLabelRenderer :: DrawEdgeLabel --, defaultDrawEdgeLabel :: DrawEdgeLabel
-- Lifecycle -- Lifecycle
--, allowInvalidContainer :: Boolean --, allowInvalidContainer :: Boolean
...@@ -300,9 +298,7 @@ sigmaSettings _theme = ...@@ -300,9 +298,7 @@ sigmaSettings _theme =
--, hideLablesOnMove : false --, hideLablesOnMove : false
renderLabels: false -- initially false, because of forceatlas renderLabels: false -- initially false, because of forceatlas
--, renderEdgeLabels : true --, renderEdgeLabels : true
--, enableEdgeClickEvents : false , enableEdgeEvents: false
--, enableEdgeWheelEvents : false
, enableEdgeHoverEvents: false
-- Component rendering -- Component rendering
, defaultNodeColor: "#FFF" , defaultNodeColor: "#FFF"
--, defaultNodeType : "circle" --, defaultNodeType : "circle"
...@@ -329,9 +325,9 @@ sigmaSettings _theme = ...@@ -329,9 +325,9 @@ sigmaSettings _theme =
--, minCameraRatio : Nothing --, minCameraRatio : Nothing
--, maxCameraRatio : Nothing --, maxCameraRatio : Nothing
-- Renderers -- Renderers
--, labelRenderer : drawLabel --, defaultDrawNodeLabel : drawLabel
--, hoverRenderer : drawHover --, defaultDrawNodeHover : drawHover
--, edgeLabelRenderer : drawEdgeLabel --, defaultDrawEdgeLabel : drawEdgeLabel
-- Lifecycle -- Lifecycle
--, allowInvalidContainer : false --, allowInvalidContainer : false
......
...@@ -19,7 +19,7 @@ derive instance Generic Selection _ ...@@ -19,7 +19,7 @@ derive instance Generic Selection _
instance Show Selection where instance Show Selection where
show MyListsFirst = "My lists first" show MyListsFirst = "My lists first"
-- show OtherListsFirst = "Other lists first" -- show OtherListsFirst = "Other lists first"
show NoList = "No list (the analysis will not generate any terms)" show NoList = "NoList"
show (SelectedLists _) = "Selected lists" show (SelectedLists _) = "Selected lists"
instance Eq Selection where instance Eq Selection where
......
...@@ -30,7 +30,6 @@ import Gargantext.Utils.Reactix as R2 ...@@ -30,7 +30,6 @@ 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 Record as Record import Record as Record
import Record.Unsafe (unsafeSet)
import Toestand as T import Toestand as T
here :: R2.Here here :: R2.Here
...@@ -119,13 +118,13 @@ componentCpt = here.component "main" cpt ...@@ -119,13 +118,13 @@ componentCpt = here.component "main" cpt
-- @XXX StateRecord with distinct value types -- @XXX StateRecord with distinct value types
onAgreedCheckboxChange :: Boolean -> Effect Unit onAgreedCheckboxChange :: Boolean -> Effect Unit
onAgreedCheckboxChange value = T.modify_ onAgreedCheckboxChange value = T.modify_
(\prev -> unsafeSet "agreed" value prev) (\prev -> prev { agreed = value })
stateBox stateBox
-- @XXX StateRecord with distinct value types -- @XXX StateRecord with distinct value types
onAgreedLabelClick :: Unit -> Effect Unit onAgreedLabelClick :: Unit -> Effect Unit
onAgreedLabelClick _ = T.modify_ onAgreedLabelClick _ = T.modify_
(\prev -> unsafeSet "agreed" (not state.agreed) prev) (\prev -> prev { agreed = not state.agreed })
stateBox stateBox
-- | Render -- | Render
......
...@@ -273,6 +273,7 @@ sessionPath (R.ChartHash { chartType, listId, tabType } i) = ...@@ -273,6 +273,7 @@ sessionPath (R.ChartHash { chartType, listId, tabType } i) =
sessionPath (R.PhyloAPI nId) = "node/" <> show nId <> "/phylo" sessionPath (R.PhyloAPI nId) = "node/" <> show nId <> "/phylo"
sessionPath R.Members = "members" sessionPath R.Members = "members"
sessionPath (R.ShareURL i t) = "shareurl?type=" <> show t <> "&id=" <> show i sessionPath (R.ShareURL i t) = "shareurl?type=" <> show t <> "&id=" <> show i
sessionPath (R.SubCorpus i) = "corpus/" <> show i <> "/subcorpus"
------- misc routing stuff ------- misc routing stuff
......
...@@ -4,7 +4,7 @@ import Graph from 'graphology'; ...@@ -4,7 +4,7 @@ import Graph from 'graphology';
import Sigma from 'sigma'; import Sigma from 'sigma';
//import { takeScreenshot } from '../../src/external-deps/sigmajs-screenshot.js'; //import { takeScreenshot } from '../../src/external-deps/sigmajs-screenshot.js';
import takeScreenshot from '../../src/external-deps/sigmajs-screenshot-with-canvas.js'; import takeScreenshot from '../../src/external-deps/sigmajs-screenshot-with-canvas.js';
import CircleNodeProgram from 'sigma/rendering/webgl/programs/node.fast'; import { NodeCircleProgram } from 'sigma/rendering';
import ContourCircleNodeProgram from '../../src/external-deps/sigmajs-circle-with-contour.js'; import ContourCircleNodeProgram from '../../src/external-deps/sigmajs-circle-with-contour.js';
import TriangleNodeProgram from '../../src/external-deps/sigmajs-triangle.js'; import TriangleNodeProgram from '../../src/external-deps/sigmajs-triangle.js';
import ContourTriangleNodeProgram from '../../src/external-deps/sigmajs-triangle-with-contour.js'; import ContourTriangleNodeProgram from '../../src/external-deps/sigmajs-triangle-with-contour.js';
...@@ -198,7 +198,7 @@ function _sigma(left, right, el, opts) { ...@@ -198,7 +198,7 @@ function _sigma(left, right, el, opts) {
const settings = { const settings = {
labelRenderer: drawLabel, labelRenderer: drawLabel,
nodeProgramClasses: { nodeProgramClasses: {
circle: CircleNodeProgram.default, // TODO why default? It seems that import should be fixed circle: NodeCircleProgram,
ccircle: ContourCircleNodeProgram, ccircle: ContourCircleNodeProgram,
triangle: TriangleNodeProgram, triangle: TriangleNodeProgram,
ctriangle: ContourTriangleNodeProgram, ctriangle: ContourTriangleNodeProgram,
...@@ -209,7 +209,7 @@ function _sigma(left, right, el, opts) { ...@@ -209,7 +209,7 @@ function _sigma(left, right, el, opts) {
}, },
...opts.settings ...opts.settings
}; };
let s = new sigma(graph, el, settings); let s = new Sigma(graph, el, settings);
console.log('[_sigma] initializing sigma with el', el, 'opts', opts.settings, 'sigma', s); console.log('[_sigma] initializing sigma with el', el, 'opts', opts.settings, 'sigma', s);
console.log('[_sigma] labelRenderedSizeThreshold', opts.settings.labelRenderedSizeThreshold); console.log('[_sigma] labelRenderedSizeThreshold', opts.settings.labelRenderedSizeThreshold);
sigmaMouseSelector(s); sigmaMouseSelector(s);
......
'use strict';
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
export const _useFeatureFlag = function useFeatureFlag(cookieKeys) {
const checkFeatureFlag = () =>
cookieKeys.some((key) => Cookies.get(key) === 'true');
const [isFeatureEnabled, setIsFeatureEnabled] = useState(checkFeatureFlag);
useEffect(() => {
const hasEnabledFlag = checkFeatureFlag();
isFeatureEnabled && console.error("Warning: feature flags are in use");
if (isFeatureEnabled !== hasEnabledFlag) {
setIsFeatureEnabled(hasEnabledFlag);
}
}, [cookieKeys, isFeatureEnabled]);
return isFeatureEnabled;
};
module Gargantext.Hooks.UseFeatureFlag where
import Prelude
import Effect.Uncurried (runEffectFn1, EffectFn1)
import Gargantext.Components.CodeEditor (previewPostProcess)
import Gargantext.Utils.Reactix as R2
import Reactix (hooksComponent)
import Reactix as R
import Reactix.DOM.HTML as H
foreign import _useFeatureFlag :: EffectFn1 (Array String) Boolean
here :: R2.Here
here = R2.here "Gargantext.Hooks.UseFeatureFlag"
{-
useFeatureFlag is a custom React hook that determines whether a feature flag is enabled based on the presence and value of specific cookies.
It checks the cookies provided as keys and returns true if any of them are set to 'true'. Otherwise, it returns false.
This hook is useful for conditional rendering of features based on user-specific feature toggles managed via cookies.
Usage :
parentCpt :: R.Component ()
parentCpt = here.component "container" cpt
where
cpt _ _ = do
shouldBeVisible <- useFeatureFlag ["keyOne", "keyTwo", "keyThree"]
pure $
H.div {} [if shouldBeVisible then H.text "I should be visible" else H.text "Im not visible"]
-}
useFeatureFlag :: Array String -> R.Hooks Boolean
useFeatureFlag keys =
R.unsafeHooksEffect $ runEffectFn1 _useFeatureFlag keys
type FeatureProps =
( render :: Boolean -> R.Element
, keys :: Array String
)
{-
Use feature component two wrap your component behind a feature flag.
It might be cleaner to use it instead of directly using the useFeatureFlag hook
Usage:
feature { keys: [ "keyOne" ], render: \shouldRender -> if shouldRender then H.text "hello" else H.text "you cant see me" }
-}
feature :: R2.Leaf FeatureProps
feature = R2.renderLeaf "" $
( \{ keys, render } _ -> do
shouldRender <- useFeatureFlag keys
pure $
render shouldRender
)
hideBehind :: forall a. Array String -> R2.Component a -> R2.Component a
hideBehind keys cpt = R2.renderComponent "" \props children -> do
shouldRender <- useFeatureFlag keys
pure $
if shouldRender then cpt props children
else H.text ""
...@@ -148,6 +148,7 @@ data SessionRoute ...@@ -148,6 +148,7 @@ data SessionRoute
| PhyloAPI Id | PhyloAPI Id
| Members | Members
| ShareURL Id NodeType | ShareURL Id NodeType
| SubCorpus Id
------------------------------------------------------ ------------------------------------------------------
......
...@@ -821,7 +821,7 @@ asyncTaskTypePath AddNode = "async/nobody/" ...@@ -821,7 +821,7 @@ asyncTaskTypePath AddNode = "async/nobody/"
asyncTaskTypePath CorpusFormUpload = "add/form/async/" asyncTaskTypePath CorpusFormUpload = "add/form/async/"
asyncTaskTypePath GraphRecompute = "async/recompute/" asyncTaskTypePath GraphRecompute = "async/recompute/"
asyncTaskTypePath ListUpload = "add/form/async/" asyncTaskTypePath ListUpload = "add/form/async/"
asyncTaskTypePath ListCSVUpload = "csv/add/form/async/" asyncTaskTypePath ListCSVUpload = "tsv/add/form/async/"
asyncTaskTypePath NodeDocument = "document/upload/async" asyncTaskTypePath NodeDocument = "document/upload/async"
asyncTaskTypePath Query = "query/" asyncTaskTypePath Query = "query/"
asyncTaskTypePath UpdateNgramsCharts = "ngrams/async/charts/update/" asyncTaskTypePath UpdateNgramsCharts = "ngrams/async/charts/update/"
......
...@@ -72,6 +72,14 @@ component ...@@ -72,6 +72,14 @@ component
-> R.Element -> R.Element
component cpt props children = R.createElement cpt props children component cpt props children = R.createElement cpt props children
renderComponent :: forall a. String -> R.HooksComponent a -> (Record a -> Array R.Element -> R.Element)
renderComponent name cpt props children =
R.createElement (R.hooksComponent name cpt) props children
renderLeaf :: forall a. String -> R.HooksComponent a -> Leaf a
renderLeaf name cpt props =
renderComponent name cpt props []
leaf leaf
:: forall cpt p :: forall cpt p
. R.IsComponent cpt p (Array R.Element) . R.IsComponent cpt p (Array R.Element)
......
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
precision mediump float;
varying vec4 v_color;
varying float v_border;
const float radius = 0.5;
const vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0);
void main(void) {
vec2 m = gl_PointCoord - vec2(0.5, 0.5);
float dist = radius - length(m);
// No antialiasing for picking mode:
#ifdef PICKING_MODE
if (dist > v_border)
gl_FragColor = v_color;
else
gl_FragColor = transparent;
#else
float t = 0.0;
if (dist > v_border)
t = 1.0;
else if (dist > 0.0)
t = dist / v_border;
gl_FragColor = mix(transparent, v_color, t);
#endif
}
`;
export default SHADER_SOURCE;
\ No newline at end of file
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
attribute vec4 a_id;
attribute vec4 a_color;
attribute vec2 a_position;
attribute float a_size;
uniform float u_sizeRatio;
uniform float u_pixelRatio;
uniform mat3 u_matrix;
varying vec4 v_color;
varying float v_border;
const float bias = 255.0 / 254.0;
void main() {
gl_Position = vec4(
(u_matrix * vec3(a_position, 1)).xy,
0,
1
);
// Multiply the point size twice:
// - x SCALING_RATIO to correct the canvas scaling
// - x 2 to correct the formulae
gl_PointSize = a_size / u_sizeRatio * u_pixelRatio * 2.0;
v_border = (0.5 / a_size) * u_sizeRatio;
#ifdef PICKING_MODE
// For picking mode, we use the ID as the color:
v_color = a_id;
#else
// For normal mode, we use the color:
v_color = a_color;
#endif
v_color.a *= bias;
}
`;
export default SHADER_SOURCE;
\ No newline at end of file
// Based on sigma.js/src/rendering/webgl/programs/node.fast.ts // Based on https://github.com/jacomyal/sigma.js/blob/main/packages/sigma/src/rendering/programs/node-point/index.ts
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import vertexShaderSource from "sigma/rendering/webgl/shaders/node.fast.vert.glsl"; import { NodeProgram } from 'sigma/rendering';
import fragmentShaderSource from "sigma/rendering/webgl/shaders/node.fast.frag.glsl";
import { AbstractNodeProgram } from "sigma/rendering/webgl/programs/common/node";
import { RenderParams } from "sigma/rendering/webgl/programs/common/program";
import CircleNodeProgram from 'sigma/rendering/webgl/programs/node.fast';
const POINTS = 2; import VERTEX_SHADER_SOURCE from "./circle-vert.glsl";
const ATTRIBUTES = 4; import FRAGMENT_SHADER_SOURCE from "./circle-frag.glsl";
const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
const UNIFORMS = ["u_sizeRatio", "u_pixelRatio", "u_matrix"]
/* /*
export default class NodeContourFastProgram extends AbstractNodeProgram { export default class NodePointProgram<
//constructor(gl : WebGLRenderingContext) { N extends Attributes = Attributes,
constructor(gl) { E extends Attributes = Attributes,
super(gl, vertexShaderSource, fragmentShaderSource, POINTS, ATTRIBUTES); G extends Attributes = Attributes,
this.bind(); > extends NodeProgram<(typeof UNIFORMS)[number], N, E, G> {
}
*/ */
export default class NodeContourFastProgram extends CircleNodeProgram { export default class NodeContourFastProgram extends NodeProgram {
constructor(gl) { getDefinition() {
super(gl, vertexShaderSource, fragmentShaderSource, POINTS, ATTRIBUTES); return {
// NOTE super method above will set POINTS = 1 from CircleNodeProgram VERTICES: 2,
// We need to overwrite this VERTEX_SHADER_SOURCE,
// https://gitlab.iscpif.fr/gargantext/purescript-gargantext/issues/471 FRAGMENT_SHADER_SOURCE,
this.points = POINTS; METHOD: WebGLRenderingContext.POINTS,
this.bind(); UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
};
} }
//process(data: NodeDisplayData, hidden: boolean, offset: number): void { // processVisibleItem(nodeIndex: number, startIndex: number, data: NodeDisplayData) {
process(data, hidden, offset) { processVisibleItem(nodeIndex, startIndex, data) {
const array = this.array; const array = this.array;
let i = offset * POINTS * ATTRIBUTES;
if (hidden) {
// contour
array[i++] = 0;
array[i++] = 0;
array[i++] = 0;
array[i++] = 0;
// circle
array[i++] = 0;
array[i++] = 0;
array[i++] = 0;
array[i++] = 0;
return;
}
const color = floatColor(data.color); const color = floatColor(data.color);
//const black = floatColor('black'); //const black = floatColor('black');
const gray = floatColor('#aaa') const gray = floatColor('#aaa')
// contour // contour
array[i++] = data.x; array[startIndex++] = data.x;
array[i++] = data.y; array[startIndex++] = data.y;
array[i++] = data.size + 1; array[startIndex++] = data.size + 1;
//array[i++] = black; //array[i++] = black;
array[i++] = gray; array[startIndex++] = gray;
array[startIndex++] = nodeIndex;
// circle // circle
array[i++] = data.x; array[startIndex++] = data.x;
array[i++] = data.y; array[startIndex++] = data.y;
array[i++] = data.size; array[startIndex++] = data.size;
array[i] = color; array[startIndex++] = color;
array[startIndex++] = nodeIndex;
} }
//render(params: RenderParams): void { //setUniforms({ sizeRatio, pixelRatio, matrix }: RenderParams, { gl, uniformLocations }: ProgramInfo): void {
render(params) { setUniforms({ sizeRatio, pixelRatio, matrix }, { gl, uniformLocations }) {
if (this.hasNothingToRender()) return; const { u_sizeRatio, u_pixelRatio, u_matrix } = uniformLocations;
const gl = this.gl;
const program = this.program;
gl.useProgram(program);
gl.uniform1f(this.ratioLocation, 1 / Math.sqrt(params.ratio));
gl.uniform1f(this.scaleLocation, params.scalingRatio);
gl.uniformMatrix3fv(this.matrixLocation, false, params.matrix);
gl.drawArrays(gl.POINTS, 0, this.array.length / ATTRIBUTES); gl.uniform1f(u_pixelRatio, pixelRatio);
gl.uniform1f(u_sizeRatio, sizeRatio);
gl.uniformMatrix3fv(u_matrix, false, matrix);
} }
} }
...@@ -8,33 +8,72 @@ ...@@ -8,33 +8,72 @@
* @module * @module
*/ */
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import TriangleProgram from "./sigmajs-triangle-abstract"; import { NodeProgram } from "sigma/rendering";
const POINTS = 12; import VERTEX_SHADER_SOURCE from "./triangle-vert.glsl";
const ATTRIBUTES = 5; import FRAGMENT_SHADER_SOURCE from "./triangle-frag.glsl";
const ANGLE_1_1 = (0 * Math.PI) / 4; const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
const ANGLE_1_2 = (2 * Math.PI) / 4;
const ANGLE_1_3 = (4 * Math.PI) / 4;
const ANGLE_2_1 = (4 * Math.PI) / 4;
const ANGLE_2_2 = (6 * Math.PI) / 4;
const ANGLE_2_3 = (8 * Math.PI) / 4;
export default class NodeProgram extends TriangleProgram { const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_matrix"];
constructor(gl) {
super(gl, POINTS, ATTRIBUTES); export default class NodeDiamondContourProgram extends NodeProgram {
static ANGLE_1_1 = (0 * Math.PI) / 4;
static ANGLE_1_2 = (2 * Math.PI) / 4;
static ANGLE_1_3 = (4 * Math.PI) / 4;
static ANGLE_2_1 = (4 * Math.PI) / 4;
static ANGLE_2_2 = (6 * Math.PI) / 4;
static ANGLE_2_3 = (8 * Math.PI) / 4;
getDefinition() {
return {
VERTICES: 12,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
CONSTANT_ATTRIBUTES: [{ name: "a_angle", size: 1, type: FLOAT }],
CONSTANT_DATA: [[NodeDiamondContourProgram.ANGLE_1_1], [NodeDiamondContourProgram.ANGLE_1_2], [NodeDiamondContourProgram.ANGLE_1_3]
,[NodeDiamondContourProgram.ANGLE_2_1], [NodeDiamondContourProgram.ANGLE_2_2], [NodeDiamondContourProgram.ANGLE_2_3]
,[NodeDiamondContourProgram.ANGLE_1_1], [NodeDiamondContourProgram.ANGLE_1_2], [NodeDiamondContourProgram.ANGLE_1_3]
,[NodeDiamondContourProgram.ANGLE_2_1], [NodeDiamondContourProgram.ANGLE_2_2], [NodeDiamondContourProgram.ANGLE_2_3]],
};
} }
triangleDefinitions(data) { processVisibleItem(nodeIndex, startIndex, data) {
const gray = '#aaa'; const array = this.array;
const color = floatColor(data.color);
const gray = floatColor('#aaa')
const size = data.size / 1.7; // experimental... const size = data.size / 1.7; // experimental...
const contourSize = size + 0.8; // experimental... const contour_size = size + 0.8;
// contour
array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = contour_size;
array[startIndex++] = gray;
array[startIndex++] = nodeIndex;
// circle
array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = size;
array[startIndex++] = color;
array[startIndex++] = nodeIndex;
}
setUniforms(params, { gl, uniformLocations }) {
const { u_sizeRatio, u_correctionRatio, u_matrix } = uniformLocations;
return [ { x: data.x, y: data.y, size: contourSize, color: gray, angles: [ANGLE_1_1, ANGLE_1_2, ANGLE_1_3] }, gl.uniform1f(u_correctionRatio, params.correctionRatio);
{ x: data.x, y: data.y, size: contourSize, color: gray, angles: [ANGLE_2_1, ANGLE_2_2, ANGLE_2_3] }, gl.uniform1f(u_sizeRatio, params.sizeRatio);
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_1_1, ANGLE_1_2, ANGLE_1_3] }, gl.uniformMatrix3fv(u_matrix, false, params.matrix);
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_2_1, ANGLE_2_2, ANGLE_2_3] } ];
} }
} }
\ No newline at end of file
...@@ -8,29 +8,60 @@ ...@@ -8,29 +8,60 @@
* @module * @module
*/ */
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import TriangleProgram from "./sigmajs-triangle-abstract"; import { NodeProgram } from "sigma/rendering";
const POINTS = 6; import VERTEX_SHADER_SOURCE from "./triangle-vert.glsl";
const ATTRIBUTES = 5; import FRAGMENT_SHADER_SOURCE from "./triangle-frag.glsl";
const ANGLE_1_1 = (0 * Math.PI) / 4; const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
const ANGLE_1_2 = (2 * Math.PI) / 4;
const ANGLE_1_3 = (4 * Math.PI) / 4;
const ANGLE_2_1 = (4 * Math.PI) / 4;
const ANGLE_2_2 = (6 * Math.PI) / 4;
const ANGLE_2_3 = (8 * Math.PI) / 4;
export default class NodeProgram extends TriangleProgram { const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_matrix"];
constructor(gl) {
super(gl, POINTS, ATTRIBUTES); export default class NodeDiamondProgram extends NodeProgram {
static ANGLE_1_1 = (0 * Math.PI) / 4;
static ANGLE_1_2 = (2 * Math.PI) / 4;
static ANGLE_1_3 = (4 * Math.PI) / 4;
static ANGLE_2_1 = (4 * Math.PI) / 4;
static ANGLE_2_2 = (6 * Math.PI) / 4;
static ANGLE_2_3 = (8 * Math.PI) / 4;
getDefinition() {
return {
VERTICES: 6,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
CONSTANT_ATTRIBUTES: [{ name: "a_angle", size: 1, type: FLOAT }],
CONSTANT_DATA: [[NodeDiamondProgram.ANGLE_1_1], [NodeDiamondProgram.ANGLE_1_2], [NodeDiamondProgram.ANGLE_1_3]
,[NodeDiamondProgram.ANGLE_2_1], [NodeDiamondProgram.ANGLE_2_2], [NodeDiamondProgram.ANGLE_2_3]],
};
} }
triangleDefinitions(data) { processVisibleItem(nodeIndex, startIndex, data) {
const array = this.array;
const color = floatColor(data.color);
const size = data.size / 1.7; // experimental... const size = data.size / 1.7; // experimental...
return [ { x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_1_1, ANGLE_1_2, ANGLE_1_3] }, array[startIndex++] = data.x;
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_2_1, ANGLE_2_2, ANGLE_2_3] } ]; array[startIndex++] = data.y;
array[startIndex++] = size;
array[startIndex++] = color;
array[startIndex++] = nodeIndex;
}
setUniforms(params, { gl, uniformLocations }) {
const { u_sizeRatio, u_correctionRatio, u_matrix } = uniformLocations;
gl.uniform1f(u_correctionRatio, params.correctionRatio);
gl.uniform1f(u_sizeRatio, params.sizeRatio);
gl.uniformMatrix3fv(u_matrix, false, params.matrix);
} }
} }
...@@ -8,33 +8,72 @@ ...@@ -8,33 +8,72 @@
* @module * @module
*/ */
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import TriangleProgram from "./sigmajs-triangle-abstract"; import { NodeProgram } from "sigma/rendering";
const POINTS = 12; import VERTEX_SHADER_SOURCE from "./triangle-vert.glsl";
const ATTRIBUTES = 5; import FRAGMENT_SHADER_SOURCE from "./triangle-frag.glsl";
const ANGLE_1_1 = - (1 * Math.PI) / 4; const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
const ANGLE_1_2 = (1 * Math.PI) / 4;
const ANGLE_1_3 = (3 * Math.PI) / 4;
const ANGLE_2_1 = (3 * Math.PI) / 4;
const ANGLE_2_2 = (5 * Math.PI) / 4;
const ANGLE_2_3 = (7 * Math.PI) / 4;
export default class NodeProgram extends TriangleProgram { const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_matrix"];
constructor(gl) {
super(gl, POINTS, ATTRIBUTES); export default class NodeSquareContourProgram extends NodeProgram {
static ANGLE_1_1 = - (1 * Math.PI) / 4;
static ANGLE_1_2 = (1 * Math.PI) / 4;
static ANGLE_1_3 = (3 * Math.PI) / 4;
static ANGLE_2_1 = (3 * Math.PI) / 4;
static ANGLE_2_2 = (5 * Math.PI) / 4;
static ANGLE_2_3 = (7 * Math.PI) / 4;
getDefinition() {
return {
VERTICES: 12,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
CONSTANT_ATTRIBUTES: [{ name: "a_angle", size: 1, type: FLOAT }],
CONSTANT_DATA: [[NodeSquareContourProgram.ANGLE_1_1], [NodeSquareContourProgram.ANGLE_1_2], [NodeSquareContourProgram.ANGLE_1_3]
,[NodeSquareContourProgram.ANGLE_2_1], [NodeSquareContourProgram.ANGLE_2_2], [NodeSquareContourProgram.ANGLE_2_3]
,[NodeSquareContourProgram.ANGLE_1_1], [NodeSquareContourProgram.ANGLE_1_2], [NodeSquareContourProgram.ANGLE_1_3]
,[NodeSquareContourProgram.ANGLE_2_1], [NodeSquareContourProgram.ANGLE_2_2], [NodeSquareContourProgram.ANGLE_2_3]],
};
} }
triangleDefinitions(data) { processVisibleItem(nodeIndex, startIndex, data) {
const gray = '#aaa'; const array = this.array;
const color = floatColor(data.color);
const gray = floatColor('#aaa')
const size = data.size / 1.7; // experimental... const size = data.size / 1.7; // experimental...
const contourSize = size + 0.8; // experimental... const contour_size = size + 0.8;
// contour
array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = contour_size;
array[startIndex++] = gray;
array[startIndex++] = nodeIndex;
// circle
array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = size;
array[startIndex++] = color;
array[startIndex++] = nodeIndex;
}
setUniforms(params, { gl, uniformLocations }) {
const { u_sizeRatio, u_correctionRatio, u_matrix } = uniformLocations;
return [ { x: data.x, y: data.y, size: contourSize, color: gray, angles: [ANGLE_1_1, ANGLE_1_2, ANGLE_1_3] }, gl.uniform1f(u_correctionRatio, params.correctionRatio);
{ x: data.x, y: data.y, size: contourSize, color: gray, angles: [ANGLE_2_1, ANGLE_2_2, ANGLE_2_3] }, gl.uniform1f(u_sizeRatio, params.sizeRatio);
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_1_1, ANGLE_1_2, ANGLE_1_3] }, gl.uniformMatrix3fv(u_matrix, false, params.matrix);
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_2_1, ANGLE_2_2, ANGLE_2_3] } ];
} }
} }
\ No newline at end of file
...@@ -8,29 +8,61 @@ ...@@ -8,29 +8,61 @@
* @module * @module
*/ */
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import TriangleProgram from "./sigmajs-triangle-abstract"; import { NodeProgram } from "sigma/rendering";
const POINTS = 6; import VERTEX_SHADER_SOURCE from "./triangle-vert.glsl";
const ATTRIBUTES = 5; import FRAGMENT_SHADER_SOURCE from "./triangle-frag.glsl";
const ANGLE_1_1 = - (1 * Math.PI) / 4; const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
const ANGLE_1_2 = (1 * Math.PI) / 4;
const ANGLE_1_3 = (3 * Math.PI) / 4;
const ANGLE_2_1 = (3 * Math.PI) / 4;
const ANGLE_2_2 = (5 * Math.PI) / 4;
const ANGLE_2_3 = (7 * Math.PI) / 4;
export default class NodeProgram extends TriangleProgram { const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_matrix"];
constructor(gl) {
super(gl, POINTS, ATTRIBUTES); export default class NodeSquareProgram extends NodeProgram {
static ANGLE_1_1 = - (1 * Math.PI) / 4;
static ANGLE_1_2 = (1 * Math.PI) / 4;
static ANGLE_1_3 = (3 * Math.PI) / 4;
static ANGLE_2_1 = (3 * Math.PI) / 4;
static ANGLE_2_2 = (5 * Math.PI) / 4;
static ANGLE_2_3 = (7 * Math.PI) / 4;
getDefinition() {
return {
VERTICES: 6,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
CONSTANT_ATTRIBUTES: [{ name: "a_angle", size: 1, type: FLOAT }],
CONSTANT_DATA: [[NodeSquareProgram.ANGLE_1_1], [NodeSquareProgram.ANGLE_1_2], [NodeSquareProgram.ANGLE_1_3]
,[NodeSquareProgram.ANGLE_2_1], [NodeSquareProgram.ANGLE_2_2], [NodeSquareProgram.ANGLE_2_3]],
};
} }
triangleDefinitions(data) { processVisibleItem(nodeIndex, startIndex, data) {
const array = this.array;
const color = floatColor(data.color);
const size = data.size / 1.7; // experimental... const size = data.size / 1.7; // experimental...
return [ { x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_1_1, ANGLE_1_2, ANGLE_1_3] }, array[startIndex++] = data.x;
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_2_1, ANGLE_2_2, ANGLE_2_3] }]; array[startIndex++] = data.y;
array[startIndex++] = size;
array[startIndex++] = color;
array[startIndex++] = nodeIndex;
}
setUniforms(params, { gl, uniformLocations }) {
const { u_sizeRatio, u_correctionRatio, u_matrix } = uniformLocations;
gl.uniform1f(u_correctionRatio, params.correctionRatio);
gl.uniform1f(u_sizeRatio, params.sizeRatio);
gl.uniformMatrix3fv(u_matrix, false, params.matrix);
} }
} }
/**
* Sigma.js WebGL Renderer Node Program
* =====================================
*
* Simple program rendering nodes as triangles.
* It does not extend AbstractNodeProgram, which works very differently, and
* really targets the gl.POINTS drawing methods.
* @module
*/
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils";
import { AbstractProgram, RenderParams } from "sigma/rendering/webgl/programs/common/program";
const vertexShaderSource = `
attribute vec2 a_position;
attribute float a_size;
attribute float a_angle;
attribute vec4 a_color;
uniform mat3 u_matrix;
uniform float u_sqrtZoomRatio;
uniform float u_correctionRatio;
varying vec4 v_color;
varying vec2 v_diffVector;
varying float v_radius;
varying float v_border;
const float bias = 255.0 / 254.0;
const float marginRatio = 1.05;
void main() {
float size = a_size * u_correctionRatio * u_sqrtZoomRatio * 4.0;
vec2 diffVector = size * vec2(cos(a_angle), sin(a_angle));
vec2 position = a_position + diffVector * marginRatio;
gl_Position = vec4(
(u_matrix * vec3(position, 1)).xy,
0,
1
);
v_border = u_correctionRatio * u_sqrtZoomRatio * u_sqrtZoomRatio;
v_diffVector = diffVector;
//v_radius = size / 2.0 / marginRatio;
v_radius = 1.0;
v_color = a_color;
v_color.a *= bias;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec4 v_color;
varying vec2 v_diffVector;
varying float v_radius;
varying float v_border;
const vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0);
void main(void) {
float dist = length(v_diffVector) - v_radius;
// Originally, a triangle is drawn. This code paints it in such a
// way that a circle is rendered.
//float t = 0.0;
//if (dist > v_border) {
// t = 1.0;
//} else if (dist > 0.0) {
// t = dist / v_border;
//}
//gl_FragColor = mix(v_color, transparent, t);
gl_FragColor = v_color;
}
`;
const POINTS = 3;
const ATTRIBUTES = 5;
const ANGLE_1 = - (0.5 * Math.PI) / 3;
const ANGLE_2 = (1.5 * Math.PI) / 3;
const ANGLE_3 = (3.5 * Math.PI) / 3;
export default class NodeProgram extends AbstractProgram {
constructor(gl, points, attributes) {
let pts = points || POINTS;
let attribs = attributes || ATTRIBUTES;
super(gl, vertexShaderSource, fragmentShaderSource, pts, attribs);
// Locations
this.positionLocation = gl.getAttribLocation(this.program, "a_position");
this.sizeLocation = gl.getAttribLocation(this.program, "a_size");
this.colorLocation = gl.getAttribLocation(this.program, "a_color");
this.angleLocation = gl.getAttribLocation(this.program, "a_angle");
// Uniform Location
const matrixLocation = gl.getUniformLocation(this.program, "u_matrix");
if (matrixLocation === null) throw new Error("AbstractNodeProgram: error while getting matrixLocation");
this.matrixLocation = matrixLocation;
const sqrtZoomRatioLocation = gl.getUniformLocation(this.program, "u_sqrtZoomRatio");
if (sqrtZoomRatioLocation === null) throw new Error("NodeProgram: error while getting sqrtZoomRatioLocation");
this.sqrtZoomRatioLocation = sqrtZoomRatioLocation;
const correctionRatioLocation = gl.getUniformLocation(this.program, "u_correctionRatio");
if (correctionRatioLocation === null) throw new Error("NodeProgram: error while getting correctionRatioLocation");
this.correctionRatioLocation = correctionRatioLocation;
this.bind();
}
bind() {
const gl = this.gl;
gl.enableVertexAttribArray(this.positionLocation);
gl.enableVertexAttribArray(this.sizeLocation);
gl.enableVertexAttribArray(this.colorLocation);
gl.enableVertexAttribArray(this.angleLocation);
gl.vertexAttribPointer(
this.positionLocation,
2,
gl.FLOAT,
false,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
0,
);
gl.vertexAttribPointer(
this.sizeLocation,
1,
gl.FLOAT,
false,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
8
);
gl.vertexAttribPointer(
this.colorLocation,
4,
gl.UNSIGNED_BYTE,
true,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
12,
);
gl.vertexAttribPointer(
this.angleLocation,
1,
gl.FLOAT,
false,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
16,
);
}
process(data, hidden, offset) {
const array = this.array;
let i = offset * this.points * this.attributes;
if (hidden) {
for (let l = i + this.points * this.attributes; i < l; i++) array[i] = 0;
return;
}
let definitions = this.triangleDefinitions(data);
for(let l = 0; l < definitions.length; l++) {
this.renderTriangle(i + l * 3*this.attributes, definitions[l]);
}
}
// overwrite this function
triangleDefinitions(data) {
const size = data.size / 1.7; // experimental...
return [ { x: data.x, y: data.y, size: data.size, color: data.color, angles: [ANGLE_1, ANGLE_2, ANGLE_3] } ];
}
renderTriangle(i, { x, y, size, color, angles }) {
const array = this.array;
const fColor = floatColor(color);
array[i++] = x;
array[i++] = y;
array[i++] = size;
array[i++] = fColor;
array[i++] = angles[0];
array[i++] = x;
array[i++] = y;
array[i++] = size;
array[i++] = fColor;
array[i++] = angles[1];
array[i++] = x;
array[i++] = y;
array[i++] = size;
array[i++] = fColor;
array[i] = angles[2];
}
render(params) {
if (this.hasNothingToRender()) return;
const gl = this.gl;
const program = this.program;
gl.useProgram(program);
gl.uniformMatrix3fv(this.matrixLocation, false, params.matrix);
gl.uniform1f(this.sqrtZoomRatioLocation, Math.sqrt(params.ratio));
gl.uniform1f(this.correctionRatioLocation, params.correctionRatio);
gl.drawArrays(gl.TRIANGLES, 0, this.array.length / this.attributes);
}
}
...@@ -8,28 +8,67 @@ ...@@ -8,28 +8,67 @@
* @module * @module
*/ */
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import TriangleProgram from "./sigmajs-triangle-abstract"; import { NodeProgram } from "sigma/rendering";
const POINTS = 6; import VERTEX_SHADER_SOURCE from "./triangle-vert.glsl";
const ATTRIBUTES = 5; import FRAGMENT_SHADER_SOURCE from "./triangle-frag.glsl";
const ANGLE_1 = - (0.5 * Math.PI) / 3; const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
const ANGLE_2 = (1.5 * Math.PI) / 3;
const ANGLE_3 = (3.5 * Math.PI) / 3;
export default class NodeProgram extends TriangleProgram { const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_matrix"];
constructor(gl) {
super(gl, POINTS, ATTRIBUTES); export default class NodeTriangleContourProgram extends NodeProgram {
static ANGLE_1 = - (0.5 * Math.PI) / 3;
static ANGLE_2 = (1.5 * Math.PI) / 3;
static ANGLE_3 = (3.5 * Math.PI) / 3;
getDefinition() {
return {
VERTICES: 6,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
CONSTANT_ATTRIBUTES: [{ name: "a_angle", size: 1, type: FLOAT }],
CONSTANT_DATA: [[NodeTriangleContourProgram.ANGLE_1], [NodeTriangleContourProgram.ANGLE_2], [NodeTriangleContourProgram.ANGLE_3]
,[NodeTriangleContourProgram.ANGLE_1], [NodeTriangleContourProgram.ANGLE_2], [NodeTriangleContourProgram.ANGLE_3]],
};
} }
triangleDefinitions(data) { processVisibleItem(nodeIndex, startIndex, data) {
const array = this.array;
const color = floatColor(data.color);
const gray = floatColor('#aaa') const gray = floatColor('#aaa')
const size = data.size / 2.3; // experimental... const size = data.size / 1.7; // experimental...
const contourSize = size + 0.8; // experimental... const contour_size = size + 0.8;
// contour
array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = contour_size;
array[startIndex++] = gray;
array[startIndex++] = nodeIndex;
// circle
array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = size;
array[startIndex++] = color;
array[startIndex++] = nodeIndex;
}
setUniforms(params, { gl, uniformLocations }) {
const { u_sizeRatio, u_correctionRatio, u_matrix } = uniformLocations;
return [ { x: data.x, y: data.y, size: contourSize, color: '#aaa', angles: [ANGLE_1, ANGLE_2, ANGLE_3] }, gl.uniform1f(u_correctionRatio, params.correctionRatio);
{ x: data.x, y: data.y, size: size, color: data.color, angles: [ANGLE_1, ANGLE_2, ANGLE_3] }]; gl.uniform1f(u_sizeRatio, params.sizeRatio);
gl.uniformMatrix3fv(u_matrix, false, params.matrix);
} }
} }
\ No newline at end of file
...@@ -8,193 +8,56 @@ ...@@ -8,193 +8,56 @@
* @module * @module
*/ */
import { NodeDisplayData } from "sigma/types";
import { floatColor } from "sigma/utils"; import { floatColor } from "sigma/utils";
import { AbstractProgram, RenderParams } from "sigma/rendering/webgl/programs/common/program"; import { NodeProgram } from "sigma/rendering";
const vertexShaderSource = ` import VERTEX_SHADER_SOURCE from "./triangle-vert.glsl";
attribute vec2 a_position; import FRAGMENT_SHADER_SOURCE from "./triangle-frag.glsl";
attribute float a_size;
attribute float a_angle; const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;
attribute vec4 a_color;
const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_matrix"];
uniform mat3 u_matrix;
uniform float u_sqrtZoomRatio; export default class NodeTriangleProgram extends NodeProgram {
uniform float u_correctionRatio; static ANGLE_1 = - (0.5 * Math.PI) / 3;
static ANGLE_2 = (1.5 * Math.PI) / 3;
varying vec4 v_color; static ANGLE_3 = (3.5 * Math.PI) / 3;
varying vec2 v_diffVector;
varying float v_radius; getDefinition() {
varying float v_border; return {
VERTICES: 3,
const float bias = 255.0 / 254.0; VERTEX_SHADER_SOURCE,
const float marginRatio = 1.05; FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
void main() { UNIFORMS,
float size = a_size * u_correctionRatio * u_sqrtZoomRatio * 4.0; ATTRIBUTES: [
vec2 diffVector = size * vec2(cos(a_angle), sin(a_angle)); { name: "a_position", size: 2, type: FLOAT },
vec2 position = a_position + diffVector * marginRatio; { name: "a_size", size: 1, type: FLOAT },
gl_Position = vec4( { name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
(u_matrix * vec3(position, 1)).xy, { name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
0, ],
1 CONSTANT_ATTRIBUTES: [{ name: "a_angle", size: 1, type: FLOAT }],
); CONSTANT_DATA: [[NodeTriangleProgram.ANGLE_1], [NodeTriangleProgram.ANGLE_2], [NodeTriangleProgram.ANGLE_3]],
};
v_border = u_correctionRatio * u_sqrtZoomRatio * u_sqrtZoomRatio;
v_diffVector = diffVector;
//v_radius = size / 2.0 / marginRatio;
v_radius = 1.0;
v_color = a_color;
v_color.a *= bias;
}
`;
const fragmentShaderSource = `
precision mediump float;
varying vec4 v_color;
varying vec2 v_diffVector;
varying float v_radius;
varying float v_border;
const vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0);
void main(void) {
float dist = length(v_diffVector) - v_radius;
// Originally, a triangle is drawn. This code paints it in such a
// way that a circle is rendered.
//float t = 0.0;
//if (dist > v_border) {
// t = 1.0;
//} else if (dist > 0.0) {
// t = dist / v_border;
//}
//gl_FragColor = mix(v_color, transparent, t);
gl_FragColor = v_color;
}
`;
const POINTS = 3;
const ATTRIBUTES = 5;
const ANGLE_1 = - (0.5 * Math.PI) / 3;
const ANGLE_2 = (1.5 * Math.PI) / 3;
const ANGLE_3 = (3.5 * Math.PI) / 3;
export default class NodeProgram extends AbstractProgram {
constructor(gl) {
super(gl, vertexShaderSource, fragmentShaderSource, POINTS, ATTRIBUTES);
// Locations
this.positionLocation = gl.getAttribLocation(this.program, "a_position");
this.sizeLocation = gl.getAttribLocation(this.program, "a_size");
this.colorLocation = gl.getAttribLocation(this.program, "a_color");
this.angleLocation = gl.getAttribLocation(this.program, "a_angle");
// Uniform Location
const matrixLocation = gl.getUniformLocation(this.program, "u_matrix");
if (matrixLocation === null) throw new Error("AbstractNodeProgram: error while getting matrixLocation");
this.matrixLocation = matrixLocation;
const sqrtZoomRatioLocation = gl.getUniformLocation(this.program, "u_sqrtZoomRatio");
if (sqrtZoomRatioLocation === null) throw new Error("NodeProgram: error while getting sqrtZoomRatioLocation");
this.sqrtZoomRatioLocation = sqrtZoomRatioLocation;
const correctionRatioLocation = gl.getUniformLocation(this.program, "u_correctionRatio");
if (correctionRatioLocation === null) throw new Error("NodeProgram: error while getting correctionRatioLocation");
this.correctionRatioLocation = correctionRatioLocation;
this.bind();
} }
bind() { processVisibleItem(nodeIndex, startIndex, data) {
const gl = this.gl;
gl.enableVertexAttribArray(this.positionLocation);
gl.enableVertexAttribArray(this.sizeLocation);
gl.enableVertexAttribArray(this.colorLocation);
gl.enableVertexAttribArray(this.angleLocation);
gl.vertexAttribPointer(
this.positionLocation,
2,
gl.FLOAT,
false,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
0,
);
gl.vertexAttribPointer(
this.sizeLocation,
1,
gl.FLOAT,
false,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
8
);
gl.vertexAttribPointer(
this.colorLocation,
4,
gl.UNSIGNED_BYTE,
true,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
12,
);
gl.vertexAttribPointer(
this.angleLocation,
1,
gl.FLOAT,
false,
this.attributes * Float32Array.BYTES_PER_ELEMENT,
16,
);
}
process(data, hidden, offset) {
const array = this.array; const array = this.array;
let i = offset * POINTS * ATTRIBUTES;
if (hidden) {
for (let l = i + POINTS * ATTRIBUTES; i < l; i++) array[i] = 0;
return;
}
const color = floatColor(data.color); const color = floatColor(data.color);
const size = data.size / 1.7; // experimental... const size = data.size / 1.7; // experimental...
array[i++] = data.x; array[startIndex++] = data.x;
array[i++] = data.y; array[startIndex++] = data.y;
array[i++] = size; array[startIndex++] = size;
array[i++] = color; array[startIndex++] = color;
array[i++] = ANGLE_1; array[startIndex++] = nodeIndex;
array[i++] = data.x;
array[i++] = data.y;
array[i++] = size;
array[i++] = color;
array[i++] = ANGLE_2;
array[i++] = data.x;
array[i++] = data.y;
array[i++] = size;
array[i++] = color;
array[i] = ANGLE_3;
} }
render(params) { setUniforms(params, { gl, uniformLocations }) {
if (this.hasNothingToRender()) return; const { u_sizeRatio, u_correctionRatio, u_matrix } = uniformLocations;
const gl = this.gl;
const program = this.program;
gl.useProgram(program);
gl.uniformMatrix3fv(this.matrixLocation, false, params.matrix);
gl.uniform1f(this.sqrtZoomRatioLocation, Math.sqrt(params.ratio));
gl.uniform1f(this.correctionRatioLocation, params.correctionRatio);
gl.drawArrays(gl.TRIANGLES, 0, this.array.length / ATTRIBUTES); gl.uniform1f(u_correctionRatio, params.correctionRatio);
gl.uniform1f(u_sizeRatio, params.sizeRatio);
gl.uniformMatrix3fv(u_matrix, false, params.matrix);
} }
} }
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
precision highp float;
varying vec4 v_color;
varying vec2 v_diffVector;
varying float v_radius;
uniform float u_correctionRatio;
const vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0);
void main(void) {
float border = u_correctionRatio * 2.0;
float dist = length(v_diffVector) - v_radius + border;
// Originally, a triangle is drawn. This code paints it in such a
// way that a circle is rendered.
//float t = 0.0;
//if (dist > v_border) {
// t = 1.0;
//} else if (dist > 0.0) {
// t = dist / v_border;
//}
//gl_FragColor = mix(v_color, transparent, t);
gl_FragColor = v_color;
}
`;
export default SHADER_SOURCE;
\ No newline at end of file
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
attribute vec4 a_id;
attribute vec4 a_color;
attribute vec2 a_position;
attribute float a_size;
attribute float a_angle;
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_correctionRatio;
varying vec4 v_color;
varying vec2 v_diffVector;
varying float v_radius;
varying float v_border;
const float bias = 255.0 / 254.0;
void main() {
float size = a_size * u_correctionRatio / u_sizeRatio * 4.0;
vec2 diffVector = size * vec2(cos(a_angle), sin(a_angle));
vec2 position = a_position + diffVector;
gl_Position = vec4(
(u_matrix * vec3(position, 1)).xy,
0,
1
);
v_diffVector = diffVector;
v_radius = size / 2.0;
#ifdef PICKING_MODE
// For picking mode, we use the ID as the color:
v_color = a_id;
#else
// For normal mode, we use the color:
v_color = a_color;
#endif
v_color.a *= bias;
}
`;
export default SHADER_SOURCE;
\ No newline at end of file
@import './abstract/_variables'
@import './abstract/_members'
@import './abstract/_variables';
@import './abstract/_members';
@import "./base/_reset.scss"
@import "./base/_general.scss"
@import "./base/_form.scss"
@import "./base/_layout.scss"
@import "./base/_nav.scss"
@import "./base/_typography.scss"
@import "./base/_animations.scss"
@import "./base/_bootstrap.scss"
@import "./base/_placeholder.scss"
@import "./base/_utilities.scss"
@import "./base/_range_slider.sass"
@import "./base/_reset.scss";
@import "./base/_general.scss";
@import "./base/_form.scss";
@import "./base/_layout.scss";
@import "./base/_nav.scss";
@import "./base/_typography.scss";
@import "./base/_animations.scss";
@import "./base/_bootstrap.scss";
@import "./base/_placeholder.scss";
@import "./base/_utilities.scss";
@import "./base/_range_slider.scss";
@import "./components/_grouped.scss"
@import "./components/_loading_spinner.scss"
@import "./components/_grouped.scss";
@import "./components/_loading_spinner.scss";
@import "./_legacy/_menu"
@import "./_legacy/_graph"
@import "./_legacy/_login"
@import "./_legacy/_tree"
@import "./_legacy/_code_editor"
@import "./_legacy/_styles"
@import "./_legacy/_list"
@import "./_legacy/_annotation"
@import "./_legacy/_phylo"
@import "./_legacy/_frame"
@import "./_legacy/_document"
@import "./_legacy/_folder"
@import "./_legacy/_corpus"
@import "./_legacy/_annuaire"
@import "./_legacy/_search"
@import "./_legacy/_breadcrumb"
\ No newline at end of file
@import "./_legacy/_menu";
@import "./_legacy/_graph";
@import "./_legacy/_login";
@import "./_legacy/_tree";
@import "./_legacy/_code_editor";
@import "./_legacy/_styles";
@import "./_legacy/_list";
@import "./_legacy/_annotation";
@import "./_legacy/_phylo";
@import "./_legacy/_frame";
@import "./_legacy/_document";
@import "./_legacy/_folder";
@import "./_legacy/_corpus";
@import "./_legacy/_annuaire";
@import "./_legacy/_search";
@import "./_legacy/_breadcrumb";
\ No newline at end of file
.page-annuaire
&__nooverflow
max-width: 300px
height : 24px
overflow: hidden
text-overflow: ellipsis
&:hover
overflow: visible
height: auto
.page-annuaire {
&__nooverflow {
max-width: 300px;
height: 24px;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
overflow: visible;
height: auto;
}
}
}
.breadcrumb-wrapper
border-bottom: 1px solid $border-color
.breadcrumb
background-color: transparent
padding: space-x(0.5) space-x(0.5) !important
margin: 0 !important
&-item
color: $gray-700
& .active-page
font-weight: bold
color: $primary
.b-icon
margin: 0 space-x(0.5)
.fa-spinner
font-size: 100%
margin: auto 10px
\ No newline at end of file
.breadcrumb-wrapper {
border-bottom: 1px solid $border-color;
.breadcrumb {
background-color: transparent;
padding: space-x(0.5) space-x(0.5) !important;
margin: 0 !important;
&-item {
color: $gray-700;
& .active-page {
font-weight: bold;
color: $primary;
}
.b-icon {
margin: 0 space-x(0.5);
}
}
}
.fa-spinner {
font-size: 100%;
margin: auto 10px;
}
}
@mixin font-inherit()
font-family: inherit
font-size: inherit
font-style: inherit
font-variant-ligatures: inherit
font-weight: inherit
letter-spacing: inherit
line-height: inherit
text-indent: inherit
text-rendering: inherit
text-transform: inherit
@mixin common-overlay-props()
box-sizing: inherit
display: inherit
margin: 0px
padding: 10px
overflow-wrap: break-word
white-space: pre-wrap
word-break: keep-all
.code-editor
.editor
.code-area
flex-grow: 1
max-height: 200px
min-width: 25%
overflow: auto
.code-container
background-color: #fafafa
box-sizing: border-box
position: relative
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace
font-size: 12px
font-variant-ligatures: common-ligatures
line-height: 1.5
overflow: hidden
padding: 0px
text-align: left
textarea
border: 0px
color: inherit
position: absolute
left: 0px
top: 0px
resize: none
height: 100%
overflow: hidden
width: 100%
-webkit-text-fill-color: transparent
@include common-overlay-props()
@include font-inherit()
pre
background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%
border: 0px none
color: #000
pointer-events: none
position: relative
@include common-overlay-props()
@include font-inherit()
.v-divider
border-left: 1px solid gray
cursor: sw-resize
height: 100%
margin-left: 5px
margin-right: 5px
width: 1px
.html
flex-grow: 2
margin-left: 8px
margin-right: 8px
padding-left: 8px
padding-right: 8px
&.language-haskell
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace
white-space: pre
&.language-python
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace
white-space: pre
&.language-json
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace
white-space: pre
&.language-md
ul
li
list-style: disc !important
ol
li
list-style: decimal !important
&__toolbar
margin-bottom: space-x(2.5)
&__type
width: 200px
margin-right: space-x(2)
@mixin font-inherit() {
font-family: inherit;
font-size: inherit;
font-style: inherit;
font-variant-ligatures: inherit;
font-weight: inherit;
letter-spacing: inherit;
line-height: inherit;
text-indent: inherit;
text-rendering: inherit;
text-transform: inherit;
}
@mixin common-overlay-props() {
box-sizing: inherit;
display: inherit;
margin: 0px;
padding: 10px;
overflow-wrap: break-word;
white-space: pre-wrap;
word-break: keep-all;
}
.code-editor {
.editor {
.code-area {
flex-grow: 1;
max-height: 200px;
min-width: 25%;
overflow: auto;
.code-container {
background-color: #fafafa;
box-sizing: border-box;
position: relative;
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace;
font-size: 12px;
font-variant-ligatures: common-ligatures;
line-height: 1.5;
overflow: hidden;
padding: 0px;
text-align: left;
textarea {
border: 0px;
color: inherit;
position: absolute;
left: 0px;
top: 0px;
resize: none;
height: 100%;
overflow: hidden;
width: 100%;
-webkit-text-fill-color: transparent;
@include common-overlay-props();
@include font-inherit();
}
pre {
background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%;
border: 0px none;
color: #000;
pointer-events: none;
position: relative;
@include common-overlay-props();
@include font-inherit();
}
}
}
.v-divider {
border-left: 1px solid gray;
cursor: sw-resize;
height: 100%;
margin-left: 5px;
margin-right: 5px;
width: 1px;
}
.html {
flex-grow: 2;
margin-left: 8px;
margin-right: 8px;
padding-left: 8px;
padding-right: 8px;
&.language-haskell {
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace;
white-space: pre;
}
&.language-python {
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace;
white-space: pre;
}
&.language-json {
font-family: Fira code,Fira Mono,Consolas,Menlo,Courier,monospace;
white-space: pre;
}
&.language-md {
ul {
li {
list-style: disc !important;
}
}
ol {
li {
list-style: decimal !important;
}
}
}
}
}
&__toolbar {
margin-bottom: space-x(2.5);
&__type {
width: 200px;
margin-right: space-x(2);
}
}
}
\ No newline at end of file
.corpus-layout .corpus-layout {
margin-bottom: space-x(1) margin-bottom: space-x(1);
&__title &__title {
display: flex display: flex;
align-items: center align-items: center;
&__text &__text {
font-size: $h3-font-size font-size: $h3-font-size;
font-family: $headings-font-family font-family: $headings-font-family;
}
&__line &__line {
// (?) see "_list.sass" > ".table-header-rename__line" for more infos // (?) see "_list.sass" > ".table-header-rename__line" for more infos
border-bottom: 1px solid $gray-200 border-bottom: 1px solid $gray-200;
border-top: none border-top: none;
// (?) strange behavior where browser engine add it by itself // (?) strange behavior where browser engine add it by itself
height: 0 height: 0;
flex-grow: 1 flex-grow: 1;
margin-left: space-x(4) margin-left: space-x(4);
margin-right: space-x(4) margin-right: space-x(4);
}
&__expand &__expand {
margin-left: space-x(4) margin-left: space-x(4);
}
}
&__edition-block {
border-bottom: 1px solid $gray-200;
&__edition-block
border-bottom: 1px solid $gray-200
// using <table-header-rename-edition> padding to prettify the overall UI // using <table-header-rename-edition> padding to prettify the overall UI
// (+ <form-group> padding) // (+ <form-group> padding)
margin-bottom: calc( #{$card-spacer-y} + #{ space-x(0.75) } ) margin-bottom: calc( #{$card-spacer-y} + #{space-x(0.75)} );
}
&__code-section &__code-section {
margin-top: space-x(1) margin-top: space-x(1);
}
&__folders &__folders {
margin-top: space-x(6) margin-top: space-x(6);
}
// @TODO: switch <tile-menu> component using a <popover> for a reworked // @TODO: switch <tile-menu> component using a <popover> for a reworked
// <context-menu> (easier to use). <popover> has far too many issues, // <context-menu> (easier to use). <popover> has far too many issues,
// such as the below micro-managing // such as the below micro-managing
.tile-menu__popover .tile-menu__popover {
position: relative position: relative;
width: 200px width: 200px;
}
}
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
.corpus-code-layout .corpus-code-layout {
position: relative position: relative;
&__toolbar &__toolbar {
display: flex display: flex;
align-items: center align-items: center;
justify-content: space-between justify-content: space-between;
border-bottom: 1px solid $border-color border-bottom: 1px solid $border-color;
padding: $card-spacer-y $card-spacer-x padding: $card-spacer-y $card-spacer-x;
background-color: $body-bg background-color: $body-bg;
position: sticky position: sticky;
top: $topbar-height top: $topbar-height;
z-index: z-index('corpus-code', 'toolbar') z-index: z-index("corpus-code", "toolbar");
// @XXX: reset "main-page__main-route" wrapper margin // @XXX: reset "main-page__main-route" wrapper margin
margin-left: space-x(-4) margin-left: space-x(-4);
margin-right: space-x(-4) margin-right: space-x(-4);
margin-top: space-x(-3) margin-top: space-x(-3);
}
&__fields
margin-top: space-x(3) &__fields {
z-index: z-index('corpus-code', 'content') margin-top: space-x(3);
z-index: z-index("corpus-code", "content");
.code-section-item }
}
.code-section-item {
// accentuate disabled icon effect // accentuate disabled icon effect
.card-header .b-icon-button--disabled .card-header .b-icon-button--disabled {
opacity: 0.2 opacity: 0.2;
}
// using "bootstrap" Element change to alter the caret icon // using "bootstrap" Element change to alter the caret icon
[aria-expanded="true"] [aria-expanded="true"] {
transform: rotate(180deg) transform: rotate(180deg);
}
}
// Every website specialised in scientific articles deals with a column
// container between 760px and 800px
$document-container-width: 780px
///////////////////////////////////////////////////////////
.document-layout
$label-column-size: 96px
position: relative
width: 100%
&__header
display: flex
justify-content: space-between
padding: $card-spacer-y $card-spacer-x
border-bottom: 1px solid $border-color
// margin-bottom: $card-spacer-y
&__main-controls
display: flex
align-items: center
&__side-controls
display: flex
align-items: center
&__saving
color: $primary
display: flex
&__spinner
width: 16px
height: 16px
&__icon
font-size: 20px
// fading icon apparition with a little pulse
// wait a little, then fading icon on hiding
animation: fade-in 200ms ease-in-out, fade-out 100ms ease-in-out 2000ms 1 normal forwards, pulse 200ms ease-in-out 0ms 1
&__body
// max-width: $document-container-width
padding: $card-spacer-x
margin: 0
max-height: 70vh
overflow-y: auto
&__separator-label
font-weight: bold
border-bottom: 1px solid $gray-200
padding-bottom: space-x(0.5)
margin-bottom: space-x(1.5)
&__title
font-size: 26px
font-weight: bold
line-height: 1.4
word-wrap: break-word
// margin-bottom: space-x(3)
font-family: $headings-font-family
&__hdata
display: flex
margin-bottom: space-x(0.5)
line-height: 1.3em
font-size: 15px
&__label
font-weight: bold
flex-basis: $label-column-size
flex-shrink: 0
&__content
color: $gray-600
&__abstract
margin-top: space-x(3)
margin-bottom: space-x(3)
&__content
color: $gray-800
font-size: 18px
line-height: 1.4
// Every website specialised in scientific articles deals with a column
// container between 760px and 800px
$document-container-width: 780px;
///////////////////////////////////////////////////////////
.document-layout {
$label-column-size: 96px;
position: relative;
width: 100%;
&__header {
display: flex;
justify-content: space-between;
padding: $card-spacer-y $card-spacer-x;
border-bottom: 1px solid $border-color;
}
&__main-controls {
display: flex;
align-items: center;
}
&__side-controls {
display: flex;
align-items: center;
}
&__saving {
color: $primary;
display: flex;
&__spinner {
width: 16px;
height: 16px;
}
&__icon {
font-size: 20px;
// fading icon apparition with a little pulse
// wait a little, then fading icon on hiding
animation: fade-in 200ms ease-in-out, fade-out 100ms ease-in-out 2000ms 1 normal forwards, pulse 200ms ease-in-out 0ms 1;
}
}
&__body {
// max-width: $document-container-width
padding: $card-spacer-x;
margin: 0;
max-height: 70vh;
overflow-y: auto;
}
&__separator-label {
font-weight: bold;
border-bottom: 1px solid $gray-200;
padding-bottom: space-x(0.5);
margin-bottom: space-x(1.5);
}
&__title {
font-size: 26px;
font-weight: bold;
line-height: 1.4;
word-wrap: break-word;
// margin-bottom: space-x(3)
font-family: $headings-font-family;
}
&__hdata {
display: flex;
margin-bottom: space-x(0.5);
line-height: 1.3em;
font-size: 15px;
&__label {
font-weight: bold;
flex-basis: $label-column-size;
flex-shrink: 0;
}
&__content {
color: $gray-600;
}
}
&__abstract {
margin-top: space-x(3);
margin-bottom: space-x(3);
&__content {
color: $gray-800;
font-size: 18px;
line-height: 1.4;
}
}
}
\ No newline at end of file
.folder-view
display: flex
gap: space-x(4.5)
flex-wrap: wrap
/////////////////////////////
.folder-view-item
$body-size: 200px
$icon-size: 30px
$text-size: 17px
transition: $transition-base
display: flex
align-items: stretch
width: $body-size
flex-grow: 0
flex-shrink: 0
position: relative
&:hover,
&:active,
&:focus
.folder-view-item__settings
display: block
&__body
@include clickable
flex-grow: 1
display: flex
flex-direction: column
align-items: center
position: relative
padding: #{ $card-spacer-y * 2 } $card-spacer-x
border-radius: $border-radius
transition: $transition-base
background-color: $gray-100
border: 1px solid $gray-100
&:hover,
&:active,
&:focus
background-color: $gray-200
text-decoration: none
&__icon
font-size: $icon-size
margin-bottom: #{ $card-spacer-y * 2 }
&__text
line-height: 1.2
text-align: center
font-size: $text-size
&__settings
transition: $transition-base
display: none
// best alignment to fit <icon-button> hovering block
$offset-y: 6px
$offset-x: 11px
position: absolute
top: $offset-y
right: $offset-x
// enhenced hover contrast
.b-icon-button:hover::before
background-color: $gray-200
// fitting radius with its parent container
.b-icon-button::before
border-radius: $border-radius
.folder-view {
display: flex;
gap: space-x(4.5);
flex-wrap: wrap;
}
/////////////////////////////
.folder-view-item {
$body-size: 200px;
$icon-size: 30px;
$text-size: 17px;
transition: $transition-base;
display: flex;
align-items: stretch;
width: $body-size;
flex-grow: 0;
flex-shrink: 0;
position: relative;
&:hover,
&:active,
&:focus {
.folder-view-item__settings {
display: block;
}
}
&__body {
@include clickable;
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
padding: #{ $card-spacer-y * 2 } $card-spacer-x;
border-radius: $border-radius;
transition: $transition-base;
background-color: $gray-100;
border: 1px solid $gray-100;
&:hover,
&:active,
&:focus {
background-color: $gray-200;
text-decoration: none;
}
}
&__icon {
font-size: $icon-size;
margin-bottom: #{ $card-spacer-y * 2 };
}
&__text {
line-height: 1.2;
text-align: center;
font-size: $text-size;
}
&__settings {
transition: $transition-base;
display: none;
// best alignment to fit <icon-button> hovering block
$offset-y: 6px;
$offset-x: 11px;
position: absolute;
top: $offset-y;
right: $offset-x;
// enhenced hover contrast
.b-icon-button:hover::before {
background-color: $gray-200;
}
// fitting radius with its parent container
.b-icon-button::before {
border-radius: $border-radius;
}
}
}
\ No newline at end of file
.frame-layout
position: relative
width: 100%
height: $layout-height
.frame-layout {
position: relative;
width: 100%;
height: $layout-height;
}
\ No newline at end of file
.graph-layout
position: relative
z-index: 0 // new stacking context "graph"
width: 100%
height: $layout-height
&__frame
@include fit-positions
position: absolute
display: flex
z-index: z-index('graph', 'frame')
width: 100%
height: $sidebar-height
justify-content: flex-end
pointer-events: none
@include right-handed
flex-direction: row
@include left-handed
flex-direction: row-reverse
&__sidebar
width: $sidebar-width
height: inherit
flex-grow: 0
pointer-events: all
&__inner
position: fixed
height: inherit
width: inherit
@include right-handed
border-left: 1px solid $border-color
@include left-handed
border-right: 1px solid $border-color
&__focus
flex-grow: 1
pointer-events: all
position: relative
// (?) last resort solution to have a fixed scrollable child, with
// its dimension relative to its parent
// (flaw: no overflow)
// contain: layout
&__inner
@include fit-positions
position: absolute
background-color: $body-bg
&__toolbar
position: absolute
z-index: z-index('graph', 'toolbar')
background-color: $body-bg
// width: 100%
border-bottom: 1px solid $border-color
.b-fieldset
background-color: $body-bg
// flex-grow: 1
button
.b-icon
$button-icon-margin: 0.4em
@include right-handed
margin-right: $button-icon-margin
@include left-handed
margin-left: $button-icon-margin
&__content
width: 100%
height: 100%
/////////////////////////////////////////////
.graph-topbar
@include aside-topbar
display: flex
// padding: $grah-topbar-item-margin $topbar-item-margin $grah-topbar-item-margin 0
&__toolbar,
&__sidebar
// width: $topbar-fixed-button-width
margin-top: $topbar-item-margin
margin-bottom: $topbar-item-margin
margin-left: $topbar-item-margin
margin-right: $topbar-item-margin
padding-left: $topbar-item-margin
padding-right: $topbar-item-margin
&__search
width: $topbar-input-width
margin-top: $topbar-item-margin
margin-bottom: $topbar-item-margin
margin-left: $topbar-item-margin
margin-right: $topbar-item-margin
[type="submit"]
display: none
.form-control
height: 2rem
padding-top: 0
padding-bottom: 0
/////////////////////////////////////////////
.graph-doc-focus
@include term-window
&__header
// avoid overlap with document side controls
@include right-handed
margin-left: space-x(4)
@include left-handed
margin-right: space-x(4)
// remove document header top spacing due to the already presence of
// the inner column top teaser
.document-layout__header
padding-top: initial
/////////////////////////////////////////////
.graph-sidebar
@include term-window
$margin-x: $sidebar-tab-margin-x
$margin-y: space-x(2)
&__close
position: absolute
right: 0
margin-right: space-x(3)
&__legend-tab
padding: $margin-y $margin-x
&__data-tab
padding: $margin-y $margin-x
&__community-tab
padding: $margin-y $margin-x
&__separator
margin-top: $margin-y
margin-bottom: $margin-y
color: $gray-500
text-align: center
.graph-legend
$legend-code-width: 24px
$legend-code-height: 12px
&__item
display: flex
align-items: baseline
list-style: none
position: relative
&:not(:first-child)
margin-top: space-x(0.25)
&__title
color: $gray-800
font-size: 15px
font-weight: bold
margin-bottom: space-x(0.25)
&__code
min-width: $legend-code-width
min-height: $legend-code-height
display: inline-block
margin-right: space-x(1)
border: 1px solid $gray-500
.graph-legend .renameable-container__text
margin-right: space-x(1)
.graph-legend-nodes
&__item
display: inline-block
&:first-child
margin-top: space-x(0.25)
&:not(:last-child)
margin-bottom: space-x(0.25)
margin-right: space-x(0.25)
&--count
color: $gray-800
font-size: 11px
font-weight: bold
&__badge
font-size: 12px
white-space: normal
word-break: break-word
&--selected
background-color: darken($light, 10%) // from Bootstrap "_badge.scss"
.graph-documentation
&__text-section
margin-bottom: space-x(3)
font-size: 15px
line-height: 1.5
p
margin-bottom: space-x(1)
li
list-style-type: circle
padding-left: space-x(0.5)
margin-left: space-x(3)
line-height: 1.4
&:not(:last-child)
margin-bottom: space-x(1.5)
.graph-selected-nodes
&__list
display: flex
gap: space-x(0.5)
flex-wrap: wrap
padding-right: space-x(4)
&__badge
font-size: $font-size-base
white-space: normal
word-break: break-word
&__expand
// Following <list-group-item> spacing
$position-x: $list-group-item-padding-x
// Empirical: fit well with icon overlay
$position-y: 0.75rem
position: absolute
right: $position-x
top: $position-y
&__actions
.b-button-group
width: 100%
.b-button
width: 50%
.graph-neighborhood
&__parent
display: flex
list-style: none
flex-wrap: wrap
&__counter
font-weight: bold
&__expand
// Following <list-group-item> spacing
$position-x: $list-group-item-padding-x
// Empirical: fit well with icon overlay
$position-y: 0.75rem
position: absolute
right: $position-x
top: $position-y
&__badge
border: 1px solid
border-radius: space-x(0.75)
white-space: normal
word-break: break-word
// Show on one line
margin-right: space-x(0.75)
margin-bottom: space-x(0.75)
&:not(:last-child)
//margin-bottom: space-x(0.75)
.badge
// reduce bootstrap padding due to a vertical asymmetry (empirical)
$padding-top: 0.15em
padding-top: $padding-top
color: inherit
background-color: rgba(0, 0, 0, 0)
&__show-more
margin-top: space-x(2)
.graph-doc-list
&__item
@include clickable
display: flex
align-items: flex-start
transition: $transition-base
&:hover
background-color: $gray-50
&--selected::before
@include fit-positions
content: ""
position: absolute
z-index: 1
width: 2px
background-color: $info
// (following list group item border radius)
&--selected:first-child::before
border-top-left-radius: $list-group-border-radius
&--selected:last-child::before
border-bottom-left-radius: $list-group-border-radius
&__main
flex-grow: 1
padding-right: $card-spacer-x
&__title,
&__source,
&__date
line-height: 1.3
margin-bottom: space-x(0.5)
&__source
font-size: 15px
color: $gray-700
&__date
font-size: 14px
color: $gray-600
.graph-contact-list
&__item
&__title,
&__subtitle
line-height: 1.3
margin-bottom: space-x(0.25)
&__subtitle
font-size: 15px
color: $gray-700
/////////////////////////////////////////////
.graph-toolbar
$self: &
$section-margin: space-x(2)
$item-margin: space-x(1)
display: flex
padding: #{ $section-margin / 2 }
padding-top: $section-margin
&__gap
width: $item-margin
display: inline-block
&__section
margin: #{ $section-margin / 4 }
// Selection settings
&--selection
.b-fieldset__content
display: flex
#{ $self }__gap
width: #{ $item-margin * 2 }
.range-simple
flex-grow: 1
// Controls
&--controls
.b-fieldset__content
position: relative
#{ $self }__gap
width: #{ $item-margin * 2 }
.range-control,
.range-simple
// flex-basis: calc(50% - #{ $item-margin * 2 })
flex-basis: 100%
min-width: 150px
&__region_advanced
.btn-advanced.collapsed .b-icon
transform: rotate(180)
// Atlas button animation
.on-running-animation .b-icon
animation-name: pulse
animation-duration: 0.5s
animation-timing-function: ease
animation-direction: alternate
animation-iteration-count: infinite
animation-play-state: running
@use "sass:math";
.graph-layout {
position: relative;
z-index: 0;
width: 100%;
height: $layout-height;
&__frame {
@include fit-positions;
position: absolute;
display: flex;
z-index: z-index("graph", "frame");
width: 100%;
height: $sidebar-height;
justify-content: flex-end;
pointer-events: none;
@include right-handed {
flex-direction: row;
}
@include left-handed {
flex-direction: row-reverse;
}
}
&__sidebar {
width: $sidebar-width;
height: inherit;
flex-grow: 0;
pointer-events: all;
&__inner {
position: fixed;
height: inherit;
width: inherit;
@include right-handed {
border-left: 1px solid $border-color;
}
@include left-handed {
border-right: 1px solid $border-color;
}
}
}
&__focus {
flex-grow: 1;
pointer-events: all;
position: relative;
// (?) last resort solution to have a fixed scrollable child, with
// its dimension relative to its parent
// (flaw: no overflow)
// contain: layout
&__inner {
@include fit-positions;
position: absolute;
background-color: $body-bg;
}
}
&__toolbar {
position: absolute;
z-index: z-index("graph", "toolbar");
background-color: $body-bg;
// width: 100%
border-bottom: 1px solid $border-color;
.b-fieldset {
background-color: $body-bg;
// flex-grow: 1
button {
.b-icon {
$button-icon-margin: 0.4em;
@include right-handed {
margin-right: $button-icon-margin;
}
@include left-handed {
margin-left: $button-icon-margin;
}
}
}
}
}
&__content {
width: 100%;
height: 100%;
}
}
/////////////////////////////////////////////
.graph-topbar {
@include aside-topbar;
display: flex;
// padding: $grah-topbar-item-margin $topbar-item-margin $grah-topbar-item-margin 0
&__toolbar,
&__sidebar {
// width: $topbar-fixed-button-width
margin-top: $topbar-item-margin;
margin-bottom: $topbar-item-margin;
margin-left: $topbar-item-margin;
margin-right: $topbar-item-margin;
padding-left: $topbar-item-margin;
padding-right: $topbar-item-margin;
}
&__search {
width: $topbar-input-width;
margin-top: $topbar-item-margin;
margin-bottom: $topbar-item-margin;
margin-left: $topbar-item-margin;
margin-right: $topbar-item-margin;
[type="submit"] {
display: none;
}
.form-control {
height: 2rem;
padding-top: 0;
padding-bottom: 0;
}
}
}
/////////////////////////////////////////////
.graph-doc-focus {
@include term-window;
&__header {
// avoid overlap with document side controls
@include right-handed {
margin-left: space-x(4);
}
@include left-handed {
margin-right: space-x(4);
}
}
// remove document header top spacing due to the already presence of
// the inner column top teaser
.document-layout__header {
padding-top: initial;
}
}
/////////////////////////////////////////////
.graph-sidebar {
@include term-window;
$margin-x: $sidebar-tab-margin-x;
$margin-y: space-x(2);
&__close {
position: absolute;
right: 0;
margin-right: space-x(3);
}
&__legend-tab {
padding: $margin-y $margin-x;
}
&__data-tab {
padding: $margin-y $margin-x;
}
&__community-tab {
padding: $margin-y $margin-x;
}
&__separator {
margin-top: $margin-y;
margin-bottom: $margin-y;
color: $gray-500;
text-align: center;
}
}
.graph-legend {
$legend-code-width: 24px;
$legend-code-height: 12px;
&__item {
display: flex;
align-items: baseline;
list-style: none;
position: relative;
&:not(:first-child) {
margin-top: space-x(0.25);
}
}
&__title {
color: $gray-800;
font-size: 15px;
font-weight: bold;
margin-bottom: space-x(0.25);
}
&__code {
min-width: $legend-code-width;
min-height: $legend-code-height;
display: inline-block;
margin-right: space-x(1);
border: 1px solid $gray-500;
}
}
.graph-legend .renameable-container__text {
margin-right: space-x(1);
}
.graph-legend-nodes {
&__item {
display: inline-block;
&:first-child {
margin-top: space-x(0.25);
}
&:not(:last-child) {
margin-bottom: space-x(0.25);
margin-right: space-x(0.25);
}
&--count {
color: $gray-800;
font-size: 11px;
font-weight: bold;
}
}
&__badge {
font-size: 12px;
white-space: normal;
word-break: break-word;
&--selected {
background-color: darken($light, 10%);
}
}
}
.graph-documentation {
&__text-section {
margin-bottom: space-x(3);
font-size: 15px;
line-height: 1.5;
p {
margin-bottom: space-x(1);
}
li {
list-style-type: circle;
padding-left: space-x(0.5);
margin-left: space-x(3);
line-height: 1.4;
&:not(:last-child) {
margin-bottom: space-x(1.5);
}
}
}
}
.graph-selected-nodes {
&__list {
display: flex;
gap: space-x(0.5);
flex-wrap: wrap;
padding-right: space-x(4);
}
&__badge {
font-size: $font-size-base;
white-space: normal;
word-break: break-word;
}
&__expand {
// Following <list-group-item> spacing
$position-x: $list-group-item-padding-x;
// Empirical: fit well with icon overlay
$position-y: 0.75rem;
position: absolute;
right: $position-x;
top: $position-y;
}
&__actions {
.b-button-group {
width: 100%;
}
.b-button {
width: 50%;
}
}
}
.graph-neighborhood {
&__parent {
display: flex;
list-style: none;
flex-wrap: wrap;
}
&__counter {
font-weight: bold;
}
&__expand {
// Following <list-group-item> spacing
$position-x: $list-group-item-padding-x;
// Empirical: fit well with icon overlay
$position-y: 0.75rem;
position: absolute;
right: $position-x;
top: $position-y;
}
&__badge {
border: 1px solid;
border-radius: space-x(0.75);
white-space: normal;
word-break: break-word;
// Show on one line
margin-right: space-x(0.75);
margin-bottom: space-x(0.75);
&:not(:last-child) {
//margin-bottom: space-x(0.75);
}
.badge {
// reduce bootstrap padding due to a vertical asymmetry (empirical)
$padding-top: 0.15em;
padding-top: $padding-top;
color: inherit;
background-color: rgba(0, 0, 0, 0);
}
}
&__show-more {
margin-top: space-x(2);
}
}
.graph-doc-list {
&__item {
@include clickable;
display: flex;
align-items: flex-start;
transition: $transition-base;
&:hover {
background-color: $gray-50;
}
&--selected::before {
@include fit-positions;
content: "";
position: absolute;
z-index: 1;
width: 2px;
background-color: $info;
}
// (following list group item border radius)
&--selected:first-child::before {
border-top-left-radius: $list-group-border-radius;
}
&--selected:last-child::before {
border-bottom-left-radius: $list-group-border-radius;
}
&__main {
flex-grow: 1;
padding-right: $card-spacer-x;
}
&__title,
&__source,
&__date {
line-height: 1.3;
margin-bottom: space-x(0.5);
}
&__source {
font-size: 15px;
color: $gray-700;
}
&__date {
font-size: 14px;
color: $gray-600;
}
}
}
.graph-contact-list {
&__item {
&__title,
&__subtitle {
line-height: 1.3;
margin-bottom: space-x(0.25);
}
&__subtitle {
font-size: 15px;
color: $gray-700;
}
}
}
/////////////////////////////////////////////
.graph-toolbar {
$self: &;
$section-margin: space-x(2);
$item-margin: space-x(1);
display: flex;
padding: math.div($section-margin, 2);
padding-top: $section-margin;
&__gap {
width: $item-margin;
display: inline-block;
}
&__section {
margin: math.div($section-margin, 4);
// Selection settings
&--selection {
.b-fieldset__content {
display: flex;
}
#{$self}__gap {
width: #{$item-margin * 2};
}
.range-simple {
flex-grow: 1;
}
}
// Controls
&--controls {
.b-fieldset__content {
position: relative;
}
#{$self}__gap {
width: #{$item-margin * 2};
}
.range-control,
.range-simple {
// flex-basis: calc(50% - #{ $item-margin * 2 })
flex-basis: 100%;
min-width: 150px;
}
}
}
&__region_advanced {
.btn-advanced.collapsed .b-icon {
transform: rotate(180);
}
}
}
// Atlas button animation
.on-running-animation .b-icon {
animation-name: pulse;
animation-duration: 0.5s;
animation-timing-function: ease;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-play-state: running;
}
.login-modal
// making ".thead" small, but not its affiliated ".tbody"
thead th
padding: $table-cell-padding-sm
&__active-places
&__actions
display: flex
gap: space-x(3.5)
//////////////////////////////////:
.login-modal-form
$cta-width: 200px
$form-width: 520px
&__title
position: relative
background-color: $border-color
padding: $card-spacer-y $card-spacer-x
text-align: center
// (?) dirty negative margins to overlap the ".modal-body" paddings
margin-top: - $modal-inner-padding
margin-left: - $modal-inner-padding
margin-right: - $modal-inner-padding
&__return
@include centered
position: absolute
left: space-x(3.5)
&__text
font-family: $font-family-monospace
font-size: 20px
font-weight: bold
&__separator
padding: $card-spacer-y calc(2* #{ $card-spacer-x })
position: relative
&__text
@include centered
position: absolute
display: inline-block
padding-left: space-x(4)
padding-right: space-x(4)
font-family: $headings-font-family
font-size: 24px
background-color: $modal-content-bg
&__request-access
margin-top: calc(3 * #{ $card-spacer-y})
margin-left: auto
margin-right: auto
margin-bottom: $card-spacer-y
padding: $btn-padding-y-lg $btn-padding-x-lg
width: $cta-width
display: block
font-weight: bold
&__log-in
padding: $btn-padding-y-lg $btn-padding-x-lg
width: $cta-width
margin-bottom: calc(2 * #{ $card-spacer-y})
margin-left: auto
margin-right: auto
margin-top: $card-spacer-y
display: block
font-weight: bold
&__form
margin: 0 auto
width: $form-width
&__error
margin-bottom: $form-group-margin-bottom
color: $danger
text-align: center
//////////////////////////////////////////////////////////
.forgot-password-form
$cta-width: 200px
$form-width: 520px
&__title
position: relative
background-color: $border-color
padding: $card-spacer-y $card-spacer-x
text-align: center
// (?) dirty negative margins to overlap the ".modal-body" paddings
margin-top: - $modal-inner-padding
margin-left: - $modal-inner-padding
margin-right: - $modal-inner-padding
&__return
@include centered
position: absolute
left: space-x(3.5)
&__text
font-family: $font-family-monospace
font-size: 20px
font-weight: bold
&__subtitle
padding-top: calc(2 * #{ $card-spacer-y})
padding-left: calc(2* #{ $card-spacer-x })
padding-right: calc(2* #{ $card-spacer-x })
padding-bottom: $card-spacer-y
text-align: center
font-family: $headings-font-family
font-size: 24px
&__submit
padding: $btn-padding-y-lg $btn-padding-x-lg
width: $cta-width
margin-bottom: calc(2 * #{ $card-spacer-y})
margin-left: auto
margin-right: auto
margin-top: $card-spacer-y
display: block
font-weight: bold
&__form
margin: 0 auto
width: $form-width
&__error
margin-bottom: $form-group-margin-bottom
color: $danger
text-align: center
&__success
margin-bottom: $form-group-margin-bottom
color: $success
text-align: center
.login-modal {
// making ".thead" small, but not its affiliated ".tbody"
thead th {
padding: $table-cell-padding-sm;
}
&__active-places {
&__actions {
display: flex;
gap: space-x(3.5);
}
}
}
//////////////////////////////////:
.login-modal-form {
$cta-width: 200px;
$form-width: 520px;
&__title {
position: relative;
background-color: $border-color;
padding: $card-spacer-y $card-spacer-x;
text-align: center;
// (?) dirty negative margins to overlap the ".modal-body" paddings
margin-top: -$modal-inner-padding;
margin-left: -$modal-inner-padding;
margin-right: -$modal-inner-padding;
&__return {
@include centered;
position: absolute;
left: space-x(3.5);
}
&__text {
font-family: $font-family-monospace;
font-size: 20px;
font-weight: bold;
}
}
&__separator {
padding: $card-spacer-y calc(2* #{$card-spacer-x});
position: relative;
&__text {
@include centered;
position: absolute;
display: inline-block;
padding-left: space-x(4);
padding-right: space-x(4);
font-family: $headings-font-family;
font-size: 24px;
background-color: $modal-content-bg;
}
}
&__request-access {
margin-top: calc(3 * #{$card-spacer-y});
margin-left: auto;
margin-right: auto;
margin-bottom: $card-spacer-y;
padding: $btn-padding-y-lg $btn-padding-x-lg;
width: $cta-width;
display: block;
font-weight: bold;
}
&__log-in {
padding: $btn-padding-y-lg $btn-padding-x-lg;
width: $cta-width;
margin-bottom: calc(2 * #{$card-spacer-y});
margin-left: auto;
margin-right: auto;
margin-top: $card-spacer-y;
display: block;
font-weight: bold;
}
&__form {
margin: 0 auto;
width: $form-width;
}
&__error {
margin-bottom: $form-group-margin-bottom;
color: $danger;
text-align: center;
}
}
//////////////////////////////////////////////////////////
.forgot-password-form {
$cta-width: 200px;
$form-width: 520px;
&__title {
position: relative;
background-color: $border-color;
padding: $card-spacer-y $card-spacer-x;
text-align: center;
// (?) dirty negative margins to overlap the ".modal-body" paddings
margin-top: -$modal-inner-padding;
margin-left: -$modal-inner-padding;
margin-right: -$modal-inner-padding;
&__return {
@include centered;
position: absolute;
left: space-x(3.5);
}
&__text {
font-family: $font-family-monospace;
font-size: 20px;
font-weight: bold;
}
}
&__subtitle {
padding-top: calc(2 * #{$card-spacer-y});
padding-left: calc(2* #{$card-spacer-x});
padding-right: calc(2* #{$card-spacer-x});
padding-bottom: $card-spacer-y;
text-align: center;
font-family: $headings-font-family;
font-size: 24px;
}
&__submit {
padding: $btn-padding-y-lg $btn-padding-x-lg;
width: $cta-width;
margin-bottom: calc(2 * #{$card-spacer-y});
margin-left: auto;
margin-right: auto;
margin-top: $card-spacer-y;
display: block;
font-weight: bold;
}
&__form {
margin: 0 auto;
width: $form-width;
}
&__error {
margin-bottom: $form-group-margin-bottom;
color: $danger;
text-align: center;
}
&__success {
margin-bottom: $form-group-margin-bottom;
color: $success;
text-align: center;
}
}
.main-topbar
position: sticky
top: 0
z-index: z-index('main', 'topbar')
height: 40px
.nav-item a:hover
text-decoration: none
select
padding-top: 0
padding-bottom: 0
height: 2rem
&__theme-switcher,
&__lang-switcher,
&__tree-switcher
margin-right: space-x(1)
&__tree-switcher
padding: 0.15rem 0.5rem
// add hovering effect
&.navbar-dark .navbar-text:hover
color: $navbar-dark-hover-color
// remove Bootstrap native rules
&__hand-button .nav-link,
&__help-button .nav-link
padding-left: initial
padding-right: initial
&__logo
margin-right: initial
img
max-width: none
@include right-handed
flex-direction: row
&__logo
margin-left: 0
margin-right: space-x(2)
&__hand-button
margin-left: space-x(2)
margin-right: space-x(1)
&__help-button
margin-left: space-x(1)
margin-right: space-x(2)
@include left-handed
flex-direction: row-reverse
&__logo
margin-left: space-x(2)
margin-right: 0
&__hand-button
margin-left: space-x(1)
margin-right: space-x(2)
&__help-button
margin-left: space-x(2)
margin-right: space-x(1)
#corporatop.nav-tabs > li
padding-left: 1
padding-right: 1
#corporatop.nav-tabs > li > a
padding-top: 8
padding-bottom: 8
line-height: .85
margin-bottom: -5px
.spacing-class
margin-right: 10px
.exportbtn
/* border: 1px solid #333 */
margin-top:17px /* valigns with bootstrap h2 */
.btn .glyphicon
/* glyphicons are always rendered too high within bootstrap buttons */
vertical-align: middle
/* graph name => editable input => submitName() => minimsg */
.editable
color: grey
#graphname-edit
color: white
background-color: transparent
border: none
max-width: 8em
.minimsg
font-size: .7em
padding: 7p0x 9px
.minimsg *
line-height: 100%
.tile-menu
&__item
.fa
margin-right: space-x(1)
.dropdown
.dropdown-item
padding: 0.25rem 0
a
padding: 0 1rem
display: block
span
min-width: 24px
text-align: center
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.
@import './modules/_form'
@import './modules/_layout'
@import './modules/_annotation'
@import './modules/_tree'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment