mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Merge pull request #437 from YunoHost/enh-configpanels
Enh configpanels
This commit is contained in:
commit
895bab5e26
11 changed files with 288 additions and 166 deletions
|
@ -40,9 +40,9 @@
|
|||
<!-- The `key` on router-view make sure that if a link points to a page that
|
||||
use the same component as the previous one, it will be refreshed -->
|
||||
<transition v-if="transitions" :name="transitionName">
|
||||
<router-view class="animated" :key="$route.fullPath" />
|
||||
<router-view class="animated" :key="routerKey" />
|
||||
</transition>
|
||||
<router-view v-else class="static" :key="$route.fullPath" />
|
||||
<router-view v-else class="static" :key="routerKey" />
|
||||
</main>
|
||||
</view-lock-overlay>
|
||||
|
||||
|
@ -86,25 +86,15 @@ export default {
|
|||
ViewLockOverlay
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
transitionName: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['connected', 'yunohost', 'transitions', 'waiting'])
|
||||
},
|
||||
|
||||
watch: {
|
||||
// Set the css class to animate the components transition
|
||||
'$route' (to, from) {
|
||||
if (!this.transitions || from.name === null) 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'
|
||||
}
|
||||
...mapGetters([
|
||||
'connected',
|
||||
'yunohost',
|
||||
'routerKey',
|
||||
'transitions',
|
||||
'transitionName',
|
||||
'waiting'
|
||||
])
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
70
app/src/components/ConfigPanel.vue
Normal file
70
app/src/components/ConfigPanel.vue
Normal file
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<abstract-form
|
||||
v-bind="{ id: panel.id + '-form', validation, serverError: panel.serverError }"
|
||||
@submit.prevent.stop="$emit('submit', panel.id)"
|
||||
>
|
||||
<slot name="tab-top" />
|
||||
|
||||
<template v-if="panel.help" #disclaimer>
|
||||
<div class="alert alert-info" v-html="help" />
|
||||
</template>
|
||||
|
||||
<slot name="tab-before" />
|
||||
|
||||
<template v-for="section in panel.sections">
|
||||
<div v-if="isVisible(section.visible, section)" :key="section.id" class="mb-5">
|
||||
<b-card-title v-if="section.name" title-tag="h3">
|
||||
{{ section.name }} <small v-if="section.help">{{ section.help }}</small>
|
||||
</b-card-title>
|
||||
|
||||
<template v-for="(field, fname) in section.fields">
|
||||
<form-field
|
||||
v-if="isVisible(field.visible, field)" :key="fname"
|
||||
v-model="forms[panel.id][fname]" v-bind="field" :validation="validation[fname]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot name="tab-after" />
|
||||
</abstract-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { configPanelsFieldIsVisible } from '@/helpers/yunohostArguments'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ConfigPanel',
|
||||
|
||||
props: {
|
||||
tabId: { type: String, required: true },
|
||||
panels: { type: Array, default: undefined },
|
||||
forms: { type: Object, default: undefined },
|
||||
v: { type: Object, default: undefined }
|
||||
},
|
||||
|
||||
computed: {
|
||||
panel () {
|
||||
return this.panels.find(panel => panel.id === this.tabId)
|
||||
},
|
||||
|
||||
validation () {
|
||||
return this.v.forms[this.panel.id]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
isVisible (expression, field) {
|
||||
return configPanelsFieldIsVisible(expression, field, this.forms)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-title {
|
||||
margin-bottom: 1em;
|
||||
border-bottom: solid 1px #aaa;
|
||||
}
|
||||
</style>
|
|
@ -1,67 +1,50 @@
|
|||
<template>
|
||||
<b-card no-body>
|
||||
<b-tabs fill pills card>
|
||||
<slot name="before" />
|
||||
|
||||
<tab-form
|
||||
v-for="{ name, id, sections, help, serverError } in panels" :key="id"
|
||||
v-bind="{ name, id: id + '-form', validation: $v.forms[id], serverError }"
|
||||
@submit.prevent.stop="$emit('submit', id)"
|
||||
>
|
||||
<template v-if="help" #disclaimer>
|
||||
<div class="alert alert-info" v-html="help" />
|
||||
</template>
|
||||
|
||||
<slot :name="id + '-tab-before'" />
|
||||
|
||||
<template v-for="section in sections">
|
||||
<div v-if="isVisible(section.visible, section)" :key="section.id" class="mb-5">
|
||||
<b-card-title v-if="section.name" title-tag="h3">
|
||||
{{ section.name }} <small v-if="section.help">{{ section.help }}</small>
|
||||
</b-card-title>
|
||||
|
||||
<template v-for="(field, fname) in section.fields">
|
||||
<form-field
|
||||
v-if="isVisible(field.visible, field)" :key="fname"
|
||||
v-model="forms[id][fname]" v-bind="field" :validation="$v.forms[id][fname]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot :name="id + '-tab-after'" />
|
||||
</tab-form>
|
||||
|
||||
<slot name="default" />
|
||||
</b-tabs>
|
||||
</b-card>
|
||||
<routable-tabs
|
||||
:routes="routes_"
|
||||
v-bind="{ panels, forms, v: $v }"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
|
||||
import { configPanelsFieldIsVisible } from '@/helpers/yunohostArguments'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'ConfigPanels',
|
||||
|
||||
components: {
|
||||
RoutableTabs: () => import('@/components/RoutableTabs.vue')
|
||||
},
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
props: {
|
||||
panels: { type: Array, default: undefined },
|
||||
forms: { type: Object, default: undefined },
|
||||
validations: { type: Object, default: undefined }
|
||||
validations: { type: Object, default: undefined },
|
||||
errors: { type: Object, default: undefined }, // never used
|
||||
routes: { type: Array, default: null },
|
||||
noRedirect: { type: Boolean, default: false }
|
||||
},
|
||||
|
||||
computed: {
|
||||
routes_ () {
|
||||
if (this.routes) return this.routes
|
||||
return this.panels.map(panel => ({
|
||||
to: { params: { tabId: panel.id } },
|
||||
text: panel.name,
|
||||
icon: panel.icon || 'wrench'
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
validations () {
|
||||
const v = this.validations
|
||||
return v ? { forms: v } : null
|
||||
return { forms: this.validations }
|
||||
},
|
||||
|
||||
methods: {
|
||||
isVisible (expression, field) {
|
||||
return configPanelsFieldIsVisible(expression, field, this.forms)
|
||||
created () {
|
||||
if (!this.noRedirect && !this.$route.params.tabId) {
|
||||
this.$router.replace({ params: { tabId: this.panels[0].id } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
32
app/src/components/RoutableTabs.vue
Normal file
32
app/src/components/RoutableTabs.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<b-card no-body>
|
||||
<b-card-header header-tag="nav">
|
||||
<b-nav card-header fill pills>
|
||||
<b-nav-item
|
||||
v-for="route in routes" :key="route.text"
|
||||
:to="route.to" exact exact-active-class="active"
|
||||
>
|
||||
<icon v-if="route.icon" :iname="route.icon" />
|
||||
{{ route.text }}
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</b-card-header>
|
||||
|
||||
<!-- Bind extra props to the child view and forward child events to parent -->
|
||||
<router-view v-bind="$attrs" v-on="$listeners" />
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RoutableTabs',
|
||||
|
||||
// Thanks to `v-bind="$attrs"` and `inheritAttrs: false`, this component can forward
|
||||
// arbitrary attributes (props) directly to its children.
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
routes: { type: Array, required: true }
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,40 +1,37 @@
|
|||
<template>
|
||||
<b-tab no-body>
|
||||
<template #title>
|
||||
<icon :iname="icon" /> {{ name }}
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<b-card-body>
|
||||
<slot name="disclaimer" />
|
||||
|
||||
|
||||
<b-form
|
||||
:id="id" :inline="inline" :class="formClasses"
|
||||
@submit.prevent="onSubmit" novalidate
|
||||
>
|
||||
<slot name="default" />
|
||||
|
||||
<slot name="server-error">
|
||||
<slot name="server-error" v-bind="{ errorFeedback }">
|
||||
<b-alert
|
||||
v-if="errorFeedback"
|
||||
variant="danger" class="my-3" icon="ban"
|
||||
:show="errorFeedback !== ''" v-html="errorFeedback"
|
||||
v-html="errorFeedback"
|
||||
/>
|
||||
</slot>
|
||||
</b-form>
|
||||
</b-card-body>
|
||||
|
||||
<b-card-footer>
|
||||
<b-button type="submit" variant="success" :form="id">
|
||||
{{ submitText ? submitText : $t('save') }}
|
||||
</b-button>
|
||||
<b-card-footer v-if="!noFooter">
|
||||
<slot name="footer">
|
||||
<b-button type="submit" variant="success" :form="id">
|
||||
{{ submitText || $t('save') }}
|
||||
</b-button>
|
||||
</slot>
|
||||
</b-card-footer>
|
||||
</b-tab>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'TabForm',
|
||||
name: 'AbstractForm',
|
||||
|
||||
props: {
|
||||
id: { type: String, default: 'ynh-form' },
|
||||
|
@ -43,8 +40,7 @@ export default {
|
|||
serverError: { type: String, default: '' },
|
||||
inline: { type: Boolean, default: false },
|
||||
formClasses: { type: [Array, String, Object], default: null },
|
||||
name: { type: String, required: true },
|
||||
icon: { type: String, default: 'wrench' }
|
||||
noFooter: { type: Boolean, default: false }
|
||||
},
|
||||
|
||||
computed: {
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<b-breadcrumb v-if="routesList">
|
||||
<b-breadcrumb v-if="breadcrumb.length">
|
||||
<b-breadcrumb-item to="/">
|
||||
<span class="sr-only">{{ $t('home') }}</span>
|
||||
<icon iname="home" />
|
||||
</b-breadcrumb-item>
|
||||
|
||||
<b-breadcrumb-item
|
||||
v-for="{ name, text } in breadcrumb" :key="name"
|
||||
:to="{ name }" :active="name === $route.name"
|
||||
v-for="({ name, text }, i) in breadcrumb" :key="name"
|
||||
:to="{ name }" :active="i === breadcrumb.length - 1"
|
||||
>
|
||||
{{ text }}
|
||||
</b-breadcrumb-item>
|
||||
|
@ -15,41 +15,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Breadcrumb',
|
||||
|
||||
computed: {
|
||||
routesList () {
|
||||
const routesList = this.$route.meta.breadcrumb
|
||||
return routesList && routesList.length ? routesList : null
|
||||
},
|
||||
|
||||
breadcrumb () {
|
||||
if (!this.routesList) return
|
||||
// Get current params to pass it to potential previous routes
|
||||
const currentParams = this.$route.params
|
||||
return this.routesList.map(name => {
|
||||
const { trad, param } = this.getRouteArgs(name)
|
||||
let text = ''
|
||||
// if a traduction key string has been given and we also need to pass
|
||||
// the route param as a variable.
|
||||
if (trad && param) {
|
||||
text = this.$i18n.t(trad, { [param]: currentParams[param] })
|
||||
} else if (trad) {
|
||||
text = this.$i18n.t(trad)
|
||||
} else {
|
||||
text = currentParams[param]
|
||||
}
|
||||
return { name, text }
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getRouteArgs (routeName) {
|
||||
const route = this.$router.options.routes.find(route => route.name === routeName)
|
||||
return route ? route.meta.args : {}
|
||||
}
|
||||
...mapGetters(['breadcrumb'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-bind="$attr" :class="['custom-spinner', spinner]" />
|
||||
<div :class="['custom-spinner', spinner]" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -295,7 +295,7 @@ export function formatYunoHostConfigPanels (data) {
|
|||
}
|
||||
|
||||
for (const { id: panelId, name, help, sections } of data.panels) {
|
||||
const panel = { id: panelId, sections: [] }
|
||||
const panel = { id: panelId, sections: [], serverError: '' }
|
||||
result.forms[panelId] = {}
|
||||
result.validations[panelId] = {}
|
||||
result.errors[panelId] = {}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import i18n from '@/i18n'
|
||||
import routes from './routes'
|
||||
import store from '@/store'
|
||||
|
||||
|
@ -29,6 +28,10 @@ const router = new VueRouter({
|
|||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (store.getters.transitions && from.name !== null) {
|
||||
store.dispatch('UPDATE_TRANSITION_NAME', { to, from })
|
||||
}
|
||||
|
||||
if (store.getters.error) {
|
||||
store.dispatch('DISMISS_ERROR', true)
|
||||
}
|
||||
|
@ -41,29 +44,8 @@ router.beforeEach((to, from, next) => {
|
|||
})
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
// Display a simplified breadcrumb as the document title.
|
||||
const routeParams = to.params
|
||||
let breadcrumb = to.meta.breadcrumb
|
||||
if (breadcrumb.length === 0) {
|
||||
breadcrumb = [to.name]
|
||||
} else if (breadcrumb.length > 2) {
|
||||
breadcrumb = breadcrumb.slice(breadcrumb.length - 2)
|
||||
}
|
||||
|
||||
const title = breadcrumb.map(name => {
|
||||
const route = routes.find(route => route.name === name)
|
||||
const { trad, param } = route ? route.meta.args : {}
|
||||
// if a traduction key string has been given and we also need to pass
|
||||
// the route param as a variable.
|
||||
if (trad && param) {
|
||||
return i18n.t(trad, { [param]: routeParams[param] })
|
||||
} else if (trad) {
|
||||
return i18n.t(trad)
|
||||
}
|
||||
return routeParams[param]
|
||||
}).reverse().join(' / ')
|
||||
|
||||
document.title = `${title} | ${i18n.t('yunohost_admin')}`
|
||||
store.dispatch('UPDATE_ROUTER_KEY', { to, from })
|
||||
store.dispatch('UPDATE_BREADCRUMB', { to, from })
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
@ -17,10 +17,8 @@ const routes = [
|
|||
name: 'home',
|
||||
path: '/',
|
||||
component: Home,
|
||||
// Leave the empty breadcrumb as it is used by the animated transition to know which way to go
|
||||
meta: {
|
||||
args: { trad: 'home' },
|
||||
breadcrumb: []
|
||||
args: { trad: 'home' }
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -30,8 +28,7 @@ const routes = [
|
|||
component: Login,
|
||||
meta: {
|
||||
noAuth: true,
|
||||
args: { trad: 'login' },
|
||||
breadcrumb: []
|
||||
args: { trad: 'login' }
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -42,12 +39,10 @@ const routes = [
|
|||
name: 'post-install',
|
||||
path: '/postinstall',
|
||||
component: () => import(/* webpackChunkName: "views/post-install" */ '@/views/PostInstall'),
|
||||
// Leave the breadcrumb
|
||||
meta: {
|
||||
noAuth: true,
|
||||
args: { trad: 'postinstall.title' },
|
||||
breadcrumb: []
|
||||
}
|
||||
args: { trad: 'postinstall.title' }
|
||||
}
|
||||
},
|
||||
|
||||
/* ───────╮
|
||||
|
@ -77,7 +72,7 @@ const routes = [
|
|||
component: () => import(/* webpackChunkName: "views/user/import" */ '@/views/user/UserImport'),
|
||||
props: true,
|
||||
meta: {
|
||||
args: { param: 'name' },
|
||||
args: { trad: 'users_import' },
|
||||
breadcrumb: ['user-list', 'user-import']
|
||||
}
|
||||
},
|
||||
|
@ -156,14 +151,23 @@ const routes = [
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'domain-config',
|
||||
// no need for name here, only children are visited
|
||||
path: '/domains/:name/config',
|
||||
component: () => import(/* webpackChunkName: "views/domain/dns" */ '@/views/domain/DomainConfig'),
|
||||
component: () => import(/* webpackChunkName: "views/domain/config" */ '@/views/domain/DomainConfig'),
|
||||
props: true,
|
||||
meta: {
|
||||
args: { trad: 'config' },
|
||||
breadcrumb: ['domain-list', 'domain-info', 'domain-config']
|
||||
}
|
||||
children: [
|
||||
{
|
||||
name: 'domain-config',
|
||||
path: ':tabId?',
|
||||
component: () => import(/* webpackChunkName: "components/configPanel" */ '@/components/ConfigPanel'),
|
||||
props: true,
|
||||
meta: {
|
||||
routerParams: ['name'], // Override router key params to avoid view recreation at tab change.
|
||||
args: { trad: 'config' },
|
||||
breadcrumb: ['domain-list', 'domain-info', 'domain-config']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'domain-dns',
|
||||
|
@ -248,14 +252,23 @@ const routes = [
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'app-config-panel',
|
||||
// no need for name here, only children are visited
|
||||
path: '/apps/:id/config-panel',
|
||||
component: () => import(/* webpackChunkName: "views/apps/config" */ '@/views/app/AppConfigPanel'),
|
||||
props: true,
|
||||
meta: {
|
||||
args: { trad: 'app_config_panel' },
|
||||
breadcrumb: ['app-list', 'app-info', 'app-config-panel']
|
||||
}
|
||||
children: [
|
||||
{
|
||||
name: 'app-config-panel',
|
||||
path: ':tabId?',
|
||||
component: () => import(/* webpackChunkName: "components/configPanel" */ '@/components/ConfigPanel'),
|
||||
props: true,
|
||||
meta: {
|
||||
routerParams: ['id'],
|
||||
args: { trad: 'app_config_panel' },
|
||||
breadcrumb: ['app-list', 'app-info', 'app-config-panel']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/* ────────────────╮
|
||||
|
|
|
@ -2,7 +2,7 @@ import Vue from 'vue'
|
|||
import router from '@/router'
|
||||
import i18n from '@/i18n'
|
||||
import api from '@/api'
|
||||
import { timeout, isObjectLiteral } from '@/helpers/commons'
|
||||
import { timeout, isEmptyValue, isObjectLiteral } from '@/helpers/commons'
|
||||
|
||||
export default {
|
||||
state: {
|
||||
|
@ -15,7 +15,10 @@ export default {
|
|||
requests: [], // Array of `request`
|
||||
error: null, // null || request
|
||||
historyTimer: null, // null || setTimeout id
|
||||
tempMessages: [] // array of messages
|
||||
tempMessages: [], // Array of messages
|
||||
routerKey: undefined, // String if current route has params
|
||||
breadcrumb: [], // Array of routes
|
||||
transitionName: null // String of CSS class if transitions are enabled
|
||||
},
|
||||
|
||||
mutations: {
|
||||
|
@ -87,6 +90,18 @@ export default {
|
|||
} else {
|
||||
state.error = null
|
||||
}
|
||||
},
|
||||
|
||||
'SET_ROUTER_KEY' (state, key) {
|
||||
state.routerKey = key
|
||||
},
|
||||
|
||||
'SET_BREADCRUMB' (state, breadcrumb) {
|
||||
state.breadcrumb = breadcrumb
|
||||
},
|
||||
|
||||
'SET_TRANSITION_NAME' (state, transitionName) {
|
||||
state.transitionName = transitionName
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -264,6 +279,72 @@ export default {
|
|||
'DISMISS_WARNING' ({ commit, state }, request) {
|
||||
commit('SET_WAITING', false)
|
||||
Vue.delete(request, 'showWarningMessage')
|
||||
},
|
||||
|
||||
'UPDATE_ROUTER_KEY' ({ commit }, { to, from }) {
|
||||
if (isEmptyValue(to.params)) {
|
||||
commit('SET_ROUTER_KEY', undefined)
|
||||
return
|
||||
}
|
||||
// If the next route uses the same component as the previous one, Vue will not
|
||||
// recreate an instance of that component, so hooks like `created()` will not be
|
||||
// triggered and data will not be fetched.
|
||||
// For routes with params, we create a unique key to force the recreation of a view.
|
||||
// Params can be declared in route `meta` to stricly define which params should be
|
||||
// taken into account.
|
||||
const params = to.meta.routerParams
|
||||
? to.meta.routerParams.map(key => to.params[key])
|
||||
: Object.values(to.params)
|
||||
|
||||
commit('SET_ROUTER_KEY', `${to.name}-${params.join('-')}`)
|
||||
},
|
||||
|
||||
'UPDATE_BREADCRUMB' ({ commit }, { to, from }) {
|
||||
function getRouteNames (route) {
|
||||
if (route.meta.breadcrumb) return route.meta.breadcrumb
|
||||
const parentRoute = route.matched.slice().reverse().find(route => route.meta.breadcrumb)
|
||||
if (parentRoute) return parentRoute.meta.breadcrumb
|
||||
return []
|
||||
}
|
||||
|
||||
function formatRoute (route) {
|
||||
const { trad, param } = route.meta.args || {}
|
||||
let text = ''
|
||||
// if a traduction key string has been given and we also need to pass
|
||||
// the route param as a variable.
|
||||
if (trad && param) {
|
||||
text = i18n.t(trad, { [param]: to.params[param] })
|
||||
} else if (trad) {
|
||||
text = i18n.t(trad)
|
||||
} else {
|
||||
text = to.params[param]
|
||||
}
|
||||
return { name: route.name, text }
|
||||
}
|
||||
|
||||
const routeNames = getRouteNames(to)
|
||||
const allRoutes = router.getRoutes()
|
||||
const breadcrumb = routeNames.map(name => {
|
||||
const route = allRoutes.find(route => route.name === name)
|
||||
return formatRoute(route)
|
||||
})
|
||||
|
||||
commit('SET_BREADCRUMB', breadcrumb)
|
||||
|
||||
function getTitle (breadcrumb) {
|
||||
if (breadcrumb.length === 0) return formatRoute(to).text
|
||||
return (breadcrumb.length > 2 ? breadcrumb.slice(-2) : breadcrumb).map(route => route.text).reverse().join(' / ')
|
||||
}
|
||||
|
||||
// Display a simplified breadcrumb as the document title.
|
||||
document.title = `${getTitle(breadcrumb)} | ${i18n.t('yunohost_admin')}`
|
||||
},
|
||||
|
||||
'UPDATE_TRANSITION_NAME' ({ state, commit }, { to, from }) {
|
||||
// Use the breadcrumb array length as a direction indicator
|
||||
const toDepth = (to.meta.breadcrumb || []).length
|
||||
const fromDepth = (from.meta.breadcrumb || []).length
|
||||
commit('SET_TRANSITION_NAME', toDepth < fromDepth ? 'slide-right' : 'slide-left')
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -279,6 +360,9 @@ export default {
|
|||
currentRequest: state => {
|
||||
const request = state.requests.find(({ status }) => status === 'pending')
|
||||
return request || state.requests[state.requests.length - 1]
|
||||
}
|
||||
},
|
||||
routerKey: state => state.routerKey,
|
||||
breadcrumb: state => state.breadcrumb,
|
||||
transitionName: state => state.transitionName
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue