mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'main' of github.com:directus/directus into main
This commit is contained in:
6
.changeset/fluffy-zebras-cross.md
Normal file
6
.changeset/fluffy-zebras-cross.md
Normal 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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user