Translations interface (#660)

* Pass scope onto group in v-item

* Add translations interface
This commit is contained in:
Rijk van Zanten
2020-05-28 16:49:31 -04:00
committed by GitHub
parent 8f98e6f611
commit 6209a2a89b
4 changed files with 342 additions and 1 deletions

View File

@@ -14,9 +14,13 @@ export default defineComponent({
type: String,
default: null,
},
scope: {
type: String,
default: 'item-group',
},
},
setup(props) {
const { active, toggle } = useGroupable(props.value);
const { active, toggle } = useGroupable(props.value, props.scope);
return { active, toggle };
},
});

View File

@@ -25,6 +25,7 @@ import InterfaceRepeater from './repeater';
import InterfaceCode from './code';
import InterfaceFile from './file';
import InterfaceCollections from './collections';
import InterfaceTranslations from './translations';
export const interfaces = [
InterfaceTextInput,
@@ -54,6 +55,7 @@ export const interfaces = [
InterfaceCode,
InterfaceFile,
InterfaceCollections,
InterfaceTranslations,
];
export default interfaces;

View File

@@ -0,0 +1,11 @@
import { defineInterface } from '../define';
import InterfaceTranslations from './translations.vue';
export default defineInterface(({ i18n }) => ({
id: 'translations',
name: i18n.t('translations'),
icon: 'replay',
types: ['o2m'],
component: InterfaceTranslations,
options: [],
}));

View File

@@ -0,0 +1,324 @@
<template>
<div v-if="itemsLoading || languagesLoading" class="loader">
<v-skeleton-loader v-for="n in 5" :key="n" />
</div>
<v-item-group v-else scope="translations" class="translations">
<v-item
scope="translations"
class="row"
v-for="(item, index) in languages"
:key="item[languagesPrimaryKeyField.field]"
#default="{ active, toggle }"
>
<div class="header" @click="toggle">
<render-template
:template="template"
:collection="languagesCollection"
:item="item"
/>
</div>
<transition-expand>
<div v-if="active">
<div class="form">
<v-divider />
<v-form
:initial-values="existing[index]"
:collection="relatedCollection.collection"
:primary-key="existing[index][relatedPrimaryKeyField.field] || '+'"
:edits="edits[index]"
@input="
emitValue($event, existing[index][relatedPrimaryKeyField.field])
"
/>
</div>
</div>
</transition-expand>
</v-item>
</v-item-group>
</template>
<script lang="ts">
import { defineComponent, computed, ref, toRefs, Ref, watch, PropType } from '@vue/composition-api';
import useProjectsStore from '@/stores/projects';
import useRelationsStore from '@/stores/relations';
import useCollectionsStore from '@/stores/collections';
import useCollection from '@/composables/use-collection';
import api from '@/api';
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
export default defineComponent({
props: {
languagesCollection: {
type: String,
required: true,
},
languageField: {
type: String,
required: true,
},
template: {
type: String,
required: true,
},
collection: {
type: String,
required: true,
},
field: {
type: String,
required: true,
},
primaryKey: {
type: String,
required: true,
},
value: {
type: Array as PropType<Record<string, any>[]>,
default: () => [],
},
},
setup(props, { emit }) {
const projectsStore = useProjectsStore();
const collectionsStore = useCollectionsStore();
const relationsStore = useRelationsStore();
const {
languages,
loading: languagesLoading,
error: languagesError,
primaryKeyField: languagesPrimaryKeyField,
} = useLanguages();
const { relation, relatedCollection, relatedPrimaryKeyField } = useRelation();
const { items, loading: itemsLoading, error: itemsError } = useCurrent();
const { existing, edits, emitValue } = useValues();
return {
relation,
relatedCollection,
relatedPrimaryKeyField,
languages,
languagesLoading,
languagesError,
languagesPrimaryKeyField,
items,
itemsLoading,
itemsError,
existing,
edits,
emitValue,
};
function useRelation() {
const relation = computed(() => {
return relationsStore.getRelationsForField(props.collection, props.field)?.[0];
});
const relatedCollection = computed(() => {
if (!relation.value) return null;
return collectionsStore.getCollection(relation.value.collection_many);
});
const { collection } = toRefs(relatedCollection.value);
const { primaryKeyField: relatedPrimaryKeyField } = useCollection(
collection as Ref<string>
);
return { relation, relatedCollection, relatedPrimaryKeyField };
}
function useLanguages() {
const languages = ref<Record<string, any>>(null);
const loading = ref(false);
const error = ref(null);
const { languagesCollection } = toRefs(props);
const { primaryKeyField } = useCollection(languagesCollection);
watch(() => props.languagesCollection, fetchLanguages);
return { languages, loading, error, primaryKeyField };
async function fetchLanguages() {
const { currentProjectKey } = projectsStore.state;
loading.value = true;
const fields = getFieldsFromTemplate(props.template);
if (fields.includes(primaryKeyField.value.field) === false) {
fields.push(primaryKeyField.value.field);
}
try {
const response = await api.get(
`/${currentProjectKey}/items/${props.languagesCollection}`,
{
params: {
fields: fields,
limit: -1,
},
}
);
languages.value = response.data.data;
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
}
function useCurrent() {
const loading = ref(false);
const items = ref<any[]>([]);
const error = ref(null);
watch(
() => props.primaryKey,
(newKey) => {
if (newKey !== null && newKey !== '+') {
fetchCurrent();
}
}
);
return { loading, items, error };
async function fetchCurrent() {
const { currentProjectKey } = projectsStore.state;
loading.value = true;
try {
const response = await api.get(
`/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`,
{
params: {
fields: props.field + '.*',
},
}
);
items.value = response.data.data[props.field];
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
}
function useValues() {
const existing = computed(() => {
if (!languages.value) return [];
return languages.value.map((language: any) => {
const existing =
items.value.find(
(item) =>
item[props.languageField] ===
language[languagesPrimaryKeyField.value.field]
) || {};
return existing;
});
});
const edits = computed(() => {
if (!languages.value) return [];
return languages.value.map((language: any) => {
const edits =
(props.value || []).find(
(edit) =>
edit[props.languageField] ===
language[languagesPrimaryKeyField.value.field]
) || {};
edits[props.languageField] = language[languagesPrimaryKeyField.value.field];
return edits;
});
});
return { existing, edits, emitValue };
function emitValue(newEdit: any, existingPrimaryKey: undefined | string | number) {
const currentEdits = [...(props.value || [])];
if (existingPrimaryKey) {
newEdit = {
...newEdit,
[relatedPrimaryKeyField.value.field]: existingPrimaryKey,
};
}
if (
currentEdits.some(
(edit) => edit[props.languageField] === newEdit[props.languageField]
)
) {
emit(
'input',
currentEdits.map((edit) => {
if (edit[props.languageField] === newEdit[props.languageField]) {
return newEdit;
}
return edit;
})
);
} else {
currentEdits.push(newEdit);
emit('input', currentEdits);
}
}
}
},
});
</script>
<style lang="scss" scoped>
@import '@/styles/mixins/type-styles.scss';
.loader .v-skeleton-loader + .v-skeleton-loader {
margin-top: 8px;
}
.header {
display: flex;
align-items: center;
padding: 12px;
cursor: pointer;
}
.row {
background-color: var(--background-subdued);
border-radius: var(--border-radius);
& + .row {
margin-top: 8px;
}
}
.v-divider {
margin-bottom: 12px;
}
.form {
--v-form-vertical-gap: 24px;
--v-form-horizontal-gap: 12px;
padding: 12px;
padding-top: 0;
::v-deep .type-label {
@include type-text;
}
}
</style>