module Gargantext.Components.Search.SearchField ( Search, Props, defaultSearch, searchField, searchFieldComponent, isIsTex) where import Data.Maybe (Maybe(..), maybe) import Data.Newtype (over) import Data.String (length) import Data.Set as Set import Data.Tuple (fst) import Data.Tuple.Nested ((/\)) import DOM.Simple.Console (log, log2) import Effect.Aff (launchAff_) import Effect.Class (liftEffect) import Effect (Effect) import Reactix as R import Reactix.DOM.HTML as H import Gargantext.Prelude (Unit, bind, const, discard, map, pure, show, ($), (&&), (<), (<$>), (<<<), (<>), (==)) import Gargantext.Components.Data.Lang (Lang) import Gargantext.Components.Search.Types (DataField(..), Database(..), IMT_org(..), Org(..), SearchQuery(..), allIMTorgs, allOrgs, dataFields, defaultSearchQuery, doc, performSearch, readDatabase, readOrg) -- (Database(..), readDatabase, Lang(..), readLang, Org(..), readOrg, allOrgs, allIMTorgs, HAL_Filters(..), IMT_org(..)) import Gargantext.Sessions (Session) import Gargantext.Types as GT import Gargantext.Utils.Reactix as R2 select :: forall props. R.IsComponent String props (Array R.Element) => Record props -> Array R.Element -> R.Element select = R.createElement "select" type Search = { databases :: Array Database , datafield :: Maybe DataField , lang :: Maybe Lang , node_id :: Maybe Int , term :: String } eqSearch :: Search -> Search -> Boolean eqSearch s s' = (s.databases == s'.databases) && (s.datafield == s'.datafield) && (s.term == s'.term) && (s.lang == s'.lang) && (s.node_id == s'.node_id) defaultSearch :: Search defaultSearch = { databases: [] , datafield: Nothing , node_id: Nothing , lang: Nothing , term: "" } type Props = -- list of databases to search, or parsers to use on uploads ( databases :: Array Database , langs :: Array Lang -- State hook for a search, how we get data in and out , onSearch :: GT.AsyncTaskWithType -> Effect Unit , search :: R.State Search , session :: Session ) searchField :: Record Props -> R.Element searchField p = R.createElement searchFieldComponent p [] --searchFieldComponent :: R.Memo Props --searchFieldComponent = R.memo (R.hooksComponent "SearchField" cpt) eqProps searchFieldComponent :: R.Component Props searchFieldComponent = R.hooksComponent "G.C.S.SearchField" cpt where cpt props@{onSearch, search: search@(s /\ _)} _ = do pure $ H.div { className: "search-field-group", style: { width: "100%" } } [ H.div { className: "row" } [ H.div { className: "col-md-12" } [ searchInput {search} , if length s.term < 3 then H.div {}[] else H.div {} [ dataFieldNav search dataFields , if isExternal s.datafield then databaseInput search props.databases else H.div {} [] , if isHAL s.datafield then orgInput search allOrgs else H.div {} [] , if isIMT s.datafield then componentIMT search else H.div {} [] , if isCNRS s.datafield then componentCNRS search else H.div {} [] ] ] ] , H.div { className : "panel-footer" } [ if needsLang s.datafield then langNav search props.langs else H.div {} [] , H.div {} [] , H.div {className: "flex-center"} [submitButton {onSearch, search, session: props.session}] ] ] eqProps :: Record Props -> Record Props -> Boolean eqProps p p' = (p.databases == p'.databases ) && (p.langs == p'.langs ) && (eqSearch (fst p.search) (fst p'.search)) -- && (fst p.filters == fst p'.filters ) componentIMT (search /\ setSearch) = R.fragment [ H.ul {} $ map liCpt allIMTorgs --, filterInput fi ] where liCpt org = H.li {} [ H.input { type: "checkbox" , checked: isIn org search.datafield , on: { change: \_ -> (setSearch $ _ { datafield = updateFilter org search.datafield }) } } , if org == All_IMT then H.i {} [H.text $ " " <> show org] else H.text $ " " <> show org ] componentCNRS (search /\ setSearch) = R.fragment [ H.div {} [] --, filterInput fi ] isExternal :: Maybe DataField -> Boolean isExternal (Just (External _)) = true isExternal _ = false isHAL :: Maybe DataField -> Boolean isHAL (Just (External (Just (HAL _)))) = true isHAL _ = false isIsTex :: Maybe DataField -> Boolean isIsTex (Just (External (Just (IsTex)))) = true isIsTex _ = false isIMT :: Maybe DataField -> Boolean isIMT (Just ( External ( Just ( HAL ( Just ( IMT _)))))) = true isIMT _ = false isCNRS :: Maybe DataField -> Boolean isCNRS (Just ( External ( Just ( HAL ( Just ( CNRS _)))))) = true isCNRS _ = false needsLang :: Maybe DataField -> Boolean needsLang (Just Gargantext) = true needsLang (Just Web) = true needsLang (Just ( External ( Just (HAL _)))) = true needsLang _ = false isIn :: IMT_org -> Maybe DataField -> Boolean isIn org (Just (External (Just (HAL (Just (IMT imtOrgs)))))) = Set.member org imtOrgs isIn _ _ = false updateFilter :: IMT_org -> Maybe DataField -> Maybe DataField updateFilter org (Just (External (Just (HAL (Just (IMT imtOrgs)))))) = (Just (External (Just (HAL (Just $ IMT imtOrgs'))))) where imtOrgs' = if Set.member org imtOrgs then if org == All_IMT then Set.empty else Set.delete All_IMT $ Set.delete org imtOrgs else if org == All_IMT then Set.fromFoldable allIMTorgs else Set.insert org imtOrgs updateFilter org _ = (Just (External (Just (HAL (Just (IMT imtOrgs')))))) where imtOrgs' = if org == All_IMT then Set.fromFoldable allIMTorgs else Set.fromFoldable [org] ------------------------------------------------------------------------ langNav :: R.State Search -> Array Lang -> R.Element langNav ({lang} /\ setSearch) langs = R.fragment [ H.div {className: "text-primary center"} [H.text "with lang"] , H.div { className: "nav nav-tabs"} (liItem <$> langs) ] where liItem :: Lang -> R.Element liItem lang' = H.div { className : "nav-item nav-link" <> if (Just lang') == lang then " active" else "" , on: { click: \_ -> setSearch $ _ { lang = Just lang' } } } [ H.text (show lang') ] ------------------------------------------------------------------------ dataFieldNav :: R.State Search -> Array DataField -> R.Element dataFieldNav ({datafield} /\ setSearch) datafields = R.fragment [ H.div {className: "text-primary center"} [H.text "with DataField"] , H.div { className: "nav nav-tabs"} (liItem <$> dataFields) , H.div {className:"center"} [ H.text $ maybe "" doc datafield ] ] where liItem :: DataField -> R.Element liItem df' = H.div { className : "nav-item nav-link" <> if (Just df') == datafield then " active" else "" , on: { click: \_ -> setSearch $ _ { datafield = Just df'} } } [ H.text (show df') ] ------------------------------------------------------------------------ databaseNav :: R.State Search -> Array Database -> R.Element databaseNav ({datafield} /\ setSearch) dbs = R.fragment [ H.div {className: "text-primary center"} [H.text "with DataField"] , H.div { className: "nav nav-tabs"} (liItem <$> dbs) , H.div {className:"center"} [ H.text $ maybe "" doc db ] ] where db = case datafield of (Just (External (Just x))) -> Just x _ -> Nothing liItem :: Database -> R.Element liItem df' = H.div { className : "nav-item nav-link" <> if (Just $ External $ Just df') == datafield then " active" else "" , on: { click: \_ -> setSearch $ _ { datafield = Just $ External $ Just df' } } } [ H.text (show df') ] databaseInput :: R.State Search -> Array Database -> R.Element databaseInput ({datafield} /\ setSearch) dbs = H.div { className: "form-group" } [ H.div {className: "text-primary center"} [H.text "in database"] , R2.select { className: "form-control" , on: { change: onChange } } (liItem <$> dbs) , H.div {className:"center"} [ H.text $ maybe "" doc db ] ] where db = case datafield of (Just (External (Just x))) -> Just x _ -> Nothing liItem :: Database -> R.Element liItem db' = H.option {className : "text-primary center"} [ H.text (show db') ] onChange e = do let value = R2.unsafeEventValue e setSearch $ _ {datafield = Just $ External $ readDatabase value } orgInput :: R.State Search -> Array Org -> R.Element orgInput ({datafield} /\ setSearch) orgs = H.div { className: "form-group" } [ H.div {className: "text-primary center"} [H.text "filter with organization: "] , R2.select { className: "form-control" , on: { change: onChange } } (liItem <$> orgs) ] where liItem :: Org -> R.Element liItem org = H.option {className : "text-primary center"} [ H.text (show org) ] onChange e = do let value = R2.unsafeEventValue e setSearch $ _ { datafield = Just $ External $ Just $ HAL $ readOrg value } filterInput :: R.State String -> R.Element filterInput (term /\ setTerm) = H.div {className: "form-group"} [ H.input { defaultValue: term , className: "form-control" , type: "text" , on: { change: setTerm <<< const <<< R2.unsafeEventValue } , "required pattern": "[[0-9]+[ ]+]*" -- TODO ^FIXME not sure about the regex comprehension: that should match "123 2334 44545" only (Integers separated by one space) -- form validation with CSS -- DOC: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation , placeholder : "Filter with struct_Ids as integer" } ] type SearchInputProps = ( search :: R.State Search ) searchInput :: Record SearchInputProps -> R.Element searchInput p = R.createElement searchInputComponent p [] searchInputComponent :: R.Component SearchInputProps searchInputComponent = R.hooksComponent "G.C.S.SearchInput" cpt where cpt {search: (search /\ setSearch)} _ = do pure $ H.div { className : "" } [ H.input { defaultValue: search.term , className: "form-control" , type: "text" , on: { change : onChange setSearch } , placeholder: "Your Query here" } ] onChange setSearch e = do let value = R2.unsafeEventValue e setSearch $ _ { term = value } type SubmitButtonProps = ( onSearch :: GT.AsyncTaskWithType -> Effect Unit , search :: R.State Search , session :: Session ) submitButton :: Record SubmitButtonProps -> R.Element submitButton p = R.createElement submitButtonComponent p [] submitButtonComponent :: R.Component SubmitButtonProps submitButtonComponent = R.hooksComponent "G.C.S.SubmitButton" cpt where cpt {onSearch, search: (search /\ _), session} _ = pure $ H.button { className: "btn btn-primary" , type: "button" , on: {click: doSearch onSearch session search} , style: { width: "100%" } } [ H.text "Launch Search" ] doSearch os s q = \_ -> do log2 "[submitButton] searching" q triggerSearch os s q --case search.term of -- "" -> setSearch $ const defaultSearch -- _ -> setSearch $ const q triggerSearch :: (GT.AsyncTaskWithType -> Effect Unit) -> Session -> Search -> Effect Unit triggerSearch os s q = launchAff_ $ do liftEffect $ do -- log2 "Searching datafield: " $ show q.database log2 "[triggerSearch] Searching term: " q.term log2 "[triggerSearch] Searching lang: " q.lang case q.node_id of Nothing -> liftEffect $ log "[triggerSearch] node_id is Nothing, don't know what to do" Just id -> do task <- performSearch s id $ searchQuery q liftEffect $ do log2 "[triggerSearch] task" task os task --liftEffect $ do -- log2 "Return:" r -- modalShow "addCorpus" searchQuery :: Search -> SearchQuery searchQuery {datafield: Nothing, term} = over SearchQuery (_ {query=term}) defaultSearchQuery searchQuery {databases, datafield, lang, term, node_id} = over SearchQuery (_ { databases=databases , datafield=datafield , lang=lang , query=term , node_id=node_id }) defaultSearchQuery