mirror of
https://github.com/directus/directus.git
synced 2026-01-30 01:48:27 -05:00
Add o2m
This commit is contained in:
@@ -51,8 +51,13 @@
|
||||
"not_available_for_type": "Not Available for this Type",
|
||||
|
||||
"configure_m2o": "Configure your Many-to-One Relationship...",
|
||||
"configure_o2m": "Configure your One-to-Many Relationship...",
|
||||
"configure_m2m": "Configure your Many-to-Many Relationship...",
|
||||
|
||||
"add_m2o_to_collection": "Add Many-to-One to \"{collection}\"",
|
||||
"add_o2m_to_collection": "Add One-to-Many to \"{collection}\"",
|
||||
"add_m2m_to_collection": "Add Many-to-Many to \"{collection}\"",
|
||||
|
||||
"choose_a_type": "Choose a Type...",
|
||||
"determined_by_relationship": "Determined by Relationship",
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('create_corresponding_field') }}</div>
|
||||
<v-checkbox block :label="$t('add_field_related')" v-model="hasCorresponding" />
|
||||
<v-checkbox block :label="correspondingLabel" v-model="hasCorresponding" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('corresponding_field_name') }}</div>
|
||||
@@ -43,6 +43,7 @@ import { orderBy } from 'lodash';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import i18n from '@/lang';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -75,9 +76,17 @@ export default defineComponent({
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { items, relatedPrimary } = useRelation();
|
||||
const { hasCorresponding, correspondingField } = useCorresponding();
|
||||
const { hasCorresponding, correspondingField, correspondingLabel } = useCorresponding();
|
||||
|
||||
return { _relations, _newFields, items, relatedPrimary, hasCorresponding, correspondingField };
|
||||
return {
|
||||
_relations,
|
||||
_newFields,
|
||||
items,
|
||||
relatedPrimary,
|
||||
hasCorresponding,
|
||||
correspondingField,
|
||||
correspondingLabel,
|
||||
};
|
||||
|
||||
function useRelation() {
|
||||
const availableCollections = computed(() => {
|
||||
@@ -132,21 +141,6 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => _relations.value[0].collection_one,
|
||||
() => {
|
||||
if (hasCorresponding.value === true) {
|
||||
_newFields.value = [
|
||||
{
|
||||
...(_newFields.value[0] || {}),
|
||||
collection: _relations.value[0].collection_one,
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const correspondingField = computed({
|
||||
get() {
|
||||
return _newFields.value?.[0]?.field || null;
|
||||
@@ -168,7 +162,15 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
return { hasCorresponding, correspondingField };
|
||||
const correspondingLabel = computed(() => {
|
||||
if (_relations.value[0].collection_one) {
|
||||
return i18n.t('add_o2m_to_collection', { collection: _relations.value[0].collection_one });
|
||||
}
|
||||
|
||||
return i18n.t('add_field_related');
|
||||
});
|
||||
|
||||
return { hasCorresponding, correspondingField, correspondingLabel };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,181 @@
|
||||
<template>
|
||||
<div>O2M</div>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_o2m') }}</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('related_collection') }}</div>
|
||||
<v-select :placeholder="$t('choose_a_collection')" :items="items" v-model="collectionMany" />
|
||||
</div>
|
||||
<v-input disabled :value="currentCollectionPrimaryKey.field" />
|
||||
<v-select
|
||||
v-model="_relations[0].field_many"
|
||||
:disabled="!_relations[0].collection_many"
|
||||
:items="fields"
|
||||
:placeholder="!_relations[0].collection_many ? $t('choose_a_collection') : $t('choose_a_field')"
|
||||
/>
|
||||
<v-icon name="arrow_forward" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
import { Relation } from '@/stores/relations/types';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import { orderBy } from 'lodash';
|
||||
import i18n from '@/lang';
|
||||
|
||||
export default defineComponent({});
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
relations: {
|
||||
type: Array as PropType<Relation[]>,
|
||||
required: true,
|
||||
},
|
||||
newFields: {
|
||||
type: Array as PropType<DeepPartial<Field>[]>,
|
||||
required: true,
|
||||
},
|
||||
fieldData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const _relations = useSync(props, 'relations', emit);
|
||||
const _newFields = useSync(props, 'newFields', emit);
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { items, fields, currentCollectionPrimaryKey, collectionMany } = useRelation();
|
||||
|
||||
return { _relations, items, fields, currentCollectionPrimaryKey, collectionMany };
|
||||
|
||||
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 (!_relations.value[0].collection_many) return [];
|
||||
|
||||
return fieldsStore.state.fields
|
||||
.filter((field) => {
|
||||
if (field.collection !== _relations.value[0].collection_many) return false;
|
||||
|
||||
// Make sure the selected field matches the type of primary key of the current
|
||||
// collection. Otherwise you aren't able to properly save the primary key
|
||||
if (!field.database || field.database.type !== currentCollectionPrimaryKey.value.database.type)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((field) => field.field);
|
||||
});
|
||||
|
||||
const collectionMany = computed({
|
||||
get() {
|
||||
return _relations.value[0].collection_many;
|
||||
},
|
||||
set(collection: string) {
|
||||
_relations.value[0].collection_many = collection;
|
||||
_relations.value[0].field_many = '';
|
||||
},
|
||||
});
|
||||
|
||||
return { availableCollections, items, fields, currentCollectionPrimaryKey, collectionMany };
|
||||
}
|
||||
|
||||
function useCorresponding() {
|
||||
const hasCorresponding = computed({
|
||||
get() {
|
||||
return _newFields.value.length > 0;
|
||||
},
|
||||
set(enabled: boolean) {
|
||||
if (enabled === true) {
|
||||
_newFields.value = [
|
||||
{
|
||||
field: _relations.value[0].field_many,
|
||||
collection: _relations.value[0].collection_many,
|
||||
system: {
|
||||
interface: 'many-to-one',
|
||||
},
|
||||
},
|
||||
];
|
||||
} else {
|
||||
_newFields.value = [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const correspondingLabel = computed(() => {
|
||||
if (_relations.value[0].collection_many) {
|
||||
return i18n.t('add_m2o_to_collection', { collection: _relations.value[0].collection_many });
|
||||
}
|
||||
|
||||
return i18n.t('add_field_related');
|
||||
});
|
||||
|
||||
return { hasCorresponding, correspondingLabel };
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grid {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px 32px;
|
||||
margin-top: 48px;
|
||||
|
||||
.v-icon {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -60,6 +60,7 @@ import api from '@/api';
|
||||
import { Relation } from '@/stores/relations/types';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import router from '@/router';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -244,11 +245,56 @@ export default defineComponent({
|
||||
fieldData.database.type = field.database.type;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => relations.value[0].collection_one,
|
||||
() => {
|
||||
if (newFields.value.length > 0) {
|
||||
newFields.value[0].collection = relations.value[0].collection_one;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === 'o2m') {
|
||||
delete fieldData.database;
|
||||
|
||||
fieldData.system.special = 'o2m';
|
||||
|
||||
relations.value = [
|
||||
{
|
||||
collection_many: '',
|
||||
field_many: '',
|
||||
primary_many: '',
|
||||
|
||||
collection_one: props.collection,
|
||||
field_one: fieldData.field,
|
||||
primary_one: fieldsStore.getPrimaryKeyFieldForCollection(props.collection)?.field,
|
||||
},
|
||||
];
|
||||
|
||||
watch(
|
||||
() => fieldData.field,
|
||||
() => {
|
||||
relations.value[0].field_one = fieldData.field;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => relations.value[0].collection_many,
|
||||
() => {
|
||||
relations.value[0].primary_many = fieldsStore.getPrimaryKeyFieldForCollection(
|
||||
relations.value[0].collection_many
|
||||
).field;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === 'm2m' || props.type === 'files') {
|
||||
delete fieldData.database;
|
||||
|
||||
fieldData.system.special = 'm2m';
|
||||
|
||||
relations.value = [
|
||||
{
|
||||
collection_many: '',
|
||||
@@ -319,6 +365,8 @@ export default defineComponent({
|
||||
);
|
||||
|
||||
await api.post(`/relations`, relations.value);
|
||||
|
||||
router.push(`/settings/data-model/${props.collection}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user