From b394484f3c1a22142b12dcafe8aeaaaf2e06e3a8 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Fri, 3 Jul 2020 16:35:19 +0300 Subject: [PATCH] Implement an undelete function to mass-undelete changes. --- src/components/JournalEntries.tsx | 125 +++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 4 deletions(-) diff --git a/src/components/JournalEntries.tsx b/src/components/JournalEntries.tsx index 4b959ab..f620af5 100644 --- a/src/components/JournalEntries.tsx +++ b/src/components/JournalEntries.tsx @@ -20,6 +20,106 @@ import IconError from '@material-ui/icons/Error'; import { TaskType, EventType, ContactType, parseString } from '../pim-types'; import * as EteSync from 'etesync'; +import LoadingIndicator from '../widgets/LoadingIndicator'; +import { useCredentials } from '../login'; +import { createJournalEntry } from '../etesync-helpers'; +import { useSelector, useDispatch } from 'react-redux'; +import { StoreState } from '../store'; +import { addEntries } from '../store/actions'; + +interface RollbackToHereDialogPropsType { + journal: EteSync.Journal; + entries: Immutable.List; + entryUid: string; + open: boolean; + onClose: () => void; +} + +function RollbackToHereDialog(props: RollbackToHereDialogPropsType) { + const [loading, setLoading] = React.useState(false); + const etesync = useCredentials(); + const dispatch = useDispatch(); + const userInfo = useSelector((state: StoreState) => state.cache.userInfo); + + async function go() { + setLoading(true); + + const changes = new Map(); + + for (const entry of props.entries.reverse()) { + const comp = parseString(entry.content); + const itemComp = comp.getFirstSubcomponent('vevent') ?? comp.getFirstSubcomponent('vtodo') ?? comp; + const itemUid = itemComp.getFirstPropertyValue('uid'); + + if (itemUid && !changes.has(itemUid)) { + changes.set(itemUid, entry); + } + + if (entry.uid === props.entryUid) { + break; + } + } + + const last = props.entries.last(null); + const lastUid = last?.uid ? last.uid : null; + + // XXX implement chunked push most likely... + let prevUid = lastUid; + const journalItems = []; + for (const syncEntry of changes.values()) { + if (syncEntry.action === EteSync.SyncEntryAction.Delete) { + const ret = createJournalEntry(etesync, userInfo, props.journal, prevUid, EteSync.SyncEntryAction.Add, syncEntry.content); + journalItems.push(ret); + + prevUid = ret.uid; + } + } + + if (journalItems.length > 0) { + await dispatch( + addEntries(etesync, props.journal.uid, journalItems, lastUid) + ); + } + + props.onClose(); + } + + return ( + + + Recover items + + + {loading ? ( + + ) : ( +

+ This function restores all of the deleted items that happened after this change entry. It will not modify any items that haven't been changed since the item was deleted. +

+ )} +
+ + + + +
+ ); +} class JournalEntries extends React.PureComponent { public static defaultProps = { @@ -27,7 +127,8 @@ class JournalEntries extends React.PureComponent { }; public state: { - dialog?: string; + dialog?: EteSync.SyncEntry; + rollbackDialogId?: string; }; public props: { @@ -63,7 +164,7 @@ class JournalEntries extends React.PureComponent { secondaryText="Unknown" onClick={() => { this.setState({ - dialog: syncEntry.content, + dialog: syncEntry, }); }} /> @@ -113,7 +214,7 @@ class JournalEntries extends React.PureComponent { secondaryText={uid} onClick={() => { this.setState({ - dialog: syncEntry.content, + dialog: syncEntry, }); }} /> @@ -122,6 +223,13 @@ class JournalEntries extends React.PureComponent { return (
+ this.setState({ rollbackDialogId: undefined })} + /> { @@ -132,9 +240,18 @@ class JournalEntries extends React.PureComponent { Raw Content -
{this.state.dialog}
+
{this.state.dialog?.content}
+