From e92462857325928a74f0c573eedde803974fb0df Mon Sep 17 00:00:00 2001 From: robertu <4065233+robertu7@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:53:12 +0700 Subject: [PATCH 1/7] feat(ptr): setup global pull to refresh webpage --- package-lock.json | 28 ++++++- package.json | 2 + src/common/styles/vendors/ptr.css | 48 ++++++++++++ src/components/GlobalStyles/index.tsx | 4 + src/components/Hook/index.ts | 1 + src/components/Hook/usePullToRefresh.tsx | 95 ++++++++++++++++++++++++ src/components/Layout/index.tsx | 26 +++++-- src/components/PullToRefresh/index.tsx | 39 ++++++++++ src/components/index.tsx | 1 + 9 files changed, 235 insertions(+), 9 deletions(-) create mode 100644 src/common/styles/vendors/ptr.css create mode 100644 src/components/Hook/usePullToRefresh.tsx create mode 100644 src/components/PullToRefresh/index.tsx diff --git a/package-lock.json b/package-lock.json index ce3e26a145..b548db8e0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matters-web", - "version": "4.15.0", + "version": "4.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "matters-web", - "version": "4.15.0", + "version": "4.16.0", "license": "Apache-2.0", "dependencies": { "@apollo/react-common": "^3.1.3", @@ -67,6 +67,7 @@ "nprogress": "^0.2.0", "number-precision": "^1.6.0", "path-to-regexp": "^6.2.1", + "pulltorefreshjs": "^0.1.22", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", "react-copy-to-clipboard": "^5.1.0", @@ -109,6 +110,7 @@ "@types/jump.js": "^1.0.4", "@types/lodash": "^4.14.191", "@types/nprogress": "0.2.0", + "@types/pulltorefreshjs": "^0.1.5", "@types/react": "^18.0.26", "@types/react-beautiful-dnd": "^13.1.3", "@types/react-copy-to-clipboard": "^5.0.4", @@ -19310,6 +19312,12 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, + "node_modules/@types/pulltorefreshjs": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@types/pulltorefreshjs/-/pulltorefreshjs-0.1.5.tgz", + "integrity": "sha512-/VRTgBettvBg1KI8mGnA9oeWs359tTXQ7qsxLuXnksL88jvK6ZNMStG5T9x9vUO9O7jLsgREB0cElz/BWFfdew==", + "dev": true + }, "node_modules/@types/qs": { "version": "6.9.5", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", @@ -37395,6 +37403,11 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, + "node_modules/pulltorefreshjs": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz", + "integrity": "sha512-haxNVEHnS4NCQA7NeG7TSV69z4uqy/N7nfPRuc4dPWe8H6ygUrMjdNeohE+6v0lVVX/ukSjbLYwPUGUYtFKfvQ==" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -59171,6 +59184,12 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, + "@types/pulltorefreshjs": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@types/pulltorefreshjs/-/pulltorefreshjs-0.1.5.tgz", + "integrity": "sha512-/VRTgBettvBg1KI8mGnA9oeWs359tTXQ7qsxLuXnksL88jvK6ZNMStG5T9x9vUO9O7jLsgREB0cElz/BWFfdew==", + "dev": true + }, "@types/qs": { "version": "6.9.5", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", @@ -73367,6 +73386,11 @@ } } }, + "pulltorefreshjs": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz", + "integrity": "sha512-haxNVEHnS4NCQA7NeG7TSV69z4uqy/N7nfPRuc4dPWe8H6ygUrMjdNeohE+6v0lVVX/ukSjbLYwPUGUYtFKfvQ==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/package.json b/package.json index ac7ba55773..f8c44d6a01 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "nprogress": "^0.2.0", "number-precision": "^1.6.0", "path-to-regexp": "^6.2.1", + "pulltorefreshjs": "^0.1.22", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", "react-copy-to-clipboard": "^5.1.0", @@ -134,6 +135,7 @@ "@types/jump.js": "^1.0.4", "@types/lodash": "^4.14.191", "@types/nprogress": "0.2.0", + "@types/pulltorefreshjs": "^0.1.5", "@types/react": "^18.0.26", "@types/react-beautiful-dnd": "^13.1.3", "@types/react-copy-to-clipboard": "^5.0.4", diff --git a/src/common/styles/vendors/ptr.css b/src/common/styles/vendors/ptr.css new file mode 100644 index 0000000000..6ec5ac1138 --- /dev/null +++ b/src/common/styles/vendors/ptr.css @@ -0,0 +1,48 @@ +/* @styled-jsx=global */ + +/* https://github.com/BoxFactura/pulltorefresh.js/blob/master/src/lib/styles.js */ + +.ptr--ptr { + top: 0; + display: flex; + align-content: stretch; + align-items: flex-end; + width: 100%; + height: 0; + overflow: hidden; + text-align: center; + pointer-events: none; + transition: height 0.3s, min-height 0.3s; +} + +.ptr--box { + flex-basis: 100%; + padding: var(--spacing-base); +} + +.ptr--pull { + transition: none; +} + +.ptr--text { + font-size: 0; + visibility: hidden; +} + +.ptr--icon { + font-size: 0; + color: var(--color-grey-light); + transition: transform 0.3s; +} + +/* +When at the top of the page, disable vertical overscroll so passive touch +listeners can take over. +*/ +.ptr--top { + touch-action: pan-x pan-down pinch-zoom; +} + +.ptr--release .ptr--icon { + transform: rotate(180deg); +} diff --git a/src/components/GlobalStyles/index.tsx b/src/components/GlobalStyles/index.tsx index aca4e68841..6a05d1b546 100644 --- a/src/components/GlobalStyles/index.tsx +++ b/src/components/GlobalStyles/index.tsx @@ -7,6 +7,7 @@ import interactionStyles from '~/common/styles/utils/interaction.css' import linkStyles from '~/common/styles/utils/link.css' import motionStyles from '~/common/styles/utils/motion.css' import fresnelStyles from '~/common/styles/vendors/fresnel.css' +import ptrStyles from '~/common/styles/vendors/ptr.css' import tippyStyles from '~/common/styles/vendors/tippy.css' import { useWindowResize } from '~/components' @@ -56,6 +57,9 @@ export const GlobalStyles = () => { + diff --git a/src/components/Hook/index.ts b/src/components/Hook/index.ts index 0ec3561d46..e41c1feaf6 100644 --- a/src/components/Hook/index.ts +++ b/src/components/Hook/index.ts @@ -10,6 +10,7 @@ export * from './useImmersiveMode' export * from './useInterval' export * from './useNativeEventListener' export * from './useOutsideClick' +export * from './usePullToRefresh' export * from './useRoute' export * from './useStep' export * from './useTargetNetwork' diff --git a/src/components/Hook/usePullToRefresh.tsx b/src/components/Hook/usePullToRefresh.tsx new file mode 100644 index 0000000000..10a5981139 --- /dev/null +++ b/src/components/Hook/usePullToRefresh.tsx @@ -0,0 +1,95 @@ +import { useEffect } from 'react' +import ReactDOMServer from 'react-dom/server' + +import { ReactComponent as IconPullToRefresh } from '@/public/static/icons/24px/pull-to-refresh.svg' +import { analytics, sleep } from '~/common/utils' +import { IconSpinner16, withIcon } from '~/components' + +import { useEventListener } from './useEventListener' + +/** + * Core of Pull to Refresh + * + * Usage: + * + * ```tsx + * usePullToRefresh.Register() + * usePullToRefresh.Handler(refetch) + * ``` + * + */ + +const PTR_START = 'startPullToRefresh' +const PTR_END = 'endPullToRefresh' +const PTR_TIMEOUT = 3000 + +/** + * React Hook to register Pull To Refresh + */ +const Register = (selector = 'body', timeout = PTR_TIMEOUT) => { + let PTR: any + + const initPTR = (ptr: any) => { + PTR = ptr + + PTR.init({ + mainElement: selector, + triggerElement: selector, + distReload: 56, + onRefresh: (cb: () => void) => { + // start refresh + window.dispatchEvent(new CustomEvent(PTR_START, {})) + analytics.trackEvent('pull_to_refresh') + + // end refresh + let polling = true + + const onEnd = () => { + if (polling) { + cb() + polling = false + } + window.removeEventListener(PTR_END, onEnd) + } + + window.addEventListener(PTR_END, onEnd) + sleep(timeout).then(onEnd) + }, + getStyles: () => ``, // remove default styles + iconArrow: ReactDOMServer.renderToString( + withIcon(IconPullToRefresh)({ size: 'md' }) + ), + iconRefreshing: ReactDOMServer.renderToString( + + ), + }) + } + + useEffect(() => { + import('pulltorefreshjs').then(initPTR) + + return () => { + if (PTR) { + PTR.destroyAll() + } + } + }, []) +} + +/** + * React Hook to handle `PTR_START` event from `usePullToRefresh.Register` + */ +const Handler = (onPull: () => any) => { + useEventListener(PTR_START, async () => { + if (onPull) { + await onPull() + } + + window.dispatchEvent(new CustomEvent(PTR_END, {})) + }) +} + +export const usePullToRefresh = { + Register, + Handler, +} diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 3b791c5be0..ce76941810 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -2,7 +2,14 @@ import { useQuery } from '@apollo/react-hooks' import classNames from 'classnames' import dynamic from 'next/dynamic' -import { Head, Media, SearchBar, useRoute } from '~/components' +import { + Head, + Media, + PullToRefresh, + SearchBar, + usePullToRefresh, + useRoute, +} from '~/components' import CLIENT_PREFERENCE from '~/components/GQL/queries/clientPreference' import { ClientPreferenceQuery } from '~/gql/graphql' @@ -107,16 +114,21 @@ const Main: React.FC> = ({ hasOnboardingTasks: showOnboardingTasks, }) + usePullToRefresh.Register() + usePullToRefresh.Handler(() => window.location.reload()) + return ( <>
- {children} + + {children} - {showOnboardingTasks && ( - - - - )} + {showOnboardingTasks && ( + + + + )} +