Commit b8ed3d4b authored by Przemyslaw Kaminski's avatar Przemyslaw Kaminski

[CodeEditor] more improvements, JSON renders with indent now, errors shown

parent 59af3f6f
module Gargantext.Components.CodeEditor where module Gargantext.Components.CodeEditor where
import Data.Argonaut.Parser (jsonParser)
import Data.Either (either, Either(..)) import Data.Either (either, Either(..))
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, null, toMaybe) import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple (fst) import Data.Tuple (fst, snd)
import Data.Tuple.Nested ((/\)) import Data.Tuple.Nested ((/\))
import DOM.Simple.Console (log, log2) import DOM.Simple.Console (log, log2)
import DOM.Simple.Types (Element) import DOM.Simple.Types (Element)
...@@ -22,6 +23,10 @@ import Text.Smolder.Renderer.String (render) ...@@ -22,6 +23,10 @@ import Text.Smolder.Renderer.String (render)
import Gargantext.Prelude import Gargantext.Prelude
import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Reactix as R2
type Code = String
type Html = String
type Error = String
data CodeType = JSON | Markdown data CodeType = JSON | Markdown
derive instance genericCodeType :: Generic CodeType _ derive instance genericCodeType :: Generic CodeType _
instance eqCodeType :: Eq CodeType where instance eqCodeType :: Eq CodeType where
...@@ -38,14 +43,21 @@ instance showViewType :: Show ViewType where ...@@ -38,14 +43,21 @@ instance showViewType :: Show ViewType where
type Props = type Props =
( code :: String ( code :: String
, codeType :: CodeType , defaultCodeType :: CodeType
, onChange :: String -> Effect Unit , onChange :: String -> Effect Unit
) )
compile :: CodeType -> String -> String compile :: CodeType -> Code -> Either Error Html
compile JSON code = code compile JSON code = result
compile Markdown code = compileMd code 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' :: forall e. MD.ToMarkupOptions e -> String -> String
compileMd' options input = compileMd' options input =
either identity (MD.toMarkup' options >>> render) either identity (MD.toMarkup' options >>> render)
...@@ -60,22 +72,21 @@ codeEditor p = R.createElement codeEditorCpt p [] ...@@ -60,22 +72,21 @@ codeEditor p = R.createElement codeEditorCpt p []
codeEditorCpt :: R.Component Props codeEditorCpt :: R.Component Props
codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt
where where
cpt {code, codeType, onChange} _ = do cpt {code, defaultCodeType, onChange} _ = do
htmlRef <- R.useRef null htmlRef <- R.useRef null
codeRef <- R.useRef null codeRef <- R.useRef null
editorCodeRef <- R.useRef code editorCodeRef <- R.useRef code
codeTypeS <- R.useState' codeType codeType <- R.useState' defaultCodeType
error <- R.useState' Nothing
viewType <- R.useState' Both viewType <- R.useState' Both
-- Initial rendering of elements with given data
-- Note: delay is necessary here, otherwise initially the HTML won't get -- Note: delay is necessary here, otherwise initially the HTML won't get
-- rendered (mDiv is still null) -- rendered (mDiv is still null)
R.useEffect $ delay unit $ \_ -> do R.useEffectOnce $ delay unit $ \_ -> do
let mHtmlEl = toMaybe $ R.readRef htmlRef _ <- renderHtml (fst codeType) code htmlRef error
case mHtmlEl of pure $ pure unit
Nothing -> pure $ pure unit
Just htmlEl -> do
_ <- pure $ (htmlEl .= "innerHTML") $ compile codeType code
pure $ pure unit
R.useEffectOnce $ delay unit $ \_ -> do R.useEffectOnce $ delay unit $ \_ -> do
let mCodeEl = toMaybe $ R.readRef codeRef let mCodeEl = toMaybe $ R.readRef codeRef
...@@ -87,20 +98,19 @@ codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt ...@@ -87,20 +98,19 @@ codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt
pure $ H.div { className: "code-editor" } [ pure $ H.div { className: "code-editor" } [
H.div { className: "row toolbar" } [ H.div { className: "row toolbar" } [
codeTypeSelector {codeType: codeTypeS} codeTypeSelector {codeType, onChange: onChangeCodeType editorCodeRef htmlRef error}
, H.div { className: "btn-group" } [ , viewTypeSelector {state: viewType}
viewTypeButton {viewType: Code, state: viewType}
, viewTypeButton {viewType: Both, state: viewType}
, viewTypeButton {viewType: Preview, state: viewType}
]
] ]
, H.div { className: "row error" } [
errorComponent {error}
]
, H.div { className: "row editor" } [ , H.div { className: "row editor" } [
H.div { className: "code " <> (codeHidden $ fst viewType) } [ H.div { className: "code " <> (codeHidden $ fst viewType) } [
H.code { className: "" H.code { className: ""
, contentEditable: "true" , contentEditable: "true"
, ref: codeRef , ref: codeRef
, rows: 30 , rows: 30
, on: { input: onEditChange codeType codeRef htmlRef editorCodeRef } , on: { input: onEditChange (fst codeType) codeRef htmlRef editorCodeRef error }
} [] } []
] ]
, H.div { ref: htmlRef, className: "html " <> (previewHidden $ fst viewType) } [] , H.div { ref: htmlRef, className: "html " <> (previewHidden $ fst viewType) } []
...@@ -117,8 +127,14 @@ codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt ...@@ -117,8 +127,14 @@ codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt
previewHidden Both = "" previewHidden Both = ""
previewHidden _ = "hidden" previewHidden _ = "hidden"
onEditChange :: forall e. CodeType -> R.Ref (Nullable Element) -> R.Ref (Nullable Element) -> R.Ref String -> e -> Effect Unit -- Handle rerendering of preview when viewType changed
onEditChange codeType codeRef htmlRef editorCodeRef e = do 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 log2 "[onChange] e" e
let mCode = toMaybe $ R.readRef codeRef let mCode = toMaybe $ R.readRef codeRef
case mCode of case mCode of
...@@ -126,18 +142,42 @@ codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt ...@@ -126,18 +142,42 @@ codeEditorCpt = R.hooksComponent "G.C.CodeEditor" cpt
Just code -> do Just code -> do
R.setRef editorCodeRef $ R2.innerText code R.setRef editorCodeRef $ R2.innerText code
pure unit pure unit
let mHtml = toMaybe $ R.readRef htmlRef renderHtml codeType (R.readRef editorCodeRef) htmlRef error
case mHtml of
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 Nothing -> pure unit
Just html -> do Just htmlEl -> do
_ <- pure $ (html .= "innerHTML") $ compile codeType $ R.readRef editorCodeRef case compile codeType code of
pure unit Left err -> do
pure unit 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 = type CodeTypeSelectorProps =
( (
codeType :: R.State CodeType codeType :: R.State CodeType
, onChange :: CodeType -> Effect Unit
) )
codeTypeSelector :: Record CodeTypeSelectorProps -> R.Element codeTypeSelector :: Record CodeTypeSelectorProps -> R.Element
...@@ -146,49 +186,59 @@ codeTypeSelector p = R.createElement codeTypeSelectorCpt p [] ...@@ -146,49 +186,59 @@ codeTypeSelector p = R.createElement codeTypeSelectorCpt p []
codeTypeSelectorCpt :: R.Component CodeTypeSelectorProps codeTypeSelectorCpt :: R.Component CodeTypeSelectorProps
codeTypeSelectorCpt = R.hooksComponent "G.C.CodeTypeSelector" cpt codeTypeSelectorCpt = R.hooksComponent "G.C.CodeTypeSelector" cpt
where where
cpt {codeType} _ = do cpt {codeType, onChange} _ = do
pure $ R2.select { className: "form-control" pure $ R2.select { className: "form-control"
, on: { change: onSelectChange codeType } , on: { change: onSelectChange codeType onChange }
, style: { width: "150px" } , style: { width: "150px" }
, value: show $ fst codeType } , value: show $ fst codeType }
(option <$> [JSON, Markdown]) (option <$> [JSON, Markdown])
option :: CodeType -> R.Element option :: CodeType -> R.Element
option value = H.option { value: show value } [ H.text $ show value ] option value = H.option { value: show value } [ H.text $ show value ]
onSelectChange (_ /\ setCodeType) e = do
onSelectChange :: forall e. R.State CodeType -> (CodeType -> Effect Unit) -> e -> Effect Unit
onSelectChange (_ /\ setCodeType) onChange e = do
let codeType = case value of let codeType = case value of
"JSON" -> JSON "JSON" -> JSON
"Markdown" -> Markdown "Markdown" -> Markdown
_ -> Markdown _ -> Markdown
setCodeType $ const codeType setCodeType $ const codeType
onChange codeType
where where
value = R2.unsafeEventValue e value = R2.unsafeEventValue e
type ViewTypeProps = type ViewTypeSelectorProps =
( (
viewType :: ViewType state :: R.State ViewType
, state :: R.State ViewType
) )
viewTypeButton :: Record ViewTypeProps -> R.Element viewTypeSelector :: Record ViewTypeSelectorProps -> R.Element
viewTypeButton p = R.createElement viewTypeButtonCpt p [] viewTypeSelector p = R.createElement viewTypeSelectorCpt p []
viewTypeButtonCpt :: R.Component ViewTypeProps viewTypeSelectorCpt :: R.Component ViewTypeSelectorProps
viewTypeButtonCpt = R.hooksComponent "G.C.ViewTypeButton" cpt viewTypeSelectorCpt = R.hooksComponent "G.C.ViewTypeSelector" cpt
where where
cpt {viewType, state: (state /\ setState)} _ = cpt {state} _ =
pure $ H.label { pure $ H.div { className: "btn-group" } [
className: "btn btn-default" <> (active viewType state) viewTypeButton Code state
, on: { click: onClick viewType setState } , viewTypeButton Both state
} [ , viewTypeButton Preview state
H.i { className: "glyphicon " <> (icon viewType) } [] ]
]
active viewType state = if viewType == state then " active" else "" 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 Preview = "glyphicon-eye-open"
icon Both = "glyphicon-transfer" icon Both = "glyphicon-transfer"
icon Code = "glyphicon-pencil" icon Code = "glyphicon-pencil"
onClick viewType setState _ = setState $ const viewType
...@@ -28,7 +28,7 @@ corpusLayoutCpt = R.hooksComponent "G.P.Corpus.corpusLayout" cpt ...@@ -28,7 +28,7 @@ corpusLayoutCpt = R.hooksComponent "G.P.Corpus.corpusLayout" cpt
cpt {nodeId} _ = do cpt {nodeId} _ = do
pure $ H.div {} pure $ H.div {}
[ [
CE.codeEditor {code, codeType: CE.Markdown, onChange} CE.codeEditor {code, defaultCodeType: CE.Markdown, onChange}
--H.iframe { src: gargMd , width: "100%", height: "100%", style: {"border-style": "none"}} [] --H.iframe { src: gargMd , width: "100%", height: "100%", style: {"border-style": "none"}} []
] ]
--gargMd = "https://hackmd.iscpif.fr/g9Aah4iwQtCayIzsKQjA0Q#" --gargMd = "https://hackmd.iscpif.fr/g9Aah4iwQtCayIzsKQjA0Q#"
......
...@@ -11,5 +11,10 @@ function getSelection(_u) { ...@@ -11,5 +11,10 @@ function getSelection(_u) {
return window.getSelection(); return window.getSelection();
} }
function stringify(j, indent) {
return JSON.stringify(j, null, indent);
}
exports._addRootElement = addRootElement; exports._addRootElement = addRootElement;
exports._getSelection = getSelection; exports._getSelection = getSelection;
exports._stringify = stringify;
...@@ -2,16 +2,18 @@ module Gargantext.Utils.Reactix where ...@@ -2,16 +2,18 @@ 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)
...@@ -242,3 +244,8 @@ getSelection :: Unit -> Effect Selection ...@@ -242,3 +244,8 @@ getSelection :: Unit -> Effect Selection
getSelection = runEffectFn1 _getSelection getSelection = runEffectFn1 _getSelection
foreign import _getSelection :: EffectFn1 Unit Selection foreign import _getSelection :: EffectFn1 Unit Selection
stringify :: Json -> Int -> String
stringify j indent = runFn2 _stringify j indent
foreign import _stringify :: Fn2 Json Int String
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