mirror of
https://github.com/directus/directus.git
synced 2026-01-27 04:48:04 -05:00
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<value-null v-if="!relatedCollection" />
|
||||
<v-menu v-else-if="type.toLowerCase() === 'o2m'" show-arrow :disabled="value.length === 0">
|
||||
<v-menu v-else-if="type.toLowerCase() === 'o2m' || type.toLowerCase() === 'm2m'" show-arrow :disabled="value.length === 0">
|
||||
<template #activator="{ toggle }">
|
||||
<span @click.stop="toggle" class="toggle" :class="{ subdued: value.length === 0 }">
|
||||
<span class="label">{{ $tc('item_count', value.length) }}</span>
|
||||
|
||||
@@ -6,6 +6,7 @@ export default defineInterface(({ i18n }) => ({
|
||||
name: i18n.t('translations'),
|
||||
icon: 'replay',
|
||||
types: ['alias'],
|
||||
relationship: 'translations',
|
||||
component: InterfaceTranslations,
|
||||
options: [
|
||||
/** @todo add custom options component */
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#default="{ active, toggle }"
|
||||
>
|
||||
<div class="header" @click="toggle">
|
||||
<render-template :template="template" :collection="languagesCollection" :item="item" />
|
||||
<render-template :template="rowTemplate" :collection="languagesCollection" :item="item" />
|
||||
</div>
|
||||
<transition-expand>
|
||||
<div v-if="active">
|
||||
@@ -20,10 +20,10 @@
|
||||
<v-divider />
|
||||
<v-form
|
||||
:initial-values="existing[index]"
|
||||
:collection="relatedCollection.collection"
|
||||
:primary-key="existing[index][relatedPrimaryKeyField.field] || '+'"
|
||||
:collection="translationsCollection"
|
||||
:primary-key="existing[index][translationsPrimaryKeyField.field] || '+'"
|
||||
:edits="edits[index]"
|
||||
@input="emitValue($event, existing[index][relatedPrimaryKeyField.field])"
|
||||
@input="emitValue($event, existing[index][translationsPrimaryKeyField.field])"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,25 +34,14 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, toRefs, watch, PropType } from '@vue/composition-api';
|
||||
import { useCollectionsStore, useRelationsStore } from '@/stores/';
|
||||
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import api from '@/api';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { Relation } from '@/types';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
languagesCollection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
languageField: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
template: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
@@ -65,6 +54,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
template: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
default: () => [],
|
||||
@@ -72,8 +65,11 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
const { relations, translationsCollection, languagesCollection, languageField, translationsPrimaryKeyField } = useRelation();
|
||||
|
||||
const {
|
||||
languages,
|
||||
loading: languagesLoading,
|
||||
@@ -81,16 +77,20 @@ export default defineComponent({
|
||||
primaryKeyField: languagesPrimaryKeyField,
|
||||
} = useLanguages();
|
||||
|
||||
const { relation, relatedCollection, relatedPrimaryKeyField } = useRelation();
|
||||
|
||||
const { items, loading: itemsLoading, error: itemsError } = useCurrent();
|
||||
|
||||
const { existing, edits, emitValue } = useValues();
|
||||
|
||||
const rowTemplate = computed(() => {
|
||||
const { info, primaryKeyField } = useCollection(languagesCollection);
|
||||
const defaultTemplate = info.value?.meta?.display_template;
|
||||
|
||||
return defaultTemplate || props.template || `{{ $${primaryKeyField.value.field} }}`;
|
||||
});
|
||||
|
||||
return {
|
||||
relation,
|
||||
relatedCollection,
|
||||
relatedPrimaryKeyField,
|
||||
relations,
|
||||
translationsCollection,
|
||||
languagesCollection,
|
||||
languages,
|
||||
languagesLoading,
|
||||
languagesError,
|
||||
@@ -101,20 +101,51 @@ export default defineComponent({
|
||||
existing,
|
||||
edits,
|
||||
emitValue,
|
||||
rowTemplate,
|
||||
translationsPrimaryKeyField,
|
||||
};
|
||||
|
||||
function useRelation() {
|
||||
const relation = computed(() => {
|
||||
return relationsStore.getRelationsForField(props.collection, props.field)?.[0];
|
||||
const relations = computed(() => {
|
||||
return relationsStore.getRelationsForField(props.collection, props.field);
|
||||
});
|
||||
|
||||
const relatedCollection = computed(() => {
|
||||
return collectionsStore.getCollection(relation.value.many_collection)!;
|
||||
const translationsRelation = computed(() => {
|
||||
if (!relations.value || relations.value.length === 0) return null;
|
||||
|
||||
return relations.value.find((relation: Relation) => {
|
||||
return relation.one_collection === props.collection && relation.one_field === props.field;
|
||||
}) || null;
|
||||
})
|
||||
|
||||
const translationsCollection = computed(() => {
|
||||
if (!translationsRelation.value) return null;
|
||||
return translationsRelation.value.many_collection;
|
||||
});
|
||||
|
||||
const { primaryKeyField: relatedPrimaryKeyField } = useCollection(relatedCollection.value.collection);
|
||||
const translationsPrimaryKeyField = computed(() => {
|
||||
return fieldsStore.getPrimaryKeyFieldForCollection(translationsCollection.value);
|
||||
});
|
||||
|
||||
return { relation, relatedCollection, relatedPrimaryKeyField };
|
||||
const languagesRelation = computed(() => {
|
||||
if (!relations.value || relations.value.length === 0) return null;
|
||||
|
||||
return relations.value.find((relation: Relation) => {
|
||||
return relation.one_collection !== props.collection && relation.one_field !== props.field;
|
||||
}) || null;
|
||||
});
|
||||
|
||||
const languagesCollection = computed(() => {
|
||||
if (!languagesRelation.value) return null;
|
||||
return languagesRelation.value.one_collection;
|
||||
});
|
||||
|
||||
const languageField = computed(() => {
|
||||
if (!languagesRelation.value) return null;
|
||||
return languagesRelation.value.many_field;
|
||||
})
|
||||
|
||||
return { relations, translationsCollection, languagesCollection, languageField, translationsPrimaryKeyField };
|
||||
}
|
||||
|
||||
function useLanguages() {
|
||||
@@ -122,25 +153,24 @@ export default defineComponent({
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const { languagesCollection } = toRefs(props);
|
||||
|
||||
const { primaryKeyField } = useCollection(languagesCollection);
|
||||
|
||||
watch(() => props.languagesCollection, fetchLanguages);
|
||||
watch(languagesCollection, fetchLanguages, { immediate: true });
|
||||
|
||||
return { languages, loading, error, primaryKeyField };
|
||||
|
||||
async function fetchLanguages() {
|
||||
loading.value = true;
|
||||
|
||||
const fields = getFieldsFromTemplate(props.template);
|
||||
// const fields = getFieldsFromTemplate(props.template);
|
||||
const fields = ['*'];
|
||||
|
||||
if (fields.includes(primaryKeyField.value.field) === false) {
|
||||
fields.push(primaryKeyField.value.field);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/items/${props.languagesCollection}`, {
|
||||
const response = await api.get(`/items/${languagesCollection.value}`, {
|
||||
params: {
|
||||
fields: fields,
|
||||
limit: -1,
|
||||
@@ -167,6 +197,9 @@ export default defineComponent({
|
||||
if (newKey !== null && newKey !== '+') {
|
||||
fetchCurrent();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -198,7 +231,7 @@ export default defineComponent({
|
||||
return languages.value.map((language: any) => {
|
||||
const existing =
|
||||
items.value.find(
|
||||
(item) => item[props.languageField] === language[languagesPrimaryKeyField.value.field]
|
||||
(item) => item[languageField.value] === language[languagesPrimaryKeyField.value.field]
|
||||
) || {};
|
||||
|
||||
return existing;
|
||||
@@ -211,10 +244,10 @@ export default defineComponent({
|
||||
return languages.value.map((language: any) => {
|
||||
const edits =
|
||||
(props.value || []).find(
|
||||
(edit) => edit[props.languageField] === language[languagesPrimaryKeyField.value.field]
|
||||
(edit) => edit[languageField.value] === language[languagesPrimaryKeyField.value.field]
|
||||
) || {};
|
||||
|
||||
edits[props.languageField] = language[languagesPrimaryKeyField.value.field];
|
||||
edits[languageField.value] = language[languagesPrimaryKeyField.value.field];
|
||||
|
||||
return edits;
|
||||
});
|
||||
@@ -228,15 +261,15 @@ export default defineComponent({
|
||||
if (existingPrimaryKey) {
|
||||
newEdit = {
|
||||
...newEdit,
|
||||
[relatedPrimaryKeyField.value.field]: existingPrimaryKey,
|
||||
[translationsPrimaryKeyField.value.field]: existingPrimaryKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (currentEdits.some((edit) => edit[props.languageField] === newEdit[props.languageField])) {
|
||||
if (currentEdits.some((edit) => edit[languageField.value] === newEdit[languageField.value])) {
|
||||
emit(
|
||||
'input',
|
||||
currentEdits.map((edit) => {
|
||||
if (edit[props.languageField] === newEdit[props.languageField]) {
|
||||
if (edit[languageField.value] === newEdit[languageField.value]) {
|
||||
return newEdit;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type InterfaceConfig = {
|
||||
component: Component;
|
||||
options: DeepPartial<Field>[] | Component;
|
||||
types: typeof types[number][];
|
||||
relationship?: null | 'm2o' | 'o2m' | 'm2m';
|
||||
relationship?: null | 'm2o' | 'o2m' | 'm2m' | 'translations';
|
||||
hideLabel?: boolean;
|
||||
hideLoader?: boolean;
|
||||
system?: boolean;
|
||||
|
||||
@@ -153,6 +153,8 @@
|
||||
"field_m2o": "M2O Relationship",
|
||||
"field_o2m": "O2M Relationship",
|
||||
"field_m2m": "M2M Relationship",
|
||||
"field_translations": "Translations",
|
||||
"languages": "Languages",
|
||||
|
||||
"reset_page_preferences": "Reset Page Preferences",
|
||||
|
||||
@@ -185,6 +187,8 @@
|
||||
"configure_m2o": "Configure your Many-to-One Relationship...",
|
||||
"configure_o2m": "Configure your One-to-Many Relationship...",
|
||||
"configure_m2m": "Configure your Many-to-Many Relationship...",
|
||||
"configure_translations": "Configure your Translations...",
|
||||
"configure_languages": "Configure your Languages...",
|
||||
|
||||
"add_m2o_to_collection": "Add Many-to-One to \"{collection}\"",
|
||||
"add_o2m_to_collection": "Add One-to-Many to \"{collection}\"",
|
||||
@@ -297,6 +301,8 @@
|
||||
"item_in": "Item {primaryKey} in {collection} | Items {primaryKey} in {collection}",
|
||||
"this_collection": "This Collection",
|
||||
"related_collection": "Related Collection",
|
||||
"translations_collection": "Translations Collection",
|
||||
"languages_collection": "Languages Collection",
|
||||
|
||||
"related_values": "Related Values",
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_languages') }}</h2>
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('translations_collection') }}</div>
|
||||
<v-input disabled :value="relations[1].many_collection" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('languages_collection') }}</div>
|
||||
<v-input :class="{ matches: languagesCollectionExists }" db-safe key="languages-collection" v-model="relations[1].one_collection" :disabled="isExisting" :placeholder="$t('collection') + '...'">
|
||||
<template #append>
|
||||
<v-menu show-arrow placement="bottom-end">
|
||||
<template #activator="{ toggle }">
|
||||
<v-icon name="list_alt" @click="toggle" v-tooltip="$t('select_existing')" :disabled="isExisting" />
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in items"
|
||||
:key="item.value"
|
||||
:active="relations[1].one_collection === item.value"
|
||||
:disabled="item.disabled"
|
||||
@click="relations[1].one_collection = item.value"
|
||||
>
|
||||
<v-list-item-content>
|
||||
{{ item.text }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-input>
|
||||
</div>
|
||||
<v-input :value="relations[1].many_field" :placeholder="$t('foreign_key') + '...'"/>
|
||||
<v-input db-safe :disabled="languagesCollectionExists" v-model="relations[1].one_primary" :placeholder="$t('primary_key') + '...'" />
|
||||
<v-icon class="arrow" name="arrow_back" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, watch } from '@vue/composition-api';
|
||||
import { Relation } from '@/types';
|
||||
import { Field } from '@/types';
|
||||
import { orderBy } from 'lodash';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import { useCollectionsStore, useFieldsStore } from '@/stores';
|
||||
import i18n from '@/lang';
|
||||
|
||||
import { state } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isExisting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { items } = useRelation();
|
||||
|
||||
const languagesCollectionExists = computed(() => {
|
||||
return !!collectionsStore.getCollection(state.relations[1].one_collection);
|
||||
});
|
||||
|
||||
return {
|
||||
relations: state.relations,
|
||||
items,
|
||||
fieldData: state.fieldData,
|
||||
languagesCollectionExists,
|
||||
};
|
||||
|
||||
function useRelation() {
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
collectionsStore.state.collections.filter((collection) => {
|
||||
return collection.collection.startsWith('directus_') === false;
|
||||
}),
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
|
||||
const items = computed(() =>
|
||||
availableCollections.value.map((collection) => ({
|
||||
text: collection.collection,
|
||||
value: collection.collection,
|
||||
}))
|
||||
);
|
||||
|
||||
return { items };
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grid {
|
||||
--v-select-font-family: var(--family-monospace);
|
||||
--v-input-font-family: var(--family-monospace);
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 32px;
|
||||
margin-top: 48px;
|
||||
|
||||
.v-input.matches {
|
||||
--v-input-color: var(--primary);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
--v-icon-color: var(--primary);
|
||||
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.v-list {
|
||||
--v-list-item-content-font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
margin: 48px 0;
|
||||
}
|
||||
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -39,7 +39,6 @@
|
||||
<v-input v-model="fieldData.meta.note" :placeholder="$t('add_note')" />
|
||||
</div>
|
||||
|
||||
<!-- @todo base default value field type on selected type -->
|
||||
<div class="field full" v-if="fieldData.schema">
|
||||
<div class="label type-label">{{ $t('default_value') }}</div>
|
||||
<v-input
|
||||
@@ -228,7 +227,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const typeDisabled = computed(() => {
|
||||
return ['file', 'files', 'o2m', 'm2m', 'm2o'].includes(props.type);
|
||||
return ['file', 'files', 'o2m', 'm2m', 'm2o', 'translations'].includes(props.type);
|
||||
});
|
||||
|
||||
const typePlaceholder = computed(() => {
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_translations') }}</h2>
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('this_collection') }}</div>
|
||||
<v-input disabled :value="collection" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('translations_collection') }}</div>
|
||||
<v-input
|
||||
db-safe
|
||||
:placeholder="$t('collection') + '...'"
|
||||
v-model="relations[0].many_collection"
|
||||
:disabled="isExisting"
|
||||
:class="{ matches: translationsCollectionExists }"
|
||||
>
|
||||
<template #append>
|
||||
<v-menu show-arrow placement="bottom-end">
|
||||
<template #activator="{ toggle }">
|
||||
<v-icon name="list_alt" @click="toggle" v-tooltip="$t('select_existing')" :disabled="isExisting" />
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in items"
|
||||
:key="item.value"
|
||||
:active="relations[0].many_collection === item.value"
|
||||
:disabled="item.disabled"
|
||||
@click="relations[0].many_collection = item.value"
|
||||
>
|
||||
<v-list-item-content>
|
||||
{{ item.text }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-input>
|
||||
</div>
|
||||
<v-input disabled :value="currentCollectionPrimaryKey.field" />
|
||||
<v-input
|
||||
db-safe
|
||||
v-model="relations[0].many_field"
|
||||
:disabled="isExisting"
|
||||
:placeholder="$t('foreign_key') + '...'"
|
||||
:class="{ matches: translationsFieldExists }"
|
||||
>
|
||||
<template #append v-if="fields && fields.length > 0">
|
||||
<v-menu show-arrow placement="bottom-end">
|
||||
<template #activator="{ toggle }">
|
||||
<v-icon name="list_alt" @click="toggle" v-tooltip="$t('select_existing')" />
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list-item
|
||||
v-for="field in fields"
|
||||
:key="field.value"
|
||||
:active="relations[0].many_field === field.value"
|
||||
@click="relations[0].many_field = field.value"
|
||||
:disabled="field.disabled"
|
||||
>
|
||||
<v-list-item-content>
|
||||
{{ field.text }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-input>
|
||||
<v-icon class="arrow" name="arrow_forward" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
import { Relation, Field } from '@/types';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import { useFieldsStore, useCollectionsStore } from '@/stores';
|
||||
import { orderBy } from 'lodash';
|
||||
import i18n from '@/lang';
|
||||
|
||||
import { state } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isExisting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { items, fields, currentCollectionPrimaryKey, collectionMany } = useRelation();
|
||||
|
||||
const translationsCollectionExists = computed(() => {
|
||||
return collectionsStore.state.collections.find((col) => col.collection === state.relations?.[0].many_collection);
|
||||
});
|
||||
|
||||
const translationsFieldExists = computed(() => {
|
||||
if (!state?.relations?.[0].many_collection || !state?.relations?.[0].many_field) return false;
|
||||
return !!fieldsStore.getField(state.relations[0].many_collection, state.relations[0].many_field);
|
||||
});
|
||||
|
||||
return { relations: state.relations, items, fields, currentCollectionPrimaryKey, collectionMany, translationsCollectionExists, translationsFieldExists };
|
||||
|
||||
function useRelation() {
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
collectionsStore.state.collections.filter((collection) => {
|
||||
return (
|
||||
collection.collection.startsWith('directus_') === false &&
|
||||
collection.collection !== props.collection
|
||||
);
|
||||
}),
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
|
||||
const items = computed(() =>
|
||||
availableCollections.value.map((collection) => ({
|
||||
text: collection.collection,
|
||||
value: collection.collection,
|
||||
}))
|
||||
);
|
||||
|
||||
const currentCollectionPrimaryKey = computed(() =>
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(props.collection)
|
||||
);
|
||||
|
||||
const fields = computed(() => {
|
||||
if (!state.relations[0].many_collection) return [];
|
||||
|
||||
return fieldsStore.state.fields
|
||||
.filter((field) => field.collection === state.relations[0].many_collection)
|
||||
.map((field) => ({
|
||||
text: field.field,
|
||||
value: field.field,
|
||||
disabled:
|
||||
!field.schema ||
|
||||
field.schema?.is_primary_key ||
|
||||
field.type !== currentCollectionPrimaryKey.value.type,
|
||||
}));
|
||||
});
|
||||
|
||||
const collectionMany = computed({
|
||||
get() {
|
||||
return state.relations[0].many_collection!;
|
||||
},
|
||||
set(collection: string) {
|
||||
state.relations[0].many_collection = collection;
|
||||
state.relations[0].many_field = '';
|
||||
},
|
||||
});
|
||||
|
||||
return { availableCollections, items, fields, currentCollectionPrimaryKey, collectionMany };
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grid {
|
||||
--v-select-font-family: var(--family-monospace);
|
||||
--v-input-font-family: var(--family-monospace);
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 32px;
|
||||
margin-top: 48px;
|
||||
|
||||
.v-input.matches {
|
||||
--v-input-color: var(--primary);
|
||||
}
|
||||
|
||||
.v-icon.arrow {
|
||||
--v-icon-color: var(--primary);
|
||||
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.v-list {
|
||||
--v-list-item-content-font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
margin: 48px 0;
|
||||
}
|
||||
|
||||
.corresponding {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 32px;
|
||||
margin-top: 48px;
|
||||
|
||||
.arrow {
|
||||
--v-icon-color: var(--primary);
|
||||
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -27,6 +27,20 @@
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-translations
|
||||
v-if="currentTab[0] === 'translations'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-languages
|
||||
v-if="currentTab[0] === 'languages'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-interface
|
||||
v-if="currentTab[0] === 'interface'"
|
||||
:is-existing="field !== '+'"
|
||||
@@ -61,6 +75,8 @@ import SetupTabs from './components/tabs.vue';
|
||||
import SetupActions from './components/actions.vue';
|
||||
import SetupSchema from './components/schema.vue';
|
||||
import SetupRelationship from './components/relationship.vue';
|
||||
import SetupTranslations from './components/translations.vue';
|
||||
import SetupLanguages from './components/languages.vue';
|
||||
import SetupInterface from './components/interface.vue';
|
||||
import SetupDisplay from './components/display.vue';
|
||||
import { i18n } from '@/lang';
|
||||
@@ -81,6 +97,8 @@ export default defineComponent({
|
||||
SetupActions,
|
||||
SetupSchema,
|
||||
SetupRelationship,
|
||||
SetupTranslations,
|
||||
SetupLanguages,
|
||||
SetupInterface,
|
||||
SetupDisplay,
|
||||
},
|
||||
@@ -94,7 +112,7 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'standard' | 'file' | 'files' | 'm2o' | 'o2m' | 'm2m' | 'presentation'>,
|
||||
type: String as PropType<'standard' | 'file' | 'files' | 'm2o' | 'o2m' | 'm2m' | 'presentation' | 'translations'>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
@@ -116,7 +134,7 @@ export default defineComponent({
|
||||
const localType = computed(() => {
|
||||
if (props.field === '+') return props.type;
|
||||
|
||||
let type: 'standard' | 'file' | 'files' | 'o2m' | 'm2m' | 'm2o' | 'presentation' = 'standard';
|
||||
let type: 'standard' | 'file' | 'files' | 'o2m' | 'm2m' | 'm2o' | 'presentation' | 'translations' = 'standard';
|
||||
type = getLocalTypeForField(props.collection, props.field);
|
||||
|
||||
return type;
|
||||
@@ -173,6 +191,21 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
if (localType.value === 'translations') {
|
||||
tabs.splice(1, 0, ...[
|
||||
{
|
||||
text: i18n.t('translations'),
|
||||
value: 'translations',
|
||||
disabled: translationsDisabled(),
|
||||
},
|
||||
{
|
||||
text: i18n.t('languages'),
|
||||
value: 'languages',
|
||||
disabled: languagesDisabled(),
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
return tabs;
|
||||
});
|
||||
|
||||
@@ -181,10 +214,20 @@ export default defineComponent({
|
||||
return { tabs, currentTab };
|
||||
|
||||
function relationshipDisabled() {
|
||||
return (
|
||||
isEmpty(state.fieldData.field) ||
|
||||
(['o2m', 'm2m', 'files', 'm2o'].includes(localType.value) === false &&
|
||||
isEmpty(state.fieldData.type))
|
||||
return isEmpty(state.fieldData.field);
|
||||
}
|
||||
|
||||
function translationsDisabled() {
|
||||
return isEmpty(state.fieldData.field);
|
||||
}
|
||||
|
||||
function languagesDisabled() {
|
||||
return isEmpty(state.fieldData.field) || (
|
||||
state.relations.length === 0 ||
|
||||
isEmpty(state.relations[0].many_collection) ||
|
||||
isEmpty(state.relations[0].many_field) ||
|
||||
isEmpty(state.relations[0].one_collection) ||
|
||||
isEmpty(state.relations[0].one_primary)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,7 +242,7 @@ export default defineComponent({
|
||||
);
|
||||
}
|
||||
|
||||
if (['m2m', 'files'].includes(localType.value)) {
|
||||
if (['m2m', 'files', 'translations'].includes(localType.value)) {
|
||||
return (
|
||||
state.relations.length !== 2 ||
|
||||
isEmpty(state.relations[0].many_collection) ||
|
||||
@@ -294,11 +337,14 @@ export default defineComponent({
|
||||
function getLocalTypeForField(
|
||||
collection: string,
|
||||
field: string
|
||||
): 'standard' | 'file' | 'files' | 'o2m' | 'm2m' | 'm2o' {
|
||||
): 'standard' | 'file' | 'files' | 'o2m' | 'm2m' | 'm2o' | 'presentation' | 'translations' {
|
||||
const fieldInfo = fieldsStore.getField(collection, field);
|
||||
const relations = relationsStore.getRelationsForField(collection, field);
|
||||
|
||||
if (relations.length === 0) return 'standard';
|
||||
if (relations.length === 0) {
|
||||
if (fieldInfo.type === 'alias') return 'presentation';
|
||||
return 'standard';
|
||||
}
|
||||
|
||||
if (relations.length === 1) {
|
||||
const relation = relations[0];
|
||||
|
||||
@@ -27,7 +27,7 @@ export { state, availableInterfaces, availableDisplays, initLocalStore, clearLoc
|
||||
function initLocalStore(
|
||||
collection: string,
|
||||
field: string,
|
||||
type: 'standard' | 'file' | 'files' | 'm2o' | 'o2m' | 'm2m' | 'presentation'
|
||||
type: 'standard' | 'file' | 'files' | 'm2o' | 'o2m' | 'm2m' | 'presentation' | 'translations'
|
||||
) {
|
||||
const interfaces = getInterfaces();
|
||||
const displays = getDisplays();
|
||||
@@ -75,6 +75,8 @@ function initLocalStore(
|
||||
matchesRelation = inter.relationship === 'm2o';
|
||||
} else if (type === 'files') {
|
||||
matchesRelation = inter.relationship === 'm2m';
|
||||
} else if (type === 'translations') {
|
||||
matchesRelation = inter.relationship === 'translations';
|
||||
} else {
|
||||
matchesRelation = inter.relationship === type;
|
||||
}
|
||||
@@ -277,8 +279,6 @@ function initLocalStore(
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
console.log(state.newFields);
|
||||
}, 50);
|
||||
|
||||
if (!isExisting) {
|
||||
@@ -321,7 +321,7 @@ function initLocalStore(
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'm2m' || type === 'files') {
|
||||
if (type === 'm2m' || type === 'files' || type === 'translations') {
|
||||
delete state.fieldData.schema;
|
||||
delete state.fieldData.type;
|
||||
|
||||
@@ -424,26 +424,6 @@ function initLocalStore(
|
||||
];
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.fieldData.field,
|
||||
() => {
|
||||
state.relations[0].one_field = state.fieldData.field;
|
||||
|
||||
if (collectionExists(state.fieldData.field)) {
|
||||
state.relations[0].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[0].many_field = `${state.relations[0].one_collection}_${state.relations[0].one_primary}`;
|
||||
state.relations[1].one_collection = state.fieldData.field;
|
||||
state.relations[1].one_primary = fieldsStore.getPrimaryKeyFieldForCollection(collection)?.field;
|
||||
state.relations[1].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_${state.relations[1].one_primary}`;
|
||||
|
||||
if (state.relations[0].many_field === state.relations[1].many_field) {
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_related_${state.relations[1].one_primary}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.relations[0].many_collection,
|
||||
() => {
|
||||
@@ -490,34 +470,75 @@ function initLocalStore(
|
||||
syncNewCollectionsM2M
|
||||
)
|
||||
|
||||
let stop: WatchStopHandle;
|
||||
watch(
|
||||
() => state.fieldData.field,
|
||||
() => {
|
||||
state.relations[0].one_field = state.fieldData.field;
|
||||
|
||||
watch(() => state.autoFillJunctionRelation, (startWatching) => {
|
||||
if (startWatching) {
|
||||
stop = watch([() => state.relations[1].one_collection, () => state.relations[1].one_primary], ([newRelatedCollection, newRelatedPrimary]: string[]) => {
|
||||
if (newRelatedCollection) {
|
||||
state.relations[0].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[1].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[0].many_field = `${state.relations[0].one_collection}_${state.relations[0].one_primary}`;
|
||||
}
|
||||
|
||||
if (newRelatedPrimary) {
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_${state.relations[1].one_primary}`;
|
||||
}
|
||||
if (collectionExists(state.fieldData.field) && type !== 'translations') {
|
||||
state.relations[0].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[0].many_field = `${state.relations[0].one_collection}_${state.relations[0].one_primary}`;
|
||||
state.relations[1].one_collection = state.fieldData.field;
|
||||
state.relations[1].one_primary = fieldsStore.getPrimaryKeyFieldForCollection(collection)?.field;
|
||||
state.relations[1].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_${state.relations[1].one_primary}`;
|
||||
|
||||
if (state.relations[0].many_field === state.relations[1].many_field) {
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_related_${state.relations[1].one_primary}`;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
stop?.();
|
||||
}
|
||||
}
|
||||
}, { immediate: true });
|
||||
);
|
||||
|
||||
if (type !== 'translations') {
|
||||
let stop: WatchStopHandle;
|
||||
|
||||
watch(() => state.autoFillJunctionRelation, (startWatching) => {
|
||||
if (startWatching) {
|
||||
stop = watch([() => state.relations[1].one_collection, () => state.relations[1].one_primary], ([newRelatedCollection, newRelatedPrimary]: string[]) => {
|
||||
if (newRelatedCollection) {
|
||||
state.relations[0].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[1].many_collection = `${state.relations[0].one_collection}_${state.relations[1].one_collection}`;
|
||||
state.relations[0].many_field = `${state.relations[0].one_collection}_${state.relations[0].one_primary}`;
|
||||
}
|
||||
|
||||
if (newRelatedPrimary) {
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_${state.relations[1].one_primary}`;
|
||||
}
|
||||
|
||||
if (state.relations[0].many_field === state.relations[1].many_field) {
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_related_${state.relations[1].one_primary}`;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
stop?.();
|
||||
}
|
||||
}, { immediate: true });
|
||||
}
|
||||
|
||||
if (type === 'translations') {
|
||||
watch(() => state.relations[0].many_collection, (newManyCollection: string) => {
|
||||
state.relations[1].many_collection = newManyCollection;
|
||||
}, { immediate: true });
|
||||
|
||||
state.relations[0].many_collection = `${collection}_translations`;
|
||||
state.relations[0].many_field = `${collection}_${fieldsStore.getPrimaryKeyFieldForCollection(collection)?.field}`;
|
||||
state.relations[1].one_collection = 'languages';
|
||||
|
||||
if (collectionExists('languages')) {
|
||||
state.relations[1].one_primary = fieldsStore.getPrimaryKeyFieldForCollection('languages')?.field;
|
||||
} else {
|
||||
state.relations[1].one_primary = 'id';
|
||||
}
|
||||
|
||||
state.relations[1].many_field = `${state.relations[1].one_collection}_${state.relations[1].one_primary}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'presentation') {
|
||||
delete state.fieldData.schema;
|
||||
delete state.fieldData.type;
|
||||
|
||||
state.fieldData.meta.special = 'alias';
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,14 @@ export default defineComponent({
|
||||
icon: 'import_export',
|
||||
text: i18n.t('m2m_relationship'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'translations',
|
||||
icon: 'translate',
|
||||
text: i18n.t('translations')
|
||||
}
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user