Merge pull request #6535 from Ocelot-Social-Community/6500-refactor-filter-menu

refactor(webapp): refactor filter menu
This commit is contained in:
Hannes Heine 2023-07-31 16:53:24 +02:00 committed by GitHub
commit 776d7b984c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 235 additions and 111 deletions

View File

@ -4,7 +4,7 @@ import CategoriesFilter from './CategoriesFilter'
const localVue = global.localVue
let wrapper, environmentAndNatureButton, democracyAndPoliticsButton
let wrapper, environmentAndNatureButton
describe('CategoriesFilter.vue', () => {
const mutations = {
@ -63,12 +63,13 @@ describe('CategoriesFilter.vue', () => {
expect(allCategoriesButton.attributes().class).toContain('--filled')
})
it('sets category button attribute `filled` when corresponding category is filtered', async () => {
getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
wrapper = await Wrapper()
democracyAndPoliticsButton = wrapper.find('.categories-filter .item-save-topics .base-button')
expect(democracyAndPoliticsButton.attributes().class).toContain('--filled')
})
// TODO move to FilterMenuComponent.spec.js?
// it('sets category button attribute `filled` when corresponding category is filtered', async () => {
// getters['posts/filteredCategoryIds'] = jest.fn(() => ['cat9'])
// wrapper = await Wrapper()
// democracyAndPoliticsButton = wrapper.find('.categories-filter .item-save-topics .base-button')
// expect(democracyAndPoliticsButton.attributes().class).toContain('--filled')
// })
describe('click on an "catetories-buttons" button', () => {
it('calls TOGGLE_CATEGORY when clicked', () => {
@ -88,13 +89,14 @@ describe('CategoriesFilter.vue', () => {
})
})
describe('save categories', () => {
it('calls the API', async () => {
wrapper = await Wrapper()
const saveButton = wrapper.find('.categories-filter .item-save-topics .base-button')
saveButton.trigger('click')
expect(apolloMutationMock).toBeCalled()
})
})
// TODO move to FilterMenuComponent.spec.js?
// describe('save categories', () => {
// it('calls the API', async () => {
// wrapper = await Wrapper()
// const saveButton = wrapper.find('.categories-filter .item-save-topics .base-button')
// saveButton.trigger('click')
// expect(apolloMutationMock).toBeCalled()
// })
// })
})
})

View File

@ -1,27 +1,23 @@
<template>
<filter-menu-section :title="$t('filter-menu.categories')" class="categories-filter">
<template #filter-topics>
<li class="item item-all-topics">
<labeled-button
<template #filter-list>
<div class="item item-all-topics">
<base-button
:filled="!filteredCategoryIds.length"
:label="$t('filter-menu.all')"
icon="check"
@click="setResetCategories"
/>
</li>
<li class="item item-save-topics">
<labeled-button filled :label="$t('actions.save')" icon="save" @click="saveCategories" />
</li>
</template>
<template #filter-list>
size="small"
>
{{ $t('filter-menu.all') }}
</base-button>
</div>
<div class="category-filter-list">
<hr />
<ds-space margin="small" />
<!-- <ds-space margin="small" /> -->
<base-button
v-for="category in categories"
:key="category.id"
@click="toggleCategory(category.id)"
@click="saveCategories(category.id)"
:filled="filteredCategoryIds.includes(category.id)"
:icon="category.icon"
size="small"
@ -40,15 +36,12 @@
<script>
import { mapGetters, mapMutations } from 'vuex'
import CategoryQuery from '~/graphql/CategoryQuery.js'
import SaveCategories from '~/graphql/SaveCategories.js'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
import SortCategories from '~/mixins/sortCategoriesMixin.js'
export default {
components: {
FilterMenuSection,
LabeledButton,
},
mixins: [SortCategories],
data() {
@ -70,19 +63,9 @@ export default {
this.resetCategories()
this.$emit('showFilterMenu')
},
saveCategories() {
this.$apollo
.mutate({
mutation: SaveCategories(),
variables: { activeCategories: this.filteredCategoryIds },
})
.then(() => {
this.$emit('showFilterMenu')
this.$toast.success(this.$t('filter-menu.save.success'))
})
.catch(() => {
this.$toast.error(this.$t('filter-menu.save.error'))
})
saveCategories(categoryId) {
this.toggleCategory(categoryId)
this.$emit('updateCategories', categoryId)
},
},
apollo: {
@ -101,7 +84,7 @@ export default {
</script>
<style lang="scss">
.category-filter-list {
margin-left: $space-xx-large;
margin-left: $space-xx-small;
> .base-button {
margin-right: $space-xx-small;

View File

@ -2,24 +2,30 @@
<filter-menu-section class="order-by-filter" :title="sectionTitle" :divider="false">
<template #filter-list>
<li class="item">
<labeled-button
<base-button
icon="check"
:label="$t('filter-menu.ended.all.label')"
:filled="!eventsEnded"
:title="$t('filter-menu.ended.all.hint')"
@click="toggleEventsEnded"
data-test="all-button"
/>
size="small"
>
{{ $t('filter-menu.ended.all.label') }}
</base-button>
</li>
<li class="item">
<labeled-button
<base-button
icon="calendar"
:label="$t('filter-menu.ended.onlyEnded.label')"
:filled="eventsEnded"
:title="$t('filter-menu.ended.onlyEnded.hint')"
@click="toggleEventsEnded"
data-test="not-ended-button"
/>
size="small"
>
{{ $t('filter-menu.ended.onlyEnded.label') }}
</base-button>
</li>
</template>
</filter-menu-section>
@ -28,13 +34,11 @@
<script>
import { mapGetters, mapMutations } from 'vuex'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
name: 'EventsByFilter',
components: {
FilterMenuSection,
LabeledButton,
},
computed: {
...mapGetters({

View File

@ -1,9 +1,19 @@
<template>
<div>
<div class="filter-menu-options">
<div class="filter-header">
<h2 class="title">{{ $t('filter-menu.filter-by') }}</h2>
<following-filter />
<div class="item-save-topics">
<labeled-button
filled
:label="$t('actions.saveCategories')"
icon="save"
@click="saveCategories"
/>
</div>
</div>
<post-type-filter />
<following-filter @showFilterMenu="$emit('showFilterMenu')" />
<categories-filter v-if="categoriesActive" @showFilterMenu="$emit('showFilterMenu')" />
</div>
<div v-if="eventSetInPostTypeFilter" class="filter-menu-options">
@ -24,6 +34,8 @@ import PostTypeFilter from './PostTypeFilter'
import FollowingFilter from './FollowingFilter'
import OrderByFilter from './OrderByFilter'
import CategoriesFilter from './CategoriesFilter'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
import SaveCategories from '~/graphql/SaveCategories.js'
export default {
components: {
@ -32,24 +44,49 @@ export default {
OrderByFilter,
CategoriesFilter,
PostTypeFilter,
LabeledButton,
},
data() {
return {
categoriesActive: this.$env.CATEGORIES_ACTIVE,
categoriesActive: this.$env ? this.$env.CATEGORIES_ACTIVE : false,
}
},
computed: {
...mapGetters({
filteredPostTypes: 'posts/filteredPostTypes',
filteredCategoryIds: 'posts/filteredCategoryIds',
}),
eventSetInPostTypeFilter() {
return this.filteredPostTypes.includes('Event')
return this.filteredPostTypes ? this.filteredPostTypes.includes('Event') : null
},
},
methods: {
saveCategories() {
this.$apollo
.mutate({
mutation: SaveCategories(),
variables: { activeCategories: this.filteredCategoryIds },
})
.then(() => {
this.$emit('showFilterMenu')
this.$toast.success(this.$t('filter-menu.save.success'))
})
.catch(() => {
this.$toast.error(this.$t('filter-menu.save.error'))
})
},
},
}
</script>
<style lang="scss">
.filter-header {
display: flex;
justify-content: space-between;
& .labeled-button {
margin-right: 2em;
}
}
.filter-menu-options {
max-width: $size-max-width-filter-menu;
padding: $space-small $space-x-small;

View File

@ -61,18 +61,18 @@ export default {
> .filter-list {
display: flex;
flex-wrap: wrap;
flex-basis: 100%;
flex-grow: 1;
padding-left: $space-base;
> .item {
width: 30%;
padding: 0 $space-x-small;
// width: 30%;
padding: 0 $space-xx-small;
margin-bottom: $space-small;
text-align: center;
@media only screen and (min-width: 800px) {
width: 20%;
// width: 15%;
}
}
}

View File

@ -10,6 +10,7 @@ describe('FollowingFilter', () => {
const mutations = {
'posts/TOGGLE_FILTER_BY_FOLLOWED': jest.fn(),
'posts/TOGGLE_FILTER_BY_MY_GROUPS': jest.fn(),
'posts/RESET_FOLLOWERS_FILTER': jest.fn(),
}
const getters = {
'auth/user': () => {
@ -65,5 +66,15 @@ describe('FollowingFilter', () => {
expect(mutations['posts/TOGGLE_FILTER_BY_MY_GROUPS']).toHaveBeenCalled()
})
})
describe('clears follower filter', () => {
it('when all button is clicked', async () => {
wrapper = await Wrapper()
const clearFollowerButton = wrapper.find(
'.following-filter .item-all-follower .base-button',
)
clearFollowerButton.trigger('click')
expect(mutations['posts/RESET_FOLLOWERS_FILTER']).toHaveBeenCalledTimes(1)
})
})
})
})

View File

@ -1,24 +1,47 @@
<template>
<filter-menu-section :divider="false" class="following-filter">
<filter-menu-section
:title="$t('filter-menu.following-title')"
:divider="false"
class="following-filter"
>
<template #filter-follower>
<div class="item item-all-follower">
<base-button
:filled="!filteredByUsersFollowed && !filteredByPostsInMyGroups"
:label="$t('filter-menu.all')"
icon="check"
@click="setResetFollowers"
size="small"
>
{{ $t('filter-menu.all') }}
</base-button>
</div>
<div class="follower-filter-list">
<li class="item follower-item">
<labeled-button
<base-button
icon="user-plus"
:label="$t('filter-menu.following')"
:filled="filteredByUsersFollowed"
:title="$t('contribution.filterFollow')"
@click="toggleFilteredByFollowed(currentUser.id)"
/>
size="small"
>
{{ $t('contribution.filterFollow') }}
</base-button>
</li>
<li class="item posts-in-my-groups-item">
<labeled-button
<base-button
icon="users"
:label="$t('filter-menu.my-groups')"
:filled="filteredByPostsInMyGroups"
:title="$t('contribution.filterMyGroups')"
@click="toggleFilteredByMyGroups()"
/>
size="small"
>
{{ $t('contribution.filterMyGroups') }}
</base-button>
</li>
</div>
</template>
</filter-menu-section>
</template>
@ -26,13 +49,11 @@
<script>
import { mapGetters, mapMutations } from 'vuex'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
name: 'FollowingFilter',
components: {
FilterMenuSection,
LabeledButton,
},
computed: {
...mapGetters({
@ -43,9 +64,26 @@ export default {
},
methods: {
...mapMutations({
resetFollowers: 'posts/RESET_FOLLOWERS_FILTER',
toggleFilteredByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED',
toggleFilteredByMyGroups: 'posts/TOGGLE_FILTER_BY_MY_GROUPS',
}),
setResetFollowers() {
this.resetFollowers()
this.$emit('showFilterMenu')
},
},
}
</script>
<style lang="scss">
.follower-filter-list {
display: flex;
margin-left: $space-xx-small;
& .base-button {
margin-right: $space-xx-small;
margin-bottom: $space-xx-small;
}
}
</style>

View File

@ -35,7 +35,7 @@ describe('OrderByFilter', () => {
it('sets "newest-button" attribute `filled`', () => {
expect(
wrapper
.find('.order-by-filter .filter-list [data-test="newest-button"] .base-button')
.find('.order-by-filter .filter-list .base-button[data-test="newest-button"]')
.classes('--filled'),
).toBe(true)
})
@ -43,7 +43,7 @@ describe('OrderByFilter', () => {
it('don\'t sets "oldest-button" attribute `filled`', () => {
expect(
wrapper
.find('.order-by-filter .filter-list [data-test="oldest-button"] .base-button')
.find('.order-by-filter .filter-list .base-button[data-test="oldest-button"]')
.classes('--filled'),
).toBe(false)
})
@ -58,7 +58,7 @@ describe('OrderByFilter', () => {
it('don\'t sets "newest-button" attribute `filled`', () => {
expect(
wrapper
.find('.order-by-filter .filter-list [data-test="newest-button"] .base-button')
.find('.order-by-filter .filter-list .base-button[data-test="newest-button"]')
.classes('--filled'),
).toBe(false)
})
@ -66,7 +66,7 @@ describe('OrderByFilter', () => {
it('sets "oldest-button" attribute `filled`', () => {
expect(
wrapper
.find('.order-by-filter .filter-list [data-test="oldest-button"] .base-button')
.find('.order-by-filter .filter-list .base-button[data-test="oldest-button"]')
.classes('--filled'),
).toBe(true)
})
@ -75,7 +75,7 @@ describe('OrderByFilter', () => {
describe('click "newest-button"', () => {
it('calls TOGGLE_ORDER with "createdAt_desc"', () => {
wrapper
.find('.order-by-filter .filter-list [data-test="newest-button"] .base-button')
.find('.order-by-filter .filter-list .base-button[data-test="newest-button"]')
.trigger('click')
expect(mutations['posts/TOGGLE_ORDER']).toHaveBeenCalledWith({}, 'createdAt_desc')
})
@ -84,7 +84,7 @@ describe('OrderByFilter', () => {
describe('click "oldest-button"', () => {
it('calls TOGGLE_ORDER with "createdAt_asc"', () => {
wrapper
.find('.order-by-filter .filter-list [data-test="oldest-button"] .base-button')
.find('.order-by-filter .filter-list .base-button[data-test="oldest-button"]')
.trigger('click')
expect(mutations['posts/TOGGLE_ORDER']).toHaveBeenCalledWith({}, 'createdAt_asc')
})

View File

@ -2,24 +2,30 @@
<filter-menu-section class="order-by-filter" :title="sectionTitle" :divider="false">
<template #filter-list>
<li class="item">
<labeled-button
<base-button
icon="sort-amount-asc"
:label="buttonLabel('desc')"
:filled="orderBy === orderedDesc"
:title="buttonTitle('desc')"
@click="toggleOrder(orderedDesc)"
data-test="newest-button"
/>
size="small"
>
{{ buttonLabel('desc') }}
</base-button>
</li>
<li class="item">
<labeled-button
<base-button
icon="sort-amount-desc"
:label="buttonLabel('asc')"
:filled="orderBy === orderedAsc"
:title="buttonTitle('asc')"
@click="toggleOrder(orderedAsc)"
data-test="oldest-button"
/>
size="small"
>
{{ buttonTitle('asc') }}
</base-button>
</li>
</template>
</filter-menu-section>
@ -28,13 +34,11 @@
<script>
import { mapGetters, mapMutations } from 'vuex'
import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
export default {
name: 'OrderByFilter',
components: {
FilterMenuSection,
LabeledButton,
},
computed: {
...mapGetters({

View File

@ -2,7 +2,7 @@
<filter-menu-section
:title="$t('filter-menu.post-type')"
:divider="false"
class="following-filter"
class="following-filter post-type-filter"
>
<template #filter-follower>
<li class="item all-item">
@ -12,7 +12,9 @@
:filled="filteredPostTypes.length === 0"
:title="$t('filter-menu.all')"
@click="togglePostType(null)"
/>
>
{{ $t('filter-menu.all') }}
</labeled-button>
</li>
<li class="item article-item">
<labeled-button
@ -59,3 +61,13 @@ export default {
},
}
</script>
<style lang="scss">
.post-type-filter {
& .filter-list {
display: grid;
grid-template-columns: repeat(3, 10%);
padding-left: 0px;
}
}
</style>

View File

@ -6,7 +6,8 @@
"edit": "Bearbeiten",
"loading": "wird geladen",
"loadMore": "mehr laden",
"save": "Speichern"
"save": "Speichern",
"saveCategories": "Themen speichern"
},
"admin": {
"categories": {
@ -298,16 +299,16 @@
"happy": "Glücklich",
"surprised": "Erstaunt"
},
"filterFollow": "Beiträge von Nutzern filtern, denen ich folge",
"filterFollow": "Nutzern, denen ich folge",
"filterMasonryGrid": {
"myFriends": "Nutzer denen ich folge",
"myGroups": "Aus meinen Gruppen",
"myTopics": "Meine Themen",
"noFilter": "Inhalt filtern",
"onlyArticles": "Nur Beiträge",
"onlyEvents": "Nur Veranstaltungen"
"noFilter": "Inhalte filtern",
"onlyArticles": "Beiträge",
"onlyEvents": "Veranstaltungen"
},
"filterMyGroups": "Beiträge in meinen Gruppen",
"filterMyGroups": "Meine Gruppen",
"inappropriatePicture": "Dieses Bild kann für einige Menschen unangemessen sein.",
"languageSelectLabel": "Sprache Deines Beitrags",
"languageSelectText": "Sprache wählen",
@ -418,21 +419,22 @@
"emotions": "Emotionen",
"ended": {
"all": {
"hint": "Zeige alle, auch beendete",
"hint": "Zeige alle, auch zukünftige",
"label": "Alle"
},
"onlyEnded": {
"hint": "Zeige nur noch nicht beendete",
"label": "Nicht beendete"
"hint": "Zeige nur noch zukünftige",
"label": "Zukünftige"
}
},
"event": "Veranstaltung",
"eventsBy": "Veranstaltungen zeige ...",
"eventsEnded": "Beendet",
"filter-by": "Filtern nach ...",
"following": "Nutzer denen ich folge",
"following": "Nutzer, denen ich folge",
"following-title": "Quellen",
"languages": "Sprachen",
"my-groups": "Meinen Gruppen",
"my-groups": "Meine Gruppen",
"order": {
"last": {
"hint": "Sortiere die Letzten nach vorn",

View File

@ -6,7 +6,8 @@
"edit": "Edit",
"loading": "loading",
"loadMore": "load more",
"save": "Save"
"save": "Save",
"saveCategories": "Save topics"
},
"admin": {
"categories": {
@ -298,16 +299,16 @@
"happy": "Happy",
"surprised": "Surprised"
},
"filterFollow": "Filter contributions from users I follow",
"filterFollow": "Users I follow",
"filterMasonryGrid": {
"myFriends": "Users I follow",
"myGroups": "By my groups",
"myTopics": "My topics",
"noFilter": "Filter content",
"onlyArticles": "Only articles",
"onlyEvents": "Only events"
"onlyArticles": "Articles",
"onlyEvents": "Events"
},
"filterMyGroups": "Contributions in my groups",
"filterMyGroups": "My groups",
"inappropriatePicture": "This image may be inappropriate for some people.",
"languageSelectLabel": "Language of your contribution",
"languageSelectText": "Select Language",
@ -431,6 +432,7 @@
"eventsEnded": "Ended",
"filter-by": "Filter by ...",
"following": "Users I follow",
"following-title": "Sources",
"languages": "Languages",
"my-groups": "My groups",
"order": {

View File

@ -47,6 +47,16 @@ export const mutations = {
delete filter.categories_some
state.filter = filter
},
RESET_FOLLOWERS_FILTER(state) {
const filter = clone(state.filter)
if (get(filter, 'postsInMyGroups')) {
delete filter.postsInMyGroups
}
if (get(filter, 'author.followedBy_some.id')) {
delete filter.author
}
state.filter = filter
},
RESET_EMOTIONS(state) {
const filter = clone(state.filter)
delete filter.emotions_some

View File

@ -146,6 +146,25 @@ describe('mutations', () => {
})
})
describe('RESET_FOLLOWERS_FILTER', () => {
beforeEach(() => {
testMutation = () => {
mutations.RESET_FOLLOWERS_FILTER(state)
return getters.filter(state)
}
})
it('resets the categories filter', () => {
state = {
filter: {
author: { followedBy_some: { id: 4711 } },
postsInMyGroups: true,
},
}
expect(testMutation()).toEqual({})
})
})
describe('TOGGLE_LANGUAGE', () => {
beforeEach(() => {
testMutation = (languageCode) => {