diff --git a/marlowe-dashboard-client/src/Component/Contacts/State.purs b/marlowe-dashboard-client/src/Component/Contacts/State.purs index 003a921a8a..2a5aaf15df 100644 --- a/marlowe-dashboard-client/src/Component/Contacts/State.purs +++ b/marlowe-dashboard-client/src/Component/Contacts/State.purs @@ -86,7 +86,7 @@ handleAction addressBook (SetCardSection cardSection) = do $ InputField.SetValidator $ either Just (const Nothing) <<< lmap walletNicknameErrorToLegacyError - <<< WN.fromString (AddressBook.nicknames addressBook) + <<< WN.fromStringExclusive (AddressBook.nicknames addressBook) handleAction addressBook $ AddressInputAction InputField.Reset handleAction addressBook $ AddressInputAction @@ -102,7 +102,7 @@ handleAction _ (SaveWallet mTokenName) = do addressString <- use (_addressInput <<< _value) let result = Tuple - <$> hush (WN.fromString Set.empty walletNicknameString) + <$> hush (WN.fromString walletNicknameString) <*> hush (A.fromString Set.empty addressString) case result of Just (Tuple walletNickname address) -> do @@ -189,6 +189,7 @@ walletNicknameErrorToLegacyError walletNicknameErrorToLegacyError = case _ of WN.Empty -> EmptyWalletNickname WN.Exists -> DuplicateWalletNickname + WN.DoesNotExist -> DuplicateWalletNickname WN.ContainsNonAlphaNumeric -> BadWalletNickname addressErrorToLegacyError :: A.AddressError -> AddressError diff --git a/marlowe-dashboard-client/src/Component/ContractSetupForm.purs b/marlowe-dashboard-client/src/Component/ContractSetupForm.purs new file mode 100644 index 0000000000..fef38708ee --- /dev/null +++ b/marlowe-dashboard-client/src/Component/ContractSetupForm.purs @@ -0,0 +1,161 @@ +module Component.ContractSetupForm where + +import Prologue + +import Data.Address (Address) +import Data.AddressBook (AddressBook) +import Data.AddressBook as AddressBook +import Data.ContractNickname (ContractNickname) +import Data.ContractNickname as CN +import Data.ContractTimeout (ContractTimeout) +import Data.ContractTimeout as CT +import Data.ContractValue (ContractValue) +import Data.ContractValue as CV +import Data.Either (note) +import Data.Lens (Lens') +import Data.Lens.Record (prop) +import Data.Map (Map) +import Data.Map as Map +import Data.Set (Set) +import Data.Set as Set +import Data.Validation.Semigroup (V(..)) +import Data.WalletNickname (WalletNicknameError(..)) +import Data.WalletNickname as WN +import Effect.Aff.Class (class MonadAff) +import Forms (InputSlots) +import Forms as Forms +import Halogen as H +import Halogen.Form (Form, subform, useForm) +import Halogen.Form as Form +import Halogen.Hooks as Hooks +import Marlowe.Semantics (TokenName) +import Polyform.Validator (liftFnV) +import Type.Proxy (Proxy(..)) + +type Input = + { roles :: Set TokenName + , timeouts :: Set String + , values :: Set String + , addressBook :: AddressBook + } + +type ContractInput = + { nickname :: String + , roles :: Map TokenName String + , timeouts :: Map String String + , values :: Map String String + } + +_nickname :: Lens' ContractInput String +_nickname = prop (Proxy :: _ "nickname") + +_roles :: Lens' ContractInput (Map TokenName String) +_roles = prop (Proxy :: _ "roles") + +_timeouts :: Lens' ContractInput (Map String String) +_timeouts = prop (Proxy :: _ "timeouts") + +_values :: Lens' ContractInput (Map String String) +_values = prop (Proxy :: _ "values") + +data ContractParams = + ContractParams + ContractNickname + (Map TokenName Address) + (Map String ContractTimeout) + (Map String ContractValue) + +derive instance Eq ContractParams + +data Msg + = Back + | Next ContractParams + +type Component q m = + H.Component q Input Msg m + +contractNicknameForm + :: forall s m. Monad m => Form (InputSlots s) m String ContractNickname +contractNicknameForm = + Forms.input "contract-nickname" "Contract title" CN.validator case _ of + CN.Empty -> "Required." + +roleAssignmentForm + :: forall s m + . Monad m + => AddressBook + -> TokenName + -> Form (InputSlots s) m String Address +roleAssignmentForm addressBook roleName = + Forms.input ("role-input-" <> roleName) roleName validator case _ of + WN.Empty -> "Required." + WN.Exists -> "Already exists." + WN.DoesNotExist -> "Not found." + WN.ContainsNonAlphaNumeric -> "Can only contain letters and digits." + where + validator = + liftFnV $ + V <<< + ( note WN.DoesNotExist <<< flip AddressBook.lookupAddress addressBook + <=< WN.fromString + ) + +roleForms + :: forall s m + . Monad m + => AddressBook + -> Form (InputSlots s) m (Map TokenName String) (Map TokenName Address) +roleForms addressBook = + Form.multiWithIndex $ roleAssignmentForm $ addressBook + +timeoutForm + :: forall s m + . Monad m + => String + -> Form (InputSlots s) m String ContractTimeout +timeoutForm name = + Forms.input ("slot-input-" <> name) name CT.validator case _ of + CT.Empty -> "Required." + CT.Past -> "Must be in the future." + CT.Invalid -> "Must be a number of slots from contract start." + +valueForm + :: forall s m + . Monad m + => String + -> Form (InputSlots s) m String ContractValue +valueForm name = + Forms.input ("value-input-" <> name) name CV.validator case _ of + CV.Empty -> "Required." + CV.Negative -> "Must by positive." + CV.Invalid -> "Must by a number." + +mkForm + :: forall slots m + . Monad m + => AddressBook + -> Form (InputSlots slots) m ContractInput ContractParams +mkForm addressBook = + ContractParams + <$> subform _nickname contractNicknameForm + <*> subform _roles (roleForms addressBook) + <*> subform _timeouts (Form.multiWithIndex timeoutForm) + <*> subform _values (Form.multiWithIndex valueForm) + +initialFormInput :: Set TokenName -> Set String -> Set String -> ContractInput +initialFormInput roles timeouts values = + { nickname: "" + , roles: Map.fromFoldable $ Set.map (flip Tuple "") roles + , timeouts: Map.fromFoldable $ Set.map (flip Tuple "") timeouts + , values: Map.fromFoldable $ Set.map (flip Tuple "") values + } + +component :: forall q m. MonadAff m => Component q m +component = Hooks.component \{ outputToken } input -> Hooks.do + let { addressBook, roles, timeouts, values } = input + form <- Hooks.captures { addressBook } Hooks.useMemo \_ -> mkForm addressBook + { result, html } <- useForm form (initialFormInput roles timeouts values) + let back = Hooks.raise outputToken Back + let next = Hooks.raise outputToken <<< Next <$> result + Hooks.pure do + html [] diff --git a/marlowe-dashboard-client/src/Component/Template/State.purs b/marlowe-dashboard-client/src/Component/Template/State.purs index d4a59bf575..387f54e094 100644 --- a/marlowe-dashboard-client/src/Component/Template/State.purs +++ b/marlowe-dashboard-client/src/Component/Template/State.purs @@ -146,7 +146,7 @@ handleAction _ (ContractNicknameInputAction inputFieldAction) = handleAction input@{ addressBook } UpdateRoleWalletValidators = setInputValidators input _roleWalletInputs RoleWalletInputAction - $ roleError addressBook <<< hush <<< WN.fromString mempty + $ roleError addressBook <<< hush <<< WN.fromString handleAction _ (RoleWalletInputAction tokenName inputFieldAction) = toRoleWalletInput tokenName $ InputField.handleAction inputFieldAction diff --git a/marlowe-dashboard-client/src/Data/ContractNickname.purs b/marlowe-dashboard-client/src/Data/ContractNickname.purs new file mode 100644 index 0000000000..fdb2f6f9dd --- /dev/null +++ b/marlowe-dashboard-client/src/Data/ContractNickname.purs @@ -0,0 +1,103 @@ +module Data.ContractNickname + ( ContractNickname + , ContractNicknameError(..) + , dual + , fromFoldable + , fromString + , validator + , toString + ) where + +import Prologue + +import Data.Argonaut + ( class DecodeJson + , class EncodeJson + , JsonDecodeError(..) + , decodeJson + ) +import Data.Bifunctor (lmap) +import Data.Bounded.Generic (genericBottom, genericTop) +import Data.Enum (class BoundedEnum, class Enum) +import Data.Enum.Generic + ( genericCardinality + , genericFromEnum + , genericPred + , genericSucc + , genericToEnum + ) +import Data.Foldable (class Foldable) +import Data.Generic.Rep (class Generic) +import Data.Set (Set) +import Data.Set as Set +import Data.Show.Generic (genericShow) +import Data.String (null) +import Data.Validation.Semigroup (V(..)) +import Polyform (Validator) +import Polyform.Dual as Dual +import Polyform.Validator (liftFnV) +import Polyform.Validator.Dual (Dual) + +data ContractNicknameError = Empty + +derive instance genericContractNicknameError :: Generic ContractNicknameError _ +derive instance eqContractNicknameError :: Eq ContractNicknameError +derive instance ordContractNicknameError :: Ord ContractNicknameError + +instance semigroupContractNicknameError :: Semigroup ContractNicknameError where + append _ _ = Empty + +instance boundedContractNicknameError :: Bounded ContractNicknameError where + bottom = genericBottom + top = genericTop + +instance enumContractNicknameError :: Enum ContractNicknameError where + succ = genericSucc + pred = genericPred + +instance boundedEnumContractNicknameError :: BoundedEnum ContractNicknameError where + cardinality = genericCardinality + toEnum = genericToEnum + fromEnum = genericFromEnum + +instance showContractNicknameError :: Show ContractNicknameError where + show = genericShow + +newtype ContractNickname = ContractNickname String + +derive instance Eq ContractNickname +derive instance Ord ContractNickname +derive newtype instance Show ContractNickname +derive newtype instance EncodeJson ContractNickname + +instance DecodeJson ContractNickname where + decodeJson = + lmap (const $ TypeMismatch "ContractNickname") <<< fromString + <=< decodeJson + +fromFoldable :: forall f. Foldable f => f String -> Set ContractNickname +fromFoldable = Set.map ContractNickname <<< Set.fromFoldable + +fromString :: String -> Either ContractNicknameError ContractNickname +fromString s + | null s = Left Empty + | otherwise = Right $ ContractNickname s + +toString :: ContractNickname -> String +toString (ContractNickname s) = s + +------------------------------------------------------------------------------- +-- Polyform adapters +------------------------------------------------------------------------------- + +validator + :: forall m + . Applicative m + => Validator m ContractNicknameError String ContractNickname +validator = liftFnV \s -> V $ fromString s + +dual + :: forall m + . Applicative m + => Dual m ContractNicknameError String ContractNickname +dual = Dual.dual validator (pure <<< toString) diff --git a/marlowe-dashboard-client/src/Data/ContractTimeout.purs b/marlowe-dashboard-client/src/Data/ContractTimeout.purs new file mode 100644 index 0000000000..d078738469 --- /dev/null +++ b/marlowe-dashboard-client/src/Data/ContractTimeout.purs @@ -0,0 +1,117 @@ +module Data.ContractTimeout + ( ContractTimeout + , ContractTimeoutError(..) + , dual + , fromInt + , fromString + , validator + , toString + , toInt + ) where + +import Prologue + +import Data.Argonaut + ( class DecodeJson + , class EncodeJson + , JsonDecodeError(..) + , decodeJson + ) +import Data.Bifunctor (lmap) +import Data.BigInt.Argonaut (BigInt) +import Data.BigInt.Argonaut as BigInt +import Data.Bounded.Generic (genericBottom, genericTop) +import Data.Enum (class BoundedEnum, class Enum) +import Data.Enum.Generic + ( genericCardinality + , genericFromEnum + , genericPred + , genericSucc + , genericToEnum + ) +import Data.Generic.Rep (class Generic) +import Data.Show.Generic (genericShow) +import Data.String (null) +import Data.Validation.Semigroup (V(..)) +import Polyform (Validator) +import Polyform.Dual as Dual +import Polyform.Validator (liftFnV) +import Polyform.Validator.Dual (Dual) + +data ContractTimeoutError + = Empty + | Past + | Invalid + +derive instance genericContractTimeoutError :: Generic ContractTimeoutError _ +derive instance eqContractTimeoutError :: Eq ContractTimeoutError +derive instance ordContractTimeoutError :: Ord ContractTimeoutError + +instance semigroupContractTimeoutError :: Semigroup ContractTimeoutError where + append Empty _ = Empty + append _ Empty = Empty + append Past _ = Past + append _ Past = Past + append Invalid Invalid = Invalid + +instance boundedContractTimeoutError :: Bounded ContractTimeoutError where + bottom = genericBottom + top = genericTop + +instance enumContractTimeoutError :: Enum ContractTimeoutError where + succ = genericSucc + pred = genericPred + +instance boundedEnumContractTimeoutError :: BoundedEnum ContractTimeoutError where + cardinality = genericCardinality + toEnum = genericToEnum + fromEnum = genericFromEnum + +instance showContractTimeoutError :: Show ContractTimeoutError where + show = genericShow + +newtype ContractTimeout = ContractTimeout BigInt + +derive instance Eq ContractTimeout +derive instance Ord ContractTimeout +derive newtype instance Show ContractTimeout +derive newtype instance EncodeJson ContractTimeout + +instance DecodeJson ContractTimeout where + decodeJson = + lmap (const $ TypeMismatch "ContractTimeout") <<< fromString + <=< decodeJson + +fromString :: String -> Either ContractTimeoutError ContractTimeout +fromString s + | null s = Left Empty + | otherwise = case BigInt.fromString s of + Nothing -> Left Invalid + Just i -> fromInt i + +fromInt :: BigInt -> Either ContractTimeoutError ContractTimeout +fromInt i + | i < zero = Left Past + | otherwise = Right $ ContractTimeout i + +toString :: ContractTimeout -> String +toString = BigInt.toString <<< toInt + +toInt :: ContractTimeout -> BigInt +toInt (ContractTimeout i) = i + +------------------------------------------------------------------------------- +-- Polyform adapters +------------------------------------------------------------------------------- + +validator + :: forall m + . Applicative m + => Validator m ContractTimeoutError String ContractTimeout +validator = liftFnV \s -> V $ fromString s + +dual + :: forall m + . Applicative m + => Dual m ContractTimeoutError String ContractTimeout +dual = Dual.dual validator (pure <<< toString) diff --git a/marlowe-dashboard-client/src/Data/ContractValue.purs b/marlowe-dashboard-client/src/Data/ContractValue.purs new file mode 100644 index 0000000000..93e04f3bbf --- /dev/null +++ b/marlowe-dashboard-client/src/Data/ContractValue.purs @@ -0,0 +1,144 @@ +module Data.ContractValue + ( ContractValue + , ContractValueError(..) + , dual + , fromBigInt + , fromString + , validator + , toString + , toBigInt + ) where + +import Prologue + +import Data.Argonaut + ( class DecodeJson + , class EncodeJson + , JsonDecodeError(..) + , decodeJson + ) +import Data.Array (replicate) +import Data.Bifunctor (lmap) +import Data.BigInt.Argonaut (BigInt) +import Data.BigInt.Argonaut as BigInt +import Data.Bounded.Generic (genericBottom, genericTop) +import Data.Enum (class BoundedEnum, class Enum) +import Data.Enum.Generic + ( genericCardinality + , genericFromEnum + , genericPred + , genericSucc + , genericToEnum + ) +import Data.Generic.Rep (class Generic) +import Data.Show.Generic (genericShow) +import Data.String (Pattern(..), null, split) +import Data.String as String +import Data.String.CodeUnits (dropRight, fromCharArray) +import Data.Validation.Semigroup (V(..)) +import Polyform (Validator) +import Polyform.Dual as Dual +import Polyform.Validator (liftFnV) +import Polyform.Validator.Dual (Dual) + +data ContractValueError + = Empty + | Negative + | Invalid + +derive instance genericContractValueError :: Generic ContractValueError _ +derive instance eqContractValueError :: Eq ContractValueError +derive instance ordContractValueError :: Ord ContractValueError + +instance semigroupContractValueError :: Semigroup ContractValueError where + append Empty _ = Empty + append _ Empty = Empty + append Negative _ = Negative + append _ Negative = Negative + append Invalid Invalid = Invalid + +instance boundedContractValueError :: Bounded ContractValueError where + bottom = genericBottom + top = genericTop + +instance enumContractValueError :: Enum ContractValueError where + succ = genericSucc + pred = genericPred + +instance boundedEnumContractValueError :: BoundedEnum ContractValueError where + cardinality = genericCardinality + toEnum = genericToEnum + fromEnum = genericFromEnum + +instance showContractValueError :: Show ContractValueError where + show = genericShow + +newtype ContractValue = ContractValue BigInt + +derive instance Eq ContractValue +derive instance Ord ContractValue +derive newtype instance Show ContractValue +derive newtype instance EncodeJson ContractValue + +instance DecodeJson ContractValue where + decodeJson = + lmap (const $ TypeMismatch "ContractValue") <<< fromString + <=< decodeJson + +fromString :: String -> Either ContractValueError ContractValue +fromString s + | null s = Left Empty + | otherwise = case split (Pattern ".") s of + [ s' ] -> case BigInt.fromString s' of + Nothing -> Left Invalid + Just i -> fromBigInt $ i * BigInt.fromInt 1000000 + [ whole, fractional ] -> + case BigInt.fromString whole of + Nothing -> Left Invalid + Just whole' -> fromFractional whole' fractional + _ -> Left Invalid + | otherwise = Left Invalid + +fromFractional :: BigInt -> String -> Either ContractValueError ContractValue +fromFractional whole fractional = case BigInt.fromString normalized of + Nothing -> Left Invalid + Just fractional' -> Right $ ContractValue + (whole * BigInt.fromInt 1000000 + fractional') + where + normalized = case compare (String.length fractional) 6 of + GT -> dropRight (6 - String.length fractional) fractional + LT -> pad (String.length fractional - 6) fractional + EQ -> fractional + pad num value = value <> fromCharArray (replicate num '0') + +fromBigInt :: BigInt -> Either ContractValueError ContractValue +fromBigInt i + | i < zero = Left Negative + | otherwise = Right $ ContractValue i + +toString :: ContractValue -> String +toString (ContractValue i) = + BigInt.toString whole <> "." <> normalized (BigInt.toString fractional) + where + whole = i / BigInt.fromInt 1000000 + fractional = i `mod` BigInt.fromInt 1000000 + normalized s = fromCharArray (replicate (String.length s - 6) '0') <> s + +toBigInt :: ContractValue -> BigInt +toBigInt (ContractValue i) = i + +------------------------------------------------------------------------------- +-- Polyform adapters +------------------------------------------------------------------------------- + +validator + :: forall m + . Applicative m + => Validator m ContractValueError String ContractValue +validator = liftFnV \s -> V $ fromString s + +dual + :: forall m + . Applicative m + => Dual m ContractValueError String ContractValue +dual = Dual.dual validator (pure <<< toString) diff --git a/marlowe-dashboard-client/src/Data/WalletNickname.purs b/marlowe-dashboard-client/src/Data/WalletNickname.purs index c2bef7d806..ec0d9e8bb2 100644 --- a/marlowe-dashboard-client/src/Data/WalletNickname.purs +++ b/marlowe-dashboard-client/src/Data/WalletNickname.purs @@ -1,11 +1,15 @@ module Data.WalletNickname ( WalletNickname , WalletNicknameError(..) - , dual + , dualExclusive + , dualInclusive , fromFoldable + , fromStringExclusive + , fromStringInclusive , fromString , new - , validator + , validatorExclusive + , validatorInclusive , toString ) where @@ -47,6 +51,7 @@ data WalletNicknameError = Empty | ContainsNonAlphaNumeric | Exists + | DoesNotExist derive instance genericWalletNicknameError :: Generic WalletNicknameError _ derive instance eqWalletNicknameError :: Eq WalletNicknameError @@ -57,7 +62,9 @@ instance semigroupWalletNicknameError :: Semigroup WalletNicknameError where append _ Empty = Empty append ContainsNonAlphaNumeric _ = ContainsNonAlphaNumeric append _ ContainsNonAlphaNumeric = ContainsNonAlphaNumeric - append Exists Exists = Exists + append Exists _ = Exists + append _ Exists = Exists + append DoesNotExist DoesNotExist = DoesNotExist instance boundedWalletNicknameError :: Bounded WalletNicknameError where bottom = genericBottom @@ -84,8 +91,7 @@ derive newtype instance EncodeJson WalletNickname instance DecodeJson WalletNickname where decodeJson = - lmap (const $ TypeMismatch "WalletId") <<< fromString Set.empty - <=< decodeJson + lmap (const $ TypeMismatch "WalletNickname") <<< fromString <=< decodeJson nicknameRegex :: Regex nicknameRegex = unsafeRegex "^[a-z0-9]+$" ignoreCase @@ -96,13 +102,23 @@ new = WalletNickname "newWallet" fromFoldable :: forall f. Foldable f => f String -> Set WalletNickname fromFoldable = Set.map WalletNickname <<< Set.fromFoldable -fromString - :: Set WalletNickname -> String -> Either WalletNicknameError WalletNickname -fromString used s +fromString :: String -> Either WalletNicknameError WalletNickname +fromString s | null s = Left Empty + | not $ Regex.test nicknameRegex s = Left ContainsNonAlphaNumeric + | otherwise = Right $ WalletNickname s + +fromStringInclusive + :: Set WalletNickname -> String -> Either WalletNicknameError WalletNickname +fromStringInclusive used s + | Set.member (WalletNickname s) used = fromString s + | otherwise = Left DoesNotExist + +fromStringExclusive + :: Set WalletNickname -> String -> Either WalletNicknameError WalletNickname +fromStringExclusive used s | Set.member (WalletNickname s) used = Left Exists - | Regex.test nicknameRegex s = Right $ WalletNickname s - | otherwise = Left ContainsNonAlphaNumeric + | otherwise = fromString s toString :: WalletNickname -> String toString (WalletNickname s) = s @@ -111,16 +127,30 @@ toString (WalletNickname s) = s -- Polyform adapters ------------------------------------------------------------------------------- -validator +validatorExclusive + :: forall m + . Applicative m + => Set WalletNickname + -> Validator m WalletNicknameError String WalletNickname +validatorExclusive used = liftFnV \s -> V $ fromStringExclusive used s + +dualExclusive + :: forall m + . Applicative m + => Set WalletNickname + -> Dual m WalletNicknameError String WalletNickname +dualExclusive used = Dual.dual (validatorExclusive used) (pure <<< toString) + +validatorInclusive :: forall m . Applicative m => Set WalletNickname -> Validator m WalletNicknameError String WalletNickname -validator used = liftFnV \s -> V $ fromString used s +validatorInclusive used = liftFnV \s -> V $ fromStringInclusive used s -dual +dualInclusive :: forall m . Applicative m => Set WalletNickname -> Dual m WalletNicknameError String WalletNickname -dual used = Dual.dual (validator used) (pure <<< toString) +dualInclusive used = Dual.dual (validatorInclusive used) (pure <<< toString) diff --git a/marlowe-dashboard-client/src/Forms.purs b/marlowe-dashboard-client/src/Forms.purs index 6812220a21..44ea9a0df8 100644 --- a/marlowe-dashboard-client/src/Forms.purs +++ b/marlowe-dashboard-client/src/Forms.purs @@ -172,10 +172,12 @@ walletNickname => Set WalletNickname -> Form (InputSlots s) m String WalletNickname walletNickname used = - input "wallet-nickname" "Wallet nickname" (WN.validator used) case _ of - WN.Empty -> "Required." - WN.Exists -> "Already exists." - WN.ContainsNonAlphaNumeric -> "Can only contain letters and digits." + input "wallet-nickname" "Wallet nickname" (WN.validatorExclusive used) + case _ of + WN.Empty -> "Required." + WN.Exists -> "Already exists." + WN.DoesNotExist -> "Not found." + WN.ContainsNonAlphaNumeric -> "Can only contain letters and digits." type MnemonicPhraseInput = AsyncInput String MnemonicPhraseError MnemonicPhrase diff --git a/marlowe-dashboard-client/src/Halogen/Form.purs b/marlowe-dashboard-client/src/Halogen/Form.purs index ab39c22bc6..464b7a28cf 100644 --- a/marlowe-dashboard-client/src/Halogen/Form.purs +++ b/marlowe-dashboard-client/src/Halogen/Form.purs @@ -7,6 +7,8 @@ module Halogen.Form , hoistForm , split , subform + , multiWithIndex + , multi ) where import Prelude @@ -19,9 +21,11 @@ import Control.Monad.Writer (WriterT(..), mapWriterT) import Data.Bifunctor (bimap, lmap) import Data.Foldable (for_) import Data.Lens (Lens', _1, _2, set, view) +import Data.Lens.Index (class Index, ix) import Data.Maybe (Maybe(..)) import Data.Newtype (over, unwrap) import Data.Profunctor.Star (Star(..)) +import Data.TraversableWithIndex (class TraversableWithIndex, traverseWithIndex) import Data.Tuple (Tuple(..)) import Effect.AVar (AVar) import Effect.Aff.AVar as AVar @@ -48,6 +52,13 @@ type Form slots m input a = input a +type FormEvalResult slots m input a = + MaybeT + ( WriterT (Array (H.ComponentHTML input slots m)) + (StateT FormState (Free (FormF input m))) + ) + a + form :: forall s m i a . Functor m @@ -63,29 +74,60 @@ split -> Form s m (Tuple i j) (Tuple a b) split f g = Tuple <$> subform _1 f <*> subform _2 g -subform - :: forall s m i j a +multiWithIndex + :: forall slots m t index i a + . TraversableWithIndex index t + => Index (t i) index i + => Monad m + => (index -> Form slots m i a) + -> Form slots m (t i) (t a) +multiWithIndex getItemForm = + Reporter $ Star \ti -> traverseWithIndex (runItemForm ti) ti + where + runItemForm ti index = + let + Reporter (Star itemForm) = getItemForm index + in + mapFormResults (\i -> set (ix index) i ti) <<< itemForm + +multi + :: forall slots m t index i a + . TraversableWithIndex index t + => Index (t i) index i + => Monad m + => Form slots m i a + -> Form slots m (t i) (t a) +multi = multiWithIndex <<< const + +mapFormResults + :: forall slots m i j a . Functor m - => Lens' i j - -> Form s m j a - -> Form s m i a -subform lens (Reporter (Star f)) = Reporter $ Star \i -> + => (j -> i) + -> FormEvalResult slots m j a + -> FormEvalResult slots m i a +mapFormResults f = let - adaptAction j = set lens j i - adaptFormF :: FormF j m ~> FormF i m - adaptFormF (Update j a) = Update (set lens j i) a + adaptFormF (Update j a) = Update (f j) a adaptFormF (Lift m) = Lift m in mapMaybeT ( mapWriterT ( mapStateT ( hoistFree adaptFormF <<< map - (lmap (map (map (bimap (map adaptAction) adaptAction)))) + (lmap (map (map (bimap (map f) f)))) ) ) ) - (f (view lens i)) + +subform + :: forall s m i j a + . Functor m + => Lens' i j + -> Form s m j a + -> Form s m i a +subform lens (Reporter (Star f)) = Reporter $ Star \i -> + mapFormResults (flip (set lens) i) $ f (view lens i) hoistForm :: forall i s m1 m2 a diff --git a/marlowe-dashboard-client/src/Page/Dashboard/State.purs b/marlowe-dashboard-client/src/Page/Dashboard/State.purs index f2b8322099..97d9805c52 100644 --- a/marlowe-dashboard-client/src/Page/Dashboard/State.purs +++ b/marlowe-dashboard-client/src/Page/Dashboard/State.purs @@ -461,7 +461,7 @@ handleAction input@{ currentSlot, addressBook } (TemplateAction templateAction) roleWalletInputs <- use (_templateState <<< _roleWalletInputs) let roleWallets = - filterMap (hush <<< WN.fromString mempty <<< view _value) + filterMap (hush <<< WN.fromString <<< view _value) roleWalletInputs roles = mapMaybe (flip AB.lookupAddress addressBook) roleWallets