diff --git a/examples/08-layouts/public/index.html b/examples/08-layouts/public/index.html index 03e7f27..7a64c8e 100644 --- a/examples/08-layouts/public/index.html +++ b/examples/08-layouts/public/index.html @@ -11,7 +11,7 @@ a { text-decoration: underline;} a, button { cursor: pointer;} .border-right { border-right: solid 1px #ddd;} - .tab { border-bottom: solid 2px white; padding: 1rem; text-decoration: none; } + .tab { border-bottom: solid 2px white; padding: 1rem; text-decoration: none; transition: color 200ms, border-color 200ms; } .tab--active { border-bottom: solid 2px dodgerblue; padding: 0.75rem 1rem; color: dodgerblue; } .active { color: dodgerblue; } diff --git a/examples/08-layouts/src/Gen/Layout.elm b/examples/08-layouts/src/Gen/Layout.elm index cd1798c..950d312 100644 --- a/examples/08-layouts/src/Gen/Layout.elm +++ b/examples/08-layouts/src/Gen/Layout.elm @@ -16,6 +16,7 @@ module Gen.Layout exposing import Effect exposing (Effect) import Request exposing (Request) import Shared +import Transition import View exposing (View) @@ -98,7 +99,7 @@ toBundle : { init : Shared.Model -> Request -> ( genModel, Effect genMsg ) , update : msg -> model -> Shared.Model -> Request -> ( genModel, Effect genMsg ) , subscriptions : model -> Shared.Model -> Request -> Sub genMsg - , view : model -> { viewPage : View mainMsg, toMainMsg : genMsg -> mainMsg } -> Shared.Model -> Request -> View mainMsg + , view : List Transition.Attribute -> model -> { viewPage : View mainMsg, toMainMsg : genMsg -> mainMsg } -> Shared.Model -> Request -> View mainMsg } toBundle toModel toMsg toLayout = let @@ -120,9 +121,9 @@ toBundle toModel toMsg toLayout = (toRecord shared req).subscriptions model |> Sub.map toMsg , view = - \model options shared req -> + \attrs model options shared req -> (toRecord shared req).view - { viewPage = options.viewPage + { viewPage = Transition.apply attrs options.viewPage , toMainMsg = toMsg >> options.toMainMsg } model @@ -140,7 +141,7 @@ toBundle2 : , update1 : model2 -> msg1 -> model1 -> Shared.Model -> Request -> ( genModel, Effect genMsg ) , update2 : model1 -> msg2 -> model2 -> Shared.Model -> Request -> ( genModel, Effect genMsg ) , subscriptions : model2 -> Shared.Model -> Request -> Sub genMsg - , view : model2 -> { viewPage : View mainMsg, toMainMsg : genMsg -> mainMsg } -> Shared.Model -> Request -> View mainMsg + , view : List Transition.Attribute -> model2 -> { viewPage : View mainMsg, toMainMsg : genMsg -> mainMsg } -> Shared.Model -> Request -> View mainMsg } toBundle2 toModel toMsg1 toMsg2 toLayout1 toLayout2 = let @@ -182,9 +183,9 @@ toBundle2 toModel toMsg1 toMsg2 toLayout1 toLayout2 = (toRecord2 shared req).subscriptions model |> Sub.map toMsg2 , view = - \model options shared req -> + \attrs model options shared req -> (toRecord2 shared req).view - { viewPage = options.viewPage + { viewPage = Transition.apply attrs options.viewPage , toMainMsg = toMsg2 >> options.toMainMsg } model diff --git a/examples/08-layouts/src/Gen/Layouts.elm b/examples/08-layouts/src/Gen/Layouts.elm index 62d55e6..37c443b 100644 --- a/examples/08-layouts/src/Gen/Layouts.elm +++ b/examples/08-layouts/src/Gen/Layouts.elm @@ -22,6 +22,7 @@ import Layouts.Sidebar import Layouts.Sidebar.Header import Request exposing (Request) import Shared +import Transition import View exposing (View) @@ -116,15 +117,26 @@ subscriptions model_ shared req = view : - Model + { before : Maybe Layout, after : Maybe Layout } + -> { current : List Transition.Attribute } + -> Model -> { viewPage : View mainMsg, toMainMsg : Msg -> mainMsg } -> Shared.Model -> Request -> View mainMsg -view model_ options shared req = +view { before, after } { current } model_ options shared req = case model_ of Sidebar_Model model -> - layouts.sidebar.view model options shared req + layouts.sidebar.view current model options shared req Sidebar__Header_Model model1 model2 -> - layouts.sidebar.view model1 { options | viewPage = layouts.sidebar__header.view model2 options shared req } shared req + let + ( transition1, transition2 ) = + case ( before, after ) of + ( Just Sidebar__Header, Just Sidebar__Header ) -> + ( Transition.visible, current ) + + _ -> + ( current, Transition.visible ) + in + layouts.sidebar.view transition1 model1 { options | viewPage = layouts.sidebar__header.view transition2 model2 options shared req } shared req diff --git a/examples/08-layouts/src/Layouts/Sidebar/Header.elm b/examples/08-layouts/src/Layouts/Sidebar/Header.elm index 7197dde..f57878e 100644 --- a/examples/08-layouts/src/Layouts/Sidebar/Header.elm +++ b/examples/08-layouts/src/Layouts/Sidebar/Header.elm @@ -31,17 +31,17 @@ view route { viewPage } = { title = viewPage.title , body = [ Html.div [ Attr.class "col align-top gap-lg fill-y" ] - [ viewHeader viewPage.title route + [ viewHeader route , Html.div [ Attr.class "page" ] viewPage.body ] ] } -viewHeader : String -> Route -> Html msg -viewHeader title route = +viewHeader : Route -> Html msg +viewHeader route = Html.header [ Attr.class "col gap-md fill-y pad-right-lg" ] - [ Html.span [ Attr.class "h1" ] [ Html.text title ] + [ Html.span [ Attr.class "h1" ] [ Html.text "Settings" ] , Html.div [ Attr.class "row gap-md" ] [ viewTab route { label = "General", route = Route.Settings__General } , viewTab route { label = "Profile", route = Route.Settings__Profile } diff --git a/examples/08-layouts/src/Main.elm b/examples/08-layouts/src/Main.elm index 36a4bbf..a4dc623 100644 --- a/examples/08-layouts/src/Main.elm +++ b/examples/08-layouts/src/Main.elm @@ -7,8 +7,11 @@ import Gen.Layouts exposing (Layout) import Gen.Model import Gen.Pages_ as Pages import Gen.Route as Route +import Process import Request exposing (Request) import Shared +import Task +import Transition import Url exposing (Url) import View @@ -35,9 +38,16 @@ type alias Model = , shared : Shared.Model , layout : Maybe { kind : Layout, model : Gen.Layouts.Model } , page : Pages.Model + , transition : Transition } +type Transition + = InvisibleApp { before : Maybe Layout, after : Maybe Layout } + | FadingOutPage { before : Maybe Layout, after : Maybe Layout } + | VisiblePage { before : Maybe Layout, after : Maybe Layout } + + init : Shared.Flags -> Url -> Key -> ( Model, Cmd Msg ) init flags url key = let @@ -53,16 +63,37 @@ init flags url key = maybeLayout = Pages.layout (Route.fromUrl url) |> Maybe.map (initializeLayout Nothing { shared = shared, request = req }) + + transitionEffect = + if Transition.duration > 0 then + sendDelayedMsg Transition.duration FadeInApp + + else + Cmd.none in - ( Model url key shared (Maybe.map toKindAndModel maybeLayout) page + ( { url = url + , key = key + , shared = shared + , layout = Maybe.map toKindAndModel maybeLayout + , page = page + , transition = InvisibleApp { before = Nothing, after = Maybe.map .kind maybeLayout } + } , Cmd.batch [ Cmd.map Shared sharedCmd , Effect.toCmd ( Shared, Page ) pageEffect , Effect.toCmd ( Shared, Layout ) (toLayoutEffect maybeLayout) + , transitionEffect ] ) +sendDelayedMsg : Int -> Msg -> Cmd Msg +sendDelayedMsg ms msg = + Process.sleep (toFloat ms) + |> Task.map (\_ -> msg) + |> Task.perform identity + + initializeLayout : Maybe Gen.Layouts.Model -> { shared : Shared.Model, request : Request } -> Layout -> { kind : Layout, model : Gen.Layouts.Model, effect : Effect Gen.Layouts.Msg } initializeLayout maybeModel { shared, request } layoutKind = let @@ -97,6 +128,8 @@ type Msg | Shared Shared.Msg | Layout Gen.Layouts.Msg | Page Pages.Msg + | FadeInApp + | FadeInPage Url update : Msg -> Model -> ( Model, Cmd Msg ) @@ -114,49 +147,29 @@ update msg model = ChangedUrl url -> if url.path /= model.url.path then - let - route = - Route.fromUrl url - - ( page, effect ) = - Pages.init route model.shared url model.key - - currentLayout = - model.layout - - newLayoutKind = - Pages.layout route - in - if Maybe.map .kind currentLayout == newLayoutKind then - ( { model | url = url, page = page } - , Effect.toCmd ( Shared, Page ) effect + if Transition.duration > 0 then + ( { model + | transition = + FadingOutPage + { before = Maybe.map .kind model.layout + , after = Pages.layout (Route.fromUrl url) + } + } + , sendDelayedMsg Transition.duration (FadeInPage url) ) else - let - maybeLayout = - newLayoutKind - |> Maybe.map - (initializeLayout (Maybe.map .model currentLayout) - { shared = model.shared - , request = Request.create () url model.key - } - ) - in - ( { model - | url = url - , page = page - , layout = Maybe.map toKindAndModel maybeLayout - } - , Cmd.batch - [ Effect.toCmd ( Shared, Page ) effect - , Effect.toCmd ( Shared, Layout ) (toLayoutEffect maybeLayout) - ] - ) + updateModelFromUrl url model else ( { model | url = url }, Cmd.none ) + FadeInApp -> + ( { model | transition = VisiblePage { before = Nothing, after = Maybe.map .kind model.layout } }, Cmd.none ) + + FadeInPage url -> + updateModelFromUrl url model + Shared sharedMsg -> let ( shared, sharedCmd ) = @@ -205,6 +218,54 @@ update msg model = ( model, Cmd.none ) +updateModelFromUrl : Url -> Model -> ( Model, Cmd Msg ) +updateModelFromUrl url model = + let + route = + Route.fromUrl url + + ( page, effect ) = + Pages.init route model.shared url model.key + + currentLayout = + model.layout + + newLayoutKind = + Pages.layout route + in + if Maybe.map .kind currentLayout == newLayoutKind then + ( { model + | url = url + , page = page + , transition = VisiblePage { before = Maybe.map .kind currentLayout, after = newLayoutKind } + } + , Effect.toCmd ( Shared, Page ) effect + ) + + else + let + maybeLayout = + newLayoutKind + |> Maybe.map + (initializeLayout (Maybe.map .model currentLayout) + { shared = model.shared + , request = Request.create () url model.key + } + ) + in + ( { model + | url = url + , page = page + , layout = Maybe.map toKindAndModel maybeLayout + , transition = VisiblePage { before = Maybe.map .kind currentLayout, after = newLayoutKind } + } + , Cmd.batch + [ Effect.toCmd ( Shared, Page ) effect + , Effect.toCmd ( Shared, Layout ) (toLayoutEffect maybeLayout) + ] + ) + + -- VIEW @@ -216,10 +277,24 @@ view model = Pages.view model.page model.shared model.url model.key |> View.map Page + ( attrs, layoutKinds ) = + case model.transition of + InvisibleApp kinds -> + ( Transition.invisible, kinds ) + + FadingOutPage kinds -> + ( Transition.invisible, kinds ) + + VisiblePage kinds -> + ( Transition.visible, kinds ) + viewLayout = case model.layout of Just layout -> - Gen.Layouts.view layout.model + Gen.Layouts.view + layoutKinds + { current = attrs } + layout.model { viewPage = viewPage , toMainMsg = Layout } @@ -227,9 +302,14 @@ view model = (Request.create () model.url model.key) Nothing -> - viewPage + Transition.apply attrs viewPage in - View.toBrowserDocument viewLayout + case ( layoutKinds.before, layoutKinds.after ) of + ( Just _, Just _ ) -> + View.toBrowserDocument (Transition.apply Transition.visible viewLayout) + + _ -> + View.toBrowserDocument (Transition.apply attrs viewLayout) diff --git a/examples/08-layouts/src/Transition.elm b/examples/08-layouts/src/Transition.elm index d09e29e..5af4dea 100644 --- a/examples/08-layouts/src/Transition.elm +++ b/examples/08-layouts/src/Transition.elm @@ -1,24 +1,39 @@ -module Transition exposing (Transition, layout, page) +module Transition exposing + ( Attribute + , apply + , duration + , invisible + , visible + ) + +import Html +import Html.Attributes as Attr +import View exposing (View) -type alias Transition attr = - { duration : Int - , invisible : List attr - , visible : List attr - } - - -layout : Transition attr -layout = - { duration = 0 - , invisible = [] - , visible = [] - } - - -page : Transition attr -page = - { duration = 0 - , invisible = [] - , visible = [] +type alias Attribute = + Html.Attribute Never + + +duration : number +duration = + 500 + + +invisible : List Attribute +invisible = + [ Attr.class "fill-y", Attr.style "opacity" "0", Attr.style "transition" "opacity 500ms ease-in-out" ] + + +visible : List Attribute +visible = + [ Attr.class "fill-y", Attr.style "opacity" "1", Attr.style "transition" "opacity 500ms ease-in-out" ] + + +apply : List Attribute -> View msg -> View msg +apply attrs view = + { title = view.title + , body = + [ Html.div (List.map (Attr.map never) attrs) view.body + ] }