Merge branch 'main' of github.com:directus/directus into main

This commit is contained in:
rijkvanzanten
2021-05-24 21:05:35 -04:00
5 changed files with 181 additions and 25 deletions

View File

@@ -0,0 +1,6 @@
---
'@directus/app': patch
---
Render inline previews of translated values in translations display, based on two templates (one for language, the other
for translation preview)

View File

@@ -15,7 +15,7 @@
</v-input>
</template>
<v-list v-if="!disabled">
<v-list v-if="!disabled" :mandatory="false">
<field-list-item @add="addField" v-for="field in tree" :key="field.field" :field="field" :depth="depth" />
</v-list>
</v-menu>

View File

@@ -3,14 +3,28 @@
{{ $t('interfaces.translations.no_collection') }}
</v-notice>
<div v-else class="form-grid">
<div class="field full">
<p class="type-label">{{ $t('display_template') }}</p>
<div class="field half">
<p class="type-label">{{ $t('language_display_template') }}</p>
<v-field-template
:collection="relatedCollection"
v-model="template"
:collection="languageCollection"
v-model="languageTemplate"
:depth="2"
:placeholder="
relatedCollectionInfo && relatedCollectionInfo.meta && relatedCollectionInfo.meta.display_template
languageCollectionInfo && languageCollectionInfo.meta && languageCollectionInfo.meta.display_template
"
/>
</div>
<div class="field half">
<p class="type-label">{{ $t('translations_display_template') }}</p>
<v-field-template
:collection="translationsCollection"
v-model="translationsTemplate"
:depth="2"
:placeholder="
translationsCollectionInfo &&
translationsCollectionInfo.meta &&
translationsCollectionInfo.meta.display_template
"
/>
</div>
@@ -22,6 +36,7 @@ import { Field } from '@/types';
import { defineComponent, PropType, computed } from '@vue/composition-api';
import { Relation } from '@/types/relations';
import { useCollectionsStore } from '@/stores/';
import { translate } from '@/utils/translate-object-values';
export default defineComponent({
props: {
@@ -45,33 +60,73 @@ export default defineComponent({
setup(props, { emit }) {
const collectionsStore = useCollectionsStore();
const template = computed({
const translationsTemplate = computed({
get() {
return props.value?.template;
return props.value?.translationsTemplate;
},
set(newTemplate: string) {
emit('input', {
...(props.value || {}),
template: newTemplate,
translationsTemplate: newTemplate,
});
},
});
const relatedCollection = computed(() => {
const languageTemplate = computed({
get() {
return props.value?.languageTemplate;
},
set(newTemplate: string) {
emit('input', {
...(props.value || {}),
languageTemplate: newTemplate,
});
},
});
const translationsRelation = computed(() => {
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
const { field } = props.fieldData;
const relation = props.relations.find(
(relation) => relation.related_collection === props.collection && relation.meta?.one_field === field
return (
props.relations.find(
(relation) => relation.related_collection === props.collection && relation.meta?.one_field === field
) ?? null
);
return relation?.collection || null;
});
const relatedCollectionInfo = computed(() => {
if (!relatedCollection.value) return null;
return collectionsStore.getCollection(relatedCollection.value);
const languageRelation = computed(() => {
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
if (!translationsRelation.value) return null;
return (
props.relations.find(
(relation) =>
relation.collection === translationsRelation.value?.collection &&
relation.meta?.junction_field === translationsRelation.value?.field
) ?? null
);
});
return { template, relatedCollection, relatedCollectionInfo };
const translationsCollection = computed(() => translationsRelation.value?.collection ?? null);
const languageCollection = computed(() => languageRelation.value?.related_collection ?? null);
const translationsCollectionInfo = computed(() => {
if (!translationsCollection.value) return null;
return collectionsStore.getCollection(translationsCollection.value);
});
const languageCollectionInfo = computed(() => {
if (!languageCollection.value) return null;
return collectionsStore.getCollection(languageCollection.value);
});
return {
languageTemplate,
translationsTemplate,
translationsCollection,
translationsCollectionInfo,
languageCollection,
languageCollectionInfo,
};
},
});
</script>

View File

@@ -5,14 +5,20 @@
<v-list class="translations" v-else>
<v-list-item
v-for="languageItem in languages"
v-for="(languageItem, i) in languages"
:key="languageItem[languagesPrimaryKeyField]"
@click="startEditing(languageItem[languagesPrimaryKeyField])"
class="language-row"
block
>
<v-icon class="translate" name="translate" left />
<render-template :template="languagesTemplate" :collection="languagesCollection" :item="languageItem" />
<render-template :template="_languageTemplate" :collection="languagesCollection" :item="languageItem" />
<render-template
class="preview"
:template="_translationsTemplate"
:collection="translationsCollection"
:item="previewItems[i]"
/>
<div class="spacer" />
</v-list-item>
@@ -38,6 +44,7 @@ import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
import DrawerItem from '@/views/private/components/drawer-item/drawer-item.vue';
import { useCollection } from '@/composables/use-collection';
import { unexpectedError } from '@/utils/unexpected-error';
import { isPlainObject } from 'lodash';
export default defineComponent({
components: { DrawerItem },
@@ -54,7 +61,11 @@ export default defineComponent({
type: String,
required: true,
},
template: {
languageTemplate: {
type: String,
default: null,
},
translationsTemplate: {
type: String,
default: null,
},
@@ -78,9 +89,9 @@ export default defineComponent({
translationsLanguageField,
} = useRelations();
const { languages, loading: languagesLoading, template: languagesTemplate } = useLanguages();
const { languages, loading: languagesLoading, template: _languageTemplate } = useLanguages();
const { startEditing, editing, edits, stageEdits, cancelEdit } = useEdits();
const { previewItems, template: _translationsTemplate } = usePreview();
return {
relationsForField,
@@ -88,7 +99,8 @@ export default defineComponent({
translationsCollection,
languagesRelation,
languages,
languagesTemplate,
_languageTemplate,
_translationsTemplate,
languagesCollection,
languagesPrimaryKeyField,
languagesLoading,
@@ -98,6 +110,7 @@ export default defineComponent({
stageEdits,
cancelEdit,
edits,
previewItems,
};
function useRelations() {
@@ -158,7 +171,7 @@ export default defineComponent({
}
function useLanguages() {
const languages = ref();
const languages = ref<Record<string, any>[]>();
const loading = ref(false);
const error = ref<any>(null);
@@ -166,8 +179,9 @@ export default defineComponent({
const template = computed(() => {
if (!languagesPrimaryKeyField.value) return '';
return (
props.template ||
props.languageTemplate ||
languagesCollectionInfo.value?.meta?.display_template ||
`{{ ${languagesPrimaryKeyField.value} }}`
);
@@ -337,6 +351,85 @@ export default defineComponent({
editing.value = false;
}
}
function usePreview() {
const loading = ref(false);
const error = ref(null);
const previewItems = ref<Record<string, any>[]>([]);
const { info: translationsCollectionInfo } = useCollection(translationsCollection);
const template = computed(() => {
if (!translationsPrimaryKeyField.value) return '';
return (
props.translationsTemplate ||
translationsCollectionInfo.value?.meta?.display_template ||
`{{ ${translationsPrimaryKeyField.value} }}`
);
});
watch(() => props.value, fetchPreviews, { immediate: true });
watch(languages, fetchPreviews, { immediate: true });
return { loading, error, previewItems, fetchPreviews, template };
async function fetchPreviews() {
if (!translationsRelation.value || !languagesRelation.value || !languages.value) return;
loading.value = true;
try {
const fields = getFieldsFromTemplate(template.value);
if (fields.includes(languagesRelation.value.field) === false) {
fields.push(languagesRelation.value.field);
}
const existing = await api.get(`/items/${translationsCollection.value}`, {
params: {
fields,
filter: {
[translationsRelation.value.field]: {
_eq: props.primaryKey,
},
},
},
});
previewItems.value = languages.value.map((language) => {
const existingEdit =
props.value && Array.isArray(props.value)
? (props.value.find(
(edit) =>
isPlainObject(edit) &&
edit[languagesRelation.value!.field] === language[languagesPrimaryKeyField.value]
) as Record<string, any>)
: {};
return {
...(existing.data.data?.find(
(item: Record<string, any>) =>
item[languagesRelation.value!.field] === language[languagesPrimaryKeyField.value]
) ?? {}),
...existingEdit,
};
});
} catch (err) {
console.log(err);
error.value = err;
previewItems.value = [];
} finally {
loading.value = false;
}
}
}
},
});
</script>
<style scoped>
.preview {
color: var(--foreground-subdued);
}
</style>

View File

@@ -508,6 +508,8 @@ value_unique: Value has to be unique
all_activity: All Activity
create_item: Create Item
display_template: Display Template
language_display_template: Language Display Template
translations_display_template: Translations Display Template
n_items_selected: 'No Items Selected | 1 Item Selected | {n} Items Selected'
per_page: Per Page
all_files: All Files