feat: attachment download & progress from editor

This commit is contained in:
ammarahm-ed
2021-10-05 10:31:16 +05:00
parent c236809ca1
commit 0b70f39fa3
6 changed files with 177 additions and 224 deletions

View File

@@ -124,6 +124,9 @@ function init_tiny(size) {
directionality: EDITOR_SETTINGS.directionality,
skin_url: 'dist/skins/notesnook',
content_css: 'dist/skins/notesnook',
attachmenthandler_download_attachment:(hash) => {
reactNativeEventHandler('attachment_download',hash);
},
plugins: [
'checklist advlist autolink textpattern hr lists link noneditable image',
'searchreplace codeblock inlinecode keyboardquirks attachmentshandler',
@@ -177,24 +180,6 @@ function init_tiny(size) {
.h {
display: none;
}
.img_float_left {
float:left;
}
.img_float_right {
float:right;
}
.img_float_none {
float:none;
}
.img_size_one {
width:100%;
}
.img_size_two {
width:50%;
}
.img_size_three {
width:25%;
}
span.diff-del {
background-color: #FDB0C0;
}

View File

@@ -1,6 +1,9 @@
import React, {useEffect, useRef, useState} from 'react';
import {Platform, ScrollView, Text, View} from 'react-native';
import {Platform, TouchableOpacity, View} from 'react-native';
import {FlatList} from 'react-native-gesture-handler';
import * as Progress from 'react-native-progress';
import * as ScopedStorage from 'react-native-scoped-storage';
import Sodium from 'react-native-sodium';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {useTracked} from '../../provider';
import {useAttachmentStore} from '../../provider/stores';
@@ -12,24 +15,15 @@ import {
import {db} from '../../utils/database';
import {
eCloseAttachmentDialog,
eCloseTagsDialog,
eOpenAttachmentsDialog,
eOpenTagsDialog
eOpenAttachmentsDialog
} from '../../utils/Events';
import filesystem from '../../utils/filesystem';
import {SIZE} from '../../utils/SizeUtils';
import {sleep} from '../../utils/TimeUtils';
import Storage from '../../utils/storage';
import {ActionIcon} from '../ActionIcon';
import ActionSheetWrapper from '../ActionSheetComponent/ActionSheetWrapper';
import DialogHeader from '../Dialog/dialog-header';
import {PressableButton} from '../PressableButton';
import Heading from '../Typography/Heading';
import Paragraph from '../Typography/Paragraph';
import * as Progress from 'react-native-progress';
import filesystem from '../../utils/filesystem';
import * as ScopedStorage from 'react-native-scoped-storage';
import RNFetchBlob from 'rn-fetch-blob';
import Sodium from 'react-native-sodium';
import Storage from '../../utils/storage';
export const AttachmentDialog = () => {
const [state] = useTracked();
@@ -142,7 +136,6 @@ const Attachment = ({attachment, note, setNote}) => {
const [state] = useTracked();
const colors = state.colors;
const progress = useAttachmentStore(state => state.progress);
const setProgress = useAttachmentStore(state => state.setProgress);
const [currentProgress, setCurrentProgress] = useState(null);
const onPress = async () => {
@@ -151,57 +144,22 @@ const Attachment = ({attachment, note, setNote}) => {
useAttachmentStore.getState().remove(attachment.metadata.hash);
return;
}
let folder = {};
if (Platform.OS === 'android') {
folder = await ScopedStorage.openDocumentTree(false);
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir('/Downloads/');
}
try {
setCurrentProgress({
value: 0,
percent: '0%'
});
await db.fs.downloadFile(
attachment.metadata.hash,
attachment.metadata.hash
);
let key = await db.user.getEncryptionKey();
let info = {
iv: attachment.iv,
salt: attachment.salt,
length: attachment.length,
alg: attachment.alg,
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
fileName: attachment.metadata.filename,
uri: folder.uri
};
await Sodium.decryptFile(key, info, false);
ToastEvent.show({
heading: 'Download successful',
message: attachment.metadata.filename + 'downloaded',
type: 'success',
context: 'local'
});
} catch (e) {
console.log('download attachment error: ', e);
useAttachmentStore.getState().remove(attachment.metadata.hash);
}
filesystem.downloadAttachment(attachment.metadata.hash);
};
useEffect(() => {
let prog = progress[attachment.metadata.hash];
if (prog && prog.type === 'download') {
prog = prog.recieved / prog.total;
if (prog) {
let type = prog.type;
let loaded = prog.type === 'download' ? prog.recieved : prog.sent;
prog = loaded / prog.total;
prog = (prog * 100).toFixed(0);
console.log('progress: ', prog);
console.log(prog);
setCurrentProgress({
value: prog,
percent: prog + '%'
percent: prog + '%',
type: type
});
} else {
setCurrentProgress(null);
@@ -263,30 +221,38 @@ const Attachment = ({attachment, note, setNote}) => {
</Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)} ({attachment.metadata.type})
{formatBytes(attachment.length)}{' '}
{currentProgress?.type ? '(' + currentProgress.type + 'ing - tap to cancel)' : ''}
</Paragraph>
</View>
</View>
{currentProgress ? (
<View
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
db.fs.cancel(attachment.metadata.hash);
setCurrentProgress(null);
}}
style={{
justifyContent: 'center',
marginLeft: 5
marginLeft: 5,
marginTop:5,
marginRight:-5
}}>
<Progress.Circle
size={SIZE.xxl}
progress={currentProgress?.value ? currentProgress?.value / 100 : 0}
showsText
textStyle={{
fontSize: 9
fontSize: 10
}}
color={colors.accent}
formatText={progress => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
</View>
</TouchableOpacity>
) : (
<ActionIcon
onPress={() => onPress(attachment)}

View File

@@ -158,11 +158,10 @@ interface AttachmentStore {
hash: string,
recieved: number,
type: "upload" | "download",
success: boolean
}
},
remove:(hash:string) => void
setProgress: (sent: number, total: number, hash: string, recieved: number, type: "upload" | "download", success) => void
setProgress: (sent: number, total: number, hash: string, recieved: number, type: "upload" | "download") => void
}
export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
@@ -170,11 +169,20 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
remove:(hash) => {
let _p = get().progress;
_p[hash] = null
tiny.call(EditorWebView, `
(function() {
let progress = ${JSON.stringify({
loaded:1,
total:1,
hash
})}
tinymce.activeEditor.execCommand("mceUpdateAttachmentProgress",progress);
})()`);
set({ progress: { ..._p } });
},
setProgress: (sent, total, hash, recieved, type, success) => {
setProgress: (sent, total, hash, recieved, type) => {
let _p = get().progress;
_p[hash] = { sent, total, hash, recieved, type, success };
_p[hash] = { sent, total, hash, recieved, type };
let progress = { total, hash, loaded: type === "download" ? recieved : sent };
tiny.call(EditorWebView, `
(function() {

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,7 @@ import {
eShowGetPremium,
eShowMergeDialog
} from '../../utils/Events';
import filesystem from '../../utils/filesystem';
import {openLinkInBrowser} from '../../utils/functions';
import {MMKV} from '../../utils/mmkv';
import {tabBarRef} from '../../utils/Refs';
@@ -31,6 +32,7 @@ export const editorTitleInput = createRef();
export const sourceUri =
Platform.OS === 'android' ? 'file:///android_asset/' : 'Web.bundle/site/';
let lastEditTime = 0;
let EDITOR_SETTINGS = null;
let webviewOK = true;
let noteEdited = false;
@@ -153,6 +155,7 @@ async function setNote(item) {
title = note.title;
id = note.id;
noteEdited = false;
lastEditTime = item.dateEdited;
if (note.locked) {
content.data = note.content.data;
content.type = note.content.type;
@@ -188,6 +191,7 @@ export const loadNote = async item => {
console.log('.....OPEN NOTE.....');
editing.currentlyEditing = true;
editing.movedAway = false;
if (editing.isFocused) {
tiny.call(EditorWebView, tiny.blur);
}
@@ -197,6 +201,7 @@ export const loadNote = async item => {
await clearEditor(true, true, true);
}
disableSaving = false;
lastEditTime = 0;
clearNote();
noteEdited = false;
isFirstLoad = false;
@@ -274,7 +279,6 @@ const checkStatus = async noreset => {
}, 1000);
});
};
let lastEditTime = 0;
export const _onMessage = async evt => {
if (!evt || !evt.nativeEvent || !evt.nativeEvent.data) return;
@@ -303,7 +307,6 @@ export const _onMessage = async evt => {
case 'title':
if (message.value !== title) {
noteEdited = true;
lastEditTime = Date.now();
title = message.value;
eSendEvent('editorScroll', {
@@ -315,6 +318,9 @@ export const _onMessage = async evt => {
case 'scroll':
eSendEvent('editorScroll', message);
break;
case 'attachment_download':
filesystem.downloadAttachment(message.value);
break;
case 'noteLoaded':
tiny.call(EditorWebView, tiny.notLoading);
eSendEvent('loadingNote');
@@ -611,6 +617,7 @@ export async function saveNote(preventUpdate) {
Navigation.routeNames.Notes
]);
let n = db.notes.note(id)?.data?.dateEdited;
lastEditTime = n + 10;
tiny.call(EditorWebView, tiny.updateDateEdited(timeConverter(n)));
tiny.call(EditorWebView, tiny.updateSavingState('Saved'));
}
@@ -678,16 +685,28 @@ export async function updateNoteInEditor() {
presentResolveConflictDialog(_note);
return;
}
let data = await db.content.raw(note.contentId);
let data = await db.content.raw(_note.contentId);
if (lastEditTime > _note.dateEdited) return;
if (data.data === content.data) return;
if (content.data.indexOf(data.data) !== -1) return;
if (note.dateEdited === _note.dateEdited) return;
console.log('injecting note in editor', lastEditTime, _note.dateEdited);
title = note.title;
content.data = data.data;
note = _note;
lastEditTime = _note.dateEdited + 10;
tiny.call(EditorWebView, tiny.isLoading);
await setNote(_note);
tiny.call(EditorWebView, tiny.isLoading);
post('title', title);
post('inject', content.data);
setTimeout(() => {
tiny.call(EditorWebView, tiny.notLoading);
}, 50);
if (id) {
db.attachments.download(id);
}
tiny.call(EditorWebView, tiny.notLoading);
}

View File

@@ -94,7 +94,12 @@ export const execCommands = {
EditorWebView,
`
(function() {
let file = ${JSON.stringify(attachment)}
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})
@@ -127,8 +132,7 @@ export const execCommands = {
maxHeight: 2000,
quality: 0.8,
mediaType: 'photo',
encryptToFile: false,
...key
encryptToFile: false
},
handleImageResponse
);
@@ -147,8 +151,7 @@ export const execCommands = {
maxHeight: 2000,
quality: 0.8,
mediaType: 'photo',
encryptToFile: false,
...key
encryptToFile: false
},
handleImageResponse
);
@@ -170,69 +173,9 @@ export const execCommands = {
tablesplitcell: "tinymce.activeEditor.execCommand('mceTableSplitCells');",
tablemergecell: "tinymce.activeEditor.execCommand('mceTableMergeCells');",
tablerowprops: "tinymce.activeEditor.execCommand('mceTableRowProps');",
imageResize25: `(function() {
let node = tinymce.activeEditor.selection.getNode();
if (tinymce.activeEditor.selection.getNode().tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
if (tinymce.activeEditor.dom.hasClass(node,"img_size_one")) {
tinymce.activeEditor.dom.removeClass(node,"img_size_one")
}
if (tinymce.activeEditor.dom.hasClass(node,"img_size_two")) {
tinymce.activeEditor.dom.removeClass(node,"img_size_two")
}
tinymce.activeEditor.dom.addClass(node,"img_size_three")
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})();
`,
imageResize50: `(function() {
let node = tinymce.activeEditor.selection.getNode();
if (tinymce.activeEditor.selection.getNode().tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
if (tinymce.activeEditor.dom.hasClass(node,"img_size_one")) {
tinymce.activeEditor.dom.removeClass(node,"img_size_one")
}
if (tinymce.activeEditor.dom.hasClass(node,"img_size_three")) {
tinymce.activeEditor.dom.removeClass(node,"img_size_three")
}
tinymce.activeEditor.dom.addClass(node,"img_size_two")
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})()
`,
imageResize100: `(function() {
let node = tinymce.activeEditor.selection.getNode();
if (tinymce.activeEditor.selection.getNode().tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
if (tinymce.activeEditor.dom.hasClass(node,"img_size_three")) {
tinymce.activeEditor.dom.removeClass(node,"img_size_three")
}
if (tinymce.activeEditor.dom.hasClass(node,"img_size_two")) {
tinymce.activeEditor.dom.removeClass(node,"img_size_two")
}
tinymce.activeEditor.dom.addClass(node,"img_size_one")
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})()
`,
imageResize25: () => setImageSize(0.25),
imageResize50: () => setImageSize(0.5),
imageResize100: () => setImageSize(1),
imagepreview: `(function() {
if (tinymce.activeEditor.selection.getNode().tagName === 'IMG') {
var xhr = new XMLHttpRequest();
@@ -272,67 +215,9 @@ export const execCommands = {
}
})();
`,
imagefloatleft: `(function () {
let node = tinymce.activeEditor.selection.getNode();
if (node.tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
if (tinymce.activeEditor.dom.hasClass(node,"img_float_right")) {
tinymce.activeEditor.dom.removeClass(node,"img_float_right")
}
if (tinymce.activeEditor.dom.hasClass(node,"img_float_none")) {
tinymce.activeEditor.dom.removeClass(node,"img_float_none")
}
tinymce.activeEditor.dom.addClass(node,"img_float_left")
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})();
`,
imagefloatright: `(function () {
let node = tinymce.activeEditor.selection.getNode();
if (node.tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
if (tinymce.activeEditor.dom.hasClass(node,"img_float_left")) {
tinymce.activeEditor.dom.removeClass(node, "img_float_left")
}
if (tinymce.activeEditor.dom.hasClass(node,"img_float_none")) {
tinymce.activeEditor.dom.removeClass(node,"img_float_none")
}
tinymce.activeEditor.dom.addClass(node,"img_float_right")
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})()
`,
imagefloatnone: `(function () {
let node = tinymce.activeEditor.selection.getNode();
if (node.tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
if (tinymce.activeEditor.dom.hasClass(node,"img_float_left")) {
tinymce.activeEditor.dom.removeClass(node,"img_float_left")
}
if (tinymce.activeEditor.dom.hasClass(node,"img_float_right")) {
tinymce.activeEditor.dom.removeClass(node,"img_float_right")
}
tinymce.activeEditor.dom.addClass(node,"img_float_none")
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})()
`,
imagefloatleft: () => setFloat('left'),
imagefloatright: () => setFloat('right'),
imagefloatnone: () => setFloat('none'),
'line-break': `
tinymce.activeEditor.undoManager.transact(function() {
tinymce.activeEditor.execCommand('InsertLineBreak');
@@ -358,7 +243,7 @@ const handleImageResponse = async response => {
uri: image.uri,
type: 'url'
});
console.log('hash: ',hash);
tiny.call(
EditorWebView,
`
@@ -367,7 +252,8 @@ const handleImageResponse = async response => {
hash: hash,
type: image.type,
filename: image.fileName,
dataurl: b64
dataurl: b64,
size: image.fileSize
})}
tinymce.activeEditor.execCommand('mceAttachImage',image);
setTimeout(function() {
@@ -404,3 +290,39 @@ async function attachFile(uri, hash, type, filename) {
return false;
}
}
const setFloat = float => `(function () {
let node = tinymce.activeEditor.selection.getNode();
if (node.tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
node.style.float = "${float}";
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})()`;
const setImageSize = size => `(function() {
let node = tinymce.activeEditor.selection.getNode();
if (tinymce.activeEditor.selection.getNode().tagName === 'IMG') {
tinymce.activeEditor.undoManager.transact(function() {
let rect = node.getBoundingClientRect();
let originalWidth = rect.width;
let originalHeight = rect.height;
if (node.dataset.width) {
originalWidth = node.dataset.width;
originalHeight = node.dataset.height;
} else {
node.dataset.width = originalWidth;
node.dataset.height = originalHeight;
}
node.width = originalWidth * ${size}
setTimeout(function() {
tinymce.activeEditor.nodeChanged({selectionChange:true})
},100)
});
}
})();
`;