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:
Rijk van Zanten
2020-04-24 20:12:51 -04:00
committed by GitHub
parent d338ee7b4f
commit 8498c37431
9 changed files with 197 additions and 76 deletions

View File

@@ -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() {

View File

@@ -73,7 +73,7 @@ export default defineComponent({
}
.content {
max-width: 340px;
max-width: 360px;
color: var(--foreground-subdued);
&:not(:last-child) {

View File

@@ -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;

View File

@@ -117,8 +117,14 @@
"comments": "Comments",
"item_count": "No Items | One Item | {count} Items",
"no_items_copy": "It looks like you dont 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 dont have any Collections yet. Fortunately, its very easy to create one — click the button below to get started.",
"no_collections_copy": "It looks like you dont 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",

View File

@@ -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');

View File

@@ -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({

View File

@@ -141,6 +141,10 @@ export default defineComponent({
);
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
if (viewType.value === null) {
viewType.value = 'tabular';
}
return {
addNewLink,
batchDelete,

View File

@@ -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>

View File

@@ -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>