Relational setup (#652)

* Add m2o relational setup

* Fix type check in localType

* wip o2m relational

* Finish relational setup on creation

* Account for m2m type
This commit is contained in:
Rijk van Zanten
2020-05-27 18:33:59 -04:00
committed by GitHub
parent 3fecddd90a
commit 88c0b1a7d9
7 changed files with 358 additions and 27 deletions

View File

@@ -106,6 +106,9 @@
"this_collection": "This Collection",
"related_collection": "Related Collection",
"choose_a_collection": "Choose a Collection...",
"choose_a_field": "Choose a Field...",
"submit": "Submit",
"system": "System",

View File

@@ -6,17 +6,25 @@
</div>
<div class="field">
<div class="type-label">{{ $t('junction_collection') }}</div>
<v-select :items="collectionItems" />
<v-select :items="collectionItems" v-model="junctionCollection" />
</div>
<div class="field">
<div class="type-label">{{ $t('related_collection') }}</div>
<v-select :items="collectionItems" />
<v-select :items="collectionItems" v-model="relatedCollection" />
</div>
<v-input disabled :value="field.field" />
<v-select :items="collectionItems" allow-other />
<v-select
:disabled="!junctionCollection"
:items="junctionFields"
v-model="junctionFieldCurrent"
/>
<div class="spacer" />
<div class="spacer" />
<v-select :items="collectionItems" allow-other />
<v-select
:disabled="!junctionCollection"
:items="junctionFields"
v-model="junctionFieldRelated"
/>
<v-input disabled value="id" />
<v-icon name="arrow_forward" />
<v-icon name="arrow_backward" />
@@ -28,6 +36,9 @@ import { defineComponent, computed, PropType } from '@vue/composition-api';
import useCollectionsStore from '@/stores/collections';
import orderBy from 'lodash/orderBy';
import { Field } from '@/stores/fields/types';
import useFieldsStore from '@/stores/fields/';
import { Relation } from '@/stores/relations/types';
import useSync from '@/composables/use-sync';
export default defineComponent({
props: {
@@ -39,9 +50,23 @@ export default defineComponent({
type: Object as PropType<Field>,
required: true,
},
isNew: {
type: Boolean,
required: true,
},
newRelations: {
type: Array as PropType<Partial<Relation>[]>,
required: true,
},
existingRelations: {
type: Array as PropType<Relation[]>,
required: true,
},
},
setup(props) {
setup(props, { emit }) {
const _newRelations = useSync(props, 'newRelations', emit);
const collectionsStore = useCollectionsStore();
const fieldsStore = useFieldsStore();
const availableCollections = computed(() => {
return orderBy(
@@ -63,7 +88,106 @@ export default defineComponent({
}))
);
return { availableCollections, collectionItems };
const defaultNewRelations = computed<Partial<Relation>[]>(() => [
{
collection_many: undefined,
field_many: undefined,
collection_one: props.field.collection,
field_one: props.field.field,
junction_field: undefined,
},
{
collection_many: undefined,
field_many: undefined,
collection_one: undefined,
field_one: undefined,
junction_field: undefined,
},
]);
const junctionCollection = computed({
get() {
return props.newRelations[0]?.collection_many || null;
},
set(newJunctionCollection: string | null) {
let relations: readonly Partial<Relation>[];
if (_newRelations.value.length === 0) relations = [...defaultNewRelations.value];
else relations = [..._newRelations.value];
relations[0].collection_many = newJunctionCollection || undefined;
relations[1].collection_many = newJunctionCollection || undefined;
_newRelations.value = relations;
},
});
const junctionFields = computed(() => {
if (!junctionCollection.value) return [];
return fieldsStore
.getFieldsForCollection(junctionCollection.value)
.map((field: Field) => ({
text: field.name,
value: field.field,
}));
});
const relatedCollection = computed({
get() {
return props.newRelations[1]?.collection_one || null;
},
set(newRelatedCollection: string | null) {
let relations: readonly Partial<Relation>[];
if (_newRelations.value.length === 0) relations = [...defaultNewRelations.value];
else relations = [..._newRelations.value];
relations[1].collection_one = newRelatedCollection || undefined;
_newRelations.value = relations;
},
});
const junctionFieldCurrent = computed({
get() {
return props.newRelations[0]?.field_many || null;
},
set(newJunctionField: string | null) {
let relations: readonly Partial<Relation>[];
if (_newRelations.value.length === 0) relations = [...defaultNewRelations.value];
else relations = [..._newRelations.value];
relations[0].field_many = newJunctionField || undefined;
relations[1].junction_field = newJunctionField || undefined;
_newRelations.value = relations;
},
});
const junctionFieldRelated = computed({
get() {
return props.newRelations[1]?.field_many || null;
},
set(newJunctionField: string | null) {
let relations: readonly Partial<Relation>[];
if (_newRelations.value.length === 0) relations = [...defaultNewRelations.value];
else relations = [..._newRelations.value];
relations[1].field_many = newJunctionField || undefined;
relations[0].junction_field = newJunctionField || undefined;
_newRelations.value = relations;
},
});
return {
availableCollections,
collectionItems,
junctionCollection,
junctionFields,
relatedCollection,
junctionFieldCurrent,
junctionFieldRelated,
};
},
});
</script>

View File

@@ -7,7 +7,13 @@
</div>
<div class="field">
<div class="type-label">{{ $t('related_collection') }}</div>
<v-select :items="items" />
<v-select
v-if="isNew"
:placeholder="$t('choose_a_collection')"
:items="items"
v-model="collectionOne"
/>
<v-input disabled v-else :value="existingRelation.collection_many" />
</div>
<v-input disabled :value="field.field" />
<v-input disabled value="id" />
@@ -19,11 +25,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 :disabled="isNew === false" :label="$t('add_field_related')" />
</div>
<div class="field">
<div class="type-label">{{ $t('corresponding_field_name') }}</div>
<v-input />
<v-input :disabled="isNew === false" />
</div>
<v-icon name="arrow_forward" />
</div>
@@ -35,6 +41,8 @@ import { defineComponent, computed, PropType } from '@vue/composition-api';
import useCollectionsStore from '@/stores/collections';
import orderBy from 'lodash/orderBy';
import { Field } from '@/stores/fields/types';
import { Relation } from '@/stores/relations/types';
import useSync from '@/composables/use-sync';
export default defineComponent({
props: {
@@ -46,8 +54,21 @@ export default defineComponent({
type: Object as PropType<Field>,
required: true,
},
existingRelations: {
type: Array as PropType<Relation[]>,
required: true,
},
newRelations: {
type: Array as PropType<Partial<Relation>[]>,
required: true,
},
isNew: {
type: Boolean,
required: true,
},
},
setup(props) {
setup(props, { emit }) {
const _newRelations = useSync(props, 'newRelations', emit);
const collectionsStore = useCollectionsStore();
const availableCollections = computed(() => {
@@ -70,7 +91,31 @@ export default defineComponent({
}))
);
return { availableCollections, items };
const existingRelation = computed(() => {
return props.existingRelations.find((relation) => {
return (
relation.field_many === props.field.field &&
relation.collection_many === props.field.collection
);
});
});
const collectionOne = computed({
get() {
return _newRelations.value[0]?.collection_one || null;
},
set(newCollectionOne: string | null) {
_newRelations.value = [
{
field_many: props.field.field,
collection_many: props.field.collection,
collection_one: newCollectionOne || undefined,
},
];
},
});
return { availableCollections, items, existingRelation, collectionOne };
},
});
</script>

View File

@@ -6,10 +6,23 @@
</div>
<div class="field">
<div class="type-label">{{ $t('related_collection') }}</div>
<v-select :items="collectionItems" />
<v-select
v-if="isNew"
:placeholder="$t('choose_a_collection')"
:items="collectionItems"
v-model="collectionMany"
/>
<v-input disabled v-else :value="existingRelation.collection_many" />
</div>
<v-input disabled :value="field.field" />
<v-select :items="collectionItems" allow-other />
<v-select
v-if="isNew"
:disabled="!collectionMany"
:items="collectionFields"
v-model="fieldMany"
:placeholder="!collectionMany ? $t('choose_a_collection') : $t('choose_a_field')"
/>
<v-input disabled v-else :value="existingRelation.field_many" />
<v-icon name="arrow_forward" />
</div>
</template>
@@ -19,6 +32,9 @@ import { defineComponent, computed, PropType } from '@vue/composition-api';
import useCollectionsStore from '@/stores/collections';
import orderBy from 'lodash/orderBy';
import { Field } from '@/stores/fields/types';
import { Relation } from '@/stores/relations/types';
import useSync from '@/composables/use-sync';
import useFieldsStore from '@/stores/fields';
export default defineComponent({
props: {
@@ -30,9 +46,23 @@ export default defineComponent({
type: Object as PropType<Field>,
required: true,
},
existingRelations: {
type: Array as PropType<Relation[]>,
required: true,
},
newRelations: {
type: Array as PropType<Partial<Relation>[]>,
required: true,
},
isNew: {
type: Boolean,
default: false,
},
},
setup(props) {
setup(props, { emit }) {
const collectionsStore = useCollectionsStore();
const fieldsStore = useFieldsStore();
const _newRelations = useSync(props, 'newRelations', emit);
const availableCollections = computed(() => {
return orderBy(
@@ -54,7 +84,64 @@ export default defineComponent({
}))
);
return { availableCollections, collectionItems };
const existingRelation = computed(() => {
return props.existingRelations.find((relation) => {
return (
relation.field_one === props.field.field &&
relation.collection_one === props.field.collection
);
});
});
const defaultNewRelation = computed(() => ({
collection_one: props.field.collection,
field_one: props.field.field,
}));
const collectionMany = computed({
get() {
return props.newRelations[0]?.collection_many || null;
},
set(newCollectionOne: string | null) {
_newRelations.value = [
{
...(props.newRelations[0] || defaultNewRelation.value),
collection_many: newCollectionOne || undefined,
},
];
},
});
const fieldMany = computed({
get() {
return props.newRelations[0]?.field_many || null;
},
set(newCollectionOne: string | null) {
_newRelations.value = [
{
...(props.newRelations[0] || defaultNewRelation),
field_many: newCollectionOne || undefined,
},
];
},
});
const collectionFields = computed(() => {
if (!collectionMany.value) return [];
return fieldsStore.getFieldsForCollection(collectionMany.value).map((field: Field) => ({
text: field.name,
value: field.field,
}));
});
return {
availableCollections,
collectionItems,
collectionMany,
fieldMany,
existingRelation,
collectionFields,
};
},
});
</script>

View File

@@ -1,37 +1,54 @@
<template>
<div>
<h2 class="type-title" v-if="isNew">{{ $t('relationship_setup_title') }}</h2>
<v-fancy-select :items="items" :value="value.type" @input="emitValue('type', $event)" />
<v-fancy-select
:items="items"
:value="value.type && value.type.toLowerCase()"
@input="emitValue('type', $event)"
:disabled="isNew === false"
/>
<many-to-one
v-if="value.type === 'm2o'"
v-if="value.type && value.type.toLowerCase() === 'm2o'"
:collection="value.collection"
:field="value"
@update:field="emit('input', $event)"
:existing-relations="existingRelations"
:new-relations.sync="_newRelations"
:is-new="isNew"
/>
<one-to-many
v-else-if="value.type === 'o2m'"
v-else-if="value.type && value.type.toLowerCase() === 'o2m'"
:collection="value.collection"
:field="value"
@update:field="emit('input', $event)"
:existing-relations="existingRelations"
:new-relations.sync="_newRelations"
:is-new="isNew"
/>
<many-to-many
v-else-if="value.type === 'm2m'"
v-else-if="value.type && value.type.toLowerCase() === 'm2m'"
:collection="value.collection"
:field="value"
@update:field="emit('input', $event)"
:existing-relations="existingRelations"
:new-relations.sync="_newRelations"
:is-new="isNew"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from '@vue/composition-api';
import { defineComponent, PropType, computed, watch } from '@vue/composition-api';
import { FancySelectItem } from '@/components/v-fancy-select/types';
import { Field } from '@/stores/fields/types';
import i18n from '@/lang';
import ManyToOne from './field-setup-relationship-m2o.vue';
import OneToMany from './field-setup-relationship-o2m.vue';
import ManyToMany from './field-setup-relationship-m2m.vue';
import useRelationsStore from '@/stores/relations';
import { Relation } from '@/stores/relations/types';
import useSync from '@/composables/use-sync';
export default defineComponent({
components: {
@@ -48,8 +65,22 @@ export default defineComponent({
type: Object as PropType<Field>,
required: true,
},
newRelations: {
type: Array as PropType<Partial<Relation>[]>,
required: true,
},
},
setup(props, { emit }) {
const relationsStore = useRelationsStore();
const _newRelations = useSync(props, 'newRelations', emit);
watch(
() => props.value.type,
() => {
_newRelations.value = [];
}
);
const items = computed<FancySelectItem[]>(() => {
return [
{
@@ -70,7 +101,12 @@ export default defineComponent({
];
});
return { emitValue, items };
const existingRelations = computed(() => {
if (props.isNew) return [];
return relationsStore.getRelationsForField(props.value.collection, props.value.field);
});
return { emitValue, items, existingRelations, _newRelations };
function emitValue(key: string, value: any) {
emit('input', {

View File

@@ -22,6 +22,7 @@
v-model="field"
:local-type.sync="localType"
:is-new="existingField === null"
:new-relations.sync="newRelations"
/>
<field-setup-interface
v-if="currentTab[0] === 'interface'"
@@ -70,6 +71,9 @@ import FieldSetupAdvanced from './field-setup-advanced.vue';
import SetupTabs from './setup-tabs.vue';
import SetupActions from './setup-actions.vue';
import useFieldsStore from '@/stores/fields/';
import { Relation } from '@/stores/relations/types';
import api from '@/api';
import useProjectsStore from '@/stores/projects';
import { LocalType } from './types';
@@ -103,12 +107,15 @@ export default defineComponent({
},
setup(props, { emit }) {
const fieldsStore = useFieldsStore();
const projectsStore = useProjectsStore();
const { field, localType } = usefield();
const { tabs, currentTab } = useTabs();
const { save, saving } = useSave();
return { field, tabs, currentTab, localType, save, saving };
const newRelations = ref<Partial<Relation>[]>([]);
return { field, tabs, currentTab, localType, save, saving, newRelations };
function usefield() {
const defaults = {
@@ -143,17 +150,29 @@ export default defineComponent({
const field = ref<any>({ ...defaults });
const localType = ref<LocalType>(null);
watch(
() => props.active,
() => {
if (!props.existingField) {
field.value = { ...defaults };
localType.value = null;
}
}
);
watch(
() => props.existingField,
(existingField: Field) => {
if (existingField) {
field.value = existingField;
if (existingField.type === 'file') {
const type = existingField.type.toLowerCase();
if (type === 'file') {
localType.value = 'file';
} else if (existingField.type === 'files') {
} else if (type === 'files') {
localType.value = 'files';
} else if (['o2m', 'm2o'].includes(existingField.type)) {
} else if (['o2m', 'm2o', 'm2m'].includes(type)) {
localType.value = 'relational';
} else {
localType.value = 'standard';
@@ -170,6 +189,14 @@ export default defineComponent({
function useTabs() {
const currentTab = ref(['field']);
watch(
() => props.active,
() => {
currentTab.value = ['field'];
}
);
const tabs = computed(() => {
const tabs = [
{
@@ -215,6 +242,10 @@ export default defineComponent({
try {
if (field.value.id === null) {
await fieldsStore.createField(props.collection, field.value);
for (const relation of newRelations.value) {
await createRelation(relation);
}
} else {
if (field.value.hasOwnProperty('name')) {
delete field.value.name;
@@ -233,6 +264,11 @@ export default defineComponent({
saving.value = false;
}
}
async function createRelation(relation: Partial<Relation>) {
const { currentProjectKey } = projectsStore.state;
await api.post(`/${currentProjectKey}/relations`, relation);
}
}
},
});

View File

@@ -27,7 +27,7 @@
dashed
outlined
large
@click="openFieldSetup"
@click="openFieldSetup()"
>
<v-icon name="add" />
@@ -63,7 +63,7 @@
dashed
outlined
large
@click="openFieldSetup"
@click="openFieldSetup()"
>
<v-icon name="add" />