Files
site/nova/resources/js/views/Index.vue

1284 lines
34 KiB
Vue
Executable File

<template>
<loading-view
:loading="initialLoading"
:dusk="resourceName + '-index-component'"
:data-relationship="viaRelationship"
>
<custom-index-header
v-if="!viaResource"
class="mb-3"
:resource-name="resourceName"
/>
<div v-if="shouldShowCards">
<cards
v-if="smallCards.length > 0"
:cards="smallCards"
class="mb-3"
:resource-name="resourceName"
/>
<cards
v-if="largeCards.length > 0"
:cards="largeCards"
size="large"
:resource-name="resourceName"
/>
</div>
<heading :level="1" class="mb-3" v-html="headingTitle" />
<div class="flex">
<!-- Search -->
<div
v-if="resourceInformation.searchable && !viaHasOne"
class="relative h-9 flex-no-shrink"
:class="{
'mb-6': resourceInformation.searchable && !viaHasOne,
}"
>
<icon type="search" class="absolute search-icon-center ml-3 text-70" />
<input
data-testid="search-input"
dusk="search"
class="appearance-none form-search w-search pl-search shadow"
:placeholder="__('Search')"
type="search"
v-model="search"
@keydown.stop="performSearch"
@search="performSearch"
spellcheck="false"
/>
</div>
<div class="w-full flex items-center" :class="{ 'mb-6': !viaResource }">
<custom-index-toolbar
v-if="!viaResource"
:resource-name="resourceName"
/>
<!-- Create / Attach Button -->
<create-resource-button
:label="createButtonLabel"
:singular-name="singularName"
:resource-name="resourceName"
:via-resource="viaResource"
:via-resource-id="viaResourceId"
:via-relationship="viaRelationship"
:relationship-type="relationshipType"
:authorized-to-create="authorizedToCreate && !resourceIsFull"
:authorized-to-relate="authorizedToRelate"
class="flex-no-shrink ml-auto"
:class="{ 'mb-6': viaResource }"
/>
</div>
</div>
<card>
<div
class="flex items-center"
:class="{
'py-3 border-b border-50':
shouldShowCheckBoxes ||
shouldShowDeleteMenu ||
softDeletes ||
!viaResource ||
hasFilters ||
haveStandaloneActions,
}"
>
<div class="flex items-center">
<div class="px-3" v-if="shouldShowCheckBoxes">
<!-- Select All -->
<dropdown
dusk="select-all-dropdown"
placement="bottom-end"
class="-mx-2"
>
<dropdown-trigger class="px-2">
<fake-checkbox :checked="selectAllChecked" />
</dropdown-trigger>
<dropdown-menu slot="menu" direction="ltr" width="250">
<div class="p-4">
<ul class="list-reset">
<li class="flex items-center mb-4">
<checkbox-with-label
:checked="selectAllChecked"
@input="toggleSelectAll"
dusk="select-all-button"
>
{{ __('Select All') }}
</checkbox-with-label>
</li>
<li class="flex items-center">
<checkbox-with-label
dusk="select-all-matching-button"
:checked="selectAllMatchingChecked"
@input="toggleSelectAllMatching"
>
<template>
<span class="mr-1">
{{ __('Select All Matching') }} ({{
allMatchingResourceCount
}})
</span>
</template>
</checkbox-with-label>
</li>
</ul>
</div>
</dropdown-menu>
</dropdown>
</div>
</div>
<div class="flex items-center ml-auto px-3">
<resource-polling-button
v-if="shouldShowPollingToggle"
:currently-polling="currentlyPolling"
@start-polling="startPolling"
@stop-polling="stopPolling"
class="mr-1"
/>
<!-- Action Selector -->
<action-selector
v-if="selectedResources.length > 0 || haveStandaloneActions"
:resource-name="resourceName"
:actions="availableActions"
:pivot-actions="pivotActions"
:pivot-name="pivotName"
:query-string="{
currentSearch,
encodedFilters,
currentTrashed,
viaResource,
viaResourceId,
viaRelationship,
}"
:selected-resources="selectedResourcesForActionSelector"
@actionExecuted="getResources"
/>
<!-- Lenses -->
<dropdown
class="bg-30 hover:bg-40 mr-3 rounded"
v-if="lenses.length > 0"
>
<dropdown-trigger class="px-3">
<h3
slot="default"
class="flex items-center font-normal text-base text-90 h-9"
>
{{ __('Lens') }}
</h3>
</dropdown-trigger>
<dropdown-menu slot="menu" width="240" direction="rtl">
<lens-selector :resource-name="resourceName" :lenses="lenses" />
</dropdown-menu>
</dropdown>
<!-- Filters -->
<filter-menu
:resource-name="resourceName"
:soft-deletes="softDeletes"
:via-resource="viaResource"
:via-has-one="viaHasOne"
:trashed="trashed"
:per-page="perPage"
:per-page-options="
perPageOptions || resourceInformation.perPageOptions
"
@clear-selected-filters="clearSelectedFilters"
@filter-changed="filterChanged"
@trashed-changed="trashedChanged"
@per-page-changed="updatePerPageChanged"
/>
<delete-menu
v-if="shouldShowDeleteMenu"
dusk="delete-menu"
:soft-deletes="softDeletes"
:resources="resources"
:selected-resources="selectedResources"
:via-many-to-many="viaManyToMany"
:all-matching-resource-count="allMatchingResourceCount"
:all-matching-selected="selectAllMatchingChecked"
:authorized-to-delete-selected-resources="
authorizedToDeleteSelectedResources
"
:authorized-to-force-delete-selected-resources="
authorizedToForceDeleteSelectedResources
"
:authorized-to-delete-any-resources="authorizedToDeleteAnyResources"
:authorized-to-force-delete-any-resources="
authorizedToForceDeleteAnyResources
"
:authorized-to-restore-selected-resources="
authorizedToRestoreSelectedResources
"
:authorized-to-restore-any-resources="
authorizedToRestoreAnyResources
"
@deleteSelected="deleteSelectedResources"
@deleteAllMatching="deleteAllMatchingResources"
@forceDeleteSelected="forceDeleteSelectedResources"
@forceDeleteAllMatching="forceDeleteAllMatchingResources"
@restoreSelected="restoreSelectedResources"
@restoreAllMatching="restoreAllMatchingResources"
@close="deleteModalOpen = false"
/>
</div>
</div>
<loading-view :loading="loading">
<div
v-if="!resources.length"
class="flex justify-center items-center px-6 py-8"
>
<div class="text-center">
<svg
class="mb-3"
xmlns="http://www.w3.org/2000/svg"
width="65"
height="51"
viewBox="0 0 65 51"
>
<path
fill="#A8B9C5"
d="M56 40h2c.552285 0 1 .447715 1 1s-.447715 1-1 1h-2v2c0 .552285-.447715 1-1 1s-1-.447715-1-1v-2h-2c-.552285 0-1-.447715-1-1s.447715-1 1-1h2v-2c0-.552285.447715-1 1-1s1 .447715 1 1v2zm-5.364125-8H38v8h7.049375c.350333-3.528515 2.534789-6.517471 5.5865-8zm-5.5865 10H6c-3.313708 0-6-2.686292-6-6V6c0-3.313708 2.686292-6 6-6h44c3.313708 0 6 2.686292 6 6v25.049375C61.053323 31.5511 65 35.814652 65 41c0 5.522847-4.477153 10-10 10-5.185348 0-9.4489-3.946677-9.950625-9zM20 30h16v-8H20v8zm0 2v8h16v-8H20zm34-2v-8H38v8h16zM2 30h16v-8H2v8zm0 2v4c0 2.209139 1.790861 4 4 4h12v-8H2zm18-12h16v-8H20v8zm34 0v-8H38v8h16zM2 20h16v-8H2v8zm52-10V6c0-2.209139-1.790861-4-4-4H6C3.790861 2 2 3.790861 2 6v4h52zm1 39c4.418278 0 8-3.581722 8-8s-3.581722-8-8-8-8 3.581722-8 8 3.581722 8 8 8z"
/>
</svg>
<h3
class="text-base text-80 font-normal"
:class="{ 'mb-6': authorizedToCreate && !resourceIsFull }"
>
{{
__('No :resource matched the given criteria.', {
resource: singularName.toLowerCase(),
})
}}
</h3>
<create-resource-button
classes="btn btn-sm btn-outline inline-flex items-center focus:outline-none focus:shadow-outline active:outline-none active:shadow-outline"
:label="createButtonLabel"
:singular-name="singularName"
:resource-name="resourceName"
:via-resource="viaResource"
:via-resource-id="viaResourceId"
:via-relationship="viaRelationship"
:relationship-type="relationshipType"
:authorized-to-create="authorizedToCreate && !resourceIsFull"
:authorized-to-relate="authorizedToRelate"
>
</create-resource-button>
</div>
</div>
<div class="overflow-hidden overflow-x-auto relative">
<!-- Resource Table -->
<resource-table
:authorized-to-relate="authorizedToRelate"
:resource-name="resourceName"
:resources="resources"
:singular-name="singularName"
:selected-resources="selectedResources"
:selected-resource-ids="selectedResourceIds"
:actions-are-available="allActions.length > 0"
:should-show-checkboxes="shouldShowCheckBoxes"
:via-resource="viaResource"
:via-resource-id="viaResourceId"
:via-relationship="viaRelationship"
:relationship-type="relationshipType"
:update-selection-status="updateSelectionStatus"
@order="orderByField"
@reset-order-by="resetOrderBy"
@delete="deleteResources"
@restore="restoreResources"
@actionExecuted="getResources"
ref="resourceTable"
/>
</div>
<!-- Pagination -->
<component
:is="paginationComponent"
v-if="shouldShowPagination"
:next="hasNextPage"
:previous="hasPreviousPage"
@load-more="loadMore"
@page="selectPage"
:pages="totalPages"
:page="currentPage"
:per-page="perPage"
:resource-count-label="resourceCountLabel"
:current-resource-count="resources.length"
:all-matching-resource-count="allMatchingResourceCount"
>
<span
v-if="resourceCountLabel"
class="text-sm text-80 px-4"
:class="{
'ml-auto': paginationComponent == 'pagination-links',
}"
>
{{ resourceCountLabel }}
</span>
</component>
</loading-view>
</card>
</loading-view>
</template>
<script>
import {
Capitalize,
Deletable,
Filterable,
HasCards,
InteractsWithQueryString,
InteractsWithResourceInformation,
Minimum,
Paginatable,
PerPageable,
mapProps,
} from 'laravel-nova'
import HasActions from '@/mixins/HasActions'
import { CancelToken, Cancel } from 'axios'
export default {
mixins: [
HasActions,
Deletable,
Filterable,
HasCards,
Paginatable,
PerPageable,
InteractsWithResourceInformation,
InteractsWithQueryString,
],
metaInfo() {
if (this.shouldOverrideMeta) {
return {
title: this.__(`${this.resourceInformation.label}`),
}
}
},
props: {
shouldOverrideMeta: {
type: Boolean,
default: true,
},
field: {
type: Object,
},
...mapProps([
'resourceName',
'viaResource',
'viaResourceId',
'viaRelationship',
]),
relationshipType: {
type: String,
default: '',
},
disablePagination: {
type: Boolean,
default: false,
},
initialPerPage: {
type: Number,
required: false,
},
},
data: () => ({
debouncer: null,
canceller: null,
pollingListener: null,
initialLoading: true,
loading: true,
resourceResponse: null,
resources: [],
softDeletes: false,
selectedResources: [],
selectAllMatchingResources: false,
allMatchingResourceCount: 0,
deleteModalOpen: false,
search: '',
lenses: [],
authorizedToRelate: false,
orderBy: '',
orderByDirection: '',
trashed: '',
// Load More Pagination
currentPageLoadMore: null,
currentlyPolling: false,
}),
/**
* Mount the component and retrieve its initial data.
*/
async created() {
if (Nova.missingResource(this.resourceName))
return this.$router.push({ name: '404' })
this.debouncer = _.debounce(
callback => callback(),
this.resourceInformation.debounce
)
// Bind the keydown even listener when the router is visited if this
// component is not a relation on a Detail page
if (!this.viaResource && !this.viaResourceId) {
Nova.addShortcut('c', this.handleKeydown)
}
this.initializeSearchFromQueryString()
this.initializePerPageFromQueryString()
this.initializeTrashedFromQueryString()
this.initializeOrderingFromQueryString()
this.currentlyPolling = this.resourceInformation.polling
await this.initializeFilters()
await this.getResources()
await this.getAuthorizationToRelate()
this.getLenses()
this.getActions()
this.initialLoading = false
this.$watch(
() => {
return (
this.resourceName +
this.encodedFilters +
this.currentSearch +
this.currentPage +
this.perPage +
this.currentOrderBy +
this.currentOrderByDirection +
this.currentTrashed
)
},
() => {
if (this.canceller !== null) this.canceller()
this.getResources()
}
)
Nova.$on('refresh-resources', () => {
this.getResources()
})
if (this.resourceInformation.polling) {
this.startPolling()
}
},
/**
* Unbind the keydown even listener when the before component is destroyed
*/
beforeDestroy() {
if (this.pollingListener) {
clearInterval(this.pollingListener)
}
if (!this.viaResource && !this.viaResourceId) {
Nova.disableShortcut('c')
}
},
watch: {
$route(to, from) {
this.initializeSearchFromQueryString()
this.initializeState(false)
},
},
methods: {
/**
* Handle the keydown event
*/
handleKeydown(e) {
// `c`
if (
this.authorizedToCreate &&
e.target.tagName != 'INPUT' &&
e.target.tagName != 'TEXTAREA' &&
e.target.contentEditable != 'true'
) {
this.$router.push({
name: 'create',
params: { resourceName: this.resourceName },
})
}
},
/**
* Select all of the available resources
*/
selectAllResources() {
this.selectedResources = this.resources.slice(0)
},
/**
* Toggle the selection of all resources
*/
toggleSelectAll(event) {
if (this.selectAllChecked) return this.clearResourceSelections()
this.selectAllResources()
},
/**
* Toggle the selection of all matching resources in the database
*/
toggleSelectAllMatching() {
if (!this.selectAllMatchingResources) {
this.selectAllResources()
this.selectAllMatchingResources = true
return
}
this.selectAllMatchingResources = false
},
/*
* Update the resource selection status
*/
updateSelectionStatus(resource) {
if (!_(this.selectedResources).includes(resource))
return this.selectedResources.push(resource)
const index = this.selectedResources.indexOf(resource)
if (index > -1) return this.selectedResources.splice(index, 1)
},
/**
* Get the resources based on the current page, search, filters, etc.
*/
getResources() {
this.loading = true
this.$nextTick(() => {
this.clearResourceSelections()
return Minimum(
Nova.request().get('/nova-api/' + this.resourceName, {
params: this.resourceRequestQueryString,
cancelToken: new CancelToken(canceller => {
this.canceller = canceller
}),
}),
300
)
.then(({ data }) => {
this.resources = []
this.resourceResponse = data
this.resources = data.resources
this.softDeletes = data.softDeletes
this.perPage = data.per_page
this.allMatchingResourceCount = data.total
this.loading = false
Nova.$emit('resources-loaded')
})
.catch(e => {
if (e instanceof Cancel) {
return
}
throw e
})
})
},
/**
* Get the relatable authorization status for the resource.
*/
getAuthorizationToRelate() {
if (
!this.authorizedToCreate &&
this.relationshipType != 'belongsToMany' &&
this.relationshipType != 'morphToMany'
) {
return
}
if (!this.viaResource) {
return (this.authorizedToRelate = true)
}
return Nova.request()
.get(
'/nova-api/' +
this.resourceName +
'/relate-authorization' +
'?viaResource=' +
this.viaResource +
'&viaResourceId=' +
this.viaResourceId +
'&viaRelationship=' +
this.viaRelationship +
'&relationshipType=' +
this.relationshipType
)
.then(response => {
this.authorizedToRelate = response.data.authorized
})
},
/**
* Get the lenses available for the current resource.
*/
getLenses() {
this.lenses = []
if (this.viaResource) {
return
}
return Nova.request()
.get('/nova-api/' + this.resourceName + '/lenses')
.then(response => {
this.lenses = response.data
})
},
/**
* Get the actions available for the current resource.
*/
getActions() {
this.actions = []
this.pivotActions = null
return Nova.request()
.get(`/nova-api/${this.resourceName}/actions`, {
params: {
viaResource: this.viaResource,
viaResourceId: this.viaResourceId,
viaRelationship: this.viaRelationship,
relationshipType: this.relationshipType,
display: 'index',
},
})
.then(response => {
this.actions = response.data.actions
this.pivotActions = response.data.pivotActions
})
},
/**
* Execute a search against the resource.
*/
performSearch(event) {
this.debouncer(() => {
// Only search if we're not tabbing into the field
if (event.which != 9) {
this.updateQueryString({
[this.pageParameter]: 1,
[this.searchParameter]: this.search,
})
}
})
},
/**
* Clear the selected resouces and the "select all" states.
*/
clearResourceSelections() {
this.selectAllMatchingResources = false
this.selectedResources = []
},
/**
* Get the count of all of the matching resources.
*/
getAllMatchingResourceCount() {
Nova.request()
.get('/nova-api/' + this.resourceName + '/count', {
params: this.resourceRequestQueryString,
})
.then(response => {
this.allMatchingResourceCount = response.data.count
})
},
/**
* Sort the resources by the given field.
*/
orderByField(field) {
let direction = this.currentOrderByDirection == 'asc' ? 'desc' : 'asc'
if (this.currentOrderBy != field.sortableUriKey) {
direction = 'asc'
}
this.updateQueryString({
[this.orderByParameter]: field.sortableUriKey,
[this.orderByDirectionParameter]: direction,
})
},
/**
* Reset the order by to its default state
*/
resetOrderBy(field) {
this.updateQueryString({
[this.orderByParameter]: field.sortableUriKey,
[this.orderByDirectionParameter]: null,
})
},
/**
* Sync the current search value from the query string.
*/
initializeSearchFromQueryString() {
this.search = this.currentSearch
},
/**
* Sync the current order by values from the query string.
*/
initializeOrderingFromQueryString() {
this.orderBy = this.currentOrderBy
this.orderByDirection = this.currentOrderByDirection
},
/**
* Sync the trashed state values from the query string.
*/
initializeTrashedFromQueryString() {
this.trashed = this.currentTrashed
},
/**
* Update the trashed constraint for the resource listing.
*/
trashedChanged(trashedStatus) {
this.trashed = trashedStatus
this.updateQueryString({ [this.trashedParameter]: this.trashed })
},
/**
* Update the per page parameter in the query string
*/
updatePerPageChanged(perPage) {
this.perPage = perPage
this.perPageChanged()
},
/**
* Load more resources.
*/
loadMore() {
if (this.currentPageLoadMore === null) {
this.currentPageLoadMore = this.currentPage
}
this.currentPageLoadMore = this.currentPageLoadMore + 1
return Minimum(
Nova.request().get('/nova-api/' + this.resourceName, {
params: {
...this.resourceRequestQueryString,
page: this.currentPageLoadMore, // We do this to override whatever page number is in the URL
},
}),
300
).then(({ data }) => {
this.resourceResponse = data
this.resources = [...this.resources, ...data.resources]
this.allMatchingResourceCount = data.total
Nova.$emit('resources-loaded')
})
},
/**
* Select the next page.
*/
selectPage(page) {
this.updateQueryString({ [this.pageParameter]: page })
},
/**
* Sync the per page values from the query string.
*/
initializePerPageFromQueryString() {
this.perPage =
this.$route.query[this.perPageParameter] ||
this.initialPerPage ||
this.resourceInformation.perPageOptions[0]
},
/**
* Pause polling for new resources.
*/
stopPolling() {
clearInterval(this.pollingListener)
this.$nextTick(() => (this.currentlyPolling = false))
},
/**
* Start polling for new resources.
*/
startPolling() {
this.pollingListener = setInterval(() => {
if (
document.hasFocus() &&
document.querySelectorAll('div.modal').length < 1
) {
this.getResources()
}
}, this.resourceInformation.pollingInterval)
this.$nextTick(() => (this.currentlyPolling = true))
},
},
computed: {
/**
* Determine if the resource has any filters
*/
hasFilters() {
return this.$store.getters[`${this.resourceName}/hasFilters`]
},
/**
* Determine if the resource should show any cards
*/
shouldShowCards() {
// Don't show cards if this resource is beings shown via a relations
return (
this.cards.length > 0 &&
this.resourceName == this.$route.params.resourceName
)
},
/**
* Get the endpoint for this resource's metrics.
*/
cardsEndpoint() {
return `/nova-api/${this.resourceName}/cards`
},
/**
* Get the name of the search query string variable.
*/
searchParameter() {
return this.viaRelationship
? this.viaRelationship + '_search'
: this.resourceName + '_search'
},
/**
* Get the name of the order by query string variable.
*/
orderByParameter() {
return this.viaRelationship
? this.viaRelationship + '_order'
: this.resourceName + '_order'
},
/**
* Get the name of the order by direction query string variable.
*/
orderByDirectionParameter() {
return this.viaRelationship
? this.viaRelationship + '_direction'
: this.resourceName + '_direction'
},
/**
* Get the name of the trashed constraint query string variable.
*/
trashedParameter() {
return this.viaRelationship
? this.viaRelationship + '_trashed'
: this.resourceName + '_trashed'
},
/**
* Get the name of the per page query string variable.
*/
perPageParameter() {
return this.viaRelationship
? this.viaRelationship + '_per_page'
: this.resourceName + '_per_page'
},
/**
* Get the name of the page query string variable.
*/
pageParameter() {
return this.viaRelationship
? this.viaRelationship + '_page'
: this.resourceName + '_page'
},
/**
* Build the resource request query string.
*/
resourceRequestQueryString() {
return {
search: this.currentSearch,
filters: this.encodedFilters,
orderBy: this.currentOrderBy,
orderByDirection: this.currentOrderByDirection,
perPage: this.currentPerPage,
trashed: this.currentTrashed,
page: this.currentPage,
viaResource: this.viaResource,
viaResourceId: this.viaResourceId,
viaRelationship: this.viaRelationship,
viaResourceRelationship: this.viaResourceRelationship,
relationshipType: this.relationshipType,
}
},
/**
* Determine if all resources are selected.
*/
selectAllChecked() {
return this.selectedResources.length == this.resources.length
},
/**
* Determine if all matching resources are selected.
*/
selectAllMatchingChecked() {
return (
this.selectedResources.length == this.resources.length &&
this.selectAllMatchingResources
)
},
/**
* Get the IDs for the selected resources.
*/
selectedResourceIds() {
return _.map(this.selectedResources, resource => resource.id.value)
},
/**
* Get the current search value from the query string.
*/
currentSearch() {
return this.$route.query[this.searchParameter] || ''
},
/**
* Get the current order by value from the query string.
*/
currentOrderBy() {
return this.$route.query[this.orderByParameter] || ''
},
/**
* Get the current order by direction from the query string.
*/
currentOrderByDirection() {
return this.$route.query[this.orderByDirectionParameter] || null
},
/**
* Get the current trashed constraint value from the query string.
*/
currentTrashed() {
return this.$route.query[this.trashedParameter] || ''
},
/**
* Determine if the current resource listing is via a many-to-many relationship.
*/
viaManyToMany() {
return (
this.relationshipType == 'belongsToMany' ||
this.relationshipType == 'morphToMany'
)
},
/**
* Determine if the resource / relationship is "full".
*/
resourceIsFull() {
return (
(Boolean(this.viaHasOne) && this.resources.length > 0) ||
Boolean(this.viaHasOneThrough && this.resources.length > 0)
)
},
/**
* Determine if the current resource listing is via a has-one relationship.
*/
viaHasOne() {
return (
this.relationshipType == 'hasOne' || this.relationshipType == 'morphOne'
)
},
viaHasOneThrough() {
return this.relationshipType == 'hasOneThrough'
},
/**
* Get the singular name for the resource
*/
singularName() {
if (this.isRelation && this.field) {
return Capitalize(this.field.singularLabel)
}
return Capitalize(this.resourceInformation.singularLabel)
},
/**
* Get the default label for the create button
*/
createButtonLabel() {
return this.resourceInformation.createButtonLabel
},
/**
* Determine if there are any resources for the view
*/
hasResources() {
return Boolean(this.resources.length > 0)
},
/**
* Determine if there any lenses for this resource
*/
hasLenses() {
return Boolean(this.lenses.length > 0)
},
/**
* Determine whether to show the selection checkboxes for resources
*/
shouldShowCheckBoxes() {
return (
Boolean(this.hasResources && !this.viaHasOne) &&
Boolean(
this.actionsAreAvailable ||
this.authorizedToDeleteAnyResources ||
this.canShowDeleteMenu
)
)
},
/**
* Determine if any selected resources may be deleted.
*/
authorizedToDeleteSelectedResources() {
return Boolean(
_.find(this.selectedResources, resource => resource.authorizedToDelete)
)
},
/**
* Determine if any selected resources may be force deleted.
*/
authorizedToForceDeleteSelectedResources() {
return Boolean(
_.find(
this.selectedResources,
resource => resource.authorizedToForceDelete
)
)
},
/**
* Determine if the user is authorized to delete any listed resource.
*/
authorizedToDeleteAnyResources() {
return (
this.resources.length > 0 &&
Boolean(_.find(this.resources, resource => resource.authorizedToDelete))
)
},
/**
* Determine if the user is authorized to force delete any listed resource.
*/
authorizedToForceDeleteAnyResources() {
return (
this.resources.length > 0 &&
Boolean(
_.find(this.resources, resource => resource.authorizedToForceDelete)
)
)
},
/**
* Determine if any selected resources may be restored.
*/
authorizedToRestoreSelectedResources() {
return Boolean(
_.find(this.selectedResources, resource => resource.authorizedToRestore)
)
},
/**
* Determine if the user is authorized to restore any listed resource.
*/
authorizedToRestoreAnyResources() {
return (
this.resources.length > 0 &&
Boolean(
_.find(this.resources, resource => resource.authorizedToRestore)
)
)
},
/**
* Determine whether the delete menu should be shown to the user
*/
shouldShowDeleteMenu() {
return (
Boolean(this.selectedResources.length > 0) && this.canShowDeleteMenu
)
},
/**
* Determine whether the user is authorized to perform actions on the delete menu
*/
canShowDeleteMenu() {
return Boolean(
this.authorizedToDeleteSelectedResources ||
this.authorizedToForceDeleteSelectedResources ||
this.authorizedToRestoreSelectedResources ||
this.selectAllMatchingChecked
)
},
/**
* Determine if the index is a relation field
*/
isRelation() {
return Boolean(this.viaResourceId && this.viaRelationship)
},
/**
* Return the heading for the view
*/
headingTitle() {
return this.loading
? '&nbsp;'
: this.isRelation && this.field
? this.field.name
: this.resourceResponse.label
},
/**
* Return the resource count label
*/
resourceCountLabel() {
const first = this.perPage * (this.currentPage - 1)
return (
this.resources.length &&
`${Nova.formatNumber(first + 1)}-${Nova.formatNumber(
first + this.resources.length
)} ${this.__('of')} ${Nova.formatNumber(this.allMatchingResourceCount)}`
)
},
/**
* Return the currently encoded filter string from the store
*/
encodedFilters() {
return this.$store.getters[`${this.resourceName}/currentEncodedFilters`]
},
/**
* Return the initial encoded filters from the query string
*/
initialEncodedFilters() {
return this.$route.query[this.filterParameter] || ''
},
paginationComponent() {
return `pagination-${Nova.config['pagination'] || 'links'}`
},
hasNextPage() {
return Boolean(
this.resourceResponse && this.resourceResponse.next_page_url
)
},
hasPreviousPage() {
return Boolean(
this.resourceResponse && this.resourceResponse.prev_page_url
)
},
totalPages() {
return Math.ceil(this.allMatchingResourceCount / this.currentPerPage)
},
/**
* Get the current per page value from the query string.
*/
currentPerPage() {
return this.perPage
},
/**
* The per-page options configured for this resource.
*/
perPageOptions() {
if (this.resourceResponse) {
return this.resourceResponse.per_page_options
}
},
/**
* Determine whether the pagination component should be shown.
*/
shouldShowPagination() {
return (
this.disablePagination !== true &&
this.resourceResponse &&
this.resources.length > 0
)
},
/**
* Determine if the polling toggle button should be shown.
*/
shouldShowPollingToggle() {
return this.resourceInformation.showPollingToggle
},
},
}
</script>