mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Image display (#505)
* Add adjust for displays util * Adjust fetched fields for configured options * Add padding to table rows * Add image display * Rename Query to Options * Fix codesmell
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
@click="$emit('click', $event)"
|
||||
:style="{
|
||||
'--table-row-height': height + 2 + 'px',
|
||||
'--table-row-line-height': height + 'px',
|
||||
'--table-row-line-height': 1,
|
||||
}"
|
||||
>
|
||||
<td v-if="showManualSort" class="manual cell">
|
||||
@@ -102,9 +102,13 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-row {
|
||||
height: var(--table-row-height);
|
||||
|
||||
.cell {
|
||||
height: var(--table-row-height);
|
||||
padding: 0 0 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
padding-left: 12px;
|
||||
overflow: hidden;
|
||||
line-height: var(--table-row-line-height);
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -9,7 +9,7 @@ import filtersToQuery from '@/utils/filters-to-query';
|
||||
import { orderBy } from 'lodash';
|
||||
import moveInArray from '@/utils/move-in-array';
|
||||
|
||||
type Options = {
|
||||
type Query = {
|
||||
limit: Ref<number>;
|
||||
fields: Ref<readonly string[]>;
|
||||
sort: Ref<string>;
|
||||
@@ -18,11 +18,11 @@ type Options = {
|
||||
searchQuery: Ref<string | null>;
|
||||
};
|
||||
|
||||
export function useItems(collection: Ref<string>, options: Options) {
|
||||
export function useItems(collection: Ref<string>, query: Query) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { primaryKeyField, sortField } = useCollection(collection);
|
||||
|
||||
const { limit, fields, sort, page, filters, searchQuery } = options;
|
||||
const { limit, fields, sort, page, filters, searchQuery } = query;
|
||||
|
||||
const endpoint = computed(() => {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
@@ -52,7 +52,7 @@ export function useItems(collection: Ref<string>, options: Options) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Waiting for the tick here makes sure the options have been adjusted for the new
|
||||
// Waiting for the tick here makes sure the query have been adjusted for the new
|
||||
// collection
|
||||
await Vue.nextTick();
|
||||
reset();
|
||||
|
||||
48
src/displays/image/image.vue
Normal file
48
src/displays/image/image.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<img v-if="src" :src="src" role="presentation" :alt="value && value.title" />
|
||||
<span v-else>--</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
|
||||
type Image = {
|
||||
type: string;
|
||||
data: {
|
||||
thumbnails: {
|
||||
key: string;
|
||||
url: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Object as PropType<Image>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const src = computed(() => {
|
||||
if (props.value === null) return null;
|
||||
return (
|
||||
props.value?.data?.thumbnails?.find((thumb) => thumb.key === 'directus-small-crop')
|
||||
?.url || null
|
||||
);
|
||||
});
|
||||
|
||||
return { src };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
img {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
vertical-align: -30%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
12
src/displays/image/index.ts
Normal file
12
src/displays/image/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineDisplay } from '@/displays/define';
|
||||
import DisplayImage from './image.vue';
|
||||
|
||||
export default defineDisplay(({ i18n }) => ({
|
||||
id: 'image',
|
||||
name: i18n.t('image'),
|
||||
types: ['file'],
|
||||
icon: 'insert_photo',
|
||||
handler: DisplayImage,
|
||||
options: null,
|
||||
fields: ['data', 'type', 'title'],
|
||||
}));
|
||||
@@ -4,6 +4,7 @@ import DisplayStatusDot from './status-dot/';
|
||||
import DisplayStatusBadge from './status-badge/';
|
||||
import DisplayTags from './tags/';
|
||||
import DisplayFormattedText from './formatted-text';
|
||||
import DisplayImage from './image';
|
||||
|
||||
export const displays = [
|
||||
DisplayIcon,
|
||||
@@ -12,5 +13,6 @@ export const displays = [
|
||||
DisplayStatusBadge,
|
||||
DisplayTags,
|
||||
DisplayFormattedText,
|
||||
DisplayImage,
|
||||
];
|
||||
export default displays;
|
||||
|
||||
@@ -13,6 +13,7 @@ export type DisplayConfig = {
|
||||
handler: DisplayHandlerFunction | Component;
|
||||
options: null | Partial<Field>[] | Component;
|
||||
types: string[];
|
||||
fields?: string[];
|
||||
};
|
||||
|
||||
export type DisplayContext = { i18n: VueI18n };
|
||||
|
||||
@@ -147,6 +147,7 @@ import { render } from 'micromustache';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import CardsHeader from './components/header.vue';
|
||||
import i18n from '@/lang';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Item = Record<string, any>;
|
||||
@@ -345,21 +346,26 @@ export default defineComponent({
|
||||
fields.push(`${imageSource.value}.data`);
|
||||
}
|
||||
|
||||
if (title.value) {
|
||||
fields.push(...getFieldsFromTemplate(title.value));
|
||||
}
|
||||
|
||||
if (subtitle.value) {
|
||||
fields.push(...getFieldsFromTemplate(subtitle.value));
|
||||
}
|
||||
|
||||
const sortField = sort.value.startsWith('-') ? sort.value.substring(1) : sort.value;
|
||||
|
||||
if (fields.includes(sortField) === false) {
|
||||
fields.push(sortField);
|
||||
}
|
||||
|
||||
return fields;
|
||||
const titleSubtitleFields: string[] = [];
|
||||
|
||||
if (title.value) {
|
||||
titleSubtitleFields.push(...getFieldsFromTemplate(title.value));
|
||||
}
|
||||
|
||||
if (subtitle.value) {
|
||||
titleSubtitleFields.push(...getFieldsFromTemplate(subtitle.value));
|
||||
}
|
||||
|
||||
return [
|
||||
...fields,
|
||||
...adjustFieldsForDisplays(titleSubtitleFields, props.collection),
|
||||
];
|
||||
});
|
||||
|
||||
return { sort, limit, page, fields };
|
||||
|
||||
@@ -270,6 +270,7 @@ export default defineComponent({
|
||||
.title,
|
||||
.subtitle {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
line-height: 1.3em;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -153,6 +153,7 @@ import useItems from '@/compositions/use-items';
|
||||
import { render } from 'micromustache';
|
||||
import { Filter } from '@/stores/collection-presets/types';
|
||||
import i18n from '@/lang';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
|
||||
type ViewOptions = {
|
||||
widths?: {
|
||||
@@ -224,7 +225,7 @@ export default defineComponent({
|
||||
fieldsInCollection.value.filter(({ hidden_browse }) => hidden_browse === false)
|
||||
);
|
||||
|
||||
const { sort, limit, page, fields } = useItemOptions();
|
||||
const { sort, limit, page, fields, fieldsWithRelational } = useItemOptions();
|
||||
|
||||
const { items, loading, error, totalPages, itemCount, changeManualSort } = useItems(
|
||||
collection,
|
||||
@@ -232,7 +233,7 @@ export default defineComponent({
|
||||
sort,
|
||||
limit,
|
||||
page,
|
||||
fields,
|
||||
fields: fieldsWithRelational,
|
||||
filters: _filters,
|
||||
searchQuery,
|
||||
}
|
||||
@@ -348,10 +349,11 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
const fields =
|
||||
_viewQuery.value?.fields ||
|
||||
availableFields.value.slice(0, 4).map(({ field }) => field)
|
||||
);
|
||||
availableFields.value.slice(0, 4).map(({ field }) => field);
|
||||
|
||||
return fields;
|
||||
},
|
||||
set(newFields: string[]) {
|
||||
_viewQuery.value = {
|
||||
@@ -361,7 +363,11 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
return { sort, limit, page, fields };
|
||||
const fieldsWithRelational = computed(() =>
|
||||
adjustFieldsForDisplays(fields.value, props.collection)
|
||||
);
|
||||
|
||||
return { sort, limit, page, fields, fieldsWithRelational };
|
||||
}
|
||||
|
||||
function useTable() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import displays from '@/displays';
|
||||
|
||||
export default function adjustFieldsForDisplays(
|
||||
fields: readonly string[],
|
||||
parentCollection: string
|
||||
) {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const adjustedFields = fields
|
||||
.map((fieldKey) => {
|
||||
const field = fieldsStore.getField(parentCollection, fieldKey);
|
||||
|
||||
if (!field) return fieldKey;
|
||||
if (field.display === null) return fieldKey;
|
||||
|
||||
const display = displays.find((d) => d.id === field.display);
|
||||
|
||||
if (!display?.fields) return fieldKey;
|
||||
|
||||
if (Array.isArray(display.fields)) {
|
||||
return display.fields.map(
|
||||
(relatedFieldKey: string) => `${fieldKey}.${relatedFieldKey}`
|
||||
);
|
||||
}
|
||||
|
||||
return fieldKey;
|
||||
})
|
||||
.flat();
|
||||
|
||||
return adjustedFields;
|
||||
}
|
||||
4
src/utils/adjust-fields-for-displays/index.ts
Normal file
4
src/utils/adjust-fields-for-displays/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import adjustFieldsForDisplays from './adjust-fields-for-displays';
|
||||
|
||||
export { adjustFieldsForDisplays };
|
||||
export default adjustFieldsForDisplays;
|
||||
Reference in New Issue
Block a user