about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-07-25 23:08:24 -0500
committerPaul Frazee <pfrazee@gmail.com>2022-07-25 23:08:24 -0500
commit041bfa22a99d8d6b4b17ad36c983e9e2b2444918 (patch)
treeb03d7934b27af87c76fa62424fa70e6d0e5229a8
parentaf55a89758fc6d44896051b9ddd015a73b92e0f6 (diff)
downloadvoidsky-041bfa22a99d8d6b4b17ad36c983e9e2b2444918.tar.zst
Implement Web versions of the bottom sheet, toast, and progress circle
-rw-r--r--package.json1
-rw-r--r--public/index.html39
-rw-r--r--src/App.web.tsx14
-rw-r--r--src/state/lib/api.ts4
-rw-r--r--src/view/com/composer/Composer.tsx127
-rw-r--r--src/view/com/feed/Feed.tsx6
-rw-r--r--src/view/com/feed/FeedItem.tsx2
-rw-r--r--src/view/com/modals/SharePost.native.tsx (renamed from src/view/com/sheets/SharePost.tsx)11
-rw-r--r--src/view/com/modals/SharePost.tsx57
-rw-r--r--src/view/com/modals/WebModal.tsx20
-rw-r--r--src/view/com/post-thread/PostThread.tsx6
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx2
-rw-r--r--src/view/com/util/ProgressCircle.native.tsx3
-rw-r--r--src/view/com/util/ProgressCircle.tsx20
-rw-r--r--src/view/com/util/Toast.native.tsx2
-rw-r--r--src/view/com/util/Toast.tsx62
-rw-r--r--src/view/index.ts2
-rw-r--r--todos.txt4
-rw-r--r--yarn.lock5
19 files changed, 295 insertions, 92 deletions
diff --git a/package.json b/package.json
index 93363c354..add0e37ce 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
     "mobx-react-lite": "^3.4.0",
     "moment": "^2.29.4",
     "react": "17.0.2",
+    "react-circular-progressbar": "^2.1.0",
     "react-dom": "17.0.2",
     "react-native": "0.68.2",
     "react-native-gesture-handler": "^2.5.0",
diff --git a/public/index.html b/public/index.html
index f6cb612b6..54fb90eea 100644
--- a/public/index.html
+++ b/public/index.html
@@ -11,6 +11,45 @@
       body { overflow: hidden; }
       /* These styles make the root element full-height */
       #root { display:flex; height:100%; }
+
+      /* These styles are for src/view/com/modals/WebModal */
+      div[data-modal-overlay] {
+        position: fixed;
+        top: 0;
+        left: 0;
+        background: #0004;
+        width: 100vw;
+        height: 100vh;
+      }
+      div[data-modal-container] {
+        position: fixed;
+        top: 20vh;
+        left: calc(50vw - 300px);
+        width: 600px;
+        padding: 20px;
+        background: #fff;
+        border-radius: 10px;
+        box-shadow: 0 5px 10px #0005;
+      }
+
+      /* These styles are for src/view/com/util/Toast */
+      div[data-toast-container] {
+        position: fixed;
+        bottom: 5vh;
+        right: 5vh;
+        width: 350px;
+        padding: 20px;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        background: #fff;
+        border-radius: 10px;
+        box-shadow: 0 5px 10px #0005;
+      }
+      div[data-toast-container] > div {
+        font-size: 18px;
+        margin-left: 10px;
+      }
     </style>
   </head>
   <body>
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 838b81ee2..9a6fedd5a 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -1,9 +1,8 @@
 import React, {useState, useEffect} from 'react'
-import {RootSiblingParent} from 'react-native-root-siblings'
-import {GestureHandlerRootView} from 'react-native-gesture-handler'
 import * as view from './view/index'
 import {RootStoreModel, setupState, RootStoreProvider} from './state'
 import * as Routes from './view/routes'
+import Toast from './view/com/util/Toast'
 
 function App() {
   const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
@@ -22,13 +21,10 @@ function App() {
   }
 
   return (
-    <GestureHandlerRootView style={{flex: 1}}>
-      <RootSiblingParent>
-        <RootStoreProvider value={rootStore}>
-          <Routes.Root />
-        </RootStoreProvider>
-      </RootSiblingParent>
-    </GestureHandlerRootView>
+    <RootStoreProvider value={rootStore}>
+      <Routes.Root />
+      <Toast.ToastContainer />
+    </RootStoreProvider>
   )
 }
 
diff --git a/src/state/lib/api.ts b/src/state/lib/api.ts
index bb3ef5d1a..df0047991 100644
--- a/src/state/lib/api.ts
+++ b/src/state/lib/api.ts
@@ -97,7 +97,7 @@ export async function unrepost(adx: AdxClient, user: string, uri: string) {
   return numDels > 0
 }
 
-type WherePred = (record: GetRecordResponseValidated) => Boolean
+type WherePred = (_record: GetRecordResponseValidated) => Boolean
 async function deleteWhere(
   coll: AdxRepoCollectionClient,
   schema: SchemaOpt,
@@ -115,7 +115,7 @@ async function deleteWhere(
   return toDelete.length
 }
 
-type IterateAllCb = (record: GetRecordResponseValidated) => void
+type IterateAllCb = (_record: GetRecordResponseValidated) => void
 async function iterateAll(
   coll: AdxRepoCollectionClient,
   schema: SchemaOpt,
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index c7ce3f4c7..57dc0ef86 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -1,9 +1,8 @@
 import React, {useState, forwardRef, useImperativeHandle} from 'react'
 import {observer} from 'mobx-react-lite'
 import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native'
-import Toast from 'react-native-root-toast'
-// @ts-ignore no type definition -prf
-import ProgressCircle from 'react-native-progress/Circle'
+import Toast from '../util/Toast'
+import ProgressCircle from '../util/ProgressCircle'
 import {useStores} from '../../../state'
 import {s} from '../../lib/styles'
 import * as apilib from '../../../state/lib/api'
@@ -12,75 +11,71 @@ const MAX_TEXT_LENGTH = 256
 const WARNING_TEXT_LENGTH = 200
 const DANGER_TEXT_LENGTH = 255
 
-export const Composer = observer(
-  forwardRef(function Composer(
-    {
-      replyTo,
-    }: {
-      replyTo: string | undefined
-    },
-    ref,
-  ) {
-    const store = useStores()
-    const [text, setText] = useState('')
+export const Composer = forwardRef(function Composer(
+  {
+    replyTo,
+  }: {
+    replyTo: string | undefined
+  },
+  ref,
+) {
+  const store = useStores()
+  const [text, setText] = useState('')
 
-    const onChangeText = (newText: string) => {
-      if (newText.length > MAX_TEXT_LENGTH) {
-        setText(newText.slice(0, MAX_TEXT_LENGTH))
-      } else {
-        setText(newText)
-      }
+  const onChangeText = (newText: string) => {
+    if (newText.length > MAX_TEXT_LENGTH) {
+      setText(newText.slice(0, MAX_TEXT_LENGTH))
+    } else {
+      setText(newText)
     }
+  }
 
-    useImperativeHandle(ref, () => ({
-      async publish() {
-        if (text.trim().length === 0) {
-          return false
-        }
-        await apilib.post(store.api, 'alice.com', text, replyTo)
-        Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been created`, {
-          duration: Toast.durations.LONG,
-          position: Toast.positions.TOP,
-          shadow: true,
-          animation: true,
-          hideOnPress: true,
-        })
-        return true
-      },
-    }))
+  useImperativeHandle(ref, () => ({
+    async publish() {
+      if (text.trim().length === 0) {
+        return false
+      }
+      await apilib.post(store.api, 'alice.com', text, replyTo)
+      Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been created`, {
+        duration: Toast.durations.LONG,
+        position: Toast.positions.TOP,
+        shadow: true,
+        animation: true,
+        hideOnPress: true,
+      })
+      return true
+    },
+  }))
 
-    const progressColor =
-      text.length > DANGER_TEXT_LENGTH
-        ? '#e60000'
-        : text.length > WARNING_TEXT_LENGTH
-        ? '#f7c600'
-        : undefined
+  const progressColor =
+    text.length > DANGER_TEXT_LENGTH
+      ? '#e60000'
+      : text.length > WARNING_TEXT_LENGTH
+      ? '#f7c600'
+      : undefined
 
-    return (
-      <KeyboardAvoidingView style={styles.outer} behavior="padding">
-        <TextInput
-          multiline
-          scrollEnabled
-          onChangeText={text => onChangeText(text)}
-          value={text}
-          placeholder={
-            replyTo ? 'Write your reply' : "What's new in the scene?"
-          }
-          style={styles.textInput}
-        />
-        <View style={[s.flexRow, s.pt10, s.pb10, s.pr5]}>
-          <View style={s.flex1} />
-          <View>
-            <ProgressCircle
-              color={progressColor}
-              progress={text.length / MAX_TEXT_LENGTH}
-            />
-          </View>
+  return (
+    <KeyboardAvoidingView style={styles.outer} behavior="padding">
+      <TextInput
+        multiline
+        scrollEnabled
+        onChangeText={text => onChangeText(text)}
+        value={text}
+        placeholder={replyTo ? 'Write your reply' : "What's new in the scene?"}
+        style={styles.textInput}
+      />
+      <View style={[s.flexRow, s.pt10, s.pb10, s.pr5]}>
+        <View style={s.flex1} />
+        <View>
+          <ProgressCircle
+            color={progressColor}
+            progress={text.length / MAX_TEXT_LENGTH}
+          />
         </View>
-      </KeyboardAvoidingView>
-    )
-  }),
-)
+      </View>
+    </KeyboardAvoidingView>
+  )
+})
 
 const styles = StyleSheet.create({
   outer: {
diff --git a/src/view/com/feed/Feed.tsx b/src/view/com/feed/Feed.tsx
index 8283e275e..6787b51ae 100644
--- a/src/view/com/feed/Feed.tsx
+++ b/src/view/com/feed/Feed.tsx
@@ -4,7 +4,7 @@ import {Text, View, FlatList} from 'react-native'
 import {OnNavigateContent} from '../../routes/types'
 import {FeedViewModel, FeedViewItemModel} from '../../../state/models/feed-view'
 import {FeedItem} from './FeedItem'
-import {ShareBottomSheet} from '../sheets/SharePost'
+import {ShareModal} from '../modals/SharePost'
 
 export const Feed = observer(function Feed({
   feed,
@@ -13,7 +13,7 @@ export const Feed = observer(function Feed({
   feed: FeedViewModel
   onNavigateContent: OnNavigateContent
 }) {
-  const shareSheetRef = useRef<{open: (uri: string) => void}>()
+  const shareSheetRef = useRef<{open: (_uri: string) => void}>()
 
   const onPressShare = (uri: string) => {
     shareSheetRef.current?.open(uri)
@@ -52,7 +52,7 @@ export const Feed = observer(function Feed({
         />
       )}
       {feed.isEmpty && <Text>This feed is empty!</Text>}
-      <ShareBottomSheet ref={shareSheetRef} />
+      <ShareModal ref={shareSheetRef} />
     </View>
   )
 })
diff --git a/src/view/com/feed/FeedItem.tsx b/src/view/com/feed/FeedItem.tsx
index 018b58179..616fb0aca 100644
--- a/src/view/com/feed/FeedItem.tsx
+++ b/src/view/com/feed/FeedItem.tsx
@@ -16,7 +16,7 @@ export const FeedItem = observer(function FeedItem({
 }: {
   item: FeedViewItemModel
   onNavigateContent: OnNavigateContent
-  onPressShare: (uri: string) => void
+  onPressShare: (_uri: string) => void
 }) {
   const record = item.record as unknown as bsky.Post.Record
 
diff --git a/src/view/com/sheets/SharePost.tsx b/src/view/com/modals/SharePost.native.tsx
index b0f22c54e..0e99bd4d1 100644
--- a/src/view/com/sheets/SharePost.tsx
+++ b/src/view/com/modals/SharePost.native.tsx
@@ -19,14 +19,11 @@ import Animated, {
   interpolate,
   useAnimatedStyle,
 } from 'react-native-reanimated'
-import Toast from 'react-native-root-toast'
+import Toast from '../util/Toast'
 import Clipboard from '@react-native-clipboard/clipboard'
 import {s} from '../../lib/styles'
 
-export const ShareBottomSheet = forwardRef(function ShareBottomSheet(
-  {}: {},
-  ref,
-) {
+export const ShareModal = forwardRef(function ShareModal({}: {}, ref) {
   const [isOpen, setIsOpen] = useState<boolean>(false)
   const [uri, setUri] = useState<string>('')
   const bottomSheetRef = useRef<BottomSheet>(null)
@@ -41,6 +38,9 @@ export const ShareBottomSheet = forwardRef(function ShareBottomSheet(
 
   const onPressCopy = () => {
     Clipboard.setString(uri)
+    console.log('showing')
+    console.log(Toast)
+    console.log(Toast.show)
     Toast.show('Link copied', {
       position: Toast.positions.TOP,
     })
@@ -56,7 +56,6 @@ export const ShareBottomSheet = forwardRef(function ShareBottomSheet(
   }
 
   const CustomBackdrop = ({animatedIndex, style}: BottomSheetBackdropProps) => {
-    console.log('hit!', animatedIndex.value)
     // animated variables
     const opacity = useAnimatedStyle(() => ({
       opacity: interpolate(
diff --git a/src/view/com/modals/SharePost.tsx b/src/view/com/modals/SharePost.tsx
new file mode 100644
index 000000000..d6d4bb5c1
--- /dev/null
+++ b/src/view/com/modals/SharePost.tsx
@@ -0,0 +1,57 @@
+import React, {forwardRef, useState, useImperativeHandle} from 'react'
+import {Button, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
+import {Modal} from './WebModal'
+import Toast from '../util/Toast'
+import {s} from '../../lib/styles'
+
+export const ShareModal = forwardRef(function ShareModal({}: {}, ref) {
+  const [isOpen, setIsOpen] = useState<boolean>(false)
+  const [uri, setUri] = useState<string>('')
+
+  useImperativeHandle(ref, () => ({
+    open(uri: string) {
+      console.log('sharing', uri)
+      setUri(uri)
+      setIsOpen(true)
+    },
+  }))
+
+  const onPressCopy = () => {
+    // TODO
+    Toast.show('Link copied', {
+      position: Toast.positions.TOP,
+    })
+  }
+  const onClose = () => {
+    setIsOpen(false)
+  }
+
+  return (
+    <>
+      {isOpen && (
+        <Modal onClose={onClose}>
+          <View>
+            <Text style={[s.textCenter, s.bold, s.mb10]}>Share this post</Text>
+            <Text style={[s.textCenter, s.mb10]}>{uri}</Text>
+            <Button title="Copy to clipboard" onPress={onPressCopy} />
+            <View style={s.p10}>
+              <TouchableOpacity onPress={onClose} style={styles.closeBtn}>
+                <Text style={s.textCenter}>Close</Text>
+              </TouchableOpacity>
+            </View>
+          </View>
+        </Modal>
+      )}
+    </>
+  )
+})
+
+const styles = StyleSheet.create({
+  closeBtn: {
+    width: '100%',
+    borderColor: '#000',
+    borderWidth: 1,
+    borderRadius: 4,
+    padding: 10,
+  },
+})
diff --git a/src/view/com/modals/WebModal.tsx b/src/view/com/modals/WebModal.tsx
new file mode 100644
index 000000000..fed2fae91
--- /dev/null
+++ b/src/view/com/modals/WebModal.tsx
@@ -0,0 +1,20 @@
+/**
+ * Use this for the Web build only.
+ * It's intended to replace the BottomSheet.
+ *
+ * Note: the dataSet properties are used to leverage custom CSS in public/index.html
+ */
+import React from 'react'
+// @ts-ignore no declarations available -prf
+import {TouchableWithoutFeedback, View} from 'react-native-web'
+
+type Props = {onClose: () => void}
+export const Modal: React.FC<Props> = ({onClose, children}) => {
+  return (
+    <TouchableWithoutFeedback onPress={onClose}>
+      <View dataSet={{'modal-overlay': 1}}>
+        <View dataSet={{'modal-container': 1}}>{children}</View>
+      </View>
+    </TouchableWithoutFeedback>
+  )
+}
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 784cc39d2..6191875c7 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -9,7 +9,7 @@ import {
 } from '../../../state/models/post-thread-view'
 import {useStores} from '../../../state'
 import {PostThreadItem} from './PostThreadItem'
-import {ShareBottomSheet} from '../sheets/SharePost'
+import {ShareModal} from '../modals/SharePost'
 import {s} from '../../lib/styles'
 
 const UPDATE_DELAY = 2e3 // wait 2s before refetching the thread for updates
@@ -24,7 +24,7 @@ export const PostThread = observer(function PostThread({
   const store = useStores()
   const [view, setView] = useState<PostThreadViewModel | undefined>()
   const [lastUpdate, setLastUpdate] = useState<number>(Date.now())
-  const shareSheetRef = useRef<{open: (uri: string) => void}>()
+  const shareSheetRef = useRef<{open: (_uri: string) => void}>()
 
   useEffect(() => {
     if (view?.params.uri === uri) {
@@ -94,7 +94,7 @@ export const PostThread = observer(function PostThread({
         refreshing={view.isRefreshing}
         onRefresh={onRefresh}
       />
-      <ShareBottomSheet ref={shareSheetRef} />
+      <ShareModal ref={shareSheetRef} />
     </View>
   )
 })
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 30a64bc0e..7ed00403b 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -25,7 +25,7 @@ export const PostThreadItem = observer(function PostThreadItem({
 }: {
   item: PostThreadViewPostModel
   onNavigateContent: OnNavigateContent
-  onPressShare: (uri: string) => void
+  onPressShare: (_uri: string) => void
 }) {
   const record = item.record as unknown as bsky.Post.Record
   const hasEngagement = item.likeCount || item.repostCount
diff --git a/src/view/com/util/ProgressCircle.native.tsx b/src/view/com/util/ProgressCircle.native.tsx
new file mode 100644
index 000000000..a09232b4b
--- /dev/null
+++ b/src/view/com/util/ProgressCircle.native.tsx
@@ -0,0 +1,3 @@
+// @ts-ignore no type definition -prf
+import ProgressCircle from 'react-native-progress/Circle'
+export default ProgressCircle
diff --git a/src/view/com/util/ProgressCircle.tsx b/src/view/com/util/ProgressCircle.tsx
new file mode 100644
index 000000000..0e425a6e6
--- /dev/null
+++ b/src/view/com/util/ProgressCircle.tsx
@@ -0,0 +1,20 @@
+import {View} from 'react-native'
+import {CircularProgressbar, buildStyles} from 'react-circular-progressbar'
+
+const ProgressCircle = ({
+  color,
+  progress,
+}: {
+  color?: string
+  progress: number
+}) => {
+  return (
+    <View style={{width: 20, height: 20}}>
+      <CircularProgressbar
+        value={progress * 100}
+        styles={buildStyles({pathColor: color || '#00f'})}
+      />
+    </View>
+  )
+}
+export default ProgressCircle
diff --git a/src/view/com/util/Toast.native.tsx b/src/view/com/util/Toast.native.tsx
new file mode 100644
index 000000000..4b9fd7f80
--- /dev/null
+++ b/src/view/com/util/Toast.native.tsx
@@ -0,0 +1,2 @@
+import Toast from 'react-native-root-toast'
+export default Toast
diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx
new file mode 100644
index 000000000..1726b71b3
--- /dev/null
+++ b/src/view/com/util/Toast.tsx
@@ -0,0 +1,62 @@
+/*
+ * Note: the dataSet properties are used to leverage custom CSS in public/index.html
+ */
+
+import React, {useState, useEffect} from 'react'
+// @ts-ignore no declarations available -prf
+import {Text, View} from 'react-native-web'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+
+interface ActiveToast {
+  text: string
+}
+type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void
+
+// globals
+// =
+let globalSetActiveToast: GlobalSetActiveToast | undefined
+let toastTimeout: NodeJS.Timeout | undefined
+
+// components
+// =
+type ToastContainerProps = {}
+const ToastContainer: React.FC<ToastContainerProps> = ({}) => {
+  const [activeToast, setActiveToast] = useState<ActiveToast | undefined>()
+  useEffect(() => {
+    globalSetActiveToast = (t: ActiveToast | undefined) => {
+      setActiveToast(t)
+    }
+  })
+  return (
+    <>
+      {activeToast && (
+        <View dataSet={{'toast-container': 1}}>
+          <FontAwesomeIcon icon="check" size={24} />
+          <Text>{activeToast.text}</Text>
+        </View>
+      )}
+    </>
+  )
+}
+
+// exports
+// =
+export default {
+  show(text: string, _opts: any) {
+    console.log('TODO: toast', text)
+    if (toastTimeout) {
+      clearTimeout(toastTimeout)
+    }
+    globalSetActiveToast?.({text})
+    toastTimeout = setTimeout(() => {
+      globalSetActiveToast?.(undefined)
+    }, 2e3)
+  },
+  positions: {
+    TOP: 0,
+  },
+  durations: {
+    LONG: 0,
+  },
+  ToastContainer,
+}
diff --git a/src/view/index.ts b/src/view/index.ts
index 4a469d263..645aa425b 100644
--- a/src/view/index.ts
+++ b/src/view/index.ts
@@ -4,6 +4,7 @@ import {library} from '@fortawesome/fontawesome-svg-core'
 import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft'
 import {faBars} from '@fortawesome/free-solid-svg-icons/faBars'
 import {faBell} from '@fortawesome/free-solid-svg-icons/faBell'
+import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'
 import {faComment} from '@fortawesome/free-regular-svg-icons/faComment'
 import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
 import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart'
@@ -39,6 +40,7 @@ export function setup() {
     faArrowLeft,
     faBars,
     faBell,
+    faCheck,
     faComment,
     faHeart,
     fasHeart,
diff --git a/todos.txt b/todos.txt
index 70807cfe2..221de14f3 100644
--- a/todos.txt
+++ b/todos.txt
@@ -11,4 +11,6 @@ Paul's todo list
   - *
 - Linking
   - Web linking
-  - App linking
\ No newline at end of file
+  - App linking
+- Housekeeping
+  - Remove moment.js -- it's too heavy a dependency
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 231bea511..082f054ff 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11300,6 +11300,11 @@ react-app-polyfill@^3.0.0:
     regenerator-runtime "^0.13.9"
     whatwg-fetch "^3.6.2"
 
+react-circular-progressbar@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/react-circular-progressbar/-/react-circular-progressbar-2.1.0.tgz#99e5ae499c21de82223b498289e96f66adb8fa3a"
+  integrity sha512-xp4THTrod4aLpGy68FX/k1Q3nzrfHUjUe5v6FsdwXBl3YVMwgeXYQKDrku7n/D6qsJA9CuunarAboC2xCiKs1g==
+
 react-dev-utils@^12.0.1:
   version "12.0.1"
   resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"