diff --git a/app/src/interfaces/many-to-many/many-to-many.vue b/app/src/interfaces/many-to-many/many-to-many.vue index 6d5b3743af..98111cefdd 100644 --- a/app/src/interfaces/many-to-many/many-to-many.vue +++ b/app/src/interfaces/many-to-many/many-to-many.vue @@ -21,35 +21,32 @@ :interface="header.field.interface" :interface-options="header.field.interfaceOptions" :type="header.field.type" - :collection="relationFields.junctionCollection" + :collection="relationInfo.junctionCollection" :field="header.field.field" />
- {{ $t('create_new') }} + {{ $t('create_new') }} {{ $t('add_existing') }}
+
{{ JSON.stringify(value, null, 4) }}
+ )[] | null>, @@ -19,31 +19,32 @@ export default function useActions( } function getNewSelectedItems() { - const { junctionRelation } = relation.value; + const { junctionField } = relation.value; - if (value.value === null || junctionRelation === null) return []; + if (value.value === null || junctionField === null) return []; return value.value.filter( - (item) => typeof item === 'object' && junctionRelation in item && typeof item[junctionRelation] !== 'object' + (item) => typeof item === 'object' && junctionField in item && typeof item[junctionField] !== 'object' ) as Record[]; } function getNewItems() { - const { junctionRelation, relationPkField } = relation.value; + const { junctionField, relationPkField } = relation.value; - if (value.value === null || junctionRelation === null) return []; + if (value.value === null || junctionField === null) return []; return value.value.filter( - (item) => has(item, junctionRelation) && has(item, [junctionRelation, relationPkField]) === false + (item) => + typeof get(item, junctionField) === 'object' && has(item, [junctionField, relationPkField]) === false ) as Record[]; } function getUpdatedItems() { - const { junctionRelation, relationPkField } = relation.value; + const { junctionField, relationPkField } = relation.value; - if (value.value === null || junctionRelation === null) return []; + if (value.value === null || junctionField === null) return []; - return value.value.filter((item) => has(item, [junctionRelation, relationPkField])) as Record[]; + return value.value.filter((item) => has(item, [junctionField, relationPkField])) as Record[]; } function getExistingItems() { @@ -57,112 +58,69 @@ export default function useActions( if (value.value === null) return []; - return value.value - .map((item) => { - if (typeof item === 'object') { - if (junctionPkField in item) return item[junctionPkField]; - } else { - return item; - } - }) - .filter((i) => i); + return value.value.reduce((acc: any[], item) => { + const deepId = get(item, [junctionPkField]) as number | string | undefined; + + if (['string', 'number'].includes(typeof item)) acc.push(item); + else if (deepId !== undefined) acc.push(deepId); + return acc; + }, []) as (string | number)[]; } - function getRelatedPrimaryKeys(): (string | number)[] { + function getRelatedPrimaryKeys() { if (value.value === null) return []; - const { junctionRelation, relationPkField } = relation.value; - return value.value - .map((junctionItem) => { - if ( - typeof junctionItem !== 'object' || - junctionRelation === null || - junctionRelation in junctionItem === false - ) - return undefined; - const item = junctionItem[junctionRelation]; + const { junctionField, relationPkField } = relation.value; + + return value.value.reduce((acc: any[], item) => { + const relatedId = get(item, junctionField) as number | string | undefined; + const deepRelatedId = get(item, [junctionField, relationPkField]) as number | string | undefined; + + if (relatedId !== undefined) acc.push(relatedId); + else if (deepRelatedId !== undefined) acc.push(deepRelatedId); + return acc; + }, []) as (string | number)[]; + } + + function deleteItem(deletingItem: Record) { + if (value.value === null) return; + const { junctionField, relationPkField, junctionPkField } = relation.value; + + const junctionId = get(deletingItem, junctionPkField) as number | string | undefined; + const relatedId = get(deletingItem, [junctionField, relationPkField]) as number | string | undefined; + + console.log(junctionId, relatedId); + + const newValue = value.value.filter((item) => { + if (junctionId !== undefined) { if (typeof item === 'object') { - if (junctionRelation in item) return item[relationPkField]; + return get(item, [junctionPkField]) !== junctionId; } else { - return item; + return item !== junctionId; } - }) - .filter((i) => i); - } - - function deleteItem(item: Record, items: Record[]) { - if (value.value === null) return; - const { junctionRelation, relationPkField } = relation.value; - - const id = item[relationPkField] as number | string | undefined; - - if (id !== undefined) return deleteItemWithId(id, items); - if (junctionRelation === null) return; - - const newVal = value.value.filter((junctionItem) => { - if (typeof junctionItem !== 'object' || junctionRelation in junctionItem === false) return true; - return junctionItem[junctionRelation] !== item; - }); - - if (newVal.length === 0) emit(null); - else emit(newVal); - } - - function deleteItemWithId(id: string | number, items: Record[]) { - if (value.value === null) return; - const { junctionRelation, relationPkField, junctionPkField } = relation.value; - - const junctionItem = items.find( - (item) => - junctionRelation in item && - relationPkField in item[junctionRelation] && - item[junctionRelation][relationPkField] === id - ); - - if (junctionItem === undefined) return; - - // If it is a newly selected Item - if (junctionPkField in junctionItem === false) { - const newVal = value.value.filter((item) => { - if (typeof item === 'object' && junctionRelation in item) { - const jItem = item[junctionRelation]; - return typeof jItem === 'object' ? jItem[relationPkField] !== id : jItem !== id; - } - return true; - }); - - if (newVal.length === 0) emit(null); - else emit(newVal); - return; - } - - // If it is an already existing item - const newVal = value.value.filter((item) => { - if (typeof item === 'object' && junctionPkField in item) { - return junctionItem[junctionPkField] !== item[junctionPkField]; - } else { - return junctionItem[junctionPkField] !== item; } - }); - if (newVal.length === 0) emit(null); - else emit(newVal); + if (relatedId !== undefined) { + const itemRelatedId = get(item, [junctionField, relationPkField]); + if (['string', 'number'].includes(typeof itemRelatedId)) { + return itemRelatedId !== relatedId; + } + + const junctionFieldId = get(item, [junctionField]); + if (['string', 'number'].includes(typeof junctionFieldId)) { + return junctionFieldId !== relatedId; + } + } + + return isEqual(item, deletingItem) === false; + }); + emit(newValue); } function getJunctionFromRelatedId(id: string | number, items: Record[]) { - const { relationPkField, junctionRelation } = relation.value; + const { relationPkField, junctionField } = relation.value; - return ( - items.find((item) => { - return ( - typeof item === 'object' && - junctionRelation in item && - typeof item[junctionRelation] === 'object' && - relationPkField in item[junctionRelation] && - item[junctionRelation][relationPkField] === id - ); - }) || null - ); + return items.find((item) => get(item, [junctionField, relationPkField]) === id) || null; } return { @@ -175,6 +133,5 @@ export default function useActions( getRelatedPrimaryKeys, getJunctionFromRelatedId, deleteItem, - deleteItemWithId, }; } diff --git a/app/src/interfaces/many-to-many/use-edit.ts b/app/src/interfaces/many-to-many/use-edit.ts index 1f0bbeed50..bdf79148c0 100644 --- a/app/src/interfaces/many-to-many/use-edit.ts +++ b/app/src/interfaces/many-to-many/use-edit.ts @@ -7,8 +7,7 @@ export default function useEdit( relation: Ref, emit: (newVal: any[] | null) => void ) { - // Primary key of the item we're currently editing. If null, the edit modal should be - // closed + const editModalActive = ref(false); const currentlyEditing = ref(null); const relatedPrimaryKey = ref(null); @@ -16,15 +15,16 @@ export default function useEdit( const editsAtStart = ref>({}); function editItem(item: any) { - const { relationPkField, junctionRelation, junctionPkField } = relation.value; + const { relationPkField, junctionField, junctionPkField } = relation.value; + editModalActive.value = true; editsAtStart.value = item; - relatedPrimaryKey.value = get(item, [junctionRelation, relationPkField], null); currentlyEditing.value = get(item, [junctionPkField], null); + relatedPrimaryKey.value = get(item, [junctionField, relationPkField], null); } function stageEdits(edits: any) { - const { relationPkField, junctionRelation, junctionPkField } = relation.value; + const { relationPkField, junctionField, junctionPkField } = relation.value; const newValue = (value.value || []).map((item) => { if (currentlyEditing.value !== null) { @@ -40,8 +40,8 @@ export default function useEdit( if (relatedPrimaryKey.value != null) { const id = relatedPrimaryKey.value; - if (get(item, [junctionRelation], null) === id) return edits; - if (get(item, [junctionRelation, relationPkField], null) === id) return edits; + if (get(item, [junctionField], null) === id) return edits; + if (get(item, [junctionField, relationPkField], null) === id) return edits; } if (isEqual(editsAtStart.value, item)) { @@ -57,13 +57,16 @@ export default function useEdit( if (newValue.length === 0) emit(null); else emit(newValue); + + cancelEdit(); } function cancelEdit() { + editModalActive.value = false; editsAtStart.value = {}; currentlyEditing.value = null; relatedPrimaryKey.value = null; } - return { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit, relatedPrimaryKey }; + return { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit, relatedPrimaryKey, editModalActive }; } diff --git a/app/src/interfaces/many-to-many/use-preview.ts b/app/src/interfaces/many-to-many/use-preview.ts index 9f88d23768..140a26046d 100644 --- a/app/src/interfaces/many-to-many/use-preview.ts +++ b/app/src/interfaces/many-to-many/use-preview.ts @@ -4,7 +4,7 @@ import { RelationInfo } from './use-relation'; import { useFieldsStore } from '@/stores/'; import { Field, Collection } from '@/types'; import api from '@/api'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, get } from 'lodash'; export default function usePreview( value: Ref<(string | number | Record)[] | null>, @@ -25,14 +25,13 @@ export default function usePreview( const error = ref(null); function getRelatedFields(fields: string[]) { - const { junctionRelation } = relation.value; + const { junctionField } = relation.value; - return fields - .map((field) => { - const sections = field.split('.'); - if (junctionRelation === sections[0] && sections.length === 2) return sections[1]; - }) - .filter((i) => i); + return fields.reduce((acc: string[], field) => { + const sections = field.split('.'); + if (junctionField === sections[0] && sections.length === 2) acc.push(sections[1]); + return acc; + }, []); } function getJunctionFields() { @@ -48,12 +47,17 @@ export default function usePreview( } loading.value = true; - const { junctionRelation, relationPkField, junctionPkField } = relation.value; - if (junctionRelation === null) return; + const { junctionField, relationPkField, junctionPkField, relationCollection } = relation.value; + if (junctionField === null) return; // Load the junction items so we have access to the id's in the related collection const junctionItems = await loadRelatedIds(); - const relatedPrimaryKeys = junctionItems.map((junction) => junction[junctionRelation]); + + const relatedPrimaryKeys = junctionItems.reduce((acc, junction) => { + const id = get(junction, junctionField); + if (id !== null) acc.push(id); + return acc; + }, []) as (string | number)[]; const filteredFields = [...(fields.value.length > 0 ? getRelatedFields(fields.value) : getDefaultFields())]; @@ -63,35 +67,36 @@ export default function usePreview( let responseData: Record[] = []; if (relatedPrimaryKeys.length > 0) { - const endpoint = relation.value.relationCollection.startsWith('directus_') - ? `/${relation.value.relationCollection.substring(9)}` - : `/items/${relation.value.relationCollection}`; - - const response = await api.get(endpoint, { - params: { - fields: filteredFields, - [`filter[${relationPkField}][_in]`]: relatedPrimaryKeys.join(','), - }, - }); - responseData = response?.data.data as Record[]; + responseData = await request( + relationCollection, + filteredFields, + relationPkField, + relatedPrimaryKeys + ); } + console.log('responseData1', responseData); // Insert the related items into the junction items - const existingItems = responseData.map((data) => { - const id = data[relationPkField]; - const junction = junctionItems.find((junction) => junction[junctionRelation] === id); - if (junction === undefined) return; + responseData = responseData.map((data) => { + const id = get(data, relationPkField); + const junction = junctionItems.find((junction) => junction[junctionField] === id); + + if (junction === undefined || id === undefined) return; const newJunction = cloneDeep(junction); - newJunction[junctionRelation] = data; + newJunction[junctionField] = data; return newJunction; }) as Record[]; const updatedItems = getUpdatedItems(); const newItems = getNewItems(); + console.log('updatedItems', updatedItems); + console.log('newItems', newItems); + console.log('responseData2', responseData); + // Replace existing items with it's updated counterparts - const newVal = existingItems + responseData = responseData .map((item) => { const updatedItem = updatedItems.find( (updated) => updated[junctionPkField] === item[junctionPkField] @@ -100,7 +105,9 @@ export default function usePreview( return item; }) .concat(...newItems); - items.value = newVal; + + console.log('responseData3', responseData); + items.value = responseData; } catch (err) { error.value = err; } finally { @@ -111,33 +118,24 @@ export default function usePreview( ); async function loadRelatedIds() { - const { junctionPkField, junctionRelation, relationPkField } = relation.value; + const { junctionPkField, junctionField, relationPkField, junctionCollection } = relation.value; try { let data: Record[] = []; const primaryKeys = getPrimaryKeys(); + console.log('PKS', primaryKeys); if (primaryKeys.length > 0) { const filteredFields = getJunctionFields(); if (filteredFields.includes(junctionPkField) === false) filteredFields.push(junctionPkField); - if (filteredFields.includes(junctionRelation) === false) filteredFields.push(junctionRelation); + if (filteredFields.includes(junctionField) === false) filteredFields.push(junctionField); - const endpoint = relation.value.junctionCollection.startsWith('directus_') - ? `/${relation.value.junctionCollection.substring(9)}` - : `/items/${relation.value.junctionCollection}`; - - const response = await api.get(endpoint, { - params: { - fields: filteredFields, - [`filter[${junctionPkField}][_in]`]: getPrimaryKeys().join(','), - }, - }); - data = response?.data.data as Record[]; + data = await request(junctionCollection, filteredFields, junctionPkField, primaryKeys); } const updatedItems = getUpdatedItems().map((item) => ({ - [junctionRelation]: item[junctionRelation][relationPkField], + [junctionField]: item[junctionField][relationPkField], })); // Add all items that already had the id of it's related item @@ -148,16 +146,35 @@ export default function usePreview( return []; } + async function request( + collection: string, + fields: string[] | null, + filteredField: string, + primaryKeys: (string | number)[] | null + ) { + if (fields === null || fields.length === 0 || primaryKeys === null || primaryKeys.length === 0) return []; + + const endpoint = collection.startsWith('directus_') ? `/${collection.substring(9)}` : `/items/${collection}`; + + const response = await api.get(endpoint, { + params: { + fields: fields, + [`filter[${filteredField}][_in]`]: primaryKeys.join(','), + }, + }); + return response?.data.data as Record[]; + } + // Seeing we don't care about saving those tableHeaders, we can reset it whenever the // fields prop changes (most likely when we're navigating to a different o2m context) watch( () => fields.value, () => { - const { junctionRelation, junctionCollection } = relation.value; + const { junctionField, junctionCollection } = relation.value; tableHeaders.value = (fields.value.length > 0 ? fields.value - : getDefaultFields().map((field) => `${junctionRelation}.${field}`) + : getDefaultFields().map((field) => `${junctionField}.${field}`) ) .map((fieldKey) => { let field = fieldsStore.getField(junctionCollection, fieldKey); diff --git a/app/src/interfaces/many-to-many/use-relation.ts b/app/src/interfaces/many-to-many/use-relation.ts index 9b16c4b02d..c2f679aee2 100644 --- a/app/src/interfaces/many-to-many/use-relation.ts +++ b/app/src/interfaces/many-to-many/use-relation.ts @@ -6,7 +6,7 @@ import { Relation } from '@/types'; export type RelationInfo = { junctionPkField: string; relationPkField: string; - junctionRelation: string; + junctionField: string; junctionCollection: string; relationCollection: string; }; @@ -38,11 +38,11 @@ export default function useRelation(collection: Ref, field: Ref) const { primaryKeyField: junctionPrimaryKeyField } = useCollection(junctionCollection.value.collection); const { primaryKeyField: relationPrimaryKeyField } = useCollection(relationCollection.value.collection); - const relationFields = computed(() => { + const relationInfo = computed(() => { return { junctionPkField: junctionPrimaryKeyField.value.field, relationPkField: relationPrimaryKeyField.value.field, - junctionRelation: junction.value.junction_field as string, + junctionField: junction.value.junction_field as string, junctionCollection: junctionCollection.value.collection, relationCollection: relationCollection.value.collection, } as RelationInfo; @@ -53,7 +53,7 @@ export default function useRelation(collection: Ref, field: Ref) junctionCollection, relation, relationCollection, - relationFields, + relationInfo, junctionPrimaryKeyField, relationPrimaryKeyField, }; diff --git a/app/src/interfaces/many-to-many/use-selection.ts b/app/src/interfaces/many-to-many/use-selection.ts index 1eff19c858..19f2330dd3 100644 --- a/app/src/interfaces/many-to-many/use-selection.ts +++ b/app/src/interfaces/many-to-many/use-selection.ts @@ -14,11 +14,13 @@ export default function useSelection( const selectedPrimaryKeys = computed(() => { if (items.value === null) return []; - const { relationPkField, junctionRelation } = relation.value; + const { relationPkField, junctionField } = relation.value; - const selectedKeys: (number | string)[] = items.value - .map((currentItem) => get(currentItem, [junctionRelation, relationPkField])) - .filter((i) => i); + const selectedKeys = items.value.reduce((acc, current) => { + const key = get(current, [junctionField, relationPkField]); + if (key !== undefined) acc.push(key); + return acc; + }, []) as (number | string)[]; return selectedKeys; }); @@ -40,7 +42,12 @@ export default function useSelection( }); function stageSelection(newSelection: (number | string)[]) { - const selection = newSelection.filter((item) => selectedPrimaryKeys.value.includes(item) === false); + const { junctionField } = relation.value; + + const selection = newSelection.reduce((acc, item) => { + if (selectedPrimaryKeys.value.includes(item) === false) acc.push({ [junctionField]: item }); + return acc; + }, new Array()); const newVal = [...selection, ...(value.value || [])]; if (newVal.length === 0) emit(null);