Skip to content

Commit

Permalink
SCP-2409 - Apply format info to simulator (IntersectMBO#3436)
Browse files Browse the repository at this point in the history
* Add choice formatting to simulator inputs
* Replace rest of number inputs in simulator with currency input
* Move currency formatter to Pretty.purs
* Add currency formatter for choices in simulator
* Address reviews
* Remove spurious import
  • Loading branch information
palas authored Jun 29, 2021
1 parent faf3adc commit 4a62150
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 34 deletions.
51 changes: 51 additions & 0 deletions marlowe-playground-client/src/Halogen/CurrencyInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

exports.setOldValues_ = function(input) {
input.old_value = input.value;
input.old_selectionStart = input.selectionStart;
input.old_selectionEnd = input.selectionEnd;
}

exports.checkChange_ = function(input) {
if (/^-?[0-9]*(\.[0-9]*)?$/.test(input.value)) {
exports.setOldValues_(input);
} else {
input.value = input.old_value;
input.selectionStart = input.old_selectionStart;
input.selectionEnd = input.old_selectionEnd;
}
}

exports.formatValueString_ = function(text, decimalPlaces) {
var positionSeparator = text.indexOf(".");
if (positionSeparator < 0) {
if (decimalPlaces > 0) {
text += '.';
text = text.padEnd(text.length + decimalPlaces, '0');
}
} else {
text = text.substring(0, positionSeparator + decimalPlaces + 1);
text = text.padEnd(positionSeparator + decimalPlaces + 1, '0');
}
if (text == '') {
text = '0';
} else if (text[0] == '.') {
text = '0' + text;
} else if ((text == '-') || ((text[0] == '-') && (text[1] == '.'))) {
text = '-0' + text.substring(1);
}
return text;
}

exports.formatValue_ = function(input, decimalPlaces) {
input.value = exports.formatValueString_(input.value, decimalPlaces);
}

exports.setValue_ = function(input, str) {
input.value = str;
}

exports.onChangeHandler_ = function(input, decimalPlaces) {
exports.checkChange_(input);
exports.formatValue_(input, decimalPlaces);
return input.value;
}
115 changes: 115 additions & 0 deletions marlowe-playground-client/src/Halogen/CurrencyInput.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
module Halogen.CurrencyInput where

import Prelude hiding (div)
import Data.Array (filter)
import Data.BigInteger (BigInteger, fromString)
import Data.Maybe (Maybe(..), maybe)
import Data.Monoid (guard)
import Data.String (trim)
import Data.String.CodeUnits (fromCharArray, toCharArray)
import Data.Traversable (for)
import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)
import Effect.Unsafe (unsafePerformEffect)
import Halogen.Css (classNames)
import Halogen.HTML (HTML, div, input, text)
import Halogen.HTML.Events (onChange, onFocus, onInput, onKeyDown)
import Halogen.HTML.Properties (InputType(..), type_, value)
import Pretty (showBigIntegerAsCurrency)
import Web.Event.Event (currentTarget)
import Web.Event.Internal.Types (EventTarget)
import Web.UIEvent.FocusEvent as FocusEvent
import Web.UIEvent.KeyboardEvent as KeyboardEvent

foreign import setOldValues_ :: EffectFn1 EventTarget Unit

foreign import checkChange_ :: EffectFn1 EventTarget Unit

foreign import formatValue_ :: EffectFn2 EventTarget Int Unit

foreign import formatValueString_ :: EffectFn2 String Int String

foreign import setValue_ :: EffectFn2 EventTarget String Unit

foreign import onChangeHandler_ :: EffectFn2 EventTarget Int String

setOldValues :: EventTarget -> Effect Unit
setOldValues = runEffectFn1 setOldValues_

checkChange :: EventTarget -> Effect Unit
checkChange = runEffectFn1 checkChange_

setValue :: EventTarget -> String -> Effect Unit
setValue = runEffectFn2 setValue_

onChangeHandler :: EventTarget -> Int -> Effect String
onChangeHandler = runEffectFn2 onChangeHandler_

currencyInput :: forall p a. Array String -> BigInteger -> String -> Int -> (Maybe BigInteger -> Maybe a) -> HTML p a
currencyInput classList number prefix rawNumDecimals onValueChangeHandler =
div [ classNames ([ "bg-gray-light", "flex", "items-center", "border-solid", "border", "rounded-sm", "overflow-hidden", "box-border", "focus-within:ring-1", "focus-within:ring-black" ] <> classList) ]
( ( guard hasPrefix
[ div [ classNames [ "flex-none", "px-2", "py-0", "box-border", "self-center" ] ]
[ text prefix ]
]
)
<> [ input
[ classNames [ "flex-1", "px-1", "box-border", "self-stretch", "border-0", "outline-none" ]
, onFocus
( \ev ->
maybe Nothing
( \target ->
unsafePerformEffect
$ do
setOldValues target
pure Nothing
)
(currentTarget (FocusEvent.toEvent ev))
)
, onKeyDown
( \ev ->
maybe Nothing
( \target ->
unsafePerformEffect
$ do
setOldValues target
pure Nothing
)
(currentTarget (KeyboardEvent.toEvent ev))
)
, onChange
( \ev ->
maybe Nothing
( \target ->
unsafePerformEffect
$ do
res <- onChangeHandler target numDecimals
let
mObtainedBigNumStr = fromString $ filterPoint res
void $ for mObtainedBigNumStr (\x -> setValue target $ showBigIntegerAsCurrency x numDecimals)
pure $ onValueChangeHandler $ mObtainedBigNumStr
)
(currentTarget ev)
)
, onInput
( \ev ->
maybe Nothing
( \target ->
unsafePerformEffect
$ do
checkChange target
pure Nothing
)
(currentTarget ev)
)
, type_ InputText
, value (showBigIntegerAsCurrency number numDecimals)
]
]
)
where
numDecimals = max 0 rawNumDecimals

hasPrefix = trim prefix /= ""

filterPoint = fromCharArray <<< filter (\x -> x /= '.') <<< toCharArray
32 changes: 29 additions & 3 deletions marlowe-playground-client/src/Pretty.purs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
module Pretty where

import Prelude
import Data.Array (concat, drop, dropWhile, length, replicate, take)
import Data.BigInteger (BigInteger, format)
import Data.Map as Map
import Data.Maybe (maybe)
import Data.String (length, take)
import Data.String as String
import Data.String.CodeUnits (fromCharArray, toCharArray)
import Halogen.HTML (HTML, abbr, text)
import Halogen.HTML.Properties (title)
import Marlowe.Extended.Metadata (MetaData)
import Marlowe.Extended.Metadata (ChoiceFormat(..), MetaData)
import Marlowe.Semantics (Party(..), Payee(..), Token(..))

renderPrettyToken :: forall p i. Token -> HTML p i
Expand All @@ -21,7 +23,7 @@ showPrettyToken (Token "" "") = "ADA"
showPrettyToken (Token cur tok) = "\"" <> cur <> " " <> tok <> "\""

renderPrettyParty :: forall p i. MetaData -> Party -> HTML p i
renderPrettyParty _ (PK pkh) = if length pkh > 10 then abbr [ title $ "pubkey " <> pkh ] [ text $ take 10 pkh ] else text pkh
renderPrettyParty _ (PK pkh) = if String.length pkh > 10 then abbr [ title $ "pubkey " <> pkh ] [ text $ String.take 10 pkh ] else text pkh

renderPrettyParty metadata (Role role) = abbr [ title $ "role " <> role <> explanationOrEmptyString ] [ text role ]
where
Expand All @@ -41,3 +43,27 @@ renderPrettyPayee :: forall p i. MetaData -> Payee -> Array (HTML p i)
renderPrettyPayee metadata (Account owner2) = [ text "account of ", renderPrettyParty metadata owner2 ]

renderPrettyPayee metadata (Party dest) = [ text "party ", renderPrettyParty metadata dest ]

showBigIntegerAsCurrency :: BigInteger -> Int -> String
showBigIntegerAsCurrency number numDecimals = fromCharArray numberStr
where
absValStr = replicate (numDecimals + 1) '0' <> toCharArray (show (if number < zero then -number else number))

numDigits = length absValStr

numDigitsBeforeSeparator = numDigits - numDecimals

prefixStr = if number < zero then [ '-' ] else []

digitsNoZeros = dropWhile (\x -> x == '0') $ take numDigitsBeforeSeparator absValStr

digitsBeforeSeparator = if digitsNoZeros == [] then [ '0' ] else digitsNoZeros

digitsAfterSeparator = take numDecimals $ drop numDigitsBeforeSeparator (concat [ absValStr, replicate numDecimals '0' ])

numberStr = concat ([ prefixStr, digitsBeforeSeparator ] <> if digitsAfterSeparator /= [] then [ [ '.' ], digitsAfterSeparator ] else [])

showPrettyChoice :: ChoiceFormat -> BigInteger -> String
showPrettyChoice DefaultFormat num = showBigIntegerAsCurrency num 0

showPrettyChoice (DecimalFormat numDecimals strLabel) num = strLabel <> " " <> showBigIntegerAsCurrency num numDecimals
53 changes: 23 additions & 30 deletions marlowe-playground-client/src/SimulationPage/View.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import BottomPanel.Types as BottomPanelTypes
import BottomPanel.View as BottomPanel
import Data.Array (concatMap, intercalate, reverse, sortWith)
import Data.Array as Array
import Data.BigInteger (BigInteger, fromString, fromInt)
import Data.BigInteger (BigInteger)
import Data.Lens (has, only, previewOn, to, view, (^.))
import Data.Lens.Iso.Newtype (_Newtype)
import Data.Lens.NonEmptyList (_Head)
import Data.Map (Map)
import Data.Map as Map
import Data.Maybe (Maybe(..), isJust, maybe)
import Data.Maybe (Maybe(..), fromMaybe, isJust, maybe)
import Data.Newtype (unwrap, wrap)
import Data.String (trim)
import Data.Tuple (Tuple(..), snd)
Expand All @@ -21,20 +21,21 @@ import Effect.Class (liftEffect)
import Halogen (RefLabel(..))
import Halogen.Classes (aHorizontal, bold, btn, flex, flexCol, flexGrow, flexShrink0, fontBold, fullHeight, fullWidth, grid, gridColsDescriptionLocation, group, justifyBetween, justifyCenter, justifyEnd, maxH70p, minH0, noMargins, overflowHidden, overflowScroll, paddingX, plusBtn, smallBtn, smallSpaceBottom, spaceBottom, spaceLeft, spaceRight, spanText, spanTextBreakWord, textSecondaryColor, textXs, uppercase, w30p)
import Halogen.Css (classNames)
import Halogen.CurrencyInput (currencyInput)
import Halogen.Extra (renderSubmodule)
import Halogen.HTML (ClassName(..), ComponentHTML, HTML, aside, b_, button, div, div_, em_, h6, h6_, input, li, li_, p, p_, section, slot, span, span_, strong_, text, ul)
import Halogen.HTML.Events (onClick, onValueChange)
import Halogen.HTML.Properties (InputType(..), class_, classes, disabled, placeholder, type_, value)
import Halogen.HTML (ClassName(..), ComponentHTML, HTML, aside, b_, button, div, div_, em_, h6, h6_, li, li_, p, p_, section, slot, span, span_, strong_, text, ul)
import Halogen.HTML.Events (onClick)
import Halogen.HTML.Properties (class_, classes, disabled)
import Halogen.Monaco (Settings, monacoComponent)
import MainFrame.Types (ChildSlots, _simulatorEditorSlot)
import Marlowe.Extended.Metadata (MetaData)
import Marlowe.Extended.Metadata (ChoiceFormat(..), MetaData, getChoiceFormat)
import Marlowe.Monaco (daylightTheme, languageExtensionPoint)
import Marlowe.Monaco as MM
import Marlowe.Semantics (AccountId, Assets(..), Bound(..), ChoiceId(..), Input(..), Party(..), Payment(..), PubKey, Slot, SlotInterval(..), Token(..), TransactionInput(..), inBounds, timeouts)
import Marlowe.Template (IntegerTemplateType(..))
import Monaco (Editor)
import Monaco as Monaco
import Pretty (renderPrettyParty, renderPrettyToken, showPrettyMoney)
import Pretty (renderPrettyParty, renderPrettyToken, showPrettyChoice, showPrettyMoney)
import SimulationPage.BottomPanel (panelContents)
import SimulationPage.Lenses (_bottomPanelState)
import SimulationPage.Types (Action(..), BottomPanelView(..), State)
Expand Down Expand Up @@ -221,7 +222,7 @@ startSimulationWidget metadata { initialSlot, templateContent } =
$ div_
[ div [ classes [ ClassName "slot-input", ClassName "initial-slot-input" ] ]
[ spanText "Initial slot:"
, marloweActionInput (SetInitialSlot <<< wrap) initialSlot
, marloweActionInput [ "mx-2", "flex-grow", "flex-shrink-0" ] (SetInitialSlot <<< wrap) (unwrap initialSlot)
]
, div_
[ ul [ class_ (ClassName "templates") ]
Expand Down Expand Up @@ -253,7 +254,7 @@ integerTemplateParameters explanations actionGen typeName title prefix content =
, strong_ [ text key ]
, text ":"
]
, marloweActionInput (actionGen typeName key) value
, marloweActionInput [ "mx-2", "flex-grow", "flex-shrink-0" ] (actionGen typeName key) value
]
<> [ div [ classes [ ClassName "action-group-explanation" ] ]
$ maybe [] (\explanation -> [ text "" ] <> markdownToHTML explanation <> [ text "" ])
Expand Down Expand Up @@ -403,7 +404,9 @@ inputItem metadata _ person (ChoiceInput choiceId@(ChoiceId choiceName choiceOwn
( [ div [ classes [ ClassName "action-label" ] ]
( [ div [ class_ (ClassName "choice-input") ]
[ span [ class_ (ClassName "break-word-span") ] [ text "Choice ", b_ [ text (show choiceName <> ": ") ] ]
, marloweActionInput (SetChoice choiceId) chosenNum
, case mChoiceInfo of
Just { choiceFormat: DecimalFormat numDecimals currencyLabel } -> marloweCurrencyInput [ "mx-2", "flex-grow", "flex-shrink-0" ] (SetChoice choiceId) currencyLabel numDecimals chosenNum
_ -> marloweCurrencyInput [ "mx-2", "flex-grow", "flex-shrink-0" ] (SetChoice choiceId) "" 0 chosenNum
]
, div [ class_ (ClassName "choice-error") ] error
]
Expand All @@ -413,13 +416,15 @@ inputItem metadata _ person (ChoiceInput choiceId@(ChoiceId choiceName choiceOwn
([ text "" ] <> markdownToHTML explanation <> [ text "" ])
]
)
(Map.lookup choiceName metadata.choiceInfo >>= mExtractDescription)
(mChoiceInfo >>= mExtractDescription)
)
)
]
<> addButton
)
where
mChoiceInfo = Map.lookup choiceName metadata.choiceInfo

mExtractDescription { choiceDescription }
| trim choiceDescription /= "" = Just choiceDescription

Expand Down Expand Up @@ -465,7 +470,7 @@ inputItem _ state person (MoveToSlot slot) =
( [ div [ classes [ ClassName "action" ] ]
[ p [ class_ (ClassName "slot-input") ]
[ spanTextBreakWord "Move to slot "
, marloweActionInput (SetSlot <<< wrap) slot
, marloweActionInput [ "mx-2", "flex-grow", "flex-shrink-0" ] (SetSlot <<< wrap) (unwrap slot)
]
, p [ class_ (ClassName "choice-error") ] error
]
Expand All @@ -488,23 +493,11 @@ inputItem _ state person (MoveToSlot slot) =

boundsError = "The slot must be more than the current slot " <> (state ^. (_currentMarloweState <<< _executionState <<< _SimulationRunning <<< _slot <<< to show))

marloweActionInput :: forall p a action. Show a => (BigInteger -> action) -> a -> HTML p action
marloweActionInput f current =
input
[ type_ InputNumber
, placeholder "BigInteger"
, class_ $ ClassName "action-input"
, value $ show current
, onValueChange
$ ( \x ->
Just
$ f
( case fromString x of
Just y -> y
Nothing -> fromInt 0
)
)
]
marloweCurrencyInput :: forall p action. Array String -> (BigInteger -> action) -> String -> Int -> BigInteger -> HTML p action
marloweCurrencyInput classes f currencyLabel numDecimals current = currencyInput classes current currencyLabel numDecimals (Just <<< f <<< fromMaybe zero)

marloweActionInput :: forall p action. Array String -> (BigInteger -> action) -> BigInteger -> HTML p action
marloweActionInput classes f current = marloweCurrencyInput classes f "" 0 current

renderDeposit :: forall p. MetaData -> AccountId -> Party -> Token -> BigInteger -> HTML p Action
renderDeposit metadata accountOwner party tok money =
Expand Down Expand Up @@ -561,7 +554,7 @@ inputToLine metadata (SlotInterval start end) (IChoice (ChoiceId choiceName choi
[ text "Participant "
, strong_ [ renderPrettyParty metadata choiceOwner ]
, text " chooses the value "
, strong_ [ text (showPrettyMoney chosenNum) ]
, strong_ [ text (showPrettyChoice (getChoiceFormat metadata choiceName) chosenNum) ]
, text " for choice with id "
, strong_ [ text (show choiceName) ]
]
Expand Down
5 changes: 4 additions & 1 deletion web-common-marlowe/src/Marlowe/Extended/Metadata.purs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Data.Lens (Lens')
import Data.Lens.Record (prop)
import Data.Map (Map, keys)
import Data.Map as Map
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Data.Set (Set)
import Data.Set as Set
import Data.Symbol (SProxy(..))
Expand Down Expand Up @@ -149,6 +149,9 @@ emptyContractMetadata =
, choiceInfo: mempty
}

getChoiceFormat :: MetaData -> String -> ChoiceFormat
getChoiceFormat { choiceInfo } choiceName = maybe DefaultFormat (\choiceInfoVal -> choiceInfoVal.choiceFormat) $ Map.lookup choiceName choiceInfo

type MetadataHintInfo
= { roles :: Set S.TokenName
, slotParameters :: Set String
Expand Down

0 comments on commit 4a62150

Please sign in to comment.