diff --git a/src/components/v-table/table-row/table-row.vue b/src/components/v-table/table-row/table-row.vue
index f9be6670c8..e017e86a9f 100644
--- a/src/components/v-table/table-row/table-row.vue
+++ b/src/components/v-table/table-row/table-row.vue
@@ -15,7 +15,7 @@
- {{ item[header.value] }}
+ {{ get(item, header.value) }}
|
|
@@ -27,6 +27,7 @@
+
+
diff --git a/src/displays/file/index.ts b/src/displays/file/index.ts
new file mode 100644
index 0000000000..44a25fc2fd
--- /dev/null
+++ b/src/displays/file/index.ts
@@ -0,0 +1,12 @@
+import { defineDisplay } from '@/displays/define';
+import DisplayFile from './file.vue';
+
+export default defineDisplay(({ i18n }) => ({
+ id: 'file',
+ name: i18n.t('file'),
+ types: ['file'],
+ icon: 'insert_photo',
+ handler: DisplayFile,
+ options: [],
+ fields: ['data', 'type', 'title'],
+}));
diff --git a/src/displays/image/image.vue b/src/displays/image/image.vue
index e9efa5eaed..22d8dd41d5 100644
--- a/src/displays/image/image.vue
+++ b/src/displays/image/image.vue
@@ -1,13 +1,15 @@
- --
+
+
+
diff --git a/src/interfaces/many-to-one/many-to-one.vue b/src/interfaces/many-to-one/many-to-one.vue
index 829fe13f90..4515bcbad5 100644
--- a/src/interfaces/many-to-one/many-to-one.vue
+++ b/src/interfaces/many-to-one/many-to-one.vue
@@ -71,7 +71,11 @@
@click="setCurrent(item)"
>
-
+
@@ -185,6 +189,7 @@ export default defineComponent({
edits,
stageEdits,
editModalActive,
+ relatedPrimaryKeyField,
};
function useCurrent() {
diff --git a/src/interfaces/one-to-many/one-to-many.vue b/src/interfaces/one-to-many/one-to-many.vue
index 3df79901cb..7a0cf04ff9 100644
--- a/src/interfaces/one-to-many/one-to-many.vue
+++ b/src/interfaces/one-to-many/one-to-many.vue
@@ -239,7 +239,7 @@ export default defineComponent({
const pkField = relatedPrimaryKeyField.value.field;
- const updatedItems = items.value
+ const itemsWithChangesApplied = items.value
.map((item: any) => {
const changeForThisItem = changes.find((change) => change[pkField] === item[pkField]);
@@ -265,7 +265,7 @@ export default defineComponent({
const selectedPrimaryKeys = changes
.filter((change) => typeof change === 'string' || typeof change === 'number')
.filter((primaryKey) => {
- const isAlsoUpdate = updatedItems.some((update) => update[pkField] === primaryKey);
+ const isAlsoUpdate = itemsWithChangesApplied.some((update) => update[pkField] === primaryKey);
return isAlsoUpdate === false;
});
@@ -297,7 +297,7 @@ export default defineComponent({
}
}
- items.value = [...updatedItems, ...newlyAddedItems, ...selectedItems];
+ items.value = [...itemsWithChangesApplied, ...newlyAddedItems, ...selectedItems];
loading.value = false;
}
}
diff --git a/src/lang/en-US/index.json b/src/lang/en-US/index.json
index 292d4434cf..d963492a17 100644
--- a/src/lang/en-US/index.json
+++ b/src/lang/en-US/index.json
@@ -81,6 +81,8 @@
"settings_update_success": "Settings updated",
"settings_update_failed": "Updating settings failed",
+ "title": "Title",
+
"revision_delta_created": "Created",
"revision_delta_created_externally": "Created Externally",
"revision_delta_updated": "Updated {count} Fields",
diff --git a/src/stores/relations/relations.ts b/src/stores/relations/relations.ts
index cee2b2bc97..9efb0e6cb1 100644
--- a/src/stores/relations/relations.ts
+++ b/src/stores/relations/relations.ts
@@ -56,9 +56,29 @@ export const useRelationsStore = createStore({
] as Relation[];
}
- return this.getRelationsForCollection(collection).filter((relation: Relation) => {
+ const relations = this.getRelationsForCollection(collection).filter((relation: Relation) => {
return relation.field_many === field || relation.field_one === field;
});
+
+ if (relations.length > 0) {
+ const isM2M = relations[0].junction_field !== null;
+
+ // If the relation matching the field has a junction field, it's a m2m. In that case,
+ // we also want to return the secondary relationship (from the jt to the related)
+ // so any ui elements (interfaces) can utilize the full relationship
+ if (isM2M) {
+ const secondaryRelation = this.state.relations.find((relation) => {
+ return (
+ relation.collection_many === relations[0].collection_many &&
+ relation.field_many === relations[0].junction_field
+ );
+ });
+
+ if (secondaryRelation) relations.push(secondaryRelation);
+ }
+ }
+
+ return relations;
},
},
});
diff --git a/src/utils/adjust-fields-for-displays/adjust-fields-for-displays.ts b/src/utils/adjust-fields-for-displays/adjust-fields-for-displays.ts
index ffc7b33b84..d7520325f2 100644
--- a/src/utils/adjust-fields-for-displays/adjust-fields-for-displays.ts
+++ b/src/utils/adjust-fields-for-displays/adjust-fields-for-displays.ts
@@ -14,6 +14,7 @@ export default function adjustFieldsForDisplays(fields: readonly string[], paren
const display = displays.find((d) => d.id === field.display);
+ if (!display) return fieldKey;
if (!display?.fields) return fieldKey;
if (Array.isArray(display.fields)) {
@@ -23,8 +24,8 @@ export default function adjustFieldsForDisplays(fields: readonly string[], paren
if (typeof display.fields === 'function') {
return display
.fields(field.display_options, {
- collection: parentCollection,
- field: fieldKey,
+ collection: field.collection,
+ field: field.field,
type: field.type,
})
.map((relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`);
diff --git a/src/views/private/components/modal-detail/modal-detail.vue b/src/views/private/components/modal-detail/modal-detail.vue
index 37ea95efbb..0bb987dea7 100644
--- a/src/views/private/components/modal-detail/modal-detail.vue
+++ b/src/views/private/components/modal-detail/modal-detail.vue
@@ -8,6 +8,18 @@
v-model="_edits"
/>
+
+ {{ junctionFieldInfo.name }}
+
+
+
{{ $t('cancel') }}
{{ $t('save') }}
@@ -20,7 +32,10 @@ import { defineComponent, ref, computed, PropType, watch, toRefs } from '@vue/co
import api from '@/api';
import useProjectsStore from '@/stores/projects';
import useCollection from '@/composables/use-collection';
+import useFieldsStore from '@/stores/fields';
import i18n from '@/lang';
+import useRelationsStore from '@/stores/relations';
+import { Relation } from '@/stores/relations/types';
export default defineComponent({
model: {
@@ -43,11 +58,25 @@ export default defineComponent({
type: Object as PropType>,
default: undefined,
},
+ junctionField: {
+ type: String,
+ default: null,
+ },
+ // There's an interesting case where the main form can be a newly created item ('+'), while
+ // it has a pre-selected related item it needs to alter. In that case, we have to fetch the
+ // related data anyway.
+ relatedPrimaryKey: {
+ type: [String, Number],
+ default: '+',
+ },
},
setup(props, { emit }) {
const projectsStore = useProjectsStore();
+ const fieldsStore = useFieldsStore();
+ const relationsStore = useRelationsStore();
const { _active } = useActiveState();
+ const { junctionFieldInfo, junctionRelatedCollection, setJunctionEdits } = useJunction();
const { _edits, loading, error, item } = useItem();
const { save, cancel } = useActions();
@@ -63,7 +92,19 @@ export default defineComponent({
return i18n.t('editing_in', { collection: collectionInfo.value?.name });
});
- return { _active, _edits, loading, error, item, save, cancel, title };
+ return {
+ _active,
+ _edits,
+ loading,
+ error,
+ item,
+ save,
+ cancel,
+ title,
+ junctionFieldInfo,
+ junctionRelatedCollection,
+ setJunctionEdits,
+ };
function useActiveState() {
const localActive = ref(false);
@@ -109,6 +150,7 @@ export default defineComponent({
(isActive) => {
if (isActive === true) {
if (props.primaryKey !== '+') fetchItem();
+ if (props.relatedPrimaryKey !== '+') fetchRelatedItem();
} else {
loading.value = false;
error.value = null;
@@ -129,8 +171,14 @@ export default defineComponent({
? `/${currentProjectKey}/${props.collection.substring(9)}/${props.primaryKey}`
: `/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`;
+ let fields = '*';
+
+ if (props.junctionField) {
+ fields = `*,${props.junctionField}.*`;
+ }
+
try {
- const response = await api.get(endpoint);
+ const response = await api.get(endpoint, { params: { fields } });
item.value = response.data.data;
} catch (err) {
@@ -139,6 +187,64 @@ export default defineComponent({
loading.value = false;
}
}
+
+ async function fetchRelatedItem() {
+ const { currentProjectKey } = projectsStore.state;
+
+ loading.value = true;
+
+ const collection = junctionRelatedCollection.value;
+
+ const endpoint = collection.startsWith('directus_')
+ ? `/${currentProjectKey}/${collection.substring(9)}/${props.relatedPrimaryKey}`
+ : `/${currentProjectKey}/items/${collection}/${props.relatedPrimaryKey}`;
+
+ try {
+ const response = await api.get(endpoint);
+
+ item.value = {
+ ...(item.value || {}),
+ [junctionFieldInfo.value.field]: response.data.data,
+ };
+ } catch (err) {
+ error.value = err;
+ } finally {
+ loading.value = false;
+ }
+ }
+ }
+
+ function useJunction() {
+ const junctionFieldInfo = computed(() => {
+ if (!props.junctionField) return null;
+
+ return fieldsStore.getField(props.collection, props.junctionField);
+ });
+
+ const junctionRelatedCollection = computed(() => {
+ if (!props.junctionField) return null;
+
+ // If this is a m2m (likely), there will be 2 relations associated with this field
+ const relations = relationsStore.getRelationsForField(props.collection, props.junctionField);
+ return (
+ relations.find((relation: Relation) => {
+ return (
+ relation.collection_many === props.collection && relation.field_many === props.junctionField
+ );
+ })?.collection_one || null
+ );
+ });
+
+ return { junctionFieldInfo, junctionRelatedCollection, setJunctionEdits };
+
+ function setJunctionEdits(edits: any) {
+ if (!props.junctionField) return;
+
+ _edits.value = {
+ ..._edits.value,
+ [props.junctionField]: edits,
+ };
+ }
}
function useActions() {
@@ -158,3 +264,9 @@ export default defineComponent({
},
});
+
+