mirror of
https://github.com/directus/directus.git
synced 2026-01-30 02:37:56 -05:00
add support for relational sorting
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-notice v-if="!collectionField" type="warning">
|
||||
<v-notice v-if="!collectionField && !collection" type="warning">
|
||||
{{ $t('collection_field_not_setup') }}
|
||||
</v-notice>
|
||||
<v-notice v-else-if="selectItems.length === 0" type="warning">
|
||||
@@ -27,6 +27,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
typeAllowList: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
@@ -54,8 +58,8 @@ export default defineComponent({
|
||||
const values = inject('values', ref<Record<string, any>>({}));
|
||||
|
||||
const fields = computed(() => {
|
||||
if (!props.collectionField) return [];
|
||||
return fieldsStore.getFieldsForCollection(values.value[props.collectionField]);
|
||||
if (!props.collectionField && !props.collection) return [];
|
||||
return fieldsStore.getFieldsForCollection(props.collection || values.value[props.collectionField]);
|
||||
});
|
||||
|
||||
const selectItems = computed(() =>
|
||||
@@ -74,7 +78,7 @@ export default defineComponent({
|
||||
})
|
||||
);
|
||||
|
||||
return { selectItems };
|
||||
return { selectItems, values };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
<div class="one-to-many" v-else>
|
||||
<v-table
|
||||
:loading="loading"
|
||||
:items="items"
|
||||
:items="sortedItems || items"
|
||||
:headers.sync="tableHeaders"
|
||||
show-resize
|
||||
inline
|
||||
:sort.sync="sort"
|
||||
@update:items="sortItems($event)"
|
||||
@click:row="editItem"
|
||||
:disabled="disabled"
|
||||
:show-manual-sort="sortField !== null"
|
||||
:manual-sort-key="sortField"
|
||||
>
|
||||
<template v-for="header in tableHeaders" v-slot:[`item.${header.value}`]="{ item }">
|
||||
<render-display
|
||||
@@ -73,6 +77,7 @@ import useRelation from './use-relation';
|
||||
import usePreview from './use-preview';
|
||||
import useEdit from './use-edit';
|
||||
import useSelection from './use-selection';
|
||||
import useSort from './use-sort';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalItem, ModalCollection },
|
||||
@@ -93,6 +98,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sortField: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
fields: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
@@ -103,7 +112,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { value, collection, field, fields } = toRefs(props);
|
||||
const { value, collection, field, fields, sortField } = toRefs(props);
|
||||
|
||||
function emitter(newVal: any[] | null) {
|
||||
emit('input', newVal);
|
||||
@@ -127,6 +136,7 @@ export default defineComponent({
|
||||
const { tableHeaders, items, loading, error } = usePreview(
|
||||
value,
|
||||
fields,
|
||||
sortField,
|
||||
relationInfo,
|
||||
getNewSelectedItems,
|
||||
getUpdatedItems,
|
||||
@@ -151,6 +161,8 @@ export default defineComponent({
|
||||
emitter
|
||||
);
|
||||
|
||||
const { sort, sortItems, sortedItems } = useSort(sortField, fields, items, emitter);
|
||||
|
||||
return {
|
||||
junction,
|
||||
relation,
|
||||
@@ -172,6 +184,9 @@ export default defineComponent({
|
||||
relatedPrimaryKey,
|
||||
get,
|
||||
editModalActive,
|
||||
sort,
|
||||
sortItems,
|
||||
sortedItems,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="field half">
|
||||
<p class="type-label">{{ $t('sort_field') }}</p>
|
||||
<interface-field
|
||||
v-model="sortField"
|
||||
:collection="junctionCollection"
|
||||
:type-allow-list="['bigInteger', 'integer']"
|
||||
allowNone
|
||||
></interface-field>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -52,6 +61,19 @@ export default defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
const sortField = computed({
|
||||
get() {
|
||||
return props.value?.sortField;
|
||||
},
|
||||
set(newFields: string) {
|
||||
emit('input', {
|
||||
...(props.value || {}),
|
||||
sortField: newFields,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const fields = computed({
|
||||
get() {
|
||||
return props.value?.fields;
|
||||
@@ -63,6 +85,7 @@ export default defineComponent({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const junctionCollection = computed(() => {
|
||||
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
|
||||
const { field } = props.fieldData;
|
||||
@@ -71,12 +94,14 @@ export default defineComponent({
|
||||
);
|
||||
return junctionRelation?.many_collection || null;
|
||||
});
|
||||
|
||||
const junctionCollectionExists = computed(() => {
|
||||
return !!collectionsStore.state.collections.find(
|
||||
(collection) => collection.collection === junctionCollection.value
|
||||
);
|
||||
});
|
||||
return { fields, junctionCollection, junctionCollectionExists };
|
||||
|
||||
return { fields, sortField, junctionCollection, junctionCollectionExists };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { cloneDeep, get } from 'lodash';
|
||||
export default function usePreview(
|
||||
value: Ref<(string | number | Record<string, any>)[] | null>,
|
||||
fields: Ref<string[]>,
|
||||
sortField: Ref<string | null>,
|
||||
relation: Ref<RelationInfo>,
|
||||
getNewSelectedItems: () => Record<string, any>[],
|
||||
getUpdatedItems: () => Record<string, any>[],
|
||||
@@ -124,6 +125,9 @@ export default function usePreview(
|
||||
if (filteredFields.includes(junctionPkField) === false) filteredFields.push(junctionPkField);
|
||||
if (filteredFields.includes(junctionField) === false) filteredFields.push(junctionField);
|
||||
|
||||
if (sortField.value !== null && filteredFields.includes(sortField.value) === false)
|
||||
filteredFields.push(sortField.value);
|
||||
|
||||
data = await request(junctionCollection, filteredFields, junctionPkField, primaryKeys);
|
||||
}
|
||||
|
||||
|
||||
35
app/src/interfaces/many-to-many/use-sort.ts
Normal file
35
app/src/interfaces/many-to-many/use-sort.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Ref, ref, computed } from '@vue/composition-api';
|
||||
import { Sort } from '@/components/v-table/types';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
export default function useSort(
|
||||
sortField: Ref<string | null>,
|
||||
fields: Ref<string[]>,
|
||||
items: Ref<Record<string, any>[]>,
|
||||
emit: (newVal: any[] | null) => void
|
||||
) {
|
||||
const sort = ref<Sort>({ by: sortField.value || fields.value[0], desc: false });
|
||||
|
||||
function sortItems(newItems: Record<string, any>[]) {
|
||||
const sField = sortField.value;
|
||||
if (sField === null) return;
|
||||
|
||||
const itemsSorted = newItems.map((item, i) => {
|
||||
item[sField] = i;
|
||||
return item;
|
||||
});
|
||||
|
||||
emit(itemsSorted);
|
||||
}
|
||||
|
||||
const sortedItems = computed(() => {
|
||||
const sField = sortField.value;
|
||||
if (sField === null || sort.value.by !== sField) return null;
|
||||
|
||||
const desc = sort.value.desc;
|
||||
const sorted = sortBy(items.value, [sField]);
|
||||
return desc ? sorted.reverse() : sorted;
|
||||
});
|
||||
|
||||
return { sort, sortItems, sortedItems };
|
||||
}
|
||||
@@ -5,10 +5,12 @@
|
||||
<div class="one-to-many" v-else>
|
||||
<v-table
|
||||
:loading="loading"
|
||||
:items.sync="displayItems"
|
||||
:items="sortedItems || items"
|
||||
:headers.sync="tableHeaders"
|
||||
show-resize
|
||||
inline
|
||||
:sort.sync="sort"
|
||||
@update:items="sortItems($event)"
|
||||
@click:row="editItem"
|
||||
:disabled="disabled"
|
||||
:show-manual-sort="sortField !== null"
|
||||
@@ -69,9 +71,10 @@ import useCollection from '@/composables/use-collection';
|
||||
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
|
||||
import ModalItem from '@/views/private/components/modal-item';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import { Sort } from '@/components/v-table/types';
|
||||
import { Filter, Field } from '@/types';
|
||||
import { Header } from '@/components/v-table/types';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual, sortBy } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalItem, ModalCollection },
|
||||
@@ -96,6 +99,10 @@ export default defineComponent({
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
sortField: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -106,14 +113,11 @@ export default defineComponent({
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const sortField = computed(() => {
|
||||
return relatedCollection.value.meta?.sort_field || null;
|
||||
});
|
||||
|
||||
const { relation, relatedCollection, relatedPrimaryKeyField } = useRelation();
|
||||
const { tableHeaders, displayItems, loading, error } = useTable();
|
||||
const { tableHeaders, items, loading, error } = useTable();
|
||||
const { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit } = useEdits();
|
||||
const { stageSelection, selectModalActive, selectionFilters } = useSelection();
|
||||
const { sort, sortItems, sortedItems } = useSort();
|
||||
|
||||
return {
|
||||
relation,
|
||||
@@ -128,9 +132,11 @@ export default defineComponent({
|
||||
stageSelection,
|
||||
selectModalActive,
|
||||
deleteItem,
|
||||
displayItems,
|
||||
items,
|
||||
sortItems,
|
||||
selectionFilters,
|
||||
sortField,
|
||||
sort,
|
||||
sortedItems,
|
||||
};
|
||||
|
||||
function getItem(id: string | number) {
|
||||
@@ -207,6 +213,31 @@ export default defineComponent({
|
||||
);
|
||||
}
|
||||
|
||||
function useSort() {
|
||||
const sort = ref<Sort>({ by: props.sortField || props.fields[0], desc: false });
|
||||
|
||||
function sortItems(newItems: Record<string, any>[]) {
|
||||
if (props.sortField === null) return;
|
||||
|
||||
const itemsSorted = newItems.map((item, i) => {
|
||||
item[props.sortField] = i;
|
||||
return item;
|
||||
});
|
||||
|
||||
emit('input', itemsSorted);
|
||||
}
|
||||
|
||||
const sortedItems = computed(() => {
|
||||
if (props.sortField === null || sort.value.by !== props.sortField) return null;
|
||||
|
||||
const desc = sort.value.desc;
|
||||
const sorted = sortBy(items.value, [props.sortField]);
|
||||
return desc ? sorted.reverse() : sorted;
|
||||
});
|
||||
|
||||
return { sort, sortItems, sortedItems };
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds info about the current relationship, like related collection, primary key field
|
||||
* of the other collection etc
|
||||
@@ -230,7 +261,7 @@ export default defineComponent({
|
||||
// values if it needs to. This allows the user to manually resize the columns for example
|
||||
const tableHeaders = ref<Header[]>([]);
|
||||
const loading = ref(false);
|
||||
const displayItems = ref<Record<string, any>[]>([]);
|
||||
const items = ref<Record<string, any>[]>([]);
|
||||
const error = ref(null);
|
||||
|
||||
watch(
|
||||
@@ -245,8 +276,8 @@ export default defineComponent({
|
||||
fields.push(pkField);
|
||||
}
|
||||
|
||||
if (sortField.value !== null && fields.includes(sortField.value) === false)
|
||||
fields.push(sortField.value);
|
||||
if (props.sortField !== null && fields.includes(props.sortField) === false)
|
||||
fields.push(props.sortField);
|
||||
|
||||
try {
|
||||
const endpoint = relatedCollection.value.collection.startsWith('directus_')
|
||||
@@ -271,7 +302,7 @@ export default defineComponent({
|
||||
const updatedItems = getUpdatedItems();
|
||||
const newItems = getNewItems();
|
||||
|
||||
displayItems.value = existingItems
|
||||
items.value = existingItems
|
||||
.map((item) => {
|
||||
const updatedItem = updatedItems.find((updated) => updated[pkField] === item[pkField]);
|
||||
if (updatedItem !== undefined) return updatedItem;
|
||||
@@ -321,7 +352,7 @@ export default defineComponent({
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return { tableHeaders, displayItems, loading, error };
|
||||
return { tableHeaders, items, loading, error };
|
||||
}
|
||||
|
||||
function useEdits() {
|
||||
@@ -386,11 +417,11 @@ export default defineComponent({
|
||||
const selectModalActive = ref(false);
|
||||
|
||||
const selectedPrimaryKeys = computed<(number | string)[]>(() => {
|
||||
if (displayItems.value === null) return [];
|
||||
if (items.value === null) return [];
|
||||
|
||||
const pkField = relatedPrimaryKeyField.value.field;
|
||||
|
||||
return displayItems.value
|
||||
return items.value
|
||||
.filter((currentItem) => pkField in currentItem)
|
||||
.map((currentItem) => currentItem[pkField]);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
:inject="relatedCollectionExists ? null : { fields: newFields, collections: newCollections, relations }"
|
||||
/>
|
||||
</div>
|
||||
<div class="field half">
|
||||
<p class="type-label">{{ $t('sort_field') }}</p>
|
||||
<interface-field
|
||||
v-model="sortField"
|
||||
:collection="relatedCollection"
|
||||
:type-allow-list="['bigInteger', 'integer']"
|
||||
allowNone
|
||||
></interface-field>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -62,6 +71,18 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
const sortField = computed({
|
||||
get() {
|
||||
return props.value?.sortField;
|
||||
},
|
||||
set(newFields: string) {
|
||||
emit('input', {
|
||||
...(props.value || {}),
|
||||
sortField: newFields,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const relatedCollection = computed(() => {
|
||||
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
|
||||
const { field } = props.fieldData;
|
||||
@@ -77,7 +98,7 @@ export default defineComponent({
|
||||
);
|
||||
});
|
||||
|
||||
return { fields, relatedCollection, relatedCollectionExists };
|
||||
return { fields, sortField, relatedCollection, relatedCollectionExists };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user