Add m2o corresponding

This commit is contained in:
rijkvanzanten
2020-07-24 10:00:01 -04:00
parent f05277eb3a
commit 13fa2ed4b6
9 changed files with 170 additions and 36 deletions

View File

@@ -46,6 +46,7 @@
v-for="item in _items"
:key="item.value"
:active="multiple ? (value || []).includes(item.value) : value === item.value"
:disabled="item.disabled"
@click="multiple ? null : $emit('input', item.value)"
>
<v-list-item-content>
@@ -117,6 +118,7 @@ import { useCustomSelection, useCustomSelectionMultiple } from '@/composables/us
type Item = {
text: string;
value: string;
disabled?: boolean;
};
type ItemsRaw = (string | any)[];
@@ -207,6 +209,7 @@ export default defineComponent({
return {
text: item[props.itemText],
value: item[props.itemValue],
disabled: item.disabled,
};
});

View File

@@ -6,6 +6,7 @@ export default defineInterface(({ i18n }) => ({
name: i18n.t('file'),
icon: 'note_add',
component: InterfaceFile,
types: ['string'],
types: ['uuid'],
relationship: 'm2o',
options: [],
}));

View File

@@ -7,5 +7,6 @@ export default defineInterface(({ i18n }) => ({
icon: 'note_add',
component: InterfaceFiles,
types: ['alias'],
relationship: 'm2m',
options: [],
}));

View File

@@ -53,6 +53,9 @@
"configure_m2o": "Configure your Many-to-One Relationship...",
"configure_m2m": "Configure your Many-to-Many Relationship...",
"choose_a_type": "Choose a Type...",
"determined_by_relationship": "Determined by Relationship",
"include_seconds": "Include Seconds",
"add_note": "Add a helpful note for users...",

View File

@@ -100,6 +100,8 @@ export default defineComponent({
return fieldsStore.getFieldsForCollection(junctionCollection.value).map((field: Field) => ({
text: field.field,
value: field.field,
disabled:
_relations.value[0].field_many === field.field || _relations.value[1].field_many === field.field,
}));
});

View File

@@ -24,11 +24,11 @@
<div class="grid">
<div class="field">
<div class="type-label">{{ $t('create_corresponding_field') }}</div>
<v-checkbox block :label="$t('add_field_related')" />
<v-checkbox block :label="$t('add_field_related')" v-model="hasCorresponding" />
</div>
<div class="field">
<div class="type-label">{{ $t('corresponding_field_name') }}</div>
<v-input />
<v-input :disabled="hasCorresponding === false" v-model="correspondingField" />
</div>
<v-icon name="arrow_forward" />
</div>
@@ -36,8 +36,9 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from '@vue/composition-api';
import { defineComponent, PropType, computed, watch } from '@vue/composition-api';
import { Relation } from '@/stores/relations/types';
import { Field } from '@/stores/fields/types';
import { orderBy } from 'lodash';
import useSync from '@/composables/use-sync';
import useCollectionsStore from '@/stores/collections';
@@ -53,6 +54,10 @@ export default defineComponent({
type: Array as PropType<Relation[]>,
required: true,
},
newFields: {
type: Array as PropType<DeepPartial<Field>[]>,
required: true,
},
fieldData: {
type: Object,
required: true,
@@ -64,36 +69,107 @@ export default defineComponent({
},
setup(props, { emit }) {
const _relations = useSync(props, 'relations', emit);
const _newFields = useSync(props, 'newFields', 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 { items, relatedPrimary } = useRelation();
const { hasCorresponding, correspondingField } = useCorresponding();
return { _relations, _newFields, items, relatedPrimary, hasCorresponding, correspondingField };
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 items = computed(() =>
availableCollections.value.map((collection) => ({
text: collection.name,
value: collection.collection,
}))
);
const relatedPrimary = computed(() => {
return _relations.value[0].collection_one
? fieldsStore.getPrimaryKeyFieldForCollection(_relations.value[0].collection_one)?.field
: null;
});
const relatedPrimary = computed(() => {
return _relations.value[0].collection_one
? fieldsStore.getPrimaryKeyFieldForCollection(_relations.value[0].collection_one)?.field
: null;
});
return { items, relatedPrimary };
}
return { _relations, items, relatedPrimary };
function useCorresponding() {
const hasCorresponding = computed({
get() {
return _newFields.value.length > 0;
},
set(enabled: boolean) {
if (enabled === true) {
_newFields.value = [
{
field: '',
collection: _relations.value[0].collection_one,
system: {
special: 'o2m',
interface: 'one-to-many',
},
},
];
} else {
_newFields.value = [];
}
},
});
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;
},
set(field: string | null) {
_newFields.value = [
{
...(_newFields.value[0] || {}),
field: field || '',
},
];
_relations.value = [
{
..._relations.value[0],
field_one: field,
},
];
},
});
return { hasCorresponding, correspondingField };
}
},
});
</script>

View File

@@ -3,6 +3,7 @@
:collection="collection"
:field-data="fieldData"
:relations.sync="_relations"
:new-fields.sync="_newFields"
:type="type"
v-if="type === 'm2o' || type === 'file'"
/>
@@ -10,6 +11,7 @@
:collection="collection"
:field-data="fieldData"
:relations.sync="_relations"
:new-fields.sync="_newFields"
:type="type"
v-else-if="type === 'o2m'"
/>
@@ -17,6 +19,7 @@
:collection="collection"
:field-data="fieldData"
:relations.sync="_relations"
:new-fields.sync="_newFields"
:type="type"
v-else-if="type === 'm2m' || type === 'files'"
/>
@@ -25,6 +28,7 @@
<script lang="ts">
import { defineComponent, PropType } from '@vue/composition-api';
import { Relation } from '@/stores/relations/types';
import { Field } from '@/stores/fields/types';
import useSync from '@/composables/use-sync';
import RelationshipM2o from './relationship-m2o.vue';
@@ -46,6 +50,10 @@ export default defineComponent({
type: Array as PropType<Relation[]>,
required: true,
},
newFields: {
type: Array as PropType<DeepPartial<Field>[]>,
required: true,
},
fieldData: {
type: Object,
required: true,
@@ -57,8 +65,9 @@ export default defineComponent({
},
setup(props, { emit }) {
const _relations = useSync(props, 'relations', emit);
const _newFields = useSync(props, 'newFields', emit);
return { _relations };
return { _relations, _newFields };
},
});
</script>

View File

@@ -17,6 +17,7 @@
:value="_field.database.type"
@input="setType"
:items="typesWithLabels"
:placeholder="typePlaceholder"
/>
</div>
@@ -101,10 +102,18 @@ export default defineComponent({
);
const typeDisabled = computed(() => {
return ['file', 'files', 'o2m', 'm2m'].includes(props.type);
return ['file', 'files', 'o2m', 'm2m', 'm2o'].includes(props.type);
});
return { _field, typesWithLabels, setType, typeDisabled };
const typePlaceholder = computed(() => {
if (props.type === 'm2o') {
return i18n.t('determined_by_relationship');
}
return i18n.t('choose_a_type');
});
return { _field, typesWithLabels, setType, typeDisabled, typePlaceholder };
function setType(value: typeof types[number]) {
if (value === 'uuid') {

View File

@@ -16,6 +16,7 @@
v-if="currentTab[0] === 'relationship'"
:field-data.sync="fieldData"
:relations.sync="relations"
:new-fields.sync="newFields"
:type="type"
/>
@@ -58,6 +59,7 @@ import { isEmpty } from 'lodash';
import api from '@/api';
import { Relation } from '@/stores/relations/types';
import { useFieldsStore } from '@/stores/fields';
import { Field } from '@/stores/fields/types';
export default defineComponent({
components: {
@@ -92,11 +94,11 @@ export default defineComponent({
const fieldsStore = useFieldsStore();
const { tabs, currentTab } = useTabs();
const { fieldData, relations } = useData();
const { fieldData, relations, newFields } = useData();
const saving = ref(false);
return { active, tabs, currentTab, fieldData, saveField, saving, relations };
return { active, tabs, currentTab, fieldData, saveField, saving, relations, newFields };
function useTabs() {
const tabs = computed(() => {
@@ -136,7 +138,7 @@ export default defineComponent({
function relationshipDisabled() {
return (
isEmpty(fieldData.field) ||
(['o2m', 'm2m', 'files'].includes(props.type) === false && isEmpty(fieldData.database.type))
(['o2m', 'm2m', 'files', 'm2o'].includes(props.type) === false && isEmpty(fieldData.database.type))
);
}
@@ -151,8 +153,15 @@ export default defineComponent({
}
if (['m2m', 'files'].includes(props.type)) {
/** @todo extend with all values */
return relations.value.length !== 2;
return (
relations.value.length !== 2 ||
isEmpty(relations.value[0].collection_many) ||
isEmpty(relations.value[0].field_many) ||
isEmpty(relations.value[0].field_one) ||
isEmpty(relations.value[1].collection_many) ||
isEmpty(relations.value[1].field_many) ||
isEmpty(relations.value[1].collection_one)
);
}
return isEmpty(fieldData.field) || isEmpty(fieldData.database.type);
@@ -183,6 +192,10 @@ export default defineComponent({
const relations = ref<Partial<Relation>[]>([]);
// Allow the panes to create additional fields outside of this one. This is used to
// auto generated related o2m columns / junction collections etc
const newFields = ref<DeepPartial<Field>[]>([]);
if (props.type === 'file') {
fieldData.database.type = 'uuid';
@@ -221,6 +234,16 @@ export default defineComponent({
relations.value[0].field_many = fieldData.field;
}
);
// Make sure to keep the current m2o field type in sync with the primary key of the
// selected related collection
watch(
() => relations.value[0].collection_one,
() => {
const field = fieldsStore.getPrimaryKeyFieldForCollection(relations.value[0].collection_one);
fieldData.database.type = field.database.type;
}
);
}
if (props.type === 'm2m' || props.type === 'files') {
@@ -280,7 +303,7 @@ export default defineComponent({
);
}
return { fieldData, relations };
return { fieldData, relations, newFields };
}
async function saveField() {
@@ -288,6 +311,13 @@ export default defineComponent({
try {
await api.post(`/fields/${props.collection}`, fieldData);
await Promise.all(
newFields.value.map((newField) => {
return api.post(`/fields/${newField.collection}`, newField);
})
);
await api.post(`/relations`, relations.value);
} catch (error) {
console.error(error);