mirror of
https://github.com/directus/directus.git
synced 2026-01-29 16:28:02 -05:00
Add m2m config
This commit is contained in:
@@ -36,6 +36,7 @@ export default function useFormFields(fields: Ref<Field[]>) {
|
||||
special: null,
|
||||
translation: null,
|
||||
width: 'full',
|
||||
note: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"not_available_for_type": "Not Available for this Type",
|
||||
|
||||
"configure_m2o": "Configure your Many-to-One Relationship...",
|
||||
"configure_m2m": "Configure your Many-to-Many Relationship...",
|
||||
|
||||
"include_seconds": "Include Seconds",
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export default defineComponent({
|
||||
const _field = useSync(props, 'fieldData', emit);
|
||||
const availabledisplays = computed(() =>
|
||||
displays.filter((display) => {
|
||||
const matchesType = display.types.includes(props.fieldData.database.type);
|
||||
const matchesType = display.types.includes(props.fieldData.database?.type || 'alias');
|
||||
const matchesRelation = true;
|
||||
|
||||
// if (props.type === 'standard') {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default defineComponent({
|
||||
const _field = useSync(props, 'fieldData', emit);
|
||||
const availableInterfaces = computed(() =>
|
||||
interfaces.filter((inter) => {
|
||||
const matchesType = inter.types.includes(props.fieldData.database.type);
|
||||
const matchesType = inter.types.includes(props.fieldData.database?.type || 'alias');
|
||||
let matchesRelation = false;
|
||||
|
||||
if (props.type === 'standard') {
|
||||
|
||||
@@ -1,9 +1,143 @@
|
||||
<template>
|
||||
<div>M2M</div>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_m2m') }}</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('junction_collection') }}</div>
|
||||
<v-select :items="collectionItems" v-model="junctionCollection" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('related_collection') }}</div>
|
||||
<v-select
|
||||
:disabled="type === 'files'"
|
||||
:items="collectionItems"
|
||||
v-model="_relations[1].collection_one"
|
||||
/>
|
||||
</div>
|
||||
<v-input disabled :value="_relations[0].primary_one" />
|
||||
<v-select :disabled="!junctionCollection" :items="junctionFields" v-model="_relations[0].field_many" />
|
||||
<div class="spacer" />
|
||||
<div class="spacer" />
|
||||
<v-select :disabled="!junctionCollection" :items="junctionFields" v-model="_relations[1].field_many" />
|
||||
<v-input disabled :value="_relations[1].primary_one" />
|
||||
<v-icon name="arrow_forward" />
|
||||
<v-icon name="arrow_backward" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, computed, PropType } from '@vue/composition-api';
|
||||
import { orderBy } from 'lodash';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import { Relation } from '@/stores/relations/types';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
|
||||
export default defineComponent({});
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
relations: {
|
||||
type: Array as PropType<Relation[]>,
|
||||
required: true,
|
||||
},
|
||||
fieldData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const _relations = useSync(props, 'relations', emit);
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
collectionsStore.state.collections.filter((collection) => {
|
||||
return (
|
||||
collection.collection.startsWith('directus_') === false &&
|
||||
collection.collection !== props.collection
|
||||
);
|
||||
}),
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
|
||||
const collectionItems = computed(() =>
|
||||
availableCollections.value.map((collection) => ({
|
||||
text: collection.collection,
|
||||
value: collection.collection,
|
||||
}))
|
||||
);
|
||||
|
||||
const junctionCollection = computed({
|
||||
get() {
|
||||
return _relations.value[0].collection_many;
|
||||
},
|
||||
set(collection: string) {
|
||||
_relations.value[0].collection_many = collection;
|
||||
_relations.value[1].collection_many = collection;
|
||||
},
|
||||
});
|
||||
|
||||
const junctionFields = computed(() => {
|
||||
if (!junctionCollection.value) return [];
|
||||
|
||||
return fieldsStore.getFieldsForCollection(junctionCollection.value).map((field: Field) => ({
|
||||
text: field.field,
|
||||
value: field.field,
|
||||
}));
|
||||
});
|
||||
|
||||
return { _relations, collectionItems, junctionCollection, junctionFields };
|
||||
},
|
||||
});
|
||||
</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(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-top: 48px;
|
||||
|
||||
.v-icon {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
|
||||
&:first-of-type {
|
||||
bottom: 85px;
|
||||
left: 32.8%;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
bottom: 14px;
|
||||
left: 67%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -100,6 +100,9 @@ export default defineComponent({
|
||||
|
||||
<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, 1fr);
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
<div class="field">
|
||||
<div class="label type-label">{{ $t('type') }}</div>
|
||||
<v-input v-if="!_field.database" :value="$t('alias')" disabled />
|
||||
<v-select
|
||||
v-else
|
||||
:disabled="typeDisabled"
|
||||
:value="_field.database.type"
|
||||
@input="setType"
|
||||
@@ -20,11 +22,11 @@
|
||||
|
||||
<div class="field full">
|
||||
<div class="label type-label">{{ $t('note') }}</div>
|
||||
<v-input v-model="_field.database.comment" :placeholder="$t('add_note')" />
|
||||
<v-input v-model="_field.system.comment" :placeholder="$t('add_note')" />
|
||||
</div>
|
||||
|
||||
<!-- @todo base default value field type on selected type -->
|
||||
<div class="field">
|
||||
<div class="field" v-if="_field.database">
|
||||
<div class="label type-label">{{ $t('default_value') }}</div>
|
||||
<v-input
|
||||
class="monospace"
|
||||
@@ -33,7 +35,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field" v-if="_field.database">
|
||||
<div class="label type-label">{{ $t('length') }}</div>
|
||||
<v-input
|
||||
type="number"
|
||||
@@ -43,7 +45,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field" v-if="_field.database">
|
||||
<div class="label type-label">{{ $t('allow_null') }}</div>
|
||||
<v-checkbox v-model="_field.database.is_nullable" :label="$t('allow_null_label')" block />
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
:field-data.sync="fieldData"
|
||||
:type="type"
|
||||
/>
|
||||
|
||||
<setup-relationship
|
||||
:collection="collection"
|
||||
v-if="currentTab[0] === 'relationship'"
|
||||
@@ -17,12 +18,14 @@
|
||||
:relations.sync="relations"
|
||||
:type="type"
|
||||
/>
|
||||
|
||||
<setup-interface
|
||||
:collection="collection"
|
||||
v-if="currentTab[0] === 'interface'"
|
||||
:field-data.sync="fieldData"
|
||||
:type="type"
|
||||
/>
|
||||
|
||||
<setup-display
|
||||
:collection="collection"
|
||||
v-if="currentTab[0] === 'display'"
|
||||
@@ -115,7 +118,7 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
if (['o2m', 'm2o', 'm2m'].includes(props.type)) {
|
||||
if (['o2m', 'm2o', 'm2m', 'files'].includes(props.type)) {
|
||||
tabs.splice(1, 0, {
|
||||
text: i18n.t('relationship'),
|
||||
value: 'relationship',
|
||||
@@ -131,7 +134,10 @@ export default defineComponent({
|
||||
return { tabs, currentTab };
|
||||
|
||||
function relationshipDisabled() {
|
||||
return isEmpty(fieldData.field) || isEmpty(fieldData.database.type);
|
||||
return (
|
||||
isEmpty(fieldData.field) ||
|
||||
(['o2m', 'm2m', 'files'].includes(props.type) === false && isEmpty(fieldData.database.type))
|
||||
);
|
||||
}
|
||||
|
||||
function interfaceDisplayDisabled() {
|
||||
@@ -162,7 +168,6 @@ export default defineComponent({
|
||||
default_value: undefined,
|
||||
max_length: undefined,
|
||||
is_nullable: true,
|
||||
comment: undefined,
|
||||
},
|
||||
system: {
|
||||
hidden: false,
|
||||
@@ -172,6 +177,7 @@ export default defineComponent({
|
||||
display_options: undefined,
|
||||
readonly: false,
|
||||
special: undefined,
|
||||
note: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -217,6 +223,63 @@ export default defineComponent({
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === 'm2m' || props.type === 'files') {
|
||||
delete fieldData.database;
|
||||
|
||||
relations.value = [
|
||||
{
|
||||
collection_many: '',
|
||||
field_many: '',
|
||||
primary_many: '',
|
||||
collection_one: props.collection,
|
||||
field_one: fieldData.field,
|
||||
primary_one: fieldsStore.getPrimaryKeyFieldForCollection(props.collection)?.field,
|
||||
},
|
||||
{
|
||||
collection_many: '',
|
||||
field_many: '',
|
||||
primary_many: '',
|
||||
collection_one: props.type === 'files' ? 'directus_files' : '',
|
||||
field_one: null,
|
||||
primary_one:
|
||||
props.type === 'files'
|
||||
? fieldsStore.getPrimaryKeyFieldForCollection('directus_files')?.field
|
||||
: '',
|
||||
},
|
||||
];
|
||||
|
||||
watch(
|
||||
() => fieldData.field,
|
||||
() => {
|
||||
relations.value[0].field_one = fieldData.field;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => relations.value[0].collection_many,
|
||||
() => {
|
||||
const pkField = fieldsStore.getPrimaryKeyFieldForCollection(relations.value[0].collection_many)
|
||||
?.field;
|
||||
relations.value[0].primary_many = pkField;
|
||||
relations.value[1].primary_many = pkField;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => relations.value[0].field_many,
|
||||
() => {
|
||||
relations.value[1].junction_field = relations.value[0].field_many;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => relations.value[1].field_many,
|
||||
() => {
|
||||
relations.value[0].junction_field = relations.value[1].field_many;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return { fieldData, relations };
|
||||
}
|
||||
|
||||
@@ -225,6 +288,7 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
await api.post(`/fields/${props.collection}`, fieldData);
|
||||
await api.post(`/relations`, relations.value);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
||||
@@ -33,6 +33,7 @@ const fakeFilesField: Field = {
|
||||
readonly: true,
|
||||
width: 'full',
|
||||
group: null,
|
||||
note: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,6 +55,7 @@ function getSystemDefault(collection: string, field: string): Field['system'] {
|
||||
special: null,
|
||||
translation: null,
|
||||
width: 'full',
|
||||
note: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export type SystemField = {
|
||||
special: string | null;
|
||||
translation: null | Translation[];
|
||||
width: Width | null;
|
||||
note: string | null;
|
||||
};
|
||||
|
||||
export interface FieldRaw {
|
||||
|
||||
Reference in New Issue
Block a user