@@ -1,22 +1,28 @@
1−module Ur.Run exposing (Model, Msg, Program, application)
1+module Ur.Run exposing (element, document, application, Program, Model, Msg)
2+
3+{-| This module contains functions to run your application with Urbit integration.
4+
5+@docs element, document, application, Program, Model, Msg
6+
7+-}
28
39import Browser exposing (Document, UrlRequest)
410import Browser.Navigation as Nav
511import Dict exposing (Dict)
612import Either exposing (Either(..))
7−import Html
13+import Html exposing (Html)
814import Json.Decode as JD
915import Task
1016import Time
11−import Ur exposing (Agent, Path)
1217import Ur.Cmd
1318import Ur.Cmd.Internal
1419import Ur.Constructor as C
1520import Ur.Deconstructor as D
16−import Ur.Phonemic exposing (Ship)
21+import Ur.NounDiff exposing (Patch, deconstructPatch)
1722import Ur.Requests exposing (..)
1823import Ur.Sub
1924import Ur.Sub.Internal
25+import Ur.Types exposing (..)
2026import Ur.Uw
2127import Url exposing (Url)
2228
@@ -25,8 +31,9 @@ type alias SubDict msg =
2531 Dict
2632 -- (ship, agent, path)
2733 ( String, String, List String )
28− { deconstructor : D.Deconstructor (msg -> msg) msg
34+ { deconstructor : D.Deconstructor msg
2935 , number : Int
36+ , sink : Bool
3037 }
3138
3239
@@ -34,6 +41,8 @@ type alias Flags =
3441 { uid : String }
3542
3643
44+{-| An Urbit wrapper around your application model.
45+-}
3746type alias Model app msg =
3847 { subscriptions : SubDict msg
3948 , subscriptionIntMapping : Dict Int ( String, String, List String )
@@ -42,9 +51,12 @@ type alias Model app msg =
4251 , eventId : Int
4352 , flags : Flags
4453 , requestsToRetry : List UrbitRequest
54+ , sinks : Dict Int Noun
4555 }
4656
4757
58+{-| An Urbit wrapper around your application messages.
59+-}
4860type Msg msg
4961 = AppMsg msg
5062 | EventSourceMsg JD.Value
@@ -55,10 +67,61 @@ type Msg msg
5567 | RetryRequests
5668
5769
70+{-| A wrapper around your application `Program`.
71+-}
5872type alias Program model msg =
5973 Platform.Program Flags (Model model msg) (Msg msg)
6074
6175
76+{-| The same as `Browser.element` but with urbit stuff added around it.
77+-}
78+element :
79+ { init : ( model, Ur.Cmd.Cmd msg )
80+ , view : model -> Html msg
81+ , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
82+ , subscriptions : model -> Sub msg
83+ , urbitSubscriptions : model -> Ur.Sub.Sub msg
84+ , createEventSource : String -> Cmd (Msg msg)
85+ , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
86+ , urbitUrl : model -> String
87+ }
88+ -> Program model msg
89+element inp =
90+ Browser.element
91+ { init = init inp inp.init
92+ , view = \model -> inp.view model.app |> Html.map AppMsg
93+ , update = update inp
94+ , subscriptions = subscriptions inp
95+ }
96+
97+
98+{-| The same as `Browser.element` but with urbit stuff added around it.
99+-}
100+document :
101+ { init : ( model, Ur.Cmd.Cmd msg )
102+ , view : model -> Document msg
103+ , update : msg -> model -> ( model, Ur.Cmd.Cmd msg )
104+ , subscriptions : model -> Sub msg
105+ , urbitSubscriptions : model -> Ur.Sub.Sub msg
106+ , createEventSource : String -> Cmd (Msg msg)
107+ , onEventSourceMsg : (JD.Value -> Msg msg) -> Sub (Msg msg)
108+ , urbitUrl : model -> String
109+ }
110+ -> Program model msg
111+document inp =
112+ Browser.document
113+ { init = init inp inp.init
114+ , view =
115+ \model ->
116+ inp.view model.app
117+ |> (\{ body, title } -> { title = title, body = body |> List.map (Html.map AppMsg) })
118+ , update = update inp
119+ , subscriptions = subscriptions inp
120+ }
121+
122+
123+{-| The same as `Browser.element` but with urbit stuff added around it.
124+-}
62125application :
63126 { init : Url -> Nav.Key -> ( model, Ur.Cmd.Cmd msg )
64127 , view : model -> Document msg
@@ -111,12 +174,11 @@ update inp msg model =
111174 ( appModel, appCmds ) =
112175 inp.update msg_ model.app
113176
114− -- { subscriptions, eventId, subscriptionRequests, subscriptionIntMapping } =
115177 subsResult =
116178 processUrSubs
117179 model.eventId
118180 model.subscriptions
119− (inp.urbitSubscriptions model.app |> (\(Ur.Sub.Internal.Sub x) -> x))
181+ (inp.urbitSubscriptions appModel |> (\(Ur.Sub.Internal.Sub x) -> x))
120182
121183 ( eventId_, cmds, urReqs ) =
122184 processCmd subsResult.eventId appCmds
@@ -149,12 +211,10 @@ update inp msg model =
149211 Ok string ->
150212 case
151213 D.runBytes
152− (D.cell D.int (D.cell D.cord D.tar)
153− |> D.map (\a b c -> ( a, b, c ))
154− )
214+ (D.cell D.int (D.cell D.cord D.tar))
155215 (Ur.Uw.decode string)
156216 of
157− Just ( messageId, messageType, rest ) ->
217+ Just ( messageId, ( messageType, rest ) ) ->
158218 let
159219 ( eventId, ackReqs ) =
160220 tag model.eventId [ Ack messageId ]
@@ -184,14 +244,51 @@ update inp msg model =
184244 Dict.get messageId model.subscriptionIntMapping
185245 |> Maybe.andThen (\key -> Dict.get key model.subscriptions)
186246 of
187− Just { deconstructor } ->
188− case D.run (D.cell D.ignore (D.cell D.ignore deconstructor)) rest of
189− Just subMsg ->
190− ( model_, pureCmd (AppMsg subMsg) )
191−
192− -- Got gargbage
193− Nothing ->
194− ( model_, ackCmd )
247+ Just { deconstructor, sink } ->
248+ if sink then
249+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructSink) |> D.map (\( (), ( (), s ) ) -> s)) rest of
250+ Just (Flush noun) ->
251+ ( { model | sinks = Dict.insert messageId noun model.sinks }
252+ , case D.run deconstructor noun of
253+ Just subMsg ->
254+ pureCmd (AppMsg subMsg)
255+
256+ -- Got garbage
257+ Nothing ->
258+ Cmd.none
259+ )
260+
261+ Just (Drain patch) ->
262+ case Dict.get messageId model.sinks of
263+ Just oldNoun ->
264+ let
265+ newNoun =
266+ Ur.NounDiff.apply patch oldNoun
267+ in
268+ ( { model | sinks = Dict.insert messageId newNoun model.sinks }
269+ , case D.run deconstructor newNoun of
270+ Just subMsg ->
271+ pureCmd (AppMsg subMsg)
272+
273+ Nothing ->
274+ Cmd.none
275+ )
276+
277+ Nothing ->
278+ ( model, Cmd.none )
279+
280+ -- Got garbage
281+ Nothing ->
282+ ( model, Cmd.none )
283+
284+ else
285+ case D.run (D.cell D.ignore (D.cell D.ignore deconstructor) |> D.map (\( (), ( (), m ) ) -> m)) rest of
286+ Just subMsg ->
287+ ( model_, pureCmd (AppMsg subMsg) )
288+
289+ -- Got gargbage
290+ Nothing ->
291+ ( model_, ackCmd )
195292
196293 -- Got a fact for a subscription we do not hold
197294 Nothing ->
@@ -216,11 +313,11 @@ update inp msg model =
216313 NeedsActivation ->
217314 let
218315 ( eventId, reqs ) =
219− [ Poke { ship = "~zod", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
316+ [ Poke { ship = "", agent = "hood", mark = "helm-hi", noun = C.cord "Opening airlock!" } ]
220317 |> tag model.eventId
221318 in
222319 ( { model | eventId = eventId }
223− , send { url = url, requests = reqs, success = OpenConnection, error = NeedsActivation }
320+ , send { url = url, requests = reqs, success = OpenConnection, error = Noop }
224321 )
225322
226323 Noop ->
@@ -247,6 +344,19 @@ update inp msg model =
247344 )
248345
249346
347+type Sink
348+ = Flush Noun
349+ | Drain Patch
350+
351+
352+deconstructSink : D.Deconstructor Sink
353+deconstructSink =
354+ D.oneOf
355+ [ D.cell (D.const D.cord "flush") D.tar |> D.map (\( (), n ) -> Flush n)
356+ , D.cell (D.const D.cord "drain") (D.lazy deconstructPatch) |> D.map (\( (), patch ) -> Drain patch)
357+ ]
358+
359+
250360processCmd : EventId -> Ur.Cmd.Cmd msg -> ( EventId, Cmd (Msg msg), List ( EventId, UrbitRequest ) )
251361processCmd eventId urCmds =
252362 let
@@ -276,10 +386,10 @@ pureCmd msg =
276386
277387processUrSubs :
278388 EventId
279− -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId }
280− -> Dict ( Ship, Agent, Path ) d
389+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
390+ -> Dict ( Ship, Agent, Path ) { deconstructor : d, sink : Bool }
281391 ->
282− { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId }
392+ { subscriptions : Dict ( Ship, Agent, Path ) { deconstructor : d, number : EventId, sink : Bool }
283393 , eventId : EventId
284394 , subscriptionRequests : List ( EventId, UrbitRequest )
285395 , subscriptionIntMapping : Dict EventId ( Ship, Agent, Path )
@@ -288,7 +398,7 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
288398 let
289399 urbitSubs =
290400 urbitSubs_
291− |> Dict.map (\_ deconstructor -> { deconstructor = deconstructor })
401+ |> Dict.map (\_ { deconstructor, sink } -> { deconstructor = deconstructor, sink = sink })
292402
293403 ( eventId_, newSubscriptionActions ) =
294404 Dict.diff urbitSubs existingSubscriptions
@@ -311,10 +421,11 @@ processUrSubs eventId existingSubscriptions urbitSubs_ =
311421 newSubscriptions =
312422 Dict.merge
313423 (\_ _ x -> x)
314− (\key number { deconstructor } ->
424+ (\key number { deconstructor, sink } ->
315425 Dict.insert key
316426 { deconstructor = deconstructor
317427 , number = number
428+ , sink = sink
318429 }
319430 )
320431 (\_ _ x -> x)
@@ -357,6 +468,7 @@ init inp ( app, appCmds ) flags =
357468 inp.urbitUrl app ++ "/~/channel/" ++ flags.uid
358469 in
359470 ( { subscriptions = subsResult.subscriptions
471+ , sinks = Dict.empty
360472 , subscriptionIntMapping = subsResult.subscriptionIntMapping
361473 , app = app
362474 , connected = False
@@ -379,7 +491,7 @@ init inp ( app, appCmds ) flags =
379491
380492subscriptions :
381493 { a | subscriptions : b -> Sub msg, onEventSourceMsg : (JD.Value -> Msg c) -> Sub (Msg msg) }
382− -> { d | app : b, requestsToRetry : List e }
494+ -> { d | app : b, requestsToRetry : List e, connected : Bool }
383495 -> Sub (Msg msg)
384496subscriptions inp model =
385497 Sub.batch
@@ -390,4 +502,9 @@ subscriptions inp model =
390502
391503 else
392504 Time.every 1000 (always RetryRequests)
505+ , if not model.connected then
506+ Time.every 10000 (always NeedsActivation)
507+
508+ else
509+ Sub.none
393510 ]