[refactor] List store

This commit is contained in:
AkiraFukushima 2023-01-12 01:12:11 +09:00
parent 7e0fc26f8f
commit 0adc0dd8ac
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
3 changed files with 72 additions and 334 deletions

View File

@ -1,235 +0,0 @@
import { Entity } from 'megalodon'
import Show, { ShowState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show'
const account: Entity.Account = {
id: '1',
username: 'h3poteto',
acct: 'h3poteto@pleroma.io',
display_name: 'h3poteto',
locked: false,
created_at: '2019-03-26T21:30:32',
followers_count: 10,
following_count: 10,
statuses_count: 100,
note: 'engineer',
url: 'https://pleroma.io',
avatar: '',
avatar_static: '',
header: '',
header_static: '',
emojis: [],
moved: null,
fields: null,
bot: false
}
const status1: Entity.Status = {
id: '1',
uri: 'http://example.com',
url: 'http://example.com',
account: account,
in_reply_to_id: null,
in_reply_to_account_id: null,
reblog: null,
content: 'hoge',
plain_content: 'hoge',
created_at: '2019-03-26T21:40:32',
emojis: [],
replies_count: 0,
reblogs_count: 0,
favourites_count: 0,
reblogged: null,
favourited: null,
muted: null,
sensitive: false,
spoiler_text: '',
visibility: 'public',
media_attachments: [],
mentions: [],
tags: [],
card: null,
poll: null,
application: {
name: 'Web'
} as Entity.Application,
language: null,
pinned: null,
emoji_reactions: [],
bookmarked: false,
quote: false
}
const status2: Entity.Status = {
id: '2',
uri: 'http://example.com',
url: 'http://example.com',
account: account,
in_reply_to_id: null,
in_reply_to_account_id: null,
reblog: null,
content: 'fuga',
plain_content: 'fuga',
created_at: '2019-03-26T21:40:32',
emojis: [],
replies_count: 0,
reblogs_count: 0,
favourites_count: 0,
reblogged: null,
favourited: null,
muted: null,
sensitive: false,
spoiler_text: '',
visibility: 'public',
media_attachments: [],
mentions: [],
tags: [],
card: null,
poll: null,
application: {
name: 'Web'
} as Entity.Application,
language: null,
pinned: null,
emoji_reactions: [],
bookmarked: false,
quote: false
}
const rebloggedStatus: Entity.Status = {
id: '3',
uri: 'http://example.com',
url: 'http://example.com',
account: account,
in_reply_to_id: null,
in_reply_to_account_id: null,
reblog: status1,
content: 'fuga',
plain_content: 'fuga',
created_at: '2019-03-31T21:40:32',
emojis: [],
replies_count: 0,
reblogs_count: 0,
favourites_count: 0,
reblogged: null,
favourited: null,
muted: null,
sensitive: false,
spoiler_text: '',
visibility: 'public',
media_attachments: [],
mentions: [],
tags: [],
card: null,
poll: null,
application: {
name: 'Web'
} as Entity.Application,
language: null,
pinned: null,
emoji_reactions: [],
bookmarked: false,
quote: false
}
describe('TimelineSpace/Contents/Lists/Show', () => {
describe('mutations', () => {
let state: ShowState
describe('deleteToot', () => {
describe('message is not reblogged', () => {
beforeEach(() => {
state = {
lazyLoading: false,
heading: true,
timeline: [status2, status1],
unreads: []
}
})
it('should be deleted', () => {
Show.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
expect(state.timeline).toEqual([status2])
})
})
describe('message is reblogged', () => {
beforeEach(() => {
state = {
lazyLoading: false,
heading: true,
timeline: [status2, rebloggedStatus],
unreads: []
}
})
it('should be deleted', () => {
Show.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
expect(state.timeline).toEqual([status2])
})
})
})
describe('appendTimeline', () => {
describe('heading', () => {
describe('normal', () => {
beforeEach(() => {
state = {
lazyLoading: false,
heading: true,
timeline: [status2, status1],
unreads: []
}
})
it('should be updated timeline', () => {
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
})
})
describe('duplicated status', () => {
beforeEach(() => {
state = {
lazyLoading: false,
heading: true,
timeline: [rebloggedStatus, status2, status1],
unreads: []
}
})
it('should not be updated timeline', () => {
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
})
})
})
describe('not heading', () => {
describe('normal', () => {
beforeEach(() => {
state = {
lazyLoading: false,
heading: false,
timeline: [status2, status1],
unreads: []
}
})
it('should not be updated timeline', () => {
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
expect(state.timeline).toEqual([status2, status1])
expect(state.unreads).toEqual([rebloggedStatus])
})
})
describe('duplicated status', () => {
beforeEach(() => {
state = {
lazyLoading: false,
heading: false,
timeline: [rebloggedStatus, status2, status1],
unreads: []
}
})
it('should not be updated timeline', () => {
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
})
})
})
})
})
})

View File

@ -1,14 +1,16 @@
<template> <template>
<div name="list" class="list-timeline"> <div name="list" class="list-timeline">
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller"> <DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<template v-slot="{ item, index, active }"> <template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true"> <DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<toot <toot
v-if="account.account && account.server"
:message="item" :message="item"
:focused="item.uri + item.id === focusedId" :focused="item.uri + item.id === focusedId"
:overlaid="modalOpened" :overlaid="modalOpened"
:filters="[]" :filters="[]"
:account="account.account"
:server="account.server"
v-on:update="updateToot" v-on:update="updateToot"
v-on:delete="deleteToot" v-on:delete="deleteToot"
@focusRight="focusSidebar" @focusRight="focusSidebar"
@ -27,7 +29,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, toRefs, ref, computed, onMounted, watch, onBeforeUnmount, onUnmounted } from 'vue' import { defineComponent, toRefs, ref, computed, onMounted, watch, onBeforeUnmount, onUnmounted, reactive } from 'vue'
import { logicAnd } from '@vueuse/math' import { logicAnd } from '@vueuse/math'
import { useMagicKeys, whenever } from '@vueuse/core' import { useMagicKeys, whenever } from '@vueuse/core'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
@ -40,6 +42,10 @@ import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Conte
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu' import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show' import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show'
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace' import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
import { LocalAccount } from '~/src/types/localAccount'
import { LocalServer } from '~/src/types/localServer'
import { MyWindow } from '~/src/types/global'
import { useRoute } from 'vue-router'
export default defineComponent({ export default defineComponent({
name: 'list', name: 'list',
@ -48,24 +54,35 @@ export default defineComponent({
setup(props) { setup(props) {
const space = 'TimelineSpace/Contents/Lists/Show' const space = 'TimelineSpace/Contents/Lists/Show'
const store = useStore() const store = useStore()
const route = useRoute()
const i18n = useI18next() const i18n = useI18next()
const { j, k, Ctrl_r } = useMagicKeys() const { j, k, Ctrl_r } = useMagicKeys()
const win = (window as any) as MyWindow
const id = computed(() => parseInt(route.params.id as string))
const { list_id } = toRefs(props) const { list_id } = toRefs(props)
const focusedId = ref<string | null>(null) const focusedId = ref<string | null>(null)
const scroller = ref<any>(null) const scroller = ref<any>(null)
const lazyLoading = ref<boolean>(false)
const heading = ref<boolean>(true)
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
account: null,
server: null
})
const timeline = computed(() => store.state.TimelineSpace.Contents.Lists.Show.timeline) const timeline = computed(() => store.state.TimelineSpace.Contents.Lists.Show.timeline)
const unreads = computed(() => store.state.TimelineSpace.Contents.Lists.Show.unreads)
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Lists.Show.lazyLoading)
const heading = computed(() => store.state.TimelineSpace.Contents.Lists.Show.heading)
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar) const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload) const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`]) const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id)) const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
const shortcutEnabled = computed(() => !modalOpened.value) const shortcutEnabled = computed(() => !modalOpened.value)
onMounted(() => { onMounted(async () => {
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
account.account = a
account.server = s
document.getElementById('scroller')?.addEventListener('scroll', onScroll) document.getElementById('scroller')?.addEventListener('scroll', onScroll)
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true) store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
load().finally(() => { load().finally(() => {
@ -87,9 +104,9 @@ export default defineComponent({
}) })
watch(focusedId, (newVal, _oldVal) => { watch(focusedId, (newVal, _oldVal) => {
if (newVal && heading.value) { if (newVal && heading.value) {
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false) heading.value = false
} else if (newVal === null && !heading.value) { } else if (newVal === null && !heading.value) {
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true) heading.value = true
} }
}) })
whenever(logicAnd(j, shortcutEnabled), () => { whenever(logicAnd(j, shortcutEnabled), () => {
@ -110,9 +127,7 @@ export default defineComponent({
store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`) store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
}) })
onUnmounted(() => { onUnmounted(() => {
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true) heading.value = true
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
const el = document.getElementById('scroller') const el = document.getElementById('scroller')
if (el !== undefined && el !== null) { if (el !== undefined && el !== null) {
el.removeEventListener('scroll', onScroll) el.removeEventListener('scroll', onScroll)
@ -123,14 +138,18 @@ export default defineComponent({
const load = async () => { const load = async () => {
await store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`) await store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
try { try {
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE}`, list_id.value) await store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE}`, {
listID: list_id.value,
account: account.account,
server: account.server
})
} catch (err) { } catch (err) {
ElMessage({ ElMessage({
message: i18n.t('message.timeline_fetch_error'), message: i18n.t('message.timeline_fetch_error'),
type: 'error' type: 'error'
}) })
} }
store.dispatch(`${space}/${ACTION_TYPES.START_STREAMING}`, list_id.value).catch(() => { store.dispatch(`${space}/${ACTION_TYPES.START_STREAMING}`, { listID: list_id.value, account: account.account }).catch(() => {
ElMessage({ ElMessage({
message: i18n.t('message.start_streaming_error'), message: i18n.t('message.start_streaming_error'),
type: 'error' type: 'error'
@ -144,10 +163,13 @@ export default defineComponent({
document.getElementById('scroller')!.scrollHeight - 10 && document.getElementById('scroller')!.scrollHeight - 10 &&
!lazyLoading !lazyLoading
) { ) {
lazyLoading.value = true
store store
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, { .dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
list_id: list_id.value, list_id: list_id.value,
status: timeline.value[timeline.value.length - 1] status: timeline.value[timeline.value.length - 1],
account: account.account,
server: account.server
}) })
.catch(() => { .catch(() => {
ElMessage({ ElMessage({
@ -155,13 +177,15 @@ export default defineComponent({
type: 'error' type: 'error'
}) })
}) })
.finally(() => {
lazyLoading.value = false
})
} }
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) { if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false) heading.value = false
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) { } else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true) heading.value = true
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
} }
} }
const reload = async () => { const reload = async () => {
@ -227,7 +251,7 @@ export default defineComponent({
openSideBar, openSideBar,
heading, heading,
upper, upper,
unreads account
} }
} }
}) })

View File

@ -1,65 +1,38 @@
import generator, { Entity } from 'megalodon' import generator, { Entity } from 'megalodon'
import { Module, MutationTree, ActionTree } from 'vuex' import { Module, MutationTree, ActionTree } from 'vuex'
import { RootState } from '@/store' import { RootState } from '@/store'
import { LoadPositionWithList } from '@/types/loadPosition'
import { MyWindow } from '~/src/types/global' import { MyWindow } from '~/src/types/global'
import { LocalAccount } from '~/src/types/localAccount'
import { LocalServer } from '~/src/types/localServer'
const win = (window as any) as MyWindow const win = (window as any) as MyWindow
export type ShowState = { export type ShowState = {
timeline: Array<Entity.Status> timeline: Array<Entity.Status>
lazyLoading: boolean
heading: boolean
unreads: Array<Entity.Status>
} }
const state = (): ShowState => ({ const state = (): ShowState => ({
timeline: [], timeline: []
lazyLoading: false,
heading: true,
unreads: []
}) })
export const MUTATION_TYPES = { export const MUTATION_TYPES = {
CHANGE_HEADING: 'changeHeading',
APPEND_TIMELINE: 'appendTimeline', APPEND_TIMELINE: 'appendTimeline',
UPDATE_TIMELINE: 'updateTimeline', REPLACE_TIMELINE: 'replaceTimeline',
INSERT_TIMELINE: 'insertTimeline', INSERT_TIMELINE: 'insertTimeline',
ARCHIVE_TIMELINE: 'archiveTimeline',
CLEAR_TIMELINE: 'clearTimeline',
UPDATE_TOOT: 'updateToot', UPDATE_TOOT: 'updateToot',
DELETE_TOOT: 'deleteToot', DELETE_TOOT: 'deleteToot'
CHANGE_LAZY_LOADING: 'changeLazyLoading',
MERGE_UNREADS: 'mergeUnreads'
} }
const mutations: MutationTree<ShowState> = { const mutations: MutationTree<ShowState> = {
[MUTATION_TYPES.CHANGE_HEADING]: (state, value: boolean) => {
state.heading = value
},
[MUTATION_TYPES.APPEND_TIMELINE]: (state, update: Entity.Status) => { [MUTATION_TYPES.APPEND_TIMELINE]: (state, update: Entity.Status) => {
// Reject duplicated status in timeline state.timeline = [update].concat(state.timeline)
if (!state.timeline.find(item => item.id === update.id) && !state.unreads.find(item => item.id === update.id)) {
if (state.heading) {
state.timeline = [update].concat(state.timeline)
} else {
state.unreads = [update].concat(state.unreads)
}
}
}, },
[MUTATION_TYPES.UPDATE_TIMELINE]: (state, timeline: Array<Entity.Status>) => { [MUTATION_TYPES.REPLACE_TIMELINE]: (state, timeline: Array<Entity.Status>) => {
state.timeline = timeline state.timeline = timeline
}, },
[MUTATION_TYPES.INSERT_TIMELINE]: (state, messages: Array<Entity.Status>) => { [MUTATION_TYPES.INSERT_TIMELINE]: (state, messages: Array<Entity.Status>) => {
state.timeline = state.timeline.concat(messages) state.timeline = state.timeline.concat(messages)
}, },
[MUTATION_TYPES.ARCHIVE_TIMELINE]: state => {
state.timeline = state.timeline.slice(0, 20)
},
[MUTATION_TYPES.CLEAR_TIMELINE]: state => {
state.timeline = []
state.unreads = []
},
[MUTATION_TYPES.UPDATE_TOOT]: (state, message: Entity.Status) => { [MUTATION_TYPES.UPDATE_TOOT]: (state, message: Entity.Status) => {
state.timeline = state.timeline.map(toot => { state.timeline = state.timeline.map(toot => {
if (toot.id === message.id) { if (toot.id === message.id) {
@ -84,13 +57,6 @@ const mutations: MutationTree<ShowState> = {
return toot.id !== id return toot.id !== id
} }
}) })
},
[MUTATION_TYPES.CHANGE_LAZY_LOADING]: (state, value: boolean) => {
state.lazyLoading = value
},
[MUTATION_TYPES.MERGE_UNREADS]: state => {
state.timeline = state.unreads.slice(0, 80).concat(state.timeline)
state.unreads = []
} }
} }
@ -102,66 +68,49 @@ export const ACTION_TYPES = {
} }
const actions: ActionTree<ShowState, RootState> = { const actions: ActionTree<ShowState, RootState> = {
[ACTION_TYPES.FETCH_TIMELINE]: async ({ commit, rootState }, listID: string): Promise<Array<Entity.Status>> => { [ACTION_TYPES.FETCH_TIMELINE]: async (
const client = generator( { commit, rootState },
rootState.TimelineSpace.server!.sns, req: { listID: string; account: LocalAccount; server: LocalServer }
rootState.TimelineSpace.server!.baseURL, ): Promise<Array<Entity.Status>> => {
rootState.TimelineSpace.account!.accessToken, const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
rootState.App.userAgent const res = await client.getListTimeline(req.listID, { limit: 20 })
) commit(MUTATION_TYPES.REPLACE_TIMELINE, res.data)
const res = await client.getListTimeline(listID, { limit: 20 })
commit(MUTATION_TYPES.UPDATE_TIMELINE, res.data)
return res.data return res.data
}, },
[ACTION_TYPES.START_STREAMING]: ({ state, commit, rootState }, listID: string) => { [ACTION_TYPES.START_STREAMING]: ({ commit }, req: { listID: string; account: LocalAccount }) => {
win.ipcRenderer.on(`update-list-streamings-${rootState.TimelineSpace.account!.id}`, (_, update: Entity.Status) => { win.ipcRenderer.on(`update-list-streamings-${req.account.id}`, (_, update: Entity.Status) => {
commit(MUTATION_TYPES.APPEND_TIMELINE, update) commit(MUTATION_TYPES.APPEND_TIMELINE, update)
if (state.heading && Math.random() > 0.8) {
commit(MUTATION_TYPES.ARCHIVE_TIMELINE)
}
}) })
win.ipcRenderer.on(`delete-list-streamings-${rootState.TimelineSpace.account!.id}`, (_, id: string) => { win.ipcRenderer.on(`delete-list-streamings-${req.account.id}`, (_, id: string) => {
commit(MUTATION_TYPES.DELETE_TOOT, id) commit(MUTATION_TYPES.DELETE_TOOT, id)
}) })
// @ts-ignore // @ts-ignore
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// eslint-disable-line no-unused-vars // eslint-disable-line no-unused-vars
win.ipcRenderer.send('start-list-streaming', { win.ipcRenderer.send('start-list-streaming', {
listId: listID, listId: req.listID,
accountId: rootState.TimelineSpace.account!.id accountId: req.account.id
}) })
}) })
}, },
[ACTION_TYPES.STOP_STREAMING]: ({ rootState }) => { [ACTION_TYPES.STOP_STREAMING]: ({ rootState }) => {
return new Promise(resolve => { return new Promise(resolve => {
win.ipcRenderer.removeAllListeners(`update-list-streamings-${rootState.TimelineSpace.account!.id}`) if (rootState.TimelineSpace.account) {
win.ipcRenderer.removeAllListeners(`delete-list-streamings-${rootState.TimelineSpace.account!.id}`) win.ipcRenderer.removeAllListeners(`update-list-streamings-${rootState.TimelineSpace.account.id}`)
win.ipcRenderer.removeAllListeners(`delete-list-streamings-${rootState.TimelineSpace.account.id}`)
}
resolve(null) resolve(null)
}) })
}, },
[ACTION_TYPES.LAZY_FETCH_TIMELINE]: async ( [ACTION_TYPES.LAZY_FETCH_TIMELINE]: async (
{ state, commit, rootState }, { commit, rootState },
loadPosition: LoadPositionWithList req: { list_id: string; status: Entity.Status; account: LocalAccount; server: LocalServer }
): Promise<Array<Entity.Status> | null> => { ): Promise<Array<Entity.Status> | null> => {
if (state.lazyLoading) { const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
return Promise.resolve(null) return client.getListTimeline(req.list_id, { max_id: req.status.id, limit: 20 }).then(res => {
} commit(MUTATION_TYPES.INSERT_TIMELINE, res.data)
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, true) return res.data
const client = generator( })
rootState.TimelineSpace.server!.sns,
rootState.TimelineSpace.server!.baseURL,
rootState.TimelineSpace.account!.accessToken,
rootState.App.userAgent
)
return client
.getListTimeline(loadPosition.list_id, { max_id: loadPosition.status.id, limit: 20 })
.then(res => {
commit(MUTATION_TYPES.INSERT_TIMELINE, res.data)
return res.data
})
.finally(() => {
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, false)
})
} }
} }