/
MainFrame.purs
263 lines (241 loc) · 8.98 KB
/
MainFrame.purs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
module MainFrame
( initialMainFrame
, handleQuery
, handleAction
, initialState
) where
import Prelude hiding (div)
import Animation (class MonadAnimate, animate)
import Chain.Eval (handleAction) as Chain
import Chain.Types (Action(..), AnnotatedBlockchain(..), _chainFocusAppearing)
import Chain.Types (initialState) as Chain
import Clipboard (class MonadClipboard)
import Control.Monad.Reader (runReaderT)
import Control.Monad.State (class MonadState)
import Control.Monad.State.Extra (zoomStateT)
import Data.Array (filter)
import Data.Either (Either(..))
import Data.Lens (_1, _2, assign, findOf, modifying, to, traversed, use, view)
import Data.Lens.At (at)
import Data.Lens.Extra (peruse, toSetOf)
import Data.Lens.Index (ix)
import Data.Map as Map
import Data.Maybe (Maybe(..))
import Data.RawJson (RawJson(..))
import Data.Set (Set)
import Data.Set as Set
import Data.Traversable (for_, sequence, traverse_)
import Data.Tuple (Tuple(..))
import Effect.Aff.Class (class MonadAff)
import Foreign.Generic (encodeJSON)
import Halogen (Component, hoist)
import Halogen as H
import Halogen.HTML (HTML)
import Language.Plutus.Contract.Effects.ExposeEndpoint (EndpointDescription)
import Ledger.Ada (Ada(..))
import Ledger.Extra (adaToValue)
import Ledger.Value (Value)
import MonadApp (class MonadApp, activateContract, getFullReport, invokeEndpoint, log, runHalogenApp, sendWebSocketMessage)
import Network.RemoteData (RemoteData(..), _Success)
import Network.RemoteData as RemoteData
import Playground.Lenses (_endpointDescription, _schema)
import Playground.Types (FunctionSchema(..), _FunctionSchema)
import Plutus.SCB.Events.Contract (ContractInstanceState(..))
import Plutus.SCB.Types (ContractExe)
import Plutus.SCB.Webserver (SPParams_(..))
import Plutus.SCB.Webserver.Types (ContractReport, ContractSignatureResponse(..), StreamToClient(..), StreamToServer(..))
import Prim.TypeError (class Warn, Text)
import Schema (FormSchema)
import Schema.Types (formArgumentToJson, toArgument)
import Schema.Types as Schema
import Servant.PureScript.Settings (SPSettings_, defaultSettings)
import Types (EndpointForm, HAction(..), Output, Query(..), State(..), View(..), WebData, _annotatedBlockchain, _chainReport, _chainState, _contractActiveEndpoints, _contractReport, _contractSignatures, _contractStates, _crAvailableContracts, _csContract, _csCurrentState, _currentView, _events, _webSocketMessage)
import Validation (_argument)
import View as View
import WebSocket.Support as WS
initialValue :: Value
initialValue = adaToValue $ Lovelace { getLovelace: 0 }
initialState :: State
initialState =
State
{ currentView: ActiveContracts
, contractReport: NotAsked
, chainReport: NotAsked
, events: NotAsked
, chainState: Chain.initialState
, contractSignatures: Map.empty
, webSocketMessage: NotAsked
}
------------------------------------------------------------
ajaxSettings :: SPSettings_ SPParams_
ajaxSettings = defaultSettings $ SPParams_ { baseURL: "/" }
initialMainFrame ::
forall m.
MonadAff m =>
MonadClipboard m =>
Component HTML Query HAction Output m
initialMainFrame =
hoist (flip runReaderT ajaxSettings)
$ H.mkComponent
{ initialState: const initialState
, render: View.render
, eval:
H.mkEval
{ handleAction: runHalogenApp <<< handleAction
, handleQuery: runHalogenApp <<< handleQuery
, initialize: Just Init
, receive: const Nothing
, finalize: Nothing
}
}
handleQuery ::
forall m a.
Warn (Text "Handle WebSocket errors.") =>
Warn (Text "Handle WebSocket disconnections.") =>
MonadState State m =>
MonadApp m =>
Query a -> m (Maybe a)
handleQuery (ReceiveWebSocketMessage (WS.ReceiveMessage msg) next) = do
case msg of
Right (NewChainReport report) -> assign (_chainReport <<< _Success) report
Right (NewContractReport report) -> do
assign (_contractReport <<< _Success) report
traverse_ updateFormsForContractInstance
(view _contractStates report)
Right (NewChainEvents events) -> assign (_events <<< _Success) events
Right (Echo _) -> pure unit
Right (ErrorResponse _) -> pure unit
Left err -> pure unit
assign _webSocketMessage $ RemoteData.fromEither msg
pure $ Just next
handleQuery (ReceiveWebSocketMessage WS.WebSocketClosed next) = do
log "Closed"
pure $ Just next
handleAction ::
forall m.
MonadApp m =>
MonadAnimate m State =>
MonadClipboard m =>
MonadState State m =>
HAction -> m Unit
handleAction Init = handleAction LoadFullReport
handleAction (ChangeView view) = do
sendWebSocketMessage $ Ping $ show view
assign _currentView view
handleAction (ActivateContract contract) = activateContract contract
handleAction LoadFullReport = do
assignFullReportData Loading
fullReportResult <- getFullReport
assignFullReportData fullReportResult
for_ fullReportResult
( \report ->
traverse_ updateFormsForContractInstance
(view (_contractReport <<< _contractStates) report)
)
where
assignFullReportData v = do
assign _contractReport (view _contractReport <$> v)
assign _chainReport (view _chainReport <$> v)
assign _events (view _events <$> v)
handleAction (ChainAction subaction) = do
mAnnotatedBlockchain <-
peruse (_chainReport <<< _Success <<< _annotatedBlockchain <<< to AnnotatedBlockchain)
let
wrapper ::
Warn (Text "The question, 'Should we animate this?' feels like it belongs in the Chain module. Not here.") =>
m Unit -> m Unit
wrapper = case subaction of
(FocusTx _) -> animate (_chainState <<< _chainFocusAppearing)
_ -> identity
wrapper
$ zoomStateT _chainState
$ Chain.handleAction subaction mAnnotatedBlockchain
handleAction (ChangeContractEndpointCall contractInstanceId endpointIndex subaction) = do
modifying
( _contractSignatures
<<< ix contractInstanceId
<<< _Success
<<< _2
<<< ix endpointIndex
<<< _argument
)
(Schema.handleFormEvent initialValue subaction)
handleAction (InvokeContractEndpoint contractInstanceId endpointForm) = do
let
endpointDescription :: EndpointDescription
endpointDescription = view (_schema <<< _FunctionSchema <<< _endpointDescription) endpointForm
encodedForm :: Maybe RawJson
encodedForm = RawJson <<< encodeJSON <$> formArgumentToJson (view _argument endpointForm)
for_ encodedForm
$ \argument -> do
assign (_contractSignatures <<< at contractInstanceId) (Just Loading)
invokeEndpoint argument contractInstanceId endpointDescription
updateFormsForContractInstance ::
forall m.
MonadState State m =>
ContractInstanceState ContractExe -> m Unit
updateFormsForContractInstance newContractInstance = do
let
csContractId = view _csContract newContractInstance
oldContractInstance :: Maybe (ContractInstanceState ContractExe) <-
peruse
( _contractSignatures
<<< ix csContractId
<<< _Success
<<< _1
)
when (oldContractInstance /= Just newContractInstance)
$ do
contractReport :: WebData (ContractReport ContractExe) <- use _contractReport
let
newForms :: Maybe (WebData (Array EndpointForm))
newForms = sequence $ createNewEndpointForms <$> contractReport <*> pure newContractInstance
assign (_contractSignatures <<< at csContractId)
(map (Tuple newContractInstance) <$> newForms)
createNewEndpointForms ::
ContractReport ContractExe ->
ContractInstanceState ContractExe ->
Maybe (Array EndpointForm)
createNewEndpointForms contractReport instanceState =
let
matchingSignature :: Maybe (ContractSignatureResponse ContractExe)
matchingSignature = getMatchingSignature instanceState contractReport
in
createEndpointForms instanceState <$> matchingSignature
createEndpointForms ::
forall t.
ContractInstanceState t ->
ContractSignatureResponse t ->
Array EndpointForm
createEndpointForms contractState = signatureToForms
where
activeEndpoints :: Set EndpointDescription
activeEndpoints =
toSetOf
( _csCurrentState
<<< _contractActiveEndpoints
)
contractState
isActive :: FunctionSchema FormSchema -> Boolean
isActive (FunctionSchema { endpointDescription }) = Set.member endpointDescription activeEndpoints
signatureToForms :: ContractSignatureResponse t -> Array EndpointForm
signatureToForms (ContractSignatureResponse { csrSchemas }) = signatureToForm <$> filter isActive csrSchemas
signatureToForm :: FunctionSchema FormSchema -> EndpointForm
signatureToForm schema =
{ argument: toArgument initialValue $ view (_FunctionSchema <<< _argument) schema
, schema
}
getMatchingSignature ::
forall t.
Eq t =>
ContractInstanceState t ->
ContractReport t ->
Maybe (ContractSignatureResponse t)
getMatchingSignature (ContractInstanceState { csContractDefinition }) =
findOf
( _crAvailableContracts
<<< traversed
)
isMatch
where
isMatch (ContractSignatureResponse { csrDefinition }) = csrDefinition == csContractDefinition