1
0
mirror of https://github.com/h3poteto/whalebird-desktop synced 2024-12-10 16:09:23 +01:00

[refactor] Remove favourites timeline store

This commit is contained in:
AkiraFukushima 2023-01-29 22:15:25 +09:00
parent 5ec2a48571
commit 273b3c7d13
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
4 changed files with 78 additions and 176 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<div id="favourites"> <div id="favourites">
<div></div> <div style="width: 100%; height: 120px" v-loading="loading" :element-loading-background="backgroundColor" v-if="loading" />
<DynamicScroller :items="favourites" :min-item-size="60" id="scroller" class="scroller" ref="scroller"> <DynamicScroller :items="favourites" :min-item-size="60" id="scroller" class="scroller" ref="scroller" v-else>
<template #default="{ item, index, active }"> <template #default="{ 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
@ -30,10 +30,9 @@ import { useMagicKeys, whenever } from '@vueuse/core'
import { useStore } from '@/store' import { useStore } from '@/store'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useI18next } from 'vue3-i18next' import { useI18next } from 'vue3-i18next'
import { Entity } from 'megalodon' import parse from 'parse-link-header'
import generator, { Entity, MegalodonInterface } from 'megalodon'
import Toot from '@/components/organisms/Toot.vue' import Toot from '@/components/organisms/Toot.vue'
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Favourites'
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu' import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
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 { LocalAccount } from '~/src/types/localAccount'
@ -45,12 +44,10 @@ export default defineComponent({
name: 'favourites', name: 'favourites',
components: { Toot }, components: { Toot },
setup() { setup() {
const space = 'TimelineSpace/Contents/Favourites'
const store = useStore() const store = useStore()
const route = useRoute() const route = useRoute()
const i18n = useI18next() const i18n = useI18next()
const heading = ref<boolean>(false)
const focusedId = ref<string | null>(null) const focusedId = ref<string | null>(null)
const scroller = ref<any>() const scroller = ref<any>()
const { j, k, Ctrl_r } = useMagicKeys() const { j, k, Ctrl_r } = useMagicKeys()
@ -58,41 +55,53 @@ export default defineComponent({
const win = (window as any) as MyWindow const win = (window as any) as MyWindow
const id = computed(() => parseInt(route.params.id as string)) const id = computed(() => parseInt(route.params.id as string))
const loading = ref(false)
const lazyLoading = ref(false) const lazyLoading = ref(false)
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({ const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
account: null, account: null,
server: null server: null
}) })
const client = ref<MegalodonInterface | null>(null)
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload) const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
const favourites = computed(() => store.state.TimelineSpace.Contents.Favourites.favourites) const favourites = ref<Array<Entity.Status>>([])
const nextMaxId = ref<string | null>(null)
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`]) const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
const currentFocusedIndex = computed(() => favourites.value.findIndex(status => focusedId.value === status.uri)) const currentFocusedIndex = computed(() => favourites.value.findIndex(status => focusedId.value === status.uri))
const shortcutEnabled = computed(() => !modalOpened.value) const shortcutEnabled = computed(() => !modalOpened.value)
const userAgent = computed(() => store.state.App.userAgent)
const backgroundColor = computed(() => store.state.App.theme.background_color)
onMounted(async () => { onMounted(async () => {
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value) const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
account.account = a account.account = a
account.server = s account.server = s
client.value = generator(s.sns, s.baseURL, a.accessToken, userAgent.value)
document.getElementById('scroller')?.addEventListener('scroll', onScroll) document.getElementById('scroller')?.addEventListener('scroll', onScroll)
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true) loading.value = true
store try {
.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account) const res = await client.value.getFavourites({ limit: 20 })
.catch(() => { favourites.value = res.data
const link = parse(res.headers.link)
if (link !== null && link.next) {
nextMaxId.value = link.next.max_id
} else {
nextMaxId.value = null
}
} catch (err) {
console.error(err)
ElMessage({ ElMessage({
message: i18n.t('message.favourite_fetch_error'), message: i18n.t('message.favourite_fetch_error'),
type: 'error' type: 'error'
}) })
}) } finally {
.finally(() => { loading.value = false
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, false) }
})
}) })
onUnmounted(() => { onUnmounted(() => {
store.commit(`${space}/${MUTATION_TYPES.UPDATE_FAVOURITES}`, [])
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)
@ -125,12 +134,23 @@ export default defineComponent({
if ( if (
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >= (event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
document.getElementById('scroller')!.scrollHeight - 10 && document.getElementById('scroller')!.scrollHeight - 10 &&
!lazyLoading.value !lazyLoading.value &&
nextMaxId.value
) { ) {
lazyLoading.value = true lazyLoading.value = true
store client.value
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_FAVOURITES}`, account) ?.getFavourites({ limit: 20, max_id: nextMaxId.value })
.catch(() => { .then(res => {
favourites.value = [...favourites.value, ...res.data]
const link = parse(res.headers.link)
if (link !== null && link.next) {
nextMaxId.value = link.next.max_id
} else {
nextMaxId.value = null
}
})
.catch(err => {
console.error(err)
ElMessage({ ElMessage({
message: i18n.t('message.favourite_fetch_error'), message: i18n.t('message.favourite_fetch_error'),
type: 'error' type: 'error'
@ -140,36 +160,43 @@ export default defineComponent({
lazyLoading.value = false lazyLoading.value = false
}) })
} }
// for upper
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
heading.value = false
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
heading.value = true
}
} }
const updateToot = (message: Entity.Status) => { const updateToot = (message: Entity.Status) => {
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message) favourites.value = favourites.value.map(status => {
if (status.id === message.id) {
return message
} else if (status.reblog && status.reblog.id === message.id) {
return Object.assign(status, {
reblog: message
})
}
return status
})
} }
const deleteToot = (id: string) => { const deleteToot = (id: string) => {
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, id) favourites.value = favourites.value.filter(status => {
if (status.reblog !== null && status.reblog.id === id) {
return false
} else {
return status.id !== id
}
})
} }
const reload = async () => { const reload = async () => {
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true) store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
try { try {
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account).catch(() => { const res = await client.value!.getFavourites({ limit: 20 })
ElMessage({ favourites.value = res.data
message: i18n.t('message.favourite_fetch_error'), const link = parse(res.headers.link)
type: 'error' if (link !== null && link.next) {
}) nextMaxId.value = link.next.max_id
}) } else {
nextMaxId.value = null
}
} finally { } finally {
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false) store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
} }
} }
const upper = () => {
scroller.value.scrollToItem(0)
focusedId.value = null
}
const focusNext = () => { const focusNext = () => {
if (currentFocusedIndex.value === -1) { if (currentFocusedIndex.value === -1) {
focusedId.value = favourites.value[0].uri focusedId.value = favourites.value[0].uri
@ -189,15 +216,15 @@ export default defineComponent({
} }
return { return {
loading,
favourites, favourites,
backgroundColor,
scroller, scroller,
focusedId, focusedId,
modalOpened, modalOpened,
updateToot, updateToot,
deleteToot, deleteToot,
focusToot, focusToot,
heading,
upper,
account account
} }
} }

View File

@ -54,6 +54,7 @@ export default defineComponent({
const focusedId = ref<string | null>(null) const focusedId = ref<string | null>(null)
const scroller = ref<any>(null) const scroller = ref<any>(null)
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
const lazyLoading = ref(false)
const heading = ref(true) const heading = ref(true)
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({ const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
account: null, account: null,
@ -129,9 +130,11 @@ export default defineComponent({
const onScroll = (event: Event) => { const onScroll = (event: Event) => {
if ( if (
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >= (event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
document.getElementById('scroller')!.scrollHeight - 10 document.getElementById('scroller')!.scrollHeight - 10 &&
!lazyLoading.value
) { ) {
const lastStatus = statuses.value[statuses.value.length - 1] const lastStatus = statuses.value[statuses.value.length - 1]
lazyLoading.value = true
client.value client.value
?.getPublicTimeline({ max_id: lastStatus.id, limit: 20 }) ?.getPublicTimeline({ max_id: lastStatus.id, limit: 20 })
.then(res => { .then(res => {
@ -144,6 +147,9 @@ 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) {
@ -174,10 +180,6 @@ export default defineComponent({
} }
}) })
} }
const upper = () => {
scroller.value.scrollToItem(0)
focusedId.value = null
}
const focusNext = () => { const focusNext = () => {
if (currentFocusedIndex.value === -1) { if (currentFocusedIndex.value === -1) {
focusedId.value = statuses.value[0].uri + statuses.value[0].id focusedId.value = statuses.value[0].uri + statuses.value[0].id
@ -207,7 +209,6 @@ export default defineComponent({
deleteToot, deleteToot,
focusToot, focusToot,
heading, heading,
upper,
account, account,
backgroundColor backgroundColor
} }

View File

@ -1,6 +1,5 @@
import Home, { HomeState } from './Contents/Home' import Home, { HomeState } from './Contents/Home'
import Notifications, { NotificationsState } from './Contents/Notifications' import Notifications, { NotificationsState } from './Contents/Notifications'
import Favourites, { FavouritesState } from './Contents/Favourites'
import Bookmarks, { BookmarksState } from './Contents/Bookmarks' import Bookmarks, { BookmarksState } from './Contents/Bookmarks'
import Local, { LocalState } from './Contents/Local' import Local, { LocalState } from './Contents/Local'
import Search, { SearchModuleState } from './Contents/Search' import Search, { SearchModuleState } from './Contents/Search'
@ -19,7 +18,6 @@ type ContentsModule = {
Home: HomeState Home: HomeState
Notifications: NotificationsState Notifications: NotificationsState
DirectMessages: DirectMessagesState DirectMessages: DirectMessagesState
Favourites: FavouritesState
Bookmarks: BookmarksState Bookmarks: BookmarksState
Local: LocalState Local: LocalState
Search: SearchModuleState Search: SearchModuleState
@ -60,7 +58,6 @@ const Contents: Module<ContentsState, RootState> = {
modules: { modules: {
Home, Home,
Notifications, Notifications,
Favourites,
Bookmarks, Bookmarks,
Local, Local,
DirectMessages, DirectMessages,

View File

@ -1,123 +0,0 @@
import generator, { Entity } from 'megalodon'
import parse from 'parse-link-header'
import { Module, MutationTree, ActionTree } from 'vuex'
import { RootState } from '@/store'
import { LocalAccount } from '~/src/types/localAccount'
import { LocalServer } from '~/src/types/localServer'
export type FavouritesState = {
favourites: Array<Entity.Status>
maxId: string | null
}
const state = (): FavouritesState => ({
favourites: [],
maxId: null
})
export const MUTATION_TYPES = {
UPDATE_FAVOURITES: 'updateFavourites',
INSERT_FAVOURITES: 'insertFavourites',
UPDATE_TOOT: 'updateToot',
DELETE_TOOT: 'deleteToot',
CHANGE_MAX_ID: 'changeMaxId'
}
const mutations: MutationTree<FavouritesState> = {
[MUTATION_TYPES.UPDATE_FAVOURITES]: (state, favourites: Array<Entity.Status>) => {
state.favourites = favourites
},
[MUTATION_TYPES.INSERT_FAVOURITES]: (state, favourites: Array<Entity.Status>) => {
state.favourites = state.favourites.concat(favourites)
},
[MUTATION_TYPES.UPDATE_TOOT]: (state, message: Entity.Status) => {
state.favourites = state.favourites.map(toot => {
if (toot.id === message.id) {
return message
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
// When user reblog/favourite a reblogged toot, target message is a original toot.
// So, a message which is received now is original toot.
const reblog = {
reblog: message
}
return Object.assign(toot, reblog)
} else {
return toot
}
})
},
[MUTATION_TYPES.DELETE_TOOT]: (state, message: Entity.Status) => {
state.favourites = state.favourites.filter(toot => {
if (toot.reblog !== null && toot.reblog.id === message.id) {
return false
} else {
return toot.id !== message.id
}
})
},
[MUTATION_TYPES.CHANGE_MAX_ID]: (state, id: string | null) => {
state.maxId = id
}
}
export const ACTION_TYPES = {
FETCH_FAVOURITES: 'fetchFavourites',
LAZY_FETCH_FAVOURITES: 'lazyFetchFavourites'
}
const actions: ActionTree<FavouritesState, RootState> = {
[ACTION_TYPES.FETCH_FAVOURITES]: async (
{ commit, rootState },
req: { account: LocalAccount; server: LocalServer }
): Promise<Array<Entity.Status>> => {
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
const res = await client.getFavourites({ limit: 20 })
commit(MUTATION_TYPES.UPDATE_FAVOURITES, res.data)
// Parse link header
try {
const link = parse(res.headers.link)
if (link !== null && link.next) {
commit(MUTATION_TYPES.CHANGE_MAX_ID, link.next.max_id)
} else {
commit(MUTATION_TYPES.CHANGE_MAX_ID, null)
}
} catch (err) {
commit(MUTATION_TYPES.CHANGE_MAX_ID, null)
console.error(err)
}
return res.data
},
lazyFetchFavourites: async (
{ state, commit, rootState },
req: { account: LocalAccount; server: LocalServer }
): Promise<Array<Entity.Status> | null> => {
if (!state.maxId) {
return Promise.resolve(null)
}
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
const res = await client.getFavourites({ max_id: state.maxId, limit: 20 })
commit(MUTATION_TYPES.INSERT_FAVOURITES, res.data)
// Parse link header
try {
const link = parse(res.headers.link)
if (link !== null && link.next) {
commit(MUTATION_TYPES.CHANGE_MAX_ID, link.next.max_id)
} else {
commit(MUTATION_TYPES.CHANGE_MAX_ID, null)
}
} catch (err) {
commit(MUTATION_TYPES.CHANGE_MAX_ID, null)
console.error(err)
}
return res.data
}
}
const Favourites: Module<FavouritesState, RootState> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions
}
export default Favourites