Implment item and collection change history.
parent
1f00fbe8cc
commit
485b65cb69
|
@ -23,6 +23,7 @@ import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
|
|||
|
||||
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemDelete, itemSave, defaultColor } from "../Pim/helpers";
|
||||
import { historyPersistor } from "../persist-state-history";
|
||||
import ItemChangeHistory from "../Pim/ItemChangeHistory";
|
||||
|
||||
const PersistCalendar = historyPersistor("Calendar")(Calendar);
|
||||
|
||||
|
@ -180,9 +181,17 @@ export default function CalendarsMain() {
|
|||
</Route>
|
||||
<Route
|
||||
path={routeResolver.getRoute("pim.events._id.log")}
|
||||
>
|
||||
<h1>Not currently implemented.</h1>
|
||||
</Route>
|
||||
render={() => {
|
||||
const cachedCollection = cachedCollections!.find((x) => x.collection.uid === colUid)!;
|
||||
if (!cachedCollection) {
|
||||
return (<PageNotFound />);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemChangeHistory collection={cachedCollection} itemUid={itemUid} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={routeResolver.getRoute("pim.events._id")}
|
||||
exact
|
||||
|
|
|
@ -5,17 +5,13 @@ import * as React from "react";
|
|||
|
||||
import * as Etebase from "etebase";
|
||||
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
|
||||
import { useCredentials } from "../credentials";
|
||||
import { useItems } from "../etebase-helpers";
|
||||
import { CachedCollection } from "../Pim/helpers";
|
||||
import { CachedCollection, getRawItemNavigationUid } from "../Pim/helpers";
|
||||
import LoadingIndicator from "../widgets/LoadingIndicator";
|
||||
import GenericChangeHistory from "../components/GenericChangeHistory";
|
||||
import { useHistory } from "react-router";
|
||||
import { routeResolver } from "../App";
|
||||
|
||||
export interface CachedItem {
|
||||
item: Etebase.Item;
|
||||
|
@ -48,7 +44,7 @@ interface PropsType {
|
|||
|
||||
export default function CollectionChangeEntries(props: PropsType) {
|
||||
const [entries, setEntries] = React.useState<Map<string, CachedItem>>();
|
||||
const [dialog, setDialog] = React.useState<CachedItem>();
|
||||
const history = useHistory();
|
||||
const etebase = useCredentials()!;
|
||||
|
||||
const { collection, metadata } = props.collection;
|
||||
|
@ -74,33 +70,29 @@ export default function CollectionChangeEntries(props: PropsType) {
|
|||
return a - b;
|
||||
});
|
||||
|
||||
let changelogRoute = "";
|
||||
switch (colType) {
|
||||
case "etebase.vevent": {
|
||||
changelogRoute = "pim.events._id.log";
|
||||
break;
|
||||
}
|
||||
case "etebase.vtodo": {
|
||||
changelogRoute = "pim.tasks._id.log";
|
||||
break;
|
||||
}
|
||||
case "etebase.vcard": {
|
||||
changelogRoute = "pim.contacts._id.log";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: "calc(100vh - 300px)" }}>
|
||||
<Dialog
|
||||
open={dialog !== undefined}
|
||||
onClose={() => setDialog(undefined)}
|
||||
>
|
||||
<DialogTitle>
|
||||
Raw Content
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<div>Entry UID: <pre className="d-inline-block">{dialog?.item.uid}</pre></div>
|
||||
<div>Content:
|
||||
<pre>{dialog?.content}</pre>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => setDialog(undefined)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<GenericChangeHistory
|
||||
items={entriesList}
|
||||
onItemClick={setDialog}
|
||||
onItemClick={(item) =>
|
||||
history.push(routeResolver.getRoute(changelogRoute, { itemUid: getRawItemNavigationUid(collection.uid, item.item.uid) }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -21,6 +21,7 @@ import ContactEdit from "./ContactEdit";
|
|||
import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
|
||||
|
||||
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers";
|
||||
import ItemChangeHistory from "../Pim/ItemChangeHistory";
|
||||
|
||||
const colType = "etebase.vcard";
|
||||
|
||||
|
@ -148,9 +149,17 @@ export default function ContactsMain() {
|
|||
</Route>
|
||||
<Route
|
||||
path={routeResolver.getRoute("pim.contacts._id.log")}
|
||||
>
|
||||
<h1>Not currently implemented.</h1>
|
||||
</Route>
|
||||
render={() => {
|
||||
const cachedCollection = cachedCollections!.find((x) => x.collection.uid === colUid)!;
|
||||
if (!cachedCollection) {
|
||||
return (<PageNotFound />);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemChangeHistory collection={cachedCollection} itemUid={itemUid} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={routeResolver.getRoute("pim.contacts._id")}
|
||||
exact
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
// SPDX-FileCopyrightText: © 2017 EteSync Authors
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import * as Etebase from "etebase";
|
||||
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import Button from "@material-ui/core/Button";
|
||||
|
||||
import { useCredentials } from "../credentials";
|
||||
import LoadingIndicator from "../widgets/LoadingIndicator";
|
||||
import GenericChangeHistory from "../components/GenericChangeHistory";
|
||||
import { useItems } from "../etebase-helpers";
|
||||
import { CachedCollection } from "./helpers";
|
||||
|
||||
export interface CachedItem {
|
||||
item: Etebase.Item;
|
||||
metadata: Etebase.ItemMetadata;
|
||||
content: string;
|
||||
}
|
||||
|
||||
async function loadRevisions(etebase: Etebase.Account, col: Etebase.Collection, item: Etebase.Item) {
|
||||
const ret: CachedItem[] = [];
|
||||
const colMgr = etebase.getCollectionManager();
|
||||
const itemManager = colMgr.getItemManager(col);
|
||||
|
||||
let iterator: string | null = null;
|
||||
let done = false;
|
||||
while (!done) {
|
||||
// FIXME: shouldn't be any
|
||||
const revisions: any = await itemManager.itemRevisions(item, { iterator, limit: 30 });
|
||||
iterator = revisions.iterator;
|
||||
done = revisions.done;
|
||||
|
||||
for (const item of revisions.data) {
|
||||
ret.push({
|
||||
item,
|
||||
metadata: await item.getMeta(),
|
||||
content: await item.getContent(Etebase.OutputFormat.String),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
interface PropsType {
|
||||
collection: CachedCollection;
|
||||
itemUid: string;
|
||||
}
|
||||
|
||||
export default function ItemChangeHistory(props: PropsType) {
|
||||
const [entries, setEntries] = React.useState<CachedItem[]>();
|
||||
const [dialog, setDialog] = React.useState<CachedItem>();
|
||||
const etebase = useCredentials()!;
|
||||
const { collection, metadata } = props.collection;
|
||||
const items = useItems(etebase, metadata.type);
|
||||
|
||||
const item = items?.get(collection.uid)?.get(props.itemUid);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (item) {
|
||||
loadRevisions(etebase, collection, item)
|
||||
.then((entries) => setEntries(entries));
|
||||
}
|
||||
}, [etebase, collection, item]);
|
||||
|
||||
if (!entries) {
|
||||
return (
|
||||
<LoadingIndicator />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: "calc(100vh - 300px)" }}>
|
||||
<Dialog
|
||||
open={dialog !== undefined}
|
||||
onClose={() => setDialog(undefined)}
|
||||
>
|
||||
<DialogTitle>
|
||||
Raw Content
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<div>Revision UID: <pre className="d-inline-block">{dialog?.item.etag}</pre></div>
|
||||
<div>Content:
|
||||
<pre>{dialog?.content}</pre>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => setDialog(undefined)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<GenericChangeHistory
|
||||
items={entries}
|
||||
onItemClick={setDialog}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -21,9 +21,13 @@ export interface CachedCollection {
|
|||
metadata: Etebase.CollectionMetadata;
|
||||
}
|
||||
|
||||
export function getItemNavigationUid(item: PimType) {
|
||||
export function getRawItemNavigationUid(collectionUid: string, itemUid: string) {
|
||||
// Both collectionUid and itemUid are url safe
|
||||
return `${item.collectionUid}|${item.itemUid}`;
|
||||
return `${collectionUid}|${itemUid}`;
|
||||
}
|
||||
|
||||
export function getItemNavigationUid(item: PimType) {
|
||||
return getRawItemNavigationUid(item.collectionUid!, item.itemUid!);
|
||||
}
|
||||
|
||||
export function getDecryptCollectionsFunction(_colType?: string) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import TaskEdit from "./TaskEdit";
|
|||
import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
|
||||
|
||||
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers";
|
||||
import ItemChangeHistory from "../Pim/ItemChangeHistory";
|
||||
|
||||
const colType = "etebase.vtodo";
|
||||
|
||||
|
@ -150,9 +151,17 @@ export default function TasksMain() {
|
|||
</Route>
|
||||
<Route
|
||||
path={routeResolver.getRoute("pim.tasks._id.log")}
|
||||
>
|
||||
<h1>Not currently implemented.</h1>
|
||||
</Route>
|
||||
render={() => {
|
||||
const cachedCollection = cachedCollections!.find((x) => x.collection.uid === colUid)!;
|
||||
if (!cachedCollection) {
|
||||
return (<PageNotFound />);
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemChangeHistory collection={cachedCollection} itemUid={itemUid} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={routeResolver.getRoute("pim.tasks._id")}
|
||||
exact
|
||||
|
|
Loading…
Reference in New Issue