From cef060a0fc8aa0608deb287bc285928864cf3507 Mon Sep 17 00:00:00 2001 From: Rijk van Zanten Date: Thu, 30 Jun 2022 18:45:52 -0400 Subject: [PATCH] Use string displays on the calendar layout (#14232) --- app/src/composables/use-revisions.ts | 10 ++-- .../use-time-from-now/use-time-from-now.ts | 4 +- app/src/displays/datetime/datetime.vue | 2 +- app/src/displays/datetime/index.ts | 6 +- app/src/displays/related-values/index.ts | 4 +- app/src/displays/translations/index.ts | 4 +- app/src/lang/set-language.ts | 3 + app/src/layouts/calendar/index.ts | 9 ++- app/src/layouts/calendar/options.vue | 2 +- .../components/file-info-sidebar-detail.vue | 7 +-- .../components/user-info-sidebar-detail.vue | 5 +- .../get-date-fns-locale.ts | 25 -------- app/src/utils/get-date-fns-locale/index.ts | 32 +++++++++- .../localized-format-distance-strict.ts | 12 ++-- .../localized-format-distance.ts | 8 +-- .../localized-format/localized-format.ts | 8 +-- app/src/utils/render-string-template.ts | 60 ++++++++++++++++++- .../components/notifications-drawer.vue | 2 +- .../revisions-drawer-picker.vue | 4 +- packages/shared/src/types/displays.ts | 2 +- 20 files changed, 132 insertions(+), 77 deletions(-) delete mode 100644 app/src/utils/get-date-fns-locale/get-date-fns-locale.ts diff --git a/app/src/composables/use-revisions.ts b/app/src/composables/use-revisions.ts index d1311df859..91b8a0da1d 100644 --- a/app/src/composables/use-revisions.ts +++ b/app/src/composables/use-revisions.ts @@ -143,8 +143,8 @@ export function useRevisions(collection: Ref, primaryKey: Ref, primaryKey: Ref, primaryKey: Ref { interval = window.setInterval(async () => { - formattedDate.value = await localizedFormatDistance(date, new Date(), formatOptions); + formattedDate.value = localizedFormatDistance(date, new Date(), formatOptions); }, autoUpdate); }); diff --git a/app/src/displays/datetime/datetime.vue b/app/src/displays/datetime/datetime.vue index 282bd6f927..1d385167c3 100644 --- a/app/src/displays/datetime/datetime.vue +++ b/app/src/displays/datetime/datetime.vue @@ -77,7 +77,7 @@ watch( format = props.format; } - displayValue.value = await localizedFormat(newValue, format); + displayValue.value = localizedFormat(newValue, format); } }, { immediate: true } diff --git a/app/src/displays/datetime/index.ts b/app/src/displays/datetime/index.ts index e5bc81676e..e66b941d28 100644 --- a/app/src/displays/datetime/index.ts +++ b/app/src/displays/datetime/index.ts @@ -11,7 +11,7 @@ export default defineDisplay({ description: '$t:displays.datetime.description', icon: 'query_builder', component: DisplayDateTime, - handler: async (value, options, { field }) => { + handler: (value, options, { field }) => { if (!value) return value; const relativeFormat = (value: Date) => @@ -30,7 +30,7 @@ export default defineDisplay({ } if (options.relative) { - return await relativeFormat(value); + return relativeFormat(value); } else { let format; @@ -46,7 +46,7 @@ export default defineDisplay({ format = options?.format; } - return await localizedFormat(value, format); + return localizedFormat(value, format); } }, options: ({ field }) => { diff --git a/app/src/displays/related-values/index.ts b/app/src/displays/related-values/index.ts index 8d2c5ab88f..41a1964970 100644 --- a/app/src/displays/related-values/index.ts +++ b/app/src/displays/related-values/index.ts @@ -46,7 +46,7 @@ export default defineDisplay({ }, ]; }, - handler: async (value, options, { collection, field }) => { + handler: (value, options, { collection, field }) => { if (!field || !collection) return value; const relatedCollections = getRelatedCollection(collection, field.field); @@ -82,7 +82,7 @@ export default defineDisplay({ const display = getDisplay(field.meta.display); const stringValue = display?.handler - ? await display.handler(fieldValue, field?.meta?.display_options ?? {}, { + ? display.handler(fieldValue, field?.meta?.display_options ?? {}, { interfaceOptions: field?.meta?.options ?? {}, field: field ?? undefined, collection: collection, diff --git a/app/src/displays/translations/index.ts b/app/src/displays/translations/index.ts index 9597a236dd..b25f7a2e5b 100644 --- a/app/src/displays/translations/index.ts +++ b/app/src/displays/translations/index.ts @@ -19,7 +19,7 @@ export default defineDisplay({ description: '$t:displays.translations.description', icon: 'translate', component: DisplayTranslations, - handler: async (values, options, { collection, field }) => { + handler: (values, options, { collection, field }) => { if (!field || !collection || !Array.isArray(values)) return values; const relatedCollections = getRelatedCollection(collection, field.field); @@ -85,7 +85,7 @@ export default defineDisplay({ const display = getDisplay(field.meta.display); const stringValue = display?.handler - ? await display.handler(fieldValue, field?.meta?.display_options ?? {}, { + ? display.handler(fieldValue, field?.meta?.display_options ?? {}, { interfaceOptions: field?.meta?.options ?? {}, field: field ?? undefined, collection: collection, diff --git a/app/src/lang/set-language.ts b/app/src/lang/set-language.ts index a3367639b2..a033d77a30 100644 --- a/app/src/lang/set-language.ts +++ b/app/src/lang/set-language.ts @@ -15,6 +15,7 @@ const { interfaces, interfacesRaw } = getInterfaces(); const { panels, panelsRaw } = getPanels(); const { displays, displaysRaw } = getDisplays(); const { operations, operationsRaw } = getOperations(); +import { loadDateFNSLocale } from '@/utils/get-date-fns-locale'; export async function setLanguage(lang: Language): Promise { const collectionsStore = useCollectionsStore(); @@ -52,5 +53,7 @@ export async function setLanguage(lang: Language): Promise { fieldsStore.translateFields(); mergeTranslationStringsForLanguage(lang); + await loadDateFNSLocale(lang); + return true; } diff --git a/app/src/layouts/calendar/index.ts b/app/src/layouts/calendar/index.ts index 404f935934..81644eec54 100644 --- a/app/src/layouts/calendar/index.ts +++ b/app/src/layouts/calendar/index.ts @@ -2,7 +2,7 @@ import api from '@/api'; import { router } from '@/router'; import { useAppStore } from '@/stores/app'; import getFullcalendarLocale from '@/utils/get-fullcalendar-locale'; -import { renderPlainStringTemplate } from '@/utils/render-string-template'; +import { renderDisplayStringTemplate } from '@/utils/render-string-template'; import { saveAsCSV } from '@/utils/save-as-csv'; import { syncRefProperty } from '@/utils/sync-ref-property'; import { unexpectedError } from '@/utils/unexpected-error'; @@ -310,7 +310,12 @@ export default defineLayout({ return { id: primaryKey, - title: renderPlainStringTemplate(template.value || `{{ ${primaryKeyField.value.field} }}`, item) || undefined, + title: + renderDisplayStringTemplate( + collection.value, + template.value || `{{ ${primaryKeyField.value.field} }}`, + item + ) || item[primaryKeyField.value.field], start: item[startDateField.value], end: endDate, allDay, diff --git a/app/src/layouts/calendar/options.vue b/app/src/layouts/calendar/options.vue index b18d7adc7f..2e8e7356c7 100644 --- a/app/src/layouts/calendar/options.vue +++ b/app/src/layouts/calendar/options.vue @@ -78,7 +78,7 @@ export default defineComponent({ const firstDayOfWeekForDate = startOfWeek(new Date()); firstDayOptions.value = await Promise.all( [...Array(7).keys()].map(async (_, i) => ({ - text: await localizedFormat(add(firstDayOfWeekForDate, { days: i }), 'EEEE'), + text: localizedFormat(add(firstDayOfWeekForDate, { days: i }), 'EEEE'), value: i, })) ); diff --git a/app/src/modules/files/components/file-info-sidebar-detail.vue b/app/src/modules/files/components/file-info-sidebar-detail.vue index edf500291d..dc6741f06f 100644 --- a/app/src/modules/files/components/file-info-sidebar-detail.vue +++ b/app/src/modules/files/components/file-info-sidebar-detail.vue @@ -189,13 +189,10 @@ export default defineComponent({ async () => { if (!props.file) return null; - creationDate.value = await localizedFormat( - new Date(props.file.uploaded_on), - String(t('date-fns_date_short')) - ); + creationDate.value = localizedFormat(new Date(props.file.uploaded_on), String(t('date-fns_date_short'))); if (props.file.modified_on) { - modificationDate.value = await localizedFormat( + modificationDate.value = localizedFormat( new Date(props.file.modified_on), String(t('date-fns_date_short')) ); diff --git a/app/src/modules/users/components/user-info-sidebar-detail.vue b/app/src/modules/users/components/user-info-sidebar-detail.vue index 3803bb0c50..6f0cac50d7 100644 --- a/app/src/modules/users/components/user-info-sidebar-detail.vue +++ b/app/src/modules/users/components/user-info-sidebar-detail.vue @@ -73,10 +73,7 @@ export default defineComponent({ if (!props.user) return; if (props.user.last_access) { - lastAccessDate.value = await localizedFormat( - new Date(props.user.last_access), - String(t('date-fns_date_short')) - ); + lastAccessDate.value = localizedFormat(new Date(props.user.last_access), String(t('date-fns_date_short'))); } }, { immediate: true } diff --git a/app/src/utils/get-date-fns-locale/get-date-fns-locale.ts b/app/src/utils/get-date-fns-locale/get-date-fns-locale.ts deleted file mode 100644 index 60be134000..0000000000 --- a/app/src/utils/get-date-fns-locale/get-date-fns-locale.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { i18n } from '@/lang'; -import { Locale } from 'date-fns'; - -import importDateLocale from './import-date-locale'; - -export async function getDateFNSLocale(): Promise { - const lang = i18n.global.locale.value; - - const localesToTry = [lang, lang.split('-')[0], 'en-US']; - - let locale; - - for (const l of localesToTry) { - try { - const mod = await importDateLocale(l); - - locale = mod.default; - break; - } catch { - continue; - } - } - - return locale; -} diff --git a/app/src/utils/get-date-fns-locale/index.ts b/app/src/utils/get-date-fns-locale/index.ts index ea3ccbebe5..feece4415f 100644 --- a/app/src/utils/get-date-fns-locale/index.ts +++ b/app/src/utils/get-date-fns-locale/index.ts @@ -1,4 +1,30 @@ -import { getDateFNSLocale } from './get-date-fns-locale'; +import { i18n } from '@/lang'; +import { Locale } from 'date-fns'; -export { getDateFNSLocale }; -export default getDateFNSLocale; +import importDateLocale from './import-date-locale'; + +const locales: { lang: string; locale: Locale }[] = []; + +export function getDateFNSLocale(): Locale | undefined { + const currentLang = i18n.global.locale.value; + return locales.find(({ lang }) => currentLang === lang)?.locale; +} + +export async function loadDateFNSLocale(lang: string) { + const localesToTry = [lang, lang.split('-')[0], 'en-US']; + + let locale; + + for (const l of localesToTry) { + try { + const mod = await importDateLocale(l); + locale = mod.default; + locales.push({ lang, locale }); + break; + } catch { + continue; + } + } + + return locale; +} diff --git a/app/src/utils/localized-format-distance-strict/localized-format-distance-strict.ts b/app/src/utils/localized-format-distance-strict/localized-format-distance-strict.ts index 94d3c87e92..e92a6f58ec 100644 --- a/app/src/utils/localized-format-distance-strict/localized-format-distance-strict.ts +++ b/app/src/utils/localized-format-distance-strict/localized-format-distance-strict.ts @@ -1,15 +1,11 @@ -import getDateFNSLocale from '@/utils/get-date-fns-locale'; +import { getDateFNSLocale } from '@/utils/get-date-fns-locale'; import formatDistanceStrict from 'date-fns/formatDistanceStrict'; -type LocalizedFormatDistanceStrict = (...a: Parameters) => Promise; +type LocalizedFormatDistanceStrict = (...a: Parameters) => string; -export const localizedFormatDistanceStrict: LocalizedFormatDistanceStrict = async ( - date, - baseDate, - options -): Promise => { +export const localizedFormatDistanceStrict: LocalizedFormatDistanceStrict = (date, baseDate, options): string => { return formatDistanceStrict(date, baseDate, { ...options, - locale: await getDateFNSLocale(), + locale: getDateFNSLocale(), }); }; diff --git a/app/src/utils/localized-format-distance/localized-format-distance.ts b/app/src/utils/localized-format-distance/localized-format-distance.ts index a6cd5f30df..d7d4d03815 100644 --- a/app/src/utils/localized-format-distance/localized-format-distance.ts +++ b/app/src/utils/localized-format-distance/localized-format-distance.ts @@ -1,11 +1,11 @@ -import getDateFNSLocale from '@/utils/get-date-fns-locale'; +import { getDateFNSLocale } from '@/utils/get-date-fns-locale'; import formatDistanceOriginal from 'date-fns/formatDistance'; -type LocalizedFormatDistance = (...a: Parameters) => Promise; +type LocalizedFormatDistance = (...a: Parameters) => string; -export const localizedFormatDistance: LocalizedFormatDistance = async (date, baseDate, options): Promise => { +export const localizedFormatDistance: LocalizedFormatDistance = (date, baseDate, options): string => { return formatDistanceOriginal(date, baseDate, { ...options, - locale: await getDateFNSLocale(), + locale: getDateFNSLocale(), }); }; diff --git a/app/src/utils/localized-format/localized-format.ts b/app/src/utils/localized-format/localized-format.ts index 2b222ba313..90b63ef184 100644 --- a/app/src/utils/localized-format/localized-format.ts +++ b/app/src/utils/localized-format/localized-format.ts @@ -1,11 +1,11 @@ -import getDateFNSLocale from '@/utils/get-date-fns-locale'; +import { getDateFNSLocale } from '@/utils/get-date-fns-locale/'; import formatOriginal from 'date-fns/format'; -type localizedFormat = (...a: Parameters) => Promise; +type localizedFormat = (...a: Parameters) => string; -export const localizedFormat: localizedFormat = async (date, format, options): Promise => { +export const localizedFormat: localizedFormat = (date, format, options): string => { return formatOriginal(date, format, { ...options, - locale: await getDateFNSLocale(), + locale: getDateFNSLocale(), }); }; diff --git a/app/src/utils/render-string-template.ts b/app/src/utils/render-string-template.ts index 9ddd90d0e1..8c88ae33a9 100644 --- a/app/src/utils/render-string-template.ts +++ b/app/src/utils/render-string-template.ts @@ -1,6 +1,11 @@ -import { render, renderFn, get } from 'micromustache'; -import { computed, ComputedRef, Ref, unref } from 'vue'; +import useAliasFields from '@/composables/use-alias-fields'; +import { getDisplay } from '@/displays'; +import { useFieldsStore } from '@/stores'; +import { DisplayConfig, Field } from '@directus/shared/types'; import { getFieldsFromTemplate } from '@directus/shared/utils'; +import { render, renderFn } from 'micromustache'; +import { computed, ComputedRef, Ref, ref, unref } from 'vue'; +import { get, set } from 'lodash'; type StringTemplate = { fieldsInTemplate: ComputedRef; @@ -46,3 +51,54 @@ export function renderPlainStringTemplate(template: string, item?: Record +): string | null { + const fieldsStore = useFieldsStore(); + + const fields = getFieldsFromTemplate(template); + + const fieldsUsed: Record = {}; + + for (const key of fields) { + set(fieldsUsed, key, fieldsStore.getField(collection, key)); + } + + const { aliasFields } = useAliasFields(ref(fields)); + + const parsedItem: Record = {}; + + for (const key of fields) { + const value = + !aliasFields.value?.[key] || get(item, key) !== undefined + ? get(item, key) + : get(item, aliasFields.value[key].fullAlias); + + let display: DisplayConfig | undefined; + + if (fieldsUsed[key]?.meta?.display) { + display = getDisplay(fieldsUsed[key]!.meta!.display); + } + + if (value !== undefined && value !== null) { + set( + parsedItem, + key, + display?.handler + ? display.handler(value, fieldsUsed[key]?.meta?.display_options ?? {}, { + interfaceOptions: fieldsUsed[key]?.meta?.options ?? {}, + field: fieldsUsed[key] ?? undefined, + collection: collection, + }) + : value + ); + } else { + set(parsedItem, key, value); + } + } + + return renderPlainStringTemplate(template, parsedItem); +} diff --git a/app/src/views/private/components/notifications-drawer.vue b/app/src/views/private/components/notifications-drawer.vue index 11d6809d91..5582e7bd28 100644 --- a/app/src/views/private/components/notifications-drawer.vue +++ b/app/src/views/private/components/notifications-drawer.vue @@ -160,7 +160,7 @@ export default defineComponent({ for (const notification of notificationsRaw) { notificationsWithRelative.push({ ...notification, - timestampDistance: await localizedFormatDistance(parseISO(notification.timestamp), new Date(), { + timestampDistance: localizedFormatDistance(parseISO(notification.timestamp), new Date(), { addSuffix: true, }), }); diff --git a/app/src/views/private/components/revisions-drawer-detail/revisions-drawer-picker.vue b/app/src/views/private/components/revisions-drawer-detail/revisions-drawer-picker.vue index 98fdf36352..8a90380c56 100644 --- a/app/src/views/private/components/revisions-drawer-detail/revisions-drawer-picker.vue +++ b/app/src/views/private/components/revisions-drawer-detail/revisions-drawer-picker.vue @@ -85,8 +85,8 @@ export default defineComponent({ return { internalCurrent, options, selectedOption }; async function getFormattedDate(revision: Revision) { - const date = await localizedFormat(new Date(revision!.activity.timestamp), String(t('date-fns_date_short'))); - const time = await localizedFormat(new Date(revision!.activity.timestamp), String(t('date-fns_time'))); + const date = localizedFormat(new Date(revision!.activity.timestamp), String(t('date-fns_date_short'))); + const time = localizedFormat(new Date(revision!.activity.timestamp), String(t('date-fns_time'))); return `${date} (${time})`; } diff --git a/packages/shared/src/types/displays.ts b/packages/shared/src/types/displays.ts index 3da061a7d4..131236bf40 100644 --- a/packages/shared/src/types/displays.ts +++ b/packages/shared/src/types/displays.ts @@ -23,7 +23,7 @@ export interface DisplayConfig { value: any, options: Record, ctx: { interfaceOptions?: Record; field?: Field; collection?: string } - ) => string | null | Promise; + ) => string | null; options: | DeepPartial[] | { standard: DeepPartial[]; advanced: DeepPartial[] }