From ce191536bf7b2bc351366a268124e23d947dcff4 Mon Sep 17 00:00:00 2001 From: ammarahm-ed Date: Sat, 30 Oct 2021 14:02:58 +0500 Subject: [PATCH] refactor file & image picker --- .../src/views/Editor/tiny/toolbar/commands.js | 299 +---------------- .../src/views/Editor/tiny/toolbar/picker.js | 304 ++++++++++++++++++ 2 files changed, 307 insertions(+), 296 deletions(-) create mode 100644 apps/mobile/src/views/Editor/tiny/toolbar/picker.js diff --git a/apps/mobile/src/views/Editor/tiny/toolbar/commands.js b/apps/mobile/src/views/Editor/tiny/toolbar/commands.js index fecd572c6..e3f7fb80c 100644 --- a/apps/mobile/src/views/Editor/tiny/toolbar/commands.js +++ b/apps/mobile/src/views/Editor/tiny/toolbar/commands.js @@ -1,23 +1,4 @@ -import React from 'react'; -import {Platform, View} from 'react-native'; -import DocumentPicker from 'react-native-document-picker'; -import Sodium from 'react-native-sodium'; -import {launchCamera, launchImageLibrary} from 'react-native-image-picker'; -import RNFetchBlob from 'rn-fetch-blob'; -import {Attachment} from '../../../../components/AttachmentDialog'; -import {eSendEvent, ToastEvent} from '../../../../services/EventManager'; -import {editing} from '../../../../utils'; -import {db} from '../../../../utils/database'; -import { - eCloseProgressDialog, - eOpenProgressDialog -} from '../../../../utils/Events'; -import {sleep} from '../../../../utils/TimeUtils'; -import {EditorWebView, getNote} from '../../Functions'; -import tiny, {safeKeyboardDismiss} from '../tiny'; - -const FILE_SIZE_LIMIT = 500 * 1024 * 1024; -const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024; +import picker from './picker'; export const execCommands = { bold: `tinymce.activeEditor.execCommand('Bold');`, @@ -89,185 +70,8 @@ export const execCommands = { })();`, cl: `tinymce.activeEditor.execCommand('insertCheckList')`, - filepicker: async () => { - try { - let options = { - mode: 'import', - allowMultiSelection: false - }; - if (Platform.OS == 'ios') { - options.copyTo = 'cachesDirectory'; - } - - let key = await db.attachments.generateKey(); - - console.log('generated key for attachments: ', key); - - let file = await DocumentPicker.pick(options); - file = file[0]; - if (file.size > FILE_SIZE_LIMIT) { - ToastEvent.show({ - title: 'File too large', - message: 'The maximum allowed size per file is 512 MB', - type:'error' - }); - return; - } - - if (file.copyError) { - ToastEvent.show({ - heading: 'Failed to open file', - message: file.copyError, - type: 'error', - context: 'global' - }); - return; - } - - let uri = - Platform.OS === 'ios' - ? file.fileCopyUri.replace('file:///', '/') - : file.uri; - - eSendEvent(eOpenProgressDialog, { - title: 'Encrypting attachment', - paragraph: 'Please wait while we encrypt file for upload', - nowarn: true, - icon: 'attachment', - component: ( - - - - ) - }); - - let hash = await Sodium.hashFile({ - uri: uri, - type: 'url' - }); - console.log(hash); - let result = await attachFile(uri, hash, file.type, file.name); - console.log('attach file: ', result); - if (Platform.OS === 'ios') { - await RNFetchBlob.fs.unlink(uri); - } - setTimeout(() => { - eSendEvent(eCloseProgressDialog); - }, 1000); - if (!result) return; - tiny.call( - EditorWebView, - ` - (function() { - let file = ${JSON.stringify({ - hash: hash, - filename: file.name, - type: file.type, - size: file.size - })} - tinymce.activeEditor.execCommand('mceAttachFile',file); - setTimeout(function() { - tinymce.activeEditor.nodeChanged({selectionChange:true}) - },100) - })(); - ` - ); - } catch (e) { - ToastEvent.show({ - heading: e.message, - message: 'You need internet access to attach a file', - type: 'error', - context: 'global' - }); - console.log('attachment error: ', e); - } - }, - image: async () => { - if (editing.isFocused) { - safeKeyboardDismiss(); - await sleep(500); - editing.isFocused = true; - } - eSendEvent(eOpenProgressDialog, { - noProgress: true, - noIcon: true, - actionsArray: [ - { - action: async () => { - try { - let key = await db.attachments.generateKey(); - eSendEvent(eCloseProgressDialog); - await sleep(400); - launchCamera( - { - includeBase64: true, - maxWidth: 4000, - maxHeight: 4000, - quality: 0.8, - mediaType: 'photo' - }, - handleImageResponse - ); - } catch (e) { - ToastEvent.show({ - heading: e.message, - message: 'You need internet access to attach a file', - type: 'error', - context: 'global' - }); - console.log('attachment error:', e); - } - }, - actionText: 'Take photo', - icon: 'camera' - }, - { - action: async () => { - try { - let key = await db.attachments.generateKey(); - - eSendEvent(eCloseProgressDialog); - await sleep(400); - launchImageLibrary( - { - includeBase64: true, - maxWidth: 4000, - maxHeight: 4000, - quality: 0.8, - mediaType: 'photo', - selectionLimit: 1 - }, - handleImageResponse - ); - } catch (e) { - ToastEvent.show({ - heading: e.message, - message: 'You need internet access to attach a file', - type: 'error', - context: 'global' - }); - console.log('attachment error:', e); - } - }, - actionText: 'Select from gallery', - icon: 'image-multiple' - } - ] - }); - - return; - }, + filepicker: picker.file, + image: picker.image, video: `tinymce.activeEditor.execCommand('mceMedia')`, pre: ` tinymce.activeEditor.execCommand('mceInsertCodeBlock') @@ -331,103 +135,6 @@ export const execCommands = { });` }; -const handleImageResponse = async response => { - if ( - response.didCancel || - response.errorMessage || - !response.assets || - response.assets?.length === 0 - ) { - return; - } - - let image = response.assets[0]; - if (image.fileSize > IMAGE_SIZE_LIMIT) { - ToastEvent.show({ - title: 'File too large', - message: 'The maximum allowed size per image is 50 MB', - type:'error' - }); - return; - } - let b64 = `data:${image.type};base64, ` + image.base64; - let options; - if (Platform.OS === 'android') { - options = { - uri: image.uri, - type: 'url' - }; - } else { - options = { - data: image.base64, - type: 'base64' - }; - } - - let hash = await Sodium.hashFile(options); - console.log(hash); - tiny.call( - EditorWebView, - ` - (function(){ - let image = ${JSON.stringify({ - hash: hash, - type: image.type, - filename: image.fileName, - dataurl: b64, - size: image.fileSize - })} - tinymce.activeEditor.execCommand('mceAttachImage',image); - setTimeout(function() { - tinymce.activeEditor.nodeChanged({selectionChange:true}) - },100) - })(); - ` - ); - attachFile( - image.uri, - hash, - image.type, - image.fileName, - Platform.OS === 'ios' ? image.base64 : null - ); -}; - -async function attachFile(uri, hash, type, filename, b64) { - try { - let exists = db.attachments.exists(hash); - let encryptionInfo; - if (!exists) { - let key = await db.attachments.generateKey(); - let options = { - hash: hash - }; - if (Platform.OS === 'ios' && b64) { - options.data = b64; - options.type = 'base64'; - } else { - options.uri = uri; - options.type = 'url'; - } - - encryptionInfo = await Sodium.encryptFile(key, options); - encryptionInfo.type = type; - encryptionInfo.filename = filename; - encryptionInfo.alg = `xcha-argon2i13-s`; - encryptionInfo.size = encryptionInfo.length; - encryptionInfo.key = key; - } else { - encryptionInfo = {hash: hash}; - } - console.log(encryptionInfo); - await db.attachments.add(encryptionInfo, getNote()?.id); - return true; - } catch (e) { - console.log('attach file error: ', e); - return false; - } -} - const setFloat = float => `(function () { let node = tinymce.activeEditor.selection.getNode(); if (node.tagName === 'IMG') { diff --git a/apps/mobile/src/views/Editor/tiny/toolbar/picker.js b/apps/mobile/src/views/Editor/tiny/toolbar/picker.js new file mode 100644 index 000000000..91d3d63ab --- /dev/null +++ b/apps/mobile/src/views/Editor/tiny/toolbar/picker.js @@ -0,0 +1,304 @@ +import React from 'react'; +import {Platform, View} from 'react-native'; +import DocumentPicker from 'react-native-document-picker'; +import Sodium from 'react-native-sodium'; +import {launchCamera, launchImageLibrary} from 'react-native-image-picker'; +import RNFetchBlob from 'rn-fetch-blob'; +import {Attachment} from '../../../../components/AttachmentDialog'; +import {eSendEvent, ToastEvent} from '../../../../services/EventManager'; +import {editing} from '../../../../utils'; +import {db} from '../../../../utils/database'; +import { + eCloseProgressDialog, + eOpenProgressDialog +} from '../../../../utils/Events'; +import {sleep} from '../../../../utils/TimeUtils'; +import {EditorWebView, getNote} from '../../Functions'; +import tiny, {safeKeyboardDismiss} from '../tiny'; + +const FILE_SIZE_LIMIT = 500 * 1024 * 1024; +const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024; + +const showEncryptionSheet = () => { + eSendEvent(eOpenProgressDialog, { + title: 'Encrypting attachment', + paragraph: 'Please wait while we encrypt file for upload', + nowarn: true, + icon: 'attachment', + component: ( + + + + ) + }); +}; + +const santizeUri = uri => { + uri = decodeURI(uri); + uri = Platform.OS === 'ios' ? uri.replace('file:///', '/') : uri; + return uri; +}; + +const file = async () => { + try { + let options = { + mode: 'import', + allowMultiSelection: false + }; + if (Platform.OS == 'ios') { + options.copyTo = 'cachesDirectory'; + } + + let key = await db.attachments.generateKey(); + + console.log('generated key for attachments: ', key); + let file; + try { + file = await DocumentPicker.pick(options); + } catch (e) { + return; + } + + file = file[0]; + if (file.type === 'image/') { + ToastEvent.show({ + title: 'Type not supported', + message: 'Please add images from gallery or camera picker.', + type: 'error', + }); + return; + } + if (file.size > FILE_SIZE_LIMIT) { + ToastEvent.show({ + title: 'File too large', + message: 'The maximum allowed size per file is 500 MB', + type: 'error' + }); + return; + } + + if (file.copyError) { + ToastEvent.show({ + heading: 'Failed to open file', + message: file.copyError, + type: 'error', + context: 'global' + }); + return; + } + + let uri = Platform.OS === 'ios' ? file.fileCopyUri : file.uri; + uri = santizeUri(uri); + showEncryptionSheet(); + let hash = await Sodium.hashFile({ + uri: uri, + type: 'url' + }); + console.log(uri); + let result = await attachFile(uri, hash, file.type, file.name, 'file'); + console.log('attach file: ', result); + + setTimeout(() => { + eSendEvent(eCloseProgressDialog); + }, 1000); + if (!result) return; + tiny.call( + EditorWebView, + ` + (function() { + let file = ${JSON.stringify({ + hash: hash, + filename: file.name, + type: file.type, + size: file.size + })} + tinymce.activeEditor.execCommand('mceAttachFile',file); + setTimeout(function() { + tinymce.activeEditor.nodeChanged({selectionChange:true}) + },100) + })(); + ` + ); + } catch (e) { + ToastEvent.show({ + heading: e.message, + message: 'You need internet access to attach a file', + type: 'error', + context: 'global' + }); + console.log('attachment error: ', e); + } +}; + +const image = async () => { + if (editing.isFocused) { + safeKeyboardDismiss(); + await sleep(500); + editing.isFocused = true; + } + eSendEvent(eOpenProgressDialog, { + noProgress: true, + noIcon: true, + actionsArray: [ + { + action: async () => { + try { + await db.attachments.generateKey(); + eSendEvent(eCloseProgressDialog); + await sleep(400); + launchCamera( + { + includeBase64: true, + maxWidth: 4000, + maxHeight: 4000, + quality: 0.8, + mediaType: 'photo' + }, + handleImageResponse + ); + } catch (e) { + ToastEvent.show({ + heading: e.message, + message: 'You need internet access to attach a file', + type: 'error', + context: 'global' + }); + console.log('attachment error:', e); + } + }, + actionText: 'Take photo', + icon: 'camera' + }, + { + action: async () => { + try { + await db.attachments.generateKey(); + eSendEvent(eCloseProgressDialog); + await sleep(400); + launchImageLibrary( + { + includeBase64: true, + maxWidth: 4000, + maxHeight: 4000, + quality: 0.8, + mediaType: 'photo', + selectionLimit: 1 + }, + handleImageResponse + ); + } catch (e) { + ToastEvent.show({ + heading: e.message, + message: 'You need internet access to attach a file', + type: 'error', + context: 'global' + }); + console.log('attachment error:', e); + } + }, + actionText: 'Select from gallery', + icon: 'image-multiple' + } + ] + }); + + return; +}; + +const handleImageResponse = async response => { + if ( + response.didCancel || + response.errorMessage || + !response.assets || + response.assets?.length === 0 + ) { + return; + } + + let image = response.assets[0]; + if (image.fileSize > IMAGE_SIZE_LIMIT) { + ToastEvent.show({ + title: 'File too large', + message: 'The maximum allowed size per image is 50 MB', + type: 'error' + }); + return; + } + let b64 = `data:${image.type};base64, ` + image.base64; + let uri = image.uri; + uri = decodeURI(uri); + console.log(uri); + let hash = await Sodium.hashFile({ + uri: uri, + type: 'url' + }); + tiny.call( + EditorWebView, + ` + (function(){ + let image = ${JSON.stringify({ + hash: hash, + type: image.type, + filename: image.fileName, + dataurl: b64, + size: image.fileSize + })} + tinymce.activeEditor.execCommand('mceAttachImage',image); + setTimeout(function() { + tinymce.activeEditor.nodeChanged({selectionChange:true}) + },100) + })(); + ` + ); + attachFile(uri, hash, image.type, image.fileName, 'image'); +}; + +async function attachFile(uri, hash, type, filename) { + try { + let exists = db.attachments.exists(hash); + let encryptionInfo; + if (!exists) { + let key = await db.attachments.generateKey(); + encryptionInfo = await Sodium.encryptFile(key, { + uri: uri, + type: 'url', + hash: hash + }); + encryptionInfo.type = type; + encryptionInfo.filename = filename; + encryptionInfo.alg = `xcha-stream`; + encryptionInfo.size = encryptionInfo.length; + encryptionInfo.key = key; + } else { + encryptionInfo = {hash: hash}; + } + + console.log(encryptionInfo); + await db.attachments.add(encryptionInfo, getNote()?.id); + if (Platform.OS === 'ios') await RNFetchBlob.fs.unlink(uri); + + return true; + } catch (e) { + console.log('attach file error: ', e); + if (Platform.OS === 'ios') { + await RNFetchBlob.fs.unlink(uri); + } + return false; + } +} + +export default { + file, + image +};