explore a better transition api

This commit is contained in:
Ryan Haskell-Glatz 2019-10-20 22:15:10 -05:00
parent 329efa7944
commit 8efa0b5ee4
6 changed files with 290 additions and 45 deletions

View File

@ -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 ()) )
]

View File

@ -8,7 +8,7 @@ import Html.Attributes exposing (href, style)
layout : Application.Layout msg
layout =
{ view = view
, transition = Application.fade 1000
, transition = Application.none
}

View 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) ]

View 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

View File

@ -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
}
]
}

View File

@ -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
}
}