elm-pages 2.1.10 → 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/{template/public/style.css → review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock} +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +6795 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +27617 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +110 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +187 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/package.json +1 -0
- package/generator/review/elm-stuff/tests-0.19.1/src/Reporter.elm +26 -0
- package/generator/review/elm-stuff/tests-0.19.1/src/Runner.elm +62 -0
- package/generator/review/elm.json +13 -4
- package/{src → generator/review/src}/Pages/Review/NoContractViolations.elm +148 -148
- package/generator/review/tests/Pages/Review/NoContractViolationsTest.elm +331 -0
- package/generator/src/RouteBuilder.elm +420 -0
- package/generator/src/SharedTemplate.elm +4 -5
- package/generator/src/SiteConfig.elm +3 -9
- package/generator/src/build.js +308 -95
- package/generator/src/cli.js +103 -8
- package/generator/src/codegen.js +192 -35
- package/generator/src/compile-elm.js +183 -31
- package/generator/src/dev-server.js +353 -96
- package/generator/src/elm-application.json +3 -1
- package/generator/src/elm-codegen.js +34 -0
- package/generator/src/elm-file-constants.js +2 -0
- package/generator/src/error-formatter.js +20 -1
- package/generator/src/generate-template-module-connector.js +120 -924
- package/generator/src/hello.ts +5 -0
- package/generator/src/pre-render-html.js +58 -104
- package/generator/src/render-worker.js +27 -13
- package/generator/src/render.js +252 -197
- package/generator/src/request-cache-fs.js +18 -0
- package/generator/src/request-cache.js +128 -56
- package/generator/src/rewrite-client-elm-json.js +49 -0
- package/generator/src/route-codegen-helpers.js +62 -1
- package/generator/static-code/dev-style.css +22 -0
- package/generator/static-code/elm-pages.js +43 -39
- package/generator/static-code/hmr.js +98 -88
- package/generator/template/app/Api.elm +25 -0
- package/generator/template/app/ErrorPage.elm +38 -0
- package/generator/template/app/Route/Index.elm +87 -0
- package/generator/template/{src → app}/Shared.elm +34 -13
- package/generator/template/app/Site.elm +19 -0
- package/generator/template/{src → app}/View.elm +0 -0
- package/generator/template/elm-pages.config.mjs +5 -0
- package/generator/template/elm.json +14 -5
- package/generator/template/{public/index.js → index.ts} +7 -3
- package/generator/template/package.json +5 -5
- package/generator/template/public/favicon.ico +0 -0
- package/generator/template/public/images/icon-png.png +0 -0
- package/generator/template/src/.gitkeep +0 -0
- package/generator/template/style.css +4 -0
- package/package.json +30 -23
- package/src/ApiRoute.elm +176 -43
- package/src/BuildError.elm +10 -1
- package/src/CookieParser.elm +84 -0
- package/src/DataSource/Env.elm +38 -0
- package/src/DataSource/File.elm +27 -16
- package/src/DataSource/Glob.elm +126 -80
- package/src/DataSource/Http.elm +283 -304
- package/src/DataSource/Internal/Glob.elm +5 -21
- package/src/DataSource/Internal/Request.elm +25 -0
- package/src/DataSource/Port.elm +17 -14
- package/src/DataSource.elm +55 -318
- package/src/Form/Field.elm +717 -0
- package/src/Form/FieldStatus.elm +36 -0
- package/src/Form/FieldView.elm +417 -0
- package/src/Form/FormData.elm +22 -0
- package/src/Form/Validation.elm +391 -0
- package/src/Form/Value.elm +118 -0
- package/src/Form.elm +1683 -0
- package/src/FormData.elm +58 -0
- package/src/FormDecoder.elm +102 -0
- package/src/Head/Seo.elm +12 -4
- package/src/Head.elm +12 -2
- package/src/HtmlPrinter.elm +1 -1
- package/src/Internal/ApiRoute.elm +17 -4
- package/src/Internal/Request.elm +7 -0
- package/src/PageServerResponse.elm +68 -0
- package/src/Pages/ContentCache.elm +1 -229
- package/src/Pages/Fetcher.elm +58 -0
- package/src/Pages/FormState.elm +256 -0
- package/src/Pages/Generate.elm +800 -0
- package/src/Pages/Internal/Form.elm +17 -0
- package/src/Pages/Internal/NotFoundReason.elm +3 -55
- package/src/Pages/Internal/Platform/Cli.elm +777 -579
- package/src/Pages/Internal/Platform/Effect.elm +5 -5
- package/src/Pages/Internal/Platform/StaticResponses.elm +178 -394
- package/src/Pages/Internal/Platform/ToJsPayload.elm +24 -23
- package/src/Pages/Internal/Platform.elm +1244 -504
- package/src/Pages/Internal/ResponseSketch.elm +19 -0
- package/src/Pages/Internal/RoutePattern.elm +596 -45
- package/src/Pages/Manifest.elm +26 -0
- package/src/Pages/Msg.elm +79 -0
- package/src/Pages/ProgramConfig.elm +67 -14
- package/src/Pages/SiteConfig.elm +3 -6
- package/src/Pages/StaticHttp/Request.elm +4 -2
- package/src/Pages/StaticHttpRequest.elm +50 -215
- package/src/Pages/Transition.elm +70 -0
- package/src/Path.elm +1 -0
- package/src/Pattern.elm +98 -0
- package/src/RenderRequest.elm +2 -2
- package/src/RequestsAndPending.elm +111 -9
- package/src/Server/Request.elm +1253 -0
- package/src/Server/Response.elm +292 -0
- package/src/Server/Session.elm +316 -0
- package/src/Server/SetCookie.elm +169 -0
- package/src/TerminalText.elm +1 -1
- package/src/Test/Html/Internal/ElmHtml/Markdown.elm +0 -1
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +1 -1
- package/generator/src/Page.elm +0 -359
- package/generator/src/codegen-template-module.js +0 -183
- package/generator/src/elm-pages-js-minified.js +0 -1
- package/generator/template/src/Api.elm +0 -14
- package/generator/template/src/Page/Index.elm +0 -69
- package/generator/template/src/Site.elm +0 -41
- package/src/DataSource/ServerRequest.elm +0 -60
- package/src/Internal/OptimizedDecoder.elm +0 -18
- package/src/KeepOrDiscard.elm +0 -6
- package/src/OptimizedDecoder/Pipeline.elm +0 -335
- package/src/OptimizedDecoder.elm +0 -818
- package/src/Pages/Internal/ApplicationType.elm +0 -6
- package/src/Pages/Secrets.elm +0 -83
- package/src/Secrets.elm +0 -111
- package/src/SecretsDict.elm +0 -45
|
@@ -1,65 +1,93 @@
|
|
|
1
|
-
module Pages.Internal.Platform exposing
|
|
1
|
+
module Pages.Internal.Platform exposing
|
|
2
|
+
( Flags, Model, Msg(..), Program, application, init, update
|
|
3
|
+
, Effect(..), RequestInfo, view
|
|
4
|
+
)
|
|
2
5
|
|
|
3
6
|
{-| Exposed for internal use only (used in generated code).
|
|
4
7
|
|
|
5
|
-
@docs Flags, Model, Msg, Program, application
|
|
8
|
+
@docs Flags, Model, Msg, Program, application, init, update
|
|
9
|
+
|
|
10
|
+
@docs Effect, RequestInfo, view
|
|
6
11
|
|
|
7
12
|
-}
|
|
8
13
|
|
|
9
14
|
import AriaLiveAnnouncer
|
|
15
|
+
import Base64
|
|
10
16
|
import Browser
|
|
11
17
|
import Browser.Dom as Dom
|
|
12
18
|
import Browser.Navigation
|
|
13
19
|
import BuildError exposing (BuildError)
|
|
20
|
+
import Bytes exposing (Bytes)
|
|
21
|
+
import Bytes.Decode
|
|
22
|
+
import Dict exposing (Dict)
|
|
23
|
+
import Form.FormData exposing (FormData, Method(..))
|
|
24
|
+
import FormDecoder
|
|
14
25
|
import Html exposing (Html)
|
|
15
26
|
import Html.Attributes as Attr
|
|
16
27
|
import Http
|
|
17
28
|
import Json.Decode as Decode
|
|
18
29
|
import Json.Encode
|
|
19
|
-
import Pages.ContentCache as ContentCache
|
|
30
|
+
import Pages.ContentCache as ContentCache
|
|
31
|
+
import Pages.Fetcher
|
|
20
32
|
import Pages.Flags
|
|
21
|
-
import Pages.
|
|
22
|
-
import Pages.Internal.NotFoundReason
|
|
33
|
+
import Pages.FormState
|
|
34
|
+
import Pages.Internal.NotFoundReason exposing (NotFoundReason)
|
|
35
|
+
import Pages.Internal.ResponseSketch as ResponseSketch exposing (ResponseSketch)
|
|
23
36
|
import Pages.Internal.String as String
|
|
37
|
+
import Pages.Msg
|
|
24
38
|
import Pages.ProgramConfig exposing (ProgramConfig)
|
|
25
39
|
import Pages.StaticHttpRequest as StaticHttpRequest
|
|
40
|
+
import Pages.Transition
|
|
26
41
|
import Path exposing (Path)
|
|
27
42
|
import QueryParams
|
|
28
43
|
import Task
|
|
44
|
+
import Time
|
|
29
45
|
import Url exposing (Url)
|
|
30
46
|
|
|
31
47
|
|
|
48
|
+
type Transition
|
|
49
|
+
= Loading Int Path
|
|
50
|
+
| Submitting FormData
|
|
51
|
+
|
|
52
|
+
|
|
32
53
|
{-| -}
|
|
33
|
-
type alias Program userModel userMsg pageData sharedData =
|
|
34
|
-
Platform.Program Flags (Model userModel pageData sharedData) (Msg userMsg)
|
|
54
|
+
type alias Program userModel userMsg pageData actionData sharedData errorPage =
|
|
55
|
+
Platform.Program Flags (Model userModel pageData actionData sharedData) (Msg userMsg pageData actionData sharedData errorPage)
|
|
35
56
|
|
|
36
57
|
|
|
37
58
|
mainView :
|
|
38
|
-
ProgramConfig userMsg userModel route
|
|
39
|
-
-> Model userModel pageData sharedData
|
|
40
|
-
-> { title : String, body : Html userMsg }
|
|
59
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData effect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
60
|
+
-> Model userModel pageData actionData sharedData
|
|
61
|
+
-> { title : String, body : Html (Pages.Msg.Msg userMsg) }
|
|
41
62
|
mainView config model =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{ currentUrl = model.url
|
|
46
|
-
, basePath = config.basePath
|
|
47
|
-
}
|
|
48
|
-
in
|
|
49
|
-
case ContentCache.notFoundReason model.contentCache urls of
|
|
50
|
-
Just notFoundReason ->
|
|
51
|
-
Pages.Internal.NotFoundReason.document config.pathPatterns notFoundReason
|
|
63
|
+
case model.notFound of
|
|
64
|
+
Just info ->
|
|
65
|
+
Pages.Internal.NotFoundReason.document config.pathPatterns info
|
|
52
66
|
|
|
53
67
|
Nothing ->
|
|
54
68
|
case model.pageData of
|
|
55
69
|
Ok pageData ->
|
|
56
|
-
|
|
70
|
+
let
|
|
71
|
+
urls : { currentUrl : Url, basePath : List String }
|
|
72
|
+
urls =
|
|
73
|
+
{ currentUrl = model.url
|
|
74
|
+
, basePath = config.basePath
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
currentUrl : Url
|
|
78
|
+
currentUrl =
|
|
79
|
+
model.url
|
|
80
|
+
in
|
|
81
|
+
(config.view model.pageFormState
|
|
82
|
+
(model.inFlightFetchers |> toFetcherState)
|
|
83
|
+
(model.transition |> Maybe.map Tuple.second)
|
|
57
84
|
{ path = ContentCache.pathForUrl urls |> Path.join
|
|
58
|
-
, route = config.urlToRoute model.
|
|
85
|
+
, route = config.urlToRoute { currentUrl | path = model.currentPath }
|
|
59
86
|
}
|
|
60
87
|
Nothing
|
|
61
88
|
pageData.sharedData
|
|
62
89
|
pageData.pageData
|
|
90
|
+
pageData.actionData
|
|
63
91
|
|> .view
|
|
64
92
|
)
|
|
65
93
|
pageData.userModel
|
|
@@ -83,10 +111,11 @@ urlsToPagePath urls =
|
|
|
83
111
|
|> Path.join
|
|
84
112
|
|
|
85
113
|
|
|
114
|
+
{-| -}
|
|
86
115
|
view :
|
|
87
|
-
ProgramConfig userMsg userModel route
|
|
88
|
-
-> Model userModel pageData sharedData
|
|
89
|
-
-> Browser.Document (Msg userMsg)
|
|
116
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData effect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
117
|
+
-> Model userModel pageData actionData sharedData
|
|
118
|
+
-> Browser.Document (Msg userMsg pageData actionData sharedData errorPage)
|
|
90
119
|
view config model =
|
|
91
120
|
let
|
|
92
121
|
{ title, body } =
|
|
@@ -119,179 +148,186 @@ type alias Flags =
|
|
|
119
148
|
Decode.Value
|
|
120
149
|
|
|
121
150
|
|
|
151
|
+
type InitKind shared page actionData errorPage
|
|
152
|
+
= OkPage shared page (Maybe actionData)
|
|
153
|
+
| NotFound { reason : NotFoundReason, path : Path }
|
|
154
|
+
|
|
155
|
+
|
|
122
156
|
{-| -}
|
|
123
157
|
init :
|
|
124
|
-
ProgramConfig userMsg userModel route
|
|
158
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
125
159
|
-> Flags
|
|
126
160
|
-> Url
|
|
127
|
-
-> Browser.Navigation.Key
|
|
128
|
-
-> ( Model userModel pageData sharedData,
|
|
161
|
+
-> Maybe Browser.Navigation.Key
|
|
162
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
129
163
|
init config flags url key =
|
|
130
164
|
let
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
ContentCache.init
|
|
134
|
-
(Maybe.map
|
|
135
|
-
(\cj ->
|
|
136
|
-
( currentPath
|
|
137
|
-
, cj
|
|
138
|
-
)
|
|
139
|
-
)
|
|
140
|
-
contentJson
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
currentPath : List String
|
|
144
|
-
currentPath =
|
|
165
|
+
pageDataResult : Result BuildError (InitKind sharedData pageData actionData errorPage)
|
|
166
|
+
pageDataResult =
|
|
145
167
|
flags
|
|
146
|
-
|> Decode.decodeValue
|
|
147
|
-
(Decode.at [ "contentJson", "path" ]
|
|
148
|
-
(Decode.string
|
|
149
|
-
|> Decode.map Path.fromString
|
|
150
|
-
|> Decode.map Path.toSegments
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
|> Result.mapError Decode.errorToString
|
|
168
|
+
|> Decode.decodeValue (Decode.field "pageDataBase64" Decode.string)
|
|
154
169
|
|> Result.toMaybe
|
|
155
|
-
|> Maybe.
|
|
170
|
+
|> Maybe.andThen Base64.toBytes
|
|
171
|
+
|> Maybe.andThen
|
|
172
|
+
(\justBytes ->
|
|
173
|
+
case
|
|
174
|
+
Bytes.Decode.decode
|
|
175
|
+
-- TODO should this use byteDecodePageData, or should it be decoding ResponseSketch data?
|
|
176
|
+
config.decodeResponse
|
|
177
|
+
justBytes
|
|
178
|
+
of
|
|
179
|
+
Just (ResponseSketch.RenderPage _ _) ->
|
|
180
|
+
Nothing
|
|
156
181
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder)
|
|
161
|
-
|> Result.toMaybe
|
|
182
|
+
Just (ResponseSketch.HotUpdate pageData shared actionData) ->
|
|
183
|
+
OkPage shared pageData actionData
|
|
184
|
+
|> Just
|
|
162
185
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
Just (ResponseSketch.NotFound notFound) ->
|
|
187
|
+
NotFound notFound
|
|
188
|
+
|> Just
|
|
189
|
+
|
|
190
|
+
_ ->
|
|
191
|
+
Nothing
|
|
192
|
+
)
|
|
193
|
+
|> Result.fromMaybe
|
|
194
|
+
(StaticHttpRequest.DecoderError "Bytes decode error"
|
|
195
|
+
|> StaticHttpRequest.toBuildError url.path
|
|
196
|
+
)
|
|
168
197
|
in
|
|
169
|
-
case
|
|
170
|
-
|
|
198
|
+
case pageDataResult of
|
|
199
|
+
Ok (OkPage sharedData pageData actionData) ->
|
|
171
200
|
let
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|> Result.mapError (StaticHttpRequest.toBuildError url.path)
|
|
178
|
-
|
|
179
|
-
sharedDataResult : Result BuildError sharedData
|
|
180
|
-
sharedDataResult =
|
|
181
|
-
StaticHttpRequest.resolve ApplicationType.Browser
|
|
182
|
-
config.sharedData
|
|
183
|
-
justContentJson
|
|
184
|
-
|> Result.mapError (StaticHttpRequest.toBuildError url.path)
|
|
201
|
+
urls : { currentUrl : Url, basePath : List String }
|
|
202
|
+
urls =
|
|
203
|
+
{ currentUrl = url
|
|
204
|
+
, basePath = config.basePath
|
|
205
|
+
}
|
|
185
206
|
|
|
186
207
|
pagePath : Path
|
|
187
208
|
pagePath =
|
|
188
209
|
urlsToPagePath urls
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
|
|
211
|
+
userFlags : Pages.Flags.Flags
|
|
212
|
+
userFlags =
|
|
213
|
+
flags
|
|
214
|
+
|> Decode.decodeValue
|
|
215
|
+
(Decode.field "userFlags" Decode.value)
|
|
216
|
+
|> Result.withDefault Json.Encode.null
|
|
217
|
+
|> Pages.Flags.BrowserFlags
|
|
218
|
+
|
|
219
|
+
( userModel, userCmd ) =
|
|
220
|
+
Just
|
|
221
|
+
{ path =
|
|
222
|
+
{ path = pagePath
|
|
223
|
+
, query = url.query
|
|
224
|
+
, fragment = url.fragment
|
|
225
|
+
}
|
|
226
|
+
, metadata = config.urlToRoute url
|
|
227
|
+
, pageUrl =
|
|
202
228
|
Just
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
,
|
|
209
|
-
, pageUrl =
|
|
210
|
-
Just
|
|
211
|
-
{ protocol = url.protocol
|
|
212
|
-
, host = url.host
|
|
213
|
-
, port_ = url.port_
|
|
214
|
-
, path = pagePath
|
|
215
|
-
, query = url.query |> Maybe.map QueryParams.fromString
|
|
216
|
-
, fragment = url.fragment
|
|
217
|
-
}
|
|
229
|
+
{ protocol = url.protocol
|
|
230
|
+
, host = url.host
|
|
231
|
+
, port_ = url.port_
|
|
232
|
+
, path = pagePath
|
|
233
|
+
, query = url.query |> Maybe.map QueryParams.fromString
|
|
234
|
+
, fragment = url.fragment
|
|
218
235
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
, url = url
|
|
238
|
-
, contentCache = contentCache
|
|
239
|
-
, pageData =
|
|
240
|
-
Ok
|
|
241
|
-
{ pageData = pageData
|
|
242
|
-
, sharedData = sharedData
|
|
243
|
-
, userModel = userModel
|
|
244
|
-
}
|
|
245
|
-
, ariaNavigationAnnouncement = ""
|
|
246
|
-
, userFlags = flags
|
|
236
|
+
}
|
|
237
|
+
|> config.init userFlags sharedData pageData actionData
|
|
238
|
+
|
|
239
|
+
cmd : Effect userMsg pageData actionData sharedData userEffect errorPage
|
|
240
|
+
cmd =
|
|
241
|
+
UserCmd userCmd
|
|
242
|
+
|
|
243
|
+
initialModel : Model userModel pageData actionData sharedData
|
|
244
|
+
initialModel =
|
|
245
|
+
{ key = key
|
|
246
|
+
, url = url
|
|
247
|
+
, currentPath = url.path
|
|
248
|
+
, pageData =
|
|
249
|
+
Ok
|
|
250
|
+
{ pageData = pageData
|
|
251
|
+
, sharedData = sharedData
|
|
252
|
+
, userModel = userModel
|
|
253
|
+
, actionData = actionData
|
|
247
254
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
,
|
|
253
|
-
|
|
255
|
+
, ariaNavigationAnnouncement = ""
|
|
256
|
+
, userFlags = flags
|
|
257
|
+
, notFound = Nothing
|
|
258
|
+
, transition = Nothing
|
|
259
|
+
, nextTransitionKey = 0
|
|
260
|
+
, inFlightFetchers = Dict.empty
|
|
261
|
+
, pageFormState = Dict.empty
|
|
262
|
+
, pendingRedirect = False
|
|
263
|
+
, pendingData = Nothing
|
|
264
|
+
}
|
|
265
|
+
in
|
|
266
|
+
( { initialModel
|
|
267
|
+
| ariaNavigationAnnouncement = mainView config initialModel |> .title
|
|
268
|
+
}
|
|
269
|
+
, cmd
|
|
270
|
+
)
|
|
254
271
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
Ok (NotFound info) ->
|
|
273
|
+
( { key = key
|
|
274
|
+
, url = url
|
|
275
|
+
, currentPath = url.path
|
|
276
|
+
, pageData = Err "Not found"
|
|
277
|
+
, ariaNavigationAnnouncement = "Error" -- TODO use error page title for announcement?
|
|
278
|
+
, userFlags = flags
|
|
279
|
+
, notFound = Just info
|
|
280
|
+
, transition = Nothing
|
|
281
|
+
, nextTransitionKey = 0
|
|
282
|
+
, inFlightFetchers = Dict.empty
|
|
283
|
+
, pageFormState = Dict.empty
|
|
284
|
+
, pendingRedirect = False
|
|
285
|
+
, pendingData = Nothing
|
|
286
|
+
}
|
|
287
|
+
, NoEffect
|
|
288
|
+
)
|
|
265
289
|
|
|
266
|
-
|
|
290
|
+
Err error ->
|
|
267
291
|
( { key = key
|
|
268
292
|
, url = url
|
|
269
|
-
,
|
|
270
|
-
, pageData =
|
|
293
|
+
, currentPath = url.path
|
|
294
|
+
, pageData =
|
|
295
|
+
error
|
|
296
|
+
|> BuildError.errorToString
|
|
297
|
+
|> Err
|
|
271
298
|
, ariaNavigationAnnouncement = "Error"
|
|
272
299
|
, userFlags = flags
|
|
300
|
+
, notFound = Nothing
|
|
301
|
+
, transition = Nothing
|
|
302
|
+
, nextTransitionKey = 0
|
|
303
|
+
, inFlightFetchers = Dict.empty
|
|
304
|
+
, pageFormState = Dict.empty
|
|
305
|
+
, pendingRedirect = False
|
|
306
|
+
, pendingData = Nothing
|
|
273
307
|
}
|
|
274
|
-
,
|
|
308
|
+
, NoEffect
|
|
275
309
|
)
|
|
276
310
|
|
|
277
311
|
|
|
278
312
|
{-| -}
|
|
279
|
-
type Msg userMsg
|
|
313
|
+
type Msg userMsg pageData actionData sharedData errorPage
|
|
280
314
|
= LinkClicked Browser.UrlRequest
|
|
281
315
|
| UrlChanged Url
|
|
282
|
-
| UserMsg userMsg
|
|
283
|
-
|
|
|
284
|
-
|
|
|
316
|
+
| UserMsg (Pages.Msg.Msg userMsg)
|
|
317
|
+
| SetField { formId : String, name : String, value : String }
|
|
318
|
+
| UpdateCacheAndUrlNew Bool Url (Maybe userMsg) (Result Http.Error ( Url, ResponseSketch pageData actionData sharedData ))
|
|
319
|
+
| FetcherComplete Bool String Int (Result Http.Error ( Maybe userMsg, Maybe actionData ))
|
|
320
|
+
| FetcherStarted String Int FormData Time.Posix
|
|
285
321
|
| PageScrollComplete
|
|
286
|
-
|
|
|
287
|
-
|
|
|
322
|
+
| HotReloadCompleteNew Bytes
|
|
323
|
+
| ProcessFetchResponse Int (Result Http.Error ( Url, ResponseSketch pageData actionData sharedData )) (Result Http.Error ( Url, ResponseSketch pageData actionData sharedData ) -> Msg userMsg pageData actionData sharedData errorPage)
|
|
288
324
|
|
|
289
325
|
|
|
290
326
|
{-| -}
|
|
291
|
-
type alias Model userModel pageData sharedData =
|
|
292
|
-
{ key : Browser.Navigation.Key
|
|
327
|
+
type alias Model userModel pageData actionData sharedData =
|
|
328
|
+
{ key : Maybe Browser.Navigation.Key
|
|
293
329
|
, url : Url
|
|
294
|
-
,
|
|
330
|
+
, currentPath : String
|
|
295
331
|
, ariaNavigationAnnouncement : String
|
|
296
332
|
, pageData :
|
|
297
333
|
Result
|
|
@@ -299,17 +335,40 @@ type alias Model userModel pageData sharedData =
|
|
|
299
335
|
{ userModel : userModel
|
|
300
336
|
, pageData : pageData
|
|
301
337
|
, sharedData : sharedData
|
|
338
|
+
, actionData : Maybe actionData
|
|
302
339
|
}
|
|
340
|
+
, notFound : Maybe { reason : NotFoundReason, path : Path }
|
|
303
341
|
, userFlags : Decode.Value
|
|
342
|
+
, transition : Maybe ( Int, Pages.Transition.Transition )
|
|
343
|
+
, nextTransitionKey : Int
|
|
344
|
+
, inFlightFetchers : Dict String ( Int, Pages.Transition.FetcherState actionData )
|
|
345
|
+
, pageFormState : Pages.FormState.PageFormState
|
|
346
|
+
, pendingRedirect : Bool
|
|
347
|
+
, pendingData : Maybe ( pageData, sharedData, Maybe actionData )
|
|
304
348
|
}
|
|
305
349
|
|
|
306
350
|
|
|
351
|
+
{-| -}
|
|
352
|
+
type Effect userMsg pageData actionData sharedData userEffect errorPage
|
|
353
|
+
= ScrollToTop
|
|
354
|
+
| NoEffect
|
|
355
|
+
| BrowserLoadUrl String
|
|
356
|
+
| BrowserPushUrl String
|
|
357
|
+
| BrowserReplaceUrl String
|
|
358
|
+
| FetchPageData Int (Maybe FormData) Url (Result Http.Error ( Url, ResponseSketch pageData actionData sharedData ) -> Msg userMsg pageData actionData sharedData errorPage)
|
|
359
|
+
| Submit FormData
|
|
360
|
+
| SubmitFetcher String Int FormData
|
|
361
|
+
| Batch (List (Effect userMsg pageData actionData sharedData userEffect errorPage))
|
|
362
|
+
| UserCmd userEffect
|
|
363
|
+
| CancelRequest Int
|
|
364
|
+
|
|
365
|
+
|
|
307
366
|
{-| -}
|
|
308
367
|
update :
|
|
309
|
-
ProgramConfig userMsg userModel route
|
|
310
|
-
-> Msg userMsg
|
|
311
|
-
-> Model userModel pageData sharedData
|
|
312
|
-
-> ( Model userModel pageData sharedData,
|
|
368
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
369
|
+
-> Msg userMsg pageData actionData sharedData errorPage
|
|
370
|
+
-> Model userModel pageData actionData sharedData
|
|
371
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
313
372
|
update config appMsg model =
|
|
314
373
|
case appMsg of
|
|
315
374
|
LinkClicked urlRequest ->
|
|
@@ -318,185 +377,308 @@ update config appMsg model =
|
|
|
318
377
|
let
|
|
319
378
|
navigatingToSamePage : Bool
|
|
320
379
|
navigatingToSamePage =
|
|
321
|
-
|
|
380
|
+
url.path == model.url.path
|
|
322
381
|
in
|
|
323
382
|
if navigatingToSamePage then
|
|
324
383
|
-- this is a workaround for an issue with anchor fragment navigation
|
|
325
384
|
-- see https://github.com/elm/browser/issues/39
|
|
326
|
-
( model
|
|
385
|
+
( model
|
|
386
|
+
, BrowserLoadUrl (Url.toString url)
|
|
387
|
+
)
|
|
327
388
|
|
|
328
389
|
else
|
|
329
390
|
( model
|
|
330
|
-
,
|
|
391
|
+
, BrowserPushUrl url.path
|
|
331
392
|
)
|
|
332
393
|
|
|
333
394
|
Browser.External href ->
|
|
334
|
-
( model
|
|
395
|
+
( model
|
|
396
|
+
, BrowserLoadUrl href
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
SetField info ->
|
|
400
|
+
( { model | pageFormState = Pages.FormState.setField info model.pageFormState }
|
|
401
|
+
, NoEffect
|
|
402
|
+
)
|
|
335
403
|
|
|
336
404
|
UrlChanged url ->
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
405
|
+
case model.pendingData of
|
|
406
|
+
Just ( newPageData, newSharedData, newActionData ) ->
|
|
407
|
+
loadDataAndUpdateUrl
|
|
408
|
+
( newPageData, newSharedData, newActionData )
|
|
409
|
+
Nothing
|
|
410
|
+
url
|
|
411
|
+
url
|
|
412
|
+
False
|
|
413
|
+
config
|
|
414
|
+
model
|
|
415
|
+
|
|
416
|
+
Nothing ->
|
|
417
|
+
if model.url.path == url.path then
|
|
418
|
+
( { model
|
|
419
|
+
| -- update the URL in case query params or fragment changed
|
|
420
|
+
url = url
|
|
421
|
+
}
|
|
422
|
+
, NoEffect
|
|
423
|
+
)
|
|
341
424
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
-- which scrolls to the top after page changes.
|
|
352
|
-
-- This is important because we may have just scrolled
|
|
353
|
-
-- to a specific page location for an anchor link.
|
|
354
|
-
model.pageData
|
|
355
|
-
|> Result.map
|
|
356
|
-
(\pageData ->
|
|
357
|
-
let
|
|
358
|
-
updatedPageData : Result String { userModel : userModel, sharedData : sharedData, pageData : pageData }
|
|
359
|
-
updatedPageData =
|
|
360
|
-
Ok
|
|
361
|
-
{ userModel = userModel
|
|
362
|
-
, sharedData = pageData.sharedData
|
|
363
|
-
, pageData = pageData.pageData
|
|
364
|
-
}
|
|
425
|
+
else
|
|
426
|
+
( { model
|
|
427
|
+
| url = url
|
|
428
|
+
}
|
|
429
|
+
, NoEffect
|
|
430
|
+
)
|
|
431
|
+
-- TODO is it reasonable to always re-fetch route data if you re-navigate to the current route? Might be a good
|
|
432
|
+
-- parallel to the browser behavior
|
|
433
|
+
|> startNewGetLoad url (UpdateCacheAndUrlNew True url Nothing)
|
|
365
434
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
435
|
+
FetcherComplete forPageReload fetcherKey transitionId___ userMsgResult ->
|
|
436
|
+
case userMsgResult of
|
|
437
|
+
Ok ( userMsg, maybeFetcherDoneActionData ) ->
|
|
438
|
+
( { model
|
|
439
|
+
| inFlightFetchers =
|
|
440
|
+
model.inFlightFetchers
|
|
441
|
+
|> Dict.update fetcherKey
|
|
442
|
+
(Maybe.map
|
|
443
|
+
(\( transitionId, fetcherState ) ->
|
|
444
|
+
( transitionId
|
|
445
|
+
, { fetcherState
|
|
446
|
+
| status =
|
|
447
|
+
maybeFetcherDoneActionData
|
|
448
|
+
|> Maybe.map Pages.Transition.FetcherReloading
|
|
449
|
+
-- TODO remove this bad default, FetcherSubmitting is incorrect
|
|
450
|
+
|> Maybe.withDefault Pages.Transition.FetcherSubmitting
|
|
451
|
+
}
|
|
452
|
+
)
|
|
380
453
|
)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
, NoEffect
|
|
457
|
+
)
|
|
458
|
+
|> (case userMsg of
|
|
459
|
+
Just justUserMsg ->
|
|
460
|
+
performUserMsg justUserMsg config
|
|
461
|
+
|
|
462
|
+
Nothing ->
|
|
463
|
+
identity
|
|
464
|
+
)
|
|
465
|
+
|> startNewGetLoad (currentUrlWithPath model.url.path model) (UpdateCacheAndUrlNew False model.url Nothing)
|
|
466
|
+
|
|
467
|
+
Err _ ->
|
|
468
|
+
-- TODO how to handle error?
|
|
469
|
+
( model, NoEffect )
|
|
470
|
+
|> startNewGetLoad (currentUrlWithPath model.url.path model) (UpdateCacheAndUrlNew False model.url Nothing)
|
|
471
|
+
|
|
472
|
+
ProcessFetchResponse transitionId response toMsg ->
|
|
473
|
+
case response of
|
|
474
|
+
Ok ( _, ResponseSketch.Redirect redirectTo ) ->
|
|
475
|
+
( model, NoEffect )
|
|
476
|
+
|> startNewGetLoad (currentUrlWithPath redirectTo model) toMsg
|
|
477
|
+
|
|
478
|
+
_ ->
|
|
479
|
+
update config (toMsg response) (clearLoadingFetchersAfterDataLoad transitionId model)
|
|
480
|
+
|
|
481
|
+
UserMsg userMsg_ ->
|
|
482
|
+
case userMsg_ of
|
|
483
|
+
Pages.Msg.UserMsg userMsg ->
|
|
484
|
+
( model, NoEffect )
|
|
485
|
+
|> performUserMsg userMsg config
|
|
486
|
+
|
|
487
|
+
Pages.Msg.Submit fields ->
|
|
488
|
+
( { model
|
|
489
|
+
| transition =
|
|
490
|
+
Just
|
|
491
|
+
( -- TODO remove hardcoded number
|
|
492
|
+
-1
|
|
493
|
+
, Pages.Transition.Submitting fields
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
, Submit fields
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
Pages.Msg.SubmitIfValid formId fields isValid ->
|
|
500
|
+
if isValid then
|
|
501
|
+
( { model
|
|
502
|
+
-- TODO should I setSubmitAttempted here, too?
|
|
503
|
+
| transition =
|
|
504
|
+
Just
|
|
505
|
+
( -- TODO remove hardcoded number
|
|
506
|
+
-1
|
|
507
|
+
, Pages.Transition.Submitting fields
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
, Submit fields
|
|
393
511
|
)
|
|
394
|
-
|> Result.withDefault ( model, Cmd.none )
|
|
395
512
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
513
|
+
else
|
|
514
|
+
( { model
|
|
515
|
+
| pageFormState =
|
|
516
|
+
model.pageFormState
|
|
517
|
+
|> Pages.FormState.setSubmitAttempted formId
|
|
518
|
+
}
|
|
519
|
+
, NoEffect
|
|
520
|
+
)
|
|
402
521
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
522
|
+
Pages.Msg.SubmitFetcher fetcherKey fields isValid maybeUserMsg ->
|
|
523
|
+
if isValid then
|
|
524
|
+
-- TODO should I setSubmitAttempted here, too?
|
|
525
|
+
( { model | nextTransitionKey = model.nextTransitionKey + 1 }
|
|
526
|
+
, SubmitFetcher fetcherKey model.nextTransitionKey fields
|
|
527
|
+
)
|
|
528
|
+
|> (case maybeUserMsg of
|
|
529
|
+
Just justUserMsg ->
|
|
530
|
+
performUserMsg justUserMsg config
|
|
409
531
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
in
|
|
414
|
-
( { model | pageData = updatedPageData }, userCmd |> Cmd.map UserMsg )
|
|
532
|
+
Nothing ->
|
|
533
|
+
identity
|
|
534
|
+
)
|
|
415
535
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
( { model | contentCache = updatedCache }
|
|
425
|
-
, Cmd.none
|
|
426
|
-
)
|
|
536
|
+
else
|
|
537
|
+
( { model
|
|
538
|
+
| pageFormState =
|
|
539
|
+
model.pageFormState
|
|
540
|
+
|> Pages.FormState.setSubmitAttempted fetcherKey
|
|
541
|
+
}
|
|
542
|
+
, NoEffect
|
|
543
|
+
)
|
|
427
544
|
|
|
428
|
-
|
|
429
|
-
-- TODO
|
|
430
|
-
( model
|
|
545
|
+
Pages.Msg.FormFieldEvent value ->
|
|
546
|
+
-- TODO when init is called for a new page, also need to clear out client-side `pageFormState`
|
|
547
|
+
( { model | pageFormState = Pages.FormState.update value model.pageFormState }
|
|
548
|
+
, NoEffect
|
|
549
|
+
)
|
|
431
550
|
|
|
432
|
-
|
|
551
|
+
UpdateCacheAndUrlNew scrollToTopWhenDone urlWithoutRedirectResolution maybeUserMsg updateResult ->
|
|
552
|
+
-- TODO remove all fetchers that are in the state `FetcherReloading` here -- I think that's the right logic?
|
|
433
553
|
case
|
|
434
|
-
Result.map2 Tuple.pair
|
|
554
|
+
Result.map2 Tuple.pair
|
|
555
|
+
(updateResult
|
|
556
|
+
|> Result.mapError (\_ -> "Http error")
|
|
557
|
+
)
|
|
558
|
+
model.pageData
|
|
435
559
|
of
|
|
436
|
-
|
|
437
|
-
-- to keep track of the last url change
|
|
438
|
-
Ok ( ( _, contentJson, updatedCache ), pageData ) ->
|
|
560
|
+
Ok ( ( newUrl, newData ), previousPageData ) ->
|
|
439
561
|
let
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
562
|
+
redirectPending : Bool
|
|
563
|
+
redirectPending =
|
|
564
|
+
newUrl /= urlWithoutRedirectResolution
|
|
565
|
+
in
|
|
566
|
+
if redirectPending then
|
|
567
|
+
( { model
|
|
568
|
+
| pendingRedirect = True
|
|
569
|
+
, pendingData =
|
|
570
|
+
case newData of
|
|
571
|
+
ResponseSketch.RenderPage pageData actionData ->
|
|
572
|
+
Just ( pageData, previousPageData.sharedData, actionData )
|
|
573
|
+
|
|
574
|
+
ResponseSketch.HotUpdate pageData sharedData actionData ->
|
|
575
|
+
Just ( pageData, sharedData, actionData )
|
|
576
|
+
|
|
577
|
+
_ ->
|
|
578
|
+
Nothing
|
|
579
|
+
}
|
|
580
|
+
, BrowserReplaceUrl newUrl.path
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
else
|
|
584
|
+
let
|
|
585
|
+
stayingOnSamePath : Bool
|
|
586
|
+
stayingOnSamePath =
|
|
587
|
+
newUrl.path == model.url.path
|
|
588
|
+
|
|
589
|
+
( newPageData, newSharedData, newActionData ) =
|
|
590
|
+
case newData of
|
|
591
|
+
ResponseSketch.RenderPage pageData actionData ->
|
|
592
|
+
( pageData, previousPageData.sharedData, actionData )
|
|
593
|
+
|
|
594
|
+
ResponseSketch.HotUpdate pageData sharedData actionData ->
|
|
595
|
+
( pageData, sharedData, actionData )
|
|
596
|
+
|
|
597
|
+
_ ->
|
|
598
|
+
( previousPageData.pageData, previousPageData.sharedData, previousPageData.actionData )
|
|
599
|
+
|
|
600
|
+
updatedPageData : { userModel : userModel, sharedData : sharedData, actionData : Maybe actionData, pageData : pageData }
|
|
601
|
+
updatedPageData =
|
|
602
|
+
{ userModel = userModel
|
|
603
|
+
, sharedData = newSharedData
|
|
604
|
+
, pageData = newPageData
|
|
605
|
+
, actionData = newActionData
|
|
606
|
+
}
|
|
450
607
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
(
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
608
|
+
( userModel, _ ) =
|
|
609
|
+
-- TODO if urlWithoutRedirectResolution is different from the url with redirect resolution, then
|
|
610
|
+
-- instead of calling update, call pushUrl (I think?)
|
|
611
|
+
-- TODO include user Cmd
|
|
612
|
+
config.update model.pageFormState
|
|
613
|
+
(model.inFlightFetchers |> toFetcherState)
|
|
614
|
+
(model.transition |> Maybe.map Tuple.second)
|
|
615
|
+
newSharedData
|
|
616
|
+
newPageData
|
|
617
|
+
model.key
|
|
618
|
+
(config.onPageChange
|
|
619
|
+
{ protocol = model.url.protocol
|
|
620
|
+
, host = model.url.host
|
|
621
|
+
, port_ = model.url.port_
|
|
622
|
+
, path = urlPathToPath urlWithoutRedirectResolution
|
|
623
|
+
, query = urlWithoutRedirectResolution.query
|
|
624
|
+
, fragment = urlWithoutRedirectResolution.fragment
|
|
625
|
+
, metadata = config.urlToRoute urlWithoutRedirectResolution
|
|
626
|
+
}
|
|
461
627
|
)
|
|
628
|
+
previousPageData.userModel
|
|
629
|
+
|
|
630
|
+
updatedModel : Model userModel pageData actionData sharedData
|
|
631
|
+
updatedModel =
|
|
632
|
+
-- TODO should these be the same (no if)?
|
|
633
|
+
if model.pendingRedirect || redirectPending then
|
|
634
|
+
{ model
|
|
635
|
+
| url = newUrl
|
|
636
|
+
, pageData = Ok updatedPageData
|
|
637
|
+
, transition = Nothing
|
|
638
|
+
, pendingRedirect = False
|
|
639
|
+
, pageFormState = Dict.empty
|
|
640
|
+
}
|
|
462
641
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
{ protocol = model.url.protocol
|
|
470
|
-
, host = model.url.host
|
|
471
|
-
, port_ = model.url.port_
|
|
472
|
-
, path = url |> urlPathToPath config
|
|
473
|
-
, query = url.query
|
|
474
|
-
, fragment = url.fragment
|
|
475
|
-
, metadata = config.urlToRoute url
|
|
642
|
+
else
|
|
643
|
+
{ model
|
|
644
|
+
| url = newUrl
|
|
645
|
+
, pageData = Ok updatedPageData
|
|
646
|
+
, pendingRedirect = False
|
|
647
|
+
, transition = Nothing
|
|
476
648
|
}
|
|
477
|
-
)
|
|
478
|
-
pageData.userModel
|
|
479
649
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
650
|
+
onActionMsg : Maybe userMsg
|
|
651
|
+
onActionMsg =
|
|
652
|
+
newActionData |> Maybe.andThen config.onActionData
|
|
653
|
+
in
|
|
654
|
+
( { updatedModel
|
|
655
|
+
| ariaNavigationAnnouncement = mainView config updatedModel |> .title
|
|
656
|
+
, currentPath = newUrl.path
|
|
657
|
+
}
|
|
658
|
+
, if not stayingOnSamePath && scrollToTopWhenDone then
|
|
659
|
+
ScrollToTop
|
|
660
|
+
|
|
661
|
+
else
|
|
662
|
+
NoEffect
|
|
663
|
+
)
|
|
664
|
+
|> (case maybeUserMsg of
|
|
665
|
+
Just userMsg ->
|
|
666
|
+
withUserMsg config userMsg
|
|
496
667
|
|
|
497
|
-
|
|
668
|
+
Nothing ->
|
|
669
|
+
identity
|
|
670
|
+
)
|
|
671
|
+
|> (case onActionMsg of
|
|
672
|
+
Just actionMsg ->
|
|
673
|
+
withUserMsg config actionMsg
|
|
674
|
+
|
|
675
|
+
Nothing ->
|
|
676
|
+
identity
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
Err _ ->
|
|
498
680
|
{-
|
|
499
|
-
When there is an error loading the content.
|
|
681
|
+
When there is an error loading the content.dat, we are either
|
|
500
682
|
1) in the dev server, and should show the relevant DataSource error for the page
|
|
501
683
|
we're navigating to. This could be done more cleanly, but it's simplest to just
|
|
502
684
|
do a fresh page load and use the code path for presenting an error for a fresh page.
|
|
@@ -509,237 +691,795 @@ update config appMsg model =
|
|
|
509
691
|
|
|
510
692
|
-}
|
|
511
693
|
( model
|
|
512
|
-
,
|
|
694
|
+
, urlWithoutRedirectResolution
|
|
513
695
|
|> Url.toString
|
|
514
|
-
|>
|
|
696
|
+
|> BrowserLoadUrl
|
|
515
697
|
)
|
|
516
698
|
|
|
517
699
|
PageScrollComplete ->
|
|
518
|
-
( model,
|
|
700
|
+
( model, NoEffect )
|
|
701
|
+
|
|
702
|
+
HotReloadCompleteNew pageDataBytes ->
|
|
703
|
+
model.pageData
|
|
704
|
+
|> Result.map
|
|
705
|
+
(\pageData ->
|
|
706
|
+
let
|
|
707
|
+
newThing : Maybe (ResponseSketch pageData actionData sharedData)
|
|
708
|
+
newThing =
|
|
709
|
+
-- TODO if ErrorPage, call ErrorPage.init to get appropriate Model?
|
|
710
|
+
pageDataBytes
|
|
711
|
+
|> Bytes.Decode.decode config.decodeResponse
|
|
712
|
+
in
|
|
713
|
+
case newThing of
|
|
714
|
+
Just (ResponseSketch.RenderPage newPageData newActionData) ->
|
|
715
|
+
( { model
|
|
716
|
+
| pageData =
|
|
717
|
+
Ok
|
|
718
|
+
{ userModel = pageData.userModel
|
|
719
|
+
, sharedData = pageData.sharedData
|
|
720
|
+
, pageData = newPageData
|
|
721
|
+
, actionData = newActionData
|
|
722
|
+
}
|
|
723
|
+
, notFound = Nothing
|
|
724
|
+
}
|
|
725
|
+
, NoEffect
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
Just (ResponseSketch.HotUpdate newPageData newSharedData newActionData) ->
|
|
729
|
+
( { model
|
|
730
|
+
| pageData =
|
|
731
|
+
Ok
|
|
732
|
+
{ userModel = pageData.userModel
|
|
733
|
+
, sharedData = newSharedData
|
|
734
|
+
, pageData = newPageData
|
|
735
|
+
, actionData = newActionData
|
|
736
|
+
}
|
|
737
|
+
, notFound = Nothing
|
|
738
|
+
}
|
|
739
|
+
, NoEffect
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
Just (ResponseSketch.NotFound info) ->
|
|
743
|
+
( { model | notFound = Just info }, NoEffect )
|
|
744
|
+
|
|
745
|
+
_ ->
|
|
746
|
+
( model, NoEffect )
|
|
747
|
+
)
|
|
748
|
+
|> Result.withDefault ( model, NoEffect )
|
|
749
|
+
|
|
750
|
+
FetcherStarted fetcherKey transitionId fetcherData initiatedAt ->
|
|
751
|
+
( { model
|
|
752
|
+
| inFlightFetchers =
|
|
753
|
+
model.inFlightFetchers
|
|
754
|
+
|> Dict.insert fetcherKey
|
|
755
|
+
( transitionId
|
|
756
|
+
, { payload = fetcherData
|
|
757
|
+
, status = Pages.Transition.FetcherSubmitting
|
|
758
|
+
, initiatedAt = initiatedAt
|
|
759
|
+
}
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
, NoEffect
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
toFetcherState : Dict String ( Int, Pages.Transition.FetcherState actionData ) -> Dict String (Pages.Transition.FetcherState actionData)
|
|
767
|
+
toFetcherState inFlightFetchers =
|
|
768
|
+
inFlightFetchers
|
|
769
|
+
|> Dict.map (\_ ( index, fetcherState ) -> fetcherState)
|
|
519
770
|
|
|
520
|
-
|
|
771
|
+
|
|
772
|
+
performUserMsg :
|
|
773
|
+
userMsg
|
|
774
|
+
-> ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
775
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
776
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
777
|
+
performUserMsg userMsg config ( model, effect ) =
|
|
778
|
+
case model.pageData of
|
|
779
|
+
Ok pageData ->
|
|
521
780
|
let
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
{ currentUrl = model.url
|
|
525
|
-
, basePath = config.basePath
|
|
526
|
-
}
|
|
781
|
+
( userModel, userCmd ) =
|
|
782
|
+
config.update model.pageFormState (model.inFlightFetchers |> toFetcherState) (model.transition |> Maybe.map Tuple.second) pageData.sharedData pageData.pageData model.key userMsg pageData.userModel
|
|
527
783
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
(config.data (config.urlToRoute model.url))
|
|
532
|
-
contentJson.staticData
|
|
533
|
-
|> Result.mapError (StaticHttpRequest.toBuildError model.url.path)
|
|
534
|
-
|
|
535
|
-
sharedDataResult : Result BuildError sharedData
|
|
536
|
-
sharedDataResult =
|
|
537
|
-
StaticHttpRequest.resolve ApplicationType.Browser
|
|
538
|
-
config.sharedData
|
|
539
|
-
contentJson.staticData
|
|
540
|
-
|> Result.mapError (StaticHttpRequest.toBuildError model.url.path)
|
|
541
|
-
|
|
542
|
-
from404ToNon404 : Bool
|
|
543
|
-
from404ToNon404 =
|
|
544
|
-
not contentJson.is404
|
|
545
|
-
&& was404
|
|
546
|
-
|
|
547
|
-
was404 : Bool
|
|
548
|
-
was404 =
|
|
549
|
-
ContentCache.is404 model.contentCache urls
|
|
784
|
+
updatedPageData : Result error { userModel : userModel, pageData : pageData, actionData : Maybe actionData, sharedData : sharedData }
|
|
785
|
+
updatedPageData =
|
|
786
|
+
Ok { pageData | userModel = userModel }
|
|
550
787
|
in
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
updateResult : Maybe ( userModel, Cmd userMsg )
|
|
555
|
-
updateResult =
|
|
556
|
-
if from404ToNon404 then
|
|
557
|
-
case model.pageData of
|
|
558
|
-
Ok pageData_ ->
|
|
559
|
-
config.update
|
|
560
|
-
sharedData
|
|
561
|
-
pageData
|
|
562
|
-
(Just model.key)
|
|
563
|
-
(config.onPageChange
|
|
564
|
-
{ protocol = model.url.protocol
|
|
565
|
-
, host = model.url.host
|
|
566
|
-
, port_ = model.url.port_
|
|
567
|
-
, path = model.url |> urlPathToPath config
|
|
568
|
-
, query = model.url.query
|
|
569
|
-
, fragment = model.url.fragment
|
|
570
|
-
, metadata = config.urlToRoute model.url
|
|
571
|
-
}
|
|
572
|
-
)
|
|
573
|
-
pageData_.userModel
|
|
574
|
-
|> Just
|
|
788
|
+
( { model | pageData = updatedPageData }
|
|
789
|
+
, Batch [ effect, UserCmd userCmd ]
|
|
790
|
+
)
|
|
575
791
|
|
|
576
|
-
|
|
577
|
-
|
|
792
|
+
Err _ ->
|
|
793
|
+
( model, effect )
|
|
578
794
|
|
|
579
|
-
|
|
580
|
-
|
|
795
|
+
|
|
796
|
+
perform : ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Msg userMsg pageData actionData sharedData errorPage) errorPage -> Model userModel pageData actionData sharedData -> Effect userMsg pageData actionData sharedData userEffect errorPage -> Cmd (Msg userMsg pageData actionData sharedData errorPage)
|
|
797
|
+
perform config model effect =
|
|
798
|
+
-- elm-review: known-unoptimized-recursion
|
|
799
|
+
case effect of
|
|
800
|
+
NoEffect ->
|
|
801
|
+
Cmd.none
|
|
802
|
+
|
|
803
|
+
Batch effects ->
|
|
804
|
+
effects
|
|
805
|
+
|> List.map (perform config model)
|
|
806
|
+
|> Cmd.batch
|
|
807
|
+
|
|
808
|
+
ScrollToTop ->
|
|
809
|
+
Task.perform (\_ -> PageScrollComplete) (Dom.setViewport 0 0)
|
|
810
|
+
|
|
811
|
+
BrowserLoadUrl url ->
|
|
812
|
+
Browser.Navigation.load url
|
|
813
|
+
|
|
814
|
+
BrowserPushUrl url ->
|
|
815
|
+
model.key
|
|
816
|
+
|> Maybe.map
|
|
817
|
+
(\key ->
|
|
818
|
+
Browser.Navigation.pushUrl key url
|
|
819
|
+
)
|
|
820
|
+
|> Maybe.withDefault Cmd.none
|
|
821
|
+
|
|
822
|
+
BrowserReplaceUrl url ->
|
|
823
|
+
model.key
|
|
824
|
+
|> Maybe.map
|
|
825
|
+
(\key ->
|
|
826
|
+
Browser.Navigation.replaceUrl key url
|
|
827
|
+
)
|
|
828
|
+
|> Maybe.withDefault Cmd.none
|
|
829
|
+
|
|
830
|
+
FetchPageData transitionKey maybeRequestInfo url toMsg ->
|
|
831
|
+
fetchRouteData True transitionKey toMsg config url maybeRequestInfo
|
|
832
|
+
|
|
833
|
+
Submit fields ->
|
|
834
|
+
if fields.method == Get then
|
|
835
|
+
model.key
|
|
836
|
+
|> Maybe.map (\key -> Browser.Navigation.pushUrl key (appendFormQueryParams fields))
|
|
837
|
+
|> Maybe.withDefault Cmd.none
|
|
838
|
+
|
|
839
|
+
else
|
|
840
|
+
let
|
|
841
|
+
urlToSubmitTo : Url
|
|
842
|
+
urlToSubmitTo =
|
|
843
|
+
-- TODO add optional path parameter to Submit variant to allow submitting to other routes
|
|
844
|
+
model.url
|
|
845
|
+
in
|
|
846
|
+
fetchRouteData False -1 (UpdateCacheAndUrlNew False model.url Nothing) config urlToSubmitTo (Just fields)
|
|
847
|
+
|
|
848
|
+
SubmitFetcher fetcherKey transitionId formData ->
|
|
849
|
+
startFetcher2 config False fetcherKey transitionId formData model
|
|
850
|
+
|
|
851
|
+
UserCmd cmd ->
|
|
852
|
+
case model.key of
|
|
853
|
+
Just key ->
|
|
854
|
+
let
|
|
855
|
+
prepare :
|
|
856
|
+
(Result Http.Error Url -> userMsg)
|
|
857
|
+
-> Result Http.Error ( Url, ResponseSketch pageData actionData sharedData )
|
|
858
|
+
-> Msg userMsg pageData actionData sharedData errorPage
|
|
859
|
+
prepare toMsg info =
|
|
860
|
+
UpdateCacheAndUrlNew False model.url (info |> Result.map Tuple.first |> toMsg |> Just) info
|
|
581
861
|
in
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
)
|
|
862
|
+
cmd
|
|
863
|
+
|> config.perform
|
|
864
|
+
{ fetchRouteData =
|
|
865
|
+
\fetchInfo ->
|
|
866
|
+
fetchRouteData False
|
|
867
|
+
-1
|
|
868
|
+
(prepare fetchInfo.toMsg)
|
|
869
|
+
config
|
|
870
|
+
(urlFromAction model.url fetchInfo.data)
|
|
871
|
+
fetchInfo.data
|
|
872
|
+
|
|
873
|
+
---- TODO map the Msg with the wrapper type (like in the PR branch)
|
|
874
|
+
, submit =
|
|
875
|
+
\fetchInfo ->
|
|
876
|
+
fetchRouteData False -1 (prepare fetchInfo.toMsg) config (fetchInfo.values.action |> Url.fromString |> Maybe.withDefault model.url) (Just fetchInfo.values)
|
|
877
|
+
, runFetcher =
|
|
878
|
+
\(Pages.Fetcher.Fetcher options) ->
|
|
879
|
+
-- TODO need to get the fetcherId here
|
|
880
|
+
-- TODO need to increment and pass in the transitionId
|
|
881
|
+
startFetcher "TODO" -1 options model
|
|
882
|
+
, fromPageMsg = Pages.Msg.UserMsg >> UserMsg
|
|
883
|
+
, key = key
|
|
884
|
+
, setField = \info -> Task.succeed (SetField info) |> Task.perform identity
|
|
885
|
+
}
|
|
605
886
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
pagePath : Path
|
|
609
|
-
pagePath =
|
|
610
|
-
urlsToPagePath urls
|
|
611
|
-
|
|
612
|
-
userFlags : Pages.Flags.Flags
|
|
613
|
-
userFlags =
|
|
614
|
-
model.userFlags
|
|
615
|
-
|> Decode.decodeValue
|
|
616
|
-
(Decode.field "userFlags" Decode.value)
|
|
617
|
-
|> Result.withDefault Json.Encode.null
|
|
618
|
-
|> Pages.Flags.BrowserFlags
|
|
619
|
-
|
|
620
|
-
( userModel, userCmd ) =
|
|
621
|
-
Just
|
|
622
|
-
{ path =
|
|
623
|
-
{ path = pagePath
|
|
624
|
-
, query = model.url.query
|
|
625
|
-
, fragment = model.url.fragment
|
|
626
|
-
}
|
|
627
|
-
, metadata = config.urlToRoute model.url
|
|
628
|
-
, pageUrl =
|
|
629
|
-
Just
|
|
630
|
-
{ protocol = model.url.protocol
|
|
631
|
-
, host = model.url.host
|
|
632
|
-
, port_ = model.url.port_
|
|
633
|
-
, path = pagePath
|
|
634
|
-
, query = model.url.query |> Maybe.map QueryParams.fromString
|
|
635
|
-
, fragment = model.url.fragment
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|> config.init userFlags sharedData pageData (Just model.key)
|
|
639
|
-
in
|
|
640
|
-
( { model
|
|
641
|
-
| contentCache =
|
|
642
|
-
ContentCache.init
|
|
643
|
-
(Just
|
|
644
|
-
( urls.currentUrl
|
|
645
|
-
|> config.urlToRoute
|
|
646
|
-
|> config.routeToPath
|
|
647
|
-
, contentJson
|
|
648
|
-
)
|
|
649
|
-
)
|
|
650
|
-
, pageData =
|
|
651
|
-
model.pageData
|
|
652
|
-
|> Result.map
|
|
653
|
-
(\previousPageData ->
|
|
654
|
-
{ pageData = pageData
|
|
655
|
-
, sharedData = sharedData
|
|
656
|
-
, userModel = previousPageData.userModel
|
|
657
|
-
}
|
|
658
|
-
)
|
|
659
|
-
|> Result.withDefault
|
|
660
|
-
{ pageData = pageData
|
|
661
|
-
, sharedData = sharedData
|
|
662
|
-
, userModel = userModel
|
|
663
|
-
}
|
|
664
|
-
|> Ok
|
|
665
|
-
}
|
|
666
|
-
, userCmd |> Cmd.map UserMsg
|
|
667
|
-
)
|
|
887
|
+
Nothing ->
|
|
888
|
+
Cmd.none
|
|
668
889
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
890
|
+
CancelRequest transitionKey ->
|
|
891
|
+
Http.cancel (String.fromInt transitionKey)
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
startFetcher : String -> Int -> { fields : List ( String, String ), url : Maybe String, decoder : Result error Bytes -> value, headers : List ( String, String ) } -> Model userModel pageData actionData sharedData -> Cmd (Msg value pageData actionData sharedData errorPage)
|
|
895
|
+
startFetcher fetcherKey transitionId options model =
|
|
896
|
+
let
|
|
897
|
+
encodedBody : String
|
|
898
|
+
encodedBody =
|
|
899
|
+
FormDecoder.encodeFormData
|
|
900
|
+
{ fields = options.fields
|
|
901
|
+
|
|
902
|
+
-- TODO remove hardcoding
|
|
903
|
+
, action = ""
|
|
904
|
+
|
|
905
|
+
-- TODO remove hardcoding
|
|
906
|
+
, method = Post
|
|
907
|
+
, id = Nothing
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
formData : { method : Method, action : String, fields : List ( String, String ), id : Maybe String }
|
|
911
|
+
formData =
|
|
912
|
+
{ -- TODO remove hardcoding
|
|
913
|
+
method = Get
|
|
914
|
+
|
|
915
|
+
-- TODO pass FormData directly
|
|
916
|
+
, action = options.url |> Maybe.withDefault model.url.path
|
|
917
|
+
, fields = options.fields
|
|
918
|
+
, id = Nothing
|
|
919
|
+
}
|
|
920
|
+
in
|
|
921
|
+
-- TODO make sure that `actionData` isn't updated in Model for fetchers
|
|
922
|
+
Cmd.batch
|
|
923
|
+
[ cancelStaleFetchers model
|
|
924
|
+
, Time.now |> Task.map (FetcherStarted fetcherKey transitionId formData) |> Task.perform identity
|
|
925
|
+
, Http.request
|
|
926
|
+
{ expect =
|
|
927
|
+
Http.expectBytesResponse (FetcherComplete False fetcherKey model.nextTransitionKey)
|
|
928
|
+
(\bytes ->
|
|
929
|
+
case bytes of
|
|
930
|
+
Http.GoodStatus_ metadata bytesBody ->
|
|
931
|
+
( options.decoder (Ok bytesBody)
|
|
932
|
+
|> Just
|
|
933
|
+
, Nothing
|
|
679
934
|
)
|
|
680
|
-
|
|
681
|
-
|
|
935
|
+
|> Ok
|
|
936
|
+
|
|
937
|
+
Http.BadUrl_ string ->
|
|
938
|
+
Err <| Http.BadUrl string
|
|
939
|
+
|
|
940
|
+
Http.Timeout_ ->
|
|
941
|
+
Err <| Http.Timeout
|
|
942
|
+
|
|
943
|
+
Http.NetworkError_ ->
|
|
944
|
+
Err <| Http.NetworkError
|
|
945
|
+
|
|
946
|
+
Http.BadStatus_ metadata body ->
|
|
947
|
+
Err <| Http.BadStatus metadata.statusCode
|
|
682
948
|
)
|
|
949
|
+
, tracker = Nothing
|
|
950
|
+
, body = Http.stringBody "application/x-www-form-urlencoded" encodedBody
|
|
951
|
+
, headers = options.headers |> List.map (\( name, value ) -> Http.header name value)
|
|
952
|
+
, url = options.url |> Maybe.withDefault (Path.join [ model.url.path, "content.dat" ] |> Path.toAbsolute)
|
|
953
|
+
, method = "POST"
|
|
954
|
+
, timeout = Nothing
|
|
955
|
+
}
|
|
956
|
+
]
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
startFetcher2 :
|
|
960
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData effect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
961
|
+
-> Bool
|
|
962
|
+
-> String
|
|
963
|
+
-> Int
|
|
964
|
+
-> FormData
|
|
965
|
+
-> Model userModel pageData actionData sharedData
|
|
966
|
+
-> Cmd (Msg userMsg pageData actionData sharedData errorPage)
|
|
967
|
+
startFetcher2 config fromPageReload fetcherKey transitionId formData model =
|
|
968
|
+
let
|
|
969
|
+
encodedBody : String
|
|
970
|
+
encodedBody =
|
|
971
|
+
FormDecoder.encodeFormData formData
|
|
972
|
+
in
|
|
973
|
+
-- TODO make sure that `actionData` isn't updated in Model for fetchers
|
|
974
|
+
Cmd.batch
|
|
975
|
+
[ cancelStaleFetchers model
|
|
976
|
+
, case Dict.get fetcherKey model.inFlightFetchers of
|
|
977
|
+
Just ( inFlightId, inFlightFetcher ) ->
|
|
978
|
+
Http.cancel (String.fromInt inFlightId)
|
|
979
|
+
|
|
980
|
+
Nothing ->
|
|
981
|
+
Cmd.none
|
|
982
|
+
, Time.now |> Task.map (FetcherStarted fetcherKey transitionId formData) |> Task.perform identity
|
|
983
|
+
, Http.request
|
|
984
|
+
{ expect =
|
|
985
|
+
Http.expectBytesResponse (FetcherComplete fromPageReload fetcherKey model.nextTransitionKey)
|
|
986
|
+
(\bytes ->
|
|
987
|
+
case bytes of
|
|
988
|
+
Http.GoodStatus_ metadata bytesBody ->
|
|
989
|
+
let
|
|
990
|
+
decodedAction : Maybe actionData
|
|
991
|
+
decodedAction =
|
|
992
|
+
case Bytes.Decode.decode config.decodeResponse bytesBody of
|
|
993
|
+
Just (ResponseSketch.RenderPage _ maybeAction) ->
|
|
994
|
+
maybeAction
|
|
995
|
+
|
|
996
|
+
Just (ResponseSketch.HotUpdate pageData shared maybeAction) ->
|
|
997
|
+
maybeAction
|
|
998
|
+
|
|
999
|
+
Just (ResponseSketch.NotFound notFound) ->
|
|
1000
|
+
Nothing
|
|
1001
|
+
|
|
1002
|
+
_ ->
|
|
1003
|
+
Nothing
|
|
1004
|
+
in
|
|
1005
|
+
-- TODO maybe have an optional way to pass the bytes through?
|
|
1006
|
+
Ok ( Nothing, decodedAction )
|
|
1007
|
+
|
|
1008
|
+
Http.BadUrl_ string ->
|
|
1009
|
+
Err <| Http.BadUrl string
|
|
1010
|
+
|
|
1011
|
+
Http.Timeout_ ->
|
|
1012
|
+
Err <| Http.Timeout
|
|
1013
|
+
|
|
1014
|
+
Http.NetworkError_ ->
|
|
1015
|
+
Err <| Http.NetworkError
|
|
1016
|
+
|
|
1017
|
+
Http.BadStatus_ metadata body ->
|
|
1018
|
+
Err <| Http.BadStatus metadata.statusCode
|
|
1019
|
+
)
|
|
1020
|
+
, tracker = Just (String.fromInt transitionId)
|
|
1021
|
+
|
|
1022
|
+
-- TODO use formData.method to do either query params or POST body
|
|
1023
|
+
, body = Http.stringBody "application/x-www-form-urlencoded" encodedBody
|
|
1024
|
+
, headers = []
|
|
1025
|
+
|
|
1026
|
+
-- TODO use formData.method to do either query params or POST body
|
|
1027
|
+
, url = formData.action |> Url.fromString |> Maybe.map (\{ path } -> Path.join [ path, "content.dat" ] |> Path.toAbsolute) |> Maybe.withDefault "/"
|
|
1028
|
+
, method = formData.method |> FormDecoder.methodToString
|
|
1029
|
+
, timeout = Nothing
|
|
1030
|
+
}
|
|
1031
|
+
]
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
cancelStaleFetchers : Model userModel pageData actionData sharedData -> Cmd msg
|
|
1035
|
+
cancelStaleFetchers model =
|
|
1036
|
+
model.inFlightFetchers
|
|
1037
|
+
|> Dict.toList
|
|
1038
|
+
|> List.filterMap
|
|
1039
|
+
(\( fetcherKey, ( id, fetcher ) ) ->
|
|
1040
|
+
case fetcher.status of
|
|
1041
|
+
Pages.Transition.FetcherReloading _ ->
|
|
1042
|
+
Http.cancel (String.fromInt id)
|
|
1043
|
+
|> Just
|
|
1044
|
+
|
|
1045
|
+
Pages.Transition.FetcherSubmitting ->
|
|
1046
|
+
Nothing
|
|
1047
|
+
|
|
1048
|
+
Pages.Transition.FetcherComplete _ ->
|
|
1049
|
+
Nothing
|
|
1050
|
+
)
|
|
1051
|
+
|> Cmd.batch
|
|
1052
|
+
|
|
683
1053
|
|
|
684
|
-
|
|
685
|
-
|
|
1054
|
+
appendFormQueryParams : FormData -> String
|
|
1055
|
+
appendFormQueryParams fields =
|
|
1056
|
+
(fields.action
|
|
1057
|
+
|> Url.fromString
|
|
1058
|
+
|> Maybe.map .path
|
|
1059
|
+
|> Maybe.withDefault "/"
|
|
1060
|
+
)
|
|
1061
|
+
++ (case fields.method of
|
|
1062
|
+
Get ->
|
|
1063
|
+
"?" ++ FormDecoder.encodeFormData fields
|
|
1064
|
+
|
|
1065
|
+
Post ->
|
|
1066
|
+
""
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
urlFromAction : Url -> Maybe FormData -> Url
|
|
1071
|
+
urlFromAction currentUrl fetchInfo =
|
|
1072
|
+
fetchInfo |> Maybe.map .action |> Maybe.andThen Url.fromString |> Maybe.withDefault currentUrl
|
|
686
1073
|
|
|
687
1074
|
|
|
688
1075
|
{-| -}
|
|
689
1076
|
application :
|
|
690
|
-
ProgramConfig userMsg userModel route
|
|
691
|
-
-> Platform.Program Flags (Model userModel pageData sharedData) (Msg userMsg)
|
|
1077
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData effect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
1078
|
+
-> Platform.Program Flags (Model userModel pageData actionData sharedData) (Msg userMsg pageData actionData sharedData errorPage)
|
|
692
1079
|
application config =
|
|
693
1080
|
Browser.application
|
|
694
1081
|
{ init =
|
|
695
1082
|
\flags url key ->
|
|
696
|
-
|
|
1083
|
+
let
|
|
1084
|
+
( model, effect ) =
|
|
1085
|
+
init config flags url (Just key)
|
|
1086
|
+
in
|
|
1087
|
+
( model
|
|
1088
|
+
, effect |> perform config model
|
|
1089
|
+
)
|
|
697
1090
|
, view = view config
|
|
698
|
-
, update =
|
|
1091
|
+
, update =
|
|
1092
|
+
\msg model ->
|
|
1093
|
+
update config msg model |> Tuple.mapSecond (perform config model)
|
|
699
1094
|
, subscriptions =
|
|
700
1095
|
\model ->
|
|
701
|
-
let
|
|
702
|
-
urls : { currentUrl : Url }
|
|
703
|
-
urls =
|
|
704
|
-
{ currentUrl = model.url }
|
|
705
|
-
in
|
|
706
1096
|
case model.pageData of
|
|
707
1097
|
Ok pageData ->
|
|
1098
|
+
let
|
|
1099
|
+
urls : { currentUrl : Url }
|
|
1100
|
+
urls =
|
|
1101
|
+
{ currentUrl = model.url }
|
|
1102
|
+
in
|
|
708
1103
|
Sub.batch
|
|
709
1104
|
[ config.subscriptions (model.url |> config.urlToRoute)
|
|
710
1105
|
(urls.currentUrl |> config.urlToRoute |> config.routeToPath |> Path.join)
|
|
711
1106
|
pageData.userModel
|
|
712
|
-
|> Sub.map UserMsg
|
|
713
|
-
, config.
|
|
714
|
-
|> Sub.map
|
|
715
|
-
(\decodeValue ->
|
|
716
|
-
case decodeValue |> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder) of
|
|
717
|
-
Ok contentJson ->
|
|
718
|
-
HotReloadComplete contentJson
|
|
719
|
-
|
|
720
|
-
Err _ ->
|
|
721
|
-
-- TODO should be no message here
|
|
722
|
-
NoOp
|
|
723
|
-
)
|
|
1107
|
+
|> Sub.map (Pages.Msg.UserMsg >> UserMsg)
|
|
1108
|
+
, config.hotReloadData
|
|
1109
|
+
|> Sub.map HotReloadCompleteNew
|
|
724
1110
|
]
|
|
725
1111
|
|
|
726
1112
|
Err _ ->
|
|
727
|
-
config.
|
|
728
|
-
|> Sub.map
|
|
729
|
-
(\decodeValue ->
|
|
730
|
-
case decodeValue |> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder) of
|
|
731
|
-
Ok contentJson ->
|
|
732
|
-
HotReloadComplete contentJson
|
|
733
|
-
|
|
734
|
-
Err _ ->
|
|
735
|
-
-- TODO should be no message here
|
|
736
|
-
NoOp
|
|
737
|
-
)
|
|
1113
|
+
config.hotReloadData
|
|
1114
|
+
|> Sub.map HotReloadCompleteNew
|
|
738
1115
|
, onUrlChange = UrlChanged
|
|
739
1116
|
, onUrlRequest = LinkClicked
|
|
740
1117
|
}
|
|
741
1118
|
|
|
742
1119
|
|
|
743
|
-
|
|
744
|
-
|
|
1120
|
+
{-| -}
|
|
1121
|
+
type alias RequestInfo =
|
|
1122
|
+
{ contentType : String
|
|
1123
|
+
, body : String
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
withUserMsg :
|
|
1128
|
+
ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
1129
|
+
-> userMsg
|
|
1130
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
1131
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
1132
|
+
withUserMsg config userMsg ( model, effect ) =
|
|
1133
|
+
case model.pageData of
|
|
1134
|
+
Ok pageData ->
|
|
1135
|
+
let
|
|
1136
|
+
( userModel, userCmd ) =
|
|
1137
|
+
config.update model.pageFormState (model.inFlightFetchers |> toFetcherState) (model.transition |> Maybe.map Tuple.second) pageData.sharedData pageData.pageData model.key userMsg pageData.userModel
|
|
1138
|
+
|
|
1139
|
+
updatedPageData : Result error { userModel : userModel, pageData : pageData, actionData : Maybe actionData, sharedData : sharedData }
|
|
1140
|
+
updatedPageData =
|
|
1141
|
+
Ok { pageData | userModel = userModel }
|
|
1142
|
+
in
|
|
1143
|
+
( { model | pageData = updatedPageData }
|
|
1144
|
+
, Batch
|
|
1145
|
+
[ effect
|
|
1146
|
+
, UserCmd userCmd
|
|
1147
|
+
]
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
Err _ ->
|
|
1151
|
+
( model, effect )
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
urlPathToPath : Url -> Path
|
|
1155
|
+
urlPathToPath urls =
|
|
745
1156
|
urls.path |> Path.fromString
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
fetchRouteData :
|
|
1160
|
+
Bool
|
|
1161
|
+
-> Int
|
|
1162
|
+
-> (Result Http.Error ( Url, ResponseSketch pageData actionData sharedData ) -> Msg userMsg pageData actionData sharedData errorPage)
|
|
1163
|
+
-> ProgramConfig userMsg userModel route pageData actionData sharedData effect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
1164
|
+
-> Url
|
|
1165
|
+
-> Maybe FormData
|
|
1166
|
+
-> Cmd (Msg userMsg pageData actionData sharedData errorPage)
|
|
1167
|
+
fetchRouteData forPageDataReload transitionKey toMsg config url details =
|
|
1168
|
+
{-
|
|
1169
|
+
TODO:
|
|
1170
|
+
- [X] `toMsg` needs a parameter for the callback Msg so it can pass it on if there is a Redirect response
|
|
1171
|
+
- [X] Handle ResponseSketch.Redirect in `update`
|
|
1172
|
+
- [ ] Set transition state when loading
|
|
1173
|
+
- [ ] Set transition state when submitting
|
|
1174
|
+
- [ ] Should transition state for redirect after submit be the same as a regular load transition state?
|
|
1175
|
+
- [ ] Expose transition state (in Shared?)
|
|
1176
|
+
- [ ] Abort stale transitions
|
|
1177
|
+
- [ ] Increment cancel key counter in Model on new transitions
|
|
1178
|
+
|
|
1179
|
+
-}
|
|
1180
|
+
let
|
|
1181
|
+
formMethod : Method
|
|
1182
|
+
formMethod =
|
|
1183
|
+
details
|
|
1184
|
+
|> Maybe.map .method
|
|
1185
|
+
|> Maybe.withDefault Get
|
|
1186
|
+
in
|
|
1187
|
+
Http.request
|
|
1188
|
+
{ method = details |> Maybe.map (.method >> FormDecoder.methodToString) |> Maybe.withDefault "GET"
|
|
1189
|
+
, headers = []
|
|
1190
|
+
, url =
|
|
1191
|
+
"/"
|
|
1192
|
+
++ ((details
|
|
1193
|
+
|> Maybe.map .action
|
|
1194
|
+
|> Maybe.andThen Url.fromString
|
|
1195
|
+
-- TODO what should happen when there is an action pointing to a different domain? Should it be a no-op? Log a warning?
|
|
1196
|
+
|> Maybe.withDefault url
|
|
1197
|
+
)
|
|
1198
|
+
|> .path
|
|
1199
|
+
|> chopForwardSlashes
|
|
1200
|
+
|> String.split "/"
|
|
1201
|
+
|> List.filter ((/=) "")
|
|
1202
|
+
|> (\l -> l ++ [ "content.dat" ])
|
|
1203
|
+
|> String.join "/"
|
|
1204
|
+
)
|
|
1205
|
+
++ (case formMethod of
|
|
1206
|
+
Post ->
|
|
1207
|
+
"/"
|
|
1208
|
+
|
|
1209
|
+
Get ->
|
|
1210
|
+
details
|
|
1211
|
+
|> Maybe.map FormDecoder.encodeFormData
|
|
1212
|
+
|> Maybe.map (\encoded -> "?" ++ encoded)
|
|
1213
|
+
|> Maybe.withDefault ""
|
|
1214
|
+
)
|
|
1215
|
+
++ (case formMethod of
|
|
1216
|
+
-- TODO extract this to something unit testable
|
|
1217
|
+
-- TODO make states mutually exclusive for submissions and direct URL requests (shouldn't be possible to append two query param strings)
|
|
1218
|
+
Post ->
|
|
1219
|
+
""
|
|
1220
|
+
|
|
1221
|
+
Get ->
|
|
1222
|
+
url.query
|
|
1223
|
+
|> Maybe.map (\encoded -> "?" ++ encoded)
|
|
1224
|
+
|> Maybe.withDefault ""
|
|
1225
|
+
)
|
|
1226
|
+
, body =
|
|
1227
|
+
case formMethod of
|
|
1228
|
+
Post ->
|
|
1229
|
+
let
|
|
1230
|
+
urlEncodedFields : Maybe String
|
|
1231
|
+
urlEncodedFields =
|
|
1232
|
+
details
|
|
1233
|
+
|> Maybe.map FormDecoder.encodeFormData
|
|
1234
|
+
in
|
|
1235
|
+
urlEncodedFields
|
|
1236
|
+
|> Maybe.map (\encoded -> Http.stringBody "application/x-www-form-urlencoded" encoded)
|
|
1237
|
+
|> Maybe.withDefault Http.emptyBody
|
|
1238
|
+
|
|
1239
|
+
_ ->
|
|
1240
|
+
Http.emptyBody
|
|
1241
|
+
, expect =
|
|
1242
|
+
Http.expectBytesResponse (\response -> ProcessFetchResponse transitionKey response toMsg)
|
|
1243
|
+
(\response ->
|
|
1244
|
+
case response of
|
|
1245
|
+
Http.BadUrl_ url_ ->
|
|
1246
|
+
Err (Http.BadUrl url_)
|
|
1247
|
+
|
|
1248
|
+
Http.Timeout_ ->
|
|
1249
|
+
Err Http.Timeout
|
|
1250
|
+
|
|
1251
|
+
Http.NetworkError_ ->
|
|
1252
|
+
Err Http.NetworkError
|
|
1253
|
+
|
|
1254
|
+
Http.BadStatus_ metadata body ->
|
|
1255
|
+
body
|
|
1256
|
+
|> Bytes.Decode.decode config.decodeResponse
|
|
1257
|
+
|> Result.fromMaybe "Decoding error"
|
|
1258
|
+
|> Result.mapError Http.BadBody
|
|
1259
|
+
|> Result.map (\okResponse -> ( url, okResponse ))
|
|
1260
|
+
|
|
1261
|
+
Http.GoodStatus_ _ body ->
|
|
1262
|
+
body
|
|
1263
|
+
|> Bytes.Decode.decode config.decodeResponse
|
|
1264
|
+
|> Result.fromMaybe "Decoding error"
|
|
1265
|
+
|> Result.mapError Http.BadBody
|
|
1266
|
+
|> Result.map (\okResponse -> ( url, okResponse ))
|
|
1267
|
+
)
|
|
1268
|
+
, timeout = Nothing
|
|
1269
|
+
, tracker = Just (String.fromInt transitionKey)
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
chopForwardSlashes : String -> String
|
|
1274
|
+
chopForwardSlashes =
|
|
1275
|
+
chopStart "/" >> chopEnd "/"
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
chopStart : String -> String -> String
|
|
1279
|
+
chopStart needle string =
|
|
1280
|
+
if String.startsWith needle string then
|
|
1281
|
+
chopStart needle (String.dropLeft (String.length needle) string)
|
|
1282
|
+
|
|
1283
|
+
else
|
|
1284
|
+
string
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
chopEnd : String -> String -> String
|
|
1288
|
+
chopEnd needle string =
|
|
1289
|
+
if String.endsWith needle string then
|
|
1290
|
+
chopEnd needle (String.dropRight (String.length needle) string)
|
|
1291
|
+
|
|
1292
|
+
else
|
|
1293
|
+
string
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
startNewGetLoad :
|
|
1297
|
+
Url
|
|
1298
|
+
-> (Result Http.Error ( Url, ResponseSketch pageData actionData sharedData ) -> Msg userMsg pageData actionData sharedData errorPage)
|
|
1299
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
1300
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
1301
|
+
startNewGetLoad urlToGet toMsg ( model, effect ) =
|
|
1302
|
+
let
|
|
1303
|
+
cancelIfStale : Effect userMsg pageData actionData sharedData userEffect errorPage
|
|
1304
|
+
cancelIfStale =
|
|
1305
|
+
case model.transition of
|
|
1306
|
+
Just ( transitionKey, Pages.Transition.Loading path loadingKind ) ->
|
|
1307
|
+
CancelRequest transitionKey
|
|
1308
|
+
|
|
1309
|
+
_ ->
|
|
1310
|
+
NoEffect
|
|
1311
|
+
in
|
|
1312
|
+
( { model
|
|
1313
|
+
| nextTransitionKey = model.nextTransitionKey + 1
|
|
1314
|
+
, transition =
|
|
1315
|
+
( model.nextTransitionKey
|
|
1316
|
+
, case model.transition of
|
|
1317
|
+
Just ( transitionKey, Pages.Transition.LoadAfterSubmit submitData _ _ ) ->
|
|
1318
|
+
Pages.Transition.LoadAfterSubmit
|
|
1319
|
+
submitData
|
|
1320
|
+
(urlToGet.path |> Path.fromString)
|
|
1321
|
+
Pages.Transition.Load
|
|
1322
|
+
|
|
1323
|
+
Just ( transitionKey, Pages.Transition.Submitting submitData ) ->
|
|
1324
|
+
Pages.Transition.LoadAfterSubmit
|
|
1325
|
+
submitData
|
|
1326
|
+
(urlToGet.path |> Path.fromString)
|
|
1327
|
+
Pages.Transition.Load
|
|
1328
|
+
|
|
1329
|
+
_ ->
|
|
1330
|
+
Pages.Transition.Loading
|
|
1331
|
+
(urlToGet.path |> Path.fromString)
|
|
1332
|
+
Pages.Transition.Load
|
|
1333
|
+
)
|
|
1334
|
+
|> Just
|
|
1335
|
+
}
|
|
1336
|
+
, Batch
|
|
1337
|
+
[ FetchPageData
|
|
1338
|
+
model.nextTransitionKey
|
|
1339
|
+
Nothing
|
|
1340
|
+
urlToGet
|
|
1341
|
+
toMsg
|
|
1342
|
+
, cancelIfStale
|
|
1343
|
+
, effect
|
|
1344
|
+
]
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
clearLoadingFetchersAfterDataLoad : Int -> Model userModel pageData actionData sharedData -> Model userModel pageData actionData sharedData
|
|
1349
|
+
clearLoadingFetchersAfterDataLoad completedTransitionId model =
|
|
1350
|
+
{ model
|
|
1351
|
+
| inFlightFetchers =
|
|
1352
|
+
model.inFlightFetchers
|
|
1353
|
+
|> Dict.map
|
|
1354
|
+
(\_ ( transitionId, fetcherState ) ->
|
|
1355
|
+
-- TODO fetchers are never removed from the list. Need to decide how and when to remove them.
|
|
1356
|
+
--(fetcherState.status /= Pages.Transition.FetcherReloading) || (transitionId > completedTransitionId)
|
|
1357
|
+
case ( transitionId > completedTransitionId, fetcherState.status ) of
|
|
1358
|
+
( False, Pages.Transition.FetcherReloading actionData ) ->
|
|
1359
|
+
( transitionId
|
|
1360
|
+
, { fetcherState | status = Pages.Transition.FetcherComplete actionData }
|
|
1361
|
+
)
|
|
1362
|
+
|
|
1363
|
+
_ ->
|
|
1364
|
+
( transitionId, fetcherState )
|
|
1365
|
+
)
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
currentUrlWithPath : String -> Model userModel pageData actionData sharedData -> Url
|
|
1370
|
+
currentUrlWithPath path { url } =
|
|
1371
|
+
{ url | path = path }
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
loadDataAndUpdateUrl :
|
|
1375
|
+
( pageData, sharedData, Maybe actionData )
|
|
1376
|
+
-> Maybe userMsg
|
|
1377
|
+
-> Url
|
|
1378
|
+
-> Url
|
|
1379
|
+
-> Bool
|
|
1380
|
+
-> ProgramConfig userMsg userModel route pageData actionData sharedData userEffect (Msg userMsg pageData actionData sharedData errorPage) errorPage
|
|
1381
|
+
-> Model userModel pageData actionData sharedData
|
|
1382
|
+
-> ( Model userModel pageData actionData sharedData, Effect userMsg pageData actionData sharedData userEffect errorPage )
|
|
1383
|
+
loadDataAndUpdateUrl ( newPageData, newSharedData, newActionData ) maybeUserMsg urlWithoutRedirectResolution newUrl redirectPending config model =
|
|
1384
|
+
case model.pageData of
|
|
1385
|
+
Ok previousPageData ->
|
|
1386
|
+
let
|
|
1387
|
+
updatedPageData : { userModel : userModel, sharedData : sharedData, actionData : Maybe actionData, pageData : pageData }
|
|
1388
|
+
updatedPageData =
|
|
1389
|
+
{ userModel = userModel
|
|
1390
|
+
, sharedData = newSharedData
|
|
1391
|
+
, pageData = newPageData
|
|
1392
|
+
, actionData = newActionData
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
( userModel, _ ) =
|
|
1396
|
+
-- TODO if urlWithoutRedirectResolution is different from the url with redirect resolution, then
|
|
1397
|
+
-- instead of calling update, call pushUrl (I think?)
|
|
1398
|
+
-- TODO include user Cmd
|
|
1399
|
+
config.update model.pageFormState
|
|
1400
|
+
(model.inFlightFetchers |> toFetcherState)
|
|
1401
|
+
(model.transition |> Maybe.map Tuple.second)
|
|
1402
|
+
newSharedData
|
|
1403
|
+
newPageData
|
|
1404
|
+
model.key
|
|
1405
|
+
(config.onPageChange
|
|
1406
|
+
{ protocol = model.url.protocol
|
|
1407
|
+
, host = model.url.host
|
|
1408
|
+
, port_ = model.url.port_
|
|
1409
|
+
, path = urlPathToPath urlWithoutRedirectResolution
|
|
1410
|
+
, query = urlWithoutRedirectResolution.query
|
|
1411
|
+
, fragment = urlWithoutRedirectResolution.fragment
|
|
1412
|
+
, metadata = config.urlToRoute urlWithoutRedirectResolution
|
|
1413
|
+
}
|
|
1414
|
+
)
|
|
1415
|
+
previousPageData.userModel
|
|
1416
|
+
|
|
1417
|
+
updatedModel : Model userModel pageData actionData sharedData
|
|
1418
|
+
updatedModel =
|
|
1419
|
+
-- TODO should these be the same (no if)?
|
|
1420
|
+
if model.pendingRedirect || redirectPending then
|
|
1421
|
+
{ model
|
|
1422
|
+
| url = newUrl
|
|
1423
|
+
, pageData = Ok updatedPageData
|
|
1424
|
+
, transition = Nothing
|
|
1425
|
+
, pendingRedirect = False
|
|
1426
|
+
, pageFormState = Dict.empty
|
|
1427
|
+
|
|
1428
|
+
--, inFlightFetchers = Dict.empty
|
|
1429
|
+
, pendingData = Nothing
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
else
|
|
1433
|
+
{ model
|
|
1434
|
+
| url = newUrl
|
|
1435
|
+
, pageData = Ok updatedPageData
|
|
1436
|
+
, pendingRedirect = False
|
|
1437
|
+
, transition = Nothing
|
|
1438
|
+
, inFlightFetchers = Dict.empty
|
|
1439
|
+
, pendingData = Nothing
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
onActionMsg : Maybe userMsg
|
|
1443
|
+
onActionMsg =
|
|
1444
|
+
newActionData |> Maybe.andThen config.onActionData
|
|
1445
|
+
in
|
|
1446
|
+
( { updatedModel
|
|
1447
|
+
| ariaNavigationAnnouncement = mainView config updatedModel |> .title
|
|
1448
|
+
, currentPath = newUrl.path
|
|
1449
|
+
}
|
|
1450
|
+
, ScrollToTop
|
|
1451
|
+
)
|
|
1452
|
+
|> (case maybeUserMsg of
|
|
1453
|
+
Just userMsg ->
|
|
1454
|
+
withUserMsg config userMsg
|
|
1455
|
+
|
|
1456
|
+
Nothing ->
|
|
1457
|
+
identity
|
|
1458
|
+
)
|
|
1459
|
+
|> (case onActionMsg of
|
|
1460
|
+
Just actionMsg ->
|
|
1461
|
+
withUserMsg config actionMsg
|
|
1462
|
+
|
|
1463
|
+
Nothing ->
|
|
1464
|
+
identity
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
Err _ ->
|
|
1468
|
+
{-
|
|
1469
|
+
When there is an error loading the content.dat, we are either
|
|
1470
|
+
1) in the dev server, and should show the relevant DataSource error for the page
|
|
1471
|
+
we're navigating to. This could be done more cleanly, but it's simplest to just
|
|
1472
|
+
do a fresh page load and use the code path for presenting an error for a fresh page.
|
|
1473
|
+
2) In a production app. That means we had a successful build, so there were no DataSource failures,
|
|
1474
|
+
so the app must be stale (unless it's in some unexpected state from a bug). In the future,
|
|
1475
|
+
it probably makes sense to include some sort of hash of the app version we are fetching, match
|
|
1476
|
+
it with the current version that's running, and perform this logic when we see there is a mismatch.
|
|
1477
|
+
But for now, if there is any error we do a full page load (not a single-page navigation), which
|
|
1478
|
+
gives us a fresh version of the app to make sure things are in sync.
|
|
1479
|
+
|
|
1480
|
+
-}
|
|
1481
|
+
( model
|
|
1482
|
+
, urlWithoutRedirectResolution
|
|
1483
|
+
|> Url.toString
|
|
1484
|
+
|> BrowserLoadUrl
|
|
1485
|
+
)
|