From ebda83b033ef2a995284f2118f12620b50fab673 Mon Sep 17 00:00:00 2001 From: arturo Date: Sat, 2 Jul 2022 16:33:46 +0200 Subject: [PATCH] [CSS] Ngrams Table, nested ngrams in groups * #414 --- dist/styles/bootstrap-darkster.css | 135 ++++- dist/styles/bootstrap-default.css | 134 ++++- dist/styles/bootstrap-greyson.css | 135 ++++- dist/styles/bootstrap-herbie.css | 135 ++++- dist/styles/bootstrap-monotony.css | 135 ++++- src/Gargantext/Components/Category.purs | 57 +- src/Gargantext/Components/DocsTable.purs | 52 +- .../Forest/Tree/Node/Action/Search.purs | 2 +- .../Tree/Node/Action/Search/SearchBar.purs | 2 +- src/Gargantext/Components/NgramsTable.purs | 505 ++++++++++++------ .../Components/NgramsTable/Search.purs | 46 +- .../NgramsTable/SyncResetButton.purs | 46 +- .../Components/NgramsTable/Tree.purs | 176 ++++-- src/Gargantext/Components/Table.purs | 7 +- src/sass/_legacy.sass | 1 + src/sass/_legacy/_list.sass | 156 ++++++ src/sass/_legacy/_styles.sass | 20 - 17 files changed, 1340 insertions(+), 404 deletions(-) create mode 100644 src/sass/_legacy/_list.sass diff --git a/dist/styles/bootstrap-darkster.css b/dist/styles/bootstrap-darkster.css index 2d0d0ffe..d9fdcaa5 100644 --- a/dist/styles/bootstrap-darkster.css +++ b/dist/styles/bootstrap-darkster.css @@ -8747,31 +8747,6 @@ a:focus, a:hover { top: 50%; } -.table tr td { - color: #005a9aff; -} -.table tr td .active { - font-weight: bold; - text-decoration: underline; -} -.table tr td .ngrams-selector { - display: flex; -} -.table tr td .ngrams-selector .ngrams-chooser { - padding: 3px; -} -.table tr td .trash { - text-decoration: line-through; -} - -.action-search { - margin: 10px; -} - -.search-bar { - margin: 10px; -} - /* */ .join-button { padding-bottom: 100px; @@ -8960,6 +8935,116 @@ select.form-control { opacity: var(--over50, 0); } +.table tr td { + height: 48px; +} +.table .page-paint-raw--selected { + position: relative; +} +.table .page-paint-raw--selected td:first-child::before { + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + width: 3px; + background-color: #0F81C7; + position: absolute; +} +.table .page-paint-row--trash { + text-decoration: line-through; +} +.table .doc-chooser { + padding-top: 3px; + text-align: center; +} +.table .rating-group { + display: flex; + padding-top: 3px; +} +.table .rating-group__action { + width: 14px; + margin-right: 8px; +} + +.search-button-prepend .input-group-text { + width: 41px; + z-index: initial; +} + +.ngrams-table-container__header { + display: flex; + align-items: flex-start; +} +.ngrams-table-container__header__item { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__header__item:not(:first-child) { + margin-left: 8px; +} +.ngrams-table-container__header__item:not(:last-child) { + margin-right: 8px; +} +.ngrams-table-container__add-term { + margin-top: -8px; + margin-bottom: 12px; +} +.ngrams-table-container__footer { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__footer__item { + text-align: center; +} + +.ngrams-tree-edit-real { + min-width: 416px; + width: fit-content; + border-color: #FF550B; + margin-top: 16px; + margin-bottom: 16px; +} +.ngrams-tree-edit-real__actions { + display: flex; + margin-top: 16px; +} +.ngrams-tree-edit-real__actions .b-button { + margin-right: 8px; +} + +.loaded-ngrams-table-header { + text-align: center; + margin-top: 12px; + margin-bottom: 12px; +} +.loaded-ngrams-table-header__icon { + font-size: 16px; +} +.loaded-ngrams-table-header__text { + font-size: 22px; + font-weight: bold; + font-family: "Comfortaa"; +} + +.ngrams-tree-loaded-node--first-child::before, .ngrams-tree-loaded-node--grand-child::before { + color: #6C757D; + font-size: 11px; + margin-right: 4px; +} +.right-handed .ngrams-tree-loaded-node--first-child::before, .right-handed .ngrams-tree-loaded-node--grand-child::before { + content: "└"; +} + +.left-handed .ngrams-tree-loaded-node--first-child::before, .left-handed .ngrams-tree-loaded-node--grand-child::before { + content: "┘"; +} + +.ngrams-tree-loaded-node--first-child { + margin-left: -2px; +} +.ngrams-tree-loaded-node--grand-child { + margin-left: 13px; +} + .annotation-run { cursor: pointer; } diff --git a/dist/styles/bootstrap-default.css b/dist/styles/bootstrap-default.css index f1653783..d3ce27d2 100644 --- a/dist/styles/bootstrap-default.css +++ b/dist/styles/bootstrap-default.css @@ -8700,31 +8700,6 @@ a:focus, a:hover { top: 50%; } -.table tr td { - color: #005a9aff; -} -.table tr td .active { - font-weight: bold; - text-decoration: underline; -} -.table tr td .ngrams-selector { - display: flex; -} -.table tr td .ngrams-selector .ngrams-chooser { - padding: 3px; -} -.table tr td .trash { - text-decoration: line-through; -} - -.action-search { - margin: 10px; -} - -.search-bar { - margin: 10px; -} - /* */ .join-button { padding-bottom: 100px; @@ -8913,6 +8888,115 @@ select.form-control { opacity: var(--over50, 0); } +.table tr td { + height: 48px; +} +.table .page-paint-raw--selected { + position: relative; +} +.table .page-paint-raw--selected td:first-child::before { + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + width: 3px; + background-color: #17a2b8; + position: absolute; +} +.table .page-paint-row--trash { + text-decoration: line-through; +} +.table .doc-chooser { + padding-top: 3px; + text-align: center; +} +.table .rating-group { + display: flex; + padding-top: 3px; +} +.table .rating-group__action { + width: 14px; + margin-right: 8px; +} + +.search-button-prepend .input-group-text { + width: 41px; + z-index: initial; +} + +.ngrams-table-container__header { + display: flex; + align-items: flex-start; +} +.ngrams-table-container__header__item { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__header__item:not(:first-child) { + margin-left: 8px; +} +.ngrams-table-container__header__item:not(:last-child) { + margin-right: 8px; +} +.ngrams-table-container__add-term { + margin-top: -8px; + margin-bottom: 12px; +} +.ngrams-table-container__footer { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__footer__item { + text-align: center; +} + +.ngrams-tree-edit-real { + min-width: 416px; + width: fit-content; + border-color: #005a9a; + margin-top: 16px; + margin-bottom: 16px; +} +.ngrams-tree-edit-real__actions { + display: flex; + margin-top: 16px; +} +.ngrams-tree-edit-real__actions .b-button { + margin-right: 8px; +} + +.loaded-ngrams-table-header { + text-align: center; + margin-top: 12px; + margin-bottom: 12px; +} +.loaded-ngrams-table-header__icon { + font-size: 16px; +} +.loaded-ngrams-table-header__text { + font-size: 22px; + font-weight: bold; +} + +.ngrams-tree-loaded-node--first-child::before, .ngrams-tree-loaded-node--grand-child::before { + color: #CED4DA; + font-size: 11px; + margin-right: 4px; +} +.right-handed .ngrams-tree-loaded-node--first-child::before, .right-handed .ngrams-tree-loaded-node--grand-child::before { + content: "└"; +} + +.left-handed .ngrams-tree-loaded-node--first-child::before, .left-handed .ngrams-tree-loaded-node--grand-child::before { + content: "┘"; +} + +.ngrams-tree-loaded-node--first-child { + margin-left: -2px; +} +.ngrams-tree-loaded-node--grand-child { + margin-left: 13px; +} + .annotation-run { cursor: pointer; } diff --git a/dist/styles/bootstrap-greyson.css b/dist/styles/bootstrap-greyson.css index b6a593f0..c11b0878 100644 --- a/dist/styles/bootstrap-greyson.css +++ b/dist/styles/bootstrap-greyson.css @@ -8456,31 +8456,6 @@ a:focus, a:hover { top: 50%; } -.table tr td { - color: #005a9aff; -} -.table tr td .active { - font-weight: bold; - text-decoration: underline; -} -.table tr td .ngrams-selector { - display: flex; -} -.table tr td .ngrams-selector .ngrams-chooser { - padding: 3px; -} -.table tr td .trash { - text-decoration: line-through; -} - -.action-search { - margin: 10px; -} - -.search-bar { - margin: 10px; -} - /* */ .join-button { padding-bottom: 100px; @@ -8669,6 +8644,116 @@ select.form-control { opacity: var(--over50, 0); } +.table tr td { + height: 48px; +} +.table .page-paint-raw--selected { + position: relative; +} +.table .page-paint-raw--selected td:first-child::before { + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + width: 3px; + background-color: #5c8f94; + position: absolute; +} +.table .page-paint-row--trash { + text-decoration: line-through; +} +.table .doc-chooser { + padding-top: 3px; + text-align: center; +} +.table .rating-group { + display: flex; + padding-top: 3px; +} +.table .rating-group__action { + width: 14px; + margin-right: 8px; +} + +.search-button-prepend .input-group-text { + width: 41px; + z-index: initial; +} + +.ngrams-table-container__header { + display: flex; + align-items: flex-start; +} +.ngrams-table-container__header__item { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__header__item:not(:first-child) { + margin-left: 8px; +} +.ngrams-table-container__header__item:not(:last-child) { + margin-right: 8px; +} +.ngrams-table-container__add-term { + margin-top: -8px; + margin-bottom: 12px; +} +.ngrams-table-container__footer { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__footer__item { + text-align: center; +} + +.ngrams-tree-edit-real { + min-width: 416px; + width: fit-content; + border-color: #2f3c48; + margin-top: 16px; + margin-bottom: 16px; +} +.ngrams-tree-edit-real__actions { + display: flex; + margin-top: 16px; +} +.ngrams-tree-edit-real__actions .b-button { + margin-right: 8px; +} + +.loaded-ngrams-table-header { + text-align: center; + margin-top: 12px; + margin-bottom: 12px; +} +.loaded-ngrams-table-header__icon { + font-size: 16px; +} +.loaded-ngrams-table-header__text { + font-size: 22px; + font-weight: bold; + font-family: "Oswald"; +} + +.ngrams-tree-loaded-node--first-child::before, .ngrams-tree-loaded-node--grand-child::before { + color: #CED4DA; + font-size: 11px; + margin-right: 4px; +} +.right-handed .ngrams-tree-loaded-node--first-child::before, .right-handed .ngrams-tree-loaded-node--grand-child::before { + content: "└"; +} + +.left-handed .ngrams-tree-loaded-node--first-child::before, .left-handed .ngrams-tree-loaded-node--grand-child::before { + content: "┘"; +} + +.ngrams-tree-loaded-node--first-child { + margin-left: -2px; +} +.ngrams-tree-loaded-node--grand-child { + margin-left: 13px; +} + .annotation-run { cursor: pointer; } diff --git a/dist/styles/bootstrap-herbie.css b/dist/styles/bootstrap-herbie.css index 976bae68..39bfdd55 100644 --- a/dist/styles/bootstrap-herbie.css +++ b/dist/styles/bootstrap-herbie.css @@ -8704,31 +8704,6 @@ a:focus, a:hover { top: 50%; } -.table tr td { - color: #005a9aff; -} -.table tr td .active { - font-weight: bold; - text-decoration: underline; -} -.table tr td .ngrams-selector { - display: flex; -} -.table tr td .ngrams-selector .ngrams-chooser { - padding: 3px; -} -.table tr td .trash { - text-decoration: line-through; -} - -.action-search { - margin: 10px; -} - -.search-bar { - margin: 10px; -} - /* */ .join-button { padding-bottom: 100px; @@ -8917,6 +8892,116 @@ select.form-control { opacity: var(--over50, 0); } +.table tr td { + height: 48px; +} +.table .page-paint-raw--selected { + position: relative; +} +.table .page-paint-raw--selected td:first-child::before { + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + width: 3px; + background-color: #74DBEF; + position: absolute; +} +.table .page-paint-row--trash { + text-decoration: line-through; +} +.table .doc-chooser { + padding-top: 3px; + text-align: center; +} +.table .rating-group { + display: flex; + padding-top: 3px; +} +.table .rating-group__action { + width: 14px; + margin-right: 8px; +} + +.search-button-prepend .input-group-text { + width: 41px; + z-index: initial; +} + +.ngrams-table-container__header { + display: flex; + align-items: flex-start; +} +.ngrams-table-container__header__item { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__header__item:not(:first-child) { + margin-left: 8px; +} +.ngrams-table-container__header__item:not(:last-child) { + margin-right: 8px; +} +.ngrams-table-container__add-term { + margin-top: -8px; + margin-bottom: 12px; +} +.ngrams-table-container__footer { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__footer__item { + text-align: center; +} + +.ngrams-tree-edit-real { + min-width: 416px; + width: fit-content; + border-color: #083358; + margin-top: 16px; + margin-bottom: 16px; +} +.ngrams-tree-edit-real__actions { + display: flex; + margin-top: 16px; +} +.ngrams-tree-edit-real__actions .b-button { + margin-right: 8px; +} + +.loaded-ngrams-table-header { + text-align: center; + margin-top: 12px; + margin-bottom: 12px; +} +.loaded-ngrams-table-header__icon { + font-size: 16px; +} +.loaded-ngrams-table-header__text { + font-size: 22px; + font-weight: bold; + font-family: "Crete Round"; +} + +.ngrams-tree-loaded-node--first-child::before, .ngrams-tree-loaded-node--grand-child::before { + color: #CED4DA; + font-size: 11px; + margin-right: 4px; +} +.right-handed .ngrams-tree-loaded-node--first-child::before, .right-handed .ngrams-tree-loaded-node--grand-child::before { + content: "└"; +} + +.left-handed .ngrams-tree-loaded-node--first-child::before, .left-handed .ngrams-tree-loaded-node--grand-child::before { + content: "┘"; +} + +.ngrams-tree-loaded-node--first-child { + margin-left: -2px; +} +.ngrams-tree-loaded-node--grand-child { + margin-left: 13px; +} + .annotation-run { cursor: pointer; } diff --git a/dist/styles/bootstrap-monotony.css b/dist/styles/bootstrap-monotony.css index a8f3031c..354fa175 100644 --- a/dist/styles/bootstrap-monotony.css +++ b/dist/styles/bootstrap-monotony.css @@ -8705,31 +8705,6 @@ a:focus, a:hover { top: 50%; } -.table tr td { - color: #005a9aff; -} -.table tr td .active { - font-weight: bold; - text-decoration: underline; -} -.table tr td .ngrams-selector { - display: flex; -} -.table tr td .ngrams-selector .ngrams-chooser { - padding: 3px; -} -.table tr td .trash { - text-decoration: line-through; -} - -.action-search { - margin: 10px; -} - -.search-bar { - margin: 10px; -} - /* */ .join-button { padding-bottom: 100px; @@ -8918,6 +8893,116 @@ select.form-control { opacity: var(--over50, 0); } +.table tr td { + height: 48px; +} +.table .page-paint-raw--selected { + position: relative; +} +.table .page-paint-raw--selected td:first-child::before { + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + width: 3px; + background-color: #515151; + position: absolute; +} +.table .page-paint-row--trash { + text-decoration: line-through; +} +.table .doc-chooser { + padding-top: 3px; + text-align: center; +} +.table .rating-group { + display: flex; + padding-top: 3px; +} +.table .rating-group__action { + width: 14px; + margin-right: 8px; +} + +.search-button-prepend .input-group-text { + width: 41px; + z-index: initial; +} + +.ngrams-table-container__header { + display: flex; + align-items: flex-start; +} +.ngrams-table-container__header__item { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__header__item:not(:first-child) { + margin-left: 8px; +} +.ngrams-table-container__header__item:not(:last-child) { + margin-right: 8px; +} +.ngrams-table-container__add-term { + margin-top: -8px; + margin-bottom: 12px; +} +.ngrams-table-container__footer { + padding: 0.75rem 1.25rem; +} +.ngrams-table-container__footer__item { + text-align: center; +} + +.ngrams-tree-edit-real { + min-width: 416px; + width: fit-content; + border-color: #222222; + margin-top: 16px; + margin-bottom: 16px; +} +.ngrams-tree-edit-real__actions { + display: flex; + margin-top: 16px; +} +.ngrams-tree-edit-real__actions .b-button { + margin-right: 8px; +} + +.loaded-ngrams-table-header { + text-align: center; + margin-top: 12px; + margin-bottom: 12px; +} +.loaded-ngrams-table-header__icon { + font-size: 16px; +} +.loaded-ngrams-table-header__text { + font-size: 22px; + font-weight: bold; + font-family: "Open Sans"; +} + +.ngrams-tree-loaded-node--first-child::before, .ngrams-tree-loaded-node--grand-child::before { + color: #CED4DA; + font-size: 11px; + margin-right: 4px; +} +.right-handed .ngrams-tree-loaded-node--first-child::before, .right-handed .ngrams-tree-loaded-node--grand-child::before { + content: "└"; +} + +.left-handed .ngrams-tree-loaded-node--first-child::before, .left-handed .ngrams-tree-loaded-node--grand-child::before { + content: "┘"; +} + +.ngrams-tree-loaded-node--first-child { + margin-left: -2px; +} +.ngrams-tree-loaded-node--grand-child { + margin-left: 13px; +} + .annotation-run { cursor: pointer; } diff --git a/src/Gargantext/Components/Category.purs b/src/Gargantext/Components/Category.purs index 3fb24236..1a5b7cac 100644 --- a/src/Gargantext/Components/Category.purs +++ b/src/Gargantext/Components/Category.purs @@ -9,12 +9,15 @@ import Data.Map as Map import Data.Maybe (Maybe(..)) import Effect.Aff (launchAff_) import Effect.Class (liftEffect) +import Gargantext.Components.Bootstrap as B +import Gargantext.Components.Bootstrap.Types (Variant(..)) import Gargantext.Components.Category.Types (Category(..), Star(..), cat2score, categories, clickAgain, star2score, stars) import Gargantext.Components.DocsTable.Types (DocumentsView(..), LocalCategories, LocalUserScore) import Gargantext.Config.REST (AffRESTError) import Gargantext.Routes (SessionRoute(NodeAPI)) import Gargantext.Sessions (Session, put) import Gargantext.Types (NodeID, NodeType(..)) +import Gargantext.Utils ((?)) import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Toestand as T2 import Reactix as R @@ -42,23 +45,53 @@ ratingCpt = here.component "rating" cpt where , row: DocumentsView r , score , session - , setLocalCategories } _ = - pure $ H.div { className:"flex" } divs where - divs = map (\s -> H.div { className : icon' score s - , on: { click: onClick s } } []) stars - icon' Star_0 Star_0 = "fa fa-times-circle" - icon' _ Star_0 = "fa fa-times" - icon' c s = if star2score c < star2score s then "fa fa-star-o" else "fa fa-star" + , setLocalCategories + } _ = do + -- | Computed + -- | + let + icon' Star_0 Star_0 = "times-circle" + icon' _ Star_0 = "times" + icon' c s = star2score c < star2score s ? "star-o" $ "star" + + variant' Star_0 Star_0 = Dark + variant' _ Star_0 = Dark + variant' _ _ = Dark + + className' Star_0 Star_0 = "rating-group__action" + className' _ Star_0 = "rating-group__action" + className' _ _ = "rating-group__star" + + -- | Behaviors + -- | + let onClick c _ = do - let c' = if score == c - then clickAgain c - else c + let c' = score == c ? clickAgain c $ c setLocalCategories $ Map.insert r._id c' - launchAff_ $ do - _ <- putRating session nodeId $ RatingQuery { nodeIds: [r._id], rating: c' } + launchAff_ do + _ <- putRating session nodeId $ RatingQuery + { nodeIds: [r._id] + , rating: c' + } liftEffect $ T2.reload chartReload + -- | Render + -- | + pure $ + + H.div + { className: "rating-group" } $ + stars <#> \s -> + B.iconButton + { name: icon' score s + , callback: onClick s + , overlay: false + , variant: variant' score s + , className: className' score s + } + + newtype RatingQuery = RatingQuery { nodeIds :: Array Int , rating :: Star diff --git a/src/Gargantext/Components/DocsTable.purs b/src/Gargantext/Components/DocsTable.purs index 647de9e3..39222c14 100644 --- a/src/Gargantext/Components/DocsTable.purs +++ b/src/Gargantext/Components/DocsTable.purs @@ -6,7 +6,6 @@ import Gargantext.Prelude import DOM.Simple.Event as DE import Data.Array (any) import Data.Array as A -import Data.Either (Either) import Data.Generic.Rep (class Generic) import Data.Lens ((^.)) import Data.Lens.At (at) @@ -27,7 +26,7 @@ import Effect.Class (liftEffect) import Effect.Timer (setTimeout) import Gargantext.Components.App.Store (Boxes) import Gargantext.Components.Bootstrap as B -import Gargantext.Components.Bootstrap.Types (ComponentStatus(..)) +import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Variant(..)) import Gargantext.Components.Category (rating) import Gargantext.Components.Category.Types (Star(..)) import Gargantext.Components.DocsTable.DocumentFormCreation as DFC @@ -540,6 +539,9 @@ pagePaintRawCpt = here.component "pagePaintRaw" cpt where reload <- T.useBox GUT.newReload localCategories' <- T.useLive T.unequal localCategories + let + selected = mCurrentDocId' == Just nodeId + pure $ TT.table { colNames , container: TT.defaultContainer @@ -551,9 +553,9 @@ pagePaintRawCpt = here.component "pagePaintRaw" cpt where } where sid = sessionId session - trashClassName Star_0 _ = "trash" - trashClassName _ true = "active" - trashClassName _ false = "" + trashClassName Star_0 _ = "page-paint-row page-paint-row--trash" + trashClassName _ true = "page-paint-row page-paint-row--active" + trashClassName _ false = "" corpusDocument | Just cid <- mCorpusId = Routes.CorpusDocument sid cid listId | otherwise = Routes.Document sid listId @@ -563,7 +565,14 @@ pagePaintRawCpt = here.component "pagePaintRaw" cpt where where row dv@(DocumentsView r@{ _id, category }) = { row: - TT.makeRow [ -- H.div {} [ H.a { className, style, on: {click: click Favorite} } [] ] + TT.makeRow' + { className: "page-paint-raw " <> + (selected ? + "page-paint-raw--selected" $ + "" + ) + } + [ -- H.div {} [ H.a { className, style, on: {click: click Favorite} } [] ] H.div { className: "" } [ docChooser { boxes , listId @@ -582,10 +591,17 @@ pagePaintRawCpt = here.component "pagePaintRaw" cpt where --, H.input { type: "checkbox", defaultValue: checked, on: {click: click Trash} } -- TODO show date: Year-Month-Day only , H.div { className: tClassName } [ R2.showText r.date ] - , H.div { className: tClassName } - [ H.a { href: url frontends $ corpusDocument r._id, target: "_blank" } - [ H.text r.title ] - ] + , + H.div + { className: tClassName } + [ + H.a + { href: url frontends $ corpusDocument r._id + , target: "_blank" + , className: "text-primary" + } + [ H.text r.title ] + ] , H.div { className: tClassName } [ H.text $ showSource r.source ] , H.div {} [ H.text $ maybe "-" show r.ngramCount ] ] @@ -623,11 +639,19 @@ docChooserCpt = here.component "docChooser" cpt mCurrentDocId' <- T.useLive T.unequal mCurrentDocId let selected = mCurrentDocId' == Just nodeId - eyeClass = if selected then "fa-eye" else "fa-eye-slash" + eyeClass = selected ? "eye" $ "eye-slash" + variant = selected ? Info $ Dark - pure $ H.div { className: "btn" } [ - H.span { className: "fa " <> eyeClass - , on: { click: onClick selected } } [] + pure $ + H.div + { className: "doc-chooser" } + [ + B.iconButton + { name: eyeClass + , overlay: false + , variant + , callback: onClick selected + } ] where onClick selected _ = do diff --git a/src/Gargantext/Components/Forest/Tree/Node/Action/Search.purs b/src/Gargantext/Components/Forest/Tree/Node/Action/Search.purs index 3568fab2..9ba56038 100644 --- a/src/Gargantext/Components/Forest/Tree/Node/Action/Search.purs +++ b/src/Gargantext/Components/Forest/Tree/Node/Action/Search.purs @@ -37,7 +37,7 @@ actionSearchCpt = here.component "actionSearch" cpt cpt { boxes: { errors }, dispatch, id, session } _ = do search <- T.useBox $ defaultSearch { node_id = id } pure $ R.fragment - [ H.p { className: "action-search" } + [ H.p { className: "action-search m-1" } [ H.text $ "Search and create a private " <> "corpus with the search query as corpus name." ] , searchBar { errors diff --git a/src/Gargantext/Components/Forest/Tree/Node/Action/Search/SearchBar.purs b/src/Gargantext/Components/Forest/Tree/Node/Action/Search/SearchBar.purs index b3c0667a..d0dee907 100644 --- a/src/Gargantext/Components/Forest/Tree/Node/Action/Search/SearchBar.purs +++ b/src/Gargantext/Components/Forest/Tree/Node/Action/Search/SearchBar.purs @@ -33,7 +33,7 @@ searchBarCpt = here.component "searchBar" cpt where cpt { errors, langs, onSearch, search, session } _ = do --onSearchChange session s - pure $ H.div { className: "search-bar" } + pure $ H.div { className: "search-bar m-1" } [ searchField { databases: allDatabases , errors , langs diff --git a/src/Gargantext/Components/NgramsTable.purs b/src/Gargantext/Components/NgramsTable.purs index 6217b255..23184ec7 100644 --- a/src/Gargantext/Components/NgramsTable.purs +++ b/src/Gargantext/Components/NgramsTable.purs @@ -13,12 +13,12 @@ import Gargantext.Prelude import Data.Array as A import Data.Either (Either(..)) import Data.FunctorWithIndex (mapWithIndex) -import Data.Lens (to, view, (%~), (.~), (^.), (^?), (^..)) +import Data.Lens (to, view, (.~), (^.), (^?)) import Data.Lens.At (at) import Data.Lens.Common (_Just) import Data.Lens.Fold (folded) import Data.Lens.Index (ix) -import Data.List (List) +import Data.List (List, intercalate) import Data.List as List import Data.Map (Map) import Data.Map as Map @@ -32,26 +32,25 @@ import Data.Tuple (Tuple(..)) import Data.Tuple.Nested ((/\)) import Effect (Effect) import Effect.Aff (Aff) -import Effect.Class (liftEffect) import Gargantext.Components.App.Store (Boxes) import Gargantext.Components.Bootstrap as B -import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), Variant(..)) -import Gargantext.Core.NgramsTable.Functions (addNewNgramA, applyNgramsPatches, chartsAfterSync, commitPatch, convOrderBy, coreDispatch, filterTermSize, ngramsRepoElementToNgramsElement, normNgram, patchSetFromMap, setTermListA, singletonNgramsTablePatch, tablePatchHasNgrams, toVersioned) +import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), Sizing(..), Variant(..)) +import Gargantext.Components.NgramsTable.Loader (useLoaderWithCacheAPI) import Gargantext.Components.NgramsTable.Search as NTS import Gargantext.Components.NgramsTable.SelectionCheckbox as NTSC -import Gargantext.Components.NgramsTable.Tree (renderNgramsItem, renderNgramsTree) -import Gargantext.Components.NgramsTable.Loader (useLoaderWithCacheAPI) import Gargantext.Components.NgramsTable.SyncResetButton (syncResetButtons) +import Gargantext.Components.NgramsTable.Tree (renderNgramsItem, renderNgramsTree) import Gargantext.Components.Nodes.Lists.Types as NT import Gargantext.Components.Table as TT import Gargantext.Components.Table.Types as TT import Gargantext.Config.REST (AffRESTError, RESTError, logRESTError) -import Gargantext.Core.NgramsTable.Types (Action(..), CoreAction(..), CoreState, Dispatch, NgramsActionRef, NgramsClick, NgramsDepth, NgramsElement(..), NgramsPatch(..), NgramsTable, NgramsTablePatch(..), NgramsTerm(..), PageParams, PatchMap(..), Versioned(..), VersionedNgramsTable, VersionedWithCountNgramsTable, _NgramsElement, _NgramsRepoElement, _NgramsTable, _children, _list, _ngrams, _ngrams_repo_elements, _ngrams_scores, _occurrences, _root, applyPatchSet, ngramsTermText, replace) +import Gargantext.Core.NgramsTable.Functions (addNewNgramA, applyNgramsPatches, chartsAfterSync, commitPatch, convOrderBy, coreDispatch, filterTermSize, ngramsRepoElementToNgramsElement, normNgram, patchSetFromMap, singletonNgramsTablePatch, tablePatchHasNgrams, toVersioned) +import Gargantext.Core.NgramsTable.Types (Action(..), CoreAction(..), CoreState, Dispatch, NgramsActionRef, NgramsClick, NgramsElement(..), NgramsPatch(..), NgramsTable, NgramsTablePatch(..), NgramsTerm(..), PageParams, PatchMap(..), Versioned(..), VersionedNgramsTable, VersionedWithCountNgramsTable, _NgramsElement, _NgramsRepoElement, _NgramsTable, _children, _list, _ngrams, _ngrams_repo_elements, _ngrams_scores, _occurrences, _root, applyPatchSet, ngramsTermText, replace) import Gargantext.Hooks.Loader (useLoaderBox) -import Gargantext.Routes (SessionRoute(..)) as R +import Gargantext.Routes (SessionRoute(..)) as Routes import Gargantext.Sessions (Session, get) import Gargantext.Types (CTabNgramType, ListId, NodeID, OrderBy(..), SearchQuery, TabType, TermList(..), TermSize, termLists, termSizes) -import Gargantext.Utils (queryExactMatchesLabel, queryMatchesLabel, toggleSet, sortWith) +import Gargantext.Utils (nbsp, queryExactMatchesLabel, queryMatchesLabel, sortWith, toggleSet) import Gargantext.Utils.CacheAPI as GUC import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Seq as Seq @@ -147,107 +146,230 @@ tableContainerCpt { addCallback , queryExactMatches , syncResetButton , tabNgramType - } = here.component "tableContainer" cpt - where - cpt props _ = do - { searchQuery, termListFilter, termSizeFilter } <- T.useLive T.unequal path - - pure $ H.div {className: "container-fluid"} - [ R2.row - [ H.div {className: "card col-12"} - [ H.div {className: "card-header"} - [ R2.row - [ H.div { className: "col-md-2", style: {marginTop: "6px" } } - [ H.div {} syncResetButton - , if (not queryExactMatches || A.null props.tableBody) && searchQuery /= "" then - -- , if (not $ Set.member (normNgram tabNgramType searchQuery) ngramsSelection) && searchQuery /= "" then - H.li { className: "list-group-item" } - [ - B.button - { variant: ButtonVariant Primary - , callback: const $ addCallback searchQuery - } - [ H.text ("Add " <> searchQuery) ] - ] else H.div {} [] - ] - , H.div {className: "col-md-2", style: {marginTop : "6px"}} - [ H.li {className: "list-group-item"} - [ R2.select { id: "picklistmenu" - , className: "form-control custom-select" - , defaultValue: (maybe "" show termListFilter) - , on: {change: setTermListFilter <<< read <<< R.unsafeEventValue}} - (map optps1 termLists)] - ] - , H.div {className: "col-md-2", style: {marginTop : "6px"}} - [ H.li {className: "list-group-item"} - [ R2.select {id: "picktermtype" - , className: "form-control custom-select" - , defaultValue: (maybe "" show termSizeFilter) - , on: {change: setTermSizeFilter <<< read <<< R.unsafeEventValue}} - (map optps1 termSizes)] - ] - , H.div { className: "col-md-2", style: { marginTop: "6px" } } - [ H.li {className: "list-group-item"} - [ H.div { className: "form-inline" } - [ H.div { className: "form-group" } - [ props.pageSizeControl - , H.label {} [ H.text " items" ] - -- H.div { className: "col-md-6" } [ props.pageSizeControl ] - -- , H.div { className: "col-md-6" } [ - -- ] - ] - ] - ] - ] - , H.div {className: "col-md-4", style: {marginTop : "6px", marginBottom : "1px"}} - [ H.li {className: "list-group-item"} - [ props.pageSizeDescription - , props.paginationLinks - ] - ] - ] + } = here.component "tableContainer" cpt where + cpt props _ = do + -- | States + -- | + { searchQuery + , termListFilter + , termSizeFilter + } <- T.useLive T.unequal path + + -- | Computed + -- | + let + showAddNewTerm = + ( + (not queryExactMatches || A.null props.tableBody) + && + (searchQuery /= "") + ) + + -- | Render + -- | + pure $ + + H.div + { className: intercalate " " + [ "ngrams-table-container" + , "card" + ] + } + [ + + H.div + { className: intercalate " " + [ "ngrams-table-container__header" + , "card-header" ] - , if (selectionsExist ngramsSelection) - then H.li {className: "list-group-item"} [selectButtons true] - else H.div {} [] - , H.div {id: "terms_table", className: "card-body"} - [ H.table {className: "table able"} - [ H.thead {className: ""} [props.tableHead] - , H.tbody {} props.tableBody + } + [ + -- H.div + -- { className: "col-md-2" + -- , style: { marginTop: "6px" } + -- } + -- [ + -- H.li + -- { className: "list-group-item" } + -- syncResetButton + -- , + -- -- , if (not $ Set.member (normNgram tabNgramType searchQuery) ngramsSelection) && searchQuery /= "" then + -- + -- ] + -- , + + H.div + { className: intercalate " " + [ "ngrams-table-container__header__item" + , "card" + ] + } + syncResetButton + , + H.div + { className: intercalate " " + [ "ngrams-table-container__header__item" + , "card" ] - , H.li {className: "list-group-item"} - [ H.div { className: "row" } - [ H.div { className: "col-md-4" } - [selectButtons (selectionsExist ngramsSelection)] - , H.div {className: "col-md-4 col-md-offset-4"} - [props.paginationLinks] - ] + } + [ + R2.select + { id: "picklistmenu" + , className: "form-control custom-select" + , defaultValue: (maybe "" show termListFilter) + , on: {change: setTermListFilter <<< read <<< R.unsafeEventValue} + } + (map optps1 termLists) + ] + , + H.div + { className: intercalate " " + [ "ngrams-table-container__header__item" + , "card" ] + } + [ + R2.select + { id: "picktermtype" + , className: "form-control custom-select" + , defaultValue: (maybe "" show termSizeFilter) + , on: {change: setTermSizeFilter <<< read <<< R.unsafeEventValue} + } + (map optps1 termSizes) + ] + , + H.div + { className: intercalate " " + [ "ngrams-table-container__header__item" + , "card" + ] + } + [ + B.wad + [ "d-flex", "align-items-center" ] + [ + props.pageSizeControl + , + B.wad_ [ "mr-2", "d-inline-block" ] + , + B.label_ "items" + -- H.div { className: "col-md-6" } [ props.pageSizeControl ] + -- , H.div { className: "col-md-6" } [ + -- ] + ] + ] + , + H.div + { className: intercalate " " + [ "ngrams-table-container__header__item" + , "card" + , "flex-grow-1" + ] + } + [ + props.pageSizeDescription + , + props.paginationLinks + ] + ] + , + R2.when (selectionsExist ngramsSelection) $ + + H.li + { className: "card" } + [ + selectButtons true + ] + , + H.div + { id: "terms_table" + , className: "card-body" + } + [ + R2.when showAddNewTerm $ + + H.div + { className: "ngrams-table-container__add-term" } + [ + B.button + { variant: ButtonVariant Light + , callback: const $ addCallback searchQuery + } + [ + B.icon + { name: "circle" + , className: "mr-1 graph-term" + } + , + H.text "Add" + , + H.text $ nbsp 1 + , + B.b_ $ "« " <> searchQuery <> " »" + , + H.text $ nbsp 1 + , + H.text "to Map terms" + ] + ] + , + H.table + { className: "table able" } + [ + H.thead + {} + [ + props.tableHead + ] + , + H.tbody + {} + props.tableBody + ] + , + H.li + { className: intercalate " " + [ "ngrams-table-container__footer" + , "card" + ] + } + [ + H.div + { className: "ngrams-table-container__footer__item" } + [ + selectButtons (selectionsExist ngramsSelection) + ] + , + H.div + { className: "ngrams-table-container__footer__item" } + [ + props.paginationLinks ] ] ] ] - -- WHY setPath f = origSetPageParams (const $ f path) - setTermListFilter x = T.modify (_ { termListFilter = x }) path - setTermSizeFilter x = T.modify (_ { termSizeFilter = x }) path - setSelection term = dispatch $ setTermListSetA ngramsTableCache ngramsSelection term - - selectionsExist :: Set NgramsTerm -> Boolean - selectionsExist = not <<< Set.isEmpty - - selectButtons false = H.div {} [] - selectButtons true = - H.div {} [ - H.button { className: "btn btn-primary" - , on: { click: const $ setSelection MapTerm } - } [ H.text "Map" ] - , H.button { className: "btn btn-primary" - , on: { click: const $ setSelection StopTerm } - } [ H.text "Stop" ] - , H.button { className: "btn btn-primary" - , on: { click: const $ setSelection CandidateTerm } - } [ H.text "Candidate" ] - ] + + -- WHY setPath f = origSetPageParams (const $ f path) + setTermListFilter x = T.modify (_ { termListFilter = x }) path + setTermSizeFilter x = T.modify (_ { termSizeFilter = x }) path + setSelection term = dispatch $ setTermListSetA ngramsTableCache ngramsSelection term + + selectionsExist :: Set NgramsTerm -> Boolean + selectionsExist = not <<< Set.isEmpty + + selectButtons false = H.div {} [] + selectButtons true = + H.div {} [ + H.button { className: "btn btn-primary" + , on: { click: const $ setSelection MapTerm } + } [ H.text "Map" ] + , H.button { className: "btn btn-primary" + , on: { click: const $ setSelection StopTerm } + } [ H.text "Stop" ] + , H.button { className: "btn btn-primary" + , on: { click: const $ setSelection CandidateTerm } + } [ H.text "Candidate" ] + ] -- NEXT @@ -280,14 +402,30 @@ loadedNgramsTableHeader :: R2.Component LoadedNgramsTableHeaderProps loadedNgramsTableHeader = R.createElement loadedNgramsTableHeaderCpt loadedNgramsTableHeaderCpt :: R.Component LoadedNgramsTableHeaderProps loadedNgramsTableHeaderCpt = here.component "loadedNgramsTableHeader" cpt where - cpt { searchQuery } _ = do - pure $ R.fragment - [ H.h4 { style: { textAlign : "center" } } - [ H.span { className: "fa fa-hand-o-down" } [] - , H.text "Extracted Terms" ] - , NTS.searchInput { key: "search-input" - , searchQuery } + cpt { searchQuery } _ = pure $ + + R.fragment + [ + H.div + { className: "loaded-ngrams-table-header" } + [ + B.icon + { name: "hand-o-down" + , className: "loaded-ngrams-table-header__icon" + } + , + B.wad_ [ "mr-1", "d-inline-block" ] + , + B.span' + { className: "loaded-ngrams-table-header__text" } $ + "Extracted Terms" ] + , + NTS.searchInput + { key: "search-input" + , searchQuery + } + ] loadedNgramsTableBody :: R2.Component PropsNoReload loadedNgramsTableBody = R.createElement loadedNgramsTableBodyCpt @@ -441,7 +579,12 @@ loadedNgramsTableBodyCpt = here.component "loadedNgramsTableBody" cpt where , wrapColElts: wrapColElts { allNgramsSelected, dispatch: performAction, ngramsSelection } scoreType } - , syncResetButton + , + B.wad + [ "mt-2", "d-inline-block" ] + [ + syncResetButton + ] ] where colNames = TT.ColumnName <$> ["Show", "Select", "Map", "Stop", "Terms", "Score"] -- see convOrderBy @@ -576,7 +719,7 @@ type MainNgramsTableProps = ( getNgramsChildrenAff :: Session -> NodeID -> Array ListId -> TabType -> NgramsTerm -> Aff (Array NgramsTerm) getNgramsChildrenAff session nodeId listIds tabType (NormNgramsTerm ngrams) = do - res :: Either RESTError ({ data :: Array { children :: Array String, ngrams :: String }}) <- get session $ R.GetNgrams params (Just nodeId) + res :: Either RESTError ({ data :: Array { children :: Array String, ngrams :: String }}) <- get session $ Routes.GetNgrams params (Just nodeId) case res of Left err -> pure [] Right { data: lst } -> case A.uncons (A.filter (\d -> d.ngrams == ngrams) lst) of @@ -613,7 +756,7 @@ mainNgramsTableCpt = here.component "mainNgramsTable" cpt -- , getNgramsChildren: getNgramsChildrenAff session nodeId' tabType -- , onCancelRef -- , onNgramsClickRef - -- , onSaveRef + -- , onSaveRef -- } -- let path = initialPageParams session nodeId [defaultListId] tabType @@ -650,11 +793,14 @@ ngramsTreeEditCpt = here.component "ngramsTreeEdit" cpt where ngramsParentFocused <- T.useFocused (_.ngramsParent) (\a b -> b { ngramsParent = a}) box ngramsParentFocused' <- T.useLive T.unequal ngramsParentFocused + let + gutter = B.wad_ [ "mb-2", "d-inline-block" ] + pure $ if isEditingFocused' then case ngramsParentFocused' of - Nothing -> H.div {} [] + Nothing -> gutter Just ngramsParent' -> ngramsTreeEditReal (Record.merge props { ngramsParent' }) [] - else H.div {} [] + else gutter type NgramsTreeEditRealProps = ( ngramsParent' :: NgramsTerm @@ -669,40 +815,91 @@ ngramsTreeEditRealCpt = here.component "ngramsTreeEditReal" cpt where , ngramsParent' , onCancelRef , onNgramsClickRef - , onSaveRef } _ = do - { ngramsChildren, ngramsChildrenDiff } <- T.useLive T.unequal box - - let ngramsDepth = { depth: 0, ngrams: ngramsParent' } - ngramsChildrenPatched :: Set NgramsTerm - ngramsChildrenPatched = applyPatchSet (patchSetFromMap ngramsChildrenDiff) $ Set.fromFoldable ngramsChildren - -- A patched version of getNgramsChildren. This is used - -- because we're editing the tree and so won't fetch the API - -- ngrams children. - gnc ngrams = if ngrams == ngramsParent' - then do - pure $ A.fromFoldable ngramsChildrenPatched - else do - pure [] - - pure $ H.div {} - [ H.p {} - [ H.text $ "Editing " <> ngramsTermText ngramsDepth.ngrams ] - , renderNgramsTree { getNgramsChildren: gnc - , ngramsClick - , ngramsDepth - , ngramsEdit - , ngramsStyle: [] - , key: show ngramsParent' - <> "-" <> show ngramsChildren - <> "-" <> show ngramsChildrenDiff - } - , H.button { className: "btn btn-primary" - , on: { click: onSaveClick } --(const $ dispatch AddTermChildren)} - } [ H.text "Save" ] - , H.button { className: "btn btn-primary" - , on: { click: onCancelClick } --(const $ dispatch ClearTreeEdit)} - } [ H.text "Cancel" ] + , onSaveRef + } _ = do + -- | States + -- | + { ngramsChildren + , ngramsChildrenDiff + } <- T.useLive T.unequal box + + -- | Computed + -- | + let + ngramsDepth = { depth: 0, ngrams: ngramsParent' } + + ngramsChildrenPatched :: Set NgramsTerm + ngramsChildrenPatched = applyPatchSet (patchSetFromMap ngramsChildrenDiff) $ Set.fromFoldable ngramsChildren + -- A patched version of getNgramsChildren. This is used + -- because we're editing the tree and so won't fetch the API + -- ngrams children. + gnc ngrams = + if ngrams == ngramsParent' + then do + pure $ A.fromFoldable ngramsChildrenPatched + else do + pure [] + + + -- | Render + -- | + pure $ + + H.div + { className: intercalate " " + [ "ngrams-tree-edit-real" + , "card" + ] + } + [ + H.div + { className: "card-header" } + [ + B.icon + { name: "pencil-square-o" + } + , + B.wad_ + [ "mr-1", "d-inline-block" ] + , + B.b_ $ ngramsTermText ngramsDepth.ngrams + ] + , + H.div + { className: "card-body" } + [ + renderNgramsTree + { getNgramsChildren: gnc + , ngramsClick + , ngramsDepth + , ngramsEdit + , ngramsStyle: [] + , key: show ngramsParent' + <> "-" <> show ngramsChildren + <> "-" <> show ngramsChildrenDiff + } + , + H.div + { className: "ngrams-tree-edit-real__actions" } + [ + B.button + { variant: ButtonVariant Light + , callback: onCancelClick --(const $ dispatch ClearTreeEdit)} + , size: SmallSize + } + [ H.text "Cancel" ] + , + B.button + { variant: ButtonVariant Primary + , callback: onSaveClick --(const $ dispatch AddTermChildren)} + , size: SmallSize + } + [ H.text "Save" ] + ] + ] ] + -- | Helpers + -- | where --ngramsClick {depth: 1, ngrams: child} = Just $ dispatch $ ToggleChild false child --ngramsClick _ = Nothing @@ -712,11 +909,11 @@ ngramsTreeEditRealCpt = here.component "ngramsTreeEditReal" cpt where Just ngc -> ngc nd ngramsEdit :: NgramsClick ngramsEdit _ = Nothing - onCancelClick :: forall e. e -> Effect Unit + onCancelClick :: Unit -> Effect Unit onCancelClick _ = case R.readRef onCancelRef of Nothing -> pure unit Just onCancel -> onCancel unit - onSaveClick :: forall e. e -> Effect Unit + onSaveClick :: Unit -> Effect Unit onSaveClick _ = case R.readRef onSaveRef of Nothing -> pure unit Just onSave -> onSave unit @@ -759,7 +956,7 @@ mainNgramsTableCacheOnCpt = here.component "mainNgramsTableCacheOn" cpt where , renderer: render , spinnerClass: Nothing } - versionEndpoint { defaultListId, path: { nodeId, tabType, session } } _ = get session $ R.GetNgramsTableVersion { listId: defaultListId, tabType } (Just nodeId) + versionEndpoint { defaultListId, path: { nodeId, tabType, session } } _ = get session $ Routes.GetNgramsTableVersion { listId: defaultListId, tabType } (Just nodeId) errorHandler = logRESTError here "[mainNgramsTable]" mkRequest :: PageParams -> GUC.Request mkRequest path@{ session } = GUC.makeGetRequest session $ url path @@ -767,7 +964,7 @@ mainNgramsTableCacheOnCpt = here.component "mainNgramsTableCacheOn" cpt where url { listIds , nodeId , tabType - } = R.GetNgramsTableAll { listIds + } = Routes.GetNgramsTableAll { listIds , tabType } (Just nodeId) handleResponse :: VersionedNgramsTable -> VersionedNgramsTable handleResponse v = v @@ -810,7 +1007,7 @@ mainNgramsTableCacheOffCpt = here.component "mainNgramsTableCacheOff" cpt where , termListFilter , termSizeFilter } = - get session $ R.GetNgrams params (Just nodeId) + get session $ Routes.GetNgrams params (Just nodeId) where params = { limit , listIds diff --git a/src/Gargantext/Components/NgramsTable/Search.purs b/src/Gargantext/Components/NgramsTable/Search.purs index 4d6c0de1..7dc1c0db 100644 --- a/src/Gargantext/Components/NgramsTable/Search.purs +++ b/src/Gargantext/Components/NgramsTable/Search.purs @@ -1,8 +1,12 @@ module Gargantext.Components.NgramsTable.Search where -import Data.Nullable (Nullable, null) -import DOM.Simple as DOM import Gargantext.Prelude + +import DOM.Simple as DOM +import Data.Foldable (intercalate) +import Data.Nullable (Nullable, null) +import Gargantext.Components.Bootstrap as B +import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), Variant(..)) import Gargantext.Utils.Reactix as R2 import Reactix as R import Reactix.DOM.HTML as H @@ -24,7 +28,7 @@ searchInputCpt = here.component "searchInput" cpt where cpt { searchQuery } _ = do inputRef <- R.useRef null - + pure $ R2.row [ H.div { className: "col-12" } [ H.div { className: "input-group" } @@ -46,14 +50,34 @@ searchButtonCpt = here.component "searchButton" cpt where cpt { inputRef, searchQuery } _ = do searchQuery' <- T.useLive T.unequal searchQuery - pure $ H.div { className: "input-group-prepend" } - [ if searchQuery' /= "" + pure $ + + H.div + { className: intercalate " " + [ "search-button-prepend" + , "input-group-prepend" + ] + } + [ + if searchQuery' /= "" then - H.button { className: "btn btn-danger" - , on: { click: \_ -> R2.setInputValue inputRef "" } } - -- T.write "" searchQuery } } - [ H.span {className: "fa fa-times"} []] - else H.span { className: "fa fa-search input-group-text" } [] + B.button + { variant: ButtonVariant Light + , callback: \_ -> R2.setInputValue inputRef "" + -- T.write "" searchQuery + , className: "input-group-text" + } + [ + B.icon + { name: "times" + , className: "text-danger" + } + ] + else + B.icon + { name: "search" + , className: "input-group-text" + } ] type SearchFieldInputProps = @@ -67,7 +91,7 @@ searchFieldInputCpt :: R.Component SearchFieldInputProps searchFieldInputCpt = here.component "searchFieldInput" cpt where cpt { inputRef, searchQuery } _ = do -- searchQuery' <- T.useLive T.unequal searchQuery - + pure $ H.input { className: "form-control" -- , defaultValue: searchQuery' , name: "search" diff --git a/src/Gargantext/Components/NgramsTable/SyncResetButton.purs b/src/Gargantext/Components/NgramsTable/SyncResetButton.purs index f906c015..a9fa3c04 100644 --- a/src/Gargantext/Components/NgramsTable/SyncResetButton.purs +++ b/src/Gargantext/Components/NgramsTable/SyncResetButton.purs @@ -1,10 +1,13 @@ module Gargantext.Components.NgramsTable.SyncResetButton where +import Gargantext.Prelude + import Effect.Aff (Aff) import Effect.Class (liftEffect) import FFI.Simple.Functions (delay) +import Gargantext.Components.Bootstrap as B +import Gargantext.Components.Bootstrap.Types (ButtonVariant(..), ComponentStatus(..), Variant(..)) import Gargantext.Core.NgramsTable.Types (CoreAction(..), CoreDispatch, NgramsTablePatch) -import Gargantext.Prelude import Gargantext.Utils.Reactix as R2 import Reactix as R import Reactix.DOM.HTML as H @@ -32,9 +35,14 @@ syncResetButtonsCpt = here.component "syncResetButtons" cpt let hasChanges = ngramsLocalPatch /= mempty - hasChangesClass = if hasChanges then "" else " disabled" - synchronizingClass = if synchronizing' then " disabled" else "" + statusReset _ true = Disabled + statusReset false _ = Disabled + statusReset _ _ = Enabled + + statusSync _ true = Deferred + statusSync false _ = Disabled + statusSync _ _ = Enabled resetClick _ = do performAction ResetPatches @@ -47,16 +55,24 @@ syncResetButtonsCpt = here.component "syncResetButtons" cpt afterSync x liftEffect $ T.write_ false synchronizing - pure $ H.div { className: "btn-toolbar" } - [ H.div { className: "btn-group mr-2" } - [ H.button { className: "btn btn-danger " <> hasChangesClass <> synchronizingClass - , on: { click: resetClick } - } [ H.text "Reset" ] - ] - , H.div { className: "btn-group mr-2" } - [ H.button { className: "btn btn-primary " <> hasChangesClass <> synchronizingClass - , on: { click: synchronizeClick } - } [ H.text "Sync" ] - ] - ] + pure $ + B.wad + [ "d-flex" ] + [ + B.button + { variant: ButtonVariant Light + , callback: resetClick + , status: statusReset hasChanges synchronizing' + } + [ H.text "Reset" ] + , + B.wad_ [ "mr-1", "d-inline-block" ] + , + B.button + { variant: ButtonVariant Primary + , callback: synchronizeClick + , status: statusSync hasChanges synchronizing' + } + [ H.text "Sync" ] + ] diff --git a/src/Gargantext/Components/NgramsTable/Tree.purs b/src/Gargantext/Components/NgramsTable/Tree.purs index f5fb3f7f..c7ce87bd 100644 --- a/src/Gargantext/Components/NgramsTable/Tree.purs +++ b/src/Gargantext/Components/NgramsTable/Tree.purs @@ -1,30 +1,29 @@ module Gargantext.Components.NgramsTable.Tree where +import Gargantext.Prelude + import Data.Array as A import Data.Either (Either(..)) import Data.Lens ((^..), (^.), view) -import Data.Lens.At (at) import Data.Lens.Fold (folded) import Data.Lens.Index (ix) -import Data.List (List) +import Data.List (List, intercalate) import Data.List as L -import Data.Map (Map) -import Data.Map as Map -import Data.Maybe (Maybe(..), maybe, isJust) -import Data.Nullable (Nullable, null, toMaybe) +import Data.Maybe (Maybe(..), maybe) import Data.Set (Set) import Data.Set as Set -import DOM.Simple as DOM import Effect (Effect) -import Effect.Aff (Aff, launchAff_) -import Effect.Class (liftEffect) -import Gargantext.Core.NgramsTable.Functions (applyNgramsPatches, setTermListA, tablePatchHasNgrams) -import Gargantext.Core.NgramsTable.Types (Action(..), NgramsClick, NgramsDepth, NgramsElement, NgramsTable, NgramsTablePatch(..), NgramsTerm, _NgramsElement, _NgramsRepoElement, _PatchMap, _children, _list, _ngrams, _occurrences, ngramsTermText, replace) +import Effect.Aff (Aff) +import Gargantext.Components.Bootstrap as B +import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Variant(..)) import Gargantext.Components.Table as Tbl import Gargantext.Config.REST (logRESTError) +import Gargantext.Core.NgramsTable.Functions (applyNgramsPatches, setTermListA, tablePatchHasNgrams) +import Gargantext.Core.NgramsTable.Types (Action(..), NgramsClick, NgramsDepth, NgramsElement, NgramsTable, NgramsTablePatch, NgramsTerm, _NgramsElement, _NgramsRepoElement, _children, _list, _ngrams, _occurrences, ngramsTermText, replace) import Gargantext.Hooks.Loader (useLoader) -import Gargantext.Prelude (Unit, bind, const, discard, map, mempty, not, otherwise, pure, show, unit, ($), (+), (/=), (<<<), (<>), (==), (>), (||)) +import Gargantext.Prelude (Unit, bind, const, map, mempty, not, otherwise, pure, show, unit, ($), (+), (<<<), (<>), (==), (>), (||)) import Gargantext.Types as GT +import Gargantext.Utils ((?)) import Gargantext.Utils.Reactix as R2 import React.DOM (a, span, text) import React.DOM.Props as DOM @@ -55,7 +54,9 @@ renderNgramsTreeCpt :: R.Component RenderNgramsTree renderNgramsTreeCpt = here.component "renderNgramsTree" cpt where cpt { getNgramsChildren, ngramsClick, ngramsDepth, ngramsEdit, ngramsStyle } _ = do - pure $ H.ul {} + pure $ + H.ul + { className: "render-ngrams-tree" } [ H.span { className: "tree" } [ H.span { className: "righthanded" } [ tree { getNgramsChildren @@ -118,13 +119,37 @@ treeLoaded :: Record TreeLoaded -> R.Element treeLoaded p = R.createElement treeLoadedCpt p [] treeLoadedCpt :: R.Component TreeLoaded treeLoadedCpt = here.component "treeLoaded" cpt where - cpt params@{ ngramsChildren, ngramsClick, ngramsDepth, ngramsEdit, ngramsStyle } _ = do + cpt params@{ ngramsChildren + , ngramsClick + , ngramsDepth + , ngramsEdit + , ngramsStyle + } _ = do pure $ - H.li { style: { width : "100%" } } - ([ H.i { className, style } [] ] - <> [ R2.buff $ tag [ text $ " " <> ngramsTermText ngramsDepth.ngrams ] ] - <> maybe [] edit (ngramsEdit ngramsDepth) - <> [ forest ngramsChildren ] + + H.li + -- { className: "ngrams-tree-loaded-node" } + { className: intercalate " " + [ "ngrams-tree-loaded-node" + , ngramsDepth.depth == 1 ? + "ngrams-tree-loaded-node--first-child" $ + "" + , ngramsDepth.depth > 1 ? + "ngrams-tree-loaded-node--grand-child" $ + "" + ] + } + ( + -- @NOTE #414: currently commenting this, as the below icon is not + -- a call-to-action, thus deceiving the user of possible + -- yet-to-become reveal/collapse node children feature + -- [ H.i { className, style } [] ] + -- <> + [ R2.buff $ tag [ text $ " " <> ngramsTermText ngramsDepth.ngrams ] ] + <> + maybe [] edit (ngramsEdit ngramsDepth) + <> + [ forest ngramsChildren ] ) where tag = @@ -133,10 +158,16 @@ treeLoadedCpt = here.component "treeLoaded" cpt where a (ngramsStyle <> [DOM.onClick $ const effect]) Nothing -> span ngramsStyle - edit effect = [ H.text " " - , H.i { className: "fa fa-pencil" - , on: { click: const effect } } [] - ] + edit effect = + [ + B.iconButton + { name: "pencil" + , className: "ml-1" + , variant: Secondary + , callback: const effect + , overlay: false + } + ] leaf = L.null ngramsChildren className = "fa fa-chevron-" <> if open then "down" else "right" style = if leaf then {color: "#adb5bd"} else {color: ""} @@ -177,29 +208,55 @@ renderNgramsItemCpt = here.component "renderNgramsItem" cpt , ngramsTable } _ = do isEditing' <- T.useLive T.unequal isEditing - + pure $ Tbl.makeRow - [ H.div { className: "ngrams-selector" } - [ H.span { className: "ngrams-chooser fa fa-eye-slash" - , on: { click: onClick } } [] + [ + H.div + { className: "text-center" + , style: { marginTop: "6px" } + } + [ + B.iconButton + { name: "eye-slash" + , status: Disabled -- see `onClick` behavior + , callback: onClick + , className: "" + } ] , selected , checkbox GT.MapTerm , checkbox GT.StopTerm , H.div {} ( if isEditing' - then [ H.a { on: { click: const $ dispatch $ ToggleChild true ngrams } } - [ H.i { className: "fa fa-plus" } [] ] - , R2.buff $ tag [ text $ " " <> ngramsTermText ngramsDepth.ngrams ] - ] - else [ renderNgramsTree { getNgramsChildren: getNgramsChildren' - , ngramsClick - , ngramsDepth - , ngramsEdit - , ngramsStyle - , key: "" } ] + then + [ + B.iconButton + { name: "plus" + , className: "mr-1 align-bottom" + , overlay: false + , variant: Primary + , callback: const $ dispatch $ ToggleChild true ngrams + } + , + R2.buff $ + tag [ text $ " " <> ngramsTermText ngramsDepth.ngrams ] + ] + else + [ + renderNgramsTree + { getNgramsChildren: getNgramsChildren' + , ngramsClick + , ngramsDepth + , ngramsEdit + , ngramsStyle + , key: "" + } + ] ) - , H.text $ show (ngramsElement ^. _NgramsElement <<< _occurrences) + , + B.wad' + [ "pl-3" ] $ + show (ngramsElement ^. _NgramsElement <<< _occurrences) ] where ngramsDepth = { ngrams, depth: 0 } @@ -232,21 +289,42 @@ renderNgramsItemCpt = here.component "renderNgramsItem" cpt -- | ngramsTransient = const Nothing -- | otherwise = Just <<< dispatch <<< cycleTermListItem <<< view _ngrams selected = - H.input { checked: Set.member ngrams ngramsSelection - , className: "checkbox" - , on: { change: const $ dispatch $ ToggleSelect ngrams } - , type: "checkbox" - } + B.wad + [ "text-center" ] + [ + H.input + { checked: Set.member ngrams ngramsSelection + , className: "checkbox" + , on: { change: const $ dispatch $ ToggleSelect ngrams } + , type: "checkbox" + , style: + { cursor: "pointer" + , marginTop: "6px" + } + } + ] + checkbox termList' = let chkd = termList == termList' termList'' = if chkd then GT.CandidateTerm else termList' in - H.input { checked: chkd - , className: "checkbox" - , on: { change: const $ dispatch $ CoreAction $ - setTermListA ngrams (replace termList termList'') } - , readOnly: ngramsTransient - , type: "checkbox" } + B.wad + [ "text-center" ] + [ + H.input + { checked: chkd + , className: "checkbox" + , on: { change: const $ dispatch $ CoreAction $ + setTermListA ngrams (replace termList termList'') } + , readOnly: ngramsTransient + , type: "checkbox" + , style: + { cursor: "pointer" + , marginTop: "6px" + } + } + ] + ngramsTransient = tablePatchHasNgrams ngramsLocalPatch ngrams -- ^ TODO here we do not look at ngramsNewElems, shall we? ngramsOpacity diff --git a/src/Gargantext/Components/Table.purs b/src/Gargantext/Components/Table.purs index 7ca76cb9..867b65d6 100644 --- a/src/Gargantext/Components/Table.purs +++ b/src/Gargantext/Components/Table.purs @@ -306,6 +306,8 @@ tableCpt = here.component "table" cpt makeRow :: Array R.Element -> R.Element makeRow els = H.tr {} $ (\c -> H.td {} [c]) <$> els +makeRow' :: forall r. Record r -> Array R.Element -> R.Element +makeRow' p els = H.tr p $ (\c -> H.td {} [c]) <$> els type FilterRowsParams = ( @@ -320,7 +322,8 @@ filterRows { params: { limit, offset } } rs = newRs defaultContainer :: Record TableContainerProps -> R.Element defaultContainer props = R.fragment $ props.syncResetButton <> controls where - controls = [ R2.row + controls = [ H.div + { className: "d-flex align-items-center mb-2" } [ H.div {className: "col-md-4"} [ props.pageSizeDescription ] , H.div {className: "col-md-4"} [ props.paginationLinks ] , H.div {className: "col-md-4"} [ props.pageSizeControl ] @@ -374,7 +377,7 @@ sizeDDCpt = here.component "sizeDD" cpt textDescription :: Int -> PageSizes -> Int -> R.Element textDescription currPage pageSize totalRecords = - H.div {className: "row1"} [ H.div {className: ""} [ H.text msg ] ] -- TODO or col-md-6 ? + H.div {className: ""} [ H.text msg ] -- TODO or col-md-6 ? where start = (currPage - 1) * pageSizes2Int pageSize + 1 end' = currPage * pageSizes2Int pageSize diff --git a/src/sass/_legacy.sass b/src/sass/_legacy.sass index db7b6739..e7cd9238 100644 --- a/src/sass/_legacy.sass +++ b/src/sass/_legacy.sass @@ -4,6 +4,7 @@ @import "./_legacy/_tree" @import "./_legacy/_code_editor" @import "./_legacy/_styles" +@import "./_legacy/_list" @import "./_legacy/_annotation" @import "./_legacy/_folder_view" @import "./_legacy/_phylo" diff --git a/src/sass/_legacy/_list.sass b/src/sass/_legacy/_list.sass new file mode 100644 index 00000000..04edfbc7 --- /dev/null +++ b/src/sass/_legacy/_list.sass @@ -0,0 +1,156 @@ +.table + $row-min-height: 48px + + tr td + height: $row-min-height + + /////////////////////// + + .page-paint-raw + + &--selected + position: relative + + td:first-child::before + @include fit-positions + + content: "" + width: 3px + background-color: $info + position: absolute + + /////////////////////// + + .page-paint-row + + &--active + // font-weight: bold + + &--trash + text-decoration: line-through + + /////////////////////// + + .doc-chooser + $offset-top: 3px // flex alignment won't work, hence empirical value + + padding-top: $offset-top + text-align: center + + /////////////////////// + + .rating-group + $offset-top: 3px // flex alignment won't work, hence empirical value + + display: flex + padding-top: $offset-top + + &__action + // @XXX Glyphicon icons lack of homogeneous width + width: 14px + margin-right: space-x(1) + +///////////////////////////////////////////////////: + +.search-button-prepend + + .input-group-text + // @XXX Glyphicon icons lack of homogeneous width + width: 41px + + // @XXX Bootstrap adding unwanted `z-index` on "_input-group.scss" + z-index: initial + +///////////////////////////////////////////////////: + +.ngrams-table-container + + &__header + display: flex + align-items: flex-start + + &__item + padding: $card-spacer-y $card-spacer-x + + &:not(:first-child) + margin-left: space-x(1) + &:not(:last-child) + margin-right: space-x(1) + + &__add-term + // struggling to create harmonious vertical gutters due to Bootstrap adding + // helper classes surrounding the element + $offset-top: -8px + $offset-bottom: 12px + + margin-top: $offset-top + margin-bottom: $offset-bottom + + &__footer + padding: $card-spacer-y $card-spacer-x + + &__item + text-align: center + +///////////////////////////////////////////////////: + +.ngrams-tree-edit-real + // empirical value fitting the best size of the render tree + $min-with: 416px + + min-width: $min-with + width: fit-content // growing purpose + border-color: $primary + margin-top: space-x(2) + margin-bottom: space-x(2) + + + &__actions + display: flex + margin-top: space-x(2) + + .b-button + margin-right: space-x(1) + +////////////////////////////////////////////////// + +.loaded-ngrams-table-header + text-align: center + margin-top: space-x(1.5) + margin-bottom: space-x(1.5) + + &__icon + font-size: 16px + + &__text + font-size: 22px + font-weight: bold + font-family: $headings-font-family + +////////////////////////////////////////////////// + +.ngrams-tree-loaded-node + + + &--first-child::before, + &--grand-child::before + color: $gray-400 + font-size: 11px + margin-right: space-x(0.5) + + @include right-handed + content: "└" + @include left-handed + content: "┘" + + &--first-child + // empirical value where the child separator aligns with its parent text + $child-offset: -2px + + margin-left: -2px + + &--grand-child + // empirical value where the child separator aligns with its parent text + $child-offset: 13px + + margin-left: 13px diff --git a/src/sass/_legacy/_styles.sass b/src/sass/_legacy/_styles.sass index ef6c459d..95d0af29 100644 --- a/src/sass/_legacy/_styles.sass +++ b/src/sass/_legacy/_styles.sass @@ -63,25 +63,6 @@ left: 50% top: 50% -.table - tr - td - color: #005a9aff - .active - font-weight: bold - text-decoration: underline - .ngrams-selector - display: flex - .ngrams-chooser - padding: 3px - .trash - text-decoration: line-through - -.action-search - margin: 10px - -.search-bar - margin: 10px /* */ @@ -93,7 +74,6 @@ select.form-control cursor: pointer - #app width: 100% -- 2.21.0