Create the calendar route.

master
Tom Hacohen 4 years ago
parent b29f19639a
commit 32426b2460

@ -3,7 +3,7 @@
import * as React from "react"; import * as React from "react";
import PimItemHeader from "./PimItemHeader"; import PimItemHeader from "../components/PimItemHeader";
import { formatDateRange, formatOurTimezoneOffset } from "../helpers"; import { formatDateRange, formatOurTimezoneOffset } from "../helpers";

@ -0,0 +1,267 @@
// SPDX-FileCopyrightText: © 2020 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from "react";
import { Switch, Route, useHistory } from "react-router";
import * as Etebase from "etebase";
import { Button, useTheme } from "@material-ui/core";
import IconEdit from "@material-ui/icons/Edit";
import IconDuplicate from "@material-ui/icons/FileCopy";
import IconChangeHistory from "@material-ui/icons/ChangeHistory";
import { EventType, PimType } from "../pim-types";
import { useCredentials } from "../credentials";
import { useItems, useCollections, getCollectionManager } from "../etebase-helpers";
import { routeResolver } from "../App";
import Calendar from "./Calendar";
import Event from "./Event";
import LoadingIndicator from "../widgets/LoadingIndicator";
import EventEdit from "./EventEdit";
import PageNotFound from "../PageNotFound";
import { CachedCollection, getItemNavigationUid, getDecryptCollectionsFunction, getDecryptItemsFunction, PimFab } from "../Pim/helpers";
import { historyPersistor } from "../persist-state-history";
const PersistCalendar = historyPersistor("Calendar")(Calendar);
const colType = "etebase.vevent";
const decryptCollections = getDecryptCollectionsFunction(colType);
const decryptItems = getDecryptItemsFunction(colType, EventType.parse);
export default function CalendarsMain() {
const [entries, setEntries] = React.useState<Map<string, Map<string, EventType>>>();
const [cachedCollections, setCachedCollections] = React.useState<CachedCollection[]>();
const theme = useTheme();
const history = useHistory();
const etebase = useCredentials()!;
const collections = useCollections(etebase, colType);
const items = useItems(etebase, colType);
React.useEffect(() => {
if (items) {
decryptItems(items)
.then((entries) => setEntries(entries));
// FIXME: handle failure to decrypt items
}
if (collections) {
decryptCollections(collections)
.then((entries) => setCachedCollections(entries));
// FIXME: handle failure to decrypt collections
}
}, [items, collections]);
if (!entries || !cachedCollections) {
return (
<LoadingIndicator />
);
}
async function onItemSave(item: PimType, collectionUid: string, originalItem?: PimType): Promise<void> {
const itemUid = originalItem?.itemUid;
const colMgr = getCollectionManager(etebase);
const collection = collections!.find((x) => x.uid === collectionUid)!;
const itemMgr = colMgr.getItemManager(collection);
const mtime = (new Date()).getUTCMilliseconds();
const content = item.toIcal();
let eteItem;
if (itemUid) {
// Existing item
eteItem = items!.get(collectionUid)?.get(itemUid)!;
await eteItem.setContent(content);
const meta = await eteItem.getMeta();
meta.mtime = mtime;
await eteItem.setMeta(meta);
} else {
// New
const meta: Etebase.CollectionItemMetadata = {
mtime,
name: item.uid,
};
eteItem = await itemMgr.create(meta, content);
}
await itemMgr.batch([eteItem]);
}
async function onItemDelete(item: PimType, collectionUid: string) {
const itemUid = item.itemUid!;
const colMgr = getCollectionManager(etebase);
const collection = collections!.find((x) => x.uid === collectionUid)!;
const itemMgr = colMgr.getItemManager(collection);
const eteItem = items!.get(collectionUid)?.get(itemUid)!;
const mtime = (new Date()).getUTCMilliseconds();
const meta = await eteItem.getMeta();
meta.mtime = mtime;
await eteItem.setMeta(meta);
await eteItem.delete();
await itemMgr.batch([eteItem]);
history.push(routeResolver.getRoute("pim.events"));
}
function onCancel() {
history.goBack();
}
const flatEntries = [];
for (const col of entries.values()) {
for (const item of col.values()) {
flatEntries.push(item);
}
}
const styles = {
button: {
marginLeft: theme.spacing(1),
},
leftIcon: {
marginRight: theme.spacing(1),
},
};
return (
<Switch>
<Route
path={routeResolver.getRoute("pim.events")}
exact
>
<PersistCalendar
entries={flatEntries}
onItemClick={(item: EventType) => history.push(
routeResolver.getRoute("pim.events._id", { itemUid: getItemNavigationUid(item) })
)}
onSlotClick={(start?: Date, end?: Date) => history.push(
routeResolver.getRoute("pim.events.new"),
{ start, end }
)}
/>
<PimFab
onClick={() => history.push(
routeResolver.getRoute("pim.events.new")
)}
/>
</Route>
<Route
path={routeResolver.getRoute("pim.events.new")}
exact
>
<EventEdit
collections={cachedCollections}
onSave={onItemSave}
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
/>
</Route>
<Route
path={routeResolver.getRoute("pim.events._id")}
render={({ match }) => {
const [colUid, itemUid] = match.params.itemUid.split("|");
const item = entries.get(colUid)?.get(itemUid);
if (!item) {
return (<PageNotFound />);
}
/* FIXME:
const collection = collections!.find((x) => x.uid === colUid)!;
const readOnly = collection.accessLevel;
*/
const readOnly = false;
return (
<Switch>
<Route
path={routeResolver.getRoute("pim.events._id.edit")}
exact
>
<EventEdit
key={itemUid}
initialCollection={item.collectionUid}
item={item}
collections={cachedCollections}
onSave={onItemSave}
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
/>
</Route>
<Route
path={routeResolver.getRoute("pim.events._id.duplicate")}
exact
>
<EventEdit
key={itemUid}
initialCollection={item.collectionUid}
item={item}
collections={cachedCollections}
onSave={onItemSave}
onDelete={onItemDelete}
onCancel={onCancel}
history={history}
duplicate
/>
</Route>
<Route
path={routeResolver.getRoute("pim.events._id.log")}
>
<h1>Not currently implemented.</h1>
</Route>
<Route
path={routeResolver.getRoute("pim.events._id")}
exact
>
<div style={{ textAlign: "right", marginBottom: 15 }}>
<Button
variant="contained"
style={styles.button}
onClick={() =>
history.push(routeResolver.getRoute("pim.events._id.log", { itemUid: getItemNavigationUid(item) }))
}
>
<IconChangeHistory style={styles.leftIcon} />
Change History
</Button>
<Button
color="secondary"
variant="contained"
disabled={readOnly}
style={{ ...styles.button, marginLeft: 15 }}
onClick={() =>
history.push(routeResolver.getRoute("pim.events._id.edit", { itemUid: getItemNavigationUid(item) }))
}
>
<IconEdit style={styles.leftIcon} />
Edit
</Button>
<Button
color="secondary"
variant="contained"
disabled={readOnly}
style={{ ...styles.button, marginLeft: 15 }}
onClick={() =>
history.push(routeResolver.getRoute("pim.events._id.duplicate", { itemUid: getItemNavigationUid(item) }))
}
>
<IconDuplicate style={styles.leftIcon} />
Duplicate
</Button>
</div>
<Event item={item} />
</Route>
</Switch>
);
}}
/>
</Switch>
);
}

@ -24,8 +24,6 @@ import { PimType, ContactType, EventType, TaskType } from "../pim-types";
import Container from "../widgets/Container"; import Container from "../widgets/Container";
import JournalEntries from "../components/JournalEntries"; import JournalEntries from "../components/JournalEntries";
import EventEdit from "../components/EventEdit";
import Event from "../components/Event";
import TaskEdit from "../components/Tasks/TaskEdit"; import TaskEdit from "../components/Tasks/TaskEdit";
import Task from "../components/Tasks/Task"; import Task from "../components/Tasks/Task";
@ -387,26 +385,10 @@ class Pim extends React.PureComponent {
} }
public render() { public render() {
const { collectionsCalendar, collectionsTaskList, calendarItems, taskListItems } = itemsSelector(this.props); const { collectionsTaskList, taskListItems } = itemsSelector(this.props);
return ( return (
<Switch> <Switch>
<Route
path={routeResolver.getRoute("pim.events")}
render={() => (
<CollectionRoutes
syncInfo={this.props.syncInfo}
routePrefix="pim.events"
collections={collectionsCalendar}
items={calendarItems}
componentEdit={EventEdit}
componentView={Event}
onItemSave={this.onItemSave}
onItemDelete={this.onItemDelete}
onItemCancel={this.onCancel}
/>
)}
/>
<Route <Route
path={routeResolver.getRoute("pim.tasks")} path={routeResolver.getRoute("pim.tasks")}
render={() => ( render={() => (

@ -15,6 +15,7 @@ import { routeResolver } from "./App";
import AppBarOverride from "./widgets/AppBarOverride"; import AppBarOverride from "./widgets/AppBarOverride";
import LoadingIndicator from "./widgets/LoadingIndicator"; import LoadingIndicator from "./widgets/LoadingIndicator";
import ContactsMain from "./Contacts/Main"; import ContactsMain from "./Contacts/Main";
import CalendarsMain from "./Calendars/Main";
import Journals from "./Journals"; import Journals from "./Journals";
import Settings from "./Settings"; import Settings from "./Settings";
@ -96,7 +97,7 @@ export default function SyncGate() {
<Route <Route
path={routeResolver.getRoute("pim.events")} path={routeResolver.getRoute("pim.events")}
> >
events <CalendarsMain />
</Route> </Route>
<Route <Route
path={routeResolver.getRoute("pim.tasks")} path={routeResolver.getRoute("pim.tasks")}

Loading…
Cancel
Save