mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
organize codebase and fix bugs
This commit is contained in:
1
api/package-lock.json
generated
1
api/package-lock.json
generated
@@ -69,7 +69,6 @@
|
||||
"color": "^3.1.2",
|
||||
"color-string": "^1.5.3",
|
||||
"cropperjs": "^1.5.7",
|
||||
"csslint": "^1.0.5",
|
||||
"date-fns": "^2.14.0",
|
||||
"diff": "^4.0.2",
|
||||
"highlight.js": "^10.2.0",
|
||||
|
||||
162
app/package-lock.json
generated
162
app/package-lock.json
generated
@@ -2432,6 +2432,51 @@
|
||||
"tslint": "^5.20.1",
|
||||
"webpack": "^4.0.0",
|
||||
"yorkie": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
|
||||
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/cli-plugin-unit-jest": {
|
||||
@@ -2571,6 +2616,17 @@
|
||||
"unique-filename": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
@@ -2654,6 +2710,18 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
@@ -2767,6 +2835,18 @@
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
@@ -7503,51 +7583,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
|
||||
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
@@ -16173,43 +16208,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.6.tgz",
|
||||
|
||||
@@ -27,7 +27,12 @@
|
||||
</template>
|
||||
|
||||
<template #item-append="{ item }" v-if="!disabled">
|
||||
<v-icon name="close" v-tooltip="$t('deselect')" class="deselect" @click.stop="deleteItem(item)" />
|
||||
<v-icon
|
||||
name="close"
|
||||
v-tooltip="$t('deselect')"
|
||||
class="deselect"
|
||||
@click.stop="deleteItem(item, items)"
|
||||
/>
|
||||
</template>
|
||||
</v-table>
|
||||
|
||||
@@ -38,6 +43,8 @@
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<pre>{{ JSON.stringify(value, null, 4) }}</pre>
|
||||
|
||||
<modal-detail
|
||||
v-if="!disabled"
|
||||
:active="currentlyEditing !== null"
|
||||
@@ -62,23 +69,20 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, PropType, toRefs } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
|
||||
import ModalDetail from '@/views/private/components/modal-detail';
|
||||
import ModalBrowse from '@/views/private/components/modal-browse';
|
||||
import { Filter, Field } from '@/types';
|
||||
import { Header } from '@/components/v-table/types';
|
||||
import { Relation } from '@/types';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
|
||||
import useActions from './actions';
|
||||
import useActions from './use-actions';
|
||||
import useRelation from './use-relation';
|
||||
import usePreview from './use-preview';
|
||||
import useEdit from './use-edit';
|
||||
import useSelection from './use-selection';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalDetail, ModalBrowse },
|
||||
props: {
|
||||
value: {
|
||||
type: Array as PropType<(number | string | Record<string, any>)[]>,
|
||||
type: Array as PropType<(number | string | Record<string, any>)[] | null>,
|
||||
default: null,
|
||||
},
|
||||
primaryKey: {
|
||||
@@ -103,15 +107,16 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { value } = toRefs(props);
|
||||
const relationsStore = useRelationsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { value, collection, field, fields } = toRefs(props);
|
||||
|
||||
const { junction, junctionCollection, relation, relationCollection, relationFields } = useRelation();
|
||||
const { tableHeaders, items, loading, error, displayItems, getJunctionFromRelatedId } = useTable();
|
||||
const { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit } = useEdits();
|
||||
const { stageSelection, selectModalActive, selectedPrimaryKeys } = useSelection();
|
||||
function emitter(newVal: any[] | null) {
|
||||
emit('input', newVal);
|
||||
}
|
||||
|
||||
const { junction, junctionCollection, relation, relationCollection, relationFields } = useRelation(
|
||||
collection,
|
||||
field
|
||||
);
|
||||
|
||||
const {
|
||||
deleteItem,
|
||||
@@ -120,7 +125,36 @@ export default defineComponent({
|
||||
getPrimaryKeys,
|
||||
getNewSelectedItems,
|
||||
getJunctionItem,
|
||||
} = useActions(value, items, relationFields, (newValue) => emit('input', newValue));
|
||||
getJunctionFromRelatedId,
|
||||
} = useActions(value, relationFields, emitter);
|
||||
|
||||
const { tableHeaders, items, loading, error, displayItems } = usePreview(
|
||||
value,
|
||||
fields,
|
||||
relationFields,
|
||||
getNewSelectedItems,
|
||||
getUpdatedItems,
|
||||
getNewItems,
|
||||
getPrimaryKeys
|
||||
);
|
||||
|
||||
const { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit } = useEdit(
|
||||
value,
|
||||
items,
|
||||
relationFields,
|
||||
emitter,
|
||||
getJunctionFromRelatedId
|
||||
);
|
||||
|
||||
const { stageSelection, selectModalActive, selectedPrimaryKeys } = useSelection(
|
||||
items,
|
||||
displayItems,
|
||||
relationFields,
|
||||
emitter,
|
||||
getNewItems,
|
||||
getJunctionFromRelatedId,
|
||||
getJunctionItem
|
||||
);
|
||||
|
||||
return {
|
||||
junction,
|
||||
@@ -141,310 +175,6 @@ export default defineComponent({
|
||||
selectedPrimaryKeys,
|
||||
items,
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds info about the current relationship, like related collection, primary key field
|
||||
* of the other collection etc
|
||||
*/
|
||||
function useRelation() {
|
||||
const relations = computed(() => {
|
||||
return relationsStore.getRelationsForField(props.collection, props.field) as Relation[];
|
||||
});
|
||||
|
||||
const junction = computed(() => {
|
||||
return relations.value.find((relation) => relation.one_collection === props.collection) as Relation;
|
||||
});
|
||||
|
||||
const relation = computed(() => {
|
||||
return relations.value.find((relation) => relation.one_collection !== props.collection) as Relation;
|
||||
});
|
||||
|
||||
const junctionCollection = computed(() => {
|
||||
return collectionsStore.getCollection(junction.value.many_collection)!;
|
||||
});
|
||||
|
||||
const relationCollection = computed(() => {
|
||||
return collectionsStore.getCollection(relation.value.one_collection)!;
|
||||
});
|
||||
|
||||
const { primaryKeyField: junctionPrimaryKeyField } = useCollection(junctionCollection.value.collection);
|
||||
const { primaryKeyField: relationPrimaryKeyField } = useCollection(relationCollection.value.collection);
|
||||
|
||||
const relationFields = computed(() => {
|
||||
return {
|
||||
junctionPkField: junctionPrimaryKeyField.value.field,
|
||||
relationPkField: relationPrimaryKeyField.value.field,
|
||||
junctionRelation: junction.value.junction_field as string,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
junction,
|
||||
junctionCollection,
|
||||
relation,
|
||||
relationCollection,
|
||||
relationFields,
|
||||
};
|
||||
}
|
||||
|
||||
function useTable() {
|
||||
// Using a ref for the table headers here means that the table itself can update the
|
||||
// values if it needs to. This allows the user to manually resize the columns for example
|
||||
const tableHeaders = ref<Header[]>([]);
|
||||
const loading = ref(false);
|
||||
const items = ref<Record<string, any>[]>([]);
|
||||
const error = ref(null);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
async (newVal) => {
|
||||
loading.value = true;
|
||||
const { junctionRelation, relationPkField, junctionPkField } = relationFields.value;
|
||||
if (junctionRelation === null) return;
|
||||
|
||||
// Load the junction items so we have access to the id's in the related collection
|
||||
const junctionItems = await loadRelatedIds(newVal);
|
||||
const relatedPrimaryKeys = junctionItems.map((junction) => junction[junctionRelation]);
|
||||
|
||||
const fields = [...(props.fields.length > 0 ? props.fields : getDefaultFields())];
|
||||
|
||||
if (fields.includes(relationPkField) === false) fields.push(relationPkField);
|
||||
|
||||
try {
|
||||
const endpoint = relationCollection.value.collection.startsWith('directus_')
|
||||
? `/${relationCollection.value.collection.substring(9)}`
|
||||
: `/items/${relationCollection.value.collection}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
fields: fields,
|
||||
[`filter[${relationPkField}][_in]`]: relatedPrimaryKeys.join(','),
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = (response.data.data as Record<string, any>[]) || [];
|
||||
|
||||
// Insert the related items into the junction items
|
||||
const existingItems = responseData.map((data) => {
|
||||
const id = data[relationPkField];
|
||||
const junction = junctionItems.find((junction) => junction[junctionRelation] === id);
|
||||
if (junction === undefined) return;
|
||||
|
||||
const newJunction = cloneDeep(junction);
|
||||
newJunction[junctionRelation] = data;
|
||||
return newJunction;
|
||||
}) as Record<string, any>[];
|
||||
|
||||
const updatedItems = getUpdatedItems();
|
||||
const newItems = getNewItems();
|
||||
|
||||
// Replace existing items with it's updated counterparts
|
||||
const newVal = existingItems
|
||||
.map((item) => {
|
||||
const updatedItem = updatedItems.find(
|
||||
(updated) => updated[junctionPkField] === item[junctionPkField]
|
||||
);
|
||||
if (updatedItem !== undefined) return updatedItem;
|
||||
return item;
|
||||
})
|
||||
.concat(...newItems);
|
||||
items.value = newVal;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
async function loadRelatedIds(newVal: (string | number | Record<string, any>)[]) {
|
||||
const { junctionPkField } = relationFields.value;
|
||||
|
||||
try {
|
||||
const endpoint = junctionCollection.value.collection.startsWith('directus_')
|
||||
? `/${junctionCollection.value.collection.substring(9)}`
|
||||
: `/items/${junctionCollection.value.collection}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
[`filter[${junctionPkField}][_in]`]: getPrimaryKeys().join(','),
|
||||
},
|
||||
});
|
||||
const data = response.data.data as Record<string, any>[];
|
||||
|
||||
// Add all items that already had the id of it's related item
|
||||
return data.concat(...getNewSelectedItems());
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getJunctionFromRelatedId(id: string | number) {
|
||||
const { relationPkField, junctionRelation } = relationFields.value;
|
||||
|
||||
return (
|
||||
items.value.find((item) => {
|
||||
return;
|
||||
typeof item === 'object' &&
|
||||
junctionRelation in item &&
|
||||
typeof item[junctionRelation] === 'object' &&
|
||||
relationPkField in item[junctionRelation] &&
|
||||
item[junctionRelation][relationPkField] === id;
|
||||
}) || null
|
||||
);
|
||||
}
|
||||
|
||||
const displayItems = computed(() => {
|
||||
const { junctionRelation } = relationFields.value;
|
||||
return items.value.map((item) => item[junctionRelation]);
|
||||
});
|
||||
|
||||
// Seeing we don't care about saving those tableHeaders, we can reset it whenever the
|
||||
// fields prop changes (most likely when we're navigating to a different o2m context)
|
||||
watch(
|
||||
() => props.fields,
|
||||
() => {
|
||||
tableHeaders.value = (props.fields.length > 0 ? props.fields : getDefaultFields())
|
||||
.map((fieldKey) => {
|
||||
const field = fieldsStore.getField(relationCollection.value.collection, fieldKey);
|
||||
|
||||
if (!field) return null;
|
||||
|
||||
const header: Header = {
|
||||
text: field.name,
|
||||
value: fieldKey,
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
width: null,
|
||||
field: {
|
||||
display: field.meta?.display,
|
||||
displayOptions: field.meta?.display_options,
|
||||
interface: field.meta?.interface,
|
||||
interfaceOptions: field.meta?.options,
|
||||
type: field.type,
|
||||
field: field.field,
|
||||
},
|
||||
};
|
||||
|
||||
return header;
|
||||
})
|
||||
.filter((h) => h) as Header[];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return { tableHeaders, displayItems, items, loading, error, getJunctionFromRelatedId };
|
||||
}
|
||||
|
||||
function useEdits() {
|
||||
// Primary key of the item we're currently editing. If null, the edit modal should be
|
||||
// closed
|
||||
const currentlyEditing = ref<string | number | null>(null);
|
||||
|
||||
// This keeps track of the starting values so we can match with it
|
||||
const editsAtStart = ref<Record<string, any>>({});
|
||||
|
||||
return { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit };
|
||||
|
||||
function editItem(item: any) {
|
||||
const { relationPkField } = relationFields.value;
|
||||
const hasPrimaryKey = relationPkField in item;
|
||||
|
||||
editsAtStart.value = item;
|
||||
currentlyEditing.value = hasPrimaryKey ? item[relationPkField] : -1;
|
||||
}
|
||||
|
||||
function stageEdits(edits: any) {
|
||||
const { relationPkField, junctionRelation, junctionPkField } = relationFields.value;
|
||||
const editsWrapped = { [junctionRelation]: edits };
|
||||
const hasPrimaryKey = relationPkField in editsAtStart.value;
|
||||
const junctionItem = hasPrimaryKey
|
||||
? getJunctionFromRelatedId(editsAtStart.value[relationPkField])
|
||||
: null;
|
||||
|
||||
const newValue = props.value.map((item) => {
|
||||
if (junctionItem !== null && junctionPkField in junctionItem) {
|
||||
const id = junctionItem[junctionPkField];
|
||||
|
||||
if (typeof item === 'object' && junctionPkField in item) {
|
||||
if (item[junctionPkField] === id)
|
||||
return { [junctionRelation]: edits, [junctionPkField]: id };
|
||||
} else if (typeof item === 'number' || typeof item === 'string') {
|
||||
if (item === id) return { [junctionRelation]: edits, [junctionPkField]: id };
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof item === 'object' && relationPkField in edits && junctionRelation in item) {
|
||||
const id = edits[relationPkField];
|
||||
const relatedItem = item[junctionRelation] as string | number | Record<string, any>;
|
||||
if (typeof relatedItem === 'object' && relationPkField in relatedItem) {
|
||||
if (relatedItem[relationPkField] === id) return editsWrapped;
|
||||
} else if (typeof relatedItem === 'string' || typeof relatedItem === 'number') {
|
||||
if (relatedItem === id) return editsWrapped;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEqual({ [junctionRelation]: editsAtStart.value }, item)) {
|
||||
return editsWrapped;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
if (hasPrimaryKey === false && newValue.includes(editsWrapped) === false) {
|
||||
newValue.push(editsWrapped);
|
||||
}
|
||||
|
||||
emit('input', newValue);
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editsAtStart.value = {};
|
||||
currentlyEditing.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function useSelection() {
|
||||
const selectModalActive = ref(false);
|
||||
|
||||
const selectedPrimaryKeys = computed<(number | string)[]>(() => {
|
||||
if (displayItems.value === null) return [];
|
||||
|
||||
const { relationPkField } = relationFields.value;
|
||||
|
||||
return displayItems.value
|
||||
.filter((currentItem) => relationPkField in currentItem)
|
||||
.map((currentItem) => currentItem[relationPkField]);
|
||||
});
|
||||
|
||||
return { stageSelection, selectModalActive, selectedPrimaryKeys };
|
||||
|
||||
function stageSelection(newSelection: (number | string)[]) {
|
||||
const { junctionRelation, junctionPkField } = relationFields.value;
|
||||
|
||||
const newItems = getNewItems();
|
||||
|
||||
if (junctionRelation === null) return;
|
||||
|
||||
const selection = newSelection.map((item) => {
|
||||
const junction = getJunctionFromRelatedId(item);
|
||||
if (junction === null) return { [junctionRelation]: item };
|
||||
|
||||
const updatedItem = getJunctionItem(junction[junctionPkField]);
|
||||
|
||||
return updatedItem === null ? { [junctionRelation]: item } : updatedItem;
|
||||
});
|
||||
|
||||
emit('input', [...selection, ...newItems]);
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultFields(): string[] {
|
||||
const fields = fieldsStore.getFieldsForCollection(relationCollection.value.collection);
|
||||
return fields.slice(0, 3).map((field: Field) => field.field);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { Ref } from '@vue/composition-api';
|
||||
|
||||
export type RelationFields = {
|
||||
junctionPkField: string;
|
||||
relationPkField: string;
|
||||
junctionRelation: string;
|
||||
};
|
||||
import { RelationInfo } from './use-relation';
|
||||
|
||||
export default function useActions(
|
||||
value: Ref<(string | number | Record<string, any>)[]>,
|
||||
items: Ref<Record<string, any>[]>,
|
||||
relationFields: Ref<RelationFields>,
|
||||
value: Ref<(string | number | Record<string, any>)[] | null>,
|
||||
relation: Ref<RelationInfo>,
|
||||
emit: (newValue: any[] | null) => void
|
||||
) {
|
||||
function getJunctionItem(id: string | number) {
|
||||
const { junctionPkField } = relationFields.value;
|
||||
const { junctionPkField } = relation.value;
|
||||
if (value.value === null) return null;
|
||||
|
||||
return (
|
||||
value.value.find(
|
||||
(item) =>
|
||||
@@ -24,18 +19,20 @@ export default function useActions(
|
||||
}
|
||||
|
||||
function getNewSelectedItems() {
|
||||
const { junctionRelation } = relationFields.value;
|
||||
const { junctionRelation } = relation.value;
|
||||
|
||||
if (value.value === null || junctionRelation === null) return [];
|
||||
|
||||
return value.value.filter(
|
||||
(item) => typeof item === 'object' && junctionRelation in item && typeof junctionRelation !== 'object'
|
||||
) as Record<string, any>[];
|
||||
}
|
||||
|
||||
function getNewItems() {
|
||||
const { junctionRelation, relationPkField } = relationFields.value;
|
||||
const { junctionRelation, relationPkField } = relation.value;
|
||||
|
||||
if (value.value === null || junctionRelation === null) return [];
|
||||
|
||||
return value.value.filter(
|
||||
(item) =>
|
||||
typeof item === 'object' &&
|
||||
@@ -46,9 +43,10 @@ export default function useActions(
|
||||
}
|
||||
|
||||
function getUpdatedItems() {
|
||||
const { junctionRelation, relationPkField } = relationFields.value;
|
||||
const { junctionRelation, relationPkField } = relation.value;
|
||||
|
||||
if (value.value === null || junctionRelation === null) return [];
|
||||
|
||||
return value.value.filter(
|
||||
(item) =>
|
||||
typeof item === 'object' &&
|
||||
@@ -60,13 +58,15 @@ export default function useActions(
|
||||
|
||||
function getExistingItems() {
|
||||
if (value.value === null) return [];
|
||||
|
||||
return value.value.filter((item) => typeof item === 'string' || typeof item === 'number');
|
||||
}
|
||||
|
||||
function getPrimaryKeys() {
|
||||
const { junctionPkField } = relationFields.value;
|
||||
function getPrimaryKeys(): (string | number)[] {
|
||||
const { junctionPkField } = relation.value;
|
||||
|
||||
if (value.value === null) return [];
|
||||
|
||||
return value.value
|
||||
.map((item) => {
|
||||
if (typeof item === 'object') {
|
||||
@@ -78,9 +78,9 @@ export default function useActions(
|
||||
.filter((i) => i);
|
||||
}
|
||||
|
||||
function getRelatedPrimaryKeys() {
|
||||
function getRelatedPrimaryKeys(): (string | number)[] {
|
||||
if (value.value === null) return [];
|
||||
const { junctionRelation, relationPkField } = relationFields.value;
|
||||
const { junctionRelation, relationPkField } = relation.value;
|
||||
return value.value
|
||||
.map((junctionItem) => {
|
||||
if (
|
||||
@@ -100,26 +100,29 @@ export default function useActions(
|
||||
.filter((i) => i);
|
||||
}
|
||||
|
||||
function deleteItem(item: Record<string, any>) {
|
||||
const { junctionRelation, relationPkField } = relationFields.value;
|
||||
function deleteItem(item: Record<string, any>, items: Record<string, any>[]) {
|
||||
if (value.value === null) return;
|
||||
const { junctionRelation, relationPkField } = relation.value;
|
||||
|
||||
const id = item[relationPkField] as number | string | undefined;
|
||||
|
||||
if (id !== undefined) return deleteItemWithId(id);
|
||||
if (id !== undefined) return deleteItemWithId(id, items);
|
||||
if (junctionRelation === null) return;
|
||||
|
||||
emit(
|
||||
value.value.filter((junctionItem) => {
|
||||
if (typeof junctionItem !== 'object' || junctionRelation in junctionItem === false) return true;
|
||||
return junctionItem[junctionRelation] !== item;
|
||||
})
|
||||
);
|
||||
const newVal = value.value.filter((junctionItem) => {
|
||||
if (typeof junctionItem !== 'object' || junctionRelation in junctionItem === false) return true;
|
||||
return junctionItem[junctionRelation] !== item;
|
||||
});
|
||||
|
||||
if (newVal.length === 0) emit(null);
|
||||
else emit(newVal);
|
||||
}
|
||||
|
||||
function deleteItemWithId(id: string | number) {
|
||||
const { junctionRelation, relationPkField, junctionPkField } = relationFields.value;
|
||||
function deleteItemWithId(id: string | number, items: Record<string, any>[]) {
|
||||
if (value.value === null) return;
|
||||
const { junctionRelation, relationPkField, junctionPkField } = relation.value;
|
||||
|
||||
const junctionItem = items.value.find(
|
||||
const junctionItem = items.find(
|
||||
(item) =>
|
||||
junctionRelation in item &&
|
||||
relationPkField in item[junctionRelation] &&
|
||||
@@ -130,27 +133,45 @@ export default function useActions(
|
||||
|
||||
// If it is a newly selected Item
|
||||
if (junctionPkField in junctionItem === false) {
|
||||
emit(
|
||||
value.value.filter((item) => {
|
||||
if (typeof item === 'object' && junctionRelation in item) {
|
||||
const jItem = item[junctionRelation];
|
||||
return typeof jItem === 'object' ? jItem[relationPkField] !== id : jItem !== id;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
const newVal = value.value.filter((item) => {
|
||||
if (typeof item === 'object' && junctionRelation in item) {
|
||||
const jItem = item[junctionRelation];
|
||||
return typeof jItem === 'object' ? jItem[relationPkField] !== id : jItem !== id;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (newVal.length === 0) emit(null);
|
||||
else emit(newVal);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it is an already existing item
|
||||
emit(
|
||||
value.value.filter((item) => {
|
||||
if (typeof item === 'object' && junctionPkField in item) {
|
||||
return junctionItem[junctionPkField] !== item[junctionPkField];
|
||||
} else {
|
||||
return junctionItem[junctionPkField] !== item;
|
||||
}
|
||||
})
|
||||
const newVal = value.value.filter((item) => {
|
||||
if (typeof item === 'object' && junctionPkField in item) {
|
||||
return junctionItem[junctionPkField] !== item[junctionPkField];
|
||||
} else {
|
||||
return junctionItem[junctionPkField] !== item;
|
||||
}
|
||||
});
|
||||
|
||||
if (newVal.length === 0) emit(null);
|
||||
else emit(newVal);
|
||||
}
|
||||
|
||||
function getJunctionFromRelatedId(id: string | number, items: Record<string, any>[]) {
|
||||
const { relationPkField, junctionRelation } = relation.value;
|
||||
|
||||
return (
|
||||
items.find((item) => {
|
||||
return (
|
||||
typeof item === 'object' &&
|
||||
junctionRelation in item &&
|
||||
typeof item[junctionRelation] === 'object' &&
|
||||
relationPkField in item[junctionRelation] &&
|
||||
item[junctionRelation][relationPkField] === id
|
||||
);
|
||||
}) || null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,6 +183,7 @@ export default function useActions(
|
||||
getExistingItems,
|
||||
getPrimaryKeys,
|
||||
getRelatedPrimaryKeys,
|
||||
getJunctionFromRelatedId,
|
||||
deleteItem,
|
||||
deleteItemWithId,
|
||||
};
|
||||
77
app/src/interfaces/many-to-many/use-edit.ts
Normal file
77
app/src/interfaces/many-to-many/use-edit.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Ref, ref } from '@vue/composition-api';
|
||||
import { RelationInfo } from './use-relation';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
export default function useEdit(
|
||||
value: Ref<(string | number | Record<string, any>)[] | null>,
|
||||
items: Ref<Record<string, any>[]>,
|
||||
relation: Ref<RelationInfo>,
|
||||
emit: (newVal: any[] | null) => void,
|
||||
getJunctionFromRelatedId: (id: string | number, items: Record<string, any>[]) => Record<string, any> | null
|
||||
) {
|
||||
// Primary key of the item we're currently editing. If null, the edit modal should be
|
||||
// closed
|
||||
const currentlyEditing = ref<string | number | null>(null);
|
||||
|
||||
// This keeps track of the starting values so we can match with it
|
||||
const editsAtStart = ref<Record<string, any>>({});
|
||||
|
||||
function editItem(item: any) {
|
||||
const { relationPkField } = relation.value;
|
||||
const hasPrimaryKey = relationPkField in item;
|
||||
|
||||
editsAtStart.value = item;
|
||||
currentlyEditing.value = hasPrimaryKey ? item[relationPkField] : -1;
|
||||
}
|
||||
|
||||
function stageEdits(edits: any) {
|
||||
const { relationPkField, junctionRelation, junctionPkField } = relation.value;
|
||||
const editsWrapped = { [junctionRelation]: edits };
|
||||
const hasPrimaryKey = relationPkField in editsAtStart.value;
|
||||
const junctionItem = hasPrimaryKey
|
||||
? getJunctionFromRelatedId(editsAtStart.value[relationPkField], items.value)
|
||||
: null;
|
||||
|
||||
const newValue = (value.value || []).map((item) => {
|
||||
if (junctionItem !== null && junctionPkField in junctionItem) {
|
||||
const id = junctionItem[junctionPkField];
|
||||
|
||||
if (typeof item === 'object' && junctionPkField in item) {
|
||||
if (item[junctionPkField] === id) return { [junctionRelation]: edits, [junctionPkField]: id };
|
||||
} else if (typeof item === 'number' || typeof item === 'string') {
|
||||
if (item === id) return { [junctionRelation]: edits, [junctionPkField]: id };
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof item === 'object' && relationPkField in edits && junctionRelation in item) {
|
||||
const id = edits[relationPkField];
|
||||
const relatedItem = item[junctionRelation] as string | number | Record<string, any>;
|
||||
if (typeof relatedItem === 'object' && relationPkField in relatedItem) {
|
||||
if (relatedItem[relationPkField] === id) return editsWrapped;
|
||||
} else if (typeof relatedItem === 'string' || typeof relatedItem === 'number') {
|
||||
if (relatedItem === id) return editsWrapped;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEqual({ [junctionRelation]: editsAtStart.value }, item)) {
|
||||
return editsWrapped;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
if (hasPrimaryKey === false && newValue.includes(editsWrapped) === false) {
|
||||
newValue.push(editsWrapped);
|
||||
}
|
||||
|
||||
if (newValue.length === 0) emit(null);
|
||||
else emit(newValue);
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editsAtStart.value = {};
|
||||
currentlyEditing.value = null;
|
||||
}
|
||||
|
||||
return { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit };
|
||||
}
|
||||
163
app/src/interfaces/many-to-many/use-preview.ts
Normal file
163
app/src/interfaces/many-to-many/use-preview.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Ref, ref, watch, computed } from '@vue/composition-api';
|
||||
import { Header } from '@/components/v-table/types';
|
||||
import { RelationInfo } from './use-relation';
|
||||
import { useFieldsStore } from '@/stores/';
|
||||
import { Field, Collection } from '@/types';
|
||||
import api from '@/api';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
export default function usePreview(
|
||||
value: Ref<(string | number | Record<string, any>)[] | null>,
|
||||
fields: Ref<string[]>,
|
||||
relation: Ref<RelationInfo>,
|
||||
getNewSelectedItems: () => Record<string, any>[],
|
||||
getUpdatedItems: () => Record<string, any>[],
|
||||
getNewItems: () => Record<string, any>[],
|
||||
getPrimaryKeys: () => (string | number)[]
|
||||
) {
|
||||
// Using a ref for the table headers here means that the table itself can update the
|
||||
// values if it needs to. This allows the user to manually resize the columns for example
|
||||
|
||||
const fieldsStore = useFieldsStore();
|
||||
const tableHeaders = ref<Header[]>([]);
|
||||
const loading = ref(false);
|
||||
const items = ref<Record<string, any>[]>([]);
|
||||
const error = ref(null);
|
||||
|
||||
watch(
|
||||
() => value.value,
|
||||
async (newVal) => {
|
||||
if (newVal === null) {
|
||||
items.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const { junctionRelation, relationPkField, junctionPkField } = relation.value;
|
||||
if (junctionRelation === null) return;
|
||||
|
||||
// Load the junction items so we have access to the id's in the related collection
|
||||
const junctionItems = await loadRelatedIds(newVal);
|
||||
const relatedPrimaryKeys = junctionItems.map((junction) => junction[junctionRelation]);
|
||||
|
||||
const filteredFields = [...(fields.value.length > 0 ? fields.value : getDefaultFields())];
|
||||
|
||||
if (filteredFields.includes(relationPkField) === false) filteredFields.push(relationPkField);
|
||||
|
||||
try {
|
||||
const endpoint = relation.value.relationCollection.startsWith('directus_')
|
||||
? `/${relation.value.relationCollection.substring(9)}`
|
||||
: `/items/${relation.value.relationCollection}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
fields: filteredFields,
|
||||
[`filter[${relationPkField}][_in]`]: relatedPrimaryKeys.join(','),
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = (response.data.data as Record<string, any>[]) || [];
|
||||
|
||||
// Insert the related items into the junction items
|
||||
const existingItems = responseData.map((data) => {
|
||||
const id = data[relationPkField];
|
||||
const junction = junctionItems.find((junction) => junction[junctionRelation] === id);
|
||||
if (junction === undefined) return;
|
||||
|
||||
const newJunction = cloneDeep(junction);
|
||||
newJunction[junctionRelation] = data;
|
||||
return newJunction;
|
||||
}) as Record<string, any>[];
|
||||
|
||||
const updatedItems = getUpdatedItems();
|
||||
const newItems = getNewItems();
|
||||
|
||||
// Replace existing items with it's updated counterparts
|
||||
const newVal = existingItems
|
||||
.map((item) => {
|
||||
const updatedItem = updatedItems.find(
|
||||
(updated) => updated[junctionPkField] === item[junctionPkField]
|
||||
);
|
||||
if (updatedItem !== undefined) return updatedItem;
|
||||
return item;
|
||||
})
|
||||
.concat(...newItems);
|
||||
items.value = newVal;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
async function loadRelatedIds(newVal: (string | number | Record<string, any>)[]) {
|
||||
const { junctionPkField } = relation.value;
|
||||
|
||||
try {
|
||||
const endpoint = relation.value.junctionCollection.startsWith('directus_')
|
||||
? `/${relation.value.junctionCollection.substring(9)}`
|
||||
: `/items/${relation.value.junctionCollection}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
[`filter[${junctionPkField}][_in]`]: getPrimaryKeys().join(','),
|
||||
},
|
||||
});
|
||||
const data = response.data.data as Record<string, any>[];
|
||||
|
||||
// Add all items that already had the id of it's related item
|
||||
return data.concat(...getNewSelectedItems());
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const displayItems = computed(() => {
|
||||
const { junctionRelation } = relation.value;
|
||||
return items.value.map((item) => item[junctionRelation]);
|
||||
});
|
||||
|
||||
// Seeing we don't care about saving those tableHeaders, we can reset it whenever the
|
||||
// fields prop changes (most likely when we're navigating to a different o2m context)
|
||||
watch(
|
||||
() => fields.value,
|
||||
() => {
|
||||
tableHeaders.value = (fields.value.length > 0 ? fields.value : getDefaultFields())
|
||||
.map((fieldKey) => {
|
||||
const field = fieldsStore.getField(relation.value.relationCollection, fieldKey);
|
||||
|
||||
if (!field) return null;
|
||||
|
||||
const header: Header = {
|
||||
text: field.name,
|
||||
value: fieldKey,
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
width: null,
|
||||
field: {
|
||||
display: field.meta?.display,
|
||||
displayOptions: field.meta?.display_options,
|
||||
interface: field.meta?.interface,
|
||||
interfaceOptions: field.meta?.options,
|
||||
type: field.type,
|
||||
field: field.field,
|
||||
},
|
||||
};
|
||||
|
||||
return header;
|
||||
})
|
||||
.filter((h) => h) as Header[];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function getDefaultFields(): string[] {
|
||||
const fields = fieldsStore.getFieldsForCollection(relation.value.relationCollection);
|
||||
return fields.slice(0, 3).map((field: Field) => field.field);
|
||||
}
|
||||
|
||||
return { tableHeaders, displayItems, items, loading, error };
|
||||
}
|
||||
60
app/src/interfaces/many-to-many/use-relation.ts
Normal file
60
app/src/interfaces/many-to-many/use-relation.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Ref, computed } from '@vue/composition-api';
|
||||
import { useCollectionsStore, useRelationsStore } from '@/stores/';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { Relation } from '@/types';
|
||||
|
||||
export type RelationInfo = {
|
||||
junctionPkField: string;
|
||||
relationPkField: string;
|
||||
junctionRelation: string;
|
||||
junctionCollection: string;
|
||||
relationCollection: string;
|
||||
};
|
||||
|
||||
export default function useRelation(collection: Ref<string>, field: Ref<string>) {
|
||||
const relationsStore = useRelationsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const relations = computed(() => {
|
||||
return relationsStore.getRelationsForField(collection.value, field.value) as Relation[];
|
||||
});
|
||||
|
||||
const junction = computed(() => {
|
||||
return relations.value.find((relation) => relation.one_collection === collection.value) as Relation;
|
||||
});
|
||||
|
||||
const relation = computed(() => {
|
||||
return relations.value.find((relation) => relation.one_collection !== collection.value) as Relation;
|
||||
});
|
||||
|
||||
const junctionCollection = computed(() => {
|
||||
return collectionsStore.getCollection(junction.value.many_collection)!;
|
||||
});
|
||||
|
||||
const relationCollection = computed(() => {
|
||||
return collectionsStore.getCollection(relation.value.one_collection)!;
|
||||
});
|
||||
|
||||
const { primaryKeyField: junctionPrimaryKeyField } = useCollection(junctionCollection.value.collection);
|
||||
const { primaryKeyField: relationPrimaryKeyField } = useCollection(relationCollection.value.collection);
|
||||
|
||||
const relationFields = computed(() => {
|
||||
return {
|
||||
junctionPkField: junctionPrimaryKeyField.value.field,
|
||||
relationPkField: relationPrimaryKeyField.value.field,
|
||||
junctionRelation: junction.value.junction_field as string,
|
||||
junctionCollection: junctionCollection.value.collection,
|
||||
relationCollection: relationCollection.value.collection,
|
||||
} as RelationInfo;
|
||||
});
|
||||
|
||||
return {
|
||||
junction,
|
||||
junctionCollection,
|
||||
relation,
|
||||
relationCollection,
|
||||
relationFields,
|
||||
junctionPrimaryKeyField,
|
||||
relationPrimaryKeyField,
|
||||
};
|
||||
}
|
||||
54
app/src/interfaces/many-to-many/use-selection.ts
Normal file
54
app/src/interfaces/many-to-many/use-selection.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Ref, ref, computed } from '@vue/composition-api';
|
||||
import { RelationInfo } from './use-relation';
|
||||
|
||||
export default function useSelection(
|
||||
items: Ref<Record<string, any>[]>,
|
||||
displayItems: Ref<Record<string, any>[]>,
|
||||
relation: Ref<RelationInfo>,
|
||||
emit: (newVal: any[] | null) => void,
|
||||
getNewItems: () => Record<string, any>[],
|
||||
getJunctionFromRelatedId: (id: string | number, items: Record<string, any>[]) => Record<string, any> | null,
|
||||
getJunctionItem: (id: string | number) => string | number | Record<string, any> | null
|
||||
) {
|
||||
const selectModalActive = ref(false);
|
||||
|
||||
const selectedPrimaryKeys = computed(() => {
|
||||
if (displayItems.value === null) return [];
|
||||
|
||||
const { relationPkField } = relation.value;
|
||||
|
||||
const selectedKeys: (number | string)[] = displayItems.value
|
||||
.filter((currentItem) => relationPkField in currentItem)
|
||||
.map((currentItem) => currentItem[relationPkField]);
|
||||
|
||||
return selectedKeys;
|
||||
});
|
||||
|
||||
function stageSelection(newSelection: (number | string)[]) {
|
||||
const { junctionRelation, junctionPkField } = relation.value;
|
||||
const newItems = getNewItems();
|
||||
|
||||
console.log('A');
|
||||
|
||||
const selection = newSelection.map((item) => {
|
||||
const junction = getJunctionFromRelatedId(item, items.value);
|
||||
console.log('----');
|
||||
console.log('item', item);
|
||||
console.log('items', items.value);
|
||||
console.log('junction', junction);
|
||||
|
||||
if (junction === null) return { [junctionRelation]: item };
|
||||
|
||||
const updatedItem = getJunctionItem(junction[junctionPkField]);
|
||||
console.log(item, ' has Updated: ', updatedItem);
|
||||
|
||||
return updatedItem === null ? { [junctionRelation]: item } : updatedItem;
|
||||
});
|
||||
|
||||
const newVal = [...selection, ...newItems];
|
||||
if (newVal.length === 0) emit(null);
|
||||
else emit(newVal);
|
||||
}
|
||||
|
||||
return { stageSelection, selectModalActive, selectedPrimaryKeys };
|
||||
}
|
||||
@@ -75,7 +75,7 @@ export default defineComponent({
|
||||
components: { ModalDetail, ModalBrowse },
|
||||
props: {
|
||||
value: {
|
||||
type: Array as PropType<(number | string | Record<string, any>)[]>,
|
||||
type: Array as PropType<(number | string | Record<string, any>)[] | null>,
|
||||
default: null,
|
||||
},
|
||||
primaryKey: {
|
||||
@@ -175,6 +175,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function deleteItem(item: Record<string, any>) {
|
||||
if (props.value === null) return;
|
||||
const relatedPrimKey = relatedPrimaryKeyField.value.field;
|
||||
|
||||
if (relatedPrimKey in item === false) {
|
||||
@@ -333,7 +334,7 @@ export default defineComponent({
|
||||
|
||||
const hasPrimaryKey = pkField in edits;
|
||||
|
||||
const newValue = props.value.map((item) => {
|
||||
const newValue = (props.value || []).map((item) => {
|
||||
if (
|
||||
typeof item === 'object' &&
|
||||
pkField in item &&
|
||||
@@ -358,7 +359,8 @@ export default defineComponent({
|
||||
newValue.push(edits);
|
||||
}
|
||||
|
||||
emit('input', newValue);
|
||||
if (newValue.length === 0) emit('input', null);
|
||||
else emit('input', newValue);
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
|
||||
Reference in New Issue
Block a user