Skip to content

Commit

Permalink
* add reactive isChartLoading and wrapping chartLoadder.* aka the…
Browse files Browse the repository at this point in the history
… original `loadChart` to replace the jquery-style directly mutating `Element.classList` with vue-style `v-bind:class=` directive @ pages/bilibiliVote.vue

* now will not recreate the intersection observer and using an static `rootMargin` for elements with the default `topOffset=0`, that currently is `header.thread-title` in `<PostRendererListThread>` @ stores/viewportTopmostPost.ts
* fix not expanding the start page without any cursor @ `<PostNav>`
* replace the only usage of `Number.parseInt()` with `Number()` to fix ControlNet/wt-data-project.web#10 (comment) @ utils/post/queryForm/index.ts
@ fe
  • Loading branch information
n0099 committed Sep 11, 2024
1 parent 3bbae18 commit b4bbdc3
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 37 deletions.
9 changes: 5 additions & 4 deletions fe/src/components/post/Nav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ const navigate = async (cursor: Cursor, postIdObj: PostIdObj) =>
hash: routeHash(postIdObj),
params: { ...route.params, cursor }
});
const cursorKey = (cursor: Cursor) => `cursor/${cursor}`;
const pageMenuKey = (cursor: Cursor) => `${queryParam?.query}/${cursorKey(cursor)}`;
const threadMenuKey = (cursor: Cursor, tid: Tid) => `${cursorKey(cursor)}/tid/${tid}`;
const cursorKey = (cursor?: Cursor) => `cursor/${cursor ?? ''}`;
const pageMenuKey = (cursor?: Cursor) => `${queryParam?.query}/${cursorKey(cursor)}`;
const threadMenuKey = (cursor: Cursor | undefined, tid: Tid) => `${cursorKey(cursor)}/tid/${tid}`;
const selectThread: ToPromise<MenuClickEventHandler> = async ({ domEvent, key }) => {
if (!(domEvent.target as Element).classList.contains('post-nav-reply')) { // ignore clicks on reply link
const [, cursor, tid] = /c(.*)-t(\d+)/u.exec(key.toString()) ?? [];
Expand Down Expand Up @@ -132,7 +132,8 @@ const menuReplyClasses = (reply: Reply) => {
};
watchImmediate(() => [route.params.cursor, data.value?.pages], () => {
if (!_.isString(route.params.cursor))
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!(route.params.cursor === undefined || _.isString(route.params.cursor)))
return;
expandedPages.value = [pageMenuKey(route.params.cursor)];
});
Expand Down
42 changes: 28 additions & 14 deletions fe/src/pages/bilibiliVote.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@
<p><NuxtLink to="https://web.archive.org/web/0/https://tieba.baidu.com/p/6063625612" target="_blank">bilibili吧 吧主候选人支持率Top20(非官方数据,仅供参考)</NuxtLink></p>
<p><NuxtLink to="https://www.bilibili.com/video/av46507371" target="_blank">【数据可视化】一分钟看完bilibili吧吧主公投</NuxtLink></p>
<hr />
<div ref="top50CandidateCountRef" class="echarts" id="top50CandidateCount" />
<div
ref="top50CandidateCountRef"
:class="{ loading: isChartLoading.top50CandidateCount }"
class="echarts" id="top50CandidateCount" />
<hr />
<div ref="top10CandidatesTimelineRef" class="echarts" id="top10CandidatesTimeline" />
<div
ref="top10CandidatesTimelineRef"
:class="{ loading: isChartLoading.top10CandidatesTimeline }"
class="echarts" id="top10CandidatesTimeline" />
<hr />
<div class="row justify-content-end">
<label class="col-2 col-form-label text-end" for="top5CandidateCountGroupByTimeGranularity">时间粒度</label>
Expand All @@ -34,7 +40,10 @@
</div>
</div>
</div>
<div ref="top5CandidateCountGroupByTimeRef" class="echarts" id="top5CandidateCountGroupByTime" />
<div
ref="top5CandidateCountGroupByTimeRef"
:class="{ loading: isChartLoading.top5CandidateCountGroupByTime }"
class="echarts" id="top5CandidateCountGroupByTime" />
<hr />
<div class="row justify-content-end">
<label class="col-2 col-form-label text-end" for="allVoteCountGroupByTimeGranularity">时间粒度</label>
Expand All @@ -48,7 +57,10 @@
</div>
</div>
</div>
<div ref="allVoteCountGroupByTimeRef" class="echarts" id="allVoteCountGroupByTime" />
<div
ref="allVoteCountGroupByTimeRef"
:class="{ loading: isChartLoading.allVoteCountGroupByTime }"
class="echarts" id="allVoteCountGroupByTime" />
<hr />
<LazyATable
v-if="isMounted" rowKey="candidateIndex"
Expand Down Expand Up @@ -134,6 +146,7 @@ const chartElementRefs = {
};
useResizeableEcharts(Object.values(chartElementRefs));
type ChartName = keyof typeof chartElementRefs;
const chartNames = Object.keys(chartElementRefs) as ChartName[];
const {
top50CandidateCount: top50CandidateCountRef,
top10CandidatesTimeline: top10CandidatesTimelineRef,
Expand Down Expand Up @@ -464,7 +477,8 @@ const formatCandidateName = (id: number) => `${id}号\n${json.candidateNames[id
// Etc/GMT-8 in IANA tzdb is UTC+8 https://stackoverflow.com/questions/53076575/time-zones-etc-gmt-why-it-is-other-way-round
const filledTimeGranularityAxisPointerLabelFormatter =
timeGranularityAxisPointerLabelFormatter(setDateTimeZoneAndLocale('UTC+8', { keepLocalTime: true }));
const loadCharts = {
const chartLoadder = {
top50CandidateCount() {
// [{ voteFor: '1号', validVotes: 1, validAvgGrade: 18, invalidVotes: 1, invalidAvgGrade: 18 }, ... ]
const dataset = _.chain(json.top50CandidatesVoteCount)
Expand Down Expand Up @@ -691,6 +705,12 @@ const loadCharts = {
} as echarts.ComposeOption<DatasetComponentOption | GridComponentOption>);
}
};
const isChartLoading = reactive<{ [P in ChartName]: boolean }>(keysWithSameValue(chartNames, true));
const loadChart = (chartName: ChartName) => () => {
isChartLoading[chartName] = true;
chartLoadder[chartName]();
isChartLoading[chartName] = false;
};
// add candidate index as keys then deep merge will combine same keys values, finally remove keys
candidatesDetailData.value = Object.values(_.merge(
Expand All @@ -713,23 +733,17 @@ candidatesDetailData.value = Object.values(_.merge(
));
watch(() => query.value.top5CandidateCountGroupByTimeGranularity,
loadCharts.top5CandidateCountGroupByTime);
loadChart('top5CandidateCountGroupByTime'));
watch(() => query.value.allVoteCountGroupByTimeGranularity,
loadCharts.allVoteCountGroupByTime);
loadChart('allVoteCountGroupByTime'));
onMounted(() => {
_.map(chartElementRefs, (elRef: Ref<HTMLElement | undefined>, chartName: ChartName) => {
if (elRef.value === undefined)
return;
elRef.value.classList.add('loading');
const chart = echarts.init(elRef.value, echarts4ColorTheme);
chart.setOption(chartsInitialOption[chartName]);
echartsInstances[chartName] = chart;
});
_.map(echartsInstances, (chart: echarts.ECharts | null, chartName: ChartName) => {
if (chart === null)
return;
loadCharts[chartName]();
chartElementRefs[chartName].value?.classList.remove('loading');
loadChart(chartName)();
});
});
</script>
Expand Down
42 changes: 24 additions & 18 deletions fe/src/stores/viewportTopmostPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,33 @@ export const useViewportTopmostPostStore = defineStore('viewportTopmostPost', ()
const { height: windowHeight } = useWindowSize();
const intersectionObserver = (newTopmostPost: TopmostPost, topOffset = 0) => {
const stickyTitleEl = ref<HTMLElement>();
// eslint-disable-next-line @typescript-eslint/no-empty-function
let stopExistingIntersectionObserver = () => {};
watchDebounced(windowHeight, () => {
stopExistingIntersectionObserver();
const { stop } = useIntersectionObserver(stickyTitleEl, entries => {
_.orderBy(entries, 'time').forEach(e => { // https://github.com/vueuse/vueuse/issues/4197
if (e.isIntersecting
&& !(newTopmostPost.pid === undefined // prevent thread overwrite its reply
&& viewportTopmostPost.value?.tid === newTopmostPost.tid))
viewportTopmostPost.value = newTopmostPost;
});

// bottom: -windowHeight will only trigger when reaching the top border of root that defaults to viewport
// additional +topOffset and not using -100% to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1918017
// top: -topOffset will move down the trigger line below the top border to match with its offset
const onIntersect = (entries: IntersectionObserverEntry[]) => {
_.orderBy(entries, 'time').forEach(e => { // https://github.com/vueuse/vueuse/issues/4197
if (e.isIntersecting
&& !(newTopmostPost.pid === undefined // prevent thread overwrite its reply
&& viewportTopmostPost.value?.tid === newTopmostPost.tid))
viewportTopmostPost.value = newTopmostPost;
});
};
if (topOffset === 0) {
// bottom: -100% will only trigger when reaching the top border of root that defaults to viewport
// https://stackoverflow.com/questions/16302483/event-to-detect-when-positionsticky-is-triggered
// https://stackoverflow.com/questions/54807535/intersection-observer-api-observe-the-center-of-the-viewport
// https://web.archive.org/web/20240111160426/https://wilsotobianco.com/experiments/intersection-observer-playground/
}, { rootMargin: `${-topOffset}px 0px ${-windowHeight.value + topOffset}px 0px` });
stopExistingIntersectionObserver = stop;
}, { debounce: 5000, immediate: true });
useIntersectionObserver(stickyTitleEl, onIntersect, { rootMargin: '0px 0px -100% 0px' });
} else {
// eslint-disable-next-line @typescript-eslint/no-empty-function
let stopExistingIntersectionObserver = () => {};
watchDebounced(windowHeight, () => {
stopExistingIntersectionObserver();

// bottom: additional +topOffset and not using -100% to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1918017
// top: -topOffset will move down the trigger line below the top border to match with its offset
const rootMargin = `${-topOffset}px 0px ${-windowHeight.value + topOffset}px 0px`;
const { stop } = useIntersectionObserver(stickyTitleEl, onIntersect, { rootMargin });
stopExistingIntersectionObserver = stop;
}, { debounce: 5000, immediate: true });
}

return { stickyTitleEl };
};
Expand Down
2 changes: 1 addition & 1 deletion fe/src/utils/post/queryForm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export const getQueryFormDeps = () => {
if (routeName === 'posts/param' && _.isArray(route.params.pathMatch)) {
parseParamRoute(route.params.pathMatch); // omit the cursor param from route full path
} else if (routeName === 'posts/fid' && !_.isArray(route.params.fid)) {
uniqueParams.value.fid.value = parseInt(route.params.fid);
uniqueParams.value.fid.value = Number(route.params.fid);
} else { // post id routes
uniqueParams.value = _.mapValues(uniqueParams.value, param =>
fillParamDefaultValue(param, true)) as KnownUniqueParams; // reset to default
Expand Down

0 comments on commit b4bbdc3

Please sign in to comment.