Skip to content

Commit

Permalink
* fix functions within the provided userProvision are losing reacti…
Browse files Browse the repository at this point in the history
…vity of `props.initialPosts.users` @ `<RendererList>`

* now will continue scrolling up the viewport when the top edge of the scrolling post list item element is above the top of the viewport @ `scrollToPostListItem()@index.ts`
@ components/Post/renderers/list

* fix not passing `props.modelValue` to `<select>` @ `components/widgets/<SelectForum>`

* fix always showing `<PlaceholderPostList>` without loading spinner due to the regression made in 61b7973
* fix showing negative `(network|render)Time` when the result data of fetching query key is cached @ `fetchPosts()`
@ `<Post>`
@ fe
  • Loading branch information
n0099 committed Mar 6, 2024
1 parent fd09268 commit d65bec2
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 14 deletions.
9 changes: 5 additions & 4 deletions fe/src/components/Post/renderers/list/RendererList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import type { Reply, SubReply, Thread } from '@/api/post';
import { compareRouteIsNewQuery, setComponentCustomScrollBehaviour } from '@/router';
import type { Modify } from '@/shared';
import { initialTippy } from '@/shared/tippy';
import type { ComputedRef } from 'vue';
import { computed, onMounted, provide } from 'vue';
import type { RouterScrollBehavior } from 'vue-router';
import * as _ from 'lodash-es';
const props = defineProps<{ initialPosts: ApiPosts['response'] }>();
const getUser = baseGetUser(props.initialPosts.users);
const renderUsername = baseRenderUsername(getUser);
const getUser = computed(() => baseGetUser(props.initialPosts.users));
const renderUsername = computed(() => baseRenderUsername(getUser.value));
const userProvision = { getUser, renderUsername };
// export type UserProvision = typeof userProvision;
// will trigger @typescript-eslint/no-unsafe-assignment when `inject<UserProvision>('userProvision')`
export interface UserProvision {
getUser: ReturnType<typeof baseGetUser>,
renderUsername: ReturnType<typeof baseRenderUsername>
getUser: ComputedRef<ReturnType<typeof baseGetUser>>,
renderUsername: ComputedRef<ReturnType<typeof baseRenderUsername>>
}
provide<UserProvision>('userProvision', userProvision);
Expand Down
2 changes: 1 addition & 1 deletion fe/src/components/Post/renderers/list/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const scrollToPostListItem = (el: Element) => {
// and the element reached the top of viewport
const elTop = el.getBoundingClientRect().top;
const replyTitleTopOffset = getReplyTitleTopOffset();
if (Math.abs(elTop) < replyTitleTopOffset + (window.innerHeight * 0.05)) // at most 5dvh tolerance
if (elTop > 0 && Math.abs(elTop) < replyTitleTopOffset + (window.innerHeight * 0.05)) // at most 5dvh tolerance
removeEventListener('scrollend', tryScroll);
else
document.documentElement.scrollBy({ top: elTop - replyTitleTopOffset });
Expand Down
4 changes: 3 additions & 1 deletion fe/src/components/widgets/SelectForum.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<slot v-if="isFetching || isError" :renderer="indicatorsRenderer" name="indicators">
<RenderFunction :renderer="indicatorsRenderer" />
</slot>
<select v-if="!isFetching && isSuccess && data !== undefined"
<select v-if="!isFetching && isSuccess && data !== undefined" v-model="modelValue"
class="form-select form-control" v-bind="$attrs">
<option value="0">未指定</option>
<option v-for="forum in data"
Expand All @@ -13,11 +13,13 @@
<script setup lang="tsx">
import { useApiForums } from '@/api/index';
import RenderFunction from '@/components/RenderFunction';
import type { Fid } from '@/shared';
import type { VNode } from 'vue';
import { computed } from 'vue';
defineOptions({ inheritAttrs: false });
defineSlots<{ indicators: (props: { renderer: VNode }) => unknown }>();
const modelValue = defineModel<Fid>();
const { data, error, isFetching, isError, isSuccess } = useApiForums();
const indicatorsRenderer = computed(() => (<>
Expand Down
27 changes: 19 additions & 8 deletions fe/src/views/Post.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</div>
<div class="container">
<PlaceholderError :error="error" class="border-top" />
<PlaceholderPostList :isLoading="isFetching" />
<PlaceholderPostList v-show="isPending" :isLoading="isFetching" />
</div>
</template>

Expand All @@ -36,7 +36,7 @@ import { useApiPosts } from '@/api';
import type { ApiPosts, Cursor } from '@/api/index.d';
import { scrollToPostListItemByRoute } from '@/components/Post/renderers/list/index';
import { compareRouteIsNewQuery, getRouteCursorParam } from '@/router';
import type { ObjUnknown } from '@/shared';
import type { ObjUnknown, UnixTimestamp } from '@/shared';
import { notyShow, scrollBarWidth, titleTemplate } from '@/shared';
import { useTriggerRouteUpdateStore } from '@/stores/triggerRouteUpdate';
Expand All @@ -53,7 +53,8 @@ export type PostRenderer = 'list' | 'table';
const route = useRoute();
const queryParam = ref<ApiPosts['queryParam']>();
const shouldFetch = ref<boolean>(false);
const { data, error, isFetching, isFetchedAfterMount, dataUpdatedAt, errorUpdatedAt } = useApiPosts(queryParam, shouldFetch);
const { data, error, isPending, isRefetching, isFetching, isFetchedAfterMount, dataUpdatedAt, errorUpdatedAt } =
useApiPosts(queryParam, shouldFetch);
const selectedRenderTypes = ref<[PostRenderer]>(['list']);
const renderType = computed(() => selectedRenderTypes.value[0]);
const queryFormRef = ref<InstanceType<typeof QueryForm>>();
Expand Down Expand Up @@ -90,16 +91,26 @@ const fetchPosts = (queryParams: ObjUnknown[], cursor: Cursor) => {
if (value)
shouldFetch.value = false;
});
watchOnce([dataUpdatedAt, errorUpdatedAt], async updatedAt => {
const networkTime = (_.max(updatedAt) ?? 0) - startTime;
await nextTick(); // wait for child components finish dom update
const updatedAtWatcher = async (updatedAt: UnixTimestamp[]) => {
if (isRefetching.value) // background refetching with cached data already presented, watching for refetch
watchOnce([dataUpdatedAt, errorUpdatedAt], updatedAtWatcher);
const maxUpdatedAt = Math.max(...updatedAt);
if (maxUpdatedAt === 0) { // just starts to fetch, defer watching to next time
watchOnce([dataUpdatedAt, errorUpdatedAt], updatedAtWatcher);
return;
}
const isCached = maxUpdatedAt < startTime;
const networkTime = isCached ? 0 : maxUpdatedAt - startTime;
await nextTick(); // wait for child components to finish dom update
const fetchedPage = data.value?.pages.find(i => i.pages.currentCursor === cursor);
const postCount = _.sum(Object.values(fetchedPage?.pages.matchQueryPostCount ?? {}));
const renderTime = (Date.now() - startTime - networkTime) / 1000;
notyShow('success', `已加载${postCount}条记录
前端耗时${renderTime.toFixed(2)}s
后端+网络耗时${(networkTime / 1000).toFixed(2)}s`);
});
${isCached ? '使用本地缓存' : `后端+网络耗时${(networkTime / 1000).toFixed(2)}s`}`);
};
watchOnce([dataUpdatedAt, errorUpdatedAt], updatedAtWatcher);
};
watch(isFetchedAfterMount, async () => {
Expand Down

0 comments on commit d65bec2

Please sign in to comment.