mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Info state (#482)
* Render markdown in field notes * Make add field button full width on data model * Add no items with cta * Add no items cta * Add no results + clear * Add no collections state
This commit is contained in:
@@ -88,7 +88,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<small class="note" v-if="field.note">{{ field.note }}</small>
|
||||
<small class="note" v-if="field.note" v-html="marked(field.note)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -102,6 +102,7 @@ import { isEmpty } from '@/utils/is-empty';
|
||||
import { clone } from 'lodash';
|
||||
import { FormField } from './types';
|
||||
import interfaces from '@/interfaces';
|
||||
import marked from 'marked';
|
||||
|
||||
type FieldValues = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -159,6 +160,7 @@ export default defineComponent({
|
||||
batchActiveFields,
|
||||
toggleBatchField,
|
||||
unsetValue,
|
||||
marked,
|
||||
};
|
||||
|
||||
function useForm() {
|
||||
|
||||
@@ -73,7 +73,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 340px;
|
||||
max-width: 360px;
|
||||
color: var(--foreground-subdued);
|
||||
|
||||
&:not(:last-child) {
|
||||
|
||||
@@ -14,7 +14,7 @@ type Options = {
|
||||
sort: Ref<string>;
|
||||
page: Ref<number>;
|
||||
filters: Ref<readonly Filter[]>;
|
||||
searchQuery: Ref<string>;
|
||||
searchQuery: Ref<string | null>;
|
||||
};
|
||||
|
||||
export function useItems(collection: Ref<string>, options: Options) {
|
||||
@@ -180,7 +180,7 @@ export function useItems(collection: Ref<string>, options: Options) {
|
||||
// Requesting the page filter count in the actual request every time slows
|
||||
// the request down by like 600ms-1s. This makes sure we only fetch the count
|
||||
// once if needed.
|
||||
getTotalCount();
|
||||
getItemCount();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -190,7 +190,7 @@ export function useItems(collection: Ref<string>, options: Options) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getTotalCount() {
|
||||
async function getItemCount() {
|
||||
if (!primaryKeyField.value) return;
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
@@ -117,8 +117,14 @@
|
||||
"comments": "Comments",
|
||||
|
||||
"item_count": "No Items | One Item | {count} Items",
|
||||
"no_items_copy": "It looks like you don’t have any items in this collection. You can click the button below to add an item.",
|
||||
"all_items": "All Items",
|
||||
|
||||
"no_collections": "No Collections",
|
||||
"create_collection": "Create Collection",
|
||||
"no_collections_copy_admin": "It looks like you don’t have any Collections yet. Fortunately, it’s very easy to create one — click the button below to get started.",
|
||||
"no_collections_copy": "It looks like you don’t have any Collections yet. Please contact your system administrator to have them create your data-model.",
|
||||
|
||||
"radio_buttons": "Radio Buttons",
|
||||
"checkboxes": "Checkboxes",
|
||||
|
||||
@@ -136,7 +142,6 @@
|
||||
|
||||
"card_size": "Card Size",
|
||||
"sort_field": "Sort Field",
|
||||
"sort_direction": "Sort Direction",
|
||||
|
||||
"errors": {
|
||||
"3": "Only super admins have access to this",
|
||||
@@ -257,12 +262,18 @@
|
||||
|
||||
"none": "None",
|
||||
|
||||
"add_new_item": "Add New Item",
|
||||
|
||||
"n_items_selected": "No Items Selected | 1 Item Selected | {n} Items Selected",
|
||||
"per_page": "Per Page",
|
||||
"all_files": "All Files",
|
||||
"add_new_folder": "Add New Folder",
|
||||
"folder_name": "Folder Name...",
|
||||
|
||||
"no_results": "No Results",
|
||||
"no_results_copy": "Adjust or clear search filters to see results.",
|
||||
"clear_filters": "Clear Filters",
|
||||
|
||||
"saves_automatically": "Saves Automatically",
|
||||
|
||||
"show_system_collections": "Show System Collections",
|
||||
@@ -464,7 +475,6 @@
|
||||
"contains": "Contains",
|
||||
"continue": "Continue",
|
||||
"continue_as": "<b>{name}</b> is already authenticated for this project. If you recognize this account, please press continue.",
|
||||
"create_collection": "Create Collection",
|
||||
"create_field": "Create Field",
|
||||
"create_role": "Create Role",
|
||||
"creating_item": "Creating Item",
|
||||
@@ -669,8 +679,6 @@
|
||||
"new_field": "New Field",
|
||||
"new_file": "New File",
|
||||
"new_item": "New Item",
|
||||
"no_collections": "No Collections Setup",
|
||||
"no_collections_body": "It seems like there aren't any collections setup yet",
|
||||
"no_fields": "No Fields Setup",
|
||||
"no_fields_body": "It seems like this collection doesn't have any fields setup yet",
|
||||
"no_files": "No Files",
|
||||
@@ -678,8 +686,6 @@
|
||||
"no_interface_error": "Field {field} doesn't have an interface setup",
|
||||
"no_items_selected": "No items selected",
|
||||
"no_related_entries": "Has no related entries",
|
||||
"no_results": "No Results",
|
||||
"no_results_body": "The current filters do not match any collection items",
|
||||
"not_authenticated": "Not Authenticated",
|
||||
"not_between": "Not between",
|
||||
"not_contains": "Doesn't contain",
|
||||
|
||||
@@ -50,66 +50,82 @@
|
||||
</drawer-detail>
|
||||
</portal>
|
||||
|
||||
<cards-header
|
||||
:fields="availableFields"
|
||||
:size.sync="size"
|
||||
:selection.sync="_selection"
|
||||
:sort.sync="sort"
|
||||
/>
|
||||
<template v-if="loading || itemCount > 0">
|
||||
<cards-header
|
||||
:fields="availableFields"
|
||||
:size.sync="size"
|
||||
:selection.sync="_selection"
|
||||
:sort.sync="sort"
|
||||
/>
|
||||
|
||||
<div class="grid">
|
||||
<template v-if="loading">
|
||||
<card v-for="n in limit" :key="`loader-${n}`" loading />
|
||||
<div class="grid">
|
||||
<template v-if="loading">
|
||||
<card v-for="n in limit" :key="`loader-${n}`" loading />
|
||||
</template>
|
||||
|
||||
<card
|
||||
v-else
|
||||
v-for="item in items"
|
||||
:key="item[primaryKeyField.field]"
|
||||
:crop="imageFit === 'crop'"
|
||||
:icon="icon"
|
||||
:file="imageSource ? item[imageSource] : null"
|
||||
:item="item"
|
||||
:select-mode="selectMode || _selection.length > 0"
|
||||
:to="getLinkForItem(item)"
|
||||
v-model="_selection"
|
||||
>
|
||||
<template #title v-if="title">
|
||||
<render-template :collection="collection" :item="item" :template="title" />
|
||||
</template>
|
||||
<template #subtitle v-if="subtitle">
|
||||
<render-template
|
||||
:collection="collection"
|
||||
:item="item"
|
||||
:template="subtitle"
|
||||
/>
|
||||
</template>
|
||||
</card>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="pagination">
|
||||
<v-pagination
|
||||
v-if="totalPages > 1"
|
||||
:length="totalPages"
|
||||
:total-visible="7"
|
||||
show-first-last
|
||||
:value="page"
|
||||
@input="toPage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="loading === false && items.length >= 25" class="per-page">
|
||||
<span>{{ $t('per_page') }}</span>
|
||||
<v-select
|
||||
@input="limit = +$event"
|
||||
:value="`${limit}`"
|
||||
:items="['25', '50', '100', '250']"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-info v-else-if="itemCount === 0" :title="$t('no_results')" icon="search">
|
||||
{{ $t('no_results_copy') }}
|
||||
|
||||
<template #append>
|
||||
<v-button @click="clearFilters">{{ $t('clear_filters') }}</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
|
||||
<card
|
||||
v-else
|
||||
v-for="item in items"
|
||||
:key="item[primaryKeyField.field]"
|
||||
:crop="imageFit === 'crop'"
|
||||
:icon="icon"
|
||||
:file="imageSource ? item[imageSource] : null"
|
||||
:item="item"
|
||||
:select-mode="selectMode || _selection.length > 0"
|
||||
:to="getLinkForItem(item)"
|
||||
v-model="_selection"
|
||||
>
|
||||
<template #title v-if="title">
|
||||
<render-template :collection="collection" :item="item" :template="title" />
|
||||
</template>
|
||||
<template #subtitle v-if="subtitle">
|
||||
<render-template :collection="collection" :item="item" :template="subtitle" />
|
||||
</template>
|
||||
</card>
|
||||
</div>
|
||||
<v-info v-else :title="$tc('item_count', 0)" :icon="info.icon">
|
||||
{{ $t('no_items_copy') }}
|
||||
|
||||
<div class="footer">
|
||||
<div class="pagination">
|
||||
<v-pagination
|
||||
v-if="totalPages > 1"
|
||||
:length="totalPages"
|
||||
:total-visible="7"
|
||||
show-first-last
|
||||
:value="page"
|
||||
@input="toPage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="loading === false && items.length >= 25" class="per-page">
|
||||
<span>{{ $t('per_page') }}</span>
|
||||
<v-select
|
||||
@input="limit = +$event"
|
||||
:value="`${limit}`"
|
||||
:items="['25', '50', '100', '250']"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-info
|
||||
v-if="loading === false && items.length === 0"
|
||||
:title="$tc('item_count', 0)"
|
||||
icon="box"
|
||||
/>
|
||||
<template #append>
|
||||
<v-button :to="newLink">{{ $t('add_new_item') }}</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -179,7 +195,7 @@ export default defineComponent({
|
||||
default: null,
|
||||
},
|
||||
searchQuery: {
|
||||
type: String,
|
||||
type: String as PropType<string | null>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
@@ -191,9 +207,10 @@ export default defineComponent({
|
||||
const _viewOptions = useSync(props, 'viewOptions', emit);
|
||||
const _viewQuery = useSync(props, 'viewQuery', emit);
|
||||
const _filters = useSync(props, 'filters', emit);
|
||||
const _searchQuery = useSync(props, 'searchQuery', emit);
|
||||
|
||||
const { collection, searchQuery } = toRefs(props);
|
||||
const { primaryKeyField, fields: fieldsInCollection } = useCollection(collection);
|
||||
const { info, primaryKeyField, fields: fieldsInCollection } = useCollection(collection);
|
||||
|
||||
const availableFields = computed(() =>
|
||||
fieldsInCollection.value.filter((field) => field.hidden_browse === false)
|
||||
@@ -215,6 +232,15 @@ export default defineComponent({
|
||||
searchQuery,
|
||||
});
|
||||
|
||||
const newLink = computed(() => {
|
||||
return render(props.detailRoute, {
|
||||
project: projectsStore.state.currentProjectKey,
|
||||
collection: collection.value,
|
||||
primaryKey: '+',
|
||||
item: null,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
_selection,
|
||||
items,
|
||||
@@ -238,6 +264,9 @@ export default defineComponent({
|
||||
sort,
|
||||
fieldsInCollection,
|
||||
_filters,
|
||||
newLink,
|
||||
info,
|
||||
clearFilters,
|
||||
};
|
||||
|
||||
function toPage(newPage: number) {
|
||||
@@ -248,6 +277,11 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
_filters.value = [];
|
||||
_searchQuery.value = null;
|
||||
}
|
||||
|
||||
function useViewOptions() {
|
||||
const size = createViewOption('size', 120);
|
||||
const icon = createViewOption('icon', 'box');
|
||||
|
||||
@@ -109,7 +109,21 @@
|
||||
</template>
|
||||
</v-table>
|
||||
|
||||
<v-info v-else :title="$tc('item_count', 0)" icon="box" />
|
||||
<v-info v-else-if="itemCount === 0" :title="$t('no_results')" icon="search">
|
||||
{{ $t('no_results_copy') }}
|
||||
|
||||
<template #append>
|
||||
<v-button @click="clearFilters">{{ $t('clear_filters') }}</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
|
||||
<v-info v-else :title="$tc('item_count', 0)" :icon="info.icon">
|
||||
{{ $t('no_items_copy') }}
|
||||
|
||||
<template #append>
|
||||
<v-button :to="newLink">{{ $t('add_new_item') }}</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -165,7 +179,7 @@ export default defineComponent({
|
||||
default: () => [],
|
||||
},
|
||||
searchQuery: {
|
||||
type: String,
|
||||
type: String as PropType<string | null>,
|
||||
default: null,
|
||||
},
|
||||
selectMode: {
|
||||
@@ -187,9 +201,10 @@ export default defineComponent({
|
||||
const _viewOptions = useSync(props, 'viewOptions', emit);
|
||||
const _viewQuery = useSync(props, 'viewQuery', emit);
|
||||
const _filters = useSync(props, 'filters', emit);
|
||||
const _searchQuery = useSync(props, 'searchQuery', emit);
|
||||
|
||||
const { collection, searchQuery } = toRefs(props);
|
||||
const { primaryKeyField, fields: fieldsInCollection } = useCollection(collection);
|
||||
const { info, primaryKeyField, fields: fieldsInCollection } = useCollection(collection);
|
||||
|
||||
const availableFields = computed(() =>
|
||||
fieldsInCollection.value.filter(({ hidden_browse }) => hidden_browse === false)
|
||||
@@ -216,6 +231,15 @@ export default defineComponent({
|
||||
tableSpacing,
|
||||
} = useTable();
|
||||
|
||||
const newLink = computed(() => {
|
||||
return render(props.detailRoute, {
|
||||
project: currentProjectKey.value,
|
||||
collection: collection.value,
|
||||
primaryKey: '+',
|
||||
item: null,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
_selection,
|
||||
table,
|
||||
@@ -238,8 +262,16 @@ export default defineComponent({
|
||||
tableSpacing,
|
||||
primaryKeyField,
|
||||
_filters,
|
||||
info,
|
||||
newLink,
|
||||
clearFilters,
|
||||
};
|
||||
|
||||
function clearFilters() {
|
||||
_filters.value = [];
|
||||
_searchQuery.value = null;
|
||||
}
|
||||
|
||||
function toPage(newPage: number) {
|
||||
page.value = newPage;
|
||||
mainElement.value?.scrollTo({
|
||||
|
||||
@@ -141,6 +141,10 @@ export default defineComponent({
|
||||
);
|
||||
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
|
||||
|
||||
if (viewType.value === null) {
|
||||
viewType.value = 'tabular';
|
||||
}
|
||||
|
||||
return {
|
||||
addNewLink,
|
||||
batchDelete,
|
||||
|
||||
@@ -10,20 +10,39 @@
|
||||
<collections-navigation />
|
||||
</template>
|
||||
|
||||
<v-table :headers="tableHeaders" :items="navItems" @click:row="navigateToCollection">
|
||||
<v-table
|
||||
v-if="navItems.length > 0"
|
||||
:headers="tableHeaders"
|
||||
:items="navItems"
|
||||
@click:row="navigateToCollection"
|
||||
>
|
||||
<template #item.icon="{ item }">
|
||||
<v-icon class="icon" :name="item.icon" />
|
||||
</template>
|
||||
</v-table>
|
||||
|
||||
<v-info icon="box" :title="$t('no_collections')">
|
||||
<template v-if="isAdmin">
|
||||
{{ $t('no_collections_copy_admin') }}
|
||||
</template>
|
||||
<template #append v-if="isAdmin">
|
||||
<v-button :to="dataModelLink">{{ $t('create_collection') }}</v-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('no_collections_copy') }}
|
||||
</template>
|
||||
</v-info>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import CollectionsNavigation from '../../components/navigation/';
|
||||
import { i18n } from '@/lang';
|
||||
import useNavigation, { NavItem } from '../../compositions/use-navigation';
|
||||
import router from '@/router';
|
||||
import useUserStore from '@/stores/user';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'collections-overview',
|
||||
@@ -32,6 +51,9 @@ export default defineComponent({
|
||||
},
|
||||
props: {},
|
||||
setup() {
|
||||
const userStore = useUserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const tableHeaders = [
|
||||
{
|
||||
text: '',
|
||||
@@ -49,8 +71,17 @@ export default defineComponent({
|
||||
value: 'note',
|
||||
},
|
||||
];
|
||||
|
||||
const { navItems } = useNavigation();
|
||||
return { tableHeaders, navItems, navigateToCollection };
|
||||
|
||||
const isAdmin = computed(() => userStore.state.currentUser?.role.id === 1);
|
||||
|
||||
const dataModelLink = computed(() => {
|
||||
return `/${projectsStore.state.currentProjectKey}/settings/data-model`;
|
||||
});
|
||||
|
||||
return { tableHeaders, navItems, navigateToCollection, isAdmin, dataModelLink };
|
||||
|
||||
function navigateToCollection(navItem: NavItem) {
|
||||
router.push(navItem.to);
|
||||
}
|
||||
@@ -75,4 +106,8 @@ export default defineComponent({
|
||||
padding: var(--content-padding);
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.v-info {
|
||||
margin: 20vh 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,15 @@
|
||||
/>
|
||||
</draggable>
|
||||
|
||||
<v-button class="add-field" align="left" dashed outlined large @click="openFieldSetup()">
|
||||
<v-button
|
||||
full-width
|
||||
class="add-field"
|
||||
align="left"
|
||||
dashed
|
||||
outlined
|
||||
large
|
||||
@click="openFieldSetup()"
|
||||
>
|
||||
<v-icon name="add" />
|
||||
{{ $t('add_field') }}
|
||||
</v-button>
|
||||
|
||||
Reference in New Issue
Block a user