add route/component transition animation and an option to disable it

This commit is contained in:
Axolotle 2020-10-08 23:58:09 +02:00
parent 2b199b712f
commit 64c6aefe63
6 changed files with 119 additions and 16 deletions

View file

@ -1,20 +1,25 @@
<template> <template>
<div id="app" class="container"> <div id="app" class="container">
<!-- HEADER -->
<header> <header>
<b-navbar> <b-navbar>
<b-navbar-brand :to="{ name: 'home' }" exact exact-active-class="active"> <b-navbar-brand :to="{ name: 'home' }" exact exact-active-class="active">
<img alt="Yunohost logo" src="./assets/logo.png"> <img alt="Yunohost logo" src="./assets/logo.png">
</b-navbar-brand> </b-navbar-brand>
<b-navbar-nav class="ml-auto"> <b-navbar-nav class="ml-auto">
<li class="nav-item"> <li class="nav-item">
<b-button href="/yunohost/sso" <b-button
href="/yunohost/sso"
variant="primary" size="sm" block variant="primary" size="sm" block
> >
{{ $t('user_interface_link') }} <icon iname="user" /> {{ $t('user_interface_link') }} <icon iname="user" />
</b-button> </b-button>
</li> </li>
<li class="nav-item" v-show="connected"> <li class="nav-item" v-show="connected">
<b-button @click.prevent="logout" to="/logout" <b-button
@click.prevent="logout"
variant="outline-dark" block size="sm" variant="outline-dark" block size="sm"
> >
{{ $t('logout') }} <icon iname="sign-out" /> {{ $t('logout') }} <icon iname="sign-out" />
@ -24,12 +29,17 @@
</b-navbar> </b-navbar>
</header> </header>
<breadcrumb v-if="$route.meta.breadcrumb" /> <!-- MAIN -->
<breadcrumb />
<main id="main"> <main id="main">
<router-view /> <transition v-if="transitions" :name="transitionName">
<router-view class="animated" />
</transition>
<router-view v-else class="static" />
</main> </main>
<!-- FOOTER -->
<footer> <footer>
<nav> <nav>
<b-nav class="justify-content-center"> <b-nav class="justify-content-center">
@ -70,8 +80,25 @@ import { mapGetters } from 'vuex'
export default { export default {
name: 'App', name: 'App',
data () {
return {
transitionName: null
}
},
computed: { computed: {
...mapGetters(['connected', 'yunohost']) ...mapGetters(['connected', 'yunohost', 'transitions'])
},
watch: {
// Set the css class to animate the components transition
'$route' (to, from) {
if (!this.transitions) return
// Use the breadcrumb array length as a direction indicator
const toDepth = to.meta.breadcrumb.length
const fromDepth = from.meta.breadcrumb.length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}, },
methods: { methods: {
@ -139,4 +166,30 @@ export default {
} }
} }
} }
main {
position: relative;
// Routes transition
.animated {
transition: all .15s ease-in-out;
}
.slide-left-enter, .slide-right-leave-active {
position: absolute;
width: 100%;
top: 0;
transform: translate(100vw, 0);
}
.slide-left-leave-active, .slide-right-enter {
position: absolute;
width: 100%;
top: 0;
transform: translate(-100vw, 0);
}
// hack to hide last transition provoqued by the <router-view> element change
// while disabling the transitions in ToolWebAdmin
.static ~ .animated {
display: none;
}
}
</style> </style>

View file

@ -1,14 +1,14 @@
<template> <template>
<b-breadcrumb> <b-breadcrumb v-if="breadcrumb && breadcrumb.length">
<b-breadcrumb-item to="/"> <b-breadcrumb-item to="/">
<span class="sr-only">{{ $t('home') }}</span> <span class="sr-only">{{ $t('home') }}</span>
<icon iname="home" /> <icon iname="home" />
</b-breadcrumb-item> </b-breadcrumb-item>
<b-breadcrumb-item <b-breadcrumb-item
v-for="(route, index) in breadcrumb" v-for="(route, index) in breadcrumb" :key="index"
:key="index"
:to="{name: route.name}" :to="{name: route.name}"
:active="index == lastIndex ? true : false" :active="index === lastIndex"
> >
{{ route.text }} {{ route.text }}
</b-breadcrumb-item> </b-breadcrumb-item>
@ -18,11 +18,14 @@
<script> <script>
export default { export default {
name: 'Breadcrumb', name: 'Breadcrumb',
computed: { computed: {
params: function () { params: function () {
return this.$route.params return this.$route.params
}, },
breadcrumb: function () { breadcrumb: function () {
if (!this.$route.meta.breadcrumb) return null
return this.$route.meta.breadcrumb.map(({ name, trad, param }) => { return this.$route.meta.breadcrumb.map(({ name, trad, param }) => {
let text = '' let text = ''
// if a traduction key string has been given and we also need to pass // if a traduction key string has been given and we also need to pass
@ -37,6 +40,7 @@ export default {
return { name, text } return { name, text }
}) })
}, },
lastIndex: function () { lastIndex: function () {
return this.breadcrumb.length - 1 return this.breadcrumb.length - 1
} }

View file

@ -354,7 +354,8 @@
"cache": "Cache", "cache": "Cache",
"cache_description": "Consider disabling the cache if you plan on working with the CLI while also navigating in this web-admin.", "cache_description": "Consider disabling the cache if you plan on working with the CLI while also navigating in this web-admin.",
"experimental": "Experimental mode", "experimental": "Experimental mode",
"experimental_description": "Gives you access to experimental features. These are considered unstable and may break your system.<br> Enabled this only if you know what you are doing." "experimental_description": "Gives you access to experimental features. These are considered unstable and may break your system.<br> Enabled this only if you know what you are doing.",
"transitions": "Page transition animations"
}, },
"tools_webadmin_settings": "Web-admin settings", "tools_webadmin_settings": "Web-admin settings",
"udp": "UDP", "udp": "UDP",

View file

@ -7,8 +7,20 @@ import { DomainList, DomainAdd, DomainInfo, DomainDns, DomainCert } from '@/view
import { ServiceList, ServiceInfo } from '@/views/service' import { ServiceList, ServiceInfo } from '@/views/service'
const routes = [ const routes = [
{ name: 'home', path: '/', component: Home }, {
{ name: 'login', path: '/login', component: Login, meta: { noAuth: true } }, name: 'home',
path: '/',
component: Home,
// Leave the empty breadcrumb as it is used by the animated transition to know which way to go
meta: { breadcrumb: [] }
},
{
name: 'login',
path: '/login',
component: Login,
meta: { noAuth: true, breadcrumb: [] }
},
/* /*
POST INSTALL POST INSTALL
@ -17,6 +29,7 @@ const routes = [
name: 'post-install', name: 'post-install',
path: '/postinstall', path: '/postinstall',
component: () => import(/* webpackChunkName: "views/postinstall" */ '@/views/PostInstall'), component: () => import(/* webpackChunkName: "views/postinstall" */ '@/views/PostInstall'),
// Leave the breadcrumb
meta: { noAuth: true } meta: { noAuth: true }
}, },

View file

@ -12,6 +12,7 @@ export default {
locale: localStorage.getItem('locale'), locale: localStorage.getItem('locale'),
fallbackLocale: localStorage.getItem('fallbackLocale'), fallbackLocale: localStorage.getItem('fallbackLocale'),
cache: localStorage.getItem('cache') !== 'false', cache: localStorage.getItem('cache') !== 'false',
transitions: localStorage.getItem('transitions') !== 'false',
experimental: localStorage.getItem('experimental') === 'true', experimental: localStorage.getItem('experimental') === 'true',
supportedLocales: supportedLocales supportedLocales: supportedLocales
}, },
@ -32,6 +33,11 @@ export default {
state.cache = boolean state.cache = boolean
}, },
'SET_TRANSITIONS' (state, boolean) {
localStorage.setItem('transitions', boolean)
state.transitions = boolean
},
'SET_EXPERIMENTAL' (state, boolean) { 'SET_EXPERIMENTAL' (state, boolean) {
localStorage.setItem('experimental', boolean) localStorage.setItem('experimental', boolean)
state.experimental = boolean state.experimental = boolean
@ -61,6 +67,7 @@ export default {
locale: state => (state.locale), locale: state => (state.locale),
fallbackLocale: state => (state.fallbackLocale), fallbackLocale: state => (state.fallbackLocale),
cache: state => (state.cache), cache: state => (state.cache),
transitions: state => (state.transitions),
experimental: state => state.experimental, experimental: state => state.experimental,
availableLocales: state => { availableLocales: state => {

View file

@ -39,11 +39,22 @@
{{ $t('tools_webadmin.cache_description') }} {{ $t('tools_webadmin.cache_description') }}
</template> </template>
</b-form-group> </b-form-group>
<hr> <hr>
<!-- EXPERIMENTAL MODE --> <!-- TRANSITIONS -->
<b-form-group <b-form-group
label-cols="0" label-cols-lg="2"
:label="$t('tools_webadmin.transitions')" label-for="transitions" label-class="font-weight-bold"
>
<b-checkbox v-model="currentTransitions" id="transitions" switch>
{{ $t(currentTransitions ? 'enabled' : 'disabled') }}
</b-checkbox>
</b-form-group>
<hr>
<!-- EXPERIMENTAL MODE (dev environment only)-->
<b-form-group
v-if="isDev"
label-cols="0" label-cols-lg="2" label-class="font-weight-bold" label-cols="0" label-cols-lg="2" label-class="font-weight-bold"
label-for="experimental" label-for="experimental"
> >
@ -75,6 +86,7 @@ export default {
'locale', 'locale',
'fallbackLocale', 'fallbackLocale',
'cache', 'cache',
'transitions',
'experimental', 'experimental',
'availableLocales' 'availableLocales'
]), ]),
@ -100,6 +112,19 @@ export default {
} }
}, },
currentTransitions: {
get: function () { return this.transitions },
set: function (newValue) {
this.$store.commit('SET_TRANSITIONS', newValue)
}
},
// environment
isDev () {
return process.env.NODE_ENV === 'development'
},
// Only available in 'development' environment.
currentExperimental: { currentExperimental: {
get: function () { return this.experimental }, get: function () { return this.experimental },
set: function (newValue) { set: function (newValue) {