mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
script[setup]: modules/settings/data-model (#18445)
This commit is contained in:
@@ -107,111 +107,93 @@
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, ref } from 'vue';
|
||||
import SettingsNavigation from '../../../components/navigation.vue';
|
||||
<script setup lang="ts">
|
||||
import api from '@/api';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { Collection } from '@/types/collections';
|
||||
import CollectionOptions from './components/collection-options.vue';
|
||||
import { sortBy, merge } from 'lodash';
|
||||
import CollectionItem from './components/collection-item.vue';
|
||||
import { translate } from '@/utils/translate-object-values';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import api from '@/api';
|
||||
import { merge, sortBy } from 'lodash';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Draggable from 'vuedraggable';
|
||||
import SettingsNavigation from '../../../components/navigation.vue';
|
||||
import CollectionDialog from './components/collection-dialog.vue';
|
||||
import CollectionItem from './components/collection-item.vue';
|
||||
import CollectionOptions from './components/collection-options.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { SettingsNavigation, CollectionItem, CollectionOptions, Draggable, CollectionDialog },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const collectionDialogActive = ref(false);
|
||||
const editCollection = ref<Collection | null>();
|
||||
const collectionDialogActive = ref(false);
|
||||
const editCollection = ref<Collection | null>();
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const collections = computed(() => {
|
||||
return translate(
|
||||
sortBy(
|
||||
collectionsStore.collections.filter(
|
||||
(collection) => collection.collection.startsWith('directus_') === false && collection.meta
|
||||
),
|
||||
['meta.sort', 'collection']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const rootCollections = computed(() => {
|
||||
return collections.value.filter((collection) => !collection.meta?.group);
|
||||
});
|
||||
|
||||
const tableCollections = computed(() => {
|
||||
return translate(
|
||||
sortBy(
|
||||
collectionsStore.collections.filter(
|
||||
(collection) =>
|
||||
collection.collection.startsWith('directus_') === false &&
|
||||
!!collection.meta === false &&
|
||||
collection.schema
|
||||
),
|
||||
['meta.sort', 'collection']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const systemCollections = computed(() => {
|
||||
return translate(
|
||||
sortBy(
|
||||
collectionsStore.collections
|
||||
.filter((collection) => collection.collection.startsWith('directus_') === true)
|
||||
.map((collection) => ({ ...collection, icon: 'settings' })),
|
||||
'collection'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
collectionDialogActive,
|
||||
t,
|
||||
collections,
|
||||
tableCollections,
|
||||
systemCollections,
|
||||
onSort,
|
||||
rootCollections,
|
||||
editCollection,
|
||||
};
|
||||
|
||||
async function onSort(updates: Collection[], removeGroup = false) {
|
||||
const updatesWithSortValue = updates.map((collection, index) =>
|
||||
merge(collection, { meta: { sort: index + 1, group: removeGroup ? null : collection.meta?.group } })
|
||||
);
|
||||
|
||||
collectionsStore.collections = collectionsStore.collections.map((collection) => {
|
||||
const updatedValues = updatesWithSortValue.find(
|
||||
(updatedCollection) => updatedCollection.collection === collection.collection
|
||||
);
|
||||
|
||||
return updatedValues ? merge({}, collection, updatedValues) : collection;
|
||||
});
|
||||
|
||||
try {
|
||||
api.patch(
|
||||
`/collections`,
|
||||
updatesWithSortValue.map((collection) => {
|
||||
return {
|
||||
collection: collection.collection,
|
||||
meta: { sort: collection.meta.sort, group: collection.meta.group },
|
||||
};
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
}
|
||||
}
|
||||
},
|
||||
const collections = computed(() => {
|
||||
return translate(
|
||||
sortBy(
|
||||
collectionsStore.collections.filter(
|
||||
(collection) => collection.collection.startsWith('directus_') === false && collection.meta
|
||||
),
|
||||
['meta.sort', 'collection']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const rootCollections = computed(() => {
|
||||
return collections.value.filter((collection) => !collection.meta?.group);
|
||||
});
|
||||
|
||||
const tableCollections = computed(() => {
|
||||
return translate(
|
||||
sortBy(
|
||||
collectionsStore.collections.filter(
|
||||
(collection) =>
|
||||
collection.collection.startsWith('directus_') === false && !!collection.meta === false && collection.schema
|
||||
),
|
||||
['meta.sort', 'collection']
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const systemCollections = computed(() => {
|
||||
return translate(
|
||||
sortBy(
|
||||
collectionsStore.collections
|
||||
.filter((collection) => collection.collection.startsWith('directus_') === true)
|
||||
.map((collection) => ({ ...collection, icon: 'settings' })),
|
||||
'collection'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
async function onSort(updates: Collection[], removeGroup = false) {
|
||||
const updatesWithSortValue = updates.map((collection, index) =>
|
||||
merge(collection, { meta: { sort: index + 1, group: removeGroup ? null : collection.meta?.group } })
|
||||
);
|
||||
|
||||
collectionsStore.collections = collectionsStore.collections.map((collection) => {
|
||||
const updatedValues = updatesWithSortValue.find(
|
||||
(updatedCollection) => updatedCollection.collection === collection.collection
|
||||
);
|
||||
|
||||
return updatedValues ? merge({}, collection, updatedValues) : collection;
|
||||
});
|
||||
|
||||
try {
|
||||
api.patch(
|
||||
`/collections`,
|
||||
updatesWithSortValue.map((collection) => {
|
||||
return {
|
||||
collection: collection.collection,
|
||||
meta: { sort: collection.meta.sort, group: collection.meta.group },
|
||||
};
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -70,83 +70,77 @@
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import api from '@/api';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import { defineComponent, ref, reactive, PropType, watch } from 'vue';
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Collection } from '@/types/collections';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CollectionDialog',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collection: {
|
||||
type: Object as PropType<Collection>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: boolean;
|
||||
collection?: Collection;
|
||||
}>(),
|
||||
{
|
||||
modelValue: false,
|
||||
}
|
||||
);
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const values = reactive({
|
||||
collection: props.collection?.collection ?? null,
|
||||
icon: props.collection?.icon ?? 'folder',
|
||||
note: props.collection?.meta?.note ?? null,
|
||||
color: props.collection?.color ?? null,
|
||||
translations: props.collection?.meta?.translations ?? null,
|
||||
});
|
||||
const { t } = useI18n();
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue, oldValue) => {
|
||||
if (isEqual(newValue, oldValue) === false) {
|
||||
values.collection = props.collection?.collection ?? null;
|
||||
values.icon = props.collection?.icon ?? 'folder';
|
||||
values.note = props.collection?.meta?.note ?? null;
|
||||
values.color = props.collection?.color ?? null;
|
||||
values.translations = props.collection?.meta?.translations ?? null;
|
||||
}
|
||||
}
|
||||
);
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const saving = ref(false);
|
||||
|
||||
return { values, cancel, saving, save, t };
|
||||
|
||||
function cancel() {
|
||||
emit('update:modelValue', false);
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
if (props.collection) {
|
||||
await api.patch(`/collections/${props.collection.collection}`, { meta: values });
|
||||
await collectionsStore.hydrate();
|
||||
} else {
|
||||
await api.post<any>('/collections', { collection: values.collection, meta: values });
|
||||
await collectionsStore.hydrate();
|
||||
}
|
||||
|
||||
emit('update:modelValue', false);
|
||||
} catch (err) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
const values = reactive({
|
||||
collection: props.collection?.collection ?? null,
|
||||
icon: props.collection?.icon ?? 'folder',
|
||||
note: props.collection?.meta?.note ?? null,
|
||||
color: props.collection?.color ?? null,
|
||||
translations: props.collection?.meta?.translations ?? null,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue, oldValue) => {
|
||||
if (isEqual(newValue, oldValue) === false) {
|
||||
values.collection = props.collection?.collection ?? null;
|
||||
values.icon = props.collection?.icon ?? 'folder';
|
||||
values.note = props.collection?.meta?.note ?? null;
|
||||
values.color = props.collection?.color ?? null;
|
||||
values.translations = props.collection?.meta?.translations ?? null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const saving = ref(false);
|
||||
|
||||
function cancel() {
|
||||
emit('update:modelValue', false);
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
if (props.collection) {
|
||||
await api.patch(`/collections/${props.collection.collection}`, { meta: values });
|
||||
await collectionsStore.hydrate();
|
||||
} else {
|
||||
await api.post<any>('/collections', { collection: values.collection, meta: values });
|
||||
await collectionsStore.hydrate();
|
||||
}
|
||||
|
||||
emit('update:modelValue', false);
|
||||
} catch (err) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed, ref } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import CollectionOptions from './collection-options.vue';
|
||||
import { Collection } from '@/types/collections';
|
||||
import Draggable from 'vuedraggable';
|
||||
@@ -66,108 +66,85 @@ import { DeepPartial } from '@directus/types';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CollectionItem',
|
||||
components: { CollectionOptions, Draggable },
|
||||
props: {
|
||||
collection: {
|
||||
type: Object as PropType<Collection>,
|
||||
required: true,
|
||||
},
|
||||
collections: {
|
||||
type: Array as PropType<Collection[]>,
|
||||
required: true,
|
||||
},
|
||||
disableDrag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['setNestedSort', 'editCollection'],
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
collection: Collection;
|
||||
collections: Collection[];
|
||||
disableDrag?: boolean;
|
||||
}>();
|
||||
|
||||
const nestedCollections = computed(() =>
|
||||
props.collections.filter((collection) => collection.meta?.group === props.collection.collection)
|
||||
);
|
||||
const emit = defineEmits(['setNestedSort', 'editCollection']);
|
||||
|
||||
const collapseIcon = computed(() => {
|
||||
switch (props.collection.meta?.collapse) {
|
||||
case 'open':
|
||||
return 'folder_open';
|
||||
case 'closed':
|
||||
return 'folder';
|
||||
case 'locked':
|
||||
return 'folder_lock';
|
||||
}
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
return undefined;
|
||||
});
|
||||
const nestedCollections = computed(() =>
|
||||
props.collections.filter((collection) => collection.meta?.group === props.collection.collection)
|
||||
);
|
||||
|
||||
const collapseTooltip = computed(() => {
|
||||
switch (props.collection.meta?.collapse) {
|
||||
case 'open':
|
||||
return t('start_open');
|
||||
case 'closed':
|
||||
return t('start_collapsed');
|
||||
case 'locked':
|
||||
return t('always_open');
|
||||
}
|
||||
const collapseIcon = computed(() => {
|
||||
switch (props.collection.meta?.collapse) {
|
||||
case 'open':
|
||||
return 'folder_open';
|
||||
case 'closed':
|
||||
return 'folder';
|
||||
case 'locked':
|
||||
return 'folder_lock';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const collapseLoading = ref(false);
|
||||
|
||||
return {
|
||||
collapseIcon,
|
||||
onGroupSortChange,
|
||||
nestedCollections,
|
||||
update,
|
||||
toggleCollapse,
|
||||
collapseTooltip,
|
||||
collapseLoading,
|
||||
};
|
||||
|
||||
async function toggleCollapse() {
|
||||
if (collapseLoading.value === true) return;
|
||||
|
||||
collapseLoading.value = true;
|
||||
|
||||
let newCollapse: 'open' | 'closed' | 'locked' = 'open';
|
||||
|
||||
if (props.collection.meta?.collapse === 'open') {
|
||||
newCollapse = 'closed';
|
||||
} else if (props.collection.meta?.collapse === 'closed') {
|
||||
newCollapse = 'locked';
|
||||
}
|
||||
|
||||
try {
|
||||
await update({ meta: { collapse: newCollapse } });
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
collapseLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function update(updates: DeepPartial<Collection>) {
|
||||
await collectionsStore.updateCollection(props.collection.collection, updates);
|
||||
}
|
||||
|
||||
function onGroupSortChange(collections: Collection[]) {
|
||||
const updates = collections.map((collection) => ({
|
||||
collection: collection.collection,
|
||||
meta: {
|
||||
group: props.collection.collection,
|
||||
},
|
||||
}));
|
||||
|
||||
emit('setNestedSort', updates);
|
||||
}
|
||||
},
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const collapseTooltip = computed(() => {
|
||||
switch (props.collection.meta?.collapse) {
|
||||
case 'open':
|
||||
return t('start_open');
|
||||
case 'closed':
|
||||
return t('start_collapsed');
|
||||
case 'locked':
|
||||
return t('always_open');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const collapseLoading = ref(false);
|
||||
|
||||
async function toggleCollapse() {
|
||||
if (collapseLoading.value === true) return;
|
||||
|
||||
collapseLoading.value = true;
|
||||
|
||||
let newCollapse: 'open' | 'closed' | 'locked' = 'open';
|
||||
|
||||
if (props.collection.meta?.collapse === 'open') {
|
||||
newCollapse = 'closed';
|
||||
} else if (props.collection.meta?.collapse === 'closed') {
|
||||
newCollapse = 'locked';
|
||||
}
|
||||
|
||||
try {
|
||||
await update({ meta: { collapse: newCollapse } });
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
collapseLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function update(updates: DeepPartial<Collection>) {
|
||||
await collectionsStore.updateCollection(props.collection.collection, updates);
|
||||
}
|
||||
|
||||
function onGroupSortChange(collections: Collection[]) {
|
||||
const updates = collections.map((collection) => ({
|
||||
collection: collection.collection,
|
||||
meta: {
|
||||
group: props.collection.collection,
|
||||
},
|
||||
}));
|
||||
|
||||
emit('setNestedSort', updates);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -11,21 +11,15 @@
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useFieldDetailStore } from '../store';
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFieldDetailStore } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['save'],
|
||||
setup() {
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const { saving, readyToSave } = storeToRefs(fieldDetailStore);
|
||||
defineEmits(['save']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const { saving, readyToSave } = storeToRefs(fieldDetailStore);
|
||||
|
||||
return { saving, t, readyToSave };
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
@@ -22,95 +22,89 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { clone } from 'lodash';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import ExtensionOptions from '../shared/extension-options.vue';
|
||||
<script setup lang="ts">
|
||||
import { FancySelectItem } from '@/components/v-fancy-select.vue';
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
import { clone } from 'lodash';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ExtensionOptions from '../shared/extension-options.vue';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ExtensionOptions },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const { loading, field, displaysForType } = storeToRefs(fieldDetailStore);
|
||||
const { loading, field, displaysForType } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const interfaceId = computed(() => field.value.meta?.interface ?? null);
|
||||
const display = syncFieldDetailStoreProperty('field.meta.display');
|
||||
const interfaceId = computed(() => field.value.meta?.interface ?? null);
|
||||
const display = syncFieldDetailStoreProperty('field.meta.display');
|
||||
|
||||
const selectedInterface = useExtension('interface', interfaceId);
|
||||
const selectedDisplay = useExtension('display', display);
|
||||
const selectedInterface = useExtension('interface', interfaceId);
|
||||
const selectedDisplay = useExtension('display', display);
|
||||
|
||||
const selectItems = computed(() => {
|
||||
let recommended = clone(selectedInterface.value?.recommendedDisplays) || [];
|
||||
const selectItems = computed(() => {
|
||||
let recommended = clone(selectedInterface.value?.recommendedDisplays) || [];
|
||||
|
||||
recommended.push('raw', 'formatted-value');
|
||||
recommended = [...new Set(recommended)];
|
||||
recommended.push('raw', 'formatted-value');
|
||||
recommended = [...new Set(recommended)];
|
||||
|
||||
const displayItems: FancySelectItem[] = displaysForType.value.map((display) => {
|
||||
const item: FancySelectItem = {
|
||||
text: display.name,
|
||||
description: display.description,
|
||||
value: display.id,
|
||||
icon: display.icon,
|
||||
};
|
||||
const displayItems: FancySelectItem[] = displaysForType.value.map((display) => {
|
||||
const item: FancySelectItem = {
|
||||
text: display.name,
|
||||
description: display.description,
|
||||
value: display.id,
|
||||
icon: display.icon,
|
||||
};
|
||||
|
||||
if (recommended.includes(item.value as string)) {
|
||||
item.iconRight = 'star';
|
||||
}
|
||||
if (recommended.includes(item.value as string)) {
|
||||
item.iconRight = 'star';
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
return item;
|
||||
});
|
||||
|
||||
const recommendedItems: (FancySelectItem | { divider: boolean } | undefined)[] = [];
|
||||
const recommendedItems: (FancySelectItem | { divider: boolean } | undefined)[] = [];
|
||||
|
||||
const recommendedList = recommended.map((key: any) => displayItems.find((item) => item.value === key));
|
||||
const recommendedList = recommended.map((key: any) => displayItems.find((item) => item.value === key));
|
||||
|
||||
if (recommendedList !== undefined) {
|
||||
recommendedItems.push(...recommendedList.filter((i: any) => i));
|
||||
}
|
||||
if (recommendedList !== undefined) {
|
||||
recommendedItems.push(...recommendedList.filter((i: any) => i));
|
||||
}
|
||||
|
||||
if (displayItems.length >= 5 && recommended.length > 0) {
|
||||
recommendedItems.push({ divider: true });
|
||||
}
|
||||
if (displayItems.length >= 5 && recommended.length > 0) {
|
||||
recommendedItems.push({ divider: true });
|
||||
}
|
||||
|
||||
const displayList = displayItems.filter((item) => recommended.includes(item.value as string) === false);
|
||||
const displayList = displayItems.filter((item) => recommended.includes(item.value as string) === false);
|
||||
|
||||
if (displayList !== undefined) {
|
||||
recommendedItems.push(...displayList.filter((i) => i));
|
||||
}
|
||||
if (displayList !== undefined) {
|
||||
recommendedItems.push(...displayList.filter((i) => i));
|
||||
}
|
||||
|
||||
return recommendedItems;
|
||||
return recommendedItems;
|
||||
});
|
||||
|
||||
const customOptionsFields = computed(() => {
|
||||
if (typeof selectedDisplay.value?.options === 'function') {
|
||||
return selectedDisplay.value?.options(fieldDetailStore);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const options = computed({
|
||||
get() {
|
||||
return fieldDetailStore.field.meta?.display_options ?? {};
|
||||
},
|
||||
set(newOptions: Record<string, any>) {
|
||||
fieldDetailStore.$patch((state) => {
|
||||
state.field.meta = {
|
||||
...(state.field.meta ?? {}),
|
||||
display_options: newOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const customOptionsFields = computed(() => {
|
||||
if (typeof selectedDisplay.value?.options === 'function') {
|
||||
return selectedDisplay.value?.options(fieldDetailStore);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const options = computed({
|
||||
get() {
|
||||
return fieldDetailStore.field.meta?.display_options ?? {};
|
||||
},
|
||||
set(newOptions: Record<string, any>) {
|
||||
fieldDetailStore.$patch((state) => {
|
||||
state.field.meta = {
|
||||
...(state.field.meta ?? {}),
|
||||
display_options: newOptions,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { t, loading, selectItems, selectedDisplay, display, options, customOptionsFields };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -71,27 +71,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const readonly = syncFieldDetailStoreProperty('field.meta.readonly', false);
|
||||
const hidden = syncFieldDetailStoreProperty('field.meta.hidden', false);
|
||||
const required = syncFieldDetailStoreProperty('field.meta.required', false);
|
||||
const note = syncFieldDetailStoreProperty('field.meta.note');
|
||||
const translations = syncFieldDetailStoreProperty('field.meta.translations');
|
||||
const { loading, field } = storeToRefs(fieldDetailStore);
|
||||
const type = computed(() => field.value.type);
|
||||
const isGenerated = computed(() => field.value.schema?.is_generated);
|
||||
return { t, loading, readonly, hidden, required, note, translations, type, isGenerated };
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const readonly = syncFieldDetailStoreProperty('field.meta.readonly', false);
|
||||
const hidden = syncFieldDetailStoreProperty('field.meta.hidden', false);
|
||||
const required = syncFieldDetailStoreProperty('field.meta.required', false);
|
||||
const note = syncFieldDetailStoreProperty('field.meta.note');
|
||||
const translations = syncFieldDetailStoreProperty('field.meta.translations');
|
||||
const { loading, field } = storeToRefs(fieldDetailStore);
|
||||
const type = computed(() => field.value.type);
|
||||
const isGenerated = computed(() => field.value.schema?.is_generated);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -23,107 +23,101 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store/';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import ExtensionOptions from '../shared/extension-options.vue';
|
||||
<script setup lang="ts">
|
||||
import { FancySelectItem } from '@/components/v-fancy-select.vue';
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ExtensionOptions from '../shared/extension-options.vue';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store/';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ExtensionOptions },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const interfaceId = syncFieldDetailStoreProperty('field.meta.interface');
|
||||
const interfaceId = syncFieldDetailStoreProperty('field.meta.interface');
|
||||
|
||||
const { loading, field, interfacesForType } = storeToRefs(fieldDetailStore);
|
||||
const type = computed(() => field.value.type);
|
||||
const { loading, field, interfacesForType } = storeToRefs(fieldDetailStore);
|
||||
const type = computed(() => field.value.type);
|
||||
|
||||
const selectItems = computed(() => {
|
||||
const recommendedInterfacesPerType: { [type: string]: string[] } = {
|
||||
string: ['input', 'select-dropdown'],
|
||||
text: ['input-rich-text-html'],
|
||||
boolean: ['boolean'],
|
||||
integer: ['input'],
|
||||
bigInteger: ['input'],
|
||||
float: ['input'],
|
||||
decimal: ['input'],
|
||||
timestamp: ['datetime'],
|
||||
datetime: ['datetime'],
|
||||
date: ['datetime'],
|
||||
time: ['datetime'],
|
||||
json: ['select-multiple-checkbox', 'tags'],
|
||||
uuid: ['input'],
|
||||
csv: ['tags'],
|
||||
const selectItems = computed(() => {
|
||||
const recommendedInterfacesPerType: { [type: string]: string[] } = {
|
||||
string: ['input', 'select-dropdown'],
|
||||
text: ['input-rich-text-html'],
|
||||
boolean: ['boolean'],
|
||||
integer: ['input'],
|
||||
bigInteger: ['input'],
|
||||
float: ['input'],
|
||||
decimal: ['input'],
|
||||
timestamp: ['datetime'],
|
||||
datetime: ['datetime'],
|
||||
date: ['datetime'],
|
||||
time: ['datetime'],
|
||||
json: ['select-multiple-checkbox', 'tags'],
|
||||
uuid: ['input'],
|
||||
csv: ['tags'],
|
||||
};
|
||||
|
||||
const recommended = recommendedInterfacesPerType[type.value ?? 'alias'] || [];
|
||||
|
||||
const interfaceItems: FancySelectItem[] = interfacesForType.value.map((inter) => {
|
||||
const item: FancySelectItem = {
|
||||
text: inter.name,
|
||||
description: inter.description,
|
||||
value: inter.id,
|
||||
icon: inter.icon,
|
||||
};
|
||||
|
||||
if (recommended.includes(item.value as string)) {
|
||||
item.iconRight = 'star';
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
const recommendedItems: (FancySelectItem | { divider: boolean } | undefined)[] = [];
|
||||
|
||||
const recommendedList = recommended.map((key) => interfaceItems.find((item) => item.value === key));
|
||||
|
||||
if (recommendedList !== undefined) {
|
||||
recommendedItems.push(...recommendedList.filter((i) => i));
|
||||
}
|
||||
|
||||
if (interfaceItems.length >= 5 && recommended.length > 0) {
|
||||
recommendedItems.push({ divider: true });
|
||||
}
|
||||
|
||||
const interfaceList = interfaceItems.filter((item) => recommended.includes(item.value as string) === false);
|
||||
|
||||
if (interfaceList !== undefined) {
|
||||
recommendedItems.push(...interfaceList.filter((i) => i));
|
||||
}
|
||||
|
||||
return recommendedItems;
|
||||
});
|
||||
|
||||
const selectedInterface = useExtension('interface', interfaceId);
|
||||
|
||||
const customOptionsFields = computed(() => {
|
||||
if (typeof selectedInterface.value?.options === 'function') {
|
||||
return selectedInterface.value?.options(fieldDetailStore);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const options = computed({
|
||||
get() {
|
||||
return fieldDetailStore.field.meta?.options ?? {};
|
||||
},
|
||||
set(newOptions: Record<string, any>) {
|
||||
fieldDetailStore.$patch((state) => {
|
||||
state.field.meta = {
|
||||
...(state.field.meta ?? {}),
|
||||
options: newOptions,
|
||||
};
|
||||
|
||||
const recommended = recommendedInterfacesPerType[type.value ?? 'alias'] || [];
|
||||
|
||||
const interfaceItems: FancySelectItem[] = interfacesForType.value.map((inter) => {
|
||||
const item: FancySelectItem = {
|
||||
text: inter.name,
|
||||
description: inter.description,
|
||||
value: inter.id,
|
||||
icon: inter.icon,
|
||||
};
|
||||
|
||||
if (recommended.includes(item.value as string)) {
|
||||
item.iconRight = 'star';
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
const recommendedItems: (FancySelectItem | { divider: boolean } | undefined)[] = [];
|
||||
|
||||
const recommendedList = recommended.map((key) => interfaceItems.find((item) => item.value === key));
|
||||
|
||||
if (recommendedList !== undefined) {
|
||||
recommendedItems.push(...recommendedList.filter((i) => i));
|
||||
}
|
||||
|
||||
if (interfaceItems.length >= 5 && recommended.length > 0) {
|
||||
recommendedItems.push({ divider: true });
|
||||
}
|
||||
|
||||
const interfaceList = interfaceItems.filter((item) => recommended.includes(item.value as string) === false);
|
||||
|
||||
if (interfaceList !== undefined) {
|
||||
recommendedItems.push(...interfaceList.filter((i) => i));
|
||||
}
|
||||
|
||||
return recommendedItems;
|
||||
});
|
||||
|
||||
const selectedInterface = useExtension('interface', interfaceId);
|
||||
|
||||
const customOptionsFields = computed(() => {
|
||||
if (typeof selectedInterface.value?.options === 'function') {
|
||||
return selectedInterface.value?.options(fieldDetailStore);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const options = computed({
|
||||
get() {
|
||||
return fieldDetailStore.field.meta?.options ?? {};
|
||||
},
|
||||
set(newOptions: Record<string, any>) {
|
||||
fieldDetailStore.$patch((state) => {
|
||||
state.field.meta = {
|
||||
...(state.field.meta ?? {}),
|
||||
options: newOptions,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { t, loading, selectItems, selectedInterface, interfaceId, customOptionsFields, options };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -153,91 +153,67 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
<script setup lang="ts">
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useRelationsStore } from '@/stores/relations';
|
||||
import { orderBy } from 'lodash';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RelatedCollectionSelect, RelatedFieldSelect },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
const { collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const autoGenerateJunctionRelation = syncFieldDetailStoreProperty('autoGenerateJunctionRelation');
|
||||
const junctionCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const junctionFieldCurrent = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const junctionFieldRelated = syncFieldDetailStoreProperty('relations.m2o.field');
|
||||
const oneCollectionField = syncFieldDetailStoreProperty('relations.m2o.meta.one_collection_field');
|
||||
const oneAllowedCollections = syncFieldDetailStoreProperty('relations.m2o.meta.one_allowed_collections', []);
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const onDelete = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeselect = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
const autoGenerateJunctionRelation = syncFieldDetailStoreProperty('autoGenerateJunctionRelation');
|
||||
const junctionCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const junctionFieldCurrent = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const junctionFieldRelated = syncFieldDetailStoreProperty('relations.m2o.field');
|
||||
const oneCollectionField = syncFieldDetailStoreProperty('relations.m2o.meta.one_collection_field');
|
||||
const oneAllowedCollections = syncFieldDetailStoreProperty('relations.m2o.meta.one_allowed_collections', []);
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const onDelete = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeselect = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
[
|
||||
...collectionsStore.databaseCollections,
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
name: t('system'),
|
||||
selectable: false,
|
||||
children: collectionsStore.crudSafeSystemCollections,
|
||||
},
|
||||
],
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
[
|
||||
...collectionsStore.databaseCollections,
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
name: t('system'),
|
||||
selectable: false,
|
||||
children: collectionsStore.crudSafeSystemCollections,
|
||||
},
|
||||
],
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
let fields = ['item', 'collection'];
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
let fields = ['item', 'collection'];
|
||||
|
||||
if (junctionCollection.value) {
|
||||
const relations = relationsStore.getRelationsForCollection(junctionCollection.value);
|
||||
fields.push(...relations.map((field) => field.field));
|
||||
}
|
||||
if (junctionCollection.value) {
|
||||
const relations = relationsStore.getRelationsForCollection(junctionCollection.value);
|
||||
fields.push(...relations.map((field) => field.field));
|
||||
}
|
||||
|
||||
return fields;
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
availableCollections,
|
||||
generationInfo,
|
||||
collection,
|
||||
isExisting,
|
||||
autoGenerateJunctionRelation,
|
||||
junctionCollection,
|
||||
oneAllowedCollections,
|
||||
currentPrimaryKey,
|
||||
junctionFieldCurrent,
|
||||
junctionFieldRelated,
|
||||
oneCollectionField,
|
||||
sortField,
|
||||
onDelete,
|
||||
onDeselect,
|
||||
unsortableJunctionFields,
|
||||
};
|
||||
},
|
||||
return fields;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -198,110 +198,82 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
<script setup lang="ts">
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useRelationsStore } from '@/stores/relations';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RelatedCollectionSelect, RelatedFieldSelect },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { collection, editing, generationInfo, localType } = storeToRefs(fieldDetailStore);
|
||||
const { collection, editing, generationInfo, localType } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const junctionCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const junctionFieldCurrent = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const junctionFieldRelated = syncFieldDetailStoreProperty('relations.m2o.field');
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const autoGenerateJunctionRelation = syncFieldDetailStoreProperty('autoGenerateJunctionRelation');
|
||||
const onDeleteCurrent = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeleteRelated = syncFieldDetailStoreProperty('relations.m2o.schema.on_delete');
|
||||
const deselectAction = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
const correspondingField = syncFieldDetailStoreProperty('fields.corresponding');
|
||||
const correspondingFieldKey = syncFieldDetailStoreProperty('fields.corresponding.field');
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const junctionCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const junctionFieldCurrent = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const junctionFieldRelated = syncFieldDetailStoreProperty('relations.m2o.field');
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const autoGenerateJunctionRelation = syncFieldDetailStoreProperty('autoGenerateJunctionRelation');
|
||||
const onDeleteCurrent = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeleteRelated = syncFieldDetailStoreProperty('relations.m2o.schema.on_delete');
|
||||
const deselectAction = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
const correspondingField = syncFieldDetailStoreProperty('fields.corresponding');
|
||||
const correspondingFieldKey = syncFieldDetailStoreProperty('fields.corresponding.field');
|
||||
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
|
||||
const relatedPrimaryKey = computed(
|
||||
() => fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection.value)?.field ?? 'id'
|
||||
);
|
||||
const relatedPrimaryKey = computed(
|
||||
() => fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection.value)?.field ?? 'id'
|
||||
);
|
||||
|
||||
const hasCorresponding = computed({
|
||||
get() {
|
||||
return !!correspondingField.value;
|
||||
},
|
||||
set(enabled: boolean) {
|
||||
if (enabled) {
|
||||
correspondingField.value = {
|
||||
field: collection.value,
|
||||
collection: relatedCollection.value,
|
||||
type: 'alias',
|
||||
meta: {
|
||||
special: ['m2m'],
|
||||
interface: 'list-m2m',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
correspondingField.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const correspondingLabel = computed(() => {
|
||||
if (junctionCollection.value) {
|
||||
return t('add_m2m_to_collection', { collection: relatedCollection.value });
|
||||
}
|
||||
|
||||
return t('add_field_related');
|
||||
});
|
||||
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
let fields = [];
|
||||
|
||||
if (junctionCollection.value) {
|
||||
const relations = relationsStore.getRelationsForCollection(junctionCollection.value);
|
||||
fields.push(...relations.map((field) => field.field));
|
||||
}
|
||||
|
||||
return fields;
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
autoGenerateJunctionRelation,
|
||||
collection,
|
||||
localType,
|
||||
isExisting,
|
||||
junctionCollection,
|
||||
junctionFieldCurrent,
|
||||
relatedCollection,
|
||||
sortField,
|
||||
currentPrimaryKey,
|
||||
junctionFieldRelated,
|
||||
relatedPrimaryKey,
|
||||
onDeleteCurrent,
|
||||
onDeleteRelated,
|
||||
deselectAction,
|
||||
hasCorresponding,
|
||||
correspondingLabel,
|
||||
correspondingFieldKey,
|
||||
generationInfo,
|
||||
unsortableJunctionFields,
|
||||
};
|
||||
const hasCorresponding = computed({
|
||||
get() {
|
||||
return !!correspondingField.value;
|
||||
},
|
||||
set(enabled: boolean) {
|
||||
if (enabled) {
|
||||
correspondingField.value = {
|
||||
field: collection.value,
|
||||
collection: relatedCollection.value,
|
||||
type: 'alias',
|
||||
meta: {
|
||||
special: ['m2m'],
|
||||
interface: 'list-m2m',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
correspondingField.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const correspondingLabel = computed(() => {
|
||||
if (junctionCollection.value) {
|
||||
return t('add_m2m_to_collection', { collection: relatedCollection.value });
|
||||
}
|
||||
|
||||
return t('add_field_related');
|
||||
});
|
||||
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
let fields = [];
|
||||
|
||||
if (junctionCollection.value) {
|
||||
const relations = relationsStore.getRelationsForCollection(junctionCollection.value);
|
||||
fields.push(...relations.map((field) => field.field));
|
||||
}
|
||||
|
||||
return fields;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -70,106 +70,86 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RelatedCollectionSelect },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const correspondingField = syncFieldDetailStoreProperty('fields.corresponding');
|
||||
const correspondingFieldKey = syncFieldDetailStoreProperty('fields.corresponding.field');
|
||||
const onDeleteRelated = syncFieldDetailStoreProperty('relations.m2o.schema.on_delete');
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const correspondingField = syncFieldDetailStoreProperty('fields.corresponding');
|
||||
const correspondingFieldKey = syncFieldDetailStoreProperty('fields.corresponding.field');
|
||||
const onDeleteRelated = syncFieldDetailStoreProperty('relations.m2o.schema.on_delete');
|
||||
|
||||
const { field, collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
const { field, collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
|
||||
const relatedPrimaryKey = computed(
|
||||
() => fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection.value)?.field ?? 'id'
|
||||
);
|
||||
const relatedPrimaryKey = computed(
|
||||
() => fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection.value)?.field ?? 'id'
|
||||
);
|
||||
|
||||
const currentField = computed(() => field.value.field);
|
||||
const currentField = computed(() => field.value.field);
|
||||
|
||||
const hasCorresponding = computed({
|
||||
get() {
|
||||
return !!correspondingField.value;
|
||||
},
|
||||
set(enabled: boolean) {
|
||||
if (enabled) {
|
||||
correspondingField.value = {
|
||||
field: collection.value,
|
||||
collection: relatedCollection.value,
|
||||
type: 'alias',
|
||||
meta: {
|
||||
special: ['o2m'],
|
||||
interface: 'list-o2m',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
correspondingField.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const correspondingLabel = computed(() => {
|
||||
if (relatedCollection.value) {
|
||||
return t('add_o2m_to_collection', { collection: relatedCollection.value });
|
||||
}
|
||||
|
||||
return t('add_field_related');
|
||||
});
|
||||
|
||||
const onDeleteOptions = computed(() =>
|
||||
[
|
||||
{
|
||||
text: t('referential_action_set_null', { field: currentField.value }),
|
||||
value: 'SET NULL',
|
||||
const hasCorresponding = computed({
|
||||
get() {
|
||||
return !!correspondingField.value;
|
||||
},
|
||||
set(enabled: boolean) {
|
||||
if (enabled) {
|
||||
correspondingField.value = {
|
||||
field: collection.value,
|
||||
collection: relatedCollection.value,
|
||||
type: 'alias',
|
||||
meta: {
|
||||
special: ['o2m'],
|
||||
interface: 'list-o2m',
|
||||
},
|
||||
{
|
||||
text: t('referential_action_set_default', { field: currentField.value }),
|
||||
value: 'SET DEFAULT',
|
||||
},
|
||||
{
|
||||
text: t('referential_action_cascade', {
|
||||
collection: collection.value,
|
||||
field: currentField.value,
|
||||
}),
|
||||
value: 'CASCADE',
|
||||
},
|
||||
{
|
||||
text: t('referential_action_no_action', { field: currentField.value }),
|
||||
value: 'NO ACTION',
|
||||
},
|
||||
].filter((o) => !(o.value === 'SET NULL' && field.value.schema?.is_nullable === false))
|
||||
);
|
||||
|
||||
return {
|
||||
t,
|
||||
collection,
|
||||
relatedCollection,
|
||||
isExisting,
|
||||
relatedPrimaryKey,
|
||||
currentField,
|
||||
hasCorresponding,
|
||||
correspondingLabel,
|
||||
correspondingFieldKey,
|
||||
generationInfo,
|
||||
onDeleteRelated,
|
||||
onDeleteOptions,
|
||||
};
|
||||
};
|
||||
} else {
|
||||
correspondingField.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const correspondingLabel = computed(() => {
|
||||
if (relatedCollection.value) {
|
||||
return t('add_o2m_to_collection', { collection: relatedCollection.value });
|
||||
}
|
||||
|
||||
return t('add_field_related');
|
||||
});
|
||||
|
||||
const onDeleteOptions = computed(() =>
|
||||
[
|
||||
{
|
||||
text: t('referential_action_set_null', { field: currentField.value }),
|
||||
value: 'SET NULL',
|
||||
},
|
||||
{
|
||||
text: t('referential_action_set_default', { field: currentField.value }),
|
||||
value: 'SET DEFAULT',
|
||||
},
|
||||
{
|
||||
text: t('referential_action_cascade', {
|
||||
collection: collection.value,
|
||||
field: currentField.value,
|
||||
}),
|
||||
value: 'CASCADE',
|
||||
},
|
||||
{
|
||||
text: t('referential_action_no_action', { field: currentField.value }),
|
||||
value: 'NO ACTION',
|
||||
},
|
||||
].filter((o) => !(o.value === 'SET NULL' && field.value.schema?.is_nullable === false))
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -112,9 +112,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
@@ -122,51 +122,32 @@ import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useRelationsStore } from '@/stores/relations';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RelatedCollectionSelect, RelatedFieldSelect },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const relatedField = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const onDelete = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeselect = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const relatedField = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const onDelete = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeselect = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
|
||||
const { collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
const { collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
let fields = [];
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
let fields = [];
|
||||
|
||||
if (relatedCollection.value) {
|
||||
const relations = relationsStore.getRelationsForCollection(relatedCollection.value);
|
||||
fields.push(...relations.map((field) => field.field));
|
||||
}
|
||||
if (relatedCollection.value) {
|
||||
const relations = relationsStore.getRelationsForCollection(relatedCollection.value);
|
||||
fields.push(...relations.map((field) => field.field));
|
||||
}
|
||||
|
||||
return fields;
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
isExisting,
|
||||
collection,
|
||||
relatedCollection,
|
||||
currentPrimaryKey,
|
||||
relatedField,
|
||||
generationInfo,
|
||||
sortField,
|
||||
onDelete,
|
||||
onDeselect,
|
||||
unsortableJunctionFields,
|
||||
};
|
||||
},
|
||||
return fields;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ t('languages_collection') }}</div>
|
||||
<related-collection-select v-model="relatedCollection" :disabled="type === 'files' || isExisting" />
|
||||
<related-collection-select v-model="relatedCollection" :disabled="isExisting" />
|
||||
</div>
|
||||
<v-input disabled :model-value="currentPrimaryKey" />
|
||||
<related-field-select
|
||||
@@ -145,110 +145,38 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RelatedCollectionSelect, RelatedFieldSelect },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { field, collection, editing, generationInfo } = storeToRefs(fieldDetailStore);
|
||||
const { collection, editing } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const sortField = syncFieldDetailStoreProperty('relations.o2m.meta.sort_field');
|
||||
const junctionCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const junctionFieldCurrent = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const junctionFieldRelated = syncFieldDetailStoreProperty('relations.m2o.field');
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const autoGenerateJunctionRelation = syncFieldDetailStoreProperty('autoGenerateJunctionRelation');
|
||||
const onDeleteCurrent = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeleteRelated = syncFieldDetailStoreProperty('relations.m2o.schema.on_delete');
|
||||
const deselectAction = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
const correspondingField = syncFieldDetailStoreProperty('fields.corresponding');
|
||||
const junctionCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const junctionFieldCurrent = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const junctionFieldRelated = syncFieldDetailStoreProperty('relations.m2o.field');
|
||||
const relatedCollection = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const autoGenerateJunctionRelation = syncFieldDetailStoreProperty('autoGenerateJunctionRelation');
|
||||
const onDeleteCurrent = syncFieldDetailStoreProperty('relations.o2m.schema.on_delete');
|
||||
const onDeleteRelated = syncFieldDetailStoreProperty('relations.m2o.schema.on_delete');
|
||||
const deselectAction = syncFieldDetailStoreProperty('relations.o2m.meta.one_deselect_action');
|
||||
|
||||
const type = computed(() => field.value.type);
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
const currentPrimaryKey = computed(() => fieldsStore.getPrimaryKeyFieldForCollection(collection.value!)?.field);
|
||||
|
||||
const relatedPrimaryKey = computed(
|
||||
() => fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection.value)?.field ?? 'id'
|
||||
);
|
||||
|
||||
const hasCorresponding = computed({
|
||||
get() {
|
||||
return !!correspondingField.value;
|
||||
},
|
||||
set(enabled: boolean) {
|
||||
if (enabled) {
|
||||
correspondingField.value = {
|
||||
field: collection.value,
|
||||
collection: relatedCollection.value,
|
||||
type: 'alias',
|
||||
meta: {
|
||||
special: ['m2m'],
|
||||
interface: 'list-m2m',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
correspondingField.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const correspondingLabel = computed(() => {
|
||||
if (junctionCollection.value) {
|
||||
return t('add_m2m_to_collection', { collection: relatedCollection.value });
|
||||
}
|
||||
|
||||
return t('add_field_related');
|
||||
});
|
||||
|
||||
const correspondingFieldKey = computed({
|
||||
get() {
|
||||
return correspondingField.value?.field;
|
||||
},
|
||||
set(key: string | undefined) {
|
||||
if (!hasCorresponding.value) {
|
||||
hasCorresponding.value = true;
|
||||
}
|
||||
|
||||
correspondingField.value!.field = key;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
autoGenerateJunctionRelation,
|
||||
collection,
|
||||
type,
|
||||
isExisting,
|
||||
junctionCollection,
|
||||
junctionFieldCurrent,
|
||||
relatedCollection,
|
||||
sortField,
|
||||
currentPrimaryKey,
|
||||
junctionFieldRelated,
|
||||
relatedPrimaryKey,
|
||||
onDeleteCurrent,
|
||||
onDeleteRelated,
|
||||
deselectAction,
|
||||
hasCorresponding,
|
||||
correspondingLabel,
|
||||
correspondingFieldKey,
|
||||
generationInfo,
|
||||
};
|
||||
},
|
||||
});
|
||||
const relatedPrimaryKey = computed(
|
||||
() => fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection.value)?.field ?? 'id'
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -6,32 +6,16 @@
|
||||
<relationship-translations v-else-if="localType === 'translations'" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import RelationshipM2o from './field-detail-advanced-relationship-m2o.vue';
|
||||
import RelationshipO2m from './field-detail-advanced-relationship-o2m.vue';
|
||||
import RelationshipM2m from './field-detail-advanced-relationship-m2m.vue';
|
||||
import RelationshipM2a from './field-detail-advanced-relationship-m2a.vue';
|
||||
import RelationshipTranslations from './field-detail-advanced-relationship-translations.vue';
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFieldDetailStore } from '../store';
|
||||
import RelationshipM2a from './field-detail-advanced-relationship-m2a.vue';
|
||||
import RelationshipM2m from './field-detail-advanced-relationship-m2m.vue';
|
||||
import RelationshipM2o from './field-detail-advanced-relationship-m2o.vue';
|
||||
import RelationshipO2m from './field-detail-advanced-relationship-o2m.vue';
|
||||
import RelationshipTranslations from './field-detail-advanced-relationship-translations.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
RelationshipM2o,
|
||||
RelationshipO2m,
|
||||
RelationshipM2m,
|
||||
RelationshipM2a,
|
||||
RelationshipTranslations,
|
||||
},
|
||||
setup() {
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const { collection, localType } = storeToRefs(fieldDetailStore);
|
||||
|
||||
return { collection, localType };
|
||||
},
|
||||
});
|
||||
const { localType } = storeToRefs(fieldDetailStore);
|
||||
</script>
|
||||
|
||||
@@ -132,18 +132,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { GEOMETRY_TYPES } from '@directus/constants';
|
||||
<script setup lang="ts">
|
||||
import { translate } from '@/utils/translate-object-values';
|
||||
import { Type } from '@directus/types';
|
||||
import { TranslateResult } from 'vue-i18n';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { TranslateResult, useI18n } from 'vue-i18n';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store';
|
||||
|
||||
type FieldTypeOption = { value: Type; text: TranslateResult | string; children?: FieldTypeOption[] };
|
||||
export const fieldTypes: Array<FieldTypeOption | { divider: true }> = [
|
||||
|
||||
const fieldTypes: Array<FieldTypeOption | { divider: true }> = [
|
||||
{
|
||||
text: '$t:string',
|
||||
value: 'string',
|
||||
@@ -239,250 +238,216 @@ export const fieldTypes: Array<FieldTypeOption | { divider: true }> = [
|
||||
},
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const { localType, relations, editing } = storeToRefs(fieldDetailStore);
|
||||
const { localType, relations, editing } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
const isExisting = computed(() => editing.value !== '+');
|
||||
|
||||
const type = syncFieldDetailStoreProperty('field.type');
|
||||
const defaultValue = syncFieldDetailStoreProperty('field.schema.default_value');
|
||||
const field = syncFieldDetailStoreProperty('field.field');
|
||||
const special = syncFieldDetailStoreProperty('field.meta.special');
|
||||
const maxLength = syncFieldDetailStoreProperty('field.schema.max_length');
|
||||
const numericPrecision = syncFieldDetailStoreProperty('field.schema.numeric_precision');
|
||||
const nullable = syncFieldDetailStoreProperty('field.schema.is_nullable', true);
|
||||
const unique = syncFieldDetailStoreProperty('field.schema.is_unique', false);
|
||||
const numericScale = syncFieldDetailStoreProperty('field.schema.numeric_scale');
|
||||
const type = syncFieldDetailStoreProperty('field.type');
|
||||
const defaultValue = syncFieldDetailStoreProperty('field.schema.default_value');
|
||||
const field = syncFieldDetailStoreProperty('field.field');
|
||||
const special = syncFieldDetailStoreProperty('field.meta.special');
|
||||
const maxLength = syncFieldDetailStoreProperty('field.schema.max_length');
|
||||
const numericPrecision = syncFieldDetailStoreProperty('field.schema.numeric_precision');
|
||||
const nullable = syncFieldDetailStoreProperty('field.schema.is_nullable', true);
|
||||
const unique = syncFieldDetailStoreProperty('field.schema.is_unique', false);
|
||||
const numericScale = syncFieldDetailStoreProperty('field.schema.numeric_scale');
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const typesWithLabels = computed(() => translate(fieldTypes));
|
||||
const typesWithLabels = computed(() => translate(fieldTypes));
|
||||
|
||||
const typeDisabled = computed(() => localType.value !== 'standard');
|
||||
const typeDisabled = computed(() => localType.value !== 'standard');
|
||||
|
||||
const typePlaceholder = computed(() => {
|
||||
if (localType.value === 'm2o') {
|
||||
return t('determined_by_relationship');
|
||||
const typePlaceholder = computed(() => {
|
||||
if (localType.value === 'm2o') {
|
||||
return t('determined_by_relationship');
|
||||
}
|
||||
|
||||
return t('choose_a_type');
|
||||
});
|
||||
|
||||
const { onCreateOptions, onCreateValue } = useOnCreate();
|
||||
const { onUpdateOptions, onUpdateValue } = useOnUpdate();
|
||||
|
||||
const hasCreateUpdateTriggers = computed(() => {
|
||||
return ['uuid', 'date', 'time', 'dateTime', 'timestamp'].includes(type.value) && localType.value !== 'file';
|
||||
});
|
||||
|
||||
const isAlias = computed(() => {
|
||||
return !fieldDetailStore.field.schema;
|
||||
});
|
||||
|
||||
const isPrimaryKey = computed(() => {
|
||||
return fieldDetailStore.field.schema?.is_primary_key === true;
|
||||
});
|
||||
|
||||
const isGenerated = computed(() => {
|
||||
return fieldDetailStore.field.schema?.is_generated;
|
||||
});
|
||||
|
||||
function useOnCreate() {
|
||||
const onCreateSpecials = ['uuid', 'user-created', 'role-created', 'date-created'];
|
||||
|
||||
const onCreateOptions = computed(() => {
|
||||
if (type.value === 'uuid') {
|
||||
const options = [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
text: t('generate_and_save_uuid'),
|
||||
value: 'uuid',
|
||||
},
|
||||
{
|
||||
text: t('save_current_user_id'),
|
||||
value: 'user-created',
|
||||
},
|
||||
{
|
||||
text: t('save_current_user_role'),
|
||||
value: 'role-created',
|
||||
},
|
||||
];
|
||||
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_users') {
|
||||
return options.filter(({ value }) => [null, 'user-created'].includes(value));
|
||||
}
|
||||
|
||||
return t('choose_a_type');
|
||||
});
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_roles') {
|
||||
return options.filter(({ value }) => [null, 'role-created'].includes(value));
|
||||
}
|
||||
|
||||
const { onCreateOptions, onCreateValue } = useOnCreate();
|
||||
const { onUpdateOptions, onUpdateValue } = useOnUpdate();
|
||||
|
||||
const hasCreateUpdateTriggers = computed(() => {
|
||||
return ['uuid', 'date', 'time', 'dateTime', 'timestamp'].includes(type.value) && localType.value !== 'file';
|
||||
});
|
||||
|
||||
const isAlias = computed(() => {
|
||||
return !fieldDetailStore.field.schema;
|
||||
});
|
||||
|
||||
const isPrimaryKey = computed(() => {
|
||||
return fieldDetailStore.field.schema?.is_primary_key === true;
|
||||
});
|
||||
|
||||
const isGenerated = computed(() => {
|
||||
return fieldDetailStore.field.schema?.is_generated;
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
typesWithLabels,
|
||||
GEOMETRY_TYPES,
|
||||
typeDisabled,
|
||||
typePlaceholder,
|
||||
defaultValue,
|
||||
onCreateOptions,
|
||||
onCreateValue,
|
||||
onUpdateOptions,
|
||||
onUpdateValue,
|
||||
hasCreateUpdateTriggers,
|
||||
field,
|
||||
isAlias,
|
||||
type,
|
||||
maxLength,
|
||||
numericPrecision,
|
||||
numericScale,
|
||||
special,
|
||||
nullable,
|
||||
unique,
|
||||
isPrimaryKey,
|
||||
isExisting,
|
||||
isGenerated,
|
||||
};
|
||||
|
||||
function useOnCreate() {
|
||||
const onCreateSpecials = ['uuid', 'user-created', 'role-created', 'date-created'];
|
||||
|
||||
const onCreateOptions = computed(() => {
|
||||
if (type.value === 'uuid') {
|
||||
const options = [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
text: t('generate_and_save_uuid'),
|
||||
value: 'uuid',
|
||||
},
|
||||
{
|
||||
text: t('save_current_user_id'),
|
||||
value: 'user-created',
|
||||
},
|
||||
{
|
||||
text: t('save_current_user_role'),
|
||||
value: 'role-created',
|
||||
},
|
||||
];
|
||||
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_users') {
|
||||
return options.filter(({ value }) => [null, 'user-created'].includes(value));
|
||||
}
|
||||
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_roles') {
|
||||
return options.filter(({ value }) => [null, 'role-created'].includes(value));
|
||||
}
|
||||
|
||||
return options;
|
||||
} else if (['date', 'time', 'dateTime', 'timestamp'].includes(type.value!)) {
|
||||
return [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
text: t('save_current_datetime'),
|
||||
value: 'date-created',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const onCreateValue = computed({
|
||||
get() {
|
||||
const specials = special.value ?? [];
|
||||
|
||||
for (const special of onCreateSpecials) {
|
||||
if (specials.includes(special)) {
|
||||
return special;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return options;
|
||||
} else if (['date', 'time', 'dateTime', 'timestamp'].includes(type.value!)) {
|
||||
return [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
set(newOption: string | null) {
|
||||
// In case of previously persisted empty string
|
||||
if (typeof special.value === 'string') {
|
||||
special.value = [];
|
||||
}
|
||||
|
||||
special.value = (special.value ?? []).filter(
|
||||
(special: string) => onCreateSpecials.includes(special) === false
|
||||
);
|
||||
|
||||
if (newOption) {
|
||||
special.value = [...(special.value ?? []), newOption];
|
||||
}
|
||||
|
||||
// Prevent empty array saved as empty string
|
||||
if (special.value && special.value.length === 0) {
|
||||
special.value = null;
|
||||
}
|
||||
{
|
||||
text: t('save_current_datetime'),
|
||||
value: 'date-created',
|
||||
},
|
||||
});
|
||||
|
||||
return { onCreateSpecials, onCreateOptions, onCreateValue };
|
||||
];
|
||||
}
|
||||
|
||||
function useOnUpdate() {
|
||||
const onUpdateSpecials = ['user-updated', 'role-updated', 'date-updated'];
|
||||
return [];
|
||||
});
|
||||
|
||||
const onUpdateOptions = computed(() => {
|
||||
if (type.value === 'uuid') {
|
||||
const options = [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
text: t('save_current_user_id'),
|
||||
value: 'user-updated',
|
||||
},
|
||||
{
|
||||
text: t('save_current_user_role'),
|
||||
value: 'role-updated',
|
||||
},
|
||||
];
|
||||
const onCreateValue = computed({
|
||||
get() {
|
||||
const specials = special.value ?? [];
|
||||
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_users') {
|
||||
return options.filter(({ value }) => [null, 'user-updated'].includes(value));
|
||||
}
|
||||
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_roles') {
|
||||
return options.filter(({ value }) => [null, 'role-updated'].includes(value));
|
||||
}
|
||||
|
||||
return options;
|
||||
} else if (['date', 'time', 'dateTime', 'timestamp'].includes(type.value!)) {
|
||||
return [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
text: t('save_current_datetime'),
|
||||
value: 'date-updated',
|
||||
},
|
||||
];
|
||||
for (const special of onCreateSpecials) {
|
||||
if (specials.includes(special)) {
|
||||
return special;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
return null;
|
||||
},
|
||||
set(newOption: string | null) {
|
||||
// In case of previously persisted empty string
|
||||
if (typeof special.value === 'string') {
|
||||
special.value = [];
|
||||
}
|
||||
|
||||
const onUpdateValue = computed({
|
||||
get() {
|
||||
const specials = special.value ?? [];
|
||||
special.value = (special.value ?? []).filter((special: string) => onCreateSpecials.includes(special) === false);
|
||||
|
||||
for (const special of onUpdateSpecials) {
|
||||
if (specials.includes(special)) {
|
||||
return special;
|
||||
}
|
||||
}
|
||||
if (newOption) {
|
||||
special.value = [...(special.value ?? []), newOption];
|
||||
}
|
||||
|
||||
return null;
|
||||
// Prevent empty array saved as empty string
|
||||
if (special.value && special.value.length === 0) {
|
||||
special.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { onCreateSpecials, onCreateOptions, onCreateValue };
|
||||
}
|
||||
|
||||
function useOnUpdate() {
|
||||
const onUpdateSpecials = ['user-updated', 'role-updated', 'date-updated'];
|
||||
|
||||
const onUpdateOptions = computed(() => {
|
||||
if (type.value === 'uuid') {
|
||||
const options = [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
set(newOption: string | null) {
|
||||
// In case of previously persisted empty string
|
||||
if (typeof special.value === 'string') {
|
||||
special.value = [];
|
||||
}
|
||||
|
||||
special.value = (special.value ?? []).filter(
|
||||
(special: string) => onUpdateSpecials.includes(special) === false
|
||||
);
|
||||
|
||||
if (newOption) {
|
||||
special.value = [...(special.value ?? []), newOption];
|
||||
}
|
||||
|
||||
// Prevent empty array saved as empty string
|
||||
if (special.value && special.value.length === 0) {
|
||||
special.value = null;
|
||||
}
|
||||
{
|
||||
text: t('save_current_user_id'),
|
||||
value: 'user-updated',
|
||||
},
|
||||
});
|
||||
{
|
||||
text: t('save_current_user_role'),
|
||||
value: 'role-updated',
|
||||
},
|
||||
];
|
||||
|
||||
return { onUpdateSpecials, onUpdateOptions, onUpdateValue };
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_users') {
|
||||
return options.filter(({ value }) => [null, 'user-updated'].includes(value));
|
||||
}
|
||||
|
||||
if (localType.value === 'm2o' && relations.value.m2o?.related_collection === 'directus_roles') {
|
||||
return options.filter(({ value }) => [null, 'role-updated'].includes(value));
|
||||
}
|
||||
|
||||
return options;
|
||||
} else if (['date', 'time', 'dateTime', 'timestamp'].includes(type.value!)) {
|
||||
return [
|
||||
{
|
||||
text: t('do_nothing'),
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
text: t('save_current_datetime'),
|
||||
value: 'date-updated',
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const onUpdateValue = computed({
|
||||
get() {
|
||||
const specials = special.value ?? [];
|
||||
|
||||
for (const special of onUpdateSpecials) {
|
||||
if (specials.includes(special)) {
|
||||
return special;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
set(newOption: string | null) {
|
||||
// In case of previously persisted empty string
|
||||
if (typeof special.value === 'string') {
|
||||
special.value = [];
|
||||
}
|
||||
|
||||
special.value = (special.value ?? []).filter((special: string) => onUpdateSpecials.includes(special) === false);
|
||||
|
||||
if (newOption) {
|
||||
special.value = [...(special.value ?? []), newOption];
|
||||
}
|
||||
|
||||
// Prevent empty array saved as empty string
|
||||
if (special.value && special.value.length === 0) {
|
||||
special.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { onUpdateSpecials, onUpdateOptions, onUpdateValue };
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -6,87 +6,80 @@
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
<script setup lang="ts">
|
||||
import { useSync } from '@directus/composables';
|
||||
import { useFieldDetailStore } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFieldDetailStore } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
currentTab: {
|
||||
type: Array as PropType<string[]>,
|
||||
required: true,
|
||||
const props = defineProps<{
|
||||
currentTab: string[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:currentTab']);
|
||||
|
||||
const fieldDetail = useFieldDetailStore();
|
||||
|
||||
const { localType } = storeToRefs(fieldDetail);
|
||||
|
||||
const currentTabSync = useSync(props, 'currentTab', emit);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const tabs = computed(() => {
|
||||
const tabs = [
|
||||
{
|
||||
text: t('schema'),
|
||||
value: 'schema',
|
||||
},
|
||||
},
|
||||
emits: ['update:currentTab'],
|
||||
setup(props, { emit }) {
|
||||
const fieldDetail = useFieldDetailStore();
|
||||
{
|
||||
text: t('field', 1),
|
||||
value: 'field',
|
||||
},
|
||||
{
|
||||
text: t('interface_label'),
|
||||
value: 'interface',
|
||||
},
|
||||
];
|
||||
|
||||
const { localType } = storeToRefs(fieldDetail);
|
||||
|
||||
const currentTabSync = useSync(props, 'currentTab', emit);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const tabs = computed(() => {
|
||||
const tabs = [
|
||||
{
|
||||
text: t('schema'),
|
||||
value: 'schema',
|
||||
},
|
||||
{
|
||||
text: t('field', 1),
|
||||
value: 'field',
|
||||
},
|
||||
{
|
||||
text: t('interface_label'),
|
||||
value: 'interface',
|
||||
},
|
||||
];
|
||||
|
||||
if (localType.value !== 'presentation' && localType.value !== 'group') {
|
||||
tabs.push({
|
||||
text: t('display'),
|
||||
value: 'display',
|
||||
});
|
||||
}
|
||||
|
||||
if (['o2m', 'm2o', 'm2m', 'm2a', 'files', 'file'].includes(localType.value)) {
|
||||
tabs.splice(1, 0, {
|
||||
text: t('relationship'),
|
||||
value: 'relationship',
|
||||
});
|
||||
}
|
||||
|
||||
if (localType.value === 'translations') {
|
||||
tabs.splice(
|
||||
1,
|
||||
0,
|
||||
...[
|
||||
{
|
||||
text: t('translations'),
|
||||
value: 'relationship',
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
text: t('validation'),
|
||||
value: 'validation',
|
||||
});
|
||||
|
||||
tabs.push({
|
||||
text: t('conditions'),
|
||||
value: 'conditions',
|
||||
});
|
||||
|
||||
return tabs;
|
||||
if (localType.value !== 'presentation' && localType.value !== 'group') {
|
||||
tabs.push({
|
||||
text: t('display'),
|
||||
value: 'display',
|
||||
});
|
||||
}
|
||||
|
||||
return { tabs, currentTabSync };
|
||||
},
|
||||
if (['o2m', 'm2o', 'm2m', 'm2a', 'files', 'file'].includes(localType.value)) {
|
||||
tabs.splice(1, 0, {
|
||||
text: t('relationship'),
|
||||
value: 'relationship',
|
||||
});
|
||||
}
|
||||
|
||||
if (localType.value === 'translations') {
|
||||
tabs.splice(
|
||||
1,
|
||||
0,
|
||||
...[
|
||||
{
|
||||
text: t('translations'),
|
||||
value: 'relationship',
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
text: t('validation'),
|
||||
value: 'validation',
|
||||
});
|
||||
|
||||
tabs.push({
|
||||
text: t('conditions'),
|
||||
value: 'conditions',
|
||||
});
|
||||
|
||||
return tabs;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -19,11 +19,9 @@ import FieldDetailAdvancedDisplay from './field-detail-advanced-display.vue';
|
||||
import FieldDetailAdvancedValidation from './field-detail-advanced-validation.vue';
|
||||
import FieldDetailAdvancedConditions from './field-detail-advanced-conditions.vue';
|
||||
|
||||
interface Props {
|
||||
defineProps<{
|
||||
currentTab: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -61,113 +61,86 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFieldDetailStore, syncFieldDetailStoreProperty } from '../store/';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import ExtensionOptions from '../shared/extension-options.vue';
|
||||
import RelationshipConfiguration from './relationship-configuration.vue';
|
||||
import { useExtensions } from '@/extensions';
|
||||
<script setup lang="ts">
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
import { useExtensions } from '@/extensions';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ExtensionOptions from '../shared/extension-options.vue';
|
||||
import { syncFieldDetailStoreProperty, useFieldDetailStore } from '../store/';
|
||||
import RelationshipConfiguration from './relationship-configuration.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ExtensionOptions, RelationshipConfiguration },
|
||||
props: {
|
||||
row: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
defineProps<{
|
||||
row?: number;
|
||||
}>();
|
||||
|
||||
defineEmits(['save', 'toggleAdvanced']);
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const { readyToSave, saving, localType } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const key = syncFieldDetailStoreProperty('field.field');
|
||||
const type = syncFieldDetailStoreProperty('field.type');
|
||||
const defaultValue = syncFieldDetailStoreProperty('field.schema.default_value');
|
||||
const chosenInterface = syncFieldDetailStoreProperty('field.meta.interface');
|
||||
const required = syncFieldDetailStoreProperty('field.meta.required', false);
|
||||
|
||||
const chosenInterfaceConfig = useExtension('interface', chosenInterface);
|
||||
|
||||
const typeOptions = computed(() => {
|
||||
if (!chosenInterfaceConfig.value) return [];
|
||||
|
||||
return chosenInterfaceConfig.value.types.map((type) => ({
|
||||
text: t(type),
|
||||
value: type,
|
||||
}));
|
||||
});
|
||||
|
||||
const typeDisabled = computed(() => typeOptions.value.length === 1 || localType.value !== 'standard');
|
||||
|
||||
const { interfaces } = useExtensions();
|
||||
|
||||
const interfaceIdsToInterface = computed(() => Object.fromEntries(interfaces.value.map((inter) => [inter.id, inter])));
|
||||
|
||||
const customOptionsFields = computed(() => {
|
||||
if (typeof chosenInterfaceConfig.value?.options === 'function') {
|
||||
return chosenInterfaceConfig.value?.options(fieldDetailStore);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
watch(
|
||||
chosenInterface,
|
||||
(newVal, oldVal) => {
|
||||
if (!newVal) return;
|
||||
|
||||
if (interfaceIdsToInterface.value[newVal].autoKey) {
|
||||
const simplifiedId = newVal.includes('-') ? newVal.split('-')[1] : newVal;
|
||||
key.value = `${simplifiedId}-${nanoid(6).toLowerCase()}`;
|
||||
} else if (oldVal && interfaceIdsToInterface.value[oldVal].autoKey) {
|
||||
key.value = null;
|
||||
}
|
||||
},
|
||||
emits: ['save', 'toggleAdvanced'],
|
||||
setup() {
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const { readyToSave, saving, localType } = storeToRefs(fieldDetailStore);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const key = syncFieldDetailStoreProperty('field.field');
|
||||
const type = syncFieldDetailStoreProperty('field.type');
|
||||
const defaultValue = syncFieldDetailStoreProperty('field.schema.default_value');
|
||||
const chosenInterface = syncFieldDetailStoreProperty('field.meta.interface');
|
||||
const required = syncFieldDetailStoreProperty('field.meta.required', false);
|
||||
const note = syncFieldDetailStoreProperty('field.meta.note');
|
||||
|
||||
const chosenInterfaceConfig = useExtension('interface', chosenInterface);
|
||||
|
||||
const typeOptions = computed(() => {
|
||||
if (!chosenInterfaceConfig.value) return [];
|
||||
|
||||
return chosenInterfaceConfig.value.types.map((type) => ({
|
||||
text: t(type),
|
||||
value: type,
|
||||
}));
|
||||
const options = computed({
|
||||
get() {
|
||||
return fieldDetailStore.field.meta?.options ?? {};
|
||||
},
|
||||
set(newOptions: Record<string, any>) {
|
||||
fieldDetailStore.$patch((state) => {
|
||||
state.field.meta = {
|
||||
...(state.field.meta ?? {}),
|
||||
options: newOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const typeDisabled = computed(() => typeOptions.value.length === 1 || localType.value !== 'standard');
|
||||
|
||||
const { interfaces } = useExtensions();
|
||||
|
||||
const interfaceIdsToInterface = computed(() =>
|
||||
Object.fromEntries(interfaces.value.map((inter) => [inter.id, inter]))
|
||||
);
|
||||
|
||||
const customOptionsFields = computed(() => {
|
||||
if (typeof chosenInterfaceConfig.value?.options === 'function') {
|
||||
return chosenInterfaceConfig.value?.options(fieldDetailStore);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
watch(
|
||||
chosenInterface,
|
||||
(newVal, oldVal) => {
|
||||
if (!newVal) return;
|
||||
|
||||
if (interfaceIdsToInterface.value[newVal].autoKey) {
|
||||
const simplifiedId = newVal.includes('-') ? newVal.split('-')[1] : newVal;
|
||||
key.value = `${simplifiedId}-${nanoid(6).toLowerCase()}`;
|
||||
} else if (oldVal && interfaceIdsToInterface.value[oldVal].autoKey) {
|
||||
key.value = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const options = computed({
|
||||
get() {
|
||||
return fieldDetailStore.field.meta?.options ?? {};
|
||||
},
|
||||
set(newOptions: Record<string, any>) {
|
||||
fieldDetailStore.$patch((state) => {
|
||||
state.field.meta = {
|
||||
...(state.field.meta ?? {}),
|
||||
options: newOptions,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
key,
|
||||
t,
|
||||
type,
|
||||
typeDisabled,
|
||||
typeOptions,
|
||||
defaultValue,
|
||||
chosenInterface,
|
||||
chosenInterfaceConfig,
|
||||
required,
|
||||
note,
|
||||
readyToSave,
|
||||
saving,
|
||||
localType,
|
||||
customOptionsFields,
|
||||
options,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -67,53 +67,44 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { LOCAL_TYPES } from '@directus/constants';
|
||||
import { syncFieldDetailStoreProperty } from '../store';
|
||||
import { orderBy } from 'lodash';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import RelatedCollectionSelect from '../shared/related-collection-select.vue';
|
||||
import RelatedFieldSelect from '../shared/related-field-select.vue';
|
||||
import { orderBy } from 'lodash';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { syncFieldDetailStoreProperty } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RelatedCollectionSelect, RelatedFieldSelect },
|
||||
props: {
|
||||
localType: {
|
||||
type: String as PropType<(typeof LOCAL_TYPES)[number]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
defineProps<{
|
||||
localType: (typeof LOCAL_TYPES)[number];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const relatedCollectionM2O = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const o2mCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const o2mField = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const oneAllowedCollections = syncFieldDetailStoreProperty('relations.m2o.meta.one_allowed_collections', []);
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
[
|
||||
...collectionsStore.databaseCollections,
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
name: t('system'),
|
||||
selectable: false,
|
||||
children: collectionsStore.crudSafeSystemCollections,
|
||||
},
|
||||
],
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const relatedCollectionM2O = syncFieldDetailStoreProperty('relations.m2o.related_collection');
|
||||
const o2mCollection = syncFieldDetailStoreProperty('relations.o2m.collection');
|
||||
const o2mField = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const oneAllowedCollections = syncFieldDetailStoreProperty('relations.m2o.meta.one_allowed_collections', []);
|
||||
|
||||
return { availableCollections, oneAllowedCollections, relatedCollectionM2O, o2mCollection, o2mField, t };
|
||||
},
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
[
|
||||
...collectionsStore.databaseCollections,
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
name: t('system'),
|
||||
selectable: false,
|
||||
children: collectionsStore.crudSafeSystemCollections,
|
||||
},
|
||||
],
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -28,103 +28,78 @@
|
||||
</v-error-boundary>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed, toRefs } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFieldDetailStore } from '../store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String as PropType<'interface' | 'display' | 'panel' | 'operation'>,
|
||||
required: true,
|
||||
},
|
||||
extension: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
showAdvanced: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rawEditorEnabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
type: 'interface' | 'display' | 'panel' | 'operation';
|
||||
extension?: string;
|
||||
showAdvanced?: boolean;
|
||||
options?: Record<string, any>;
|
||||
modelValue?: Record<string, any>;
|
||||
disabled?: boolean;
|
||||
rawEditorEnabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showAdvanced: false,
|
||||
modelValue: () => ({}),
|
||||
disabled: false,
|
||||
rawEditorEnabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const { collection, field } = storeToRefs(fieldDetailStore);
|
||||
const { extension, type } = toRefs(props);
|
||||
|
||||
const extensionInfo = useExtension(type, extension);
|
||||
|
||||
const usesCustomComponent = computed(() => {
|
||||
if (!extensionInfo.value) return false;
|
||||
|
||||
return extensionInfo.value.options && 'render' in extensionInfo.value.options;
|
||||
});
|
||||
|
||||
const optionsFields = computed(() => {
|
||||
if (usesCustomComponent.value === true) return [];
|
||||
|
||||
let optionsObjectOrArray;
|
||||
|
||||
if (props.options) {
|
||||
optionsObjectOrArray = props.options;
|
||||
} else {
|
||||
if (!extensionInfo.value) return [];
|
||||
if (!extensionInfo.value?.options) return [];
|
||||
optionsObjectOrArray = extensionInfo.value.options;
|
||||
}
|
||||
|
||||
if (!optionsObjectOrArray) return [];
|
||||
|
||||
if (Array.isArray(optionsObjectOrArray)) return optionsObjectOrArray;
|
||||
|
||||
if (props.showAdvanced) {
|
||||
return [...optionsObjectOrArray.standard, ...optionsObjectOrArray.advanced];
|
||||
}
|
||||
|
||||
return optionsObjectOrArray.standard;
|
||||
});
|
||||
|
||||
const optionsValues = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const { collection, field } = storeToRefs(fieldDetailStore);
|
||||
const { extension, type } = toRefs(props);
|
||||
|
||||
const extensionInfo = useExtension(type, extension);
|
||||
|
||||
const usesCustomComponent = computed(() => {
|
||||
if (!extensionInfo.value) return false;
|
||||
|
||||
return extensionInfo.value.options && 'render' in extensionInfo.value.options;
|
||||
});
|
||||
|
||||
const optionsFields = computed(() => {
|
||||
if (usesCustomComponent.value === true) return [];
|
||||
|
||||
let optionsObjectOrArray;
|
||||
|
||||
if (props.options) {
|
||||
optionsObjectOrArray = props.options;
|
||||
} else {
|
||||
if (!extensionInfo.value) return [];
|
||||
if (!extensionInfo.value?.options) return [];
|
||||
optionsObjectOrArray = extensionInfo.value.options;
|
||||
}
|
||||
|
||||
if (!optionsObjectOrArray) return [];
|
||||
|
||||
if (Array.isArray(optionsObjectOrArray)) return optionsObjectOrArray;
|
||||
|
||||
if (props.showAdvanced) {
|
||||
return [...optionsObjectOrArray.standard, ...optionsObjectOrArray.advanced];
|
||||
}
|
||||
|
||||
return optionsObjectOrArray.standard;
|
||||
});
|
||||
|
||||
const optionsValues = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(values: Record<string, any>) {
|
||||
emit('update:modelValue', values);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
usesCustomComponent,
|
||||
extensionInfo,
|
||||
optionsValues,
|
||||
optionsFields,
|
||||
t,
|
||||
collection,
|
||||
field,
|
||||
};
|
||||
set(values: Record<string, any>) {
|
||||
emit('update:modelValue', values);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -55,39 +55,34 @@
|
||||
</v-input>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
<script setup lang="ts">
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { orderBy } from 'lodash';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
const collectionExists = computed(() => {
|
||||
return !!collectionsStore.getCollection(props.modelValue);
|
||||
});
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(collectionsStore.databaseCollections, ['sort', 'collection'], ['asc']);
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const systemCollections = collectionsStore.crudSafeSystemCollections;
|
||||
|
||||
return { t, collectionExists, availableCollections, systemCollections };
|
||||
},
|
||||
const collectionExists = computed(() => {
|
||||
return !!collectionsStore.getCollection(props.modelValue);
|
||||
});
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(collectionsStore.databaseCollections, ['sort', 'collection'], ['asc']);
|
||||
});
|
||||
|
||||
const systemCollections = collectionsStore.crudSafeSystemCollections;
|
||||
</script>
|
||||
|
||||
@@ -37,73 +37,56 @@
|
||||
</v-input>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
<script setup lang="ts">
|
||||
import { i18n } from '@/lang';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabledFields: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
typeDenyList: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
typeAllowList: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: undefined,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => i18n.global.t('foreign_key') + '...',
|
||||
},
|
||||
nullable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
disabled?: boolean;
|
||||
collection?: string;
|
||||
disabledFields?: string[];
|
||||
|
||||
const fields = computed(() => {
|
||||
if (!props.collection) return [];
|
||||
typeDenyList?: string[];
|
||||
typeAllowList?: string[];
|
||||
|
||||
return fieldsStore.getFieldsForCollectionAlphabetical(props.collection).map((field) => ({
|
||||
text: field.field,
|
||||
value: field.field,
|
||||
disabled:
|
||||
!field.schema ||
|
||||
!!field.schema?.is_primary_key ||
|
||||
props.disabledFields.includes(field.field) ||
|
||||
props.typeDenyList.includes(field.type) ||
|
||||
(props.typeAllowList && !props.typeAllowList.includes(field.type)),
|
||||
}));
|
||||
});
|
||||
placeholder?: string;
|
||||
nullable?: boolean;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
disabledFields: () => [],
|
||||
typeDenyList: () => [],
|
||||
placeholder: () => i18n.global.t('foreign_key') + '...',
|
||||
nullable: false,
|
||||
}
|
||||
);
|
||||
|
||||
const fieldExists = computed(() => {
|
||||
if (!props.collection || !props.modelValue) return false;
|
||||
return !!fieldsStore.getField(props.collection, props.modelValue);
|
||||
});
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
return { t, fields, fieldExists };
|
||||
},
|
||||
const { t } = useI18n();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const fields = computed(() => {
|
||||
if (!props.collection) return [];
|
||||
|
||||
return fieldsStore.getFieldsForCollectionAlphabetical(props.collection).map((field) => ({
|
||||
text: field.field,
|
||||
value: field.field,
|
||||
disabled:
|
||||
!field.schema ||
|
||||
!!field.schema?.is_primary_key ||
|
||||
props.disabledFields.includes(field.field) ||
|
||||
props.typeDenyList.includes(field.type) ||
|
||||
(props.typeAllowList && !props.typeAllowList.includes(field.type)),
|
||||
}));
|
||||
});
|
||||
|
||||
const fieldExists = computed(() => {
|
||||
if (!props.collection || !props.modelValue) return false;
|
||||
return !!fieldsStore.getField(props.collection, props.modelValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -76,36 +76,30 @@
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue';
|
||||
import { Field } from '@directus/types';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
<script setup lang="ts">
|
||||
import { getLocalTypeForField } from '@/utils/get-local-type';
|
||||
import { Field } from '@directus/types';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldSelectMenu',
|
||||
props: {
|
||||
field: {
|
||||
type: Object as PropType<Field>,
|
||||
required: true,
|
||||
},
|
||||
noDelete: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['toggleVisibility', 'duplicate', 'delete', 'setWidth'],
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
field: Field;
|
||||
noDelete?: boolean;
|
||||
}>(),
|
||||
{
|
||||
noDelete: false,
|
||||
}
|
||||
);
|
||||
|
||||
const localType = computed(() => getLocalTypeForField(props.field.collection, props.field.field));
|
||||
const isPrimaryKey = computed(() => props.field.schema?.is_primary_key === true);
|
||||
defineEmits(['toggleVisibility', 'duplicate', 'delete', 'setWidth']);
|
||||
|
||||
const duplicable = computed(() => localType.value === 'standard' && isPrimaryKey.value === false);
|
||||
const { t } = useI18n();
|
||||
|
||||
return { t, localType, isPrimaryKey, duplicable };
|
||||
},
|
||||
});
|
||||
const localType = computed(() => getLocalTypeForField(props.field.collection, props.field.field));
|
||||
const isPrimaryKey = computed(() => props.field.schema?.is_primary_key === true);
|
||||
|
||||
const duplicable = computed(() => localType.value === 'standard' && isPrimaryKey.value === false);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -151,9 +151,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, PropType, ref, computed, unref } from 'vue';
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -170,193 +170,158 @@ import formatTitle from '@directus/format-title';
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
import { getRelatedCollection } from '@/utils/get-related-collection';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldSelect',
|
||||
components: { FieldSelectMenu, Draggable },
|
||||
props: {
|
||||
field: {
|
||||
type: Object as PropType<Field>,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fields: {
|
||||
type: Array as PropType<Field[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ['setNestedSort'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
field: Field;
|
||||
disabled?: boolean;
|
||||
fields?: Field[];
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
fields: () => [],
|
||||
}
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
const emit = defineEmits(['setNestedSort']);
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const editActive = ref(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { deleteActive, deleting, deleteField } = useDeleteField();
|
||||
const { duplicateActive, duplicateName, collections, duplicateTo, saveDuplicate, duplicating } = useDuplicate();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const inter = useExtension(
|
||||
'interface',
|
||||
computed(() => props.field.meta?.interface ?? null)
|
||||
);
|
||||
const { deleteActive, deleting, deleteField } = useDeleteField();
|
||||
const { duplicateActive, duplicateName, collections, duplicateTo, saveDuplicate, duplicating } = useDuplicate();
|
||||
|
||||
const interfaceName = computed(() => inter.value?.name ?? null);
|
||||
const inter = useExtension(
|
||||
'interface',
|
||||
computed(() => props.field.meta?.interface ?? null)
|
||||
);
|
||||
|
||||
const hidden = computed(() => props.field.meta?.hidden === true);
|
||||
const interfaceName = computed(() => inter.value?.name ?? null);
|
||||
|
||||
const localType = computed(() => getLocalTypeForField(props.field.collection, props.field.field));
|
||||
const hidden = computed(() => props.field.meta?.hidden === true);
|
||||
|
||||
const nestedFields = computed(() => props.fields.filter((field) => field.meta?.group === props.field.field));
|
||||
const localType = computed(() => getLocalTypeForField(props.field.collection, props.field.field));
|
||||
|
||||
const relatedCollectionInfo = computed(() => getRelatedCollection(props.field.collection, props.field.field));
|
||||
const nestedFields = computed(() => props.fields.filter((field) => field.meta?.group === props.field.field));
|
||||
|
||||
const showRelatedCollectionLink = computed(
|
||||
() =>
|
||||
unref(relatedCollectionInfo) !== null &&
|
||||
props.field.collection !== unref(relatedCollectionInfo)?.relatedCollection &&
|
||||
['translations', 'm2o', 'm2m', 'o2m', 'files'].includes(unref(localType) as string)
|
||||
);
|
||||
const relatedCollectionInfo = computed(() => getRelatedCollection(props.field.collection, props.field.field));
|
||||
|
||||
return {
|
||||
t,
|
||||
interfaceName,
|
||||
formatTitle,
|
||||
editActive,
|
||||
setWidth,
|
||||
deleteActive,
|
||||
deleting,
|
||||
deleteField,
|
||||
duplicateActive,
|
||||
collections,
|
||||
duplicateName,
|
||||
duplicateTo,
|
||||
saveDuplicate,
|
||||
duplicating,
|
||||
openFieldDetail,
|
||||
hidden,
|
||||
toggleVisibility,
|
||||
localType,
|
||||
showRelatedCollectionLink,
|
||||
relatedCollectionInfo,
|
||||
hideDragImage,
|
||||
onGroupSortChange,
|
||||
nestedFields,
|
||||
const showRelatedCollectionLink = computed(
|
||||
() =>
|
||||
unref(relatedCollectionInfo) !== null &&
|
||||
props.field.collection !== unref(relatedCollectionInfo)?.relatedCollection &&
|
||||
['translations', 'm2o', 'm2m', 'o2m', 'files'].includes(unref(localType) as string)
|
||||
);
|
||||
|
||||
function setWidth(width: string) {
|
||||
fieldsStore.updateField(props.field.collection, props.field.field, { meta: { width } });
|
||||
}
|
||||
|
||||
function toggleVisibility() {
|
||||
fieldsStore.updateField(props.field.collection, props.field.field, {
|
||||
meta: { hidden: !props.field.meta?.hidden },
|
||||
});
|
||||
}
|
||||
|
||||
function useDeleteField() {
|
||||
const deleteActive = ref(false);
|
||||
const deleting = ref(false);
|
||||
|
||||
return {
|
||||
deleteActive,
|
||||
deleting,
|
||||
deleteField,
|
||||
};
|
||||
|
||||
async function deleteField() {
|
||||
await fieldsStore.deleteField(props.field.collection, props.field.field);
|
||||
deleting.value = false;
|
||||
deleteActive.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function useDuplicate() {
|
||||
const duplicateActive = ref(false);
|
||||
const duplicateName = ref(props.field.field + '_copy');
|
||||
const duplicating = ref(false);
|
||||
|
||||
const collections = computed(() =>
|
||||
collectionsStore.collections
|
||||
.map(({ collection }) => collection)
|
||||
.filter((collection) => collection.startsWith('directus_') === false)
|
||||
);
|
||||
|
||||
const duplicateTo = ref(props.field.collection);
|
||||
|
||||
return {
|
||||
duplicateActive,
|
||||
duplicateName,
|
||||
collections,
|
||||
duplicateTo,
|
||||
saveDuplicate,
|
||||
duplicating,
|
||||
};
|
||||
|
||||
async function saveDuplicate() {
|
||||
const newField: Record<string, any> = {
|
||||
...cloneDeep(props.field),
|
||||
field: duplicateName.value,
|
||||
collection: duplicateTo.value,
|
||||
};
|
||||
|
||||
function setWidth(width: string) {
|
||||
fieldsStore.updateField(props.field.collection, props.field.field, { meta: { width } });
|
||||
if (newField.meta) {
|
||||
delete newField.meta.id;
|
||||
delete newField.meta.sort;
|
||||
delete newField.meta.group;
|
||||
}
|
||||
|
||||
function toggleVisibility() {
|
||||
fieldsStore.updateField(props.field.collection, props.field.field, {
|
||||
meta: { hidden: !props.field.meta?.hidden },
|
||||
if (newField.schema) {
|
||||
delete newField.schema.comment;
|
||||
}
|
||||
|
||||
delete newField.name;
|
||||
|
||||
duplicating.value = true;
|
||||
|
||||
try {
|
||||
await fieldsStore.createField(duplicateTo.value, newField);
|
||||
|
||||
notify({
|
||||
title: t('field_create_success', { field: newField.field }),
|
||||
});
|
||||
|
||||
duplicateActive.value = false;
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
duplicating.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useDeleteField() {
|
||||
const deleteActive = ref(false);
|
||||
const deleting = ref(false);
|
||||
async function openFieldDetail() {
|
||||
if (!props.field.meta) {
|
||||
const special = getSpecialForType(props.field.type);
|
||||
await fieldsStore.updateField(props.field.collection, props.field.field, { meta: { special } });
|
||||
}
|
||||
|
||||
return {
|
||||
deleteActive,
|
||||
deleting,
|
||||
deleteField,
|
||||
};
|
||||
router.push(`/settings/data-model/${props.field.collection}/${props.field.field}`);
|
||||
}
|
||||
|
||||
async function deleteField() {
|
||||
await fieldsStore.deleteField(props.field.collection, props.field.field);
|
||||
deleting.value = false;
|
||||
deleteActive.value = false;
|
||||
}
|
||||
}
|
||||
async function onGroupSortChange(fields: Field[]) {
|
||||
const updates = fields.map((field, index) => ({
|
||||
field: field.field,
|
||||
meta: {
|
||||
sort: index + 1,
|
||||
group: props.field.meta!.field,
|
||||
},
|
||||
}));
|
||||
|
||||
function useDuplicate() {
|
||||
const duplicateActive = ref(false);
|
||||
const duplicateName = ref(props.field.field + '_copy');
|
||||
const duplicating = ref(false);
|
||||
|
||||
const collections = computed(() =>
|
||||
collectionsStore.collections
|
||||
.map(({ collection }) => collection)
|
||||
.filter((collection) => collection.startsWith('directus_') === false)
|
||||
);
|
||||
|
||||
const duplicateTo = ref(props.field.collection);
|
||||
|
||||
return {
|
||||
duplicateActive,
|
||||
duplicateName,
|
||||
collections,
|
||||
duplicateTo,
|
||||
saveDuplicate,
|
||||
duplicating,
|
||||
};
|
||||
|
||||
async function saveDuplicate() {
|
||||
const newField: Record<string, any> = {
|
||||
...cloneDeep(props.field),
|
||||
field: duplicateName.value,
|
||||
collection: duplicateTo.value,
|
||||
};
|
||||
|
||||
if (newField.meta) {
|
||||
delete newField.meta.id;
|
||||
delete newField.meta.sort;
|
||||
delete newField.meta.group;
|
||||
}
|
||||
|
||||
if (newField.schema) {
|
||||
delete newField.schema.comment;
|
||||
}
|
||||
|
||||
delete newField.name;
|
||||
|
||||
duplicating.value = true;
|
||||
|
||||
try {
|
||||
await fieldsStore.createField(duplicateTo.value, newField);
|
||||
|
||||
notify({
|
||||
title: t('field_create_success', { field: newField.field }),
|
||||
});
|
||||
|
||||
duplicateActive.value = false;
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
duplicating.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openFieldDetail() {
|
||||
if (!props.field.meta) {
|
||||
const special = getSpecialForType(props.field.type);
|
||||
await fieldsStore.updateField(props.field.collection, props.field.field, { meta: { special } });
|
||||
}
|
||||
|
||||
router.push(`/settings/data-model/${props.field.collection}/${props.field.field}`);
|
||||
}
|
||||
|
||||
async function onGroupSortChange(fields: Field[]) {
|
||||
const updates = fields.map((field, index) => ({
|
||||
field: field.field,
|
||||
meta: {
|
||||
sort: index + 1,
|
||||
group: props.field.meta!.field,
|
||||
},
|
||||
}));
|
||||
|
||||
emit('setNestedSort', updates);
|
||||
}
|
||||
},
|
||||
});
|
||||
emit('setNestedSort', updates);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -49,133 +49,122 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, toRefs } from 'vue';
|
||||
import { useCollection } from '@directus/composables';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { Field } from '@directus/types';
|
||||
<script setup lang="ts">
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import FieldSelect from './field-select.vue';
|
||||
import { hideDragImage } from '@/utils/hide-drag-image';
|
||||
import { orderBy, isNil } from 'lodash';
|
||||
import { LocalType } from '@directus/types';
|
||||
import { useCollection } from '@directus/composables';
|
||||
import { Field, LocalType } from '@directus/types';
|
||||
import { isNil, orderBy } from 'lodash';
|
||||
import { computed, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Draggable from 'vuedraggable';
|
||||
import FieldSelect from './field-select.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldsManagement',
|
||||
components: { Draggable, FieldSelect },
|
||||
props: {
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
collection: string;
|
||||
}>();
|
||||
|
||||
const { collection } = toRefs(props);
|
||||
const { fields } = useCollection(collection);
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const parsedFields = computed(() => {
|
||||
return orderBy(fields.value, [(o) => (o.meta?.sort ? Number(o.meta?.sort) : null), (o) => o.meta?.id]).filter(
|
||||
(field) => field.field.startsWith('$') === false
|
||||
);
|
||||
});
|
||||
const { collection } = toRefs(props);
|
||||
const { fields } = useCollection(collection);
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const lockedFields = computed(() => {
|
||||
return parsedFields.value.filter((field) => field.meta?.system === true);
|
||||
});
|
||||
|
||||
const usableFields = computed(() => {
|
||||
return parsedFields.value.filter((field) => field.meta?.system !== true);
|
||||
});
|
||||
|
||||
const addOptions = computed<Array<{ type: LocalType; icon: string; text: any } | { divider: boolean }>>(() => [
|
||||
{
|
||||
type: 'standard',
|
||||
icon: 'create',
|
||||
text: t('standard_field'),
|
||||
},
|
||||
{
|
||||
type: 'presentation',
|
||||
icon: 'scatter_plot',
|
||||
text: t('presentation_and_aliases'),
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
icon: 'view_in_ar',
|
||||
text: t('field_group'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
icon: 'photo',
|
||||
text: t('single_file'),
|
||||
},
|
||||
{
|
||||
type: 'files',
|
||||
icon: 'collections',
|
||||
text: t('multiple_files'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'm2o',
|
||||
icon: 'call_merge',
|
||||
text: t('m2o_relationship'),
|
||||
},
|
||||
{
|
||||
type: 'o2m',
|
||||
icon: 'call_split',
|
||||
text: t('o2m_relationship'),
|
||||
},
|
||||
{
|
||||
type: 'm2m',
|
||||
icon: 'import_export',
|
||||
text: t('m2m_relationship'),
|
||||
},
|
||||
{
|
||||
type: 'm2a',
|
||||
icon: 'gesture',
|
||||
text: t('m2a_relationship'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'translations',
|
||||
icon: 'translate',
|
||||
text: t('translations'),
|
||||
},
|
||||
]);
|
||||
|
||||
return { t, usableFields, lockedFields, setSort, hideDragImage, addOptions, setNestedSort, isNil };
|
||||
|
||||
async function setSort(fields: Field[]) {
|
||||
const updates = fields.map((field, index) => ({
|
||||
field: field.field,
|
||||
meta: {
|
||||
sort: index + 1,
|
||||
group: null,
|
||||
},
|
||||
}));
|
||||
|
||||
await fieldsStore.updateFields(collection.value, updates);
|
||||
}
|
||||
|
||||
async function setNestedSort(updates?: Field[]) {
|
||||
updates = (updates || []).filter((val) => isNil(val) === false);
|
||||
|
||||
if (updates.length > 0) {
|
||||
await fieldsStore.updateFields(collection.value, updates);
|
||||
}
|
||||
}
|
||||
},
|
||||
const parsedFields = computed(() => {
|
||||
return orderBy(fields.value, [(o) => (o.meta?.sort ? Number(o.meta?.sort) : null), (o) => o.meta?.id]).filter(
|
||||
(field) => field.field.startsWith('$') === false
|
||||
);
|
||||
});
|
||||
|
||||
const lockedFields = computed(() => {
|
||||
return parsedFields.value.filter((field) => field.meta?.system === true);
|
||||
});
|
||||
|
||||
const usableFields = computed(() => {
|
||||
return parsedFields.value.filter((field) => field.meta?.system !== true);
|
||||
});
|
||||
|
||||
const addOptions = computed<Array<{ type: LocalType; icon: string; text: any } | { divider: boolean }>>(() => [
|
||||
{
|
||||
type: 'standard',
|
||||
icon: 'create',
|
||||
text: t('standard_field'),
|
||||
},
|
||||
{
|
||||
type: 'presentation',
|
||||
icon: 'scatter_plot',
|
||||
text: t('presentation_and_aliases'),
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
icon: 'view_in_ar',
|
||||
text: t('field_group'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
icon: 'photo',
|
||||
text: t('single_file'),
|
||||
},
|
||||
{
|
||||
type: 'files',
|
||||
icon: 'collections',
|
||||
text: t('multiple_files'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'm2o',
|
||||
icon: 'call_merge',
|
||||
text: t('m2o_relationship'),
|
||||
},
|
||||
{
|
||||
type: 'o2m',
|
||||
icon: 'call_split',
|
||||
text: t('o2m_relationship'),
|
||||
},
|
||||
{
|
||||
type: 'm2m',
|
||||
icon: 'import_export',
|
||||
text: t('m2m_relationship'),
|
||||
},
|
||||
{
|
||||
type: 'm2a',
|
||||
icon: 'gesture',
|
||||
text: t('m2a_relationship'),
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
type: 'translations',
|
||||
icon: 'translate',
|
||||
text: t('translations'),
|
||||
},
|
||||
]);
|
||||
|
||||
async function setSort(fields: Field[]) {
|
||||
const updates = fields.map((field, index) => ({
|
||||
field: field.field,
|
||||
meta: {
|
||||
sort: index + 1,
|
||||
group: null,
|
||||
},
|
||||
}));
|
||||
|
||||
await fieldsStore.updateFields(collection.value, updates);
|
||||
}
|
||||
|
||||
async function setNestedSort(updates?: Field[]) {
|
||||
updates = (updates || []).filter((val) => isNil(val) === false);
|
||||
|
||||
if (updates.length > 0) {
|
||||
await fieldsStore.updateFields(collection.value, updates);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -99,116 +99,77 @@
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, toRefs, ref } from 'vue';
|
||||
import SettingsNavigation from '../../../components/navigation.vue';
|
||||
import { useCollection } from '@directus/composables';
|
||||
import FieldsManagement from './components/fields-management.vue';
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEditsGuard } from '@/composables/use-edits-guard';
|
||||
import { useItem } from '@/composables/use-item';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useShortcut } from '@/composables/use-shortcut';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useShortcut } from '@/composables/use-shortcut';
|
||||
import { useEditsGuard } from '@/composables/use-edits-guard';
|
||||
import { useCollection } from '@directus/composables';
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import SettingsNavigation from '../../../components/navigation.vue';
|
||||
import FieldsManagement from './components/fields-management.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { SettingsNavigation, FieldsManagement },
|
||||
props: {
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
const props = defineProps<{
|
||||
collection: string;
|
||||
// Field detail modal only
|
||||
field?: string;
|
||||
type?: string;
|
||||
}>();
|
||||
|
||||
// Field detail modal only
|
||||
field: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
const { collection } = toRefs(props);
|
||||
const { info: collectionInfo, fields } = useCollection(collection);
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { collection } = toRefs(props);
|
||||
const { info: collectionInfo } = useCollection(collection);
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { isNew, edits, item, saving, loading, error, save, remove, deleting, saveAsCopy, isBatch } = useItem(
|
||||
ref('directus_collections'),
|
||||
collection
|
||||
);
|
||||
const { edits, item, saving, loading, save, remove, deleting, isBatch } = useItem(
|
||||
ref('directus_collections'),
|
||||
collection
|
||||
);
|
||||
|
||||
const hasEdits = computed<boolean>(() => {
|
||||
if (!edits.value.meta) return false;
|
||||
return Object.keys(edits.value.meta).length > 0;
|
||||
});
|
||||
|
||||
useShortcut('meta+s', () => {
|
||||
if (hasEdits.value) saveAndStay();
|
||||
});
|
||||
|
||||
const confirmDelete = ref(false);
|
||||
|
||||
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
|
||||
|
||||
return {
|
||||
t,
|
||||
collectionInfo,
|
||||
fields,
|
||||
confirmDelete,
|
||||
isNew,
|
||||
edits,
|
||||
item,
|
||||
saving,
|
||||
loading,
|
||||
error,
|
||||
save,
|
||||
remove,
|
||||
deleting,
|
||||
saveAsCopy,
|
||||
isBatch,
|
||||
deleteAndQuit,
|
||||
saveAndQuit,
|
||||
hasEdits,
|
||||
confirmLeave,
|
||||
leaveTo,
|
||||
discardAndLeave,
|
||||
};
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
await Promise.all([collectionsStore.hydrate(), fieldsStore.hydrate()]);
|
||||
edits.value = {};
|
||||
router.replace(`/settings/data-model`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
await save();
|
||||
await Promise.all([collectionsStore.hydrate(), fieldsStore.hydrate()]);
|
||||
}
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
await Promise.all([collectionsStore.hydrate(), fieldsStore.hydrate()]);
|
||||
router.push(`/settings/data-model`);
|
||||
}
|
||||
|
||||
function discardAndLeave() {
|
||||
if (!leaveTo.value) return;
|
||||
edits.value = {};
|
||||
confirmLeave.value = false;
|
||||
router.push(leaveTo.value);
|
||||
}
|
||||
},
|
||||
const hasEdits = computed<boolean>(() => {
|
||||
if (!edits.value.meta) return false;
|
||||
return Object.keys(edits.value.meta).length > 0;
|
||||
});
|
||||
|
||||
useShortcut('meta+s', () => {
|
||||
if (hasEdits.value) saveAndStay();
|
||||
});
|
||||
|
||||
const confirmDelete = ref(false);
|
||||
|
||||
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
await Promise.all([collectionsStore.hydrate(), fieldsStore.hydrate()]);
|
||||
edits.value = {};
|
||||
router.replace(`/settings/data-model`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
await save();
|
||||
await Promise.all([collectionsStore.hydrate(), fieldsStore.hydrate()]);
|
||||
}
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
await Promise.all([collectionsStore.hydrate(), fieldsStore.hydrate()]);
|
||||
router.push(`/settings/data-model`);
|
||||
}
|
||||
|
||||
function discardAndLeave() {
|
||||
if (!leaveTo.value) return;
|
||||
edits.value = {};
|
||||
confirmLeave.value = false;
|
||||
router.push(leaveTo.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -126,20 +126,19 @@
|
||||
</v-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { defineComponent, ref, reactive, watch } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import api from '@/api';
|
||||
import { Field, Relation } from '@directus/types';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useDialogRoute } from '@/composables/use-dialog-route';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { useFieldsStore } from '@/stores/fields';
|
||||
import { useRelationsStore } from '@/stores/relations';
|
||||
import { notify } from '@/utils/notify';
|
||||
import { useDialogRoute } from '@/composables/use-dialog-route';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import { DeepPartial } from '@directus/types';
|
||||
import { DeepPartial, Field, Relation } from '@directus/types';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const defaultSystemFields = {
|
||||
status: {
|
||||
@@ -186,329 +185,311 @@ const defaultSystemFields = {
|
||||
},
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
const isOpen = useDialogRoute();
|
||||
const isOpen = useDialogRoute();
|
||||
|
||||
const currentTab = ref(['collection_setup']);
|
||||
const currentTab = ref(['collection_setup']);
|
||||
|
||||
const collectionName = ref(null);
|
||||
const singleton = ref(false);
|
||||
const primaryKeyFieldName = ref('id');
|
||||
const primaryKeyFieldType = ref<'auto_int' | 'auto_big_int' | 'uuid' | 'manual'>('auto_int');
|
||||
const collectionName = ref(null);
|
||||
const singleton = ref(false);
|
||||
const primaryKeyFieldName = ref('id');
|
||||
const primaryKeyFieldType = ref<'auto_int' | 'auto_big_int' | 'uuid' | 'manual'>('auto_int');
|
||||
|
||||
const sortField = ref<string>();
|
||||
const sortField = ref<string>();
|
||||
|
||||
const archiveField = ref<string>();
|
||||
const archiveValue = ref<string>();
|
||||
const unarchiveValue = ref<string>();
|
||||
const archiveField = ref<string>();
|
||||
const archiveValue = ref<string>();
|
||||
const unarchiveValue = ref<string>();
|
||||
|
||||
const systemFields = reactive(cloneDeep(defaultSystemFields));
|
||||
const systemFields = reactive(cloneDeep(defaultSystemFields));
|
||||
|
||||
const saving = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
watch(() => singleton.value, setOptionsForSingleton);
|
||||
watch(() => singleton.value, setOptionsForSingleton);
|
||||
|
||||
function setOptionsForSingleton() {
|
||||
systemFields.sort = { ...defaultSystemFields.sort };
|
||||
systemFields.sort.inputDisabled = singleton.value;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
await api.post(`/collections`, {
|
||||
collection: collectionName.value,
|
||||
fields: [getPrimaryKeyField(), ...getSystemFields()],
|
||||
schema: {},
|
||||
meta: {
|
||||
sort_field: sortField.value,
|
||||
archive_field: archiveField.value,
|
||||
archive_value: archiveValue.value,
|
||||
unarchive_value: unarchiveValue.value,
|
||||
singleton: singleton.value,
|
||||
},
|
||||
});
|
||||
|
||||
const storeHydrations: Promise<void>[] = [];
|
||||
|
||||
const relations = getSystemRelations();
|
||||
|
||||
if (relations.length > 0) {
|
||||
const requests = relations.map((relation) => api.post('/relations', relation));
|
||||
await Promise.all(requests);
|
||||
storeHydrations.push(relationsStore.hydrate());
|
||||
}
|
||||
|
||||
storeHydrations.push(collectionsStore.hydrate(), fieldsStore.hydrate());
|
||||
await Promise.all(storeHydrations);
|
||||
|
||||
notify({
|
||||
title: t('collection_created'),
|
||||
});
|
||||
|
||||
router.replace(`/settings/data-model/${collectionName.value}`);
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimaryKeyField() {
|
||||
if (primaryKeyFieldType.value === 'uuid') {
|
||||
return {
|
||||
t,
|
||||
router,
|
||||
isOpen,
|
||||
currentTab,
|
||||
save,
|
||||
systemFields,
|
||||
primaryKeyFieldName,
|
||||
primaryKeyFieldType,
|
||||
collectionName,
|
||||
saving,
|
||||
singleton,
|
||||
field: primaryKeyFieldName.value,
|
||||
type: 'uuid',
|
||||
meta: {
|
||||
hidden: true,
|
||||
readonly: true,
|
||||
interface: 'input',
|
||||
special: ['uuid'],
|
||||
},
|
||||
schema: {
|
||||
is_primary_key: true,
|
||||
length: 36,
|
||||
has_auto_increment: false,
|
||||
},
|
||||
};
|
||||
} else if (primaryKeyFieldType.value === 'manual') {
|
||||
return {
|
||||
field: primaryKeyFieldName.value,
|
||||
type: 'string',
|
||||
meta: {
|
||||
interface: 'input',
|
||||
readonly: false,
|
||||
hidden: false,
|
||||
},
|
||||
schema: {
|
||||
is_primary_key: true,
|
||||
length: 255,
|
||||
has_auto_increment: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
field: primaryKeyFieldName.value,
|
||||
type: primaryKeyFieldType.value === 'auto_big_int' ? 'bigInteger' : 'integer',
|
||||
meta: {
|
||||
hidden: true,
|
||||
interface: 'input',
|
||||
readonly: true,
|
||||
},
|
||||
schema: {
|
||||
is_primary_key: true,
|
||||
has_auto_increment: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function setOptionsForSingleton() {
|
||||
systemFields.sort = { ...defaultSystemFields.sort };
|
||||
systemFields.sort.inputDisabled = singleton.value;
|
||||
}
|
||||
function getSystemFields() {
|
||||
const fields: DeepPartial<Field>[] = [];
|
||||
|
||||
async function save() {
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
await api.post(`/collections`, {
|
||||
collection: collectionName.value,
|
||||
fields: [getPrimaryKeyField(), ...getSystemFields()],
|
||||
schema: {},
|
||||
meta: {
|
||||
sort_field: sortField.value,
|
||||
archive_field: archiveField.value,
|
||||
archive_value: archiveValue.value,
|
||||
unarchive_value: unarchiveValue.value,
|
||||
singleton: singleton.value,
|
||||
},
|
||||
});
|
||||
|
||||
const storeHydrations: Promise<void>[] = [];
|
||||
|
||||
const relations = getSystemRelations();
|
||||
|
||||
if (relations.length > 0) {
|
||||
const requests = relations.map((relation) => api.post('/relations', relation));
|
||||
await Promise.all(requests);
|
||||
storeHydrations.push(relationsStore.hydrate());
|
||||
}
|
||||
|
||||
storeHydrations.push(collectionsStore.hydrate(), fieldsStore.hydrate());
|
||||
await Promise.all(storeHydrations);
|
||||
|
||||
notify({
|
||||
title: t('collection_created'),
|
||||
});
|
||||
|
||||
router.replace(`/settings/data-model/${collectionName.value}`);
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimaryKeyField() {
|
||||
if (primaryKeyFieldType.value === 'uuid') {
|
||||
return {
|
||||
field: primaryKeyFieldName.value,
|
||||
type: 'uuid',
|
||||
meta: {
|
||||
hidden: true,
|
||||
readonly: true,
|
||||
interface: 'input',
|
||||
special: ['uuid'],
|
||||
},
|
||||
schema: {
|
||||
is_primary_key: true,
|
||||
length: 36,
|
||||
has_auto_increment: false,
|
||||
},
|
||||
};
|
||||
} else if (primaryKeyFieldType.value === 'manual') {
|
||||
return {
|
||||
field: primaryKeyFieldName.value,
|
||||
type: 'string',
|
||||
meta: {
|
||||
interface: 'input',
|
||||
readonly: false,
|
||||
hidden: false,
|
||||
},
|
||||
schema: {
|
||||
is_primary_key: true,
|
||||
length: 255,
|
||||
has_auto_increment: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
field: primaryKeyFieldName.value,
|
||||
type: primaryKeyFieldType.value === 'auto_big_int' ? 'bigInteger' : 'integer',
|
||||
meta: {
|
||||
hidden: true,
|
||||
interface: 'input',
|
||||
readonly: true,
|
||||
},
|
||||
schema: {
|
||||
is_primary_key: true,
|
||||
has_auto_increment: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemFields() {
|
||||
const fields: DeepPartial<Field>[] = [];
|
||||
|
||||
// Status
|
||||
if (systemFields.status.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.status.name,
|
||||
type: 'string',
|
||||
meta: {
|
||||
width: 'full',
|
||||
options: {
|
||||
choices: [
|
||||
{
|
||||
text: '$t:published',
|
||||
value: 'published',
|
||||
},
|
||||
{
|
||||
text: '$t:draft',
|
||||
value: 'draft',
|
||||
},
|
||||
{
|
||||
text: '$t:archived',
|
||||
value: 'archived',
|
||||
},
|
||||
],
|
||||
// Status
|
||||
if (systemFields.status.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.status.name,
|
||||
type: 'string',
|
||||
meta: {
|
||||
width: 'full',
|
||||
options: {
|
||||
choices: [
|
||||
{
|
||||
text: '$t:published',
|
||||
value: 'published',
|
||||
},
|
||||
interface: 'select-dropdown',
|
||||
display: 'labels',
|
||||
display_options: {
|
||||
showAsDot: true,
|
||||
choices: [
|
||||
{
|
||||
text: '$t:published',
|
||||
value: 'published',
|
||||
foreground: '#FFFFFF',
|
||||
background: 'var(--primary)',
|
||||
},
|
||||
{
|
||||
text: '$t:draft',
|
||||
value: 'draft',
|
||||
foreground: '#18222F',
|
||||
background: '#D3DAE4',
|
||||
},
|
||||
{
|
||||
text: '$t:archived',
|
||||
value: 'archived',
|
||||
foreground: '#FFFFFF',
|
||||
background: 'var(--warning)',
|
||||
},
|
||||
],
|
||||
{
|
||||
text: '$t:draft',
|
||||
value: 'draft',
|
||||
},
|
||||
},
|
||||
schema: {
|
||||
default_value: 'draft',
|
||||
is_nullable: false,
|
||||
},
|
||||
});
|
||||
|
||||
archiveField.value = systemFields.status.name;
|
||||
archiveValue.value = 'archived';
|
||||
unarchiveValue.value = 'draft';
|
||||
}
|
||||
|
||||
// Sort
|
||||
if (systemFields.sort.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.sort.name,
|
||||
type: 'integer',
|
||||
meta: {
|
||||
interface: 'input',
|
||||
hidden: true,
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
|
||||
sortField.value = systemFields.sort.name;
|
||||
}
|
||||
|
||||
if (systemFields.userCreated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.userCreated.name,
|
||||
type: 'uuid',
|
||||
meta: {
|
||||
special: ['user-created'],
|
||||
interface: 'select-dropdown-m2o',
|
||||
options: {
|
||||
template: '{{avatar.$thumbnail}} {{first_name}} {{last_name}}',
|
||||
{
|
||||
text: '$t:archived',
|
||||
value: 'archived',
|
||||
},
|
||||
display: 'user',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields.dateCreated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.dateCreated.name,
|
||||
type: 'timestamp',
|
||||
meta: {
|
||||
special: ['date-created'],
|
||||
interface: 'datetime',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
display: 'datetime',
|
||||
display_options: {
|
||||
relative: true,
|
||||
],
|
||||
},
|
||||
interface: 'select-dropdown',
|
||||
display: 'labels',
|
||||
display_options: {
|
||||
showAsDot: true,
|
||||
choices: [
|
||||
{
|
||||
text: '$t:published',
|
||||
value: 'published',
|
||||
foreground: '#FFFFFF',
|
||||
background: 'var(--primary)',
|
||||
},
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields.userUpdated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.userUpdated.name,
|
||||
type: 'uuid',
|
||||
meta: {
|
||||
special: ['user-updated'],
|
||||
interface: 'select-dropdown-m2o',
|
||||
options: {
|
||||
template: '{{avatar.$thumbnail}} {{first_name}} {{last_name}}',
|
||||
{
|
||||
text: '$t:draft',
|
||||
value: 'draft',
|
||||
foreground: '#18222F',
|
||||
background: '#D3DAE4',
|
||||
},
|
||||
display: 'user',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields.dateUpdated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.dateUpdated.name,
|
||||
type: 'timestamp',
|
||||
meta: {
|
||||
special: ['date-updated'],
|
||||
interface: 'datetime',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
display: 'datetime',
|
||||
display_options: {
|
||||
relative: true,
|
||||
{
|
||||
text: '$t:archived',
|
||||
value: 'archived',
|
||||
foreground: '#FFFFFF',
|
||||
background: 'var(--warning)',
|
||||
},
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
schema: {
|
||||
default_value: 'draft',
|
||||
is_nullable: false,
|
||||
},
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
archiveField.value = systemFields.status.name;
|
||||
archiveValue.value = 'archived';
|
||||
unarchiveValue.value = 'draft';
|
||||
}
|
||||
|
||||
function getSystemRelations() {
|
||||
const relations: Partial<Relation>[] = [];
|
||||
// Sort
|
||||
if (systemFields.sort.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.sort.name,
|
||||
type: 'integer',
|
||||
meta: {
|
||||
interface: 'input',
|
||||
hidden: true,
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
|
||||
if (systemFields.userCreated.enabled === true) {
|
||||
relations.push({
|
||||
collection: collectionName.value!,
|
||||
field: systemFields.userCreated.name,
|
||||
related_collection: 'directus_users',
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
sortField.value = systemFields.sort.name;
|
||||
}
|
||||
|
||||
if (systemFields.userUpdated.enabled === true) {
|
||||
relations.push({
|
||||
collection: collectionName.value!,
|
||||
field: systemFields.userUpdated.name,
|
||||
related_collection: 'directus_users',
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
if (systemFields.userCreated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.userCreated.name,
|
||||
type: 'uuid',
|
||||
meta: {
|
||||
special: ['user-created'],
|
||||
interface: 'select-dropdown-m2o',
|
||||
options: {
|
||||
template: '{{avatar.$thumbnail}} {{first_name}} {{last_name}}',
|
||||
},
|
||||
display: 'user',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
return relations;
|
||||
}
|
||||
},
|
||||
});
|
||||
if (systemFields.dateCreated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.dateCreated.name,
|
||||
type: 'timestamp',
|
||||
meta: {
|
||||
special: ['date-created'],
|
||||
interface: 'datetime',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
display: 'datetime',
|
||||
display_options: {
|
||||
relative: true,
|
||||
},
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields.userUpdated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.userUpdated.name,
|
||||
type: 'uuid',
|
||||
meta: {
|
||||
special: ['user-updated'],
|
||||
interface: 'select-dropdown-m2o',
|
||||
options: {
|
||||
template: '{{avatar.$thumbnail}} {{first_name}} {{last_name}}',
|
||||
},
|
||||
display: 'user',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields.dateUpdated.enabled === true) {
|
||||
fields.push({
|
||||
field: systemFields.dateUpdated.name,
|
||||
type: 'timestamp',
|
||||
meta: {
|
||||
special: ['date-updated'],
|
||||
interface: 'datetime',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'half',
|
||||
display: 'datetime',
|
||||
display_options: {
|
||||
relative: true,
|
||||
},
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
function getSystemRelations() {
|
||||
const relations: Partial<Relation>[] = [];
|
||||
|
||||
if (systemFields.userCreated.enabled === true) {
|
||||
relations.push({
|
||||
collection: collectionName.value!,
|
||||
field: systemFields.userCreated.name,
|
||||
related_collection: 'directus_users',
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields.userUpdated.enabled === true) {
|
||||
relations.push({
|
||||
collection: collectionName.value!,
|
||||
field: systemFields.userUpdated.name,
|
||||
related_collection: 'directus_users',
|
||||
schema: {},
|
||||
});
|
||||
}
|
||||
|
||||
return relations;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user