Add new advanced filters experience (#8570)

* Remove advanced filter sidebar detail

So long, and thanks for all the fish.

* Remove filter conversion logic

* Start replacing/removing old skool filters

* Add inline mode for usages in search bar

* Make filter work in header bar

* Emit empty string as null in filter

* Move shared filter types to shared

* Upgrade use-items

* Fix manual sort on tabular

* Cleanup styling in search bar usage

* Tweak styling

* Fix filtering issues

* Update cards

* Remove activeFilterCount from tabular

* Update maps to work with new filters

* Update calendar to new filter/sort structure

* Fix activity module nav/search

* Fix no-results message

* Update file library filtering

* Finalize user search

* Allow filtering in drawer-collection

* Handle cancelled responses semi-gracefully

* Add loading start state timeout

* Replace sort type in api

* Last commit before redoing a bunch

* Finish new visual style

* Remove unused rounded prop from v-menu

* Tweak sizing

* Enough size tweaking for now

* Count all filter operators instead of top

* Fix archive casting

* Fix api build

* Add merge filters util

* Split filter in user vs system

* Fix export sidebar detail

* Show field label on permissions configuration

* Add migration for filter/sort

* Use filters in insights
This commit is contained in:
Rijk van Zanten
2021-10-07 18:06:03 -04:00
committed by GitHub
parent 046cc8539c
commit f64a5bef7e
99 changed files with 1375 additions and 1760 deletions

View File

@@ -1,6 +1,6 @@
<template>
<v-list large>
<v-list-item clickable :active="!activeFilter" @click="clearNavFilter">
<v-list-item clickable :active="!filterField" @click="clearNavFilter">
<v-list-item-icon>
<v-icon name="access_time" />
</v-list-item-icon>
@@ -11,7 +11,7 @@
<v-list-item
clickable
:active="activeFilter && activeFilter.field === 'user' && activeFilter.value === currentUserID"
:active="filterField === 'user' && filterValue === currentUserID"
@click="setNavFilter('user', currentUserID)"
>
<v-list-item-icon>
@@ -26,7 +26,7 @@
<v-list-item
clickable
:active="activeFilter && activeFilter.field === 'action' && activeFilter.value === 'create'"
:active="filterField === 'action' && filterValue === 'create'"
@click="setNavFilter('action', 'create')"
>
<v-list-item-icon>
@@ -39,7 +39,7 @@
<v-list-item
clickable
:active="activeFilter && activeFilter.field === 'action' && activeFilter.value === 'update'"
:active="filterField === 'action' && filterValue === 'update'"
@click="setNavFilter('action', 'update')"
>
<v-list-item-icon>
@@ -52,7 +52,7 @@
<v-list-item
clickable
:active="activeFilter && activeFilter.field === 'action' && activeFilter.value === 'delete'"
:active="filterField === 'action' && filterValue === 'delete'"
@click="setNavFilter('action', 'delete')"
>
<v-list-item-icon>
@@ -65,7 +65,7 @@
<v-list-item
clickable
:active="activeFilter && activeFilter.field === 'action' && activeFilter.value === 'comment'"
:active="filterField === 'action' && filterValue === 'comment'"
@click="setNavFilter('action', 'comment')"
>
<v-list-item-icon>
@@ -78,7 +78,7 @@
<v-list-item
clickable
:active="activeFilter && activeFilter.field === 'action' && activeFilter.value === 'login'"
:active="filterField === 'action' && filterValue === 'login'"
@click="setNavFilter('action', 'login')"
>
<v-list-item-icon>
@@ -95,51 +95,37 @@
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, PropType } from 'vue';
import { useUserStore } from '@/stores/user';
import { nanoid } from 'nanoid';
import { Filter } from '@directus/shared/types';
export default defineComponent({
props: {
filters: {
type: Array as PropType<Filter[]>,
required: true,
filter: {
type: Object as PropType<Filter>,
default: null,
},
},
emits: ['update:filters'],
emits: ['update:filter'],
setup(props, { emit }) {
const { t } = useI18n();
const userStore = useUserStore();
const currentUserID = computed(() => userStore.currentUser?.id);
const activeFilter = computed(() => {
return props.filters.find((filter) => filter.locked === true);
});
const filterField = computed(() => Object.keys(props.filter ?? {})[0] ?? null);
const filterValue = computed(() => Object.values(props.filter ?? {})[0]?._eq ?? null);
return { t, currentUserID, setNavFilter, clearNavFilter, activeFilter };
return { t, currentUserID, setNavFilter, clearNavFilter, filterField, filterValue };
function setNavFilter(key: string, value: any) {
emit('update:filters', [
...props.filters.filter((filter) => {
return filter.locked === false;
}),
{
key: nanoid(),
locked: true,
field: key,
operator: 'eq',
value: value,
emit('update:filter', {
[key]: {
_eq: value,
},
]);
});
}
function clearNavFilter() {
emit(
'update:filters',
props.filters.filter((filter) => {
return filter.locked === false;
})
);
emit('update:filter', null);
}
},
});

View File

@@ -4,8 +4,10 @@
v-slot="{ layoutState }"
v-model:layout-options="layoutOptions"
v-model:layout-query="layoutQuery"
v-model:filters="filters"
v-model:search-query="searchQuery"
:filter="mergeFilters"
:filter-user="filter"
:filter-system="roleFilter"
:search="search"
collection="directus_activity"
>
<private-view :title="t('activity_feed')">
@@ -20,14 +22,26 @@
</template>
<template #actions>
<search-input v-model="searchQuery" />
<search-input v-model="search" v-model:filter="filter" collection="directus_activity" />
</template>
<template #navigation>
<activity-navigation v-model:filters="filters" />
<activity-navigation v-model:filter="roleFilter" />
</template>
<component :is="`layout-${layout}`" v-bind="layoutState" class="layout" />
<component :is="`layout-${layout}`" v-bind="layoutState" class="layout">
<template #no-results>
<v-info :title="t('no_results')" icon="search" center>
{{ t('no_results_copy') }}
</v-info>
</template>
<template #no-items>
<v-info :title="t('item_count', 0)" icon="access_time" center>
{{ t('no_items_copy') }}
</v-info>
</template>
</component>
<router-view name="detail" :primary-key="primaryKey" />
@@ -50,13 +64,14 @@ import { defineComponent, computed, ref } from 'vue';
import ActivityNavigation from '../components/navigation.vue';
import usePreset from '@/composables/use-preset';
import { useLayout } from '@/composables/use-layout';
import FilterSidebarDetail from '@/views/private/components/filter-sidebar-detail';
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
import SearchInput from '@/views/private/components/search-input';
import { Filter } from '@directus/shared/types';
import { mergeFilters } from '@directus/shared/utils';
export default defineComponent({
name: 'ActivityCollection',
components: { ActivityNavigation, FilterSidebarDetail, LayoutSidebarDetail, SearchInput },
components: { ActivityNavigation, LayoutSidebarDetail, SearchInput },
props: {
primaryKey: {
type: String,
@@ -66,12 +81,25 @@ export default defineComponent({
setup() {
const { t } = useI18n();
const { layout, layoutOptions, layoutQuery, filters, searchQuery } = usePreset(ref('directus_activity'));
const { layout, layoutOptions, layoutQuery, filter, search } = usePreset(ref('directus_activity'));
const { breadcrumb } = useBreadcrumb();
const { layoutWrapper } = useLayout(layout);
return { t, breadcrumb, layout, layoutWrapper, layoutOptions, layoutQuery, searchQuery, filters };
const roleFilter = ref<Filter | null>(null);
return {
t,
breadcrumb,
layout,
layoutWrapper,
layoutOptions,
layoutQuery,
search,
filter,
roleFilter,
mergeFilters,
};
function useBreadcrumb() {
const breadcrumb = computed(() => {