Files
notesnook/apps/mobile/src/components/MergeEditor/index.js

570 lines
17 KiB
JavaScript
Raw Normal View History

2020-09-09 11:10:35 +05:00
import React, {createRef, useEffect, useState} from 'react';
import {Modal, Text, TouchableOpacity, View, SafeAreaView} from 'react-native';
import Animated, {Easing} from 'react-native-reanimated';
2020-03-26 15:42:37 +05:00
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
2020-03-26 13:39:04 +05:00
import WebView from 'react-native-webview';
2020-09-09 11:10:35 +05:00
import {useTracked} from '../../provider';
2020-10-13 17:02:14 +05:00
import {Actions} from '../../provider/Actions';
2020-03-26 15:50:10 +05:00
import {
2020-09-09 11:10:35 +05:00
eSendEvent,
2020-03-26 15:50:10 +05:00
eSubscribeEvent,
eUnSubscribeEvent,
2020-10-13 17:02:14 +05:00
} from '../../services/EventManager';
2020-03-26 15:50:10 +05:00
import {
eApplyChanges,
eShowMergeDialog,
refreshNotesPage,
2020-10-13 17:02:14 +05:00
} from '../../utils/Events';
import {dHeight} from '../../utils';
2020-09-09 11:10:35 +05:00
import {Button} from '../Button';
import {simpleDialogEvent, updateEvent} from '../DialogManager/recievers';
2020-10-13 17:02:14 +05:00
import {TEMPLATE_APPLY_CHANGES} from '../DialogManager/Templates';
2020-11-20 01:23:05 +05:00
import {normalize, SIZE} from '../../utils/SizeUtils';
import {db} from '../../utils/DB';
import Paragraph from '../Typography/Paragraph';
2020-03-26 13:39:04 +05:00
2020-09-09 11:10:35 +05:00
const {Value, timing} = Animated;
2020-04-11 11:11:42 +05:00
2020-10-13 17:02:14 +05:00
const firstWebViewHeight = new Value(dHeight * 0.5 - 50);
const secondWebViewHeight = new Value(dHeight * 0.5 - 50);
2020-03-26 13:39:04 +05:00
const primaryWebView = createRef();
const secondaryWebView = createRef();
2020-03-26 15:42:37 +05:00
let note = null;
2020-03-26 13:39:04 +05:00
2020-03-26 15:42:37 +05:00
function openEditorAnimation(
2020-03-26 13:39:04 +05:00
heightToAnimate,
extendedHeight = null,
siblingStatus,
) {
let openConfig = {
duration: 300,
2020-10-13 17:02:14 +05:00
toValue: !siblingStatus ? dHeight - 100 : dHeight * 0.5 - 50,
2020-03-26 13:39:04 +05:00
easing: Easing.inOut(Easing.ease),
};
let extendConfig = {
duration: 300,
2020-10-13 17:02:14 +05:00
toValue: dHeight * 0.5 - 50,
2020-03-26 13:39:04 +05:00
easing: Easing.inOut(Easing.ease),
};
if (extendedHeight) {
timing(extendedHeight, extendConfig).start();
}
timing(heightToAnimate, openConfig).start();
}
2020-03-26 15:42:37 +05:00
function closeEditorAnimation(heightToAnimate, heightToExtend = null) {
2020-03-26 13:39:04 +05:00
let closeConfig = {
duration: 300,
toValue: 0,
easing: Easing.inOut(Easing.ease),
};
let extendConfig = {
duration: 300,
2020-10-13 17:02:14 +05:00
toValue: dHeight - 100,
2020-03-26 13:39:04 +05:00
easing: Easing.inOut(Easing.ease),
};
if (heightToExtend) {
timing(heightToExtend, extendConfig).start();
}
timing(heightToAnimate, closeConfig).start();
}
let primaryDelta = null;
2020-03-26 15:42:37 +05:00
let primaryText = '';
2020-03-26 13:39:04 +05:00
let secondaryDelta = null;
2020-03-26 15:42:37 +05:00
let secondaryText = '';
2020-03-26 13:39:04 +05:00
const MergeEditor = () => {
const [state, dispatch] = useTracked();
2020-09-09 11:10:35 +05:00
const {colors} = state;
2020-03-26 15:42:37 +05:00
const [visible, setVisible] = useState(false);
2020-03-26 13:39:04 +05:00
const [primary, setPrimary] = useState(true);
const [secondary, setSecondary] = useState(true);
const [keepContentFrom, setKeepContentFrom] = useState(null);
const [copyToSave, setCopyToSave] = useState(null);
const [disardedContent, setDiscardedContent] = useState(null);
2020-09-09 11:10:35 +05:00
const postMessageToPrimaryWebView = (message) =>
2020-03-26 13:39:04 +05:00
primaryWebView.current?.postMessage(JSON.stringify(message));
2020-09-09 11:10:35 +05:00
const postMessageToSecondaryWebView = (message) =>
2020-03-26 13:39:04 +05:00
secondaryWebView.current?.postMessage(JSON.stringify(message));
const onPrimaryWebViewLoad = () => {
postMessageToPrimaryWebView({
2020-03-26 15:42:37 +05:00
type: 'delta',
value: primaryDelta,
2020-03-26 13:39:04 +05:00
});
2020-09-09 11:10:35 +05:00
let c = {...colors};
2020-03-26 13:39:04 +05:00
c.factor = normalize(1);
postMessageToPrimaryWebView({
type: 'theme',
value: c,
});
};
const onSecondaryWebViewLoad = () => {
postMessageToSecondaryWebView({
2020-03-26 15:42:37 +05:00
type: 'delta',
value: secondaryDelta,
2020-03-26 13:39:04 +05:00
});
2020-09-09 11:10:35 +05:00
let c = {...colors};
2020-03-26 13:39:04 +05:00
c.factor = normalize(1);
postMessageToSecondaryWebView({
type: 'theme',
value: c,
});
};
2020-09-09 11:10:35 +05:00
const _onShouldStartLoadWithRequest = (request) => {
2020-03-26 13:39:04 +05:00
if (request.url.includes('https')) {
Linking.openURL(request.url);
return false;
} else {
return true;
}
};
2020-09-09 11:10:35 +05:00
const onMessageFromPrimaryWebView = (evt) => {
2020-03-26 15:42:37 +05:00
if (evt.nativeEvent.data !== '') {
2020-03-26 13:39:04 +05:00
let data = JSON.parse(evt.nativeEvent.data);
primaryDelta = data.delta;
primaryText = data.text;
}
};
2020-09-09 11:10:35 +05:00
const onMessageFromSecondaryWebView = (evt) => {
2020-03-26 15:42:37 +05:00
if (evt.nativeEvent.data !== '') {
2020-03-26 13:39:04 +05:00
let data = JSON.parse(evt.nativeEvent.data);
secondaryDelta = data.delta;
secondaryText = data.text;
}
};
2020-03-26 15:42:37 +05:00
const applyChanges = async () => {
if (keepContentFrom === 'primary') {
await db.notes.add({
content: {
text: primaryText,
2020-04-07 10:22:17 +05:00
delta: {
data: primaryDelta,
2020-09-09 11:10:35 +05:00
resolved: true,
2020-04-07 10:22:17 +05:00
},
2020-03-26 15:42:37 +05:00
},
id: note.id,
2020-03-31 10:55:19 +05:00
conflicted: false,
2020-03-26 15:42:37 +05:00
});
2020-03-26 15:50:10 +05:00
} else if (keepContentFrom === 'secondary') {
2020-03-26 15:42:37 +05:00
await db.notes.add({
content: {
text: secondaryText,
2020-04-07 10:22:17 +05:00
delta: {
data: primaryDelta,
2020-09-09 11:10:35 +05:00
resolved: true,
2020-04-07 10:22:17 +05:00
},
2020-03-26 15:42:37 +05:00
},
id: note.id,
2020-03-31 10:55:19 +05:00
conflicted: false,
2020-03-26 15:42:37 +05:00
});
}
if (copyToSave === 'primary') {
await db.notes.add({
content: {
text: primaryText,
delta: primaryDelta,
},
id: null,
});
2020-03-26 15:50:10 +05:00
} else if (copyToSave === 'secondary') {
2020-03-26 15:42:37 +05:00
await db.notes.add({
content: {
text: secondaryText,
delta: secondaryDelta,
},
id: null,
});
}
2020-03-26 15:50:10 +05:00
eSendEvent(refreshNotesPage);
2020-10-13 17:02:14 +05:00
updateEvent({type: Actions.NOTES});
updateEvent({type: Actions.FAVORITES});
2020-03-26 15:42:37 +05:00
close();
};
2020-09-09 11:10:35 +05:00
const show = async (item) => {
2020-03-26 15:42:37 +05:00
note = item;
2020-04-11 11:11:42 +05:00
let rawDelta = await db.delta.raw(note.content.delta);
primaryDelta = rawDelta.data;
secondaryDelta = rawDelta.conflicted.data;
setVisible(true);
2020-03-26 15:42:37 +05:00
openEditorAnimation(firstWebViewHeight, secondWebViewHeight, true);
2020-03-26 13:39:04 +05:00
};
useEffect(() => {
eSubscribeEvent(eApplyChanges, applyChanges);
2020-03-26 15:42:37 +05:00
eSubscribeEvent(eShowMergeDialog, show);
2020-03-26 13:39:04 +05:00
return () => {
eUnSubscribeEvent(eApplyChanges, applyChanges);
2020-03-26 15:42:37 +05:00
eUnSubscribeEvent(eShowMergeDialog, show);
2020-03-26 13:39:04 +05:00
};
2020-09-09 11:10:35 +05:00
}, []);
2020-03-26 13:39:04 +05:00
const onPressKeepFromPrimaryWebView = () => {
if (keepContentFrom == 'primary') {
setKeepContentFrom(null);
openEditorAnimation(firstWebViewHeight, secondWebViewHeight);
} else {
setKeepContentFrom('primary');
closeEditorAnimation(firstWebViewHeight, secondWebViewHeight);
}
};
const onPressSaveCopyFromPrimaryWebView = () => {
setCopyToSave('primary');
simpleDialogEvent(TEMPLATE_APPLY_CHANGES);
};
const onPressKeepFromSecondaryWebView = () => {
if (keepContentFrom == 'secondary') {
setKeepContentFrom(null);
openEditorAnimation(secondWebViewHeight, firstWebViewHeight);
} else {
setKeepContentFrom('secondary');
closeEditorAnimation(secondWebViewHeight, firstWebViewHeight);
}
};
const onPressSaveCopyFromSecondaryWebView = () => {
setCopyToSave('secondary');
simpleDialogEvent(TEMPLATE_APPLY_CHANGES);
};
const onPressDiscardFromPrimaryWebView = () => {
setDiscardedContent('primary');
simpleDialogEvent(TEMPLATE_APPLY_CHANGES);
};
const onPressDiscardFromSecondaryWebView = () => {
setDiscardedContent('secondary');
simpleDialogEvent(TEMPLATE_APPLY_CHANGES);
};
2020-03-26 15:42:37 +05:00
const close = () => {
setVisible(false);
setPrimary(true);
setSecondary(true);
setCopyToSave(null);
setDiscardedContent(null);
setKeepContentFrom(null);
primaryDelta = null;
secondaryDelta = null;
primaryText = null;
secondaryText = null;
note = null;
openEditorAnimation(firstWebViewHeight, secondWebViewHeight, true);
};
2020-03-26 13:39:04 +05:00
const params = 'platform=' + Platform.OS;
const sourceUri =
(Platform.OS === 'android' ? 'file:///android_asset/' : '') +
'Web.bundle/loader.html';
const injectedJS = `if (!window.location.search) {
var link = document.getElementById('progress-bar');
2020-09-09 11:10:35 +05:00
link.href = './site/plaineditor.html?${params}';
2020-03-26 13:39:04 +05:00
link.click();
}`;
2020-11-23 12:32:33 +05:00
return !visible ? null : (
<Modal transparent={false} animated animationType="fade" visible={true}>
2020-09-09 11:10:35 +05:00
<SafeAreaView
2020-03-26 13:39:04 +05:00
style={{
2020-09-09 11:10:35 +05:00
backgroundColor: colors.nav,
2020-03-26 13:39:04 +05:00
}}>
<View
style={{
2020-09-09 11:10:35 +05:00
height: '100%',
2020-03-26 13:39:04 +05:00
width: '100%',
2020-09-09 11:10:35 +05:00
backgroundColor: 'rgba(0,0,0,0.3)',
2020-03-26 13:39:04 +05:00
}}>
<View
style={{
2020-09-09 11:10:35 +05:00
backgroundColor: colors.nav,
width: '100%',
height: 50,
2020-03-26 13:39:04 +05:00
flexDirection: 'row',
justifyContent: 'space-between',
2020-09-09 11:10:35 +05:00
alignItems: 'center',
paddingHorizontal: 12,
2020-03-26 13:39:04 +05:00
}}>
2020-09-09 11:10:35 +05:00
<View
2020-03-26 13:39:04 +05:00
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}>
<Icon
style={{
2020-09-09 11:10:35 +05:00
textAlign: 'center',
textAlignVertical: 'center',
marginLeft: -8,
paddingRight: 10,
paddingVertical: 10,
}}
onPress={close}
size={SIZE.xxl}
name="chevron-left"
/>
2020-03-26 13:39:04 +05:00
<TouchableOpacity
2020-09-09 11:10:35 +05:00
onPress={() => {
if (keepContentFrom === 'primary') return;
if (!primary) {
openEditorAnimation(
firstWebViewHeight,
secondary && keepContentFrom !== 'secondary'
? secondWebViewHeight
: null,
secondary && keepContentFrom !== 'secondary',
);
setPrimary(true);
} else {
closeEditorAnimation(
firstWebViewHeight,
secondary && keepContentFrom !== 'secondary'
? secondWebViewHeight
: null,
);
setPrimary(false);
}
}}
2020-03-26 13:39:04 +05:00
style={{
2020-09-09 11:10:35 +05:00
flexDirection: 'row',
2020-03-26 13:39:04 +05:00
alignItems: 'center',
2020-09-09 11:10:35 +05:00
justifyContent: 'space-between',
2020-03-26 13:39:04 +05:00
}}>
2020-11-20 01:23:05 +05:00
<Paragraph color={colors.icon} size={SIZE.xxs}>
2020-09-09 11:10:35 +05:00
Saved on 10/10/20 {'\n'}
12:30pm on Tablet
2020-11-20 01:23:05 +05:00
</Paragraph>
2020-09-09 11:10:35 +05:00
<Icon
size={SIZE.lg}
name={primary ? 'chevron-up' : 'chevron-down'}
/>
2020-03-26 13:39:04 +05:00
</TouchableOpacity>
2020-09-09 11:10:35 +05:00
</View>
2020-03-26 13:39:04 +05:00
2020-09-09 11:10:35 +05:00
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
}}>
{keepContentFrom === 'secondary' ? (
<Button
width={null}
onPress={onPressSaveCopyFromPrimaryWebView}
title="Save Copy"
/>
) : null}
<View style={{width: 10}} />
{keepContentFrom === 'secondary' ? (
<Button
width={null}
title="Discard"
color={colors.errorText}
onPress={onPressDiscardFromPrimaryWebView}
/>
) : null}
<View style={{width: 10}} />
{keepContentFrom === 'secondary' ? null : (
<Button
width={null}
title={keepContentFrom === 'primary' ? 'Undo' : 'Keep'}
onPress={onPressKeepFromPrimaryWebView}
color={
keepContentFrom === 'primary' ? colors.errorText : 'accent'
}
/>
)}
</View>
2020-03-26 13:39:04 +05:00
</View>
2020-09-09 11:10:35 +05:00
<Animated.View
2020-03-26 13:39:04 +05:00
style={{
2020-09-09 11:10:35 +05:00
height: firstWebViewHeight,
}}>
<WebView
onLoad={onPrimaryWebViewLoad}
ref={primaryWebView}
style={{
width: '100%',
height: '100%',
}}
injectedJavaScript={Platform.OS === 'ios' ? injectedJS : null}
onShouldStartLoadWithRequest={_onShouldStartLoadWithRequest}
cacheMode="LOAD_DEFAULT"
domStorageEnabled={true}
scrollEnabled={false}
bounces={false}
allowFileAccess={true}
scalesPageToFit={true}
allowingReadAccessToURL={Platform.OS === 'android' ? true : null}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
originWhitelist={['*']}
javaScriptEnabled={true}
cacheEnabled={true}
onMessage={onMessageFromPrimaryWebView}
source={
Platform.OS === 'ios'
? {uri: sourceUri}
: {
uri: 'file:///android_asset/plaineditor.html',
baseUrl: 'file:///android_asset/',
}
}
/>
</Animated.View>
2020-03-26 13:39:04 +05:00
<View
style={{
2020-09-09 11:10:35 +05:00
backgroundColor: colors.nav,
width: '100%',
height: 50,
2020-03-26 13:39:04 +05:00
flexDirection: 'row',
justifyContent: 'space-between',
2020-09-09 11:10:35 +05:00
alignItems: 'center',
paddingHorizontal: 12,
2020-03-26 13:39:04 +05:00
}}>
2020-09-09 11:10:35 +05:00
<View
2020-03-26 13:39:04 +05:00
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}>
<TouchableOpacity
2020-09-09 11:10:35 +05:00
onPress={() => {
if (keepContentFrom === 'secondary') return;
if (!secondary) {
openEditorAnimation(
secondWebViewHeight,
primary && keepContentFrom !== 'primary'
? firstWebViewHeight
: null,
primary && keepContentFrom !== 'primary',
);
setSecondary(true);
} else {
closeEditorAnimation(
secondWebViewHeight,
primary && keepContentFrom !== 'primary'
? firstWebViewHeight
: null,
);
setSecondary(false);
}
}}
2020-03-26 13:39:04 +05:00
style={{
2020-09-09 11:10:35 +05:00
flexDirection: 'row',
2020-03-26 13:39:04 +05:00
alignItems: 'center',
2020-09-09 11:10:35 +05:00
justifyContent: 'space-between',
2020-03-26 13:39:04 +05:00
}}>
2020-11-20 01:23:05 +05:00
<Paragraph color={colors.icon} size={SIZE.xs}>
2020-09-09 11:10:35 +05:00
Saved on 10/10/20 {'\n'}
12:30pm on Tablet
2020-11-20 01:23:05 +05:00
</Paragraph>
2020-09-09 11:10:35 +05:00
<Icon
size={SIZE.lg}
name={secondary ? 'chevron-up' : 'chevron-down'}
/>
2020-03-26 13:39:04 +05:00
</TouchableOpacity>
2020-09-09 11:10:35 +05:00
</View>
2020-03-26 13:39:04 +05:00
2020-09-09 11:10:35 +05:00
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
}}>
{keepContentFrom === 'primary' ? (
<Button
width={null}
onPress={onPressSaveCopyFromSecondaryWebView}
title="Save Copy"
/>
) : null}
<View style={{width: 10}} />
{keepContentFrom === 'primary' ? (
<Button
width={null}
title="Discard"
color={colors.errorText}
onPress={onPressDiscardFromSecondaryWebView}
/>
) : null}
<View style={{width: 10}} />
{keepContentFrom === 'primary' ? null : (
<Button
width={null}
title={keepContentFrom === 'secondary' ? 'Undo' : 'Keep'}
onPress={onPressKeepFromSecondaryWebView}
color={
2020-03-26 13:39:04 +05:00
keepContentFrom === 'secondary'
? colors.errorText
2020-09-09 11:10:35 +05:00
: 'accent'
}
/>
)}
</View>
2020-03-26 13:39:04 +05:00
</View>
2020-09-09 11:10:35 +05:00
<Animated.View
2020-03-26 13:39:04 +05:00
style={{
2020-09-09 11:10:35 +05:00
height: secondWebViewHeight,
}}>
<WebView
onLoad={onSecondaryWebViewLoad}
ref={secondaryWebView}
style={{
width: '100%',
height: '100%',
}}
injectedJavaScript={Platform.OS === 'ios' ? injectedJS : null}
onShouldStartLoadWithRequest={_onShouldStartLoadWithRequest}
cacheMode="LOAD_DEFAULT"
domStorageEnabled={true}
scrollEnabled={false}
bounces={false}
allowFileAccess={true}
scalesPageToFit={true}
allowingReadAccessToURL={Platform.OS === 'android' ? true : null}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
originWhitelist={['*']}
javaScriptEnabled={true}
cacheEnabled={true}
onMessage={onMessageFromSecondaryWebView}
source={
Platform.OS === 'ios'
? {uri: sourceUri}
: {
uri: 'file:///android_asset/plaineditor.html',
baseUrl: 'file:///android_asset/',
}
}
/>
</Animated.View>
</View>
</SafeAreaView>
2020-03-26 13:39:04 +05:00
</Modal>
);
};
export default MergeEditor;