diff --git a/src/components/Calendar.css b/src/Calendars/Calendar.css similarity index 100% rename from src/components/Calendar.css rename to src/Calendars/Calendar.css diff --git a/src/components/Calendar.tsx b/src/Calendars/Calendar.tsx similarity index 100% rename from src/components/Calendar.tsx rename to src/Calendars/Calendar.tsx diff --git a/src/components/Event.tsx b/src/Calendars/Event.tsx similarity index 95% rename from src/components/Event.tsx rename to src/Calendars/Event.tsx index 60c9396..bddd33e 100644 --- a/src/components/Event.tsx +++ b/src/Calendars/Event.tsx @@ -3,7 +3,7 @@ import * as React from "react"; -import PimItemHeader from "./PimItemHeader"; +import PimItemHeader from "../components/PimItemHeader"; import { formatDateRange, formatOurTimezoneOffset } from "../helpers"; diff --git a/src/components/EventEdit.tsx b/src/Calendars/EventEdit.tsx similarity index 100% rename from src/components/EventEdit.tsx rename to src/Calendars/EventEdit.tsx diff --git a/src/Calendars/Main.tsx b/src/Calendars/Main.tsx new file mode 100644 index 0000000..859ddc8 --- /dev/null +++ b/src/Calendars/Main.tsx @@ -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>>(); + const [cachedCollections, setCachedCollections] = React.useState(); + 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 ( + + ); + } + + async function onItemSave(item: PimType, collectionUid: string, originalItem?: PimType): Promise { + 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 ( + + + history.push( + routeResolver.getRoute("pim.events._id", { itemUid: getItemNavigationUid(item) }) + )} + onSlotClick={(start?: Date, end?: Date) => history.push( + routeResolver.getRoute("pim.events.new"), + { start, end } + )} + /> + history.push( + routeResolver.getRoute("pim.events.new") + )} + /> + + + + + { + const [colUid, itemUid] = match.params.itemUid.split("|"); + const item = entries.get(colUid)?.get(itemUid); + if (!item) { + return (); + } + + /* FIXME: + const collection = collections!.find((x) => x.uid === colUid)!; + const readOnly = collection.accessLevel; + */ + const readOnly = false; + + return ( + + + + + + + + +

Not currently implemented.

+
+ +
+ + + + + + +
+ +
+
+ ); + }} + /> +
+ ); +} + diff --git a/src/Pim/index.tsx b/src/Pim/index.tsx index 97bde3f..d62eddc 100644 --- a/src/Pim/index.tsx +++ b/src/Pim/index.tsx @@ -24,8 +24,6 @@ import { PimType, ContactType, EventType, TaskType } from "../pim-types"; import Container from "../widgets/Container"; import JournalEntries from "../components/JournalEntries"; -import EventEdit from "../components/EventEdit"; -import Event from "../components/Event"; import TaskEdit from "../components/Tasks/TaskEdit"; import Task from "../components/Tasks/Task"; @@ -387,26 +385,10 @@ class Pim extends React.PureComponent { } public render() { - const { collectionsCalendar, collectionsTaskList, calendarItems, taskListItems } = itemsSelector(this.props); + const { collectionsTaskList, taskListItems } = itemsSelector(this.props); return ( - ( - - )} - /> ( diff --git a/src/SyncGate.tsx b/src/SyncGate.tsx index bb2c960..64b8b72 100644 --- a/src/SyncGate.tsx +++ b/src/SyncGate.tsx @@ -15,6 +15,7 @@ import { routeResolver } from "./App"; import AppBarOverride from "./widgets/AppBarOverride"; import LoadingIndicator from "./widgets/LoadingIndicator"; import ContactsMain from "./Contacts/Main"; +import CalendarsMain from "./Calendars/Main"; import Journals from "./Journals"; import Settings from "./Settings"; @@ -96,7 +97,7 @@ export default function SyncGate() { - events +