diff --git a/cypress/e2e/pull-to-refresh.cy.ts b/cypress/e2e/pull-to-refresh.cy.ts index d56c5589..732ee413 100644 --- a/cypress/e2e/pull-to-refresh.cy.ts +++ b/cypress/e2e/pull-to-refresh.cy.ts @@ -13,7 +13,7 @@ describe('Pull To Refresh', () => { url: '/api/v1/*', }).as('apiCall'); - cy.get('.searchbar').swipe('bottom', [190, 400]); + cy.get('.searchbar').swipe('bottom', [190, 500]); cy.wait('@apiCall').then((interception) => { assert.isNotNull( diff --git a/package.json b/package.json index 5dff068e..8b82e45d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "openpgp": "5.7.0", "plex-api": "5.3.2", "pug": "3.0.2", - "pulltorefreshjs": "0.1.22", "react": "18.2.0", "react-ace": "10.1.0", "react-animate-height": "2.1.2", @@ -121,7 +120,6 @@ "@types/node": "17.0.36", "@types/node-schedule": "2.1.0", "@types/nodemailer": "6.4.7", - "@types/pulltorefreshjs": "0.1.5", "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "@types/react-transition-group": "4.4.5", diff --git a/src/components/Layout/PullToRefresh/index.tsx b/src/components/Layout/PullToRefresh/index.tsx new file mode 100644 index 00000000..cdedcf43 --- /dev/null +++ b/src/components/Layout/PullToRefresh/index.tsx @@ -0,0 +1,118 @@ +import { ArrowPathIcon } from '@heroicons/react/24/outline'; +import { useRouter } from 'next/router'; +import { useEffect, useRef, useState } from 'react'; + +const PullToRefresh = () => { + const router = useRouter(); + + const [pullStartPoint, setPullStartPoint] = useState(0); + const [pullChange, setPullChange] = useState(0); + const refreshDiv = useRef(null); + + // Various pull down thresholds that determine icon location + const pullDownInitThreshold = pullChange > 20; + const pullDownStopThreshold = 120; + const pullDownReloadThreshold = pullChange > 340; + const pullDownIconLocation = pullChange / 3; + + useEffect(() => { + // Reload function that is called when reload threshold has been hit + // Add loading class to determine when to add spin animation + const forceReload = () => { + refreshDiv.current?.classList.add('loading'); + setTimeout(() => { + router.reload(); + }, 1000); + }; + + const html = document.querySelector('html'); + + // Determines if we are at the top of the page + // Locks or unlocks page when pulling down to refresh + const pullStart = (e: TouchEvent) => { + setPullStartPoint(e.targetTouches[0].screenY); + + if (window.scrollY === 0 && window.scrollX === 0) { + refreshDiv.current?.classList.add('block'); + refreshDiv.current?.classList.remove('hidden'); + document.body.style.touchAction = 'none'; + document.body.style.overscrollBehavior = 'none'; + if (html) { + html.style.overscrollBehaviorY = 'none'; + } + } else { + refreshDiv.current?.classList.remove('block'); + refreshDiv.current?.classList.add('hidden'); + } + }; + + // Tracks how far we have pulled down the refresh icon + const pullDown = async (e: TouchEvent) => { + const screenY = e.targetTouches[0].screenY; + + const pullLength = + pullStartPoint < screenY ? Math.abs(screenY - pullStartPoint) : 0; + + setPullChange(pullLength); + }; + + // Will reload the page if we are past the threshold + // Otherwise, we reset the pull + const pullFinish = () => { + setPullStartPoint(0); + + if (pullDownReloadThreshold) { + forceReload(); + } else { + setPullChange(0); + } + + document.body.style.touchAction = 'auto'; + document.body.style.overscrollBehaviorY = 'auto'; + if (html) { + html.style.overscrollBehaviorY = 'auto'; + } + }; + + window.addEventListener('touchstart', pullStart, { passive: false }); + window.addEventListener('touchmove', pullDown, { passive: false }); + window.addEventListener('touchend', pullFinish, { passive: false }); + + return () => { + window.removeEventListener('touchstart', pullStart); + window.removeEventListener('touchmove', pullDown); + window.removeEventListener('touchend', pullFinish); + }; + }, [pullDownInitThreshold, pullDownReloadThreshold, pullStartPoint, router]); + + return ( +
+
+ +
+
+ ); +}; + +export default PullToRefresh; diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index b30b9712..878f27b1 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -1,8 +1,8 @@ import MobileMenu from '@app/components/Layout/MobileMenu'; +import PullToRefresh from '@app/components/Layout/PullToRefresh'; import SearchInput from '@app/components/Layout/SearchInput'; import Sidebar from '@app/components/Layout/Sidebar'; import UserDropdown from '@app/components/Layout/UserDropdown'; -import PullToRefresh from '@app/components/PullToRefresh'; import type { AvailableLocale } from '@app/context/LanguageContext'; import useLocale from '@app/hooks/useLocale'; import useSettings from '@app/hooks/useSettings'; diff --git a/src/components/PullToRefresh/index.tsx b/src/components/PullToRefresh/index.tsx deleted file mode 100644 index 68939c48..00000000 --- a/src/components/PullToRefresh/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { ArrowPathIcon } from '@heroicons/react/24/outline'; -import { useRouter } from 'next/router'; -import PR from 'pulltorefreshjs'; -import { useEffect } from 'react'; -import ReactDOMServer from 'react-dom/server'; - -const PullToRefresh = () => { - const router = useRouter(); - - useEffect(() => { - PR.init({ - mainElement: '#pull-to-refresh', - onRefresh() { - router.reload(); - }, - iconArrow: ReactDOMServer.renderToString( -
- -
- ), - iconRefreshing: ReactDOMServer.renderToString( -
- -
- ), - instructionsPullToRefresh: ReactDOMServer.renderToString(
), - instructionsReleaseToRefresh: ReactDOMServer.renderToString(
), - instructionsRefreshing: ReactDOMServer.renderToString(
), - distReload: 60, - distIgnore: 15, - shouldPullToRefresh: () => - !window.scrollY && document.body.style.overflow !== 'hidden', - }); - return () => { - PR.destroyAll(); - }; - }, [router]); - - return
; -}; - -export default PullToRefresh; diff --git a/src/styles/globals.css b/src/styles/globals.css index fac7272d..8110e87e 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -17,7 +17,7 @@ body { @apply bg-gray-900; - overscroll-behavior-y: contain; + -webkit-overflow-scrolling: touch; } code { diff --git a/yarn.lock b/yarn.lock index c95f591d..886aee53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3954,11 +3954,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== -"@types/pulltorefreshjs@0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@types/pulltorefreshjs/-/pulltorefreshjs-0.1.5.tgz#f15c9dbc91b8fdd8135093d81ece9e9d4d2324d7" - integrity sha512-/VRTgBettvBg1KI8mGnA9oeWs359tTXQ7qsxLuXnksL88jvK6ZNMStG5T9x9vUO9O7jLsgREB0cElz/BWFfdew== - "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -11434,11 +11429,6 @@ pug@3.0.2, pug@^3.0.2: pug-runtime "^3.0.1" pug-strip-comments "^2.0.0" -pulltorefreshjs@0.1.22: - version "0.1.22" - resolved "https://registry.yarnpkg.com/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz#ddb5e3feee0b2a49fd46e1b18e84fffef2c47ac0" - integrity sha512-haxNVEHnS4NCQA7NeG7TSV69z4uqy/N7nfPRuc4dPWe8H6ygUrMjdNeohE+6v0lVVX/ukSjbLYwPUGUYtFKfvQ== - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"