mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
fix and clean up m2m & o2m (#15220)
This commit is contained in:
@@ -12,7 +12,11 @@
|
||||
</div>
|
||||
|
||||
<div v-if="enableSearchFilter && (totalItemCount > 10 || search || searchFilter)" class="search">
|
||||
<search-input v-model="search" v-model:filter="searchFilter" :collection="junctionCollection" />
|
||||
<search-input
|
||||
v-model="search"
|
||||
v-model:filter="searchFilter"
|
||||
:collection="relationInfo.junctionCollection.collection"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-button
|
||||
@@ -51,7 +55,7 @@
|
||||
<template v-for="header in headers" :key="header.value" #[`item.${header.value}`]="{ item }">
|
||||
<render-template
|
||||
:title="header.value"
|
||||
:collection="junctionCollection"
|
||||
:collection="relationInfo.junctionCollection.collection"
|
||||
:item="item"
|
||||
:template="`{{${header.value}}}`"
|
||||
/>
|
||||
@@ -110,7 +114,11 @@
|
||||
@click="editItem(element)"
|
||||
>
|
||||
<v-icon v-if="allowDrag" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :collection="junctionCollection" :item="element" :template="templateWithDefaults" />
|
||||
<render-template
|
||||
:collection="relationInfo.junctionCollection.collection"
|
||||
:item="element"
|
||||
:template="templateWithDefaults"
|
||||
/>
|
||||
<div class="spacer" />
|
||||
|
||||
<router-link
|
||||
@@ -155,10 +163,10 @@
|
||||
<drawer-item
|
||||
:disabled="disabled"
|
||||
:active="editModalActive"
|
||||
:collection="junctionCollection"
|
||||
:collection="relationInfo.junctionCollection.collection"
|
||||
:primary-key="currentlyEditing || '+'"
|
||||
:related-primary-key="relatedPrimaryKey || '+'"
|
||||
:junction-field="junctionField"
|
||||
:junction-field="relationInfo.junctionField.field"
|
||||
:edits="editsAtStart"
|
||||
:circular-field="relationInfo.reverseJunctionField.field"
|
||||
@input="stageEdits"
|
||||
@@ -168,7 +176,7 @@
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
v-model:active="selectModalActive"
|
||||
:collection="relatedCollection"
|
||||
:collection="relationInfo.relatedCollection.collection"
|
||||
:filter="customFilter"
|
||||
multiple
|
||||
@input="select"
|
||||
@@ -180,7 +188,7 @@
|
||||
import { useRelationM2M } from '@/composables/use-relation-m2m';
|
||||
import { useRelationMultiple, RelationQueryMultiple, DisplayItem } from '@/composables/use-relation-multiple';
|
||||
import { parseFilter } from '@/utils/parse-filter';
|
||||
import { CollectionMeta, Filter } from '@directus/shared/types';
|
||||
import { Filter } from '@directus/shared/types';
|
||||
import { deepMap, getFieldsFromTemplate } from '@directus/shared/utils';
|
||||
import { render } from 'micromustache';
|
||||
import { computed, inject, ref, toRefs, watch } from 'vue';
|
||||
@@ -242,15 +250,6 @@ const { collection, field, primaryKey } = toRefs(props);
|
||||
const { relationInfo } = useRelationM2M(collection, field);
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const relatedCollection = computed(() => relationInfo.value?.relatedCollection.collection ?? '');
|
||||
const relatedPkField = computed(() => relationInfo.value?.relatedPrimaryKeyField.field ?? 'id');
|
||||
const relatedMeta = computed(() => relationInfo.value?.relatedCollection.meta ?? ({} as CollectionMeta));
|
||||
|
||||
const junctionCollection = computed(() => relationInfo.value?.junctionCollection.collection ?? '');
|
||||
const junctionPkField = computed(() => relationInfo.value?.junctionPrimaryKeyField.field ?? 'id');
|
||||
const junctionMeta = computed(() => relationInfo.value?.junctionCollection.meta ?? ({} as CollectionMeta));
|
||||
const junctionField = computed(() => relationInfo.value?.junctionField.field ?? '');
|
||||
|
||||
const value = computed({
|
||||
get: () => props.value,
|
||||
set: (val) => {
|
||||
@@ -262,9 +261,10 @@ const templateWithDefaults = computed(() => {
|
||||
if (!relationInfo.value) return null;
|
||||
|
||||
if (props.template) return props.template;
|
||||
if (junctionMeta.value.display_template) return junctionMeta.value.display_template;
|
||||
if (relationInfo.value.junctionCollection.meta?.display_template)
|
||||
return relationInfo.value.junctionCollection.meta.display_template;
|
||||
|
||||
let relatedDisplayTemplate = relatedMeta.value.display_template;
|
||||
let relatedDisplayTemplate = relationInfo.value.relatedCollection.meta?.display_template;
|
||||
if (relatedDisplayTemplate) {
|
||||
const regex = /({{.*?}})/g;
|
||||
const parts = relatedDisplayTemplate.split(regex).filter((p) => p);
|
||||
@@ -280,21 +280,23 @@ const templateWithDefaults = computed(() => {
|
||||
return relatedDisplayTemplate;
|
||||
}
|
||||
|
||||
return `{{${relationInfo.value.relation.field}.${relatedPkField.value}}}`;
|
||||
return `{{${relationInfo.value.relation.field}.${relationInfo.value.relatedPrimaryKeyField.field}}}`;
|
||||
});
|
||||
|
||||
const fields = computed(() => {
|
||||
if (!relationInfo.value) return [];
|
||||
let displayFields: string[] = [];
|
||||
|
||||
if (props.layout === LAYOUTS.TABLE) {
|
||||
displayFields = adjustFieldsForDisplays(props.fields, junctionCollection.value);
|
||||
displayFields = adjustFieldsForDisplays(props.fields, relationInfo.value.junctionCollection.collection);
|
||||
} else {
|
||||
displayFields = adjustFieldsForDisplays(
|
||||
getFieldsFromTemplate(templateWithDefaults.value),
|
||||
junctionCollection.value
|
||||
relationInfo.value.junctionCollection.collection
|
||||
);
|
||||
}
|
||||
|
||||
return addRelatedPrimaryKeyToFields(junctionCollection.value, displayFields);
|
||||
return addRelatedPrimaryKeyToFields(relationInfo.value.junctionCollection.collection, displayFields);
|
||||
});
|
||||
|
||||
const limit = ref(props.limit);
|
||||
@@ -320,7 +322,7 @@ const query = computed<RelationQueryMultiple>(() => {
|
||||
q.search = search.value;
|
||||
}
|
||||
if (sort.value) {
|
||||
q.sort = [`${sort.value.desc ? '-' : ''}${junctionField.value}.${sort.value.by}`];
|
||||
q.sort = [`${sort.value.desc ? '-' : ''}${relationInfo.value.junctionField.field}.${sort.value.by}`];
|
||||
}
|
||||
|
||||
return q;
|
||||
@@ -351,8 +353,11 @@ watch(
|
||||
() => {
|
||||
if (!relationInfo.value) {
|
||||
headers.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const junctionCollection = relationInfo.value.junctionCollection.collection;
|
||||
|
||||
const contentWidth: Record<string, number> = {};
|
||||
(displayItems.value ?? []).forEach((item: Record<string, any>) => {
|
||||
props.fields.forEach((key) => {
|
||||
@@ -367,7 +372,7 @@ watch(
|
||||
|
||||
headers.value = props.fields
|
||||
.map((key) => {
|
||||
const field = fieldsStore.getField(junctionCollection.value, key);
|
||||
const field = fieldsStore.getField(junctionCollection, key);
|
||||
|
||||
// when user has no permission to this field or junction collection
|
||||
if (!field) return null;
|
||||
@@ -422,7 +427,10 @@ function sortItems(items: DisplayItem[]) {
|
||||
const selectedPrimaryKeys = computed(() => {
|
||||
if (!relationInfo.value) return [];
|
||||
|
||||
return selected.value.map((item) => item[junctionField.value][relatedPkField.value]);
|
||||
const junctionField = relationInfo.value.junctionField.field;
|
||||
const relatedPkField = relationInfo.value.relatedPrimaryKeyField.field;
|
||||
|
||||
return selected.value.map((item) => item[junctionField][relatedPkField]);
|
||||
});
|
||||
|
||||
const editModalActive = ref(false);
|
||||
@@ -443,6 +451,10 @@ function createItem() {
|
||||
function editItem(item: DisplayItem) {
|
||||
if (!relationInfo.value) return;
|
||||
|
||||
const relatedPkField = relationInfo.value.relatedPrimaryKeyField.field;
|
||||
const junctionField = relationInfo.value.junctionField.field;
|
||||
const junctionPkField = relationInfo.value.junctionPrimaryKeyField.field;
|
||||
|
||||
newItem = false;
|
||||
editsAtStart.value = item;
|
||||
|
||||
@@ -452,8 +464,8 @@ function editItem(item: DisplayItem) {
|
||||
currentlyEditing.value = null;
|
||||
relatedPrimaryKey.value = null;
|
||||
} else {
|
||||
currentlyEditing.value = get(item, [junctionPkField.value], null);
|
||||
relatedPrimaryKey.value = get(item, [junctionPkField.value, relatedPkField.value], null);
|
||||
currentlyEditing.value = get(item, [junctionPkField], null);
|
||||
relatedPrimaryKey.value = get(item, [junctionField, relatedPkField], null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +516,7 @@ const customFilter = computed(() => {
|
||||
|
||||
if (!relationInfo.value || props.allowDuplicates) return filter;
|
||||
|
||||
const reverseRelation = `$FOLLOW(${junctionCollection.value},${junctionField.value})`;
|
||||
const reverseRelation = `$FOLLOW(${relationInfo.value.junctionCollection.collection},${relationInfo.value.junctionField.field})`;
|
||||
|
||||
const selectFilter: Filter = {
|
||||
[reverseRelation]: {
|
||||
@@ -518,7 +530,7 @@ const customFilter = computed(() => {
|
||||
|
||||
if (selectedPrimaryKeys.value.length > 0) {
|
||||
filter._and.push({
|
||||
[relatedPkField.value]: {
|
||||
[relationInfo.value.relatedPrimaryKeyField.field]: {
|
||||
_nin: selectedPrimaryKeys.value,
|
||||
},
|
||||
});
|
||||
@@ -531,8 +543,11 @@ const customFilter = computed(() => {
|
||||
|
||||
function getLinkForItem(item: DisplayItem) {
|
||||
if (relationInfo.value) {
|
||||
const primaryKey = get(item, [junctionField.value, relatedPkField.value]);
|
||||
return `/content/${relatedCollection.value}/${encodeURIComponent(primaryKey)}`;
|
||||
const primaryKey = get(item, [
|
||||
relationInfo.value.junctionField.field,
|
||||
relationInfo.value.relatedPrimaryKeyField.field,
|
||||
]);
|
||||
return `/content/${relationInfo.value.relatedCollection.collection}/${encodeURIComponent(primaryKey)}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -546,11 +561,13 @@ const createAllowed = computed(() => {
|
||||
if (admin) return true;
|
||||
|
||||
const hasJunctionPermissions = !!permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === junctionCollection.value
|
||||
(permission) =>
|
||||
permission.action === 'create' && permission.collection === relationInfo.value?.junctionCollection.collection
|
||||
);
|
||||
|
||||
const hasRelatedPermissions = !!permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === relatedCollection.value
|
||||
(permission) =>
|
||||
permission.action === 'create' && permission.collection === relationInfo.value?.relatedCollection.collection
|
||||
);
|
||||
|
||||
return hasJunctionPermissions && hasRelatedPermissions;
|
||||
@@ -561,7 +578,8 @@ const selectAllowed = computed(() => {
|
||||
if (admin) return true;
|
||||
|
||||
const hasJunctionPermissions = !!permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === junctionCollection.value
|
||||
(permission) =>
|
||||
permission.action === 'create' && permission.collection === relationInfo.value?.junctionCollection.collection
|
||||
);
|
||||
|
||||
return hasJunctionPermissions;
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
</div>
|
||||
|
||||
<div v-if="enableSearchFilter && (totalItemCount > 10 || search || searchFilter)" class="search">
|
||||
<search-input v-model="search" v-model:filter="searchFilter" :collection="relatedCollection" />
|
||||
<search-input
|
||||
v-model="search"
|
||||
v-model:filter="searchFilter"
|
||||
:collection="relationInfo.relatedCollection.collection"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-button
|
||||
@@ -51,7 +55,7 @@
|
||||
<template v-for="header in headers" :key="header.value" #[`item.${header.value}`]="{ item }">
|
||||
<render-template
|
||||
:title="header.value"
|
||||
:collection="relatedCollection"
|
||||
:collection="relationInfo.relatedCollection.collection"
|
||||
:item="item"
|
||||
:template="`{{${header.value}}}`"
|
||||
/>
|
||||
@@ -109,7 +113,11 @@
|
||||
@click="editItem(element)"
|
||||
>
|
||||
<v-icon v-if="allowDrag" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :collection="relatedCollection" :item="element" :template="templateWithDefaults" />
|
||||
<render-template
|
||||
:collection="relationInfo.relatedCollection.collection"
|
||||
:item="element"
|
||||
:template="templateWithDefaults"
|
||||
/>
|
||||
<div class="spacer" />
|
||||
|
||||
<router-link
|
||||
@@ -154,7 +162,7 @@
|
||||
<drawer-item
|
||||
:disabled="disabled"
|
||||
:active="currentlyEditing !== null"
|
||||
:collection="relatedCollection"
|
||||
:collection="relationInfo.relatedCollection.collection"
|
||||
:primary-key="currentlyEditing || '+'"
|
||||
:edits="editsAtStart"
|
||||
:circular-field="relationInfo.reverseJunctionField.field"
|
||||
@@ -165,7 +173,7 @@
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
v-model:active="selectModalActive"
|
||||
:collection="relatedCollection"
|
||||
:collection="relationInfo.relatedCollection.collection"
|
||||
:filter="customFilter"
|
||||
multiple
|
||||
@input="select"
|
||||
@@ -177,7 +185,7 @@
|
||||
import { useRelationO2M } from '@/composables/use-relation-o2m';
|
||||
import { useRelationMultiple, RelationQueryMultiple, DisplayItem } from '@/composables/use-relation-multiple';
|
||||
import { parseFilter } from '@/utils/parse-filter';
|
||||
import { CollectionMeta, Filter } from '@directus/shared/types';
|
||||
import { Filter } from '@directus/shared/types';
|
||||
import { deepMap, getFieldsFromTemplate } from '@directus/shared/utils';
|
||||
import { render } from 'micromustache';
|
||||
import { computed, inject, ref, toRefs, watch } from 'vue';
|
||||
@@ -237,10 +245,6 @@ const { collection, field, primaryKey } = toRefs(props);
|
||||
const { relationInfo } = useRelationO2M(collection, field);
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const relatedCollection = computed(() => relationInfo.value?.relatedCollection.collection ?? '');
|
||||
const relatedPkField = computed(() => relationInfo.value?.relatedPrimaryKeyField.field ?? 'id');
|
||||
const relatedMeta = computed(() => relationInfo.value?.relatedCollection.meta ?? ({} as CollectionMeta));
|
||||
|
||||
const value = computed({
|
||||
get: () => props.value,
|
||||
set: (val) => {
|
||||
@@ -249,18 +253,27 @@ const value = computed({
|
||||
});
|
||||
|
||||
const templateWithDefaults = computed(() => {
|
||||
return props.template || relatedMeta.value.display_template || `{{${relatedPkField.value}}}`;
|
||||
return (
|
||||
props.template ||
|
||||
relationInfo.value?.relatedCollection.meta?.display_template ||
|
||||
`{{${relationInfo.value?.relatedPrimaryKeyField.field}}}`
|
||||
);
|
||||
});
|
||||
|
||||
const fields = computed(() => {
|
||||
if (!relationInfo.value) return [];
|
||||
|
||||
let displayFields: string[] = [];
|
||||
if (props.layout === LAYOUTS.TABLE) {
|
||||
displayFields = adjustFieldsForDisplays(props.fields, relatedCollection.value);
|
||||
displayFields = adjustFieldsForDisplays(props.fields, relationInfo.value.relatedCollection.collection);
|
||||
} else {
|
||||
displayFields = adjustFieldsForDisplays(getFieldsFromTemplate(templateWithDefaults.value), relatedCollection.value);
|
||||
displayFields = adjustFieldsForDisplays(
|
||||
getFieldsFromTemplate(templateWithDefaults.value),
|
||||
relationInfo.value.relatedCollection.collection
|
||||
);
|
||||
}
|
||||
|
||||
return addRelatedPrimaryKeyToFields(relatedCollection.value, displayFields);
|
||||
return addRelatedPrimaryKeyToFields(relationInfo.value.relatedCollection.collection, displayFields);
|
||||
});
|
||||
|
||||
const limit = ref(props.limit);
|
||||
@@ -317,8 +330,11 @@ watch(
|
||||
() => {
|
||||
if (!relationInfo.value) {
|
||||
headers.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const relatedCollection = relationInfo.value.relatedCollection.collection;
|
||||
|
||||
const contentWidth: Record<string, number> = {};
|
||||
(displayItems.value ?? []).forEach((item: Record<string, any>) => {
|
||||
props.fields.forEach((key) => {
|
||||
@@ -333,7 +349,7 @@ watch(
|
||||
|
||||
headers.value = props.fields
|
||||
.map((key) => {
|
||||
const field = fieldsStore.getField(relatedCollection.value, key);
|
||||
const field = fieldsStore.getField(relatedCollection, key);
|
||||
|
||||
// when user has no permission to this field or junction collection
|
||||
if (!field) return null;
|
||||
@@ -388,7 +404,9 @@ function sortItems(items: DisplayItem[]) {
|
||||
const selectedPrimaryKeys = computed(() => {
|
||||
if (!relationInfo.value) return [];
|
||||
|
||||
return selected.value.map((item) => item[relatedPkField.value]);
|
||||
const relatedPkField = relationInfo.value.relatedPrimaryKeyField.field;
|
||||
|
||||
return selected.value.map((item) => item[relatedPkField]);
|
||||
});
|
||||
|
||||
const currentlyEditing = ref<string | null>(null);
|
||||
@@ -405,13 +423,15 @@ function createItem() {
|
||||
function editItem(item: DisplayItem) {
|
||||
if (!relationInfo.value) return;
|
||||
|
||||
const relatedPkField = relationInfo.value.relatedPrimaryKeyField.field;
|
||||
|
||||
newItem = false;
|
||||
editsAtStart.value = { [relatedPkField.value]: item[relatedPkField.value] };
|
||||
editsAtStart.value = { [relatedPkField]: item[relatedPkField] };
|
||||
|
||||
if (item?.$type === 'created' && !isItemSelected(item)) {
|
||||
currentlyEditing.value = '+';
|
||||
} else {
|
||||
currentlyEditing.value = item[relatedPkField.value];
|
||||
currentlyEditing.value = item[relatedPkField];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,7 +499,7 @@ const customFilter = computed(() => {
|
||||
|
||||
if (selectedPrimaryKeys.value.length > 0) {
|
||||
filter._and.push({
|
||||
[relatedPkField.value]: {
|
||||
[relationInfo.value.relatedPrimaryKeyField.field]: {
|
||||
_nin: selectedPrimaryKeys.value,
|
||||
},
|
||||
});
|
||||
@@ -492,8 +512,8 @@ const customFilter = computed(() => {
|
||||
|
||||
function getLinkForItem(item: DisplayItem) {
|
||||
if (relationInfo.value) {
|
||||
const primaryKey = get(item, relatedPkField.value);
|
||||
return `/content/${relatedCollection.value}/${encodeURIComponent(primaryKey)}`;
|
||||
const primaryKey = get(item, relationInfo.value.relatedPrimaryKeyField.field);
|
||||
return `/content/${relationInfo.value.relatedCollection.collection}/${encodeURIComponent(primaryKey)}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -507,7 +527,8 @@ const createAllowed = computed(() => {
|
||||
if (admin) return true;
|
||||
|
||||
return !!permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === relatedCollection.value
|
||||
(permission) =>
|
||||
permission.action === 'create' && permission.collection === relationInfo.value?.relatedCollection.collection
|
||||
);
|
||||
});
|
||||
|
||||
@@ -516,7 +537,8 @@ const updateAllowed = computed(() => {
|
||||
if (admin) return true;
|
||||
|
||||
return !!permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'update' && permission.collection === relatedCollection.value
|
||||
(permission) =>
|
||||
permission.action === 'update' && permission.collection === relationInfo.value?.relatedCollection.collection
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user