feat: preserving state between route changes (#132)

This commit is contained in:
Ayaka Rizumu 2022-11-28 01:34:45 +08:00 committed by GitHub
parent d967520005
commit 6414f2a4e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 63 additions and 10 deletions

30
composables/lifecycle.ts Normal file
View file

@ -0,0 +1,30 @@
import type { ComponentInternalInstance } from 'vue'
import { onActivated, onDeactivated, ref } from 'vue'
/**
* ### Whether the current component is running in the background
*
* for handling problems caused by the keepalive function
*/
export function useDeactivated() {
const deactivated = ref(false)
onActivated(() => deactivated.value = false)
onDeactivated(() => deactivated.value = true)
return deactivated
}
/**
* ### When the component is restored from the background
*
* for handling problems caused by the keepalive function
*/
export function onReactivated(hook: Function, target?: ComponentInternalInstance | null): void {
const initial = ref(true)
onActivated(() => {
if (initial.value)
return
hook()
}, target)
onDeactivated(() => initial.value = false)
}

View file

@ -1,4 +1,5 @@
import type { Paginator } from 'masto' import type { Paginator } from 'masto'
import { useDeactivated } from './lifecycle'
import type { PaginatorState } from '~/types' import type { PaginatorState } from '~/types'
export function usePaginator<T>(paginator: Paginator<any, T[]>) { export function usePaginator<T>(paginator: Paginator<any, T[]>) {
@ -10,6 +11,7 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
const bound = reactive(useElementBounding(endAnchor)) const bound = reactive(useElementBounding(endAnchor))
const isInScreen = $computed(() => bound.top < window.innerHeight * 2) const isInScreen = $computed(() => bound.top < window.innerHeight * 2)
const error = ref<unknown | undefined>() const error = ref<unknown | undefined>()
const deactivated = useDeactivated()
async function loadNext() { async function loadNext() {
if (state.value !== 'idle') if (state.value !== 'idle')
@ -44,7 +46,12 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
watch( watch(
() => isInScreen, () => isInScreen,
() => { () => {
if (isInScreen && state.value === 'idle') if (
isInScreen
&& state.value === 'idle'
// No new content is loaded when the keepAlive page enters the background
&& deactivated.value === false
)
loadNext() loadNext()
}, },
{ immediate: true }, { immediate: true },

View file

@ -53,4 +53,7 @@ export default defineNuxtConfig({
translateApi: '', translateApi: '',
}, },
}, },
app: {
keepalive: true,
},
}) })

View file

@ -2,12 +2,12 @@
import type { ComponentPublicInstance } from 'vue' import type { ComponentPublicInstance } from 'vue'
const route = useRoute() const route = useRoute()
const id = $computed(() => route.params.status as string) const id = $(computedEager(() => route.params.status as string))
const main = ref<ComponentPublicInstance | null>(null) const main = ref<ComponentPublicInstance | null>(null)
let bottomSpace = $ref(0) let bottomSpace = $ref(0)
const status = window.history.state?.status ?? await fetchStatus(id) const { data: status, refresh: refreshStatus } = useAsyncData(async () => window.history.state?.status ?? await fetchStatus(id))
const { data: context, pending } = useAsyncData(`context:${id}`, () => useMasto().statuses.fetchContext(id)) const { data: context, pending, refresh: refreshContext } = useAsyncData(`context:${id}`, () => useMasto().statuses.fetchContext(id))
function scrollTo() { function scrollTo() {
const statusElement = unrefElement(main) const statusElement = unrefElement(main)
@ -27,6 +27,13 @@ if (pending) {
scrollTo() scrollTo()
}) })
} }
onReactivated(() => {
// Silently update data when reentering the page
// The user will see the previous content first, and any changes will be updated to the UI when the request is completed
refreshStatus()
refreshContext()
})
</script> </script>
<template> <template>

View file

@ -1,14 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
const params = useRoute().params const params = useRoute().params
const accountName = $computed(() => toShortHandle(params.account as string)) const accountName = $(computedEager(() => toShortHandle(params.account as string)))
const account = await fetchAccountByName(accountName).catch(() => null) const { data: account, refresh } = $(await useAsyncData(() => fetchAccountByName(accountName).catch(() => null)))
if (account) { if (account) {
useHead({ useHead({
title: () => `${getDisplayName(account)} (@${account.acct})`, title: () => `${getDisplayName(account)} (@${account.acct})`,
}) })
} }
onReactivated(() => {
// Silently update data when reentering the page
// The user will see the previous content first, and any changes will be updated to the UI when the request is completed
refresh()
})
</script> </script>
<template> <template>

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const params = useRoute().params const params = useRoute().params
const accountName = $computed(() => params.account as string) const accountName = $(computedEager(() => params.account as string))
const account = await fetchAccountByName(accountName) const account = await fetchAccountByName(accountName)
const paginator = account ? useMasto().accounts.getFollowersIterable(account.id, {}) : null const paginator = account ? useMasto().accounts.getFollowersIterable(account.id, {}) : null

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const params = useRoute().params const params = useRoute().params
const accountName = $computed(() => params.account as string) const accountName = $(computedEager(() => params.account as string))
const account = await fetchAccountByName(accountName) const account = await fetchAccountByName(accountName)
const paginator = account ? useMasto().accounts.getFollowingIterable(account.id, {}) : null const paginator = account ? useMasto().accounts.getFollowingIterable(account.id, {}) : null

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const params = useRoute().params const params = useRoute().params
const accountName = $computed(() => params.account as string) const accountName = $(computedEager(() => params.account as string))
const account = await fetchAccountByName(accountName) const account = await fetchAccountByName(accountName)
const tabNames = ['Posts', 'Posts & replies', 'Media'] as const const tabNames = ['Posts', 'Posts & replies', 'Media'] as const

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const params = useRoute().params const params = useRoute().params
const tag = $computed(() => params.tag as string) const tag = $(computedEager(() => params.tag as string))
const paginator = useMasto().timelines.getHashtagIterable(tag) const paginator = useMasto().timelines.getHashtagIterable(tag)