Migrates lists module to store

This commit is contained in:
Sean King 2023-04-06 22:13:30 -06:00
parent ad7d47f440
commit 8eff081468
No known key found for this signature in database
GPG Key ID: 510C52BACD6E7257
13 changed files with 248 additions and 109 deletions

View File

@ -1,3 +1,4 @@
import { useListsStore } from '../../stores/lists'
import ListsCard from '../lists_card/lists_card.vue' import ListsCard from '../lists_card/lists_card.vue'
const Lists = { const Lists = {
@ -11,7 +12,7 @@ const Lists = {
}, },
computed: { computed: {
lists () { lists () {
return this.$store.state.lists.allLists return useListsStore().allLists
} }
}, },
methods: { methods: {

View File

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListsUserSearch from '../lists_user_search/lists_user_search.vue' import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue' import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
@ -10,6 +11,7 @@ import {
faChevronLeft faChevronLeft
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface' import { useInterfaceStore } from '../../stores/interface'
import { useListsStore } from '../../stores/lists'
library.add( library.add(
faSearch, faSearch,
@ -38,12 +40,12 @@ const ListsNew = {
}, },
created () { created () {
if (!this.id) return if (!this.id) return
this.$store.dispatch('fetchList', { listId: this.id }) useListsStore().fetchList({ listId: this.id })
.then(() => { .then(() => {
this.title = this.findListTitle(this.id) this.title = this.findListTitle(this.id)
this.titleDraft = this.title this.titleDraft = this.title
}) })
this.$store.dispatch('fetchListAccounts', { listId: this.id }) useListsStore().fetchListAccounts({ listId: this.id })
.then(() => { .then(() => {
this.membersUserIds = this.findListAccounts(this.id) this.membersUserIds = this.findListAccounts(this.id)
this.membersUserIds.forEach(userId => { this.membersUserIds.forEach(userId => {
@ -65,7 +67,8 @@ const ListsNew = {
...mapState({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}), }),
...mapGetters(['findUser', 'findListTitle', 'findListAccounts']) ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']),
...mapGetters(['findUser'])
}, },
methods: { methods: {
onInput () { onInput () {
@ -96,10 +99,10 @@ const ListsNew = {
return this.addedUserIds.has(user.id) return this.addedUserIds.has(user.id)
}, },
addUser (user) { addUser (user) {
this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id }) useListsStore().addListAccount({ accountId: user.id, listId: this.id })
}, },
removeUser (userId) { removeUser (userId) {
this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id }) useListsStore().removeListAccount({ accountId: userId, listId: this.id })
}, },
onSearchLoading (results) { onSearchLoading (results) {
this.searchLoading = true this.searchLoading = true
@ -112,17 +115,16 @@ const ListsNew = {
this.searchUserIds = results this.searchUserIds = results
}, },
updateListTitle () { updateListTitle () {
this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft }) useListsStore().setList({ listId: this.id, title: this.titleDraft })
.then(() => { .then(() => {
this.title = this.findListTitle(this.id) this.title = this.findListTitle(this.id)
}) })
}, },
createList () { createList () {
this.$store.dispatch('createList', { title: this.titleDraft }) useListsStore().createList({ title: this.titleDraft })
.then((list) => { .then((list) => {
return this return useListsStore()
.$store .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] })
.dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
.then(() => list.id) .then(() => list.id)
}) })
.then((listId) => { .then((listId) => {
@ -137,7 +139,7 @@ const ListsNew = {
}) })
}, },
deleteList () { deleteList () {
this.$store.dispatch('deleteList', { listId: this.id }) useListsStore().deleteList({ listId: this.id })
this.$router.push({ name: 'lists' }) this.$router.push({ name: 'lists' })
} }
} }

View File

@ -1,6 +1,8 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { getListEntries } from 'src/components/navigation/filter.js' import { getListEntries } from 'src/components/navigation/filter.js'
import { useListsStore } from '../../stores/lists'
export const ListsMenuContent = { export const ListsMenuContent = {
props: [ props: [
@ -10,8 +12,10 @@ export const ListsMenuContent = {
NavigationEntry NavigationEntry
}, },
computed: { computed: {
...mapPiniaState(useListsStore, {
lists: getListEntries
}),
...mapState({ ...mapState({
lists: getListEntries,
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating federating: state => state.instance.federating

View File

@ -1,3 +1,4 @@
import { useListsStore } from '../../stores/lists'
import Timeline from '../timeline/timeline.vue' import Timeline from '../timeline/timeline.vue'
const ListsTimeline = { const ListsTimeline = {
data () { data () {
@ -17,14 +18,14 @@ const ListsTimeline = {
this.listId = route.params.id this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list') this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' }) this.$store.commit('clearTimeline', { timeline: 'list' })
this.$store.dispatch('fetchList', { listId: this.listId }) useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
} }
} }
}, },
created () { created () {
this.listId = this.$route.params.id this.listId = this.$route.params.id
this.$store.dispatch('fetchList', { listId: this.listId }) useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId }) this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}, },
unmounted () { unmounted () {

View File

@ -11,7 +11,7 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
}) })
} }
export const getListEntries = state => state.lists.allLists.map(list => ({ export const getListEntries = store => store.allLists.map(list => ({
name: 'list-' + list.id, name: 'list-' + list.id,
routeObject: { name: 'lists-timeline', params: { id: list.id } }, routeObject: { name: 'lists-timeline', params: { id: list.id } },
labelRaw: list.title, labelRaw: list.title,

View File

@ -1,4 +1,5 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js' import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js' import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
@ -14,6 +15,7 @@ import {
faStream, faStream,
faList faList
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useListsStore } from '../../stores/lists'
library.add( library.add(
faUsers, faUsers,
@ -38,8 +40,10 @@ const NavPanel = {
getters () { getters () {
return this.$store.getters return this.$store.getters
}, },
...mapPiniaState(useListsStore, {
lists: getListEntries
}),
...mapState({ ...mapState({
lists: getListEntries,
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,

View File

@ -9,6 +9,7 @@ import {
faChevronDown faChevronDown
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from '../../stores/interface' import { useInterfaceStore } from '../../stores/interface'
import { useListsStore } from '../../stores/lists'
library.add(faChevronDown) library.add(faChevronDown)
@ -87,7 +88,7 @@ const TimelineMenu = {
return '#' + this.$route.params.tag return '#' + this.$route.params.tag
} }
if (route === 'lists-timeline') { if (route === 'lists-timeline') {
return this.$store.getters.findListTitle(this.$route.params.id) return useListsStore().findListTitle(this.$route.params.id)
} }
const i18nkey = timelineNames()[this.$route.name] const i18nkey = timelineNames()[this.$route.name]
return i18nkey ? this.$t(i18nkey) : route return i18nkey ? this.$t(i18nkey) : route

View File

@ -1,9 +1,10 @@
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronRight } from '@fortawesome/free-solid-svg-icons' import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
import { mapState } from 'vuex' import { mapState } from 'pinia'
import DialogModal from '../dialog_modal/dialog_modal.vue' import DialogModal from '../dialog_modal/dialog_modal.vue'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import { useListsStore } from '../../stores/lists'
library.add(faChevronRight) library.add(faChevronRight)
@ -22,8 +23,8 @@ const UserListMenu = {
this.$store.dispatch('fetchUserInLists', this.user.id) this.$store.dispatch('fetchUserInLists', this.user.id)
}, },
computed: { computed: {
...mapState({ ...mapState(useListsStore, {
allLists: state => state.lists.allLists allLists: store => store.allLists
}), }),
inListsSet () { inListsSet () {
return new Set(this.user.inLists.map(x => x.id)) return new Set(this.user.inLists.map(x => x.id))
@ -39,12 +40,12 @@ const UserListMenu = {
methods: { methods: {
toggleList (listId) { toggleList (listId) {
if (this.inListsSet.has(listId)) { if (this.inListsSet.has(listId)) {
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId }).then((response) => { useListsStore().removeListAccount({ accountId: this.user.id, listId }).then((response) => {
if (!response.ok) { return } if (!response.ok) { return }
this.$store.dispatch('fetchUserInLists', this.user.id) this.$store.dispatch('fetchUserInLists', this.user.id)
}) })
} else { } else {
this.$store.dispatch('addListAccount', { accountId: this.user.id, listId }).then((response) => { useListsStore().addListAccount({ accountId: this.user.id, listId }).then((response) => {
if (!response.ok) { return } if (!response.ok) { return }
this.$store.dispatch('fetchUserInLists', this.user.id) this.$store.dispatch('fetchUserInLists', this.user.id)
}) })

View File

@ -6,7 +6,6 @@ import './lib/event_target_polyfill.js'
import instanceModule from './modules/instance.js' import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js' import statusesModule from './modules/statuses.js'
import listsModule from './modules/lists.js'
import usersModule from './modules/users.js' import usersModule from './modules/users.js'
import apiModule from './modules/api.js' import apiModule from './modules/api.js'
import configModule from './modules/config.js' import configModule from './modules/config.js'
@ -66,7 +65,6 @@ const persistedStateOptions = {
// TODO refactor users/statuses modules, they depend on each other // TODO refactor users/statuses modules, they depend on each other
users: usersModule, users: usersModule,
statuses: statusesModule, statuses: statusesModule,
lists: listsModule,
api: apiModule, api: apiModule,
config: configModule, config: configModule,
serverSideConfig: serverSideConfigModule, serverSideConfig: serverSideConfigModule,

View File

@ -1,10 +1,11 @@
import { useListsStore } from '../../stores/lists.js'
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => { const fetchAndUpdate = ({ store, credentials }) => {
return apiService.fetchLists({ credentials }) return apiService.fetchLists({ credentials })
.then(lists => { .then(lists => {
store.commit('setLists', lists) useListsStore().setLists(lists)
}, () => {}) }, () => {})
.catch(() => {}) .catch(() => {})
} }

116
src/stores/lists.js Normal file
View File

@ -0,0 +1,116 @@
import { defineStore } from 'pinia'
import { remove, find } from 'lodash'
export const defaultState = {
allLists: [],
allListsObject: {}
}
export const getters = {
findListTitle (state) {
return (id) => {
if (!this.allListsObject[id]) return
return this.allListsObject[id].title
}
},
findListAccounts (state) {
return (id) => [...this.allListsObject[id].accountIds]
}
}
export const actions = {
setLists (value) {
this.allLists = value
},
createList ({ title }) {
return window.vuex.state.api.backendInteractor.createList({ title })
.then((list) => {
this.setList({ listId: list.id, title })
return list
})
},
fetchList ({ listId }) {
return window.vuex.state.api.backendInteractor.getList({ listId })
.then((list) => this.setList({ listId: list.id, title: list.title }))
},
fetchListAccounts ({ listId }) {
return window.vuex.state.api.backendInteractor.getListAccounts({ listId })
.then((accountIds) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds = accountIds
})
},
setList ({ listId, title }) {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].title = title
const entry = find(this.allLists, { id: listId })
if (!entry) {
this.allLists.push({ id: listId, title })
} else {
entry.title = title
}
},
setListAccounts ({ listId, accountIds }) {
const saved = this.allListsObject[listId].accountIds || []
const added = accountIds.filter(id => !saved.includes(id))
const removed = saved.filter(id => !accountIds.includes(id))
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds = accountIds
if (added.length > 0) {
window.vuex.state.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
}
if (removed.length > 0) {
window.vuex.state.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
}
},
addListAccount ({ listId, accountId }) {
return window.vuex.state
.api
.backendInteractor
.addAccountsToList({ listId, accountIds: [accountId] })
.then((result) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds.push(accountId)
return result
})
},
removeListAccount ({ listId, accountId }) {
return window.vuex.state
.api
.backendInteractor
.removeAccountsFromList({ listId, accountIds: [accountId] })
.then((result) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
const { accountIds } = this.allListsObject[listId]
const set = new Set(accountIds)
set.delete(accountId)
this.allListsObject[listId].accountIds = [...set]
return result
})
},
deleteList ({ listId }) {
window.vuex.state.api.backendInteractor.deleteList({ listId })
delete this.allListsObject[listId]
remove(this.allLists, list => list.id === listId)
}
}
export const useListsStore = defineStore('lists', {
state: () => (defaultState),
getters,
actions
})

View File

@ -1,83 +0,0 @@
import { cloneDeep } from 'lodash'
import { defaultState, mutations, getters } from '../../../../src/modules/lists.js'
describe('The lists module', () => {
describe('mutations', () => {
it('updates array of all lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', title: 'testList' }
mutations.setLists(state, [list])
expect(state.allLists).to.have.length(1)
expect(state.allLists).to.eql([list])
})
it('adds a new list with a title, updating the title for existing lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', title: 'testList' }
const modList = { id: '1', title: 'anotherTestTitle' }
mutations.setList(state, { listId: list.id, title: list.title })
expect(state.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(list)
mutations.setList(state, { listId: modList.id, title: modList.title })
expect(state.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(modList)
})
it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', accountIds: ['1', '2', '3'] }
const modList = { id: '1', accountIds: ['3', '4', '5'] }
mutations.setListAccounts(state, { listId: list.id, accountIds: list.accountIds })
expect(state.allListsObject[list.id]).to.eql({ accountIds: list.accountIds })
mutations.setListAccounts(state, { listId: modList.id, accountIds: modList.accountIds })
expect(state.allListsObject[modList.id]).to.eql({ accountIds: modList.accountIds })
})
it('deletes a list', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const listId = '1'
mutations.deleteList(state, { listId })
expect(state.allLists).to.have.length(0)
expect(state.allListsObject).to.eql({})
})
})
describe('getters', () => {
it('returns list title', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
expect(getters.findListTitle(state)(id)).to.eql('testList')
})
it('returns list accounts', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
expect(getters.findListAccounts(state)(id)).to.eql(['1', '2', '3'])
})
})
})

View File

@ -0,0 +1,93 @@
import { createPinia, setActivePinia } from 'pinia'
import { useListsStore } from '../../../../src/stores/lists.js'
import { createStore } from 'vuex'
import apiModule from '../../../../src/modules/api.js'
setActivePinia(createPinia())
const store = useListsStore()
window.vuex = createStore({
modules: {
api: apiModule
}
})
describe('The lists store', () => {
describe('actions', () => {
it('updates array of all lists', () => {
store.$reset()
const list = { id: '1', title: 'testList' }
store.setLists([list])
expect(store.allLists).to.have.length(1)
expect(store.allLists).to.eql([list])
})
it('adds a new list with a title, updating the title for existing lists', () => {
store.$reset()
const list = { id: '1', title: 'testList' }
const modList = { id: '1', title: 'anotherTestTitle' }
store.setList({ listId: list.id, title: list.title })
expect(store.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
expect(store.allLists).to.have.length(1)
expect(store.allLists[0]).to.eql(list)
store.setList({ listId: modList.id, title: modList.title })
expect(store.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
expect(store.allLists).to.have.length(1)
expect(store.allLists[0]).to.eql(modList)
})
it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
store.$reset()
const list = { id: '1', accountIds: ['1', '2', '3'] }
const modList = { id: '1', accountIds: ['3', '4', '5'] }
store.setListAccounts({ listId: list.id, accountIds: list.accountIds })
expect(store.allListsObject[list.id].accountIds).to.eql(list.accountIds)
store.setListAccounts({ listId: modList.id, accountIds: modList.accountIds })
expect(store.allListsObject[modList.id].accountIds).to.eql(modList.accountIds)
})
it('deletes a list', () => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
})
const listId = '1'
store.deleteList({ listId })
expect(store.allLists).to.have.length(0)
expect(store.allListsObject).to.eql({})
})
})
describe('getters', () => {
it('returns list title', () => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
})
const id = '1'
expect(store.findListTitle(id)).to.eql('testList')
})
it('returns list accounts', () => {
store.$patch({
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
})
const id = '1'
expect(store.findListAccounts(id)).to.eql(['1', '2', '3'])
})
})
})