Implements server sort in o2m table interface (#16897)

* implements server sort when dealing with multple relational pages

* always use server sort

* fixed unsetting sort

* removed frontend sorting

* make loading total count more accurate

* remove removed prop

Co-authored-by: Nitwel <mail@nitwel.de>
This commit is contained in:
Brainslug
2023-01-04 16:32:05 +01:00
committed by GitHub
parent 2ac022d286
commit cbd2af050b
3 changed files with 82 additions and 52 deletions

View File

@@ -3,8 +3,8 @@
<table :summary="internalHeaders.map((header) => header.text).join(', ')">
<table-header
v-model:headers="internalHeaders"
v-model:sort="internalSort"
v-model:reordering="reordering"
:sort="internalSort"
:show-select="showSelect"
:show-resize="showResize"
:some-items-selected="someItemsSelected"
@@ -16,6 +16,7 @@
:manual-sort-key="manualSortKey"
:allow-header-reorder="allowHeaderReorder"
@toggle-select-all="onToggleSelectAll"
@update:sort="updateSort"
>
<template v-for="header in internalHeaders" #[`header.${header.value}`]>
<slot :header="header" :name="`header.${header.value}`" />
@@ -95,7 +96,7 @@ import { ShowSelect } from '@directus/shared/types';
import { Header, HeaderRaw, Item, ItemSelectEvent, Sort } from './types';
import TableHeader from './table-header.vue';
import TableRow from './table-row.vue';
import { sortBy, clone, forEach, pick } from 'lodash';
import { clone, forEach, pick } from 'lodash';
import { i18n } from '@/lang/';
import Draggable from 'vuedraggable';
import { hideDragImage } from '@/utils/hide-drag-image';
@@ -124,7 +125,6 @@ interface Props {
loading?: boolean;
loadingText?: string;
noItemsText?: string;
serverSort?: boolean;
rowHeight?: number;
selectionUseKeys?: boolean;
inline?: boolean;
@@ -146,7 +146,6 @@ const props = withDefaults(defineProps<Props>(), {
loading: false,
loadingText: i18n.global.t('loading'),
noItemsText: i18n.global.t('no_items'),
serverSort: false,
rowHeight: 48,
selectionUseKeys: false,
inline: false,
@@ -205,18 +204,13 @@ const internalHeaders = computed({
// In case the sort prop isn't used, we'll use this local sort state as a fallback.
// This allows the table to allow inline sorting on column ootb without the need for
const internalLocalSort = ref<Sort>({
by: null,
desc: false,
});
const internalSort = computed({
get: () => props.sort || internalLocalSort.value,
set: (newSort: Sort) => {
emit('update:sort', newSort);
internalLocalSort.value = newSort;
},
});
const internalSort = computed<Sort>(
() =>
props.sort ?? {
by: null,
desc: false,
}
);
const reordering = ref<boolean>(false);
@@ -235,15 +229,7 @@ const fullColSpan = computed<string>(() => {
const internalItems = computed({
get: () => {
if (props.serverSort === true || internalSort.value.by === props.manualSortKey) {
return props.items;
}
if (internalSort.value.by === null) return props.items;
const itemsSorted = sortBy(props.items, [internalSort.value.by]);
if (internalSort.value.desc === true) return itemsSorted.reverse();
return itemsSorted;
return props.items;
},
set: (value: Item[]) => {
emit('update:items', value);
@@ -345,6 +331,9 @@ function onSortChange(event: EndEvent) {
emit('manual-sort', { item, to });
}
function updateSort(newSort: Sort) {
emit('update:sort', newSort?.by ? newSort : null);
}
</script>
<style scoped>

View File

@@ -189,24 +189,6 @@ export function useRelationMultiple(
const { create, remove, select, update } = useActions(_value);
return {
create,
update,
remove,
select,
displayItems,
totalItemCount,
loading,
selected,
fetchedSelectItems,
fetchedItems,
useActions,
cleanItem,
isItemSelected,
localDelete,
getItemEdits,
};
function useActions(target: Ref<Item>) {
return { create, update, remove, select };
@@ -311,20 +293,17 @@ export function useRelationMultiple(
if (!relation.value) return;
if (!itemId.value || itemId.value === '+') {
existingItemCount.value = 0;
fetchedItems.value = [];
return;
}
let targetCollection: string;
let targetPKField: string;
let reverseJunctionField: string;
const fields = new Set(previewQuery.value.fields);
switch (relation.value.type) {
case 'm2a':
targetCollection = relation.value.junctionCollection.collection;
targetPKField = relation.value.junctionPrimaryKeyField.field;
reverseJunctionField = relation.value.reverseJunctionField.field;
fields.add(relation.value.junctionPrimaryKeyField.field);
fields.add(relation.value.collectionField.field);
@@ -335,14 +314,12 @@ export function useRelationMultiple(
break;
case 'm2m':
targetCollection = relation.value.junctionCollection.collection;
targetPKField = relation.value.junctionPrimaryKeyField.field;
reverseJunctionField = relation.value.reverseJunctionField.field;
fields.add(relation.value.junctionPrimaryKeyField.field);
fields.add(`${relation.value.junctionField.field}.${relation.value.relatedPrimaryKeyField.field}`);
break;
case 'o2m':
targetCollection = relation.value.relatedCollection.collection;
targetPKField = relation.value.relatedPrimaryKeyField.field;
reverseJunctionField = relation.value.reverseJunctionField.field;
fields.add(relation.value.relatedPrimaryKeyField.field);
break;
@@ -353,8 +330,6 @@ export function useRelationMultiple(
try {
loading.value = true;
await updateItemCount(targetCollection, targetPKField, reverseJunctionField);
if (itemId.value !== '+') {
const filter: Filter = { _and: [{ [reverseJunctionField]: itemId.value } as Filter] };
if (previewQuery.value.filter) {
@@ -368,6 +343,7 @@ export function useRelationMultiple(
filter,
page: previewQuery.value.page,
limit: previewQuery.value.limit,
sort: previewQuery.value.sort,
},
});
@@ -380,7 +356,55 @@ export function useRelationMultiple(
}
}
async function updateItemCount(targetCollection: string, targetPKField: string, reverseJunctionField: string) {
watch(
[previewQuery, itemId, relation],
(newData, oldData) => {
const [newPreviewQuery, newItemId, newRelation] = newData;
const [oldPreviewQuery, oldItemId, oldRelation] = oldData;
if (
isEqual(newRelation, oldRelation) &&
newPreviewQuery.filter === oldPreviewQuery?.filter &&
newPreviewQuery.search === oldPreviewQuery?.search &&
newItemId === oldItemId
)
return;
updateItemCount();
},
{ immediate: true }
);
async function updateItemCount() {
if (!relation.value) return;
if (!itemId.value || itemId.value === '+') {
existingItemCount.value = 0;
return;
}
let targetCollection: string;
let targetPKField: string;
let reverseJunctionField: string;
switch (relation.value.type) {
case 'm2a':
targetCollection = relation.value.junctionCollection.collection;
targetPKField = relation.value.junctionPrimaryKeyField.field;
reverseJunctionField = relation.value.reverseJunctionField.field;
break;
case 'm2m':
targetCollection = relation.value.junctionCollection.collection;
targetPKField = relation.value.junctionPrimaryKeyField.field;
reverseJunctionField = relation.value.reverseJunctionField.field;
break;
case 'o2m':
targetCollection = relation.value.relatedCollection.collection;
targetPKField = relation.value.relatedPrimaryKeyField.field;
reverseJunctionField = relation.value.reverseJunctionField.field;
break;
}
const filter: Filter = { _and: [{ [reverseJunctionField]: itemId.value } as Filter] };
if (previewQuery.value.filter) {
filter._and.push(previewQuery.value.filter);
@@ -629,4 +653,22 @@ export function useRelationMultiple(
return { cleanItem, getPage, localDelete, getItemEdits, isEmpty };
}
return {
create,
update,
remove,
select,
displayItems,
totalItemCount,
loading,
selected,
fetchedSelectItems,
fetchedItems,
useActions,
cleanItem,
isItemSelected,
localDelete,
getItemEdits,
};
}

View File

@@ -14,7 +14,6 @@
:items="items"
:loading="loading"
:row-height="tableRowHeight"
server-sort
:item-key="primaryKeyField?.field"
:show-manual-sort="sortField !== null"
:manual-sort-key="sortField"