mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-13 00:38:06 +00:00
220 lines
5.9 KiB
Vue
220 lines
5.9 KiB
Vue
<template>
|
|
<Context ref="viewsContext" class="select" @shown="shown">
|
|
<div class="select__search">
|
|
<i class="select__search-icon fas fa-search"></i>
|
|
<input
|
|
v-model="query"
|
|
type="text"
|
|
class="select__search-input"
|
|
:placeholder="$t('viewsContext.searchView')"
|
|
/>
|
|
</div>
|
|
<div v-if="isLoading" class="context--loading">
|
|
<div class="loading"></div>
|
|
</div>
|
|
<ul
|
|
v-if="!isLoading && views.length > 0"
|
|
ref="dropdown"
|
|
v-auto-overflow-scroll
|
|
class="select__items"
|
|
>
|
|
<ViewsContextItem
|
|
v-for="view in searchAndOrder(views)"
|
|
:ref="'view-' + view.id"
|
|
:key="view.id"
|
|
v-sortable="{
|
|
enabled: !readOnly,
|
|
id: view.id,
|
|
update: order,
|
|
marginTop: -1.5,
|
|
}"
|
|
:view="view"
|
|
:table="table"
|
|
:read-only="readOnly"
|
|
@selected="selectedView"
|
|
></ViewsContextItem>
|
|
</ul>
|
|
<div v-if="!isLoading && views.length == 0" class="context__description">
|
|
{{ $t('viewsContext.noViews') }}
|
|
</div>
|
|
<div v-if="!readOnly" class="select__footer">
|
|
<div class="select__footer-create">
|
|
<CreateViewLink
|
|
v-for="(viewType, type) in viewTypes"
|
|
:key="type"
|
|
:table="table"
|
|
:view-type="viewType"
|
|
@created="scrollViewDropdownToBottom()"
|
|
></CreateViewLink>
|
|
</div>
|
|
</div>
|
|
</Context>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapState } from 'vuex'
|
|
|
|
import { notifyIf } from '@baserow/modules/core/utils/error'
|
|
import { escapeRegExp } from '@baserow/modules/core/utils/string'
|
|
import context from '@baserow/modules/core/mixins/context'
|
|
import dropdownHelpers from '@baserow/modules/core/mixins/dropdownHelpers'
|
|
import ViewsContextItem from '@baserow/modules/database/components/view/ViewsContextItem'
|
|
import CreateViewLink from '@baserow/modules/database/components/view/CreateViewLink'
|
|
|
|
export default {
|
|
name: 'ViewsContext',
|
|
components: {
|
|
ViewsContextItem,
|
|
CreateViewLink,
|
|
},
|
|
mixins: [context, dropdownHelpers],
|
|
props: {
|
|
table: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
views: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
readOnly: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
query: '',
|
|
firstShow: true,
|
|
}
|
|
},
|
|
computed: {
|
|
viewTypes() {
|
|
return this.$registry.getAll('view')
|
|
},
|
|
...mapState({
|
|
isLoading: (state) => state.view.loading,
|
|
isLoaded: (state) => state.view.loaded,
|
|
}),
|
|
},
|
|
methods: {
|
|
shown() {
|
|
if (this.firstShow) {
|
|
this.$nextTick(() => {
|
|
this.scrollViewDropdownIfNeeded()
|
|
})
|
|
this.firstShow = false
|
|
}
|
|
},
|
|
selectedView(view) {
|
|
this.hide()
|
|
this.$emit('selected-view', view)
|
|
},
|
|
/*
|
|
If the currently selected view is not visible inside the dropdown we need to
|
|
scroll just enough so that the selected view is visible as the last element
|
|
in the dropdown.
|
|
In case there are no views, we don't need to do anything and can simply return.
|
|
*/
|
|
scrollViewDropdownIfNeeded() {
|
|
if (this.views.length === 0) {
|
|
return
|
|
}
|
|
const dropdownElement = this.$refs.dropdown
|
|
const selectedViewItem = this.getSelectedViewItem()
|
|
const dropdownHeight = dropdownElement.clientHeight
|
|
if (
|
|
this.isSelectedViewOutOfDropdownView(selectedViewItem, dropdownHeight)
|
|
) {
|
|
dropdownElement.scrollTop = this.calculateOffsetToSelectedViewItem(
|
|
dropdownElement,
|
|
selectedViewItem
|
|
)
|
|
}
|
|
},
|
|
/**
|
|
* This method scrolls the ViewDropdown to the bottom
|
|
*/
|
|
scrollViewDropdownToBottom() {
|
|
this.$refs.dropdown.scrollTop = this.$refs.dropdown.scrollHeight
|
|
},
|
|
/**
|
|
* This method filters the view elements and returns the currently selected
|
|
* view dom item based on whether or not it is selected.
|
|
*/
|
|
getSelectedViewItem() {
|
|
const selectedViewArray = this.views.filter((item) => item._.selected)
|
|
const selectedViewItemID = selectedViewArray[0].id
|
|
return this.$refs[`view-${selectedViewItemID}`][0].$el
|
|
},
|
|
/**
|
|
* This method calculates whether or not the selectedViewItem is fully visible
|
|
* inside the ViewContext dropdown or not
|
|
*/
|
|
isSelectedViewOutOfDropdownView(selectedViewItem, dropdownHeight) {
|
|
const selectedOffsetPlusHeight =
|
|
selectedViewItem.offsetTop + selectedViewItem.clientHeight
|
|
return selectedOffsetPlusHeight > dropdownHeight
|
|
},
|
|
/**
|
|
* This method calculates the necessary offsetTop of the dropdown element so that
|
|
* the selected view item is the bottom element.
|
|
*/
|
|
calculateOffsetToSelectedViewItem(dropdownElement, selectedViewItem) {
|
|
const {
|
|
parentContainerBeforeHeight,
|
|
itemHeightWithMargins,
|
|
itemsInView,
|
|
} = this.getStyleProperties(dropdownElement, selectedViewItem)
|
|
|
|
const viewItemsBeforeSelectedViewItemHeight =
|
|
(itemsInView - 1) * itemHeightWithMargins
|
|
|
|
return (
|
|
selectedViewItem.offsetTop -
|
|
viewItemsBeforeSelectedViewItemHeight -
|
|
parentContainerBeforeHeight
|
|
)
|
|
},
|
|
searchAndOrder(views) {
|
|
const query = this.query
|
|
|
|
return views
|
|
.filter(function (view) {
|
|
const regex = new RegExp('(' + escapeRegExp(query) + ')', 'i')
|
|
return view.name.match(regex)
|
|
})
|
|
.sort((a, b) => a.order - b.order)
|
|
},
|
|
async order(order, oldOrder) {
|
|
try {
|
|
await this.$store.dispatch('view/order', {
|
|
table: this.table,
|
|
order,
|
|
oldOrder,
|
|
})
|
|
} catch (error) {
|
|
notifyIf(error, 'view')
|
|
}
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<i18n>
|
|
{
|
|
"en": {
|
|
"viewsContext": {
|
|
"searchView": "Search views",
|
|
"noViews": "No views found"
|
|
}
|
|
},
|
|
"fr": {
|
|
"viewsContext": {
|
|
"searchView": "Recherche",
|
|
"noViews": "Aucune vue trouvée"
|
|
}
|
|
}
|
|
}
|
|
</i18n>
|