From de3c039741df25aedf363689fad60e45ecf03925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 22 Feb 2021 15:07:02 +0100 Subject: [PATCH] Add feature to export notes --- .../notes/generated/BasePackageList.java | 1 + package.json | 7 + src/App.tsx | 1 + src/RootNavigator.tsx | 8 + src/RootStackParamList.tsx | 1 + src/import-export/export.android.ts | 20 ++ src/import-export/export.ios.ts | 19 ++ src/import-export/export.ts | 9 + src/import-export/export.web.ts | 17 ++ src/import-export/index.ts | 1 + src/import-export/share.native.ts | 6 +- src/import-export/utils.ts | 35 ++- src/screens/ExportScreen.tsx | 217 ++++++++++++++++++ src/screens/SettingsScreen.tsx | 12 + yarn.lock | 74 +++++- 15 files changed, 417 insertions(+), 11 deletions(-) create mode 100644 src/import-export/export.android.ts create mode 100644 src/import-export/export.ios.ts create mode 100644 src/import-export/export.ts create mode 100644 src/import-export/export.web.ts create mode 100644 src/screens/ExportScreen.tsx diff --git a/android/app/src/main/java/com/etesync/notes/generated/BasePackageList.java b/android/app/src/main/java/com/etesync/notes/generated/BasePackageList.java index f63e73f..55ae08f 100644 --- a/android/app/src/main/java/com/etesync/notes/generated/BasePackageList.java +++ b/android/app/src/main/java/com/etesync/notes/generated/BasePackageList.java @@ -12,6 +12,7 @@ public List getPackageList() { new expo.modules.filesystem.FileSystemPackage(), new expo.modules.font.FontLoaderPackage(), new expo.modules.imageloader.ImageLoaderPackage(), + new expo.modules.intentlauncher.IntentLauncherPackage(), new expo.modules.permissions.PermissionsPackage(), new expo.modules.splashscreen.SplashScreenPackage(), new expo.modules.taskManager.TaskManagerPackage(), diff --git a/package.json b/package.json index 128ede7..7a57d1b 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,16 @@ "etebase": "^0.41.0", "expo-background-fetch": "^8.6.0", "expo-font": "^8.3.0", + "expo-intent-launcher": "^8.4.0", "expo-splash-screen": "^0.6.1", "expo-status-bar": "~1.0.2", "expo-task-manager": "^8.6.0", "expo-updates": "~0.3.2", + "file-saver": "^2.0.5", "highlight-words-core": "^1.2.2", "immutable": "^4.0.0-rc.12", + "js-yaml": "^4.0.0", + "jszip": "^3.6.0", "localforage": "^1.9.0", "minisearch": "^3.0.2", "moment": "^2.29.0", @@ -36,6 +40,7 @@ "react-dom": "16.13.1", "react-native": "0.63.4", "react-native-etebase": "^0.1.5", + "react-native-fs": "^2.16.6", "react-native-gesture-handler": "~1.9.0", "react-native-get-random-values": "^1.5.0", "react-native-keyboard-aware-scroll-view": "^0.9.2", @@ -61,7 +66,9 @@ "@babel/core": "~7.9.0", "@expo/webpack-config": "^0.12.53", "@types/color": "^3.0.1", + "@types/file-saver": "^2.0.1", "@types/highlight-words-core": "^1.2.0", + "@types/js-yaml": "^4.0.0", "@types/markdown-it": "^12.0.0", "@types/react": "~16.9.35", "@types/react-dom": "~16.9.8", diff --git a/src/App.tsx b/src/App.tsx index fbe132f..6114d8d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -46,6 +46,7 @@ const rootStackScreens: RootStackScreens = { Settings: "settings", About: "settings/about", Password: "settings/password", + Export: "settings/export", DebugLogs: "settings/logs", AccountWizard: "account-wizard", "404": "*", diff --git a/src/RootNavigator.tsx b/src/RootNavigator.tsx index e00cdde..2d54193 100644 --- a/src/RootNavigator.tsx +++ b/src/RootNavigator.tsx @@ -14,6 +14,7 @@ import SettingsScreen from "./screens/SettingsScreen"; import AboutScreen from "./screens/AboutScreen"; import ChangePasswordScreen from "./screens/ChangePasswordScreen"; import DebugLogsScreen from "./screens/DebugLogsScreen"; +import ExportScreen from "./screens/ExportScreen"; import HomeScreen from "./screens/HomeScreen"; import NoteEditScreen from "./screens/NoteEditScreen"; import NotePropertiesScreen from "./screens/NotePropertiesScreen"; @@ -158,6 +159,13 @@ export default React.memo(function RootNavigator() { title: "Change Your Account Password", }} /> + )} diff --git a/src/RootStackParamList.tsx b/src/RootStackParamList.tsx index 2c8f309..8710fd6 100644 --- a/src/RootStackParamList.tsx +++ b/src/RootStackParamList.tsx @@ -29,6 +29,7 @@ export type RootStackParamList = { Invitations: undefined; Settings: undefined; Password: undefined; + Export: undefined; About: undefined; DebugLogs: undefined; AccountWizard: undefined; diff --git a/src/import-export/export.android.ts b/src/import-export/export.android.ts new file mode 100644 index 0000000..cfaf7f7 --- /dev/null +++ b/src/import-export/export.android.ts @@ -0,0 +1,20 @@ +import * as Etebase from "etebase"; +import * as IntentLauncher from "expo-intent-launcher"; +import RNFS from "react-native-fs"; +import { getItemsZip } from "./utils"; + +export function canExport() { + return true; +} + +export async function exportItems(items: Etebase.Item[]) { + const content = await getItemsZip(items, "base64"); + const result = await IntentLauncher.startActivityAsync("android.intent.action.CREATE_DOCUMENT", { + type: "application/zip", + category: "android.intent.category.OPENABLE", + extra: { "android.intent.extra.TITLE": "etesync-notes-export.zip" }, + }); + if (result.resultCode === IntentLauncher.ResultCode.Success && result?.data) { + await RNFS.writeFile(result.data, content, "base64"); + } +} \ No newline at end of file diff --git a/src/import-export/export.ios.ts b/src/import-export/export.ios.ts new file mode 100644 index 0000000..a1160d7 --- /dev/null +++ b/src/import-export/export.ios.ts @@ -0,0 +1,19 @@ +import * as Etebase from "etebase"; +import { Share } from "react-native"; +import RNFS from "react-native-fs"; +import { getItemsZip } from "./utils"; + +export function canExport() { + return true; +} + +export async function exportItems(items: Etebase.Item[]) { + const content = await getItemsZip(items, "base64"); + const path = `${RNFS.CachesDirectoryPath}/etesync-notes-export.zip`; + + await RNFS.writeFile(path, content, "base64"); + + await Share.share({ url: path }); + + await RNFS.unlink(path); +} \ No newline at end of file diff --git a/src/import-export/export.ts b/src/import-export/export.ts new file mode 100644 index 0000000..fc8d64b --- /dev/null +++ b/src/import-export/export.ts @@ -0,0 +1,9 @@ +import * as Etebase from "etebase"; + +export function canExport() { + return false; +} + +export async function exportItems(items: Etebase.Item[]) { + throw Error(`Cannot export ${items.length} items. Exporting is not implemented on this Platform`); +} \ No newline at end of file diff --git a/src/import-export/export.web.ts b/src/import-export/export.web.ts new file mode 100644 index 0000000..9ac2df8 --- /dev/null +++ b/src/import-export/export.web.ts @@ -0,0 +1,17 @@ +import * as Etebase from "etebase"; +import FileSaver from "file-saver"; +import { getItemsZip } from "./utils"; + +export function canExport() { + try { + const canBlob = !!new Blob; + return canBlob; + } catch (e) { + return false; + } +} + +export async function exportItems(items: Etebase.Item[]) { + const blob = await getItemsZip(items, "blob"); + FileSaver.saveAs(blob, "etesync-notes-export.zip"); +} \ No newline at end of file diff --git a/src/import-export/index.ts b/src/import-export/index.ts index 689e2f3..15b09ab 100644 --- a/src/import-export/index.ts +++ b/src/import-export/index.ts @@ -1 +1,2 @@ +export * from "./export"; export * from "./share"; \ No newline at end of file diff --git a/src/import-export/share.native.ts b/src/import-export/share.native.ts index ee5cb3b..9a726f7 100644 --- a/src/import-export/share.native.ts +++ b/src/import-export/share.native.ts @@ -9,11 +9,9 @@ export function canShare() { export async function shareItem(item: Etebase.Item) { const { name, content } = await getItemData(item); - - // Adding the name as a title at the beginning of the content because it is not shared otherwise - const message = `# ${name}\n\n${content}`; + await Share.share({ - message, + message: content, title: name, }); } \ No newline at end of file diff --git a/src/import-export/utils.ts b/src/import-export/utils.ts index f2e84be..f586fab 100644 --- a/src/import-export/utils.ts +++ b/src/import-export/utils.ts @@ -1,10 +1,33 @@ import * as Etebase from "etebase"; +import YAML from "js-yaml"; +import JSZip from "jszip"; -export async function getItemData(item: Etebase.Item) { - const { name } = item.getMeta(); - const content = await item.getContent(Etebase.OutputFormat.String); - return { - name, - content, +export async function getItemData(item: Etebase.Item, format = "") { + const data = { + name: item.getMeta().name, + content: "", }; + const content = await item.getContent(Etebase.OutputFormat.String); + + switch (format) { + case "export": { + const frontmatter = YAML.dump({ title: data.name }); + data.content = `---\n${frontmatter}---\n\n${content}`; + break; + } + default: { + data.content = `# ${data.name}\n\n${content}`; + } + } + return data; } + +export async function getItemsZip(items: Etebase.Item[], format: T) { + const zip = new JSZip(); + + for (const item of items) { + const itemData = await getItemData(item, "export"); + zip.file(`${itemData.name?.replace(/[/\\]/g, "-")}.md`, itemData.content); + } + return zip.generateAsync({ type: format }); +} \ No newline at end of file diff --git a/src/screens/ExportScreen.tsx b/src/screens/ExportScreen.tsx new file mode 100644 index 0000000..8fb984f --- /dev/null +++ b/src/screens/ExportScreen.tsx @@ -0,0 +1,217 @@ +import * as React from "react"; +import { StyleSheet, FlatList, View } from "react-native"; +import { Checkbox, FAB, List } from "react-native-paper"; +import { useSelector } from "react-redux"; +import * as Etebase from "etebase"; + +import { useSyncGate } from "../SyncGate"; +import { StoreState } from "../store"; +import { useCredentials } from "../credentials"; + +import { useLoading } from "../helpers"; +import { exportItems } from "../import-export"; +import { useTheme } from "../theme"; +import ErrorOrLoadingDialog from "../widgets/ErrorOrLoadingDialog"; + +type MetaItem = { + meta: Etebase.ItemMetadata; +}; + +function sortName(aIn: MetaItem, bIn: MetaItem) { + const a = aIn.meta.name!; + const b = bIn.meta.name!; + return a.localeCompare(b); +} + +type Collection = { + uid: string; + name: string; + items: { + [uid: string]: Item; + }; +}; + +type Item = { + name: string; +}; + +export default function NoteListScreen() { + const cacheCollections = useSelector((state: StoreState) => state.cache.collections); + const cacheItems = useSelector((state: StoreState) => state.cache.items); + const syncGate = useSyncGate(); + const { colors } = useTheme(); + const etebase = useCredentials()!; + const [selected, setSelected] = React.useState<{ [colUid: string] : Set }>({}); + const [loading, error, setPromise] = useLoading(); + + const entriesList = React.useMemo(() => { + const ret: Collection[] = []; + const sel = { ...selected }; + const collections = cacheCollections.sort(sortName); + + for (const [uid, col] of collections.entries()) { + const items = {}; + const itemsList = cacheItems.get(uid)!.filter((item) => !item.isDeleted).sort(sortName); + + for (const [uid, item] of itemsList.entries()) { + items[uid] = { name: item.meta.name! }; + } + + ret.push({ uid, name: col.meta.name!, items }); + if (!sel[uid]) { + sel[uid] = new Set(); + } + } + + setSelected(sel); + return ret; + }, [cacheCollections, cacheItems]); + + const hasSelected = React.useMemo(() => { + for (const itemList of Object.values(selected)) { + if (itemList.size > 0) { + return true; + } + } + + return false; + }, [selected]); + + if (syncGate) { + return syncGate; + } + + function renderEntry(params: { item: Collection }) { + const col = params.item; + const sel = new Set([...selected[col.uid]]); + const length = Object.keys(col.items).length; + const status = (length > 0 && sel?.size === length) ? "checked" : ((sel?.size > 0) ? "indeterminate" : "unchecked"); + + return ( + + + { + const newSel = { ...selected }; + newSel[col.uid] = (status === "checked") ? new Set() : new Set(Object.keys(col.items)); + setSelected(newSel); + }} + /> + + + } + style={{ padding: 0 }} + theme={{ colors: { primary: colors.accentText } }} + > + {(length > 0) ? Object.entries(col.items).map(([itemUid, item]) => ( + ( + + + + )} + onPress={() => { + sel.has(itemUid) ? sel.delete(itemUid) : sel.add(itemUid); + const newSel = { ...selected }; + newSel[col.uid] = sel; + setSelected(newSel); + }} + style={{ padding: 0 }} + /> + )) : ( + + )} + + + + ); + } + + function onExport() { + setPromise(async () => { + const items: Etebase.Item[] = []; + const colMgr = etebase.getCollectionManager(); + + for (const [colUid, itemList] of Object.entries(selected)) { + const col = colMgr.cacheLoad(cacheCollections.get(colUid)!.cache); + const itemMgr = colMgr.getItemManager(col); + const itemsList = cacheItems.get(colUid)!.filter((_item, uid) => itemList.has(uid)); + + for (const cacheItem of itemsList.values()) { + items.push(itemMgr.cacheLoad(cacheItem!.cache)); + } + } + + await exportItems(items); + }); + } + + return ( + <> + setPromise(undefined)} + /> + item.uid} + renderItem={renderEntry} + maxToRenderPerBatch={10} + ListEmptyComponent={() => ( + + )} + /> + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: "row", + }, + colCheckboxContainer: { + height: 40, + marginVertical: 8, + paddingLeft: 16, + }, + itemCheckboxContainer: { + width: 40, + height: 40, + marginHorizontal: 8, + justifyContent: "center", + alignItems: "center", + }, + accordionContainer: { + flexGrow: 1, + }, + fab: { + position: "absolute", + margin: 16, + right: 0, + bottom: 0, + }, +}); diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index e589e8e..31c6de1 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -25,6 +25,7 @@ import FontSelector from "../widgets/FontSelector"; import Select from "../widgets/Select"; import { RootStackParamList } from "../RootStackParamList"; import { useTheme } from "../theme"; +import { canExport } from "../import-export"; function DarkModePreferenceSelector() { const dispatch = useDispatch(); @@ -278,6 +279,17 @@ const SettingsScreen = function _SettingsScreen() { description="Change your account's password" onPress={() => { navigation.navigate("Password") }} /> + { + if (canExport()) { + navigation.navigate("Export"); + } else { + dispatch(pushMessage({ message: `Error: Export is not implemented on your platform`, severity: "error" })); + } + }} + /> )} diff --git a/yarn.lock b/yarn.lock index c79abc0..4fdb02c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2848,6 +2848,11 @@ dependencies: "@types/color-convert" "*" +"@types/file-saver@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.1.tgz#e18eb8b069e442f7b956d313f4fadd3ef887354e" + integrity sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw== + "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -2911,6 +2916,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/js-yaml@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.0.tgz#d1a11688112091f2c711674df3a65ea2f47b5dfb" + integrity sha512-4vlpCM5KPCL5CfGmTbpjwVKbISRYhduEJvvUWsH5EB7QInhEj94XPZ3ts/9FPiLZFqYO0xoW4ZL8z2AabTGgJA== + "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" @@ -3592,6 +3602,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" @@ -3977,6 +3992,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs= + base64-js@^1.0.2: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -6099,6 +6119,11 @@ expo-image-loader@~1.3.0: resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-1.3.0.tgz#06982a1a02f443ba6afa2cc7a4f0ccebc26a5415" integrity sha512-kn+9hm42TtHi1wFEp/1nq63vp33/cIbypI2hYjGsL22PAC9yk1go1hs/ktKgVWVlgmi0ruwR09SrM6ndOs7s7w== +expo-intent-launcher@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/expo-intent-launcher/-/expo-intent-launcher-8.4.0.tgz#c413a3aca06db2a02f3f7b0e40f5ffebc9ff63ed" + integrity sha512-nlsNEf7XpqWWXvGy6Bv8llGP348yvgg3dpCBIMveCosC49IjwDeXVJPT0VZWr6Q8leM/n+e+BWBDRiU6mfuwQA== + expo-permissions@~10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/expo-permissions/-/expo-permissions-10.0.0.tgz#5b31c54d561d00c7e46cd02321bc3704c51c584b" @@ -6363,6 +6388,11 @@ file-loader@~6.0.0: loader-utils "^2.0.0" schema-utils "^2.6.5" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + file-type@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" @@ -8242,6 +8272,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" + integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -8396,6 +8433,16 @@ jsx-ast-utils@^2.4.1: array-includes "^3.1.1" object.assign "^4.1.0" +jszip@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" + integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + just-curry-it@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5" @@ -8513,6 +8560,13 @@ lie@3.1.1: dependencies: immediate "~3.0.5" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -9985,7 +10039,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@^1.0.5, pako@~1.0.5: +pako@^1.0.5, pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -10948,6 +11002,14 @@ react-native-fit-image@^1.5.5: dependencies: prop-types "^15.5.10" +react-native-fs@^2.16.6: + version "2.16.6" + resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.16.6.tgz#2901789a43210a35a0ef0a098019bbef3af395fd" + integrity sha512-ZWOooD1AuFoAGY3HS2GY7Qx2LZo4oIg6AK0wbC68detxwvX75R/q9lRqThXNKP6vIo2VHWa0fYUo/SrLw80E8w== + dependencies: + base-64 "^0.1.0" + utf8 "^3.0.0" + react-native-gesture-handler@~1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.9.0.tgz#e441b1c0277c3fd4ca3e5c58fdd681e2f0ceddf0" @@ -11813,6 +11875,11 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -13069,6 +13136,11 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + utif@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759"