Implment item and collection change history.

master
Tom Hacohen 4 years ago
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 { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemDelete, itemSave, defaultColor } from "../Pim/helpers";
import { historyPersistor } from "../persist-state-history"; import { historyPersistor } from "../persist-state-history";
import ItemChangeHistory from "../Pim/ItemChangeHistory";
const PersistCalendar = historyPersistor("Calendar")(Calendar); const PersistCalendar = historyPersistor("Calendar")(Calendar);
@ -180,9 +181,17 @@ export default function CalendarsMain() {
</Route> </Route>
<Route <Route
path={routeResolver.getRoute("pim.events._id.log")} path={routeResolver.getRoute("pim.events._id.log")}
> render={() => {
<h1>Not currently implemented.</h1> const cachedCollection = cachedCollections!.find((x) => x.collection.uid === colUid)!;
</Route> if (!cachedCollection) {
return (<PageNotFound />);
}
return (
<ItemChangeHistory collection={cachedCollection} itemUid={itemUid} />
);
}}
/>
<Route <Route
path={routeResolver.getRoute("pim.events._id")} path={routeResolver.getRoute("pim.events._id")}
exact exact

@ -5,17 +5,13 @@ import * as React from "react";
import * as Etebase from "etebase"; 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 { useCredentials } from "../credentials";
import { useItems } from "../etebase-helpers"; import { useItems } from "../etebase-helpers";
import { CachedCollection } from "../Pim/helpers"; import { CachedCollection, getRawItemNavigationUid } from "../Pim/helpers";
import LoadingIndicator from "../widgets/LoadingIndicator"; import LoadingIndicator from "../widgets/LoadingIndicator";
import GenericChangeHistory from "../components/GenericChangeHistory"; import GenericChangeHistory from "../components/GenericChangeHistory";
import { useHistory } from "react-router";
import { routeResolver } from "../App";
export interface CachedItem { export interface CachedItem {
item: Etebase.Item; item: Etebase.Item;
@ -48,7 +44,7 @@ interface PropsType {
export default function CollectionChangeEntries(props: PropsType) { export default function CollectionChangeEntries(props: PropsType) {
const [entries, setEntries] = React.useState<Map<string, CachedItem>>(); const [entries, setEntries] = React.useState<Map<string, CachedItem>>();
const [dialog, setDialog] = React.useState<CachedItem>(); const history = useHistory();
const etebase = useCredentials()!; const etebase = useCredentials()!;
const { collection, metadata } = props.collection; const { collection, metadata } = props.collection;
@ -74,33 +70,29 @@ export default function CollectionChangeEntries(props: PropsType) {
return a - b; 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 ( return (
<div style={{ height: "calc(100vh - 300px)" }}> <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 <GenericChangeHistory
items={entriesList} items={entriesList}
onItemClick={setDialog} onItemClick={(item) =>
history.push(routeResolver.getRoute(changelogRoute, { itemUid: getRawItemNavigationUid(collection.uid, item.item.uid) }))
}
/> />
</div> </div>
); );

@ -21,6 +21,7 @@ import ContactEdit from "./ContactEdit";
import PageNotFound, { PageNotFoundRoute } from "../PageNotFound"; import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers"; import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers";
import ItemChangeHistory from "../Pim/ItemChangeHistory";
const colType = "etebase.vcard"; const colType = "etebase.vcard";
@ -148,9 +149,17 @@ export default function ContactsMain() {
</Route> </Route>
<Route <Route
path={routeResolver.getRoute("pim.contacts._id.log")} path={routeResolver.getRoute("pim.contacts._id.log")}
> render={() => {
<h1>Not currently implemented.</h1> const cachedCollection = cachedCollections!.find((x) => x.collection.uid === colUid)!;
</Route> if (!cachedCollection) {
return (<PageNotFound />);
}
return (
<ItemChangeHistory collection={cachedCollection} itemUid={itemUid} />
);
}}
/>
<Route <Route
path={routeResolver.getRoute("pim.contacts._id")} path={routeResolver.getRoute("pim.contacts._id")}
exact 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; metadata: Etebase.CollectionMetadata;
} }
export function getItemNavigationUid(item: PimType) { export function getRawItemNavigationUid(collectionUid: string, itemUid: string) {
// Both collectionUid and itemUid are url safe // 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) { export function getDecryptCollectionsFunction(_colType?: string) {

@ -21,6 +21,7 @@ import TaskEdit from "./TaskEdit";
import PageNotFound, { PageNotFoundRoute } from "../PageNotFound"; import PageNotFound, { PageNotFoundRoute } from "../PageNotFound";
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers"; import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab, itemSave, itemDelete } from "../Pim/helpers";
import ItemChangeHistory from "../Pim/ItemChangeHistory";
const colType = "etebase.vtodo"; const colType = "etebase.vtodo";
@ -150,9 +151,17 @@ export default function TasksMain() {
</Route> </Route>
<Route <Route
path={routeResolver.getRoute("pim.tasks._id.log")} path={routeResolver.getRoute("pim.tasks._id.log")}
> render={() => {
<h1>Not currently implemented.</h1> const cachedCollection = cachedCollections!.find((x) => x.collection.uid === colUid)!;
</Route> if (!cachedCollection) {
return (<PageNotFound />);
}
return (
<ItemChangeHistory collection={cachedCollection} itemUid={itemUid} />
);
}}
/>
<Route <Route
path={routeResolver.getRoute("pim.tasks._id")} path={routeResolver.getRoute("pim.tasks._id")}
exact exact

Loading…
Cancel
Save