mirror of
https://github.com/directus/directus.git
synced 2026-01-30 08:47:57 -05:00
Merge branch 'main' into aggregation
This commit is contained in:
@@ -192,6 +192,6 @@ fields:
|
||||
- field: item_duplication_fields
|
||||
special:
|
||||
- json
|
||||
interface: code
|
||||
interface: system-field-tree
|
||||
options:
|
||||
language: JSON
|
||||
collectionField: collection
|
||||
|
||||
@@ -15,6 +15,7 @@ import VBreadcrumb from './v-breadcrumb';
|
||||
import VButton from './v-button/';
|
||||
import VCard, { VCardActions, VCardSubtitle, VCardText, VCardTitle } from './v-card';
|
||||
import VCheckbox from './v-checkbox/';
|
||||
import VCheckboxTree from './v-checkbox-tree/';
|
||||
import VChip from './v-chip/';
|
||||
import VDetail from './v-detail';
|
||||
import VDialog from './v-dialog';
|
||||
@@ -60,6 +61,7 @@ export function registerComponents(app: App): void {
|
||||
app.component('v-card-title', VCardTitle);
|
||||
app.component('v-card', VCard);
|
||||
app.component('v-checkbox', VCheckbox);
|
||||
app.component('v-checkbox-tree', VCheckboxTree);
|
||||
app.component('v-chip', VChip);
|
||||
app.component('v-detail', VDetail);
|
||||
app.component('v-dialog', VDialog);
|
||||
|
||||
4
app/src/components/v-checkbox-tree/index.ts
Normal file
4
app/src/components/v-checkbox-tree/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import VCheckboxTree from './v-checkbox-tree.vue';
|
||||
|
||||
export { VCheckboxTree };
|
||||
export default VCheckboxTree;
|
||||
418
app/src/components/v-checkbox-tree/v-checkbox-tree-checkbox.vue
Normal file
418
app/src/components/v-checkbox-tree/v-checkbox-tree-checkbox.vue
Normal file
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<v-list-group v-if="children" v-show="visibleChildrenValues.length > 0" :value="value">
|
||||
<template #activator>
|
||||
<v-checkbox
|
||||
:indeterminate="groupIndeterminateState"
|
||||
:checked="groupCheckedStateOverride"
|
||||
:label="text"
|
||||
:value="value"
|
||||
v-model="treeValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-checkbox-tree-checkbox
|
||||
v-for="choice in children"
|
||||
:key="choice[itemValue]"
|
||||
:value-combining="valueCombining"
|
||||
:checked="childrenCheckedStateOverride"
|
||||
:hidden="visibleChildrenValues.includes(choice[itemValue]) === false"
|
||||
:search="search"
|
||||
:item-text="itemText"
|
||||
:item-value="itemValue"
|
||||
:item-children="itemChildren"
|
||||
:text="choice[itemText]"
|
||||
:value="choice[itemValue]"
|
||||
:children="choice[itemChildren]"
|
||||
v-model="treeValue"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-item v-else-if="!children && !hidden">
|
||||
<v-checkbox :checked="checked" :label="text" :value="value" v-model="treeValue" />
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { difference } from 'lodash';
|
||||
|
||||
type Delta = {
|
||||
added?: (number | string)[];
|
||||
removed?: (number | string)[];
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'v-checkbox-tree-checkbox',
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
children: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
default: null,
|
||||
},
|
||||
modelValue: {
|
||||
type: Array as PropType<(string | number)[]>,
|
||||
default: () => [],
|
||||
},
|
||||
valueCombining: {
|
||||
type: String as PropType<'all' | 'branch' | 'leaf' | 'indeterminate' | 'exclusive'>,
|
||||
required: true,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
search: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
itemText: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
itemValue: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
},
|
||||
itemChildren: {
|
||||
type: String,
|
||||
default: 'children',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const visibleChildrenValues = computed(() => {
|
||||
if (!props.search) return props.children?.map((child) => child[props.itemValue]);
|
||||
return props.children
|
||||
?.filter((child) => child[props.itemText].toLowerCase().includes(props.search.toLowerCase()))
|
||||
?.map((child) => child[props.itemValue]);
|
||||
});
|
||||
|
||||
const childrenValues = computed(() => props.children?.map((child) => child[props.itemValue]) || []);
|
||||
|
||||
const treeValue = computed({
|
||||
get() {
|
||||
return props.modelValue || [];
|
||||
},
|
||||
set(newValue: (string | number)[]) {
|
||||
const added = difference(newValue, props.modelValue);
|
||||
const removed = difference(props.modelValue, newValue);
|
||||
|
||||
if (props.children) {
|
||||
switch (props.valueCombining) {
|
||||
case 'all':
|
||||
return emitAll(newValue, { added, removed });
|
||||
case 'branch':
|
||||
return emitBranch(newValue, { added, removed });
|
||||
case 'leaf':
|
||||
return emitLeaf(newValue, { added, removed });
|
||||
case 'indeterminate':
|
||||
return emitIndeterminate(newValue, { added, removed });
|
||||
case 'exclusive':
|
||||
return emitExclusive(newValue, { added, removed });
|
||||
default:
|
||||
return emitValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
emitValue(newValue);
|
||||
},
|
||||
});
|
||||
|
||||
const groupCheckedStateOverride = computed(() => {
|
||||
if (props.checked !== null) return props.checked;
|
||||
if (props.valueCombining === 'all') return null;
|
||||
|
||||
if (props.valueCombining === 'leaf') {
|
||||
const leafChildrenRecursive = getRecursiveChildrenValues('leaf');
|
||||
return leafChildrenRecursive.every((childVal) => props.modelValue.includes(childVal));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const groupIndeterminateState = computed(() => {
|
||||
const allChildrenValues = getRecursiveChildrenValues('all');
|
||||
|
||||
if (props.valueCombining === 'all' || props.valueCombining === 'branch') {
|
||||
return (
|
||||
allChildrenValues.some((childVal) => props.modelValue.includes(childVal)) &&
|
||||
props.modelValue.includes(props.value) === false
|
||||
);
|
||||
}
|
||||
|
||||
if (props.valueCombining === 'indeterminate') {
|
||||
return (
|
||||
allChildrenValues.some((childVal) => props.modelValue.includes(childVal)) &&
|
||||
allChildrenValues.every((childVal) => props.modelValue.includes(childVal)) === false
|
||||
);
|
||||
}
|
||||
|
||||
if (props.valueCombining === 'leaf') {
|
||||
const leafChildrenRecursive = getRecursiveChildrenValues('leaf');
|
||||
return (
|
||||
leafChildrenRecursive.some((childVal) => props.modelValue.includes(childVal)) &&
|
||||
leafChildrenRecursive.every((childVal) => props.modelValue.includes(childVal)) === false
|
||||
);
|
||||
}
|
||||
|
||||
if (props.valueCombining === 'exclusive') {
|
||||
return allChildrenValues.some((childVal) => props.modelValue.includes(childVal));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const childrenCheckedStateOverride = computed(() => {
|
||||
if (props.checked !== null) return props.checked;
|
||||
if (props.valueCombining === 'all') return null;
|
||||
|
||||
if (props.valueCombining === 'branch') {
|
||||
if (props.modelValue.includes(props.value)) return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
return {
|
||||
groupCheckedStateOverride,
|
||||
childrenCheckedStateOverride,
|
||||
treeValue,
|
||||
groupIndeterminateState,
|
||||
visibleChildrenValues,
|
||||
};
|
||||
|
||||
function emitAll(rawValue: (string | number)[], { added, removed }: Delta) {
|
||||
const childrenValuesRecursive = getRecursiveChildrenValues('all');
|
||||
|
||||
// When enabling the group level
|
||||
if (added?.[0] === props.value) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && childrenValues.value.includes(val) === false),
|
||||
...childrenValuesRecursive,
|
||||
props.value,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When disabling the group level
|
||||
if (removed?.[0] === props.value) {
|
||||
const newValue = rawValue.filter(
|
||||
(val) => val !== props.value && childrenValuesRecursive.includes(val) === false
|
||||
);
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When all children are clicked
|
||||
if (childrenValues.value.every((childVal) => rawValue.includes(childVal))) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && childrenValuesRecursive.includes(val) === false),
|
||||
...childrenValuesRecursive,
|
||||
props.value,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
const newValue = rawValue.filter((val) => val !== props.value);
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
function emitBranch(rawValue: (string | number)[], { added, removed }: Delta) {
|
||||
const allChildrenRecursive = getRecursiveChildrenValues('all');
|
||||
|
||||
// When clicking on an individual item in the enabled group
|
||||
if (
|
||||
(props.modelValue.includes(props.value) || props.checked === true) &&
|
||||
added &&
|
||||
childrenValues.value.includes(added?.[0])
|
||||
) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && val !== added?.[0]),
|
||||
...childrenValues.value.filter((childVal) => childVal !== added?.[0]),
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When a childgroup is modified
|
||||
if (
|
||||
props.modelValue.includes(props.value) &&
|
||||
allChildrenRecursive.some((childVal) => rawValue.includes(childVal))
|
||||
) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value),
|
||||
...(props.children || [])
|
||||
.filter((child) => {
|
||||
if (!child[props.itemChildren]) return true;
|
||||
|
||||
const childNestedValues = getRecursiveChildrenValues('all', child[props.itemChildren]);
|
||||
return rawValue.some((rawVal) => childNestedValues.includes(rawVal)) === false;
|
||||
})
|
||||
.map((child) => child[props.itemValue]),
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When enabling the group level
|
||||
if (added?.includes(props.value)) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && allChildrenRecursive.includes(val) === false),
|
||||
props.value,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When disabling the group level
|
||||
if (removed?.includes(props.value)) {
|
||||
const newValue = rawValue.filter((val) => val !== props.value && allChildrenRecursive.includes(val) === false);
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When all children are clicked
|
||||
if (childrenValues.value.every((childVal) => rawValue.includes(childVal))) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && allChildrenRecursive.includes(val) === false),
|
||||
props.value,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
return emitValue(rawValue);
|
||||
}
|
||||
|
||||
function emitLeaf(rawValue: (string | number)[], { added, removed }: Delta) {
|
||||
const allChildrenRecursive = getRecursiveChildrenValues('all');
|
||||
const leafChildrenRecursive = getRecursiveChildrenValues('leaf');
|
||||
|
||||
// When enabling the group level
|
||||
if (added?.includes(props.value)) {
|
||||
if (leafChildrenRecursive.every((childVal) => rawValue.includes(childVal))) {
|
||||
const newValue = rawValue.filter(
|
||||
(val) => val !== props.value && allChildrenRecursive.includes(val) === false
|
||||
);
|
||||
return emitValue(newValue);
|
||||
} else {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && allChildrenRecursive.includes(val) === false),
|
||||
...leafChildrenRecursive,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
return emitValue(rawValue);
|
||||
}
|
||||
|
||||
function emitIndeterminate(rawValue: (string | number)[], { added, removed }: Delta) {
|
||||
const childrenValuesRecursive = getRecursiveChildrenValues('all');
|
||||
|
||||
// When enabling the group level
|
||||
if (added?.[0] === props.value) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && childrenValues.value.includes(val) === false),
|
||||
...childrenValuesRecursive,
|
||||
props.value,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When disabling the group level
|
||||
if (removed?.[0] === props.value) {
|
||||
const newValue = rawValue.filter(
|
||||
(val) => val !== props.value && childrenValuesRecursive.includes(val) === false
|
||||
);
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When a child value is clicked
|
||||
if (childrenValues.value.some((childVal) => rawValue.includes(childVal))) {
|
||||
const newValue = [...rawValue.filter((val) => val !== props.value), props.value];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When no children are clicked
|
||||
if (childrenValues.value.every((childVal) => rawValue.includes(childVal) === false)) {
|
||||
return emitValue(rawValue.filter((val) => val !== props.value));
|
||||
}
|
||||
|
||||
return emitValue(rawValue);
|
||||
}
|
||||
|
||||
function emitExclusive(rawValue: (string | number)[], { added, removed }: Delta) {
|
||||
const childrenValuesRecursive = getRecursiveChildrenValues('all');
|
||||
|
||||
// When enabling the group level
|
||||
if (added?.[0] === props.value) {
|
||||
const newValue = [
|
||||
...rawValue.filter((val) => val !== props.value && childrenValuesRecursive.includes(val) === false),
|
||||
props.value,
|
||||
];
|
||||
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
// When a child value is clicked
|
||||
if (childrenValuesRecursive.some((childVal) => rawValue.includes(childVal))) {
|
||||
const newValue = [...rawValue.filter((val) => val !== props.value)];
|
||||
return emitValue(newValue);
|
||||
}
|
||||
|
||||
return emitValue(rawValue);
|
||||
}
|
||||
|
||||
function emitValue(newValue: (string | number)[]) {
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
|
||||
function getRecursiveChildrenValues(
|
||||
mode: 'all' | 'branch' | 'leaf',
|
||||
children: Record<string, any>[] = props.children
|
||||
) {
|
||||
const values: (string | number)[] = [];
|
||||
|
||||
getChildrenValuesRecursive(children);
|
||||
|
||||
return values;
|
||||
|
||||
function getChildrenValuesRecursive(children: Record<string, any>[]) {
|
||||
if (!children) return;
|
||||
|
||||
for (const child of children) {
|
||||
if (mode === 'all') {
|
||||
values.push(child[props.itemValue]);
|
||||
}
|
||||
|
||||
if (mode === 'branch' && child[props.itemChildren]) {
|
||||
values.push(child[props.itemValue]);
|
||||
}
|
||||
|
||||
if (mode === 'leaf' && !child[props.itemChildren]) {
|
||||
values.push(child[props.itemValue]);
|
||||
}
|
||||
|
||||
if (child[props.itemChildren]) {
|
||||
getChildrenValuesRecursive(child[props.itemChildren]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
71
app/src/components/v-checkbox-tree/v-checkbox-tree.vue
Normal file
71
app/src/components/v-checkbox-tree/v-checkbox-tree.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<v-list :mandatory="false" v-model="openSelection">
|
||||
<v-checkbox-tree-checkbox
|
||||
v-for="choice in choices"
|
||||
:key="choice[itemValue]"
|
||||
:value-combining="valueCombining"
|
||||
:search="search"
|
||||
:item-text="itemText"
|
||||
:item-value="itemValue"
|
||||
:item-children="itemChildren"
|
||||
:text="choice[itemText]"
|
||||
:value="choice[itemValue]"
|
||||
:children="choice[itemChildren]"
|
||||
v-model="value"
|
||||
/>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ref, defineComponent, PropType } from 'vue';
|
||||
import VCheckboxTreeCheckbox from './v-checkbox-tree-checkbox.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'v-checkbox-tree',
|
||||
components: { VCheckboxTreeCheckbox },
|
||||
props: {
|
||||
choices: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
default: () => [],
|
||||
},
|
||||
modelValue: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: null,
|
||||
},
|
||||
valueCombining: {
|
||||
type: String as PropType<'all' | 'branch' | 'leaf' | 'indeterminate' | 'exclusive'>,
|
||||
default: 'all',
|
||||
},
|
||||
search: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
itemText: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
itemValue: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
},
|
||||
itemChildren: {
|
||||
type: String,
|
||||
default: 'children',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue || [];
|
||||
},
|
||||
set(newValue: string[]) {
|
||||
emit('update:modelValue', newValue);
|
||||
},
|
||||
});
|
||||
|
||||
const openSelection = ref<(string | number)[]>([]);
|
||||
|
||||
return { value, openSelection };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<component
|
||||
:is="customValue ? 'div' : 'button'"
|
||||
class="v-checkbox"
|
||||
@click="toggleInput"
|
||||
@click.stop="toggleInput"
|
||||
type="button"
|
||||
role="checkbox"
|
||||
:aria-pressed="isChecked ? 'true' : 'false'"
|
||||
@@ -10,10 +10,10 @@
|
||||
:class="{ checked: isChecked, indeterminate, block }"
|
||||
>
|
||||
<div class="prepend" v-if="$slots.prepend"><slot name="prepend" /></div>
|
||||
<v-icon class="checkbox" :name="icon" @click.stop="toggleInput" :disabled="disabled" />
|
||||
<v-icon class="checkbox" :name="icon" :disabled="disabled" />
|
||||
<span class="label type-text">
|
||||
<slot v-if="customValue === false">{{ label }}</slot>
|
||||
<input @click.stop class="custom-input" v-else v-model="internalValue" />
|
||||
<input class="custom-input" v-else v-model="internalValue" />
|
||||
</span>
|
||||
<div class="append" v-if="$slots.append"><slot name="append" /></div>
|
||||
</component>
|
||||
@@ -66,11 +66,17 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const internalValue = useSync(props, 'value', emit);
|
||||
|
||||
const isChecked = computed<boolean>(() => {
|
||||
if (props.checked !== null) return props.checked;
|
||||
|
||||
if (props.modelValue instanceof Array) {
|
||||
return props.modelValue.includes(props.value);
|
||||
}
|
||||
@@ -93,7 +99,7 @@ export default defineComponent({
|
||||
if (props.modelValue instanceof Array) {
|
||||
const newValue = [...props.modelValue];
|
||||
|
||||
if (isChecked.value === false) {
|
||||
if (props.modelValue.includes(props.value) === false) {
|
||||
newValue.push(props.value);
|
||||
} else {
|
||||
newValue.splice(newValue.indexOf(props.value), 1);
|
||||
@@ -101,7 +107,7 @@ export default defineComponent({
|
||||
|
||||
emit('update:modelValue', newValue);
|
||||
} else {
|
||||
emit('update:modelValue', !isChecked.value);
|
||||
emit('update:modelValue', !props.modelValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { useFieldsStore, useRelationsStore } from '@/stores/';
|
||||
import { useCollectionsStore, useFieldsStore, useRelationsStore } from '@/stores/';
|
||||
import { Field, Relation } from '@/types';
|
||||
import { getRelationType } from '@/utils/get-relation-type';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { cloneDeep, orderBy } from 'lodash';
|
||||
import { computed, Ref, ComputedRef } from 'vue';
|
||||
|
||||
type FieldOption = { name: string; field: string; key: string; children?: FieldOption[] };
|
||||
type FieldOption = { name: string; field: string; key: string; children?: FieldOption[]; group?: string };
|
||||
|
||||
export default function useFieldTree(
|
||||
collection: Ref<string | null>,
|
||||
/** Only allow m2o relations to be nested */
|
||||
strict = false,
|
||||
inject?: Ref<{ fields: Field[]; relations: Relation[] } | null>,
|
||||
filter: (field: Field) => boolean = () => true
|
||||
filter: (field: Field) => boolean = () => true,
|
||||
depth = 3
|
||||
): { tree: ComputedRef<FieldOption[]> } {
|
||||
const fieldsStore = useFieldsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
const tree = computed(() => {
|
||||
@@ -24,23 +26,27 @@ export default function useFieldTree(
|
||||
return { tree };
|
||||
|
||||
function parseLevel(collection: string, parentPath: string | null, level = 0) {
|
||||
const fieldsInLevel = [
|
||||
...cloneDeep(fieldsStore.getFieldsForCollectionAlphabetical(collection)),
|
||||
...(inject?.value?.fields.filter((field) => field.collection === collection) || []),
|
||||
]
|
||||
.filter((field: Field) => {
|
||||
const shown =
|
||||
field.meta?.special?.includes('alias') !== true && field.meta?.special?.includes('no-data') !== true;
|
||||
return shown;
|
||||
})
|
||||
.filter(filter)
|
||||
.map((field: Field) => ({
|
||||
name: field.name,
|
||||
field: field.field,
|
||||
key: parentPath ? `${parentPath}.${field.field}` : field.field,
|
||||
})) as FieldOption[];
|
||||
const fieldsInLevel = orderBy(
|
||||
[
|
||||
...cloneDeep(fieldsStore.getFieldsForCollectionAlphabetical(collection)),
|
||||
...(inject?.value?.fields.filter((field) => field.collection === collection) || []),
|
||||
]
|
||||
.filter((field: Field) => {
|
||||
const shown =
|
||||
field.meta?.special?.includes('alias') !== true && field.meta?.special?.includes('no-data') !== true;
|
||||
return shown;
|
||||
})
|
||||
.filter(filter)
|
||||
.map((field: Field) => ({
|
||||
name: field.name,
|
||||
field: field.field,
|
||||
key: parentPath ? `${parentPath}.${field.field}` : field.field,
|
||||
sort: field.meta?.sort,
|
||||
})) as FieldOption[],
|
||||
'sort'
|
||||
);
|
||||
|
||||
if (level >= 3) return fieldsInLevel;
|
||||
if (level >= depth) return fieldsInLevel;
|
||||
|
||||
for (const field of fieldsInLevel) {
|
||||
const relations = [
|
||||
@@ -65,10 +71,29 @@ export default function useFieldTree(
|
||||
|
||||
if (relationType === 'm2o') {
|
||||
field.children = parseLevel(
|
||||
relation.related_collection,
|
||||
relation.related_collection!,
|
||||
parentPath ? `${parentPath}.${field.field}` : field.field,
|
||||
level + 1
|
||||
);
|
||||
} else if (relationType === 'm2a') {
|
||||
field.children = [];
|
||||
|
||||
for (const relatedCollection of relation.meta!.one_allowed_collections!) {
|
||||
const relatedCollectionName =
|
||||
collectionsStore.collections.find((collection) => collection.collection === relatedCollection)?.name ||
|
||||
relatedCollection;
|
||||
|
||||
field.children.push(
|
||||
...parseLevel(
|
||||
relatedCollection,
|
||||
parentPath ? `${parentPath}.${field.field}:${relatedCollection}` : `${field.field}:${relatedCollection}`,
|
||||
level + 1
|
||||
).map((child) => ({
|
||||
...child,
|
||||
name: `${child.name} (${relatedCollectionName})`,
|
||||
}))
|
||||
);
|
||||
}
|
||||
} else if (strict === false) {
|
||||
field.children = parseLevel(
|
||||
relation.collection,
|
||||
|
||||
@@ -158,8 +158,12 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
saving.value = true;
|
||||
validationErrors.value = [];
|
||||
|
||||
const fields = collectionInfo.value?.meta?.item_duplication_fields || ['*'];
|
||||
|
||||
const itemData = await api.get(itemEndpoint.value, { params: { fields } });
|
||||
|
||||
const newItem: { [field: string]: any } = {
|
||||
...(item.value || {}),
|
||||
...(itemData.data.data || {}),
|
||||
...edits.value,
|
||||
};
|
||||
|
||||
|
||||
12
app/src/interfaces/_system/system-field-tree/index.ts
Normal file
12
app/src/interfaces/_system/system-field-tree/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineInterface } from '@/interfaces/define';
|
||||
import InterfaceSystemFieldTree from './system-field-tree.vue';
|
||||
|
||||
export default defineInterface({
|
||||
id: 'system-field-tree',
|
||||
name: '$t:field',
|
||||
icon: 'box',
|
||||
component: InterfaceSystemFieldTree,
|
||||
types: ['string'],
|
||||
options: [],
|
||||
system: true,
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<v-notice v-if="!collectionField && !collection" type="warning">
|
||||
{{ t('collection_field_not_setup') }}
|
||||
</v-notice>
|
||||
<v-notice v-else-if="!chosenCollection" type="warning">
|
||||
{{ t('select_a_collection') }}
|
||||
</v-notice>
|
||||
<div class="system-field-tree" v-else>
|
||||
<v-checkbox-tree
|
||||
@update:model-value="$emit('input', $event)"
|
||||
:model-value="value"
|
||||
:disabled="disabled"
|
||||
:choices="tree"
|
||||
item-text="name"
|
||||
item-value="key"
|
||||
value-combining="exclusive"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, inject, ref, PropType } from 'vue';
|
||||
import { useFieldTree } from '@/composables/use-field-tree';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['input'],
|
||||
props: {
|
||||
collectionField: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
allowNone: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const values = inject('values', ref<Record<string, any>>({}));
|
||||
|
||||
const chosenCollection = computed(() => values.value[props.collectionField] || props.collection);
|
||||
|
||||
const { tree } = useFieldTree(chosenCollection);
|
||||
|
||||
return { t, values, tree, chosenCollection };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.system-field-tree {
|
||||
border: var(--border-width) solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user