Implement an undelete function to mass-undelete changes.
parent
728ac6124f
commit
b394484f3c
src/components
|
@ -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<EteSync.SyncEntry>;
|
||||
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<string, EteSync.SyncEntry>();
|
||||
|
||||
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<any>(
|
||||
addEntries(etesync, props.journal.uid, journalItems, lastUid)
|
||||
);
|
||||
}
|
||||
|
||||
props.onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<DialogTitle>
|
||||
Recover items
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{loading ? (
|
||||
<LoadingIndicator style={{ display: 'block', margin: 'auto' }} />
|
||||
) : (
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
onClick={props.onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
onClick={go}
|
||||
>
|
||||
Go
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<RollbackToHereDialog
|
||||
journal={this.props.journal}
|
||||
entries={this.props.entries}
|
||||
entryUid={this.state.rollbackDialogId!}
|
||||
open={!!this.state.rollbackDialogId}
|
||||
onClose={() => this.setState({ rollbackDialogId: undefined })}
|
||||
/>
|
||||
<Dialog
|
||||
open={this.state.dialog !== undefined}
|
||||
onClose={() => {
|
||||
|
@ -132,9 +240,18 @@ class JournalEntries extends React.PureComponent {
|
|||
Raw Content
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<pre>{this.state.dialog}</pre>
|
||||
<pre>{this.state.dialog?.content}</pre>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => this.setState({
|
||||
dialog: undefined,
|
||||
rollbackDialogId: this.state.dialog?.uid,
|
||||
})}
|
||||
>
|
||||
Recover items until here
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
|
|
Loading…
Reference in New Issue