diff options
Diffstat (limited to 'FrontEnd')
-rw-r--r-- | FrontEnd/package-lock.json | 81 | ||||
-rw-r--r-- | FrontEnd/package.json | 8 | ||||
-rw-r--r-- | FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts | 45 | ||||
-rw-r--r-- | FrontEnd/src/app/utilities/useScrollToTop.ts | 49 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx | 16 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx | 11 |
6 files changed, 133 insertions, 77 deletions
diff --git a/FrontEnd/package-lock.json b/FrontEnd/package-lock.json index a86b91f0..664eff6d 100644 --- a/FrontEnd/package-lock.json +++ b/FrontEnd/package-lock.json @@ -21,7 +21,7 @@ "lodash": "^4.17.21", "pepjs": "^0.5.3", "react": "^17.0.1", - "react-bootstrap": "^2.0.0-alpha.2", + "react-bootstrap": "^2.0.0-beta.0", "react-color": "^2.19.3", "react-dom": "^17.0.2", "react-i18next": "^11.10.0", @@ -44,7 +44,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.9", "@types/color": "^3.0.1", "@types/lodash": "^4.14.170", - "@types/node": "^15.6.1", + "@types/node": "^15.9.0", "@types/react": "^17.0.9", "@types/react-color": "^3.0.4", "@types/react-dom": "^17.0.6", @@ -52,7 +52,7 @@ "@types/react-router": "^5.1.13", "@types/react-router-bootstrap": "^0.24.5", "@types/react-router-dom": "^5.1.7", - "@types/remarkable": "^2.0.1", + "@types/remarkable": "^2.0.2", "@types/webpack-env": "^1.16.0", "@types/xregexp": "^4.4.0", "@typescript-eslint/eslint-plugin": "^4.26.0", @@ -1731,15 +1731,6 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, - "node_modules/@types/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==", - "deprecated": "This is a stub types definition. classnames provides its own type definitions, so you do not need this installed.", - "dependencies": { - "classnames": "*" - } - }, "node_modules/@types/color": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz", @@ -1851,9 +1842,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz", - "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz", + "integrity": "sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA==", "dev": true }, "node_modules/@types/parse-json": { @@ -1954,9 +1945,9 @@ } }, "node_modules/@types/remarkable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz", - "integrity": "sha512-4KTpW8aVjVkTgaWlEOAuorBAdlUrvlzzPlVrt9vsAAM6nB/nUln7j0bcIRLdUH0Sf2g6ZPVBfibm4+8y1glFwQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.2.tgz", + "integrity": "sha512-z5JSTrZfEluIlTLWC9jiDojfWReaKpRDRj0pHSgNPlmhe5H0V/hJZIb8zeGxq0Zc0lDMKU1Rhr6cjkeabP7a6Q==", "dev": true, "dependencies": { "@types/highlight.js": "^9.7.0", @@ -9310,25 +9301,24 @@ } }, "node_modules/react-bootstrap": { - "version": "2.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-alpha.2.tgz", - "integrity": "sha512-Sjaq/EwI0ZJaN+yja4FLeJczvXuoYDxwNQsJsuzdpTAFCtCvTAh0P/PUA8n1CTxePfjbPzcnSLhKGN0k0FGdAg==", + "version": "2.0.0-beta.0", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-beta.0.tgz", + "integrity": "sha512-9grXyFdznG7r7HT7etkiKHkgqVVgasDFjkTtNCAx9xAQllAjKl6vuBGBVc3F+KLNpdTzrxrx5RWNIgd8ZpascA==", "dependencies": { - "@babel/runtime": "^7.13.8", + "@babel/runtime": "^7.14.0", "@restart/context": "^2.1.4", "@restart/hooks": "^0.3.26", - "@types/classnames": "^2.2.10", "@types/invariant": "^2.2.33", "@types/prop-types": "^15.7.3", - "@types/react": ">=16.14.4", + "@types/react": ">=16.14.8", "@types/react-transition-group": "^4.4.1", "@types/warning": "^3.0.0", - "classnames": "^2.2.6", - "dom-helpers": "^5.1.2", + "classnames": "^2.3.1", + "dom-helpers": "^5.2.1", "invariant": "^2.2.4", "prop-types": "^15.7.2", "prop-types-extra": "^1.1.0", - "react-overlays": "^5.0.0", + "react-overlays": "^5.0.1", "react-transition-group": "^4.4.1", "uncontrollable": "^7.2.1", "warning": "^4.0.3" @@ -13357,14 +13347,6 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, - "@types/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A==", - "requires": { - "classnames": "*" - } - }, "@types/color": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz", @@ -13476,9 +13458,9 @@ "dev": true }, "@types/node": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz", - "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==", + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz", + "integrity": "sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA==", "dev": true }, "@types/parse-json": { @@ -13579,9 +13561,9 @@ } }, "@types/remarkable": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz", - "integrity": "sha512-4KTpW8aVjVkTgaWlEOAuorBAdlUrvlzzPlVrt9vsAAM6nB/nUln7j0bcIRLdUH0Sf2g6ZPVBfibm4+8y1glFwQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.2.tgz", + "integrity": "sha512-z5JSTrZfEluIlTLWC9jiDojfWReaKpRDRj0pHSgNPlmhe5H0V/hJZIb8zeGxq0Zc0lDMKU1Rhr6cjkeabP7a6Q==", "dev": true, "requires": { "@types/highlight.js": "^9.7.0", @@ -19174,25 +19156,24 @@ } }, "react-bootstrap": { - "version": "2.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-alpha.2.tgz", - "integrity": "sha512-Sjaq/EwI0ZJaN+yja4FLeJczvXuoYDxwNQsJsuzdpTAFCtCvTAh0P/PUA8n1CTxePfjbPzcnSLhKGN0k0FGdAg==", + "version": "2.0.0-beta.0", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.0.0-beta.0.tgz", + "integrity": "sha512-9grXyFdznG7r7HT7etkiKHkgqVVgasDFjkTtNCAx9xAQllAjKl6vuBGBVc3F+KLNpdTzrxrx5RWNIgd8ZpascA==", "requires": { - "@babel/runtime": "^7.13.8", + "@babel/runtime": "^7.14.0", "@restart/context": "^2.1.4", "@restart/hooks": "^0.3.26", - "@types/classnames": "^2.2.10", "@types/invariant": "^2.2.33", "@types/prop-types": "^15.7.3", - "@types/react": ">=16.14.4", + "@types/react": ">=16.14.8", "@types/react-transition-group": "^4.4.1", "@types/warning": "^3.0.0", - "classnames": "^2.2.6", - "dom-helpers": "^5.1.2", + "classnames": "^2.3.1", + "dom-helpers": "^5.2.1", "invariant": "^2.2.4", "prop-types": "^15.7.2", "prop-types-extra": "^1.1.0", - "react-overlays": "^5.0.0", + "react-overlays": "^5.0.1", "react-transition-group": "^4.4.1", "uncontrollable": "^7.2.1", "warning": "^4.0.3" diff --git a/FrontEnd/package.json b/FrontEnd/package.json index ef476709..f00a86e7 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -1,6 +1,6 @@ {
"name": "timeline",
- "version": "0.3.0",
+ "version": "0.4.0",
"private": true,
"homepage": "https://timeline.crupest.life",
"keywords": [],
@@ -19,7 +19,7 @@ "lodash": "^4.17.21",
"pepjs": "^0.5.3",
"react": "^17.0.1",
- "react-bootstrap": "^2.0.0-alpha.2",
+ "react-bootstrap": "^2.0.0-beta.0",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-i18next": "^11.10.0",
@@ -60,7 +60,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.9",
"@types/color": "^3.0.1",
"@types/lodash": "^4.14.170",
- "@types/node": "^15.6.1",
+ "@types/node": "^15.9.0",
"@types/react": "^17.0.9",
"@types/react-color": "^3.0.4",
"@types/react-dom": "^17.0.6",
@@ -68,7 +68,7 @@ "@types/react-router": "^5.1.13",
"@types/react-router-bootstrap": "^0.24.5",
"@types/react-router-dom": "^5.1.7",
- "@types/remarkable": "^2.0.1",
+ "@types/remarkable": "^2.0.2",
"@types/webpack-env": "^1.16.0",
"@types/xregexp": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
diff --git a/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts b/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts index c86ffa74..a97d7660 100644 --- a/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts +++ b/FrontEnd/src/app/utilities/useReverseScrollPositionRemember.ts @@ -2,6 +2,10 @@ import React from "react"; let on = false; +let reverseScrollPosition = getReverseScrollPosition(); +let reverseScrollToPosition: number | null = null; +let lastScrollPosition = window.scrollY; + export function getReverseScrollPosition(): number { if (document.documentElement.scrollHeight <= window.innerHeight) { return 0; @@ -20,22 +24,47 @@ export function scrollToReverseScrollPosition(reversePosition: number): void { const old = document.documentElement.style.scrollBehavior; document.documentElement.style.scrollBehavior = "auto"; - window.scrollTo( - 0, - document.documentElement.scrollHeight - window.innerHeight - reversePosition - ); + const newPosition = + document.documentElement.scrollHeight - + window.innerHeight - + reversePosition; + + reverseScrollToPosition = newPosition; + + window.scrollTo(0, newPosition); document.documentElement.style.scrollBehavior = old; } -let scrollPosition = getReverseScrollPosition(); - const scrollListener = (): void => { - scrollPosition = getReverseScrollPosition(); + if ( + reverseScrollToPosition != null && + Math.abs(window.scrollY - reverseScrollToPosition) > 50 + ) { + console.log( + `Reverse scroll position coerce. Required: ${reverseScrollToPosition}. Actual: ${window.scrollY}.` + ); + scrollToReverseScrollPosition(reverseScrollPosition); + return; + } + if ( + reverseScrollToPosition == null && + Math.abs(window.scrollY - lastScrollPosition) > 1000 + ) { + console.log( + `Scroll jump detected. New: ${window.scrollY}. Old: ${lastScrollPosition}.` + ); + scrollToReverseScrollPosition(reverseScrollPosition); + return; + } + + reverseScrollToPosition = null; + lastScrollPosition = window.scrollY; + reverseScrollPosition = getReverseScrollPosition(); }; const resizeObserver = new ResizeObserver(() => { - scrollToReverseScrollPosition(scrollPosition); + scrollToReverseScrollPosition(reverseScrollPosition); }); export default function useReverseScrollPositionRemember(): void { diff --git a/FrontEnd/src/app/utilities/useScrollToTop.ts b/FrontEnd/src/app/utilities/useScrollToTop.ts new file mode 100644 index 00000000..da63cb0a --- /dev/null +++ b/FrontEnd/src/app/utilities/useScrollToTop.ts @@ -0,0 +1,49 @@ +import React from "react"; +import { fromEvent } from "rxjs"; +import { filter, throttleTime, tap } from "rxjs/operators"; + +function useScrollToTop( + handler: () => void, + enable = true, + option = { + maxOffset: 50, + throttle: 1000, + } +): void { + const handlerRef = React.useRef<(() => void) | null>(null); + + React.useEffect(() => { + handlerRef.current = handler; + + return () => { + handlerRef.current = null; + }; + }, [handler]); + + React.useEffect(() => { + const subscription = fromEvent(window, "scroll") + .pipe( + tap(() => { + console.log( + `Scroll event fired: ${window.scrollY}, time: ${Date.now()}.` + ); + }), + filter(() => { + return window.scrollY <= option.maxOffset; + }), + throttleTime(option.throttle) + ) + .subscribe(() => { + if (enable) { + console.log(`Fire scroll to top event, time: ${Date.now()}.`); + handlerRef.current?.(); + } + }); + + return () => { + subscription.unsubscribe(); + }; + }, [enable, option.maxOffset, option.throttle]); +} + +export default useScrollToTop; diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx index 69fd9207..37f02a82 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePagedPostListView.tsx @@ -1,8 +1,9 @@ import React from "react"; -import { fromEvent } from "rxjs"; import { HttpTimelinePostInfo } from "@/http/timeline"; +import useScrollToTop from "@/utilities/useScrollToTop"; + import TimelinePostListView from "./TimelinePostListView"; export interface TimelinePagedPostListViewProps { @@ -25,16 +26,9 @@ const TimelinePagedPostListView: React.FC<TimelinePagedPostListViewProps> = ( : posts.slice(-lastViewCount); }, [posts, lastViewCount]); - React.useEffect(() => { - if (lastViewCount < posts.length) { - const subscription = fromEvent(window, "scroll").subscribe(() => { - if (window.scrollY === 0) { - setLastViewCount(lastViewCount + 10); - } - }); - return () => subscription.unsubscribe(); - } - }, [lastViewCount, posts]); + useScrollToTop(() => { + setLastViewCount(lastViewCount + 10); + }, lastViewCount < posts.length); return ( <TimelinePostListView diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx index c9fec919..2f778ab1 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx @@ -28,15 +28,18 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => { React.useState<boolean>(false); const [deleteDialog, setDeleteDialog] = React.useState<boolean>(false); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const cardRef = React.useRef<HTMLDivElement>(null!); + const cardRef = React.useRef<HTMLDivElement>(null); React.useEffect(() => { const cardIntersectionObserver = new IntersectionObserver(([e]) => { if (e.intersectionRatio > 0) { - cardRef.current.style.animationName = "timeline-post-enter"; + if (cardRef.current != null) { + cardRef.current.style.animationName = "timeline-post-enter"; + } } }); - cardIntersectionObserver.observe(cardRef.current); + if (cardRef.current) { + cardIntersectionObserver.observe(cardRef.current); + } return () => { cardIntersectionObserver.disconnect(); |