mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-09-19 08:27:45 +03:00
explore a better transition api
This commit is contained in:
parent
329efa7944
commit
8efa0b5ee4
@ -19,7 +19,8 @@ view =
|
|||||||
[ ( "Home", Route.Homepage () )
|
[ ( "Home", Route.Homepage () )
|
||||||
, ( "Counter", Route.Counter () )
|
, ( "Counter", Route.Counter () )
|
||||||
, ( "Cats", Route.Random () )
|
, ( "Cats", Route.Random () )
|
||||||
, ( "User", Route.Users_Slug "alice" )
|
|
||||||
|
-- , ( "User", Route.Users_Slug "alice" )
|
||||||
, ( "Settings", Route.Settings (Settings.Account ()) )
|
, ( "Settings", Route.Settings (Settings.Account ()) )
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import Html.Attributes exposing (href, style)
|
|||||||
layout : Application.Layout msg
|
layout : Application.Layout msg
|
||||||
layout =
|
layout =
|
||||||
{ view = view
|
{ 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
|
subscriptions
|
||||||
{ subscriptions =
|
{ subscriptions = config.pages.bundle >> .subscriptions
|
||||||
\model ->
|
|
||||||
config.pages.bundle model Page.Initial |> .subscriptions
|
|
||||||
}
|
}
|
||||||
, view =
|
, view =
|
||||||
view
|
view
|
||||||
{ view =
|
{ view = \status model -> (config.pages.bundle model).view status
|
||||||
\model ->
|
|
||||||
config.pages.bundle model Page.Initial |> .view
|
|
||||||
, layout = config.layout
|
, layout = config.layout
|
||||||
, transition = Transition.strategy config.layout.transition
|
, transition = Transition.strategy config.layout.transition
|
||||||
}
|
}
|
||||||
@ -120,7 +116,10 @@ create config =
|
|||||||
|
|
||||||
|
|
||||||
type alias Model flags model =
|
type alias Model flags model =
|
||||||
{ url : Url
|
{ urls :
|
||||||
|
{ previous : Maybe Url
|
||||||
|
, current : Url
|
||||||
|
}
|
||||||
, flags : flags
|
, flags : flags
|
||||||
, key : Nav.Key
|
, key : Nav.Key
|
||||||
, page : Transitionable model
|
, page : Transitionable model
|
||||||
@ -143,7 +142,7 @@ init config flags url key =
|
|||||||
|> (\route -> config.init route { parentSpeed = config.speed })
|
|> (\route -> config.init route { parentSpeed = config.speed })
|
||||||
|> (\page ->
|
|> (\page ->
|
||||||
( { flags = flags
|
( { flags = flags
|
||||||
, url = url
|
, urls = { previous = Nothing, current = url }
|
||||||
, key = key
|
, key = key
|
||||||
, page = Transitionable.Ready page.model
|
, page = Transitionable.Ready page.model
|
||||||
, speed = page.speed
|
, speed = page.speed
|
||||||
@ -206,18 +205,18 @@ update config msg model =
|
|||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
Link (Browser.Internal url) ->
|
Link (Browser.Internal url) ->
|
||||||
if url == model.url && url.fragment == Nothing then
|
if url == model.urls.current && url.fragment == Nothing then
|
||||||
( model, Cmd.none )
|
( 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) )
|
( model, Nav.load (Url.toString url) )
|
||||||
|
|
||||||
else
|
else
|
||||||
( if navigatingToNewLayout { old = model.url, new = url } then
|
( if navigatingWithinLayout { old = model.urls.current, new = url } then
|
||||||
{ model | page = Transitionable.begin model.page }
|
{ model | page = Transitionable.complete model.page }
|
||||||
|
|
||||||
else
|
else
|
||||||
{ model | page = Transitionable.complete model.page }
|
{ model | page = Transitionable.begin model.page }
|
||||||
, Utils.delay model.speed (TransitionTo url)
|
, Utils.delay model.speed (TransitionTo url)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -242,7 +241,10 @@ update config msg model =
|
|||||||
|> (\route -> config.init route { parentSpeed = config.speed })
|
|> (\route -> config.init route { parentSpeed = config.speed })
|
||||||
|> (\page ->
|
|> (\page ->
|
||||||
( { model
|
( { model
|
||||||
| url = url
|
| urls =
|
||||||
|
{ previous = Just model.urls.current
|
||||||
|
, current = url
|
||||||
|
}
|
||||||
, page = Transitionable.Complete page.model
|
, page = Transitionable.Complete page.model
|
||||||
, speed = page.speed
|
, speed = page.speed
|
||||||
}
|
}
|
||||||
@ -257,8 +259,8 @@ update config msg model =
|
|||||||
(config.update pageMsg (Transitionable.unwrap model.page))
|
(config.update pageMsg (Transitionable.unwrap model.page))
|
||||||
|
|
||||||
|
|
||||||
navigatingToNewLayout : { old : Url, new : Url } -> Bool
|
navigatingWithinLayout : { old : Url, new : Url } -> Bool
|
||||||
navigatingToNewLayout urls =
|
navigatingWithinLayout urls =
|
||||||
let
|
let
|
||||||
firstSegment { path } =
|
firstSegment { path } =
|
||||||
String.split "/" path |> List.drop 1 |> List.head
|
String.split "/" path |> List.drop 1 |> List.head
|
||||||
@ -269,7 +271,7 @@ navigatingToNewLayout urls =
|
|||||||
new =
|
new =
|
||||||
firstSegment urls.new
|
firstSegment urls.new
|
||||||
in
|
in
|
||||||
old /= new || old == Nothing
|
old == new && old /= Nothing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -289,13 +291,26 @@ subscriptions config model =
|
|||||||
|
|
||||||
|
|
||||||
view :
|
view :
|
||||||
{ view : model -> Html msg
|
{ view : Page.TransitionStatus -> model -> Html msg
|
||||||
, transition : Transition.Strategy (Html msg)
|
, transition : Transition.Strategy (Html msg)
|
||||||
, layout : Layout msg
|
, layout : Layout msg
|
||||||
}
|
}
|
||||||
-> Model flags model
|
-> Model flags model
|
||||||
-> Browser.Document (Msg msg)
|
-> Browser.Document (Msg msg)
|
||||||
view config model =
|
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"
|
{ title = "elm-app demo"
|
||||||
, body =
|
, body =
|
||||||
[ Html.map Page <|
|
[ Html.map Page <|
|
||||||
@ -303,19 +318,19 @@ view config model =
|
|||||||
Transitionable.Ready page ->
|
Transitionable.Ready page ->
|
||||||
config.transition.beforeLoad
|
config.transition.beforeLoad
|
||||||
{ layout = config.layout.view
|
{ layout = config.layout.view
|
||||||
, page = config.view page
|
, page = config.view status page
|
||||||
}
|
}
|
||||||
|
|
||||||
Transitionable.Transitioning page ->
|
Transitionable.Transitioning page ->
|
||||||
config.transition.leavingPage
|
config.transition.leavingPage
|
||||||
{ layout = config.layout.view
|
{ layout = config.layout.view
|
||||||
, page = config.view page
|
, page = config.view status page
|
||||||
}
|
}
|
||||||
|
|
||||||
Transitionable.Complete page ->
|
Transitionable.Complete page ->
|
||||||
config.transition.enteringPage
|
config.transition.enteringPage
|
||||||
{ layout = config.layout.view
|
{ 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
|
type TransitionStatus
|
||||||
= Initial
|
= Initial
|
||||||
| Leaving
|
| Leaving
|
||||||
| Entering
|
| Complete
|
||||||
|
|
||||||
|
|
||||||
type alias Init model msg =
|
type alias Init model msg =
|
||||||
@ -60,11 +60,9 @@ type alias Init model msg =
|
|||||||
|
|
||||||
|
|
||||||
type alias Bundle msg =
|
type alias Bundle msg =
|
||||||
TransitionStatus
|
{ view : TransitionStatus -> Html msg
|
||||||
->
|
, subscriptions : Sub msg
|
||||||
{ view : Html msg
|
}
|
||||||
, subscriptions : Sub msg
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -88,8 +86,8 @@ static page { toModel, toMsg } =
|
|||||||
}
|
}
|
||||||
, update = \_ model -> ( toModel model, Cmd.none )
|
, update = \_ model -> ( toModel model, Cmd.none )
|
||||||
, bundle =
|
, bundle =
|
||||||
\_ _ ->
|
\_ ->
|
||||||
{ view = Html.map toMsg page.view
|
{ view = \_ -> Html.map toMsg page.view
|
||||||
, subscriptions = Sub.none
|
, subscriptions = Sub.none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,8 +120,8 @@ sandbox page { toModel, toMsg } =
|
|||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
, bundle =
|
, bundle =
|
||||||
\model _ ->
|
\model ->
|
||||||
{ view = page.view model |> Html.map toMsg
|
{ view = \_ -> page.view model |> Html.map toMsg
|
||||||
, subscriptions = Sub.none
|
, subscriptions = Sub.none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,8 +157,8 @@ element page { toModel, toMsg } =
|
|||||||
page.update msg model
|
page.update msg model
|
||||||
|> Tuple.mapBoth toModel (Cmd.map toMsg)
|
|> Tuple.mapBoth toModel (Cmd.map toMsg)
|
||||||
, bundle =
|
, bundle =
|
||||||
\model _ ->
|
\model ->
|
||||||
{ view = page.view model |> Html.map toMsg
|
{ view = \_ -> page.view model |> Html.map toMsg
|
||||||
, subscriptions = Sub.none
|
, subscriptions = Sub.none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,20 +199,21 @@ glue options { toModel, toMsg } =
|
|||||||
options.pages.update msg model
|
options.pages.update msg model
|
||||||
|> Tuple.mapBoth toModel (Cmd.map toMsg)
|
|> Tuple.mapBoth toModel (Cmd.map toMsg)
|
||||||
, bundle =
|
, bundle =
|
||||||
\model status ->
|
\model ->
|
||||||
let
|
let
|
||||||
page =
|
page =
|
||||||
options.pages.bundle model status
|
options.pages.bundle model
|
||||||
in
|
in
|
||||||
{ view =
|
{ view =
|
||||||
options.layout.view
|
\status ->
|
||||||
{ page =
|
options.layout.view
|
||||||
div []
|
{ page =
|
||||||
[ text (Debug.toString status)
|
div []
|
||||||
, page.view
|
[ text (Debug.toString status)
|
||||||
]
|
, page.view status
|
||||||
}
|
]
|
||||||
|> Html.map toMsg
|
}
|
||||||
|
|> Html.map toMsg
|
||||||
, subscriptions = Sub.none
|
, subscriptions = Sub.none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user