mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-09-17 15:28:05 +03:00
explore a better transition api
This commit is contained in:
parent
329efa7944
commit
8efa0b5ee4
@ -19,7 +19,8 @@ view =
|
||||
[ ( "Home", Route.Homepage () )
|
||||
, ( "Counter", Route.Counter () )
|
||||
, ( "Cats", Route.Random () )
|
||||
, ( "User", Route.Users_Slug "alice" )
|
||||
|
||||
-- , ( "User", Route.Users_Slug "alice" )
|
||||
, ( "Settings", Route.Settings (Settings.Account ()) )
|
||||
]
|
||||
|
||||
|
@ -8,7 +8,7 @@ import Html.Attributes exposing (href, style)
|
||||
layout : Application.Layout msg
|
||||
layout =
|
||||
{ view = view
|
||||
, transition = Application.fade 1000
|
||||
, transition = Application.none
|
||||
}
|
||||
|
||||
|
||||
|
122
example/transition-exploration/Main.elm
Normal file
122
example/transition-exploration/Main.elm
Normal file
@ -0,0 +1,122 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import Browser
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Internals.Utils as Utils
|
||||
import TransitionStuff
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = init
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ state : TransitionStuff.State Page
|
||||
, delay : Int
|
||||
}
|
||||
|
||||
|
||||
type Page
|
||||
= Homepage
|
||||
| About
|
||||
| Contact
|
||||
|
||||
|
||||
type Msg
|
||||
= NavigateTo Page
|
||||
|
||||
|
||||
init : flags -> ( Model, Cmd Msg )
|
||||
init _ =
|
||||
let
|
||||
state =
|
||||
{ visibility = TransitionStuff.Invisible, page = Homepage }
|
||||
|
||||
{ cmd, delay } =
|
||||
TransitionStuff.transition delaysForPage
|
||||
{ delay = 0
|
||||
, msg = NavigateTo
|
||||
, target = Homepage
|
||||
, state = state
|
||||
}
|
||||
in
|
||||
( { delay = delay, state = state }
|
||||
, cmd
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
NavigateTo target ->
|
||||
let
|
||||
state =
|
||||
TransitionStuff.nextState target model.state
|
||||
|
||||
{ cmd, delay } =
|
||||
TransitionStuff.transition delaysForPage
|
||||
{ delay = model.delay
|
||||
, msg = NavigateTo
|
||||
, target = target
|
||||
, state = state
|
||||
}
|
||||
in
|
||||
( { model | state = state, delay = delay }
|
||||
, cmd
|
||||
)
|
||||
|
||||
|
||||
delaysForPage : Page -> TransitionStuff.Animations
|
||||
delaysForPage page =
|
||||
case page of
|
||||
Homepage ->
|
||||
{ entering = 200
|
||||
, exiting = 200
|
||||
}
|
||||
|
||||
About ->
|
||||
{ entering = 200
|
||||
, exiting = 200
|
||||
}
|
||||
|
||||
Contact ->
|
||||
{ entering = 200
|
||||
, exiting = 200
|
||||
}
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div
|
||||
[ Attr.style "transition" ("opacity " ++ String.fromInt model.delay ++ "ms ease-in-out")
|
||||
, Attr.style "opacity"
|
||||
(if model.state.visibility == TransitionStuff.Visible then
|
||||
"1"
|
||||
|
||||
else
|
||||
"0"
|
||||
)
|
||||
]
|
||||
[ p [] (List.map viewButton [ Homepage, About, Contact ])
|
||||
, p [] [ text (Debug.toString model.state.page) ]
|
||||
]
|
||||
|
||||
|
||||
viewButton : Page -> Html Msg
|
||||
viewButton page =
|
||||
button
|
||||
[ Events.onClick (NavigateTo page) ]
|
||||
[ text (Debug.toString page) ]
|
108
example/transition-exploration/TransitionStuff.elm
Normal file
108
example/transition-exploration/TransitionStuff.elm
Normal file
@ -0,0 +1,108 @@
|
||||
module TransitionStuff exposing
|
||||
( Animations
|
||||
, State
|
||||
, Visibility(..)
|
||||
, nextState
|
||||
, outIn
|
||||
, transition
|
||||
)
|
||||
|
||||
import Internals.Utils as Utils
|
||||
|
||||
|
||||
type Visibility
|
||||
= Invisible
|
||||
| Visible
|
||||
|
||||
|
||||
type alias State page =
|
||||
{ page : page
|
||||
, visibility : Visibility
|
||||
}
|
||||
|
||||
|
||||
type alias Animations =
|
||||
{ entering : Int
|
||||
, exiting : Int
|
||||
}
|
||||
|
||||
|
||||
outIn :
|
||||
{ toAnimations : page -> Animations
|
||||
, current : State page
|
||||
, next : State page
|
||||
}
|
||||
-> Int
|
||||
outIn { toAnimations, current, next } =
|
||||
let
|
||||
animations =
|
||||
toAnimations current.page
|
||||
in
|
||||
case ( current.visibility, next.visibility ) of
|
||||
( Visible, Invisible ) ->
|
||||
0
|
||||
|
||||
( Invisible, Invisible ) ->
|
||||
animations.exiting
|
||||
|
||||
( Invisible, Visible ) ->
|
||||
animations.entering
|
||||
|
||||
( Visible, Visible ) ->
|
||||
0
|
||||
|
||||
|
||||
nextState : page -> State page -> State page
|
||||
nextState target state =
|
||||
case state.visibility of
|
||||
Invisible ->
|
||||
if state.page == target then
|
||||
{ visibility = Visible, page = target }
|
||||
|
||||
else
|
||||
{ visibility = Invisible, page = target }
|
||||
|
||||
Visible ->
|
||||
if state.page == target then
|
||||
state
|
||||
|
||||
else
|
||||
{ visibility = Invisible, page = state.page }
|
||||
|
||||
|
||||
isComplete : State page -> State page -> Bool
|
||||
isComplete =
|
||||
(==)
|
||||
|
||||
|
||||
transition :
|
||||
(page -> Animations)
|
||||
->
|
||||
{ delay : Int
|
||||
, target : page
|
||||
, state : State page
|
||||
, msg : page -> msg
|
||||
}
|
||||
-> { delay : Int, cmd : Cmd msg }
|
||||
transition toAnimations { delay, target, state, msg } =
|
||||
if isComplete state (nextState target state) then
|
||||
{ delay = delay
|
||||
, cmd = Cmd.none
|
||||
}
|
||||
|
||||
else
|
||||
let
|
||||
nextDelay =
|
||||
outIn
|
||||
{ toAnimations = toAnimations
|
||||
, current = state
|
||||
, next = nextState target state
|
||||
}
|
||||
in
|
||||
{ delay = nextDelay
|
||||
, cmd = Utils.delay nextDelay (msg target)
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- program stuff
|
@ -98,15 +98,11 @@ create config =
|
||||
}
|
||||
, subscriptions =
|
||||
subscriptions
|
||||
{ subscriptions =
|
||||
\model ->
|
||||
config.pages.bundle model Page.Initial |> .subscriptions
|
||||
{ subscriptions = config.pages.bundle >> .subscriptions
|
||||
}
|
||||
, view =
|
||||
view
|
||||
{ view =
|
||||
\model ->
|
||||
config.pages.bundle model Page.Initial |> .view
|
||||
{ view = \status model -> (config.pages.bundle model).view status
|
||||
, layout = config.layout
|
||||
, transition = Transition.strategy config.layout.transition
|
||||
}
|
||||
@ -120,7 +116,10 @@ create config =
|
||||
|
||||
|
||||
type alias Model flags model =
|
||||
{ url : Url
|
||||
{ urls :
|
||||
{ previous : Maybe Url
|
||||
, current : Url
|
||||
}
|
||||
, flags : flags
|
||||
, key : Nav.Key
|
||||
, page : Transitionable model
|
||||
@ -143,7 +142,7 @@ init config flags url key =
|
||||
|> (\route -> config.init route { parentSpeed = config.speed })
|
||||
|> (\page ->
|
||||
( { flags = flags
|
||||
, url = url
|
||||
, urls = { previous = Nothing, current = url }
|
||||
, key = key
|
||||
, page = Transitionable.Ready page.model
|
||||
, speed = page.speed
|
||||
@ -206,18 +205,18 @@ update config msg model =
|
||||
( model, Cmd.none )
|
||||
|
||||
Link (Browser.Internal url) ->
|
||||
if url == model.url && url.fragment == Nothing then
|
||||
if url == model.urls.current && url.fragment == Nothing then
|
||||
( model, Cmd.none )
|
||||
|
||||
else if url.path == model.url.path then
|
||||
else if url.path == model.urls.current.path then
|
||||
( model, Nav.load (Url.toString url) )
|
||||
|
||||
else
|
||||
( if navigatingToNewLayout { old = model.url, new = url } then
|
||||
{ model | page = Transitionable.begin model.page }
|
||||
( if navigatingWithinLayout { old = model.urls.current, new = url } then
|
||||
{ model | page = Transitionable.complete model.page }
|
||||
|
||||
else
|
||||
{ model | page = Transitionable.complete model.page }
|
||||
{ model | page = Transitionable.begin model.page }
|
||||
, Utils.delay model.speed (TransitionTo url)
|
||||
)
|
||||
|
||||
@ -242,7 +241,10 @@ update config msg model =
|
||||
|> (\route -> config.init route { parentSpeed = config.speed })
|
||||
|> (\page ->
|
||||
( { model
|
||||
| url = url
|
||||
| urls =
|
||||
{ previous = Just model.urls.current
|
||||
, current = url
|
||||
}
|
||||
, page = Transitionable.Complete page.model
|
||||
, speed = page.speed
|
||||
}
|
||||
@ -257,8 +259,8 @@ update config msg model =
|
||||
(config.update pageMsg (Transitionable.unwrap model.page))
|
||||
|
||||
|
||||
navigatingToNewLayout : { old : Url, new : Url } -> Bool
|
||||
navigatingToNewLayout urls =
|
||||
navigatingWithinLayout : { old : Url, new : Url } -> Bool
|
||||
navigatingWithinLayout urls =
|
||||
let
|
||||
firstSegment { path } =
|
||||
String.split "/" path |> List.drop 1 |> List.head
|
||||
@ -269,7 +271,7 @@ navigatingToNewLayout urls =
|
||||
new =
|
||||
firstSegment urls.new
|
||||
in
|
||||
old /= new || old == Nothing
|
||||
old == new && old /= Nothing
|
||||
|
||||
|
||||
|
||||
@ -289,13 +291,26 @@ subscriptions config model =
|
||||
|
||||
|
||||
view :
|
||||
{ view : model -> Html msg
|
||||
{ view : Page.TransitionStatus -> model -> Html msg
|
||||
, transition : Transition.Strategy (Html msg)
|
||||
, layout : Layout msg
|
||||
}
|
||||
-> Model flags model
|
||||
-> Browser.Document (Msg msg)
|
||||
view config model =
|
||||
let
|
||||
isNavigatingWithinLayout =
|
||||
model.urls.previous
|
||||
|> Maybe.map (\previous -> navigatingWithinLayout { old = previous, new = model.urls.current })
|
||||
|> Maybe.withDefault False
|
||||
|
||||
status =
|
||||
if isNavigatingWithinLayout then
|
||||
Page.Leaving
|
||||
|
||||
else
|
||||
Page.Complete
|
||||
in
|
||||
{ title = "elm-app demo"
|
||||
, body =
|
||||
[ Html.map Page <|
|
||||
@ -303,19 +318,19 @@ view config model =
|
||||
Transitionable.Ready page ->
|
||||
config.transition.beforeLoad
|
||||
{ layout = config.layout.view
|
||||
, page = config.view page
|
||||
, page = config.view status page
|
||||
}
|
||||
|
||||
Transitionable.Transitioning page ->
|
||||
config.transition.leavingPage
|
||||
{ layout = config.layout.view
|
||||
, page = config.view page
|
||||
, page = config.view status page
|
||||
}
|
||||
|
||||
Transitionable.Complete page ->
|
||||
config.transition.enteringPage
|
||||
{ layout = config.layout.view
|
||||
, page = config.view page
|
||||
, page = config.view status page
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ type alias Recipe params pageModel pageMsg model msg =
|
||||
type TransitionStatus
|
||||
= Initial
|
||||
| Leaving
|
||||
| Entering
|
||||
| Complete
|
||||
|
||||
|
||||
type alias Init model msg =
|
||||
@ -60,11 +60,9 @@ type alias Init model msg =
|
||||
|
||||
|
||||
type alias Bundle msg =
|
||||
TransitionStatus
|
||||
->
|
||||
{ view : Html msg
|
||||
, subscriptions : Sub msg
|
||||
}
|
||||
{ view : TransitionStatus -> Html msg
|
||||
, subscriptions : Sub msg
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -88,8 +86,8 @@ static page { toModel, toMsg } =
|
||||
}
|
||||
, update = \_ model -> ( toModel model, Cmd.none )
|
||||
, bundle =
|
||||
\_ _ ->
|
||||
{ view = Html.map toMsg page.view
|
||||
\_ ->
|
||||
{ view = \_ -> Html.map toMsg page.view
|
||||
, subscriptions = Sub.none
|
||||
}
|
||||
}
|
||||
@ -122,8 +120,8 @@ sandbox page { toModel, toMsg } =
|
||||
, Cmd.none
|
||||
)
|
||||
, bundle =
|
||||
\model _ ->
|
||||
{ view = page.view model |> Html.map toMsg
|
||||
\model ->
|
||||
{ view = \_ -> page.view model |> Html.map toMsg
|
||||
, subscriptions = Sub.none
|
||||
}
|
||||
}
|
||||
@ -159,8 +157,8 @@ element page { toModel, toMsg } =
|
||||
page.update msg model
|
||||
|> Tuple.mapBoth toModel (Cmd.map toMsg)
|
||||
, bundle =
|
||||
\model _ ->
|
||||
{ view = page.view model |> Html.map toMsg
|
||||
\model ->
|
||||
{ view = \_ -> page.view model |> Html.map toMsg
|
||||
, subscriptions = Sub.none
|
||||
}
|
||||
}
|
||||
@ -201,20 +199,21 @@ glue options { toModel, toMsg } =
|
||||
options.pages.update msg model
|
||||
|> Tuple.mapBoth toModel (Cmd.map toMsg)
|
||||
, bundle =
|
||||
\model status ->
|
||||
\model ->
|
||||
let
|
||||
page =
|
||||
options.pages.bundle model status
|
||||
options.pages.bundle model
|
||||
in
|
||||
{ view =
|
||||
options.layout.view
|
||||
{ page =
|
||||
div []
|
||||
[ text (Debug.toString status)
|
||||
, page.view
|
||||
]
|
||||
}
|
||||
|> Html.map toMsg
|
||||
\status ->
|
||||
options.layout.view
|
||||
{ page =
|
||||
div []
|
||||
[ text (Debug.toString status)
|
||||
, page.view status
|
||||
]
|
||||
}
|
||||
|> Html.map toMsg
|
||||
, subscriptions = Sub.none
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user