commit: 8eff0814681190619acf1a185f5f429c81ee4479
parent ad7d47f44076b7b0e7234c1cc70fd350dac5d842
Author: Sean King <seanking2919@protonmail.com>
Date:   Thu,  6 Apr 2023 22:13:30 -0600
Migrates lists module to store
Diffstat:
13 files changed, 248 insertions(+), 109 deletions(-)
diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js
@@ -1,3 +1,4 @@
+import { useListsStore } from '../../stores/lists'
 import ListsCard from '../lists_card/lists_card.vue'
 
 const Lists = {
@@ -11,7 +12,7 @@ const Lists = {
   },
   computed: {
     lists () {
-      return this.$store.state.lists.allLists
+      return useListsStore().allLists
     }
   },
   methods: {
diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js
@@ -1,4 +1,5 @@
 import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
 import BasicUserCard from '../basic_user_card/basic_user_card.vue'
 import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
 import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
@@ -10,6 +11,7 @@ import {
   faChevronLeft
 } from '@fortawesome/free-solid-svg-icons'
 import { useInterfaceStore } from '../../stores/interface'
+import { useListsStore } from '../../stores/lists'
 
 library.add(
   faSearch,
@@ -38,12 +40,12 @@ const ListsNew = {
   },
   created () {
     if (!this.id) return
-    this.$store.dispatch('fetchList', { listId: this.id })
+    useListsStore().fetchList({ listId: this.id })
       .then(() => {
         this.title = this.findListTitle(this.id)
         this.titleDraft = this.title
       })
-    this.$store.dispatch('fetchListAccounts', { listId: this.id })
+    useListsStore().fetchListAccounts({ listId: this.id })
       .then(() => {
         this.membersUserIds = this.findListAccounts(this.id)
         this.membersUserIds.forEach(userId => {
@@ -65,7 +67,8 @@ const ListsNew = {
     ...mapState({
       currentUser: state => state.users.currentUser
     }),
-    ...mapGetters(['findUser', 'findListTitle', 'findListAccounts'])
+    ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']),
+    ...mapGetters(['findUser'])
   },
   methods: {
     onInput () {
@@ -96,10 +99,10 @@ const ListsNew = {
       return this.addedUserIds.has(user.id)
     },
     addUser (user) {
-      this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id })
+      useListsStore().addListAccount({ accountId: user.id, listId: this.id })
     },
     removeUser (userId) {
-      this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
+      useListsStore().removeListAccount({ accountId: userId, listId: this.id })
     },
     onSearchLoading (results) {
       this.searchLoading = true
@@ -112,17 +115,16 @@ const ListsNew = {
       this.searchUserIds = results
     },
     updateListTitle () {
-      this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft })
+      useListsStore().setList({ listId: this.id, title: this.titleDraft })
         .then(() => {
           this.title = this.findListTitle(this.id)
         })
     },
     createList () {
-      this.$store.dispatch('createList', { title: this.titleDraft })
+      useListsStore().createList({ title: this.titleDraft })
         .then((list) => {
-          return this
-            .$store
-            .dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
+          return useListsStore()
+            .setListAccounts({ listId: list.id, accountIds: [...this.addedUserIds] })
             .then(() => list.id)
         })
         .then((listId) => {
@@ -137,7 +139,7 @@ const ListsNew = {
         })
     },
     deleteList () {
-      this.$store.dispatch('deleteList', { listId: this.id })
+      useListsStore().deleteList({ listId: this.id })
       this.$router.push({ name: 'lists' })
     }
   }
diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js
@@ -1,6 +1,8 @@
 import { mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
 import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
 import { getListEntries } from 'src/components/navigation/filter.js'
+import { useListsStore } from '../../stores/lists'
 
 export const ListsMenuContent = {
   props: [
@@ -10,8 +12,10 @@ export const ListsMenuContent = {
     NavigationEntry
   },
   computed: {
+    ...mapPiniaState(useListsStore, {
+      lists: getListEntries
+    }),
     ...mapState({
-      lists: getListEntries,
       currentUser: state => state.users.currentUser,
       privateMode: state => state.instance.private,
       federating: state => state.instance.federating
diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js
@@ -1,3 +1,4 @@
+import { useListsStore } from '../../stores/lists'
 import Timeline from '../timeline/timeline.vue'
 const ListsTimeline = {
   data () {
@@ -17,14 +18,14 @@ const ListsTimeline = {
         this.listId = route.params.id
         this.$store.dispatch('stopFetchingTimeline', '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 })
       }
     }
   },
   created () {
     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 })
   },
   unmounted () {
diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js
@@ -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,
   routeObject: { name: 'lists-timeline', params: { id: list.id } },
   labelRaw: list.title,
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
@@ -1,4 +1,5 @@
 import { mapState } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
 import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
 import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
 
@@ -14,6 +15,7 @@ import {
   faStream,
   faList
 } from '@fortawesome/free-solid-svg-icons'
+import { useListsStore } from '../../stores/lists'
 
 library.add(
   faUsers,
@@ -38,8 +40,10 @@ const NavPanel = {
     getters () {
       return this.$store.getters
     },
+    ...mapPiniaState(useListsStore, {
+      lists: getListEntries
+    }),
     ...mapState({
-      lists: getListEntries,
       currentUser: state => state.users.currentUser,
       followRequestCount: state => state.api.followRequests.length,
       privateMode: state => state.instance.private,
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
@@ -9,6 +9,7 @@ import {
   faChevronDown
 } from '@fortawesome/free-solid-svg-icons'
 import { useInterfaceStore } from '../../stores/interface'
+import { useListsStore } from '../../stores/lists'
 
 library.add(faChevronDown)
 
@@ -87,7 +88,7 @@ const TimelineMenu = {
         return '#' + this.$route.params.tag
       }
       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]
       return i18nkey ? this.$t(i18nkey) : route
diff --git a/src/components/user_list_menu/user_list_menu.js b/src/components/user_list_menu/user_list_menu.js
@@ -1,9 +1,10 @@
 import { library } from '@fortawesome/fontawesome-svg-core'
 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 Popover from '../popover/popover.vue'
+import { useListsStore } from '../../stores/lists'
 
 library.add(faChevronRight)
 
@@ -22,8 +23,8 @@ const UserListMenu = {
     this.$store.dispatch('fetchUserInLists', this.user.id)
   },
   computed: {
-    ...mapState({
-      allLists: state => state.lists.allLists
+    ...mapState(useListsStore, {
+      allLists: store => store.allLists
     }),
     inListsSet () {
       return new Set(this.user.inLists.map(x => x.id))
@@ -39,12 +40,12 @@ const UserListMenu = {
   methods: {
     toggleList (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 }
           this.$store.dispatch('fetchUserInLists', this.user.id)
         })
       } 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 }
           this.$store.dispatch('fetchUserInLists', this.user.id)
         })
diff --git a/src/main.js b/src/main.js
@@ -6,7 +6,6 @@ import './lib/event_target_polyfill.js'
 
 import instanceModule from './modules/instance.js'
 import statusesModule from './modules/statuses.js'
-import listsModule from './modules/lists.js'
 import usersModule from './modules/users.js'
 import apiModule from './modules/api.js'
 import configModule from './modules/config.js'
@@ -66,7 +65,6 @@ const persistedStateOptions = {
       // TODO refactor users/statuses modules, they depend on each other
       users: usersModule,
       statuses: statusesModule,
-      lists: listsModule,
       api: apiModule,
       config: configModule,
       serverSideConfig: serverSideConfigModule,
diff --git a/src/services/lists_fetcher/lists_fetcher.service.js b/src/services/lists_fetcher/lists_fetcher.service.js
@@ -1,10 +1,11 @@
+import { useListsStore } from '../../stores/lists.js'
 import apiService from '../api/api.service.js'
 import { promiseInterval } from '../promise_interval/promise_interval.js'
 
 const fetchAndUpdate = ({ store, credentials }) => {
   return apiService.fetchLists({ credentials })
     .then(lists => {
-      store.commit('setLists', lists)
+      useListsStore().setLists(lists)
     }, () => {})
     .catch(() => {})
 }
diff --git a/src/stores/lists.js b/src/stores/lists.js
@@ -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
+})
diff --git a/test/unit/specs/modules/lists.spec.js b/test/unit/specs/modules/lists.spec.js
@@ -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'])
-    })
-  })
-})
diff --git a/test/unit/specs/stores/lists.spec.js b/test/unit/specs/stores/lists.spec.js
@@ -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'])
+    })
+  })
+})