Enhance translations interface previews (#5860)

* Delete what was previously added

The reasons of which are beyond me

* Don't default to having the first nested field opened

* Add options for translations / language templates

* Pull previews and render based on secondary display template

* Add changeset
This commit is contained in:
Rijk van Zanten
2021-05-24 20:56:29 -04:00
committed by GitHub
parent 925c3fa3fa
commit 5c66c53478
6 changed files with 182 additions and 51 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

27
package-lock.json generated
View File

@@ -13533,7 +13533,6 @@
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
"optional": true,
"dependencies": {
"inherits": "~2.0.0"
@@ -21562,7 +21561,6 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"optional": true,
"dependencies": {
"graceful-fs": "^4.1.2",
@@ -21578,7 +21576,6 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"dependencies": {
"minimist": "^1.2.5"
@@ -21591,7 +21588,6 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"dependencies": {
"glob": "^7.1.3"
@@ -40966,7 +40962,6 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"dependencies": {
"minimist": "^1.2.5"
@@ -40979,7 +40974,6 @@
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
"optional": true,
"dependencies": {
"fstream": "^1.0.0",
@@ -41006,7 +41000,6 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
"optional": true,
"dependencies": {
"abbrev": "1"
@@ -41019,7 +41012,6 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"dependencies": {
"glob": "^7.1.3"
@@ -41032,7 +41024,6 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true,
"optional": true,
"bin": {
"semver": "bin/semver"
@@ -41042,7 +41033,6 @@
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
"optional": true,
"dependencies": {
"block-stream": "*",
@@ -41054,7 +41044,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"optional": true,
"dependencies": {
"isexe": "^2.0.0"
@@ -60028,7 +60017,6 @@
"resolved": "https://registry.npmjs.org/@oclif/command/-/command-1.8.0.tgz",
"integrity": "sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw==",
"requires": {
"@oclif/config": "^1.15.1",
"@oclif/errors": "^1.3.3",
"@oclif/parser": "^3.8.3",
"@oclif/plugin-help": "^3",
@@ -62157,7 +62145,6 @@
"integrity": "sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw==",
"dev": true,
"requires": {
"@babel/core": "^7.11.0",
"@babel/helper-compilation-targets": "^7.9.6",
"@babel/helper-module-imports": "^7.8.3",
"@babel/plugin-proposal-class-properties": "^7.8.3",
@@ -62170,7 +62157,6 @@
"@vue/babel-plugin-jsx": "^1.0.3",
"@vue/babel-preset-jsx": "^1.2.4",
"babel-plugin-dynamic-import-node": "^2.3.3",
"core-js": "^3.6.5",
"core-js-compat": "^3.6.5",
"semver": "^6.1.0"
},
@@ -66222,7 +66208,6 @@
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
"optional": true,
"requires": {
"inherits": "~2.0.0"
@@ -69930,7 +69915,7 @@
"keyv": "^4.0.3",
"keyv-memcache": "^1.0.1",
"knex": "^0.95.6",
"knex-schema-inspector": "^1.5.3",
"knex-schema-inspector": "^1.5.4",
"liquidjs": "^9.24.1",
"lodash": "^4.17.21",
"macos-release": "^2.4.1",
@@ -72842,7 +72827,6 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
@@ -72855,7 +72839,6 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
@@ -72865,7 +72848,6 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"requires": {
"glob": "^7.1.3"
@@ -88285,7 +88267,6 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
@@ -88295,7 +88276,6 @@
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
"optional": true,
"requires": {
"fstream": "^1.0.0",
@@ -88316,7 +88296,6 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
"optional": true,
"requires": {
"abbrev": "1"
@@ -88326,7 +88305,6 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"optional": true,
"requires": {
"glob": "^7.1.3"
@@ -88336,14 +88314,12 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true,
"optional": true
},
"tar": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
"optional": true,
"requires": {
"block-stream": "*",
@@ -88355,7 +88331,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"optional": true,
"requires": {
"isexe": "^2.0.0"