Commit 447e1d3c authored by arturo's avatar arturo

[tree] Annuaire debug

* #376: node upade link
* #376: annuaire node file upload form
parent 03900d28
Pipeline #2584 failed with stage
in 0 seconds
......@@ -2088,7 +2088,7 @@ textarea.form-control {
}
.form-group {
margin-bottom: 1rem;
margin-bottom: 24px;
}
.form-text {
......
@charset "UTF-8";
/*! Bootstrap `Default` https://getbootstrap.com/docs/4.6 */
/*!
* Bootstrap v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
......@@ -2080,7 +2081,7 @@ textarea.form-control {
}
.form-group {
margin-bottom: 1rem;
margin-bottom: 24px;
}
.form-text {
......
......@@ -2081,7 +2081,7 @@ textarea.form-control {
}
.form-group {
margin-bottom: 1rem;
margin-bottom: 24px;
}
.form-text {
......
......@@ -2085,7 +2085,7 @@ textarea.form-control {
}
.form-group {
margin-bottom: 1rem;
margin-bottom: 24px;
}
.form-text {
......
......@@ -2085,7 +2085,7 @@ textarea.form-control {
}
.form-group {
margin-bottom: 1rem;
margin-bottom: 24px;
}
.form-text {
......
......@@ -9,7 +9,8 @@ import Gargantext.Components.Bootstrap.Cloak(cloak) as Exports
import Gargantext.Components.Bootstrap.Div(div', div_) as Exports
import Gargantext.Components.Bootstrap.Fieldset(fieldset) as Exports
import Gargantext.Components.Bootstrap.FormInput(formInput) as Exports
import Gargantext.Components.Bootstrap.FormSelect(formSelect) as Exports
import Gargantext.Components.Bootstrap.FormSelect(formSelect, formSelect') as Exports
import Gargantext.Components.Bootstrap.FormTextarea(formTextarea) as Exports
import Gargantext.Components.Bootstrap.Icon(icon) as Exports
import Gargantext.Components.Bootstrap.Label(label', label_) as Exports
import Gargantext.Components.Bootstrap.Spinner(spinner) as Exports
module Gargantext.Components.Bootstrap.FormSelect (formSelect) where
module Gargantext.Components.Bootstrap.FormSelect
( formSelect
, formSelect'
) where
import Gargantext.Prelude
import Data.Foldable (elem, intercalate)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Sizing(..))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Unsafe.Coerce (unsafeCoerce)
type Props =
......@@ -49,7 +54,7 @@ options =
-- |
-- |
-- | (?) note that it handled `value` as a String, as it is the KISS solution
-- | here
-- | here. Please use `formSelect'` for any other type
-- |
-- | https://getbootstrap.com/docs/4.1/components/forms/
formSelect :: forall r. R2.OptComponent Options Props r
......@@ -67,18 +72,22 @@ component = R.hooksComponent componentName cpt where
, status
} children = do
-- Computed
className <- pure $ intercalate " "
-- provided custom className
[ props.className
-- BEM classNames
, componentName
, componentName <> "--" <> show status
-- Bootstrap specific classNames
, bootstrapName
, bootstrapName <> "-" <> show props.size
]
change <- pure $ onChange status callback
let
className = intercalate " "
-- provided custom className
[ props.className
-- BEM classNames
, componentName
, componentName <> "--" <> show status
-- Bootstrap specific classNames
, bootstrapName
, bootstrapName <> "-" <> show props.size
]
-- Behaviors
let
change = onChange status callback
-- Render
pure $
R2.select
......@@ -103,4 +112,99 @@ onChange :: forall event.
onChange status callback event = do
if status == Enabled
then callback $ (unsafeCoerce event).target.value
else pure unit
else R.nothing
-----------------------------------------------------------------------
type AnyTypeProps a =
( callback :: a -> Effect Unit
, value :: a
, list :: Array a
| Options
)
-- | Derived component for `formSelect` with any value type (with `Read`
-- | and `Show` constraint)
-- |
-- |
-- | ```purescript
-- | formSelect'
-- | { callback: flip T.write_ box
-- | , value: anyType
-- | , list: [ anyType, anyType, ... ]
-- | }
-- | ```
-- |
-- | (?) Note that HTML option tags will be automatically added thanks to
-- | to the provided `list` prop. You can add additional HTML option within
-- | the `children` prop
formSelect' :: forall r a.
Read a
=> Show a
=> R2.OptComponent Options (AnyTypeProps a) r
formSelect' = R2.optComponent component' options
component' :: forall a.
Read a
=> Show a
=> R.Component (AnyTypeProps a)
component' = R.hooksComponent (componentName <> "__helper") cpt where
cpt props@{ callback
, list
, status
, value
} children = do
-- Computed
let
className = intercalate " "
-- provided custom className
[ props.className
-- BEM classNames
, componentName
, componentName <> "--" <> show status
-- Bootstrap specific classNames
, bootstrapName
, bootstrapName <> "-" <> show props.size
]
-- Behaviors
let
change = onChange' status callback
-- Render
pure $
R2.select
{ className
, on: { change }
, disabled: elem status [ Disabled ]
, readOnly: elem status [ Idled ]
, type: props.type
, value: show value
}
(
children
<>
flip map list \raw ->
H.option
{ value: show raw }
[ H.text $ show raw ]
)
-- | * Change event will effectively be triggered according to the
-- | component status props
-- | * Also directly returns the newly input value
-- | (usage not so different from `targetValue` of ReactBasic)
onChange' :: forall event a.
Read a
=> Show a
=> ComponentStatus
-> (a -> Effect Unit)
-> event
-> Effect Unit
onChange' status callback event = do
if status == Enabled
then event # unsafeCoerce >>> _.target.value >>> read >>> case _ of
Nothing -> R.nothing
Just v -> callback v
else R.nothing
module Gargantext.Components.Bootstrap.Label
( label'
, label_
) where
import Reactix as R
import Reactix.DOM.HTML as H
-- | Shorthand for using HTML <label> without writing its text node
label' :: forall r. Record r -> String -> R.Element
label' props content = H.label props [ H.text content ]
-- | Shorthand for using HTML <label> without writing its text node nor props
label_ :: String -> R.Element
label_ content = H.label {} [ H.text content ]
......@@ -3,33 +3,23 @@ module Gargantext.Components.Forest.Tree.Node.Action.Link where
import Gargantext.Prelude
import Data.Either (Either)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Data.Show.Generic (genericShow)
import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..))
import Gargantext.Components.Forest.Tree.Node.Action.Update.Types (LinkNodeReq(..), UpdateNodeParams(..))
import Gargantext.Components.Forest.Tree.Node.Tools (submitButton, panel)
import Gargantext.Components.Forest.Tree.Node.Tools.SubTree (subTreeView, SubTreeParamsIn)
import Gargantext.Components.Forest.Tree.Node.Tools.SubTree.Types (SubTreeOut(..))
import Gargantext.Config.REST (AffRESTError, RESTError)
import Gargantext.Routes (SessionRoute(..))
import Gargantext.Sessions (Session, post)
import Gargantext.Types as GT
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Simple.JSON as JSON
import Toestand as T
here :: R2.Here
here = R2.here "Gargantext.Components.Forest.Tree.Node.Action.Link"
newtype LinkNodeReq = LinkNodeReq { nodeType :: GT.NodeType, id :: GT.ID }
derive instance Eq LinkNodeReq
derive instance Generic LinkNodeReq _
instance Show LinkNodeReq where show = genericShow
derive newtype instance JSON.ReadForeign LinkNodeReq
derive newtype instance JSON.WriteForeign LinkNodeReq
linkNodeReq :: Session -> Maybe GT.NodeType -> GT.ID -> GT.ID -> AffRESTError GT.AsyncTaskWithType
linkNodeReq session nt fromId toId = do
eTask :: Either RESTError GT.AsyncTask <- post session (NodeAPI GT.Node (Just fromId) "update")
......@@ -76,11 +66,15 @@ linkNodeCpt' = here.component "__clone__" cpt
action' <- T.useLive T.unequal action
let button = case action' of
LinkNode { params } -> case params of
Just val -> submitButton (LinkNode {nodeType: Just nodeType, params: Just val}) dispatch
Nothing -> H.div {} []
_ -> H.div {} []
let
button = case action' of
LinkNode { params } -> case params of
Just (SubTreeOut { in: inId }) -> submitButton
(toParams nodeType inId)
dispatch
Nothing -> mempty
_ -> mempty
pure $ panel [
subTreeView { action
......@@ -92,3 +86,9 @@ linkNodeCpt' = here.component "__clone__" cpt
, subTreeParams
} []
] button
toParams :: GT.NodeType -> GT.ID -> Action
toParams nodeType id
= UpdateNode
$ UpdateNodeParamsLink
$ { methodLink: LinkNodeReq { nodeType, id } }
......@@ -6,6 +6,7 @@ import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Data.Show.Generic (genericShow)
import Gargantext.Components.PhyloExplorer.API as Phylo
import Gargantext.Types as GT
import Simple.JSON as JSON
import Simple.JSON.Generics as JSONG
......@@ -18,6 +19,7 @@ data UpdateNodeParams
| UpdateNodeParamsTexts { methodTexts :: Granularity }
| UpdateNodeParamsBoard { methodBoard :: Charts }
| UpdateNodeParamsPhylo { methodPhylo :: Phylo.UpdateData }
| UpdateNodeParamsLink { methodLink :: LinkNodeReq }
derive instance Eq UpdateNodeParams
derive instance Generic UpdateNodeParams _
instance Show UpdateNodeParams where show = genericShow
......@@ -38,6 +40,11 @@ instance JSON.WriteForeign UpdateNodeParams where
writeImpl (UpdateNodeParamsPhylo { methodPhylo }) =
JSON.writeImpl { type: "UpdateNodePhylo"
, config: methodPhylo }
writeImpl (UpdateNodeParamsLink { methodLink: LinkNodeReq { id, nodeType }}) =
JSON.writeImpl { type: "LinkNodeReq"
, id
, nodeType
}
----------------------------------------------------------------------
data Method = Basic | Advanced | WithModel
......@@ -105,3 +112,12 @@ instance JSON.ReadForeign Charts where readImpl = JSONG.enumSumRep
instance JSON.WriteForeign Charts where
writeImpl All = JSON.writeImpl $ "AllCharts"
writeImpl f = JSON.writeImpl $ show f
----------------------------------------------------------------------
newtype LinkNodeReq = LinkNodeReq { nodeType :: GT.NodeType, id :: GT.ID }
derive instance Eq LinkNodeReq
derive instance Generic LinkNodeReq _
instance Show LinkNodeReq where show = genericShow
derive newtype instance JSON.ReadForeign LinkNodeReq
derive newtype instance JSON.WriteForeign LinkNodeReq
......@@ -3,6 +3,7 @@ module Gargantext.Components.Forest.Tree.Node.Action.Upload where
import Gargantext.Prelude
import Affjax.RequestBody (blob)
import Data.Array (singleton)
import Data.Either (Either, fromRight')
import Data.Eq.Generic (genericEq)
import Data.Foldable (intercalate)
......@@ -16,6 +17,8 @@ import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (Aff, launchAff)
import Effect.Class (liftEffect)
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..))
import Gargantext.Components.Forest.Tree.Node.Action (Props)
import Gargantext.Components.Forest.Tree.Node.Action.Types (Action(..))
import Gargantext.Components.Forest.Tree.Node.Action.Upload.Types (FileFormat(..), FileType(..), UploadFileBlob(..), readUFBAsBase64, readUFBAsText)
......@@ -60,7 +63,7 @@ actionUploadCpt = here.component "actionUpload" cpt where
pure $ uploadTermListView { dispatch, id, nodeType: GT.NodeList, session } []
cpt props@{ nodeType: NodeFrameCalc } _ = pure $ uploadFrameCalcView props []
cpt props@{ nodeType: Annuaire, dispatch, id, session } _ = do
pure $ uploadFileView { dispatch, id, nodeType: GT.Annuaire, session }
pure $ uploadListView { dispatch, id, nodeType: GT.Annuaire, session }
cpt props@{ nodeType: _ } _ = pure $ actionUploadOther props []
{-
......@@ -165,18 +168,19 @@ uploadFileViewCpt = here.component "uploadFileView" cpt
]
pure $ panel bodies footer
onChangeContents :: forall e. T.Box (Maybe UploadFile) -> E.SyntheticEvent_ e -> Effect Unit
onChangeContents mFile e = do
let mF = R2.inputFileNameWithBlob 0 e
E.preventDefault e
E.stopPropagation e
case mF of
Nothing -> pure unit
Just {blob, name} -> void $ launchAff do
--contents <- readAsText blob
--contents <- readAsDataURL blob
liftEffect $ do
T.write_ (Just $ {blob: UploadFileBlob blob, name}) mFile
onChangeContents :: forall e. T.Box (Maybe UploadFile) -> E.SyntheticEvent_ e -> Effect Unit
onChangeContents mFile e = do
let mF = R2.inputFileNameWithBlob 0 e
E.preventDefault e
E.stopPropagation e
case mF of
Nothing -> pure unit
Just {blob, name} -> void $ launchAff do
--contents <- readAsText blob
--contents <- readAsDataURL blob
liftEffect $ do
T.write_ (Just $ {blob: UploadFileBlob blob, name}) mFile
type UploadButtonProps =
......@@ -263,6 +267,149 @@ uploadButtonCpt = here.component "uploadButton" cpt
T.write_ false onPendingBox
dispatch ClosePopover
uploadListView :: R2.Leaf Props
uploadListView = R2.leafComponent uploadListViewCpt
uploadListViewCpt :: R.Component Props
uploadListViewCpt = here.component "uploadListView" cpt where
cpt { dispatch, session } _ = do
-- States
mFile
<- T.useBox (Nothing :: Maybe UploadFile)
fileType
<- T.useBox CSV
fileFormat /\ fileFormatBox
<- R2.useBox' Plain
lang /\ langBox
<- R2.useBox' EN
selection
<- T.useBox ListSelection.MyListsFirst
-- Render
pure $
panel
-- Body
[
-- Upload
H.div
{ className: "form-group" }
[
H.div
{ className: "form-group__label" }
[
B.label_ $
"Upload file"
]
,
H.div
{ className: "form-group__field" }
[
H.input
{ type: "file"
, className: "form-control"
, placeholder: "Choose file"
, on: { change: onChangeContents mFile }
}
]
]
,
-- Format
H.div
{ className: "form-group" }
[
H.div
{ className: "form-group__label" }
[
B.label_ $
"File format"
]
,
H.div
{ className: "form-group__field d-flex justify-content-between" }
[
B.formSelect
{ callback: \_ -> pure unit
, value: show CSV
, status: Disabled
, className: "col-5"
}
[
H.option
{ value: show CSV }
[ H.text $ show CSV ]
]
,
B.formSelect'
{ callback: flip T.write_ fileFormatBox
, value: fileFormat
, list: [ Plain, ZIP ]
, className: "col-5"
}
[]
]
]
,
-- Lang
H.div
{ className: "form-group" }
[
H.div
{ className: "form-group__label" }
[
B.label_ $
"File lang"
]
,
H.div
{ className: "form-group__field" }
[
B.formSelect'
{ callback: flip T.write_ langBox
, value: lang
, list: [ EN, FR, No_extraction, Universal ]
}
[]
]
]
,
-- List selection
H.div
{ className: "form-group" }
[
H.div
{ className: "form-group__label" }
[
B.label_ $
"List selection"
]
,
H.div
{ className: "form-group__field" }
[
ListSelection.selection
{ selection
, session
} []
]
]
]
-- Footer
(
H.div
{}
[
uploadButton
{ dispatch
, fileFormat: fileFormatBox
, fileType
, lang: langBox
, mFile
, selection
, nodeType: GT.Annuaire
} []
]
)
-- START File Type View
type FileTypeProps =
( dispatch :: Action -> Aff Unit
......
......@@ -5,6 +5,7 @@ import Gargantext.Prelude
import Data.Array as A
import Data.Either (Either)
import Data.Maybe (Maybe(..))
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.Forest.Tree.Node.Tools (formChoiceSafe)
import Gargantext.Components.ListSelection.Types (NodeSimple(..), Selection(..), selectedListIds)
import Gargantext.Config.REST (RESTError(..), AffRESTError)
......@@ -30,17 +31,21 @@ selection = R.createElement selectionCpt
selectionCpt :: R.Component Props
selectionCpt = here.component "selection" cpt where
cpt { selection, session } _ = do
selection' <- R2.useLive' selection
pure $ H.div { className: "list-selection" }
[ formChoiceSafe { items: [ MyListsFirst
, OtherListsFirst
, SelectedLists [] ]
, default: MyListsFirst
, callback: setSelection
, print: show } []
, selectedIds { selection, session } []
[
B.formSelect'
{ callback: flip T.write_ selection
, value: selection'
, list: [ MyListsFirst
, OtherListsFirst
, SelectedLists []
]
}
[]
,
selectedIds { selection, session } []
]
where
setSelection val = T.write_ val selection
selectedIds :: R2.Component Props
selectedIds = R.createElement selectedIdsCpt
......@@ -185,4 +190,3 @@ renderListElementCpt = here.component "renderListElement" cpt where
else SelectedLists (A.cons id lst)
f x = x
T.modify_ f selection
......@@ -5,7 +5,7 @@
$self: &;
position: relative;
margin-bottom: space-x(3);
margin-bottom: $form-group-margin-bottom;
&__label {
padding-bottom: space-x(0.5);
......
......@@ -150,8 +150,7 @@
}
&__group {
// @TODO: generic "form-group" margin-bottom constant
margin-bottom: space-x(3);
margin-bottom: $form-group-margin-bottom;
}
.b-fieldset {
......
/*! Themestr.app `Darkster` Bootstrap 4.3.1 theme */
// Bootstrap pre-requiring
//------------------------
@import '../../../node_modules/bootstrap/scss/functions';
@import '../../../node_modules/bootstrap/scss/variables';
@import '../../../node_modules/bootstrap/scss/mixins';
// Project pre-requiring
//----------------------
@import '../_abstract';
@import '../_modules';
// Theme variable overrides
//-------------------------
......@@ -40,6 +51,9 @@ $theme-colors: (
$gray-150: #FAFAFA;
$gray-175: #F0F0F0;
// Layout
$form-group-margin-bottom: space-x(3);
// Misc...
$enable-shadows:true;
$gray-300:#000000;
......@@ -102,11 +116,9 @@ $breadcrumb-active-color:$gray-500;
@import '../../../node_modules/bootstrap/scss/bootstrap';
// Project variables
//------------------
// Project sheets
//---------------
@import '../_abstract';
@import '../_modules';
@import "../_components";
@import "../_base";
@import "../_legacy";
......
/*! Bootstrap `Default` https://getbootstrap.com/docs/4.6 */
// Bootstrap pre-requiring
//------------------------
@import '../../../node_modules/bootstrap/scss/functions';
@import '../../../node_modules/bootstrap/scss/variables';
@import '../../../node_modules/bootstrap/scss/mixins';
// Project pre-requiring
//----------------------
// (?) Normally "abstract" and "modules" have to be MANUALLY IMPORTED each
// times their content is being used
//
// However, with Bootstrap non "@use" API-ready, and multiple themes
// management, we have to stick the old "@import"
//
// It also implies that, if tomorrow, transition to "@import" to "@use" API
// has to be made, every manual import must be included (eg. by checking
// the `modules/form` file, and check where its mixin `inputError` has been
// being used
@import '../_abstract';
@import '../_modules';
// Theme variable overrides
//-------------------------
......@@ -19,26 +42,17 @@ $theme-colors: (
$gray-150: #FAFAFA;
$gray-175: #F0F0F0;
// Layout
$form-group-margin-bottom: space-x(3);
// (importing Bootstrap)
@import '../../../node_modules/bootstrap/scss/bootstrap';
// Project variables
//------------------
// Project sheets
//---------------
// (?) Normally "abstract" and "modules" have to be MANUALLY IMPORTED each
// times their content is being used
//
// However, with Bootstrap non "@use" API-ready, and multiple themes
// management, we have to stick the old "@import"
//
// It also implies that, if tomorrow, transition to "@import" to "@use" API
// has to be made, every manual import must be included (eg. by checking
// the `modules/form` file, and check where its mixin `inputError` has been
// being used
@import '../_abstract';
@import '../_modules';
@import '../_components';
@import '../_base';
@import '../_legacy';
......
/*! Themestr.app `Greyson` Bootstrap 4.3.1 theme */
/* https://github.com/ThemesGuide/bootstrap-themes/blob/master/greyson/ */
// Bootstrap pre-requiring
//------------------------
@import '../../../node_modules/bootstrap/scss/functions';
@import '../../../node_modules/bootstrap/scss/variables';
@import '../../../node_modules/bootstrap/scss/mixins';
// Project pre-requiring
//----------------------
@import '../_abstract';
@import '../_modules';