mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Tweak relational interfaces
Squashed commit of the following:
commit ade7ce72e7dac9908504eacf420875baaae1cc47
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 13:16:03 2021 -0400
Add no-items notice
commit e47dd5ac1f28300a33478a2be3c50496859b09fc
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 13:13:58 2021 -0400
Remove files interface
commit 2925fb9c86719c48006f7b2619df7fd26bf7b523
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 13:10:44 2021 -0400
Fix sort field in m2m
commit 009e2b1fd99f7a31f20fba04cd9980eaa3566ac8
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 13:06:45 2021 -0400
Add dense at item count
commit 83b088f4da3ea4a1d7e030f34a07aa1cb2235b43
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 12:05:40 2021 -0400
Tweak rendering of thumbnails inside relational interfaces
commit 06770a0f16e344ab62c0228b87824a6c00ad39bc
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 11:36:07 2021 -0400
Rename $file->$thumbnail, render properly in render-template
commit 954fd725629ce055459a7925be4aaddf3fb723c2
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 11:35:53 2021 -0400
Fix injection on v-field-select
commit 83073dea2fc26af61a5155adddd5d4e3afa5cb14
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 11:35:39 2021 -0400
Adjust for virtual $thumbnail field on files
commit ee57b8316479204c0a5c931c86807afde55423a1
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 10:49:35 2021 -0400
Don't hardcode file/user relations
commit 31ed92c5a785f20b7dc58bb62f35f6e31c95cfc6
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 10:49:22 2021 -0400
Allow injecting temporary fields in field template
commit 9d98d4fe4def7bdba12d1613bd08bdb9bd9e1431
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 10:36:53 2021 -0400
Render collection level template in placeholder
commit 0e0dda1e9f5a930ce3c73c2f8003d98853d58bc0
Merge: 65fa8084f 1e3b64bf9
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 10:35:21 2021 -0400
Merge branch 'main' into relational-tweaks
commit 65fa8084f84aa1a90686fe6407a6d54ca47d1371
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 10:29:03 2021 -0400
Make input container relative
commit 0674a0a00faa5df2208b466114721ba5d5116bf7
Author: rijkvanzanten <rijkvanzanten@me.com>
Date: Thu Apr 22 10:28:44 2021 -0400
Add placeholder option to v-field-template
This commit is contained in:
@@ -110,14 +110,15 @@ router.post(
|
||||
}
|
||||
|
||||
try {
|
||||
if (Array.isArray(keys)) {
|
||||
if (Array.isArray(keys) && keys.length > 1) {
|
||||
const records = await service.readMany(keys, req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = {
|
||||
data: records,
|
||||
};
|
||||
} else {
|
||||
const record = await service.readOne(keys, req.sanitizedQuery);
|
||||
const key = Array.isArray(keys) ? keys[0] : keys;
|
||||
const record = await service.readOne(key, req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = {
|
||||
data: record,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex) {
|
||||
await knex('directus_fields').update({ interface: 'many-to-many' }).where({ interface: 'files' });
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {}
|
||||
@@ -81,13 +81,10 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const menuActive = ref(false);
|
||||
const { collection } = toRefs(props);
|
||||
const { collection, inject } = toRefs(props);
|
||||
|
||||
const { info } = useCollection(collection);
|
||||
const { tree } = useFieldTree(collection, {
|
||||
fields: props.inject?.fields.filter((field) => field.collection === props.collection) || [],
|
||||
relations: props.inject?.relations || [],
|
||||
});
|
||||
const { tree } = useFieldTree(collection, false, inject);
|
||||
|
||||
const _value = computed({
|
||||
get() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<span ref="contentEl" class="content" contenteditable @keydown="onKeyDown" @input="onInput" @click="onClick">
|
||||
<span class="text" />
|
||||
</span>
|
||||
<span class="placeholder" v-if="placeholder && !value">{{ placeholder }}</span>
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
@@ -21,12 +22,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, ref, watch, onMounted, onUnmounted } from '@vue/composition-api';
|
||||
import { defineComponent, toRefs, ref, watch, onMounted, onUnmounted, PropType } from '@vue/composition-api';
|
||||
import FieldListItem from './field-list-item.vue';
|
||||
import { useFieldsStore } from '@/stores';
|
||||
import { Field } from '@/types/';
|
||||
import useFieldTree from '@/composables/use-field-tree';
|
||||
import { FieldTree } from './types';
|
||||
import { Field, Relation } from '@/types';
|
||||
|
||||
export default defineComponent({
|
||||
components: { FieldListItem },
|
||||
@@ -51,15 +51,22 @@ export default defineComponent({
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
inject: {
|
||||
type: Object as PropType<{ fields: Field[]; relations: Relation[] }>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const fieldsStore = useFieldsStore();
|
||||
const contentEl = ref<HTMLElement | null>(null);
|
||||
|
||||
const menuActive = ref(false);
|
||||
|
||||
const { collection } = toRefs(props);
|
||||
const { tree } = useFieldTree(collection);
|
||||
const { collection, inject } = toRefs(props);
|
||||
const { tree } = useFieldTree(collection, true, inject);
|
||||
|
||||
watch(() => props.value, setContent, { immediate: true });
|
||||
|
||||
@@ -323,5 +330,15 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 14px;
|
||||
color: var(--foreground-subdued);
|
||||
transform: translateY(-50%);
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -283,6 +283,7 @@ body {
|
||||
}
|
||||
|
||||
.input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
|
||||
@@ -190,9 +190,10 @@ body {
|
||||
}
|
||||
|
||||
&.block {
|
||||
--v-list-item-min-height: 44px;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: var(--input-height);
|
||||
padding: 8px;
|
||||
background-color: var(--background-subdued);
|
||||
border: 2px solid var(--border-subdued);
|
||||
border-radius: var(--border-radius);
|
||||
@@ -228,7 +229,8 @@ body {
|
||||
}
|
||||
|
||||
&.dense {
|
||||
--v-list-item-min-height: 34px;
|
||||
height: 34px;
|
||||
padding: 4px 8px;
|
||||
|
||||
& + & {
|
||||
margin-top: 4px;
|
||||
|
||||
@@ -91,7 +91,6 @@ import uploadFiles from '@/utils/upload-files';
|
||||
import uploadFile from '@/utils/upload-file';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import api from '@/api';
|
||||
import useItem from '@/composables/use-item';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
@@ -4,9 +4,13 @@ import { Field, Relation } from '@/types';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getRelationType } from '@/utils/get-relation-type';
|
||||
|
||||
type FieldOption = { name: string; field: string; key: string; children?: FieldOption[] };
|
||||
|
||||
export default function useFieldTree(
|
||||
collection: Ref<string>,
|
||||
inject?: { fields: Field[]; relations: Relation[] },
|
||||
/** Only allow m2o relations to be nested */
|
||||
strict: boolean = false,
|
||||
inject?: Ref<{ fields: Field[]; relations: Relation[] } | null>,
|
||||
filter: (field: Field) => boolean = () => true
|
||||
) {
|
||||
const fieldsStore = useFieldsStore();
|
||||
@@ -17,7 +21,10 @@ export default function useFieldTree(
|
||||
return { tree };
|
||||
|
||||
function parseLevel(collection: string, parentPath: string | null, level = 0) {
|
||||
const fieldsInLevel = cloneDeep(fieldsStore.getFieldsForCollectionAlphabetical(collection))
|
||||
const fieldsInLevel = [
|
||||
...cloneDeep(fieldsStore.getFieldsForCollectionAlphabetical(collection)),
|
||||
...(inject?.value?.fields.filter((field) => field.collection === collection) || []),
|
||||
]
|
||||
.filter((field: Field) => {
|
||||
const shown =
|
||||
field.meta?.special?.includes('alias') !== true && field.meta?.special?.includes('no-data') !== true;
|
||||
@@ -28,18 +35,29 @@ export default function useFieldTree(
|
||||
name: field.name,
|
||||
field: field.field,
|
||||
key: parentPath ? `${parentPath}.${field.field}` : field.field,
|
||||
}));
|
||||
})) as FieldOption[];
|
||||
|
||||
if (level >= 3) return fieldsInLevel;
|
||||
|
||||
for (const field of fieldsInLevel) {
|
||||
const relations = relationsStore.getRelationsForField(collection, field.field);
|
||||
const relations = [
|
||||
...relationsStore.getRelationsForField(collection, field.field),
|
||||
...(inject?.value?.relations.filter((relation: Relation) => {
|
||||
return (
|
||||
(relation.many_collection === collection && relation.many_field === field.field) ||
|
||||
(relation.one_collection === collection && relation.one_field === field.field)
|
||||
);
|
||||
}) || []),
|
||||
];
|
||||
|
||||
const relation = relations.find(
|
||||
(relation: Relation) =>
|
||||
(relation.many_collection === collection && relation.many_field === field.field) ||
|
||||
(relation.one_collection === collection && relation.one_field === field.field)
|
||||
);
|
||||
|
||||
if (!relation) continue;
|
||||
|
||||
const relationType = getRelationType({ relation, collection, field: field.field });
|
||||
|
||||
if (relationType === 'm2o') {
|
||||
@@ -48,7 +66,7 @@ export default function useFieldTree(
|
||||
parentPath ? `${parentPath}.${field.field}` : field.field,
|
||||
level + 1
|
||||
);
|
||||
} else {
|
||||
} else if (strict === false) {
|
||||
field.children = parseLevel(
|
||||
relation.many_collection,
|
||||
parentPath ? `${parentPath}.${field.field}` : field.field,
|
||||
|
||||
@@ -159,7 +159,7 @@ export function useItems(collection: Ref<string>, query: Query) {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out fake internal columns. This is (among other things) for a fake $file m2o field
|
||||
// Filter out fake internal columns. This is (among other things) for a fake $thumbnail m2o field
|
||||
// on directus_files
|
||||
fieldsToFetch = fieldsToFetch.filter((field) => field.startsWith('$') === false);
|
||||
|
||||
@@ -184,13 +184,13 @@ export function useItems(collection: Ref<string>, query: Query) {
|
||||
* able to render out the directus_files collection (file library) using regular layouts
|
||||
*
|
||||
* Layouts expect the file to be a m2o of a `file` type, however, directus_files is the
|
||||
* only collection that doesn't have this (obviously). This fake $file field is used to
|
||||
* only collection that doesn't have this (obviously). This fake $thumbnail field is used to
|
||||
* pretend there is a file m2o, so we can use the regular layout logic for files as well
|
||||
*/
|
||||
if (collection.value === 'directus_files') {
|
||||
fetchedItems = fetchedItems.map((file: any) => ({
|
||||
...file,
|
||||
$file: file,
|
||||
$thumbnail: file,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@ export default defineDisplay({
|
||||
handler: DisplayFile,
|
||||
types: ['uuid'],
|
||||
options: [],
|
||||
fields: ['data', 'type', 'title'],
|
||||
fields: ['id', 'type', 'title'],
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<value-null v-if="!relatedCollection" />
|
||||
<v-menu
|
||||
v-else-if="['o2m', 'm2m', 'm2a', 'translations'].includes(type.toLowerCase())"
|
||||
v-else-if="['o2m', 'm2m', 'm2a', 'translations', 'files'].includes(type.toLowerCase())"
|
||||
show-arrow
|
||||
:disabled="value.length === 0"
|
||||
>
|
||||
@@ -11,7 +11,7 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<v-list>
|
||||
<v-list class="links">
|
||||
<v-list-item v-for="item in value" :key="item[primaryKeyField]" :to="getLinkForItem(item)">
|
||||
<v-list-item-content>
|
||||
<render-template :template="_template" :item="item" :collection="relatedCollection" />
|
||||
@@ -117,4 +117,10 @@ export default defineComponent({
|
||||
.subdued {
|
||||
color: var(--foreground-subdued);
|
||||
}
|
||||
|
||||
.links {
|
||||
.v-list-item-content {
|
||||
height: var(--v-list-item-min-height);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
<template>
|
||||
<v-notice type="warning" v-if="!junction || !relation">
|
||||
{{ $t('relationship_not_setup') }}
|
||||
</v-notice>
|
||||
<div v-else class="files">
|
||||
<v-table
|
||||
inline
|
||||
:items="sortedItems || items"
|
||||
:loading="loading"
|
||||
:headers.sync="tableHeaders"
|
||||
:item-key="relationInfo.junctionPkField"
|
||||
:disabled="disabled"
|
||||
@update:items="sortItems($event)"
|
||||
@click:row="editItem"
|
||||
:show-manual-sort="relationInfo.sortField !== null"
|
||||
:manual-sort-key="relationInfo.sortField"
|
||||
>
|
||||
<template #item.$thumbnail="{ item }">
|
||||
<render-display
|
||||
:value="get(item, relationInfo.junctionField)"
|
||||
display="file"
|
||||
:collection="relationInfo.junctionCollection"
|
||||
:field="relationInfo.relationPkField"
|
||||
type="file"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item-append="{ item }">
|
||||
<v-icon
|
||||
name="save_alt"
|
||||
v-show="!disabled"
|
||||
v-tooltip="$t('download')"
|
||||
class="download"
|
||||
@click.stop="downloadItem(item)"
|
||||
/>
|
||||
<v-icon
|
||||
name="close"
|
||||
v-show="!disabled"
|
||||
v-tooltip="$t('deselect')"
|
||||
class="deselect"
|
||||
@click.stop="deleteItem(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-table>
|
||||
|
||||
<div class="actions" v-if="!disabled">
|
||||
<v-button class="new" @click="showUpload = true">{{ $t('upload_file') }}</v-button>
|
||||
<v-button class="existing" @click="selectModalActive = true">
|
||||
{{ $t('add_existing') }}
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<drawer-item
|
||||
v-if="!disabled"
|
||||
:active="editModalActive"
|
||||
:collection="relationInfo.junctionCollection"
|
||||
:primary-key="currentlyEditing || '+'"
|
||||
:edits="editsAtStart"
|
||||
:related-primary-key="relatedPrimaryKey || '+'"
|
||||
:junction-field="relationInfo.junctionField"
|
||||
:circular-field="junction.many_field"
|
||||
@input="stageEdits"
|
||||
@update:active="cancelEdit"
|
||||
/>
|
||||
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
:active.sync="selectModalActive"
|
||||
:collection="relationInfo.relationCollection"
|
||||
:selection="[]"
|
||||
:filters="selectionFilters"
|
||||
@input="stageSelection"
|
||||
multiple
|
||||
/>
|
||||
|
||||
<v-dialog v-model="showUpload">
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('upload_file') }}</v-card-title>
|
||||
<v-card-text><v-upload @input="onUpload" multiple from-url /></v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button @click="showUpload = false">{{ $t('done') }}</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, toRefs, PropType } from '@vue/composition-api';
|
||||
import { Header as TableHeader } from '@/components/v-table/types';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import { get } from 'lodash';
|
||||
import i18n from '@/lang';
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import { addTokenToURL } from '@/api';
|
||||
|
||||
import useActions from '@/interfaces/many-to-many/use-actions';
|
||||
import useRelation from '@/interfaces/many-to-many/use-relation';
|
||||
import useSelection from '@/interfaces/many-to-many/use-selection';
|
||||
import usePreview from '@/interfaces/many-to-many/use-preview';
|
||||
import useEdit from '@/interfaces/many-to-many/use-edit';
|
||||
import useSort from '@/interfaces/many-to-many/use-sort';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DrawerCollection, DrawerItem },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: Array as PropType<(string | number | Record<string, any>)[] | null>,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { collection, field, value } = toRefs(props);
|
||||
|
||||
const { junction, junctionCollection, relation, relationInfo } = useRelation(collection, field);
|
||||
|
||||
function emitter(newVal: any[] | null) {
|
||||
emit('input', newVal);
|
||||
}
|
||||
|
||||
const { deleteItem, getUpdatedItems, getNewItems, getPrimaryKeys, getNewSelectedItems } = useActions(
|
||||
value,
|
||||
relationInfo,
|
||||
emitter
|
||||
);
|
||||
|
||||
const fields = computed(() => {
|
||||
const { junctionField } = relationInfo.value;
|
||||
return ['id', 'type', 'title'].map((key) => `${junctionField}.${key}`);
|
||||
});
|
||||
|
||||
const tableHeaders = ref<TableHeader[]>([
|
||||
{
|
||||
text: '',
|
||||
value: '$thumbnail',
|
||||
align: 'left',
|
||||
sortable: false,
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
text: i18n.t('title'),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
value: relationInfo.value.junctionField + '.' + 'title',
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
width: 250,
|
||||
},
|
||||
]);
|
||||
|
||||
const { loading, error, items } = usePreview(
|
||||
value,
|
||||
fields,
|
||||
relationInfo,
|
||||
getNewSelectedItems,
|
||||
getUpdatedItems,
|
||||
getNewItems,
|
||||
getPrimaryKeys
|
||||
);
|
||||
|
||||
const {
|
||||
cancelEdit,
|
||||
stageEdits,
|
||||
editsAtStart,
|
||||
editItem,
|
||||
currentlyEditing,
|
||||
editModalActive,
|
||||
relatedPrimaryKey,
|
||||
} = useEdit(value, relationInfo, emitter);
|
||||
|
||||
const { stageSelection, selectModalActive, selectionFilters } = useSelection(value, items, relationInfo, emitter);
|
||||
|
||||
const { showUpload, onUpload } = useUpload();
|
||||
|
||||
const { sort, sortItems, sortedItems } = useSort(relationInfo, fields, items, emitter);
|
||||
|
||||
return {
|
||||
junction,
|
||||
relation,
|
||||
tableHeaders,
|
||||
junctionCollection,
|
||||
loading,
|
||||
error,
|
||||
currentlyEditing,
|
||||
cancelEdit,
|
||||
showUpload,
|
||||
stageEdits,
|
||||
editsAtStart,
|
||||
selectModalActive,
|
||||
stageSelection,
|
||||
selectionFilters,
|
||||
deleteItem,
|
||||
items,
|
||||
get,
|
||||
onUpload,
|
||||
relationInfo,
|
||||
editItem,
|
||||
editModalActive,
|
||||
relatedPrimaryKey,
|
||||
sort,
|
||||
sortItems,
|
||||
sortedItems,
|
||||
downloadItem,
|
||||
};
|
||||
|
||||
function downloadItem(item: any) {
|
||||
const filePath = addTokenToURL(getRootPath() + `assets/${item.directus_files_id.id}?download`);
|
||||
window.open(filePath, '_blank');
|
||||
}
|
||||
|
||||
function useUpload() {
|
||||
const showUpload = ref(false);
|
||||
|
||||
return { showUpload, onUpload };
|
||||
|
||||
function onUpload(files: Record<string, any>[]) {
|
||||
showUpload.value = false;
|
||||
|
||||
if (files.length === 0) return;
|
||||
|
||||
const { junctionField } = relationInfo.value;
|
||||
|
||||
const filesAsJunctionRows = files.map((file) => {
|
||||
return {
|
||||
[junctionField]: file.id,
|
||||
};
|
||||
});
|
||||
|
||||
emit('input', [...(props.value || []), ...filesAsJunctionRows]);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.existing {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.download {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.deselect {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
--v-icon-color-hover: var(--danger);
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +0,0 @@
|
||||
import { defineInterface } from '../define';
|
||||
import InterfaceFiles from './files.vue';
|
||||
|
||||
export default defineInterface({
|
||||
id: 'files',
|
||||
name: '$t:interfaces.files.files',
|
||||
description: '$t:interfaces.files.description',
|
||||
icon: 'note_add',
|
||||
component: InterfaceFiles,
|
||||
types: ['alias'],
|
||||
groups: ['files'],
|
||||
relational: true,
|
||||
options: [],
|
||||
recommendedDisplays: ['files'],
|
||||
});
|
||||
@@ -6,7 +6,7 @@
|
||||
{{ $t('disabled') }}
|
||||
</v-notice>
|
||||
|
||||
<div class="image-preview" v-else-if="image" :class="{ 'is-svg': image.type.includes('svg') }">
|
||||
<div class="image-preview" v-else-if="image" :class="{ 'is-svg': image.type && image.type.includes('svg') }">
|
||||
<img :src="src" alt="" role="presentation" />
|
||||
|
||||
<div class="shadow" />
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
</div>
|
||||
|
||||
<v-list v-else>
|
||||
<v-notice v-if="previewValues.length === 0">
|
||||
{{ $t('no_items') }}
|
||||
</v-notice>
|
||||
|
||||
<draggable
|
||||
:force-fallback="true"
|
||||
:value="previewValues"
|
||||
|
||||
@@ -10,7 +10,7 @@ export default defineInterface({
|
||||
component: InterfaceManyToMany,
|
||||
relational: true,
|
||||
types: ['alias'],
|
||||
groups: ['m2m'],
|
||||
groups: ['m2m', 'files'],
|
||||
options: Options,
|
||||
recommendedDisplays: ['related-values'],
|
||||
});
|
||||
|
||||
@@ -3,17 +3,27 @@
|
||||
{{ $t('relationship_not_setup') }}
|
||||
</v-notice>
|
||||
<div class="many-to-many" v-else>
|
||||
<v-notice v-if="sortedItems.length === 0">
|
||||
{{ $t('no_items') }}
|
||||
</v-notice>
|
||||
|
||||
<v-list>
|
||||
<draggable
|
||||
:force-fallback="true"
|
||||
:value="sortedItems || items"
|
||||
:value="sortedItems"
|
||||
@input="sortItems($event)"
|
||||
handler=".drag-handle"
|
||||
:disabled="!relation.sort_field"
|
||||
:disabled="!junction.sort_field"
|
||||
>
|
||||
<v-list-item v-for="item in sortedItems || items" :key="item.id" block @click="editItem(item)">
|
||||
<v-icon v-if="relation.sort_field" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :item="item" :template="templateWithDefaults" />
|
||||
<v-list-item
|
||||
:dense="sortedItems.length > 4"
|
||||
v-for="item in sortedItems"
|
||||
:key="item.id"
|
||||
block
|
||||
@click="editItem(item)"
|
||||
>
|
||||
<v-icon v-if="junction.sort_field" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :collection="junctionCollection.collection" :item="item" :template="templateWithDefaults" />
|
||||
<div class="spacer" />
|
||||
<v-icon name="close" @click.stop="deleteItem(item)" />
|
||||
</v-list-item>
|
||||
@@ -66,6 +76,7 @@ import useEdit from './use-edit';
|
||||
import useSelection from './use-selection';
|
||||
import useSort from './use-sort';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DrawerItem, DrawerCollection, Draggable },
|
||||
@@ -104,7 +115,9 @@ export default defineComponent({
|
||||
() => props.template || junctionCollection.value.meta?.display_template || `{{${junction.value.many_primary}}}`
|
||||
);
|
||||
|
||||
const fields = computed(() => getFieldsFromTemplate(templateWithDefaults.value));
|
||||
const fields = computed(() =>
|
||||
adjustFieldsForDisplays(getFieldsFromTemplate(templateWithDefaults.value), junctionCollection.value.collection)
|
||||
);
|
||||
|
||||
const { deleteItem, getUpdatedItems, getNewItems, getPrimaryKeys, getNewSelectedItems } = useActions(
|
||||
value,
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
</v-notice>
|
||||
<div v-else class="form-grid">
|
||||
<div class="field full">
|
||||
<p class="type-label">{{ $t('select_fields') }}</p>
|
||||
<p class="type-label">{{ $t('display_template') }}</p>
|
||||
<v-field-template
|
||||
:collection="junctionCollection"
|
||||
v-model="template"
|
||||
:inject="junctionCollectionExists ? null : { fields: newFields, collections: newCollections, relations }"
|
||||
:collection="junctionCollection"
|
||||
:depth="2"
|
||||
:inject="!!junctionCollectionInfo ? null : { fields: newFields, collections: newCollections, relations }"
|
||||
:placeholder="
|
||||
junctionCollectionInfo && junctionCollectionInfo.meta && junctionCollectionInfo.meta.display_template
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,7 +21,6 @@
|
||||
<script lang="ts">
|
||||
import { Field } from '@/types';
|
||||
import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
import { useRelationsStore } from '@/stores/';
|
||||
import { Relation, Collection } from '@/types';
|
||||
import { useCollectionsStore } from '@/stores';
|
||||
export default defineComponent({
|
||||
@@ -71,13 +74,13 @@ export default defineComponent({
|
||||
return junctionRelation?.many_collection || null;
|
||||
});
|
||||
|
||||
const junctionCollectionExists = computed(() => {
|
||||
return !!collectionsStore.state.collections.find(
|
||||
(collection) => collection.collection === junctionCollection.value
|
||||
);
|
||||
const junctionCollectionInfo = computed(() => {
|
||||
if (!junctionCollection.value) return null;
|
||||
|
||||
return collectionsStore.getCollection(junctionCollection.value);
|
||||
});
|
||||
|
||||
return { template, junctionCollection, junctionCollectionExists };
|
||||
return { template, junctionCollection, junctionCollectionInfo };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function useSort(
|
||||
|
||||
const sortedItems = computed(() => {
|
||||
const sField = relation.value.sortField;
|
||||
if (sField === null || sort.value.by !== sField) return null;
|
||||
if (sField === null || sort.value.by !== sField) return items.value;
|
||||
|
||||
const desc = sort.value.desc;
|
||||
const sorted = sortBy(items.value, [sField]);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
{{ $t('relationship_not_setup') }}
|
||||
</v-notice>
|
||||
<div class="one-to-many" v-else>
|
||||
<v-notice v-if="sortedItems.length === 0">
|
||||
{{ $t('no_items') }}
|
||||
</v-notice>
|
||||
|
||||
<v-list>
|
||||
<draggable
|
||||
:force-fallback="true"
|
||||
@@ -11,7 +15,13 @@
|
||||
handler=".drag-handle"
|
||||
:disabled="!relation.sort_field"
|
||||
>
|
||||
<v-list-item v-for="item in sortedItems" :key="item.id" block @click="editItem(item)">
|
||||
<v-list-item
|
||||
:dense="sortedItems.length > 4"
|
||||
v-for="item in sortedItems"
|
||||
:key="item.id"
|
||||
block
|
||||
@click="editItem(item)"
|
||||
>
|
||||
<v-icon v-if="relation.sort_field" name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :collection="relation.many_collection" :item="item" :template="templateWithDefaults" />
|
||||
<div class="spacer" />
|
||||
@@ -64,6 +74,7 @@ import { get } from 'lodash';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import Draggable from 'vuedraggable';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DrawerItem, DrawerCollection, Draggable },
|
||||
@@ -103,7 +114,10 @@ export default defineComponent({
|
||||
const templateWithDefaults = computed(
|
||||
() => props.template || relatedCollection.value.meta?.display_template || `{{${relation.value.many_primary}}}`
|
||||
);
|
||||
const fields = computed(() => getFieldsFromTemplate(templateWithDefaults.value));
|
||||
|
||||
const fields = computed(() =>
|
||||
adjustFieldsForDisplays(getFieldsFromTemplate(templateWithDefaults.value), relatedCollection.value.collection)
|
||||
);
|
||||
|
||||
const { tableHeaders, items, loading } = useTable();
|
||||
const { currentlyEditing, editItem, editsAtStart, stageEdits, cancelEdit } = useEdits();
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
{{ $t('interfaces.one-to-many.no_collection') }}
|
||||
</v-notice>
|
||||
<div v-else class="form-grid">
|
||||
<div class="field half-left">
|
||||
<p class="type-label">{{ $t('select_fields') }}</p>
|
||||
<div class="field full">
|
||||
<p class="type-label">{{ $t('display_template') }}</p>
|
||||
<v-field-template
|
||||
:collection="relatedCollection"
|
||||
v-model="template"
|
||||
:inject="relatedCollectionExists ? null : { fields: newFields, collections: newCollections, relations }"
|
||||
:collection="relatedCollection"
|
||||
:depth="2"
|
||||
:inject="!!relatedCollectionInfo ? null : { fields: newFields, collections: newCollections, relations }"
|
||||
:placeholder="
|
||||
relatedCollectionInfo && relatedCollectionInfo.meta && relatedCollectionInfo.meta.display_template
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field half-right">
|
||||
<p class="type-label">{{ $t('order') }}</p>
|
||||
<v-field-select v-model="order" :collection="relatedCollection" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -66,18 +65,6 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
const order = computed({
|
||||
get() {
|
||||
return props.value?.order;
|
||||
},
|
||||
set(newTemplate: string) {
|
||||
emit('input', {
|
||||
...(props.value || {}),
|
||||
order: newTemplate,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const relatedCollection = computed(() => {
|
||||
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
|
||||
const { field } = props.fieldData;
|
||||
@@ -87,13 +74,12 @@ export default defineComponent({
|
||||
return relatedRelation?.many_collection || null;
|
||||
});
|
||||
|
||||
const relatedCollectionExists = computed(() => {
|
||||
return !!collectionsStore.state.collections.find(
|
||||
(collection) => collection.collection === relatedCollection.value
|
||||
);
|
||||
const relatedCollectionInfo = computed(() => {
|
||||
if (!relatedCollection.value) return null;
|
||||
return collectionsStore.getCollection(relatedCollection.value);
|
||||
});
|
||||
|
||||
return { template, order, relatedCollection, relatedCollectionExists };
|
||||
return { template, relatedCollection, relatedCollectionInfo };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
<template>
|
||||
<div class="repeater">
|
||||
<v-notice v-if="!value || value.length === 0">
|
||||
{{ $t('no_items') }}
|
||||
</v-notice>
|
||||
|
||||
<v-list>
|
||||
<draggable :force-fallback="true" :value="value" @input="$emit('input', $event)" handler=".drag-handle">
|
||||
<v-list-item v-for="(item, index) in value" :key="item.id" block @click="active = index">
|
||||
<v-list-item
|
||||
:dense="value.length > 4"
|
||||
v-for="(item, index) in value"
|
||||
:key="item.id"
|
||||
block
|
||||
@click="active = index"
|
||||
>
|
||||
<v-icon name="drag_handle" class="drag-handle" left @click.stop="() => {}" />
|
||||
<render-template :fields="fields" :item="item" :template="templateWithDefaults" />
|
||||
<div class="spacer" />
|
||||
|
||||
@@ -4,8 +4,15 @@
|
||||
</v-notice>
|
||||
<div v-else class="form-grid">
|
||||
<div class="field full">
|
||||
<p class="type-label">{{ $t('interfaces.translations.display_template') }}</p>
|
||||
<v-field-template :collection="relatedCollection" v-model="template" :depth="2" />
|
||||
<p class="type-label">{{ $t('display_template') }}</p>
|
||||
<v-field-template
|
||||
:collection="relatedCollection"
|
||||
v-model="template"
|
||||
:depth="2"
|
||||
:placeholder="
|
||||
relatedCollectionInfo && relatedCollectionInfo.meta && relatedCollectionInfo.meta.display_template
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,8 +20,9 @@
|
||||
<script lang="ts">
|
||||
import { Field } from '@/types';
|
||||
import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
import { useRelationsStore } from '@/stores/';
|
||||
import { Relation } from '@/types/relations';
|
||||
import { useCollectionsStore } from '@/stores/';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
collection: {
|
||||
@@ -35,7 +43,8 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const relationsStore = useRelationsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const template = computed({
|
||||
get() {
|
||||
return props.value?.template;
|
||||
@@ -52,12 +61,17 @@ export default defineComponent({
|
||||
if (!props.fieldData || !props.relations || props.relations.length === 0) return null;
|
||||
const { field } = props.fieldData;
|
||||
const relation = props.relations.find(
|
||||
(relation) => relation.one_collection !== props.collection && relation.one_field !== field
|
||||
(relation) => relation.one_collection === props.collection && relation.one_field === field
|
||||
);
|
||||
return relation?.one_collection || null;
|
||||
return relation?.many_collection || null;
|
||||
});
|
||||
|
||||
return { template, relatedCollection };
|
||||
const relatedCollectionInfo = computed(() => {
|
||||
if (!relatedCollection.value) return null;
|
||||
return collectionsStore.getCollection(relatedCollection.value);
|
||||
});
|
||||
|
||||
return { template, relatedCollection, relatedCollectionInfo };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -691,6 +691,7 @@ fields:
|
||||
sort_field: Sort Field
|
||||
accountability: Activity & Revision Tracking
|
||||
directus_files:
|
||||
$thumbnail: Thumbnail
|
||||
title: Title
|
||||
description: Description
|
||||
tags: Tags
|
||||
|
||||
@@ -229,7 +229,7 @@ export default defineComponent({
|
||||
|
||||
const fileFields = computed(() => {
|
||||
return fieldsInCollection.value.filter((field) => {
|
||||
if (field.field === '$file') return true;
|
||||
if (field.field === '$thumbnail') return true;
|
||||
|
||||
const relation = relationsStore.state.relations.find((relation) => {
|
||||
return (
|
||||
@@ -419,7 +419,7 @@ export default defineComponent({
|
||||
fields.push(`${imageSource.value}.id`);
|
||||
}
|
||||
|
||||
if (props.collection === 'directus_files' && imageSource.value === '$file') {
|
||||
if (props.collection === 'directus_files' && imageSource.value === '$thumbnail') {
|
||||
fields.push('modified_on');
|
||||
fields.push('type');
|
||||
}
|
||||
|
||||
@@ -10,16 +10,23 @@ import { merge, orderBy } from 'lodash';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
|
||||
/**
|
||||
* directus_files is a special case. For it to play nice with interfaces/layouts/displays, we need
|
||||
* to treat the actual image thumbnail as a separate available field, instead of part of the regular
|
||||
* item (normally all file related info is nested within a separate column). This allows layouts to
|
||||
* render out files as it if were a "normal" collection, where the actual file is a fake m2o to
|
||||
* itself.
|
||||
*/
|
||||
const fakeFilesField: Field = {
|
||||
collection: 'directus_files',
|
||||
field: '$file',
|
||||
field: '$thumbnail',
|
||||
schema: null,
|
||||
name: i18n.t('file'),
|
||||
name: '$thumbnail',
|
||||
type: 'integer',
|
||||
meta: {
|
||||
id: -1,
|
||||
collection: 'directus_files',
|
||||
field: '$file',
|
||||
field: '$thumbnail',
|
||||
sort: null,
|
||||
special: null,
|
||||
interface: null,
|
||||
@@ -55,16 +62,6 @@ export const useFieldsStore = createStore({
|
||||
|
||||
const fields: FieldRaw[] = fieldsResponse.data.data;
|
||||
|
||||
/**
|
||||
* @NOTE
|
||||
*
|
||||
* directus_files is a special case. For it to play nice with layouts, we need to
|
||||
* treat the actual image as a separate available field, instead of part of the regular
|
||||
* item (normally all file related info is nested within a separate column). This allows
|
||||
* layouts to render out files as it if were a "normal" collection, where the actual file
|
||||
* is a fake m2o to itself.
|
||||
*/
|
||||
|
||||
this.state.fields = [...fields.map(this.parseField), fakeFilesField];
|
||||
|
||||
this.translateFields();
|
||||
|
||||
@@ -27,30 +27,6 @@ export const useRelationsStore = createStore({
|
||||
|
||||
if (!fieldInfo) return [];
|
||||
|
||||
if (fieldInfo.type === 'file') {
|
||||
return [
|
||||
{
|
||||
many_collection: collection,
|
||||
many_field: field,
|
||||
one_collection: 'directus_files',
|
||||
one_field: null,
|
||||
junction_field: null,
|
||||
},
|
||||
] as Relation[];
|
||||
}
|
||||
|
||||
if (['user', 'user_created', 'user_updated', 'owner'].includes(fieldInfo.type)) {
|
||||
return [
|
||||
{
|
||||
many_collection: collection,
|
||||
many_field: field,
|
||||
one_collection: 'directus_users',
|
||||
one_field: null,
|
||||
junction_field: null,
|
||||
},
|
||||
] as Relation[];
|
||||
}
|
||||
|
||||
const relations: Relation[] = this.getRelationsForCollection(collection).filter((relation: Relation) => {
|
||||
return (
|
||||
(relation.many_collection === collection && relation.many_field === field) ||
|
||||
|
||||
@@ -18,12 +18,14 @@ export default function adjustFieldsForDisplays(fields: readonly string[], paren
|
||||
if (!display) return fieldKey;
|
||||
if (!display?.fields) return fieldKey;
|
||||
|
||||
let fieldKeys: string[] | null = null;
|
||||
|
||||
if (Array.isArray(display.fields)) {
|
||||
return display.fields.map((relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`);
|
||||
fieldKeys = display.fields.map((relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`);
|
||||
}
|
||||
|
||||
if (typeof display.fields === 'function') {
|
||||
return display
|
||||
fieldKeys = display
|
||||
.fields(field.meta?.display_options, {
|
||||
collection: field.collection,
|
||||
field: field.field,
|
||||
@@ -32,6 +34,24 @@ export default function adjustFieldsForDisplays(fields: readonly string[], paren
|
||||
.map((relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`);
|
||||
}
|
||||
|
||||
if (fieldKeys) {
|
||||
return fieldKeys.map((fieldKey) => {
|
||||
/**
|
||||
* This is for the special case where you want to show a thumbnail in a relation to
|
||||
* directus_files. The thumbnail itself isn't a real field, but shows the thumbnail based
|
||||
* on the other available fields (like ID, title, and type).
|
||||
*/
|
||||
if (fieldKey.includes('$thumbnail') && field.collection === 'directus_files') {
|
||||
return fieldKey
|
||||
.split('.')
|
||||
.filter((part) => part !== '$thumbnail')
|
||||
.join('.');
|
||||
}
|
||||
|
||||
return fieldKey;
|
||||
});
|
||||
}
|
||||
|
||||
return fieldKey;
|
||||
})
|
||||
.flat();
|
||||
|
||||
@@ -11,5 +11,6 @@ export function getFieldsFromTemplate(template: string | null) {
|
||||
fields = fields.map((field) => {
|
||||
return field.replace(/{{/g, '').replace(/}}/g, '').trim();
|
||||
});
|
||||
|
||||
return fields as string[];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function getRelatedCollection(collection: string, field: string)
|
||||
const type = fieldInfo.type.toLowerCase();
|
||||
|
||||
// o2m | m2m
|
||||
if (['o2m', 'm2m', 'm2a', 'alias', 'translations'].includes(type)) {
|
||||
if (['o2m', 'm2m', 'm2a', 'alias', 'translations', 'files'].includes(type)) {
|
||||
return relations[0].many_collection;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
<template v-for="(part, index) in parts">
|
||||
<value-null :key="index" v-if="part === null || part.value === null" />
|
||||
<component
|
||||
v-else-if="typeof part === 'object'"
|
||||
v-else-if="typeof part === 'object' && part.component"
|
||||
:is="`display-${part.component}`"
|
||||
:key="index"
|
||||
:value="part.value"
|
||||
:interface="part.interface"
|
||||
:interface-options="part.interfaceOptions"
|
||||
:type="part.type"
|
||||
:collection="part.collection"
|
||||
:field="part.field"
|
||||
v-bind="part.options"
|
||||
/>
|
||||
<span :key="index" v-else>{{ part }}</span>
|
||||
@@ -51,20 +53,6 @@ export default defineComponent({
|
||||
|
||||
const regex = /({{.*?}})/g;
|
||||
|
||||
const fields = computed(() => {
|
||||
const fields: Field[] = [];
|
||||
|
||||
if (props.collection) {
|
||||
fields.push(...fieldsStore.getFieldsForCollection(props.collection));
|
||||
}
|
||||
|
||||
if (props.fields) {
|
||||
fields.push(...props.fields);
|
||||
}
|
||||
|
||||
return fields;
|
||||
});
|
||||
|
||||
const parts = computed(() =>
|
||||
props.template
|
||||
.split(regex)
|
||||
@@ -72,15 +60,31 @@ export default defineComponent({
|
||||
.map((part) => {
|
||||
if (part.startsWith('{{') === false) return part;
|
||||
|
||||
const fieldKey = part.replace(/{{/g, '').replace(/}}/g, '').trim();
|
||||
const field: Field | undefined = fields.value.find((field) => field.field === fieldKey);
|
||||
let fieldKey = part.replace(/{{/g, '').replace(/}}/g, '').trim();
|
||||
const field: Field | undefined =
|
||||
fieldsStore.getField(props.collection, fieldKey) || props.fields?.find((field) => field.field === fieldKey);
|
||||
|
||||
/**
|
||||
* This is for cases where you are rendering a display template directly on
|
||||
* directus_files. The $thumbnail fields doesn't exist, but instead renders a
|
||||
* thumbnail based on the other fields in the file info. In that case, the value
|
||||
* should be the whole related file object, not just the fake "thumbnail" field. By
|
||||
* stripping out the thumbnail part in the field key path, the rest of the function
|
||||
* will extract the value correctly.
|
||||
*/
|
||||
if (field && field.collection === 'directus_files' && field.field === '$thumbnail') {
|
||||
fieldKey = fieldKey
|
||||
.split('.')
|
||||
.filter((part) => part !== '$thumbnail')
|
||||
.join('.');
|
||||
}
|
||||
|
||||
// Try getting the value from the item, return some question marks if it doesn't exist
|
||||
const value = get(props.item, fieldKey);
|
||||
if (value === undefined) return null;
|
||||
|
||||
// If no display is configured, we can render the raw value
|
||||
if (!field || field.meta?.display === null) return value;
|
||||
if (!field || !field.meta?.display) return value;
|
||||
|
||||
const displayInfo = displays.value.find((display) => display.id === field.meta?.display);
|
||||
|
||||
@@ -100,6 +104,8 @@ export default defineComponent({
|
||||
interface: field.meta?.interface,
|
||||
interfaceOptions: field.meta?.options,
|
||||
type: field.type,
|
||||
collection: field.collection,
|
||||
field: field.field,
|
||||
};
|
||||
})
|
||||
.map((p) => p || null)
|
||||
@@ -116,14 +122,15 @@ export default defineComponent({
|
||||
.render-template {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
padding-right: 8px;
|
||||
line-height: normal;
|
||||
|
||||
& > * {
|
||||
@include no-wrap;
|
||||
|
||||
> * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@include no-wrap;
|
||||
}
|
||||
|
||||
.subdued {
|
||||
|
||||
Reference in New Issue
Block a user