Commit 77d76224 authored by Mudada's avatar Mudada

Merge branch 'master' into iframe-interaction

parents 0f863d28 1b7d1e5a
...@@ -1475,6 +1475,21 @@ ...@@ -1475,6 +1475,21 @@
"repo": "https://github.com/justinwoo/purescript-makkori.git", "repo": "https://github.com/justinwoo/purescript-makkori.git",
"version": "v1.0.0" "version": "v1.0.0"
}, },
"markdown": {
"dependencies": [
"precise"
],
"repo": "https://github.com/poorscript/purescript-markdown",
"version": "master"
},
"markdown-smolder": {
"dependencies": [
"markdown",
"smolder"
],
"repo": "https://github.com/poorscript/purescript-markdown-smolder",
"version": "master"
},
"math": { "math": {
"dependencies": [], "dependencies": [],
"repo": "https://github.com/purescript/purescript-math.git", "repo": "https://github.com/purescript/purescript-math.git",
...@@ -2041,6 +2056,13 @@ ...@@ -2041,6 +2056,13 @@
"repo": "https://github.com/purescript-node/purescript-posix-types.git", "repo": "https://github.com/purescript-node/purescript-posix-types.git",
"version": "v4.0.0" "version": "v4.0.0"
}, },
"precise": {
"dependencies": [
"prelude"
],
"repo": "https://github.com/purescript-contrib/purescript-precise",
"version": "master"
},
"prelude": { "prelude": {
"dependencies": [], "dependencies": [],
"repo": "https://github.com/purescript/purescript-prelude.git", "repo": "https://github.com/purescript/purescript-prelude.git",
......
.code-editor .toolbar {
display: flex;
justify-content: flex-start;
width: 100%;
}
.code-editor .editor {
display: flex;
width: 100%;
}
.code-editor .editor .code {
flex-grow: 1;
}
.code-editor .editor .code code {
background-color: #f7f7f9;
color: #000;
display: block;
height: 500px;
}
.code-editor .editor .html {
flex-grow: 2;
margin-left: 25px;
padding-left: 25px;
}
.code-editor .editor .html ul li {
list-style: disc !important;
}
.code-editor .editor .html ol li {
list-style: decimal !important;
}
/*# sourceMappingURL=CodeEditor.css.map */
.code-editor
.toolbar
display: flex
justify-content: flex-start
width: 100%
.editor
display: flex
width: 100%
.code
flex-grow: 1
code
background-color: #f7f7f9
color: #000
display: block
height: 500px
.html
flex-grow: 2
margin-left: 25px
padding-left: 25px
ul
li
list-style: disc !important
ol
li
list-style: decimal !important
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"rebuild-set": "spago psc-package-insdhall", "rebuild-set": "spago psc-package-insdhall",
"install-ps": "psc-package install", "install-ps": "psc-package install",
"compile": "pulp --psc-package build", "compile": "pulp --psc-package build",
"prebuild": "yarn compile",
"build": "pulp --psc-package browserify -t dist/bundle.js", "build": "pulp --psc-package browserify -t dist/bundle.js",
"sass": "sass dist/styles/", "sass": "sass dist/styles/",
"predev": "yarn prestart", "predev": "yarn prestart",
...@@ -50,7 +51,8 @@ ...@@ -50,7 +51,8 @@
"psc": "^0.1.1", "psc": "^0.1.1",
"psc-package": "^3.0.1", "psc-package": "^3.0.1",
"pulp": "^13.0.0", "pulp": "^13.0.0",
"purescript": "^0.13.5", "purescript": "^0.13.6",
"purescript-language-server": "^0.12.7",
"purs-loader": "^3.3.0", "purs-loader": "^3.3.0",
"react-testing-library": "^6.1.2", "react-testing-library": "^6.1.2",
"sass": "^1.23.7", "sass": "^1.23.7",
...@@ -59,6 +61,7 @@ ...@@ -59,6 +61,7 @@
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"uglify-js": "^3.4.9", "uglify-js": "^3.4.9",
"uglifyify": "^5.0.1", "uglifyify": "^5.0.1",
"vscode-languageserver": "^6.0.0",
"webpack": "^4.26.0", "webpack": "^4.26.0",
"webpack-cli": "^3.1.2", "webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10", "webpack-dev-server": "^3.1.10",
......
...@@ -191,6 +191,27 @@ let additions = ...@@ -191,6 +191,27 @@ let additions =
[ "aff", "arraybuffer-types", "web-file", "web-html" ] [ "aff", "arraybuffer-types", "web-file", "web-html" ]
"https://github.com/nwolverson/purescript-dom-filereader" "https://github.com/nwolverson/purescript-dom-filereader"
"v5.0.0" "v5.0.0"
, markdown =
mkPackage
[ "precise" ]
{- "https://github.com/slamdata/purescript-markdown"
"v12.0.0" -}
"https://github.com/poorscript/purescript-markdown"
"master"
, markdown-smolder =
mkPackage
[ "markdown"
, "smolder" ]
{- "https://github.com/hgiasac/purescript-markdown-smolder"
"v2.0.1" -}
"https://github.com/poorscript/purescript-markdown-smolder"
"master"
, precise =
mkPackage
[ "prelude" ]
"https://github.com/purescript-contrib/purescript-precise"
{- "v3.0.1" -}
"master"
, reactix = , reactix =
mkPackage mkPackage
[ "aff" [ "aff"
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"globals", "globals",
"integers", "integers",
"js-timers", "js-timers",
"markdown-smolder",
"math", "math",
"maybe", "maybe",
"nonempty", "nonempty",
...@@ -28,6 +29,7 @@ ...@@ -28,6 +29,7 @@
"read", "read",
"routing", "routing",
"sequences", "sequences",
"smolder",
"spec-discovery", "spec-discovery",
"spec-quickcheck", "spec-quickcheck",
"string-parsers", "string-parsers",
......
...@@ -44,7 +44,7 @@ appCpt = R.hooksComponent "G.C.App.app" cpt where ...@@ -44,7 +44,7 @@ appCpt = R.hooksComponent "G.C.App.app" cpt where
cpt _ _ = do cpt _ _ = do
sessions <- useSessions sessions <- useSessions
route <- useHashRouter router Home route <- useHashRouter router Home
showLogin <- R.useState' false showLogin <- R.useState' false
showCorpus <- R.useState' false showCorpus <- R.useState' false
......
module Gargantext.Components.CodeEditor where
import Data.Argonaut.Parser (jsonParser)
import Data.Either (either, Either(..))
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple (fst, snd)
import Data.Tuple.Nested ((/\))
import DOM.Simple.Console (log, log2)
import DOM.Simple.Types (Element)
import Effect (Effect)
import FFI.Simple ((.=), delay)
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Eq (genericEq)
import Data.Generic.Rep.Show (genericShow)
import Reactix as R
import Reactix.DOM.HTML as H
import Text.Markdown.SlamDown.Parser (parseMd)
import Text.Markdown.SlamDown.Smolder as MD
import Text.Markdown.SlamDown.Syntax (SlamDownP(..))
import Text.Smolder.Renderer.String (render)
import Gargantext.Prelude
import Gargantext.Utils.Reactix as R2
type Code = String
type Html = String
type Error = String
data CodeType = JSON | Markdown
derive instance genericCodeType :: Generic CodeType _
instance eqCodeType :: Eq CodeType where
eq = genericEq
instance showCodeType :: Show CodeType where
show = genericShow
data ViewType = Code | Preview | Both
derive instance genericViewType :: Generic ViewType _
instance eqViewType :: Eq ViewType where
eq = genericEq
instance showViewType :: Show ViewType where
show = genericShow
type Props =
( code :: String
, defaultCodeType :: CodeType
, onChange :: String -> Effect Unit
)
compile :: CodeType -> Code -> Either Error Html
compile JSON code = result
where
parsedE = jsonParser code
result = case parsedE of
Left err -> Left err
Right parsed -> Right $ "<pre>" <> (R2.stringify parsed 2) <> "</pre>"
compile Markdown code = Right $ compileMd code
-- TODO Replace with markdown-it?
-- https://pursuit.purescript.org/packages/purescript-markdown-it
compileMd' :: forall e. MD.ToMarkupOptions e -> String -> String
compileMd' options input =
either identity (MD.toMarkup' options >>> render)
(parseMd input :: Either String (SlamDownP String))
compileMd :: String -> String
compileMd = compileMd' MD.defaultToMarkupOptions
codeEditor :: Record Props -> R.Element
codeEditor p = R.createElement codeEditorCpt p []
codeEditorCpt :: R.Component Props
codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt
where
cpt {code, defaultCodeType, onChange} _ = do
htmlRef <- R.useRef null
codeRef <- R.useRef null
editorCodeRef <- R.useRef code
codeType <- R.useState' defaultCodeType
error <- R.useState' Nothing
viewType <- R.useState' Both
-- Initial rendering of elements with given data
-- Note: delay is necessary here, otherwise initially the HTML won't get
-- rendered (mDiv is still null)
R.useEffectOnce $ delay unit $ \_ -> do
_ <- renderHtml (fst codeType) code htmlRef error
pure $ pure unit
R.useEffectOnce $ delay unit $ \_ -> do
let mCodeEl = toMaybe $ R.readRef codeRef
case mCodeEl of
Nothing -> pure $ pure unit
Just codeEl -> do
_ <- pure $ (codeEl .= "innerText") code
pure $ pure unit
pure $ H.div { className: "code-editor" } [
H.div { className: "row toolbar" } [
codeTypeSelector {codeType, onChange: onChangeCodeType editorCodeRef htmlRef error}
, viewTypeSelector {state: viewType}
]
, H.div { className: "row error" } [
errorComponent {error}
]
, H.div { className: "row editor" } [
H.div { className: "code " <> (codeHidden $ fst viewType) } [
H.code { className: ""
, contentEditable: "true"
, ref: codeRef
, rows: 30
, on: { input: onEditChange (fst codeType) codeRef htmlRef editorCodeRef error }
} []
]
, H.div { ref: htmlRef, className: "html " <> (previewHidden $ fst viewType) } []
]
]
codeHidden :: ViewType -> String
codeHidden Code = ""
codeHidden Both = ""
codeHidden _ = "hidden"
previewHidden :: ViewType -> String
previewHidden Preview = ""
previewHidden Both = ""
previewHidden _ = "hidden"
-- Handle rerendering of preview when viewType changed
onChangeCodeType :: R.Ref String -> R.Ref (Nullable Element) -> R.State (Maybe Error) -> CodeType -> Effect Unit
onChangeCodeType editorCodeRef htmlRef error codeType = do
_ <- renderHtml codeType (R.readRef editorCodeRef) htmlRef error
pure unit
onEditChange :: forall e. CodeType -> R.Ref (Nullable Element) -> R.Ref (Nullable Element) -> R.Ref String -> R.State (Maybe Error) -> e -> Effect Unit
onEditChange codeType codeRef htmlRef editorCodeRef error e = do
log2 "[onChange] e" e
let mCode = toMaybe $ R.readRef codeRef
case mCode of
Nothing -> log "[onChange] mCode = Nothing"
Just code -> do
R.setRef editorCodeRef $ R2.innerText code
pure unit
renderHtml codeType (R.readRef editorCodeRef) htmlRef error
renderHtml :: CodeType -> Code -> R.Ref (Nullable Element) -> R.State (Maybe Error) -> Effect Unit
renderHtml codeType code htmlRef (_ /\ setError) =
case (toMaybe $ R.readRef htmlRef) of
Nothing -> pure unit
Just htmlEl -> do
case compile codeType code of
Left err -> do
setError $ const $ Just err
Right compiled -> do
setError $ const Nothing
_ <- pure $ (htmlEl .= "innerHTML") compiled
pure unit
type ErrorComponentProps =
(
error :: R.State (Maybe Error)
)
errorComponent :: Record ErrorComponentProps -> R.Element
errorComponent p = R.createElement errorComponentCpt p []
errorComponentCpt :: R.Component ErrorComponentProps
errorComponentCpt = R.hooksComponent "G.C.ErrorComponent" cpt
where
cpt {error: (Nothing /\ _)} _ = pure $ H.div {} []
cpt {error: ((Just error) /\ _)} _ = do
pure $ H.div { className: "text-danger" } [ H.text error ]
type CodeTypeSelectorProps =
(
codeType :: R.State CodeType
, onChange :: CodeType -> Effect Unit
)
codeTypeSelector :: Record CodeTypeSelectorProps -> R.Element
codeTypeSelector p = R.createElement codeTypeSelectorCpt p []
codeTypeSelectorCpt :: R.Component CodeTypeSelectorProps
codeTypeSelectorCpt = R.hooksComponent "G.C.CodeTypeSelector" cpt
where
cpt {codeType, onChange} _ = do
pure $ R2.select { className: "form-control"
, on: { change: onSelectChange codeType onChange }
, style: { width: "150px" }
, value: show $ fst codeType }
(option <$> [JSON, Markdown])
option :: CodeType -> R.Element
option value = H.option { value: show value } [ H.text $ show value ]
onSelectChange :: forall e. R.State CodeType -> (CodeType -> Effect Unit) -> e -> Effect Unit
onSelectChange (_ /\ setCodeType) onChange e = do
let codeType = case value of
"JSON" -> JSON
"Markdown" -> Markdown
_ -> Markdown
setCodeType $ const codeType
onChange codeType
where
value = R2.unsafeEventValue e
type ViewTypeSelectorProps =
(
state :: R.State ViewType
)
viewTypeSelector :: Record ViewTypeSelectorProps -> R.Element
viewTypeSelector p = R.createElement viewTypeSelectorCpt p []
viewTypeSelectorCpt :: R.Component ViewTypeSelectorProps
viewTypeSelectorCpt = R.hooksComponent "G.C.ViewTypeSelector" cpt
where
cpt {state} _ =
pure $ H.div { className: "btn-group" } [
viewTypeButton Code state
, viewTypeButton Both state
, viewTypeButton Preview state
]
viewTypeButton viewType (state /\ setState) =
H.label {
className: "btn btn-default" <> active
, on: { click: onClick }
} [
H.i { className: "glyphicon " <> (icon viewType) } []
]
where
active = if viewType == state then " active" else ""
onClick _ = do
setState $ const viewType
icon Preview = "glyphicon-eye-open"
icon Both = "glyphicon-transfer"
icon Code = "glyphicon-pencil"
module Gargantext.Components.Forest.Tree where module Gargantext.Components.Forest.Tree where
import Data.Array as A
import DOM.Simple.Console (log2) import DOM.Simple.Console (log2)
import Data.Maybe (Maybe) import Data.Maybe (Maybe)
-- import Data.Newtype (class Newtype) -- import Data.Newtype (class Newtype)
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import Effect.Aff (Aff) import Effect.Aff (Aff)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Data.Array as Array import Reactix as R
import Reactix.DOM.HTML as H
import Gargantext.Prelude
import Gargantext.Components.Forest.Tree.Node.Action import Gargantext.Components.Forest.Tree.Node.Action
import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadFile) import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadFile)
import Gargantext.Components.Forest.Tree.Node.Box (nodeMainSpan) import Gargantext.Components.Forest.Tree.Node.Box (nodeMainSpan)
...@@ -14,9 +19,7 @@ import Gargantext.Ends (Frontends) ...@@ -14,9 +19,7 @@ import Gargantext.Ends (Frontends)
import Gargantext.Components.Loader (loader) import Gargantext.Components.Loader (loader)
import Gargantext.Routes (AppRoute) import Gargantext.Routes (AppRoute)
import Gargantext.Sessions (Session) import Gargantext.Sessions (Session)
import Prelude (Unit, bind, discard, map, pure, void, ($), (+), (<>)) import Gargantext.Types (AsyncTask(..))
import Reactix as R
import Reactix.DOM.HTML as H
------------------------------------------------------------------------ ------------------------------------------------------------------------
type Props = ( root :: ID type Props = ( root :: ID
...@@ -55,7 +58,7 @@ loadedTreeView reload p = R.createElement el p [] ...@@ -55,7 +58,7 @@ loadedTreeView reload p = R.createElement el p []
where where
el = R.hooksComponent "LoadedTreeView" cpt el = R.hooksComponent "LoadedTreeView" cpt
cpt {tree, mCurrentRoute, session, frontends} _ = do cpt {tree, mCurrentRoute, session, frontends} _ = do
treeState <- R.useState' {tree} treeState <- R.useState' {tree, asyncTasks: []}
pure $ H.div {className: "tree"} pure $ H.div {className: "tree"}
[ toHtml reload treeState session frontends mCurrentRoute ] [ toHtml reload treeState session frontends mCurrentRoute ]
...@@ -67,7 +70,7 @@ toHtml :: R.State Reload ...@@ -67,7 +70,7 @@ toHtml :: R.State Reload
-> Frontends -> Frontends
-> Maybe AppRoute -> Maybe AppRoute
-> R.Element -> R.Element
toHtml reload treeState@({tree: (NTree (LNode {id, name, nodeType}) ary)} /\ _) session frontends mCurrentRoute = R.createElement el {} [] toHtml reload treeState@(ts@{tree: (NTree (LNode {id, name, nodeType}) ary), asyncTasks} /\ setTreeState) session frontends mCurrentRoute = R.createElement el {} []
where where
el = R.hooksComponent "NodeView" cpt el = R.hooksComponent "NodeView" cpt
pAction = performAction session reload treeState pAction = performAction session reload treeState
...@@ -79,11 +82,21 @@ toHtml reload treeState@({tree: (NTree (LNode {id, name, nodeType}) ary)} /\ _) ...@@ -79,11 +82,21 @@ toHtml reload treeState@({tree: (NTree (LNode {id, name, nodeType}) ary)} /\ _)
pure $ H.ul {} pure $ H.ul {}
[ H.li {} [ H.li {}
( [ nodeMainSpan pAction {id, name, nodeType, mCurrentRoute} folderOpen session frontends ] ( [ nodeMainSpan pAction { id
, asyncTasks
, mCurrentRoute
, name
, nodeType
, onAsyncTaskFinish
} folderOpen session frontends ]
<> childNodes session frontends reload folderOpen mCurrentRoute ary <> childNodes session frontends reload folderOpen mCurrentRoute ary
) )
] ]
onAsyncTaskFinish (AsyncTask {id}) = setTreeState $ const $ ts { asyncTasks = newAsyncTasks }
where
newAsyncTasks = A.filter (\(AsyncTask {id: id'}) -> id /= id') asyncTasks
childNodes :: Session childNodes :: Session
-> Frontends -> Frontends
...@@ -95,15 +108,15 @@ childNodes :: Session ...@@ -95,15 +108,15 @@ childNodes :: Session
childNodes _ _ _ _ _ [] = [] childNodes _ _ _ _ _ [] = []
childNodes _ _ _ (false /\ _) _ _ = [] childNodes _ _ _ (false /\ _) _ _ = []
childNodes session frontends reload (true /\ _) mCurrentRoute ary = childNodes session frontends reload (true /\ _) mCurrentRoute ary =
map (\ctree -> childNode {tree: ctree}) $ sorted ary map (\ctree -> childNode {tree: ctree, asyncTasks: []}) $ sorted ary
where where
sorted :: Array FTree -> Array FTree sorted :: Array FTree -> Array FTree
sorted = Array.sortWith (\(NTree (LNode {id}) _) -> id) sorted = A.sortWith (\(NTree (LNode {id}) _) -> id)
childNode :: Tree -> R.Element childNode :: Tree -> R.Element
childNode props = R.createElement el props [] childNode props = R.createElement el props []
el = R.hooksComponent "ChildNodeView" cpt el = R.hooksComponent "ChildNodeView" cpt
cpt {tree} _ = do cpt {tree, asyncTasks} _ = do
treeState <- R.useState' {tree} treeState <- R.useState' {tree, asyncTasks}
pure $ toHtml reload treeState session frontends mCurrentRoute pure $ toHtml reload treeState session frontends mCurrentRoute
...@@ -124,7 +137,7 @@ performAction session (_ /\ setReload) (s@{tree: NTree (LNode {id}) _} /\ setTre ...@@ -124,7 +137,7 @@ performAction session (_ /\ setReload) (s@{tree: NTree (LNode {id}) _} /\ setTre
void $ createNode session id $ CreateValue {name, nodeType} void $ createNode session id $ CreateValue {name, nodeType}
liftEffect $ setReload (_ + 1) liftEffect $ setReload (_ + 1)
performAction session _ ({tree: NTree (LNode {id}) _} /\ _) (UploadFile fileType contents) = do performAction session _ ({tree: NTree (LNode {id}) _} /\ setTree) (UploadFile fileType contents) = do
hashes <- uploadFile session id fileType contents task <- uploadFile session id fileType contents
liftEffect $ log2 "uploaded:" hashes liftEffect $ setTree $ \t@{asyncTasks} -> t { asyncTasks = A.cons task asyncTasks }
liftEffect $ log2 "uploaded, task:" task
...@@ -9,7 +9,7 @@ import Data.Newtype (class Newtype) ...@@ -9,7 +9,7 @@ import Data.Newtype (class Newtype)
import Effect.Aff (Aff) import Effect.Aff (Aff)
import Gargantext.Routes (SessionRoute(..)) import Gargantext.Routes (SessionRoute(..))
import Gargantext.Sessions (Session, get, put, post, delete) import Gargantext.Sessions (Session, get, put, post, delete)
import Gargantext.Types (NodeType(..)) import Gargantext.Types (NodeType(..), AsyncTask(..))
import Prelude hiding (div) import Prelude hiding (div)
data Action = Submit String data Action = Submit String
...@@ -85,7 +85,7 @@ instance encodeJsonCreateValue :: EncodeJson CreateValue where ...@@ -85,7 +85,7 @@ instance encodeJsonCreateValue :: EncodeJson CreateValue where
data NTree a = NTree a (Array (NTree a)) data NTree a = NTree a (Array (NTree a))
type FTree = NTree LNode type FTree = NTree LNode
type Tree = { tree :: FTree } type Tree = { tree :: FTree, asyncTasks :: Array AsyncTask }
instance ntreeFunctor :: Functor NTree where instance ntreeFunctor :: Functor NTree where
map f (NTree x ary) = NTree (f x) (map (map f) ary) map f (NTree x ary) = NTree (f x) (map (map f) ary)
......
module Gargantext.Components.Forest.Tree.Node.Action.Upload where module Gargantext.Components.Forest.Tree.Node.Action.Upload where
import Prelude (class Show, Unit, const, discard, map, pure, show, ($), (<>), bind, void) import Prelude (class Show, Unit, bind, const, discard, map, pure, show, void, ($))
import Data.Maybe (Maybe(..), fromJust) import Data.Maybe (Maybe(..), fromJust)
import Data.Newtype (class Newtype) import Data.Newtype (class Newtype)
import Data.Tuple (Tuple) import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import Effect.Aff (Aff, launchAff) import Effect.Aff (Aff, launchAff)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Effect.Uncurried (mkEffectFn1)
import Partial.Unsafe (unsafePartial) import Partial.Unsafe (unsafePartial)
import React.SyntheticEvent as E import React.SyntheticEvent as E
import Reactix as R import Reactix as R
import Reactix.DOM.HTML as H import Reactix.DOM.HTML as H
import URI.Extra.QueryPairs as QP import URI.Extra.QueryPairs as QP
import URI.Query as Q
import Web.File.FileReader.Aff (readAsText) import Web.File.FileReader.Aff (readAsText)
import Gargantext.Components.Forest.Tree.Node.Action import Gargantext.Components.Forest.Tree.Node.Action
import Gargantext.Routes (SessionRoute(..)) import Gargantext.Routes (SessionRoute(..))
import Gargantext.Sessions (Session, postWwwUrlencoded) import Gargantext.Sessions (Session, postWwwUrlencoded)
import Gargantext.Types (class ToQuery, toQuery, NodeType(..)) import Gargantext.Types (class ToQuery, AsyncTask, NodeType(..))
import Gargantext.Utils (id) import Gargantext.Utils (id)
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
...@@ -44,16 +42,17 @@ uploadFileViewCpt d = R.hooksComponent "UploadFileView" cpt ...@@ -44,16 +42,17 @@ uploadFileViewCpt d = R.hooksComponent "UploadFileView" cpt
, H.div {} [ H.input {type: "file", placeholder: "Choose file", on: {change: onChangeContents mContents}} ] , H.div {} [ H.input {type: "file", placeholder: "Choose file", on: {change: onChangeContents mContents}} ]
, H.div {} , H.div {}
[ R2.select {className: "col-md-12 form-control" [ R2.select {className: "col-md-12 form-control"
, onChange: onChangeFileType fileType} , on: {change: onChangeFileType fileType}
}
(map renderOption [CSV, PresseRIS]) (map renderOption [CSV, PresseRIS])
] ]
, H.div {} , H.div {}
[ uploadButton id mContents fileType ] [ uploadButton d id mContents fileType ]
] ]
renderOption opt = H.option {} [ H.text $ show opt ] renderOption opt = H.option {} [ H.text $ show opt ]
onChangeContents (mContents /\ setMContents) = mkEffectFn1 $ \e -> do onChangeContents (mContents /\ setMContents) e = do
blob <- R2.inputFileBlob e blob <- R2.inputFileBlob e
E.preventDefault e E.preventDefault e
E.stopPropagation e E.stopPropagation e
...@@ -62,24 +61,24 @@ uploadFileViewCpt d = R.hooksComponent "UploadFileView" cpt ...@@ -62,24 +61,24 @@ uploadFileViewCpt d = R.hooksComponent "UploadFileView" cpt
liftEffect $ do liftEffect $ do
setMContents $ const $ Just $ UploadFileContents contents setMContents $ const $ Just $ UploadFileContents contents
onChangeFileType (fileType /\ setFileType) = mkEffectFn1 $ \e -> do onChangeFileType (fileType /\ setFileType) e = do
setFileType $ const $ unsafePartial $ fromJust $ readFileType $ R2.unsafeEventValue e setFileType $ const $ unsafePartial $ fromJust $ readFileType $ R2.unsafeEventValue e
uploadButton :: Int -> R.State (Maybe UploadFileContents) -> R.State FileType -> R.Element uploadButton :: (Action -> Aff Unit) -> Int -> R.State (Maybe UploadFileContents) -> R.State FileType -> R.Element
uploadButton id (mContents /\ setMContents) (fileType /\ setFileType) = uploadButton d id (mContents /\ setMContents) (fileType /\ setFileType) =
H.button {className: "btn btn-primary", disabled, onClick} [ H.text "Upload" ] H.button {className: "btn btn-primary", disabled, on: {click: onClick}} [ H.text "Upload" ]
where where
disabled = case mContents of disabled = case mContents of
Nothing -> "1" Nothing -> "1"
Just _ -> "" Just _ -> ""
onClick = mkEffectFn1 $ \e -> do onClick e = do
let contents = unsafePartial $ fromJust mContents let contents = unsafePartial $ fromJust mContents
void $ launchAff do void $ launchAff do
_ <- d $ UploadFile fileType contents _ <- d $ UploadFile fileType contents
liftEffect $ do liftEffect $ do
setMContents $ const $ Nothing setMContents $ const $ Nothing
setFileType $ const $ CSV setFileType $ const $ CSV
-- START File Type View -- START File Type View
type FileTypeProps = type FileTypeProps =
...@@ -115,9 +114,10 @@ fileTypeView d p (Just (DroppedFile {contents, fileType}) /\ setDroppedFile) (_ ...@@ -115,9 +114,10 @@ fileTypeView d p (Just (DroppedFile {contents, fileType}) /\ setDroppedFile) (_
[ H.h5 {} [H.text "Choose file type"] ] [ H.h5 {} [H.text "Choose file type"] ]
, H.div {className: "col-md-2"} , H.div {className: "col-md-2"}
[ H.a {className: "btn glyphitem glyphicon glyphicon-remove-circle" [ H.a {className: "btn glyphitem glyphicon glyphicon-remove-circle"
, onClick: mkEffectFn1 $ \_ -> do , on: {click: \_ -> do
setDroppedFile $ const Nothing setDroppedFile $ const Nothing
setIsDragOver $ const false setIsDragOver $ const false
}
, title: "Close"} [] , title: "Close"} []
] ]
] ]
...@@ -125,11 +125,12 @@ fileTypeView d p (Just (DroppedFile {contents, fileType}) /\ setDroppedFile) (_ ...@@ -125,11 +125,12 @@ fileTypeView d p (Just (DroppedFile {contents, fileType}) /\ setDroppedFile) (_
panelBody = panelBody =
H.div {className: "panel-body"} H.div {className: "panel-body"}
[ R2.select {className: "col-md-12 form-control" [ R2.select {className: "col-md-12 form-control"
, onChange: onChange} , on: {change: onChange}
}
(map renderOption [CSV, PresseRIS]) (map renderOption [CSV, PresseRIS])
] ]
where where
onChange = mkEffectFn1 $ \e -> onChange e =
setDroppedFile $ const $ Just $ DroppedFile $ {contents, fileType: readFileType $ R2.unsafeEventValue e} setDroppedFile $ const $ Just $ DroppedFile $ {contents, fileType: readFileType $ R2.unsafeEventValue e}
renderOption opt = H.option {} [ H.text $ show opt ] renderOption opt = H.option {} [ H.text $ show opt ]
panelFooter = panelFooter =
...@@ -139,9 +140,10 @@ fileTypeView d p (Just (DroppedFile {contents, fileType}) /\ setDroppedFile) (_ ...@@ -139,9 +140,10 @@ fileTypeView d p (Just (DroppedFile {contents, fileType}) /\ setDroppedFile) (_
Just ft -> Just ft ->
H.button {className: "btn btn-success" H.button {className: "btn btn-success"
, type: "button" , type: "button"
, onClick: mkEffectFn1 $ \_ -> do , on: {click: \_ -> do
setDroppedFile $ const Nothing setDroppedFile $ const Nothing
launchAff $ d $ UploadFile ft contents launchAff $ d $ UploadFile ft contents
}
} [H.text "Upload"] } [H.text "Upload"]
Nothing -> Nothing ->
H.button {className: "btn btn-success disabled" H.button {className: "btn btn-success disabled"
...@@ -164,9 +166,15 @@ instance fileUploadQueryToQuery :: ToQuery FileUploadQuery where ...@@ -164,9 +166,15 @@ instance fileUploadQueryToQuery :: ToQuery FileUploadQuery where
where pair :: forall a. Show a => String -> a -> Array (Tuple QP.Key (Maybe QP.Value)) where pair :: forall a. Show a => String -> a -> Array (Tuple QP.Key (Maybe QP.Value))
pair k v = [ QP.keyFromString k /\ (Just $ QP.valueFromString $ show v) ] pair k v = [ QP.keyFromString k /\ (Just $ QP.valueFromString $ show v) ]
uploadFile :: Session -> ID -> FileType -> UploadFileContents -> Aff (Array FileHash) uploadFile :: Session -> ID -> FileType -> UploadFileContents -> Aff AsyncTask
uploadFile session id fileType (UploadFileContents fileContents) = uploadFile session id fileType (UploadFileContents fileContents) =
postWwwUrlencoded session p fileContents postWwwUrlencoded session p bodyParams
--postMultipartFormData session p fileContents
where where
q = FileUploadQuery { fileType: fileType } q = FileUploadQuery { fileType: fileType }
p = NodeAPI Node (Just id) $ "add/file" <> Q.print (toQuery q) --p = NodeAPI Corpus (Just id) $ "add/file/async/nobody" <> Q.print (toQuery q)
p = NodeAPI Corpus (Just id) $ "add/form/async" -- <> Q.print (toQuery q)
bodyParams = [
Tuple "_wf_data" (Just fileContents)
, Tuple "_wf_filetype" (Just $ show fileType)
]
...@@ -3,14 +3,25 @@ module Gargantext.Components.Forest.Tree.Node.Box where ...@@ -3,14 +3,25 @@ module Gargantext.Components.Forest.Tree.Node.Box where
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple(..)) import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (Aff, launchAff) import Effect.Aff (Aff, launchAff)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Effect.Uncurried (mkEffectFn1) import Effect.Uncurried (mkEffectFn1)
import React.SyntheticEvent as E
import Reactix as R
import Reactix.DOM.HTML as H
import URI.Extra.QueryPairs as NQP
import URI.Query as Query
import Web.File.FileReader.Aff (readAsText)
import Gargantext.Prelude
import Gargantext.Components.Forest.Tree.Node (NodeAction(..), SettingsBox(..), glyphiconNodeAction, settingsBox) import Gargantext.Components.Forest.Tree.Node (NodeAction(..), SettingsBox(..), glyphiconNodeAction, settingsBox)
import Gargantext.Components.Forest.Tree.Node.Action (Action(..), DroppedFile(..), FileType(..), ID, Name, UploadFileContents(..)) import Gargantext.Components.Forest.Tree.Node.Action (Action(..), DroppedFile(..), FileType(..), ID, Name, UploadFileContents(..))
import Gargantext.Components.Forest.Tree.Node.Action.Add (NodePopup(..), createNodeView) import Gargantext.Components.Forest.Tree.Node.Action.Add (NodePopup(..), createNodeView)
import Gargantext.Components.Forest.Tree.Node.Action.Rename (renameBox) import Gargantext.Components.Forest.Tree.Node.Action.Rename (renameBox)
import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadFileView, fileTypeView) import Gargantext.Components.Forest.Tree.Node.Action.Upload (uploadFileView, fileTypeView)
import Gargantext.Components.Forest.Tree.Node.ProgressBar (asyncProgressBar)
import Gargantext.Components.Search.Types (allLangs) import Gargantext.Components.Search.Types (allLangs)
import Gargantext.Components.Search.SearchBar (searchBar) import Gargantext.Components.Search.SearchBar (searchBar)
import Gargantext.Components.Search.SearchField (Search, defaultSearch, isIsTex) import Gargantext.Components.Search.SearchField (Search, defaultSearch, isIsTex)
...@@ -19,16 +30,9 @@ import Gargantext.Ends (Frontends, url) ...@@ -19,16 +30,9 @@ import Gargantext.Ends (Frontends, url)
import Gargantext.Routes (AppRoute) import Gargantext.Routes (AppRoute)
import Gargantext.Routes as Routes import Gargantext.Routes as Routes
import Gargantext.Sessions (Session, sessionId) import Gargantext.Sessions (Session, sessionId)
import Gargantext.Types (NodeType(..), NodePath(..), fldr) import Gargantext.Types (NodeType(..), NodePath(..), fldr, AsyncTask(..))
import Gargantext.Utils (glyphicon, glyphiconActive) import Gargantext.Utils (glyphicon, glyphiconActive)
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
import Prelude (Unit, bind, const, discard, identity, map, pure, show, void, ($), (<>), (==), (-), (+))
import React.SyntheticEvent as E
import Reactix as R
import Reactix.DOM.HTML as H
import URI.Extra.QueryPairs as NQP
import URI.Query as Query
import Web.File.FileReader.Aff (readAsText)
import DOM.Simple.Types import DOM.Simple.Types
...@@ -39,9 +43,11 @@ import Effect.Console ...@@ -39,9 +43,11 @@ import Effect.Console
-- Main Node -- Main Node
type NodeMainSpanProps = type NodeMainSpanProps =
( id :: ID ( id :: ID
, asyncTasks :: Array AsyncTask
, mCurrentRoute :: Maybe AppRoute
, name :: Name , name :: Name
, nodeType :: NodeType , nodeType :: NodeType
, mCurrentRoute :: Maybe AppRoute , onAsyncTaskFinish :: AsyncTask -> Effect Unit
) )
nodeMainSpan :: (Action -> Aff Unit) nodeMainSpan :: (Action -> Aff Unit)
...@@ -53,7 +59,7 @@ nodeMainSpan :: (Action -> Aff Unit) ...@@ -53,7 +59,7 @@ nodeMainSpan :: (Action -> Aff Unit)
nodeMainSpan d p folderOpen session frontends = R.createElement el p [] nodeMainSpan d p folderOpen session frontends = R.createElement el p []
where where
el = R.hooksComponent "NodeMainSpan" cpt el = R.hooksComponent "NodeMainSpan" cpt
cpt props@{id, name, nodeType, mCurrentRoute} _ = do cpt props@{id, asyncTasks, mCurrentRoute, name, nodeType, onAsyncTaskFinish} _ = do
-- only 1 popup at a time is allowed to be opened -- only 1 popup at a time is allowed to be opened
popupOpen <- R.useState' (Nothing :: Maybe NodePopup) popupOpen <- R.useState' (Nothing :: Maybe NodePopup)
popupPosition <- R.useState' (Nothing :: Maybe R2.Point) popupPosition <- R.useState' (Nothing :: Maybe R2.Point)
...@@ -70,6 +76,10 @@ nodeMainSpan d p folderOpen session frontends = R.createElement el p [] ...@@ -70,6 +76,10 @@ nodeMainSpan d p folderOpen session frontends = R.createElement el p []
, popOverIcon showBox popupOpen popupPosition , popOverIcon showBox popupOpen popupPosition
, mNodePopupView props showBox popupOpen popupPosition , mNodePopupView props showBox popupOpen popupPosition
, fileTypeView d {id, nodeType} droppedFile isDragOver , fileTypeView d {id, nodeType} droppedFile isDragOver
, H.div {} (map (\t -> asyncProgressBar { asyncTask: t
, corpusId: id
, onFinish: \_ -> onAsyncTaskFinish t
, session }) asyncTasks)
] ]
where where
SettingsBox {show: showBox} = settingsBox nodeType SettingsBox {show: showBox} = settingsBox nodeType
......
module Gargantext.Components.Forest.Tree.Node.ProgressBar where
import Gargantext.Prelude
import Data.Int (fromNumber)
import Data.Maybe (Maybe(..), fromJust)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (Aff, launchAff_)
import Effect.Class (liftEffect)
import Effect.Timer (clearInterval, setInterval)
import Gargantext.Components.Forest.Tree.Node.Action (ID)
import Gargantext.Routes (SessionRoute(..))
import Gargantext.Sessions (Session, get)
import Gargantext.Types (AsyncProgress(..), AsyncTask(..), AsyncTaskStatus(..), NodeType(..), progressPercent)
import Partial.Unsafe (unsafePartial)
import Reactix as R
import Reactix.DOM.HTML as H
type Props =
(
asyncTask :: AsyncTask
, corpusId :: ID
, onFinish :: Unit -> Effect Unit
, session :: Session
)
asyncProgressBar :: Record Props -> R.Element
asyncProgressBar p = R.createElement asyncProgressBarCpt p []
asyncProgressBarCpt :: R.Component Props
asyncProgressBarCpt = R.hooksComponent "G.C.F.T.N.asyncProgressBar" cpt
where
cpt props@{asyncTask: (AsyncTask {id}), corpusId, onFinish} _ = do
(progress /\ setProgress) <- R.useState' 0.0
intervalIdRef <- R.useRef Nothing
R.useEffectOnce' $ do
intervalId <- setInterval 1000 $ do
launchAff_ $ do
asyncProgress@(AsyncProgress {status}) <- queryProgress props
liftEffect do
setProgress \p -> min 100.0 $ progressPercent asyncProgress
if (status == Finished) || (status == Killed) || (status == Failed) then do
_ <- case R.readRef intervalIdRef of
Nothing -> pure unit
Just iid -> clearInterval iid
onFinish unit
else
pure unit
R.setRef intervalIdRef $ Just intervalId
pure unit
pure $
H.div { className: "progress" } [
H.div { className: "progress-bar"
, role: "progressbar"
, style: { width: (show $ toInt progress) <> "%" }
} [ H.text id ]
]
toInt :: Number -> Int
toInt n = unsafePartial $ fromJust $ fromNumber n
queryProgress :: Record Props -> Aff AsyncProgress
queryProgress {asyncTask: AsyncTask {id}, corpusId, session} = get session p
where
p = NodeAPI Corpus (Just corpusId) $ "add/form/async/" <> id <> "/poll?limit=1"
...@@ -204,7 +204,7 @@ termsLink _ = ...@@ -204,7 +204,7 @@ termsLink _ =
requestAccessLink :: {} -> R.Element requestAccessLink :: {} -> R.Element
requestAccessLink _ = requestAccessLink _ =
H.a { target: "_blank", href: applyUrl } [ H.text " request access" ] H.a { target: "_blank", href: applyUrl } [ H.text " request access" ]
where applyUrl = "https://iscpif.fr/services/applyforourservices/" where applyUrl = "https://iscpif.fr/apply-for-a-services-account/"
usernameInput :: R.State String -> R.Element usernameInput :: R.State String -> R.Element
usernameInput username = usernameInput username =
......
module Gargantext.Components.MarkdownEditor where
import Data.Either (either, Either(..))
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple (fst)
import Data.Tuple.Nested ((/\))
import DOM.Simple.Console (log2)
import DOM.Simple.Types (Element)
import Effect (Effect)
import FFI.Simple ((.=), delay)
import Prelude (($), (>>>), Unit, bind, const, discard, identity, pure, unit)
import Reactix as R
import Reactix.DOM.HTML as H
import Text.Markdown.SlamDown.Parser (parseMd)
import Text.Markdown.SlamDown.Smolder as MD
import Text.Markdown.SlamDown.Syntax (SlamDownP(..))
import Text.Smolder.Renderer.String (render)
import Gargantext.Utils.Reactix as R2
type Props =
( md :: String
, nodeId :: Int
)
compileMd' :: forall e. MD.ToMarkupOptions e -> String -> String
compileMd' options input =
either identity (MD.toMarkup' options >>> render)
(parseMd input :: Either String (SlamDownP String))
compileMd :: String -> String
compileMd = compileMd' MD.defaultToMarkupOptions
markdownEditor :: Record Props -> R.Element
markdownEditor p = R.createElement markdownEditorCpt p []
markdownEditorCpt :: R.Component Props
markdownEditorCpt = R.hooksComponent "G.C.MarkdownEditor" cpt
where
cpt {md, nodeId} _ = do
ref <- R.useRef null
editedMd <- R.useState' md
-- Note: delay is necessary here, otherwise initially the HTML won't get
-- rendered (mDiv is still null)
R.useEffect $ delay unit $ \_ -> do
let mDiv = toMaybe $ R.readRef ref
case mDiv of
Nothing -> pure $ pure unit
Just d -> do
_ <- pure $ (d .= "innerHTML") $ compileMd $ fst editedMd
pure $ pure unit
pure $ H.div { className: "markdown-editor" }
[ H.div { className: "row"}
[ H.div { className: "col-md-5" } [ H.div { ref, className: "html" } []]
, H.div { className: "col-md-5" }
[ H.div { className: "md" }
[ H.textarea { className: "form-control"
, rows: 10
, on: {change: onChange ref editedMd}
} [ H.text $ fst editedMd ]
]
]
]
]
onChange :: forall e. R.Ref (Nullable Element) -> R.State String -> e -> Effect Unit
onChange ref (_ /\ setEditedMd) e = do
setEditedMd $ const value
where
value = R2.unsafeEventValue e
...@@ -4,15 +4,18 @@ import Prelude ((<<<)) ...@@ -4,15 +4,18 @@ import Prelude ((<<<))
import Data.Argonaut (class DecodeJson, decodeJson, (.:), (.:?)) import Data.Argonaut (class DecodeJson, decodeJson, (.:), (.:?))
import Data.Array (head) import Data.Array (head)
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import DOM.Simple.Console (log2)
import Effect.Aff (Aff, throwError) import Effect.Aff (Aff, throwError)
import Effect.Exception (error) import Effect.Exception (error)
import Reactix as R import Reactix as R
import Reactix.DOM.HTML as H import Reactix.DOM.HTML as H
import Gargantext.Prelude import Gargantext.Prelude
import Gargantext.Components.CodeEditor as CE
import Gargantext.Components.Node (NodePoly(..), HyperdataList) import Gargantext.Components.Node (NodePoly(..), HyperdataList)
import Gargantext.Types (NodeType(..), AffTableResult)
import Gargantext.Routes (SessionRoute(NodeAPI, Children)) import Gargantext.Routes (SessionRoute(NodeAPI, Children))
import Gargantext.Sessions (Session, get) import Gargantext.Sessions (Session, get)
import Gargantext.Types (NodeType(..), AffTableResult)
type Props = ( nodeId :: Int ) type Props = ( nodeId :: Int )
...@@ -20,13 +23,19 @@ corpusLayout :: Record Props -> R.Element ...@@ -20,13 +23,19 @@ corpusLayout :: Record Props -> R.Element
corpusLayout props = R.createElement corpusLayoutCpt props [] corpusLayout props = R.createElement corpusLayoutCpt props []
corpusLayoutCpt :: R.Component Props corpusLayoutCpt :: R.Component Props
corpusLayoutCpt = R.staticComponent "G.P.Corpus.corpusLayout" cpt corpusLayoutCpt = R.hooksComponent "G.P.Corpus.corpusLayout" cpt
where where
cpt {nodeId} _ = cpt {nodeId} _ = do
H.div {} pure $ H.div {}
[ H.iframe { src: gargMd , width: "100%", height: "100%", style: {"border-style": "none"}} [] [
] CE.codeEditor {code, defaultCodeType: CE.Markdown, onChange}
gargMd = "https://hackmd.iscpif.fr/g9Aah4iwQtCayIzsKQjA0Q#" --H.iframe { src: gargMd , width: "100%", height: "100%", style: {"border-style": "none"}} []
]
--gargMd = "https://hackmd.iscpif.fr/g9Aah4iwQtCayIzsKQjA0Q#"
code = "# Hello world\n\n## subtitle\n\n- item 1\n- item 2\n\n1. num 1\n2. num 2\n\n[purescript link](https://purescript.org)"
onChange c = do
log2 "[corpusLayoutCpt] c" c
newtype CorpusInfo = newtype CorpusInfo =
CorpusInfo CorpusInfo
{ title :: String { title :: String
......
module Gargantext.Config.REST where module Gargantext.Config.REST where
import Prelude (Unit, bind, pure, ($), (<$>), (<<<), (<>))
import Affjax (defaultRequest, printResponseFormatError, request) import Affjax (defaultRequest, printResponseFormatError, request)
import Affjax.RequestBody (RequestBody(..), string) import Affjax.RequestBody (RequestBody(..), formData, formURLEncoded, string)
import Affjax.RequestHeader (RequestHeader(..)) import Affjax.RequestHeader (RequestHeader(..))
import Affjax.ResponseFormat as ResponseFormat import Affjax.ResponseFormat as ResponseFormat
import Effect.Class (liftEffect) import DOM.Simple.Console (log)
import Data.Argonaut (class DecodeJson, decodeJson, class EncodeJson, encodeJson) import Data.Argonaut (class DecodeJson, decodeJson, class EncodeJson, encodeJson)
import Data.Either (Either(..)) import Data.Either (Either(..))
import Data.Foldable (foldMap)
import Data.FormURLEncoded as FormURLEncoded
import Data.HTTP.Method (Method(..)) import Data.HTTP.Method (Method(..))
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.MediaType.Common (applicationFormURLEncoded, applicationJSON) import Data.MediaType.Common (applicationFormURLEncoded, applicationJSON, multipartFormData)
import Data.Foldable (foldMap) import Data.Tuple (Tuple(..))
import DOM.Simple.Console (log)
import Effect.Aff (Aff, throwError) import Effect.Aff (Aff, throwError)
import Effect.Class (liftEffect)
import Effect.Exception (error) import Effect.Exception (error)
import Prelude (Unit, bind, pure, ($), (<$>), (<<<), (<>))
import Web.XHR.FormData as XHRFormData
type Token = String type Token = String
...@@ -67,9 +69,11 @@ deleteWithBody mtoken url = send DELETE mtoken url <<< Just ...@@ -67,9 +69,11 @@ deleteWithBody mtoken url = send DELETE mtoken url <<< Just
post :: forall a b. EncodeJson a => DecodeJson b => Maybe Token -> String -> a -> Aff b post :: forall a b. EncodeJson a => DecodeJson b => Maybe Token -> String -> a -> Aff b
post mtoken url = send POST mtoken url <<< Just post mtoken url = send POST mtoken url <<< Just
type FormDataParams = Array (Tuple String (Maybe String))
-- TODO too much duplicate code with `send` -- TODO too much duplicate code with `send`
postWwwUrlencoded :: forall b. DecodeJson b => Maybe Token -> String -> String -> Aff b postWwwUrlencoded :: forall b. DecodeJson b => Maybe Token -> String -> FormDataParams -> Aff b
postWwwUrlencoded mtoken url body = do postWwwUrlencoded mtoken url bodyParams = do
affResp <- request $ defaultRequest affResp <- request $ defaultRequest
{ url = url { url = url
, responseFormat = ResponseFormat.json , responseFormat = ResponseFormat.json
...@@ -80,7 +84,7 @@ postWwwUrlencoded mtoken url body = do ...@@ -80,7 +84,7 @@ postWwwUrlencoded mtoken url body = do
foldMap (\token -> foldMap (\token ->
[RequestHeader "Authorization" $ "Bearer " <> token] [RequestHeader "Authorization" $ "Bearer " <> token]
) mtoken ) mtoken
, content = Just $ string body , content = Just $ formURLEncoded urlEncodedBody
} }
case affResp.body of case affResp.body of
Left err -> do Left err -> do
...@@ -93,3 +97,30 @@ postWwwUrlencoded mtoken url body = do ...@@ -93,3 +97,30 @@ postWwwUrlencoded mtoken url body = do
case decodeJson json of case decodeJson json of
Left err -> throwError $ error $ "decodeJson affResp.body: " <> err Left err -> throwError $ error $ "decodeJson affResp.body: " <> err
Right b -> pure b Right b -> pure b
where
urlEncodedBody = FormURLEncoded.fromArray bodyParams
postMultipartFormData :: forall b. DecodeJson b => Maybe Token -> String -> String -> Aff b
postMultipartFormData mtoken url body = do
fd <- liftEffect $ XHRFormData.new
_ <- liftEffect $ XHRFormData.append (XHRFormData.EntryName "body") body fd
affResp <- request $ defaultRequest
{ url = url
, responseFormat = ResponseFormat.json
, method = Left POST
, headers = [ ContentType multipartFormData
, Accept applicationJSON
] <>
foldMap (\token ->
[ RequestHeader "Authorization" $ "Bearer " <> token ]
) mtoken
, content = Just $ formData fd
}
case affResp.body of
Left err -> do
_ <- liftEffect $ log $ printResponseFormatError err
throwError $ error $ printResponseFormatError err
Right json -> do
case decodeJson json of
Left err -> throwError $ error $ "decodeJson affResp.body: " <> err
Right b -> pure b
...@@ -223,8 +223,11 @@ deleteWithBody session@(Session {token}) p = REST.deleteWithBody (Just token) (t ...@@ -223,8 +223,11 @@ deleteWithBody session@(Session {token}) p = REST.deleteWithBody (Just token) (t
post :: forall a b p. EncodeJson a => DecodeJson b => ToUrl Session p => Session -> p -> a -> Aff b post :: forall a b p. EncodeJson a => DecodeJson b => ToUrl Session p => Session -> p -> a -> Aff b
post session@(Session {token}) p = REST.post (Just token) (toUrl session p) post session@(Session {token}) p = REST.post (Just token) (toUrl session p)
postWwwUrlencoded :: forall b p. DecodeJson b => ToUrl Session p => Session -> p -> String -> Aff b postWwwUrlencoded :: forall b p. DecodeJson b => ToUrl Session p => Session -> p -> REST.FormDataParams -> Aff b
postWwwUrlencoded session@(Session {token}) p = REST.postWwwUrlencoded (Just token) (toUrl session p) postWwwUrlencoded session@(Session {token}) p = REST.postWwwUrlencoded (Just token) (toUrl session p)
postMultipartFormData :: forall b p. DecodeJson b => ToUrl Session p => Session -> p -> String -> Aff b
postMultipartFormData session@(Session {token}) p = REST.postMultipartFormData (Just token) (toUrl session p)
getls :: Effect Storage getls :: Effect Storage
getls = window >>= localStorage getls = window >>= localStorage
module Gargantext.Types where module Gargantext.Types where
import Prelude import Prelude
import Data.Argonaut ( class DecodeJson, decodeJson, class EncodeJson, encodeJson, (:=), (~>), jsonEmptyObject) import Data.Argonaut ( class DecodeJson, decodeJson, class EncodeJson, encodeJson, (.:), (:=), (~>), jsonEmptyObject)
import Data.Array as A
import Data.Either (Either(..)) import Data.Either (Either(..))
import Data.Int (toNumber)
import Data.Maybe (Maybe(..), maybe) import Data.Maybe (Maybe(..), maybe)
import Effect.Aff (Aff) import Effect.Aff (Aff)
import Prim.Row (class Union) import Prim.Row (class Union)
...@@ -434,3 +436,73 @@ modeFromString "Sources" = Just Sources ...@@ -434,3 +436,73 @@ modeFromString "Sources" = Just Sources
modeFromString "Institutes" = Just Institutes modeFromString "Institutes" = Just Institutes
modeFromString "Terms" = Just Terms modeFromString "Terms" = Just Terms
modeFromString _ = Nothing modeFromString _ = Nothing
type AsyncTaskID = String
data AsyncTaskStatus = Running | Failed | Finished | Killed
derive instance genericAsyncTaskStatus :: Generic AsyncTaskStatus _
derive instance eqAsyncTaskStatus :: Eq AsyncTaskStatus
instance decodeJsonAsyncTaskStatus :: DecodeJson AsyncTaskStatus where
decodeJson json = do
obj <- decodeJson json
pure $ readAsyncTaskStatus obj
readAsyncTaskStatus :: String -> AsyncTaskStatus
readAsyncTaskStatus "failed" = Failed
readAsyncTaskStatus "finished" = Finished
readAsyncTaskStatus "killed" = Killed
readAsyncTaskStatus "running" = Running
readAsyncTaskStatus _ = Running
newtype AsyncTask = AsyncTask {
id :: AsyncTaskID
, status :: AsyncTaskStatus
}
derive instance genericAsyncTask :: Generic AsyncTask _
instance decodeJsonAsyncTask :: DecodeJson AsyncTask where
decodeJson json = do
obj <- decodeJson json
id <- obj .: "id"
status <- obj .: "status"
pure $ AsyncTask {id, status}
newtype AsyncProgress = AsyncProgress {
id :: AsyncTaskID
, log :: Array AsyncTaskLog
, status :: AsyncTaskStatus
}
derive instance genericAsyncProgress :: Generic AsyncProgress _
instance decodeJsonAsyncProgress :: DecodeJson AsyncProgress where
decodeJson json = do
obj <- decodeJson json
id <- obj .: "id"
log <- obj .: "log"
status <- obj .: "status"
pure $ AsyncProgress {id, log, status}
newtype AsyncTaskLog = AsyncTaskLog {
events :: Array String
, failed :: Int
, remaining :: Int
, succeeded :: Int
}
derive instance genericAsyncTaskLog :: Generic AsyncTaskLog _
instance decodeJsonAsyncTaskLog :: DecodeJson AsyncTaskLog where
decodeJson json = do
obj <- decodeJson json
events <- obj .: "events"
failed <- obj .: "failed"
remaining <- obj .: "remaining"
succeeded <- obj .: "succeeded"
pure $ AsyncTaskLog {events, failed, remaining, succeeded}
progressPercent :: AsyncProgress -> Number
progressPercent (AsyncProgress {log}) = perc
where
perc = case A.head log of
Nothing -> 0.0
Just (AsyncTaskLog {failed, remaining, succeeded}) -> 100.0*nom/denom
where
nom = toNumber $ failed + succeeded
denom = toNumber $ failed + succeeded + remaining
function _push(a, i) { a.push(i); } function _push(a, i) {
module.exports={ a.push(i);
_push: _push }
}; exports._push = _push;
...@@ -7,4 +7,14 @@ function addRootElement(rootElem) { ...@@ -7,4 +7,14 @@ function addRootElement(rootElem) {
); );
} }
function getSelection(_u) {
return window.getSelection();
}
function stringify(j, indent) {
return JSON.stringify(j, null, indent);
}
exports._addRootElement = addRootElement; exports._addRootElement = addRootElement;
exports._getSelection = getSelection;
exports._stringify = stringify;
...@@ -2,21 +2,23 @@ module Gargantext.Utils.Reactix where ...@@ -2,21 +2,23 @@ module Gargantext.Utils.Reactix where
import Prelude import Prelude
import Data.Argonaut.Core (Json)
import Data.Function.Uncurried (Fn2, runFn2)
import Data.Maybe (Maybe(..), fromJust)
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple (Tuple)
import Data.Tuple.Nested ((/\))
import DOM.Simple as DOM import DOM.Simple as DOM
import DOM.Simple.Console (log2) import DOM.Simple.Console (log2)
import DOM.Simple.Document (document) import DOM.Simple.Document (document)
import DOM.Simple.Element as Element import DOM.Simple.Element as Element
import DOM.Simple.Event as DE import DOM.Simple.Event as DE
import DOM.Simple.Types (class IsNode) import DOM.Simple.Types (class IsNode)
import Data.Maybe (Maybe(..), fromJust)
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple (Tuple)
import Data.Tuple.Nested ((/\))
import Effect (Effect) import Effect (Effect)
import Effect.Aff (Aff, launchAff, launchAff_, killFiber) import Effect.Aff (Aff, launchAff, launchAff_, killFiber)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Effect.Exception (error) import Effect.Exception (error)
import Effect.Uncurried (EffectFn1, runEffectFn1, mkEffectFn1, mkEffectFn2) import Effect.Uncurried (EffectFn1, runEffectFn1, mkEffectFn1, EffectFn2, runEffectFn2, mkEffectFn2)
import FFI.Simple ((..), (...), defineProperty, delay, args2, args3) import FFI.Simple ((..), (...), defineProperty, delay, args2, args3)
import Partial.Unsafe (unsafePartial) import Partial.Unsafe (unsafePartial)
import React (class ReactPropFields, Children, ReactClass, ReactElement) import React (class ReactPropFields, Children, ReactClass, ReactElement)
...@@ -232,3 +234,18 @@ row children = H.div { className: "row" } children ...@@ -232,3 +234,18 @@ row children = H.div { className: "row" } children
col12 :: Array R.Element -> R.Element col12 :: Array R.Element -> R.Element
col12 children = H.div { className: "col-md-12" } children col12 children = H.div { className: "col-md-12" } children
innerText :: DOM.Element -> String
innerText e = e .. "innerText"
foreign import data Selection :: Type
getSelection :: Unit -> Effect Selection
getSelection = runEffectFn1 _getSelection
foreign import _getSelection :: EffectFn1 Unit Selection
stringify :: Json -> Int -> String
stringify j indent = runFn2 _stringify j indent
foreign import _stringify :: Fn2 Json Int String
function _cloneRegex(r) { return new RegExp(r.source, r.flags); } function _cloneRegex(r) {
function _getRegexLastIndex(r) { return r.lastIndex; } return new RegExp(r.source, r.flags);
function _execRegex(r, s) { return r.exec(s); } }
module.exports={ function _getRegexLastIndex(r) {
_cloneRegex: _cloneRegex, return r.lastIndex;
_getRegexLastIndex: _getRegexLastIndex, }
_execRegex: _execRegex function _execRegex(r, s) {
}; return r.exec(s);
}
exports._cloneRegex = _cloneRegex;
exports._getRegexLastIndex = _getRegexLastIndex;
exports._execRegex = _execRegex;
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
<link rel="stylesheet" type="text/css" href="styles/menu.css"/> <link rel="stylesheet" type="text/css" href="styles/menu.css"/>
<link href="styles/Graph.css" rel="stylesheet" type="text/css" /> <link href="styles/Graph.css" rel="stylesheet" type="text/css" />
<link href="styles/Login.css" rel="stylesheet" type="text/css" /> <link href="styles/Login.css" rel="stylesheet" type="text/css" />
<link href="styles/CodeEditor.css" rel="stylesheet" type="text/css" />
<link href="styles/range-slider.css" rel="stylesheet" type="text/css" /> <link href="styles/range-slider.css" rel="stylesheet" type="text/css" />
<style> <style>
* {margin: 0; padding: 0; list-style: none;} * {margin: 0; padding: 0; list-style: none;}
......
// This file is just a wrapper so that webpack will call our main function // This file is just a wrapper so that webpack will call our main function
require('./Main.purs').main(); require('../output/Main').main();
...@@ -8,33 +8,11 @@ let HtmlWebpackPlugin = require('html-webpack-plugin'); ...@@ -8,33 +8,11 @@ let HtmlWebpackPlugin = require('html-webpack-plugin');
let CleanWebpackPlugin = require('clean-webpack-plugin'); let CleanWebpackPlugin = require('clean-webpack-plugin');
let isWatch = process.argv.some(a => a === '--watch'); let isWatch = process.argv.some(a => a === '--watch');
// TODO: We have agreed to move to spago, but not done it yet
// let spago_sources = async () =>
// exec.quiet(
// "psc-package sources",
// { options: 'strict' }
// ).then(function (res) {
// let sources = res.stdout.split(/\r?\n/);
// sources.pop(); // extra newline at the end of output
// return sources;
// });
let dist = path.join(__dirname, 'dist'); let dist = path.join(__dirname, 'dist');
let src = path.join(__dirname, 'src'); let src = path.join(__dirname, 'src');
let test = path.join(__dirname, 'test'); let test = path.join(__dirname, 'test');
// kill when spago module.exports = (env) =>{
let futured = async () => new Promise((resolve, _) => resolve([]));
module.exports = (env) =>
// spago_sources()
futured()
.then(function (ps_sources) {
ps_sources.push('src/**/*.purs');
// TODO: testing in browser and headless
// if (env === "browser" || env === "headless")
// ps_sources.push('test/Main.purs');
let config = { let config = {
cache: true, cache: true,
mode: 'development', mode: 'development',
...@@ -52,22 +30,6 @@ module.exports = (env) => ...@@ -52,22 +30,6 @@ module.exports = (env) =>
}, },
module: { module: {
rules: [ rules: [
{test: /\.purs$/,
exclude: /(node_modules)/,
use: [
{loader: "purs-loader",
options: {
src: ps_sources,
output: dist,
pscIde: true,
pscIdeClientArgs: {port: 4002},
pscIdeServerArgs: {port: 4002},
pscArgs: {codegen: "js,sourcemaps"},
pscPackage: true,
bundle: false,
watch: isWatch}},
{loader: "source-map-loader"},
]},
{test: /\.css$/, {test: /\.css$/,
exclude: /(node_modules)/, exclude: /(node_modules)/,
use: ["style-loader", "css-loader"]}, use: ["style-loader", "css-loader"]},
...@@ -75,13 +37,13 @@ module.exports = (env) => ...@@ -75,13 +37,13 @@ module.exports = (env) =>
exclude: /(node_modules)/, exclude: /(node_modules)/,
use: [ "file-loader" ]}, use: [ "file-loader" ]},
{test: /\.js$/, {test: /\.js$/,
exclude: /(node_modules)/, exclude: [/(node_modules)/, /(output)/],
use: ["babel-loader", "source-map-loader"]} use: ["babel-loader", "source-map-loader"]}
] ]
}, },
resolve: { resolve: {
modules: [ 'node_modules' ], modules: [ 'node_modules' ],
extensions: [ '.purs', '.js'] extensions: [ '.js']
}, },
plugins: [ plugins: [
// TODO: can we put the checked-in assets in dist somewhere else // TODO: can we put the checked-in assets in dist somewhere else
...@@ -110,4 +72,4 @@ module.exports = (env) => ...@@ -110,4 +72,4 @@ module.exports = (env) =>
console.log("unknown env: ", env); console.log("unknown env: ", env);
} }
return config; return config;
}); };
...@@ -5177,10 +5177,19 @@ purescript-installer@^0.2.0: ...@@ -5177,10 +5177,19 @@ purescript-installer@^0.2.0:
which "^1.3.1" which "^1.3.1"
zen-observable "^0.8.14" zen-observable "^0.8.14"
purescript@^0.13.5: purescript-language-server@^0.12.7:
version "0.13.5" version "0.12.7"
resolved "https://registry.yarnpkg.com/purescript/-/purescript-0.13.5.tgz#0b1621577c64390ee34908d772679b1e23b8edd1" resolved "https://registry.yarnpkg.com/purescript-language-server/-/purescript-language-server-0.12.7.tgz#0edc0536b46ad0f9cfd93d85c62c4bbb3d22a5e2"
integrity sha512-nwCqlhPZKfm1HkZzEy91dC9vIoRcah226OkIfW0kiz7tPl+LddwAIVT2bdhfQwzu2XUhgOKmYbvn9nlbHlOGMQ== integrity sha512-uXYrzoKPjlgEOgJqctqcfddtkCa57N3/1t2wENxS8TnxJnZt1j/LVQCWMhrEUfw90DPohhRKaaQmdrWrCpq9ow==
dependencies:
vscode-languageserver "^3.2.0"
vscode-uri "^1.0.0"
which "^1.2.9"
purescript@^0.13.6:
version "0.13.6"
resolved "https://registry.yarnpkg.com/purescript/-/purescript-0.13.6.tgz#f5f77680dd7a50b7e63ba671bd85e2ac121318b0"
integrity sha512-PC93xqr0zDs5l5xnfTlptKzv5jBWbML+dwtpDCZkOOH7h9wgLusQfU4PNfHvdwrSmsBntalGm+Cbd6VrokN7Sg==
dependencies: dependencies:
purescript-installer "^0.2.0" purescript-installer "^0.2.0"
...@@ -6614,6 +6623,62 @@ vm-browserify@^1.0.0, vm-browserify@^1.0.1: ...@@ -6614,6 +6623,62 @@ vm-browserify@^1.0.0, vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
vscode-jsonrpc@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
vscode-jsonrpc@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.0.tgz#445be9fb404e2793a91b9c0cef3c52dec3e02b89"
integrity sha512-QeAniC/xTWauVQgyNgEqNJ0Qm/Jw8QySGRQhRFPwb8c4FPp9k6QNgJp0ayXWws5qhdaHkiXkGPlzjOPZFQQKLw==
vscode-languageserver-protocol@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
dependencies:
vscode-jsonrpc "3.5.0"
vscode-languageserver-types "3.5.0"
vscode-languageserver-protocol@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0.tgz#7f306dc337a72e4d84882bd952db51e084fef8fe"
integrity sha512-PZEopQzHR3Lo422HeDxCpGN0sYz+kReO+du3F/AcFT1cCPunoVsDJv7ikEolFRKEn+hEIZiAaIX4yoSZ+ip5Nw==
dependencies:
vscode-jsonrpc "^5.0.0"
vscode-languageserver-types "3.15.0"
vscode-languageserver-types@3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a"
integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw==
vscode-languageserver-types@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
vscode-languageserver@^3.2.0:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz#e0044b7df4d2447ce12632dfc98f1ab0afacbdff"
integrity sha512-RYUKn0DgHTFcS8kS4VaNCjNMaQXYqiXdN9bKrFjXzu5RPKfjIYcoh47oVWwZj4L3R/DPB0Se7HPaDatvYY2XgQ==
dependencies:
vscode-languageserver-protocol "3.5.1"
vscode-uri "^1.0.1"
vscode-languageserver@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.0.tgz#4b6fa593422a68bab3c5e92f1d50af3a7cb770f8"
integrity sha512-QihKytod/PApM+wU9qmjxjGT+p4vYDJCbMcdwol55QeE80DCMyls6yxJzTn8SNCNpP4JZin8SIZp1zpumfktQg==
dependencies:
vscode-languageserver-protocol "^3.15.0"
vscode-uri@^1.0.0, vscode-uri@^1.0.1:
version "1.0.8"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59"
integrity sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ==
wait-for-expect@^1.1.1: wait-for-expect@^1.1.1:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.2.0.tgz#fdab6a26e87d2039101db88bff3d8158e5c3e13f" resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.2.0.tgz#fdab6a26e87d2039101db88bff3d8158e5c3e13f"
......
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