diff --git a/app/src/displays/related-values/index.ts b/app/src/displays/related-values/index.ts index b9648f1031..8d2c5ab88f 100644 --- a/app/src/displays/related-values/index.ts +++ b/app/src/displays/related-values/index.ts @@ -4,6 +4,9 @@ import { getFieldsFromTemplate } from '@directus/shared/utils'; import getRelatedCollection from '@/utils/get-related-collection'; import DisplayRelatedValues from './related-values.vue'; import { useFieldsStore } from '@/stores'; +import { getDisplay } from '@/displays'; +import { get, set } from 'lodash'; +import { renderPlainStringTemplate } from '@/utils/render-string-template'; type Options = { template: string; @@ -43,6 +46,54 @@ export default defineDisplay({ }, ]; }, + handler: async (value, options, { collection, field }) => { + if (!field || !collection) return value; + + const relatedCollections = getRelatedCollection(collection, field.field); + + if (!relatedCollections) return value; + + const fieldsStore = useFieldsStore(); + + const fieldKeys = getFieldsFromTemplate(options.template); + + const fields = fieldKeys.map((fieldKey) => { + return { + key: fieldKey, + field: fieldsStore.getField( + relatedCollections.junctionCollection ?? relatedCollections.relatedCollection, + fieldKey + ), + }; + }); + + const stringValues: Record = {}; + + for (const { key, field } of fields) { + const fieldValue = get(value, key); + + if (fieldValue === null || fieldValue === undefined) continue; + + if (!field?.meta?.display) { + set(stringValues, key, fieldValue); + continue; + } + + const display = getDisplay(field.meta.display); + + const stringValue = display?.handler + ? await display.handler(fieldValue, field?.meta?.display_options ?? {}, { + interfaceOptions: field?.meta?.options ?? {}, + field: field ?? undefined, + collection: collection, + }) + : fieldValue; + + set(stringValues, key, stringValue); + } + + return renderPlainStringTemplate(options.template, stringValues); + }, types: ['alias', 'string', 'uuid', 'integer', 'bigInteger', 'json'], localTypes: ['m2m', 'm2o', 'o2m', 'translations', 'm2a', 'file', 'files'], fields: (options: Options | null, { field, collection }) => { diff --git a/app/src/displays/translations/index.ts b/app/src/displays/translations/index.ts index 067106137a..9597a236dd 100644 --- a/app/src/displays/translations/index.ts +++ b/app/src/displays/translations/index.ts @@ -1,8 +1,12 @@ -import { defineDisplay, getFieldsFromTemplate } from '@directus/shared/utils'; -import DisplayTranslations from './translations.vue'; -import { useFieldsStore } from '@/stores'; -import { useRelationsStore } from '@/stores'; +import { getDisplay } from '@/displays'; +import { useFieldsStore, useRelationsStore } from '@/stores'; import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays'; +import { getRelatedCollection } from '@/utils/get-related-collection'; +import { renderPlainStringTemplate } from '@/utils/render-string-template'; +import { defineDisplay, getFieldsFromTemplate } from '@directus/shared/utils'; +import { get, set } from 'lodash'; +import DisplayTranslations from './translations.vue'; +import { i18n } from '@/lang'; type Options = { template: string; @@ -15,6 +19,84 @@ export default defineDisplay({ description: '$t:displays.translations.description', icon: 'translate', component: DisplayTranslations, + handler: async (values, options, { collection, field }) => { + if (!field || !collection || !Array.isArray(values)) return values; + + const relatedCollections = getRelatedCollection(collection, field.field); + + const fieldsStore = useFieldsStore(); + const relationsStore = useRelationsStore(); + + const relations = relationsStore.getRelationsForField(collection, field.field); + + const junction = relations.find( + (relation) => relation.related_collection === collection && relation.meta?.one_field === field.field + ); + + if (!junction) return values; + + const relation = relations.find( + (relation) => relation.collection === junction.collection && relation.field === junction.meta?.junction_field + ); + + const primaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(relatedCollections.relatedCollection); + + if (!relatedCollections || !primaryKeyField || !relation?.related_collection) return values; + + const relatedPrimaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(relation.related_collection); + + if (!relatedPrimaryKeyField) return values; + + const value = + values.find((translatedItem: Record) => { + const lang = translatedItem[relation!.field][relatedPrimaryKeyField.field]; + + // Default to first item if lang can't be found + if (!lang) return true; + + if (options.userLanguage) { + return lang === i18n.global.locale.value; + } + + return lang === options.defaultLanguage; + }) ?? values[0]; + + const fieldKeys = getFieldsFromTemplate(options.template); + + const fields = fieldKeys.map((fieldKey) => { + return { + key: fieldKey, + field: fieldsStore.getField(relatedCollections.relatedCollection, fieldKey), + }; + }); + + const stringValues: Record = {}; + + for (const { key, field } of fields) { + const fieldValue = get(value, key); + + if (fieldValue === null || fieldValue === undefined) continue; + + if (!field?.meta?.display) { + set(stringValues, key, fieldValue); + continue; + } + + const display = getDisplay(field.meta.display); + + const stringValue = display?.handler + ? await display.handler(fieldValue, field?.meta?.display_options ?? {}, { + interfaceOptions: field?.meta?.options ?? {}, + field: field ?? undefined, + collection: collection, + }) + : fieldValue; + + set(stringValues, key, stringValue); + } + + return renderPlainStringTemplate(options.template, stringValues); + }, options: ({ relations }) => { const fieldsStore = useFieldsStore(); diff --git a/app/src/utils/save-as-csv.ts b/app/src/utils/save-as-csv.ts index 383b1fb7a9..20063912f5 100644 --- a/app/src/utils/save-as-csv.ts +++ b/app/src/utils/save-as-csv.ts @@ -37,6 +37,7 @@ export async function saveAsCSV(collection: string, fields: string[], items: Ite ? await display.handler(value, fieldsUsed[key]?.meta?.display_options ?? {}, { interfaceOptions: fieldsUsed[key]?.meta?.options ?? {}, field: fieldsUsed[key] ?? undefined, + collection: collection, }) : value; } else { diff --git a/packages/shared/src/types/displays.ts b/packages/shared/src/types/displays.ts index 60b25fd434..3da061a7d4 100644 --- a/packages/shared/src/types/displays.ts +++ b/packages/shared/src/types/displays.ts @@ -22,7 +22,7 @@ export interface DisplayConfig { handler?: ( value: any, options: Record, - ctx: { interfaceOptions?: Record; field?: Field } + ctx: { interfaceOptions?: Record; field?: Field; collection?: string } ) => string | null | Promise; options: | DeepPartial[]