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 && (
+
+
+
+ )}
+