1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-26 21:54:40 +00:00
bramw_baserow/web-frontend/modules/database/pages/table.vue
2021-02-10 17:14:37 +00:00

275 lines
8.8 KiB
Vue

<template>
<div>
<header class="layout__col-2-1 header">
<div v-show="tableLoading" class="header__loading"></div>
<ul v-if="!tableLoading" class="header__filter">
<li class="header__filter-item header__filter-item--grids">
<a
ref="viewsSelectToggle"
class="header__filter-link"
@click="
$refs.viewsContext.toggle(
$refs.viewsSelectToggle,
'bottom',
'left',
4
)
"
>
<span v-if="hasSelectedView">
<i
class="header__filter-icon header-filter-icon--view fas"
:class="'fa-' + view._.type.iconClass"
></i>
{{ view.name }}
</span>
<span v-else>
<i
class="header__filter-icon header-filter-icon-no-choice fas fa-caret-square-down"
></i>
Choose view
</span>
</a>
<ViewsContext ref="viewsContext" :table="table"></ViewsContext>
</li>
<li
v-if="hasSelectedView && view._.type.canFilter"
class="header__filter-item"
>
<ViewFilter
:view="view"
:fields="fields"
:primary="primary"
@changed="refresh()"
></ViewFilter>
</li>
<li
v-if="hasSelectedView && view._.type.canSort"
class="header__filter-item"
>
<ViewSort
:view="view"
:fields="fields"
:primary="primary"
@changed="refresh()"
></ViewSort>
</li>
</ul>
<component
:is="getViewHeaderComponent(view)"
v-if="hasSelectedView"
:database="database"
:table="table"
:view="view"
:fields="fields"
:primary="primary"
/>
<ul v-if="!tableLoading" class="header__info">
<li>{{ database.name }}</li>
<li>{{ table.name }}</li>
</ul>
</header>
<div class="layout__col-2-2 content">
<component
:is="getViewComponent(view)"
v-if="hasSelectedView && !tableLoading"
ref="view"
:database="database"
:table="table"
:view="view"
:fields="fields"
:primary="primary"
@refresh="refresh"
/>
<div v-if="viewLoading" class="loading-overlay"></div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import ViewsContext from '@baserow/modules/database/components/view/ViewsContext'
import ViewFilter from '@baserow/modules/database/components/view/ViewFilter'
import ViewSort from '@baserow/modules/database/components/view/ViewSort'
/**
* This page component is the skeleton for a table. Depending on the selected view it
* will load the correct components into the header and body.
*/
export default {
components: {
ViewsContext,
ViewFilter,
ViewSort,
},
/**
* When the user leaves to another page we want to unselect the selected table. This
* way it will not be highlighted the left sidebar.
*/
beforeRouteLeave(to, from, next) {
this.$store.dispatch('table/unselect')
next()
},
layout: 'app',
/**
* Because there is no hook that is called before the route changes, we need the
* tableLoading middleware to change the table loading state. This change will get
* rendered right away. This allows us to have a custom loading animation when
* switching views.
*/
middleware: ['tableLoading'],
/**
* Prepares all the table, field and view data for the provided database, table and
* view id.
*/
async asyncData({ store, params, error, app }) {
// @TODO figure out why the id's aren't converted to an int in the route.
const databaseId = parseInt(params.databaseId)
const tableId = parseInt(params.tableId)
let viewId = params.viewId ? parseInt(params.viewId) : null
const data = {}
// Try to find the table in the already fetched applications by the
// groupsAndApplications middleware and select that one. By selecting the table, the
// fields and views are also going to be fetched.
try {
const { database, table } = await store.dispatch('table/selectById', {
databaseId,
tableId,
})
await store.dispatch('group/selectById', database.group.id)
data.database = database
data.table = table
} catch (e) {
// In case of a network error we want to fail hard.
if (e.response === undefined) {
throw e
}
return error({ statusCode: 404, message: 'Table not found.' })
}
// After selecting the table the fields become available which need to be added to
// the data.
data.fields = store.getters['field/getAll']
data.primary = store.getters['field/getPrimary']
// Because we do not have a dashboard for the table yet we're going to redirect to
// the first available view.
const firstView = store.getters['view/first']
if (viewId === null && firstView !== null) {
viewId = firstView.id
}
// If a view id is provided and the table is selected we can select the view. The
// views that belong to the table have already been fetched so we just need to
// select the correct one.
if (viewId !== null) {
try {
const { view } = await store.dispatch('view/selectById', viewId)
data.view = view
// It might be possible that the view also has some stores that need to be
// filled with initial data so we're going to call the fetch function here.
const type = app.$registry.get('view', view.type)
await type.fetch({ store }, view)
} catch (e) {
// In case of a network error we want to fail hard.
if (e.response === undefined) {
throw e
}
return error({ statusCode: 404, message: 'View not found.' })
}
}
return data
},
data() {
return {
// Shows a small spinning loading animation when the view is being refreshed.
viewLoading: false,
}
},
head() {
return {
title: (this.view ? this.view.name + ' - ' : '') + this.table.name,
}
},
computed: {
/**
* Indicates if there is a selected view by checking if the view object has been
* populated.
*/
hasSelectedView() {
return (
this.view !== undefined &&
Object.prototype.hasOwnProperty.call(this.view, '_')
)
},
...mapState({
// We need the tableLoading state to show a small loading animation when
// switching between views. Because some of the data will be populated by
// the asyncData function and some by mapping the state of a store it could look
// a bit strange for the user when switching between views because not all data
// renders at the same time. That is why we show this loading animation. Store
// changes are always rendered right away.
tableLoading: (state) => state.table.loading,
}),
},
/**
* The beforeCreate hook is called right after the asyncData finishes and when the
* page has been rendered for the first time. The perfect moment to stop the table
* loading animation.
*/
beforeCreate() {
this.$store.dispatch('table/setLoading', false)
},
beforeMount() {
this.$bus.$on('table-refresh', this.refresh)
},
mounted() {
this.$realtime.subscribe('table', { table_id: this.table.id })
},
beforeDestroy() {
this.$bus.$off('table-refresh', this.refresh)
this.$realtime.subscribe(null)
},
methods: {
getViewComponent(view) {
const type = this.$registry.get('view', view.type)
return type.getComponent()
},
getViewHeaderComponent(view) {
const type = this.$registry.get('view', view.type)
return type.getHeaderComponent()
},
/**
* Refreshes the whole view. All data will be reloaded and it will visually look
* the same as seeing the view for the first time.
*/
async refresh(event) {
this.viewLoading = true
const type = this.$registry.get('view', this.view.type)
await type.refresh({ store: this.$store }, this.view)
if (
Object.prototype.hasOwnProperty.call(this.$refs, 'view') &&
Object.prototype.hasOwnProperty.call(this.$refs.view, 'refresh')
) {
await this.$refs.view.refresh()
}
// It might be possible that the event has a callback that needs to be called
// after the rows are refreshed. This is for example the case when a field has
// changed. In that case we want to update the field in the store after the rows
// have been refreshed to prevent incompatible values in field types.
if (event && Object.prototype.hasOwnProperty.call(event, 'callback')) {
await event.callback()
}
this.$nextTick(() => {
this.viewLoading = false
})
},
},
}
</script>