mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Use technical collection/field names in settings (#19444)
* temp commit * change to non translated names * Add changeset * Fix linter warning * Update related collection selection as well Unfortunately, modifiying the font via css var doesn't work so we need to pass it down via prop. See https://github.com/vuejs/core/issues/7312 --------- Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch> Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
5
.changeset/tame-dolphins-begin.md
Normal file
5
.changeset/tame-dolphins-begin.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@directus/app": patch
|
||||
---
|
||||
|
||||
Switched to technical collection/field names in settings for clarity
|
||||
@@ -3,11 +3,15 @@
|
||||
v-if="field.children || supportedFunctions.length > 0"
|
||||
:clickable="!field.disabled && (relationalFieldSelectable || !field.relatedCollection)"
|
||||
:value="field.path"
|
||||
:class="{ 'raw-field-names': rawFieldNames }"
|
||||
@click="$emit('add', [field.key])"
|
||||
>
|
||||
<template #activator>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="field.name || formatTitle(field.field)" :highlight="search" />
|
||||
<v-text-overflow
|
||||
:text="rawFieldNames ? field.field : field.name || formatTitle(field.field)"
|
||||
:highlight="search"
|
||||
/>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
|
||||
@@ -24,7 +28,7 @@
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow
|
||||
:text="`${t(`functions.${fn}`)} (${field.name || formatTitle(field.field)})`"
|
||||
:text="`${t(`functions.${fn}`)} (${rawFieldNames ? field.field : field.name || formatTitle(field.field)})`"
|
||||
:highlight="search"
|
||||
/>
|
||||
</v-list-item-content>
|
||||
@@ -50,13 +54,23 @@
|
||||
:relational-field-selectable="relationalFieldSelectable"
|
||||
:parent="field.field"
|
||||
:allow-select-all="allowSelectAll"
|
||||
:raw-field-names="rawFieldNames"
|
||||
@add="$emit('add', $event)"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-item v-else :disabled="field.disabled" clickable @click="$emit('add', [field.key])">
|
||||
<v-list-item
|
||||
v-else
|
||||
:disabled="field.disabled"
|
||||
:class="{ 'raw-field-names': rawFieldNames }"
|
||||
clickable
|
||||
@click="$emit('add', [field.key])"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="field.name || formatTitle(field.field)" :highlight="search" />
|
||||
<v-text-overflow
|
||||
:text="rawFieldNames ? field.field : field.name || formatTitle(field.field)"
|
||||
:highlight="search"
|
||||
/>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@@ -86,6 +100,7 @@ interface Props {
|
||||
relationalFieldSelectable?: boolean;
|
||||
allowSelectAll?: boolean;
|
||||
parent?: string | null;
|
||||
rawFieldNames?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -94,6 +109,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
relationalFieldSelectable: true,
|
||||
allowSelectAll: false,
|
||||
parent: null,
|
||||
rawFieldNames: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['add']);
|
||||
@@ -129,4 +145,8 @@ const addAll = () => {
|
||||
--v-icon-color: var(--primary);
|
||||
--v-list-item-color: var(--primary);
|
||||
}
|
||||
|
||||
.raw-field-names {
|
||||
--v-list-item-content-font-family: var(--family-monospace);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
:include-functions="includeFunctions"
|
||||
:relational-field-selectable="relationalFieldSelectable"
|
||||
:allow-select-all="allowSelectAll"
|
||||
:raw-field-names="rawFieldNames"
|
||||
@add="$emit('add', $event)"
|
||||
/>
|
||||
</v-list>
|
||||
@@ -49,6 +50,7 @@ interface Props {
|
||||
includeRelations?: boolean;
|
||||
relationalFieldSelectable?: boolean;
|
||||
allowSelectAll?: boolean;
|
||||
rawFieldNames?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -58,6 +60,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
includeRelations: true,
|
||||
relationalFieldSelectable: true,
|
||||
allowSelectAll: false,
|
||||
rawFieldNames: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['add']);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<select-list-item-group
|
||||
v-if="childItem.children"
|
||||
:item="childItem"
|
||||
:item-label-font-family="itemLabelFontFamily"
|
||||
:model-value="modelValue"
|
||||
:multiple="multiple"
|
||||
:allow-other="allowOther"
|
||||
@@ -37,6 +38,7 @@
|
||||
v-else
|
||||
:model-value="modelValue"
|
||||
:item="childItem"
|
||||
:item-label-font-family="itemLabelFontFamily"
|
||||
:multiple="multiple"
|
||||
:allow-other="allowOther"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
@@ -52,6 +54,7 @@ import SelectListItem from './select-list-item.vue';
|
||||
|
||||
interface Props {
|
||||
item: Option;
|
||||
itemLabelFontFamily?: string;
|
||||
modelValue?: string | number | (string | number)[] | null;
|
||||
multiple?: boolean;
|
||||
allowOther?: boolean;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<span v-if="multiple === false || item.selectable === false" class="item-text">{{ item.text }}</span>
|
||||
<v-checkbox
|
||||
v-else
|
||||
class="checkbox"
|
||||
:model-value="modelValue || []"
|
||||
:label="item.text"
|
||||
:value="item.value"
|
||||
@@ -33,12 +34,14 @@ import { Option } from './types';
|
||||
|
||||
interface Props {
|
||||
item: Option;
|
||||
itemLabelFontFamily: string;
|
||||
modelValue?: string | number | (string | number)[] | null;
|
||||
multiple?: boolean;
|
||||
allowOther?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
itemLabelFontFamily: 'var(--v-select-font-family)',
|
||||
modelValue: null,
|
||||
multiple: true,
|
||||
allowOther: false,
|
||||
@@ -58,3 +61,9 @@ const isActive = computed(() => {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.checkbox :deep(.type-text) {
|
||||
font-family: v-bind('$props.itemLabelFontFamily');
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<select-list-item-group
|
||||
v-if="item.children"
|
||||
:item="item"
|
||||
:item-label-font-family="itemLabelFontFamily"
|
||||
:model-value="modelValue"
|
||||
:multiple="multiple"
|
||||
:allow-other="allowOther"
|
||||
@@ -78,6 +79,7 @@
|
||||
v-else
|
||||
:model-value="modelValue"
|
||||
:item="item"
|
||||
:item-label-font-family="itemLabelFontFamily"
|
||||
:multiple="multiple"
|
||||
:allow-other="allowOther"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
@@ -155,6 +157,8 @@ interface Props {
|
||||
itemValue?: string;
|
||||
/** Which key in items is used to show an icon */
|
||||
itemIcon?: string | null;
|
||||
/** Which font family to use for checkbox item label */
|
||||
itemLabelFontFamily?: string;
|
||||
/** Which key in items is used to model the disabled state */
|
||||
itemDisabled?: string;
|
||||
/** Which key in items is used to model the selectable state */
|
||||
@@ -368,10 +372,6 @@ function useDisplayValue() {
|
||||
--v-list-min-width: 0;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
font-family: var(--v-select-font-family);
|
||||
}
|
||||
|
||||
.v-input {
|
||||
--v-input-font-family: var(--v-select-font-family);
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
<template #item="{ element, index }">
|
||||
<li class="row">
|
||||
<div v-if="filterInfo[index].isField" block class="node field">
|
||||
<div class="header" :class="{ inline }">
|
||||
<div class="header" :class="{ inline, 'raw-field-names': rawFieldNames }">
|
||||
<v-icon name="drag_indicator" class="drag-handle" small></v-icon>
|
||||
<span v-if="field || !isExistingField(element)" class="plain-name">{{ getFieldPreview(element) }}</span>
|
||||
<span v-if="field || !isExistingField(element)" class="plain-name">
|
||||
{{ getFieldPreview(element) }}
|
||||
</span>
|
||||
<v-menu v-else placement="bottom-start" show-arrow>
|
||||
<template #activator="{ toggle }">
|
||||
<button class="name" @click="toggle">
|
||||
@@ -31,6 +33,7 @@
|
||||
:include-relations="includeRelations"
|
||||
:relational-field-selectable="relationalFieldSelectable"
|
||||
:allow-select-all="false"
|
||||
:raw-field-names="rawFieldNames"
|
||||
@add="updateField(index, $event[0])"
|
||||
/>
|
||||
</v-menu>
|
||||
@@ -89,6 +92,7 @@
|
||||
:collection="collection"
|
||||
:depth="depth + 1"
|
||||
:inline="inline"
|
||||
:raw-field-names="rawFieldNames"
|
||||
@change="$emit('change')"
|
||||
@remove-node="$emit('remove-node', [`${index}.${filterInfo[index].name}`, ...$event])"
|
||||
@update:filter="replaceNode(index, { [filterInfo[index].name]: $event })"
|
||||
@@ -146,6 +150,7 @@ interface Props {
|
||||
includeValidation?: boolean;
|
||||
includeRelations?: boolean;
|
||||
relationalFieldSelectable?: boolean;
|
||||
rawFieldNames?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -155,6 +160,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
includeValidation: false,
|
||||
includeRelations: true,
|
||||
relationalFieldSelectable: true,
|
||||
rawFieldNames: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['remove-node', 'update:filter', 'change']);
|
||||
@@ -211,7 +217,7 @@ function getFieldPreview(node: Record<string, any>) {
|
||||
const pathPrefix = fieldParts.slice(0, index);
|
||||
const field = fieldsStore.getField(props.collection, [...pathPrefix, key].join('.'));
|
||||
|
||||
const name = field?.name ?? key;
|
||||
const name = (props.rawFieldNames ? field?.field : field?.name) ?? key;
|
||||
|
||||
if (hasFunction) {
|
||||
return t(`functions.${functionName}`) + ` (${name})`;
|
||||
@@ -428,6 +434,13 @@ function isExistingField(node: Record<string, any>): boolean {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.raw-field-names {
|
||||
.plain-name,
|
||||
.name {
|
||||
font-family: var(--family-monospace);
|
||||
}
|
||||
}
|
||||
|
||||
.name,
|
||||
.comparator {
|
||||
position: relative;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
:include-validation="includeValidation"
|
||||
:include-relations="includeRelations"
|
||||
:relational-field-selectable="relationalFieldSelectable"
|
||||
:raw-field-names="rawFieldNames"
|
||||
@remove-node="removeNode($event)"
|
||||
@change="emitValue"
|
||||
/>
|
||||
@@ -47,6 +48,7 @@
|
||||
:include-relations="includeRelations"
|
||||
:relational-field-selectable="relationalFieldSelectable"
|
||||
:allow-select-all="false"
|
||||
:raw-field-names="rawFieldNames"
|
||||
@add="addNode($event[0])"
|
||||
>
|
||||
<template #prepend>
|
||||
@@ -114,6 +116,7 @@ interface Props {
|
||||
includeValidation?: boolean;
|
||||
includeRelations?: boolean;
|
||||
relationalFieldSelectable?: boolean;
|
||||
rawFieldNames?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -127,6 +130,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
includeValidation: false,
|
||||
includeRelations: true,
|
||||
relationalFieldSelectable: true,
|
||||
rawFieldNames: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['input']);
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
:placeholder="t('collection') + '...'"
|
||||
:items="availableCollections"
|
||||
item-value="collection"
|
||||
item-text="name"
|
||||
item-text="collection"
|
||||
item-label-font-family="var(--family-monospace)"
|
||||
item-disabled="meta.singleton"
|
||||
multiple
|
||||
:is-menu-same-width="false"
|
||||
@@ -188,21 +189,17 @@ 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']
|
||||
);
|
||||
return [
|
||||
...orderBy(collectionsStore.databaseCollections, ['collection'], ['asc']),
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
collection: t('system'),
|
||||
selectable: false,
|
||||
children: orderBy(collectionsStore.crudSafeSystemCollections, ['collection'], ['asc']),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const unsortableJunctionFields = computed(() => {
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
:placeholder="t('collection') + '...'"
|
||||
:items="availableCollections"
|
||||
item-value="collection"
|
||||
item-text="name"
|
||||
item-text="collection"
|
||||
item-label-font-family="var(--family-monospace)"
|
||||
item-disabled="meta.singleton"
|
||||
multiple
|
||||
:multiple-preview-threshold="0"
|
||||
@@ -90,21 +91,17 @@ const o2mField = syncFieldDetailStoreProperty('relations.o2m.field');
|
||||
const oneAllowedCollections = syncFieldDetailStoreProperty('relations.m2o.meta.one_allowed_collections', []);
|
||||
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
[
|
||||
...collectionsStore.databaseCollections,
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
name: t('system'),
|
||||
selectable: false,
|
||||
children: collectionsStore.crudSafeSystemCollections,
|
||||
},
|
||||
],
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
return [
|
||||
...orderBy(collectionsStore.databaseCollections, ['collection'], ['asc']),
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
collection: t('system'),
|
||||
selectable: false,
|
||||
children: orderBy(collectionsStore.crudSafeSystemCollections, ['collection'], ['asc']),
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -97,12 +97,12 @@ const simple = ref(props.type === null);
|
||||
|
||||
const title = computed(() => {
|
||||
const existingField = fieldsStore.getField(props.collection, props.field);
|
||||
const fieldName = existingField?.name || formatTitle(fieldDetail.field.name || '');
|
||||
const fieldName = formatTitle(existingField?.field || fieldDetail.field.field || '');
|
||||
|
||||
if (props.field === '+' && fieldName === '') {
|
||||
return t('creating_new_field', { collection: collectionInfo.value?.name });
|
||||
return t('creating_new_field', { collection: collectionInfo.value?.collection });
|
||||
} else {
|
||||
return t('field_in_collection', { field: fieldName, collection: collectionInfo.value?.name });
|
||||
return t('field_in_collection', { field: fieldName, collection: collectionInfo.value?.collection });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<private-view :title="collectionInfo && collectionInfo.name">
|
||||
<private-view :title="collectionInfo && formatTitle(collectionInfo.collection)">
|
||||
<template #headline>
|
||||
<v-breadcrumb :items="[{ name: t('settings_data_model'), to: '/settings/data-model' }]" />
|
||||
</template>
|
||||
@@ -110,6 +110,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import SettingsNavigation from '../../../components/navigation.vue';
|
||||
import FieldsManagement from './components/fields-management.vue';
|
||||
import formatTitle from '@directus/format-title';
|
||||
|
||||
const props = defineProps<{
|
||||
collection: string;
|
||||
|
||||
@@ -453,7 +453,7 @@ function useForm() {
|
||||
options: {
|
||||
choices: collectionsStore.collections
|
||||
.map((collection) => ({
|
||||
text: collection.name,
|
||||
text: collection.collection,
|
||||
value: collection.collection,
|
||||
}))
|
||||
.filter((option) => {
|
||||
@@ -543,6 +543,7 @@ function useForm() {
|
||||
width: 'half',
|
||||
options: {
|
||||
collectionField: 'collection',
|
||||
rawFieldNames: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="permissions-overview-row">
|
||||
<span class="name">
|
||||
<span v-tooltip.left="collection.collection">{{ collection.name }}</span>
|
||||
<span v-tooltip.left="collection.name">{{ collection.collection }}</span>
|
||||
<span class="actions">
|
||||
<span class="all" @click="setFullAccessAll">{{ t('all') }}</span>
|
||||
<span class="divider">/</span>
|
||||
@@ -53,11 +53,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Collection, Permission } from '@directus/types';
|
||||
import { Permission } from '@directus/types';
|
||||
import { toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useUpdatePermissions from '../composables/use-update-permissions';
|
||||
import PermissionsOverviewToggle from './permissions-overview-toggle.vue';
|
||||
import { Collection } from '@/types/collections';
|
||||
|
||||
const props = defineProps<{
|
||||
collection: Collection;
|
||||
@@ -89,6 +90,7 @@ function isLoading(action: string) {
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
font-family: var(--family-monospace);
|
||||
|
||||
.actions {
|
||||
margin-left: 8px;
|
||||
|
||||
@@ -51,7 +51,7 @@ const fieldsInCollection = computed(() => {
|
||||
|
||||
return fields.map((field: Field) => {
|
||||
return {
|
||||
text: field.name,
|
||||
text: field.field,
|
||||
value: field.field,
|
||||
};
|
||||
});
|
||||
@@ -92,6 +92,10 @@ const fields = computed({
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.checkboxes :deep(.v-checkbox .type-text) {
|
||||
font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.app-minimal {
|
||||
.v-divider {
|
||||
margin: 24px 0;
|
||||
|
||||
@@ -46,6 +46,7 @@ const fields = computed(() => [
|
||||
interface: 'system-filter',
|
||||
options: {
|
||||
collectionName: permissionSync.value.collection,
|
||||
rawFieldNames: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -41,6 +41,7 @@ const fields = computed(() => [
|
||||
collectionName: permissionSync.value.collection,
|
||||
includeValidation: true,
|
||||
includeRelations: false,
|
||||
rawFieldNames: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
<script setup lang="ts">
|
||||
import api from '@/api';
|
||||
import { useDialogRoute } from '@/composables/use-dialog-route';
|
||||
import { useCollectionsStore } from '@/stores/collections';
|
||||
import { isPermissionEmpty } from '@/utils/is-permission-empty';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import { Permission, Role } from '@directus/types';
|
||||
@@ -73,28 +72,20 @@ const { t } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const isOpen = useDialogRoute();
|
||||
|
||||
const permission = ref<Permission>();
|
||||
const role = ref<Role>();
|
||||
const loading = ref(false);
|
||||
|
||||
const collectionName = computed(() => {
|
||||
if (!permission.value) return null;
|
||||
return collectionsStore.collections.find((collection) => collection.collection === permission.value!.collection)
|
||||
?.name;
|
||||
});
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
if (loading.value || !permission.value) return t('loading');
|
||||
|
||||
if (props.roleKey) {
|
||||
return role.value!.name + ' -> ' + collectionName.value + ' -> ' + t(permission.value.action);
|
||||
return role.value!.name + ' -> ' + permission.value!.collection + ' -> ' + t(permission.value.action);
|
||||
}
|
||||
|
||||
return t('public_label') + ' -> ' + collectionName.value + ' -> ' + t(permission.value.action);
|
||||
return t('public_label') + ' -> ' + permission.value!.collection + ' -> ' + t(permission.value.action);
|
||||
});
|
||||
|
||||
watch(() => props.permissionKey, load, { immediate: true });
|
||||
|
||||
Reference in New Issue
Block a user