diff --git a/src/Collections/CollectionEdit.tsx b/src/Collections/CollectionEdit.tsx new file mode 100644 index 0000000..79b44d2 --- /dev/null +++ b/src/Collections/CollectionEdit.tsx @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: © 2017 EteSync Authors +// SPDX-License-Identifier: AGPL-3.0-only + +import * as React from "react"; +import Button from "@material-ui/core/Button"; +import TextField from "@material-ui/core/TextField"; +import Select from "@material-ui/core/Select"; +import MenuItem from "@material-ui/core/MenuItem"; +import FormControl from "@material-ui/core/FormControl"; +import InputLabel from "@material-ui/core/InputLabel"; +import IconDelete from "@material-ui/icons/Delete"; +import IconCancel from "@material-ui/icons/Clear"; +import IconSave from "@material-ui/icons/Save"; +import * as colors from "@material-ui/core/colors"; + +import AppBarOverride from "../widgets/AppBarOverride"; +import Container from "../widgets/Container"; +import ConfirmationDialog from "../widgets/ConfirmationDialog"; + +import * as Etebase from "etebase"; + +import ColorPicker from "../widgets/ColorPicker"; +import { defaultColor } from "../journal-processors"; +import { CachedCollection } from "../Pim/helpers"; +import { useCredentials } from "../credentials"; +import { getCollectionManager } from "../etebase-helpers"; + +interface PropsType { + collection?: CachedCollection; + onSave: (collection: Etebase.Collection) => void; + onDelete: (collection: Etebase.Collection) => void; + onCancel: () => void; +} + +interface FormErrors { + name?: string; + color?: string; +} + +export default function CollectionEdit(props: PropsType) { + const [errors, setErrors] = React.useState({}); + const [showDeleteDialog, setShowDeleteDialog] = React.useState(false); + const [info, setInfo] = React.useState(); + const [selectedColor, setSelectedColor] = React.useState(""); + const etebase = useCredentials()!; + + React.useEffect(() => { + if (props.collection !== undefined) { + setInfo(props.collection.metadata); + if (props.collection.metadata.color) { + setSelectedColor(props.collection.metadata.color); + } + } else { + setInfo({ + type: "etebase.vcard", + name: "", + description: "", + }); + } + }, [props.collection]); + + if (info === undefined) { + return ; + } + + async function onSubmit(e: React.FormEvent) { + e.preventDefault(); + const saveErrors: FormErrors = {}; + const fieldRequired = "This field is required!"; + + const { onSave } = props; + + if (!info) { + throw new Error("Got undefined info. Should never happen."); + } + + const name = info.name; + const color = selectedColor; + + if (!name) { + saveErrors.name = fieldRequired; + } + + if (selectedColor && !color) { + saveErrors.color = "Must be of the form #RRGGBB or #RRGGBBAA or empty"; + } + + if (Object.keys(saveErrors).length > 0) { + setErrors(saveErrors); + return; + } + + const colMgr = getCollectionManager(etebase); + const meta = { ...info, color }; + let collection; + if (props.collection) { + collection = props.collection.collection; + await collection.setMeta(meta); + } else { + collection = await colMgr.create(meta, ""); + } + + onSave(collection); + } + + function onDeleteRequest() { + setShowDeleteDialog(true); + } + + const { collection, onDelete, onCancel } = props; + const item = collection?.metadata; + + const pageTitle = (item !== undefined) ? item.name : "New Journal"; + + const styles = { + fullWidth: { + width: "100%", + }, + submit: { + marginTop: 40, + marginBottom: 20, + textAlign: "right" as any, + }, + }; + + const colTypes = { + "etebase.vcard": "Address Book", + "etebase.vevent": "Calendar", + "etebase.vtodo": "Task List", + }; + let collectionColorBox: React.ReactNode; + switch (info.type) { + case "etebase.vevent": + case "etebase.vtodo": + collectionColorBox = ( + setSelectedColor(color)} + error={errors.color} + /> + ); + break; + } + + return ( + <> + + +
+ + + Collection type + + + + ) => setInfo({ ...info, name: event.target.value })} + style={styles.fullWidth} + margin="normal" + error={!!errors.name} + helperText={errors.name} + /> + ) => setInfo({ ...info, description: event.target.value })} + style={styles.fullWidth} + margin="normal" + /> + {collectionColorBox} + +
+ + + {props.collection && + + } + + +
+ +
+ onDelete(props.collection?.collection!)} + onCancel={() => setShowDeleteDialog(false)} + > + Are you sure you would like to delete this journal? + + + ); +} diff --git a/src/Collections/Main.tsx b/src/Collections/Main.tsx index c05c436..44db901 100644 --- a/src/Collections/Main.tsx +++ b/src/Collections/Main.tsx @@ -4,13 +4,17 @@ import * as React from "react"; import { Switch, Route, useHistory } from "react-router"; +import * as Etebase from "etebase"; + import { useCredentials } from "../credentials"; -import { useCollections } from "../etebase-helpers"; +import { useCollections, getCollectionManager } from "../etebase-helpers"; import { routeResolver } from "../App"; import LoadingIndicator from "../widgets/LoadingIndicator"; import { CachedCollection, getDecryptCollectionsFunction, PimFab } from "../Pim/helpers"; import CollectionList from "./CollectionList"; +import PageNotFound from "../PageNotFound"; +import CollectionEdit from "./CollectionEdit"; const decryptCollections = getDecryptCollectionsFunction(); @@ -34,6 +38,25 @@ export default function CollectionsMain() { ); } + async function onSave(collection: Etebase.Collection): Promise { + const colMgr = getCollectionManager(etebase); + await colMgr.upload(collection); + + history.push(routeResolver.getRoute("collections")); + } + + async function onDelete(collection: Etebase.Collection) { + const colMgr = getCollectionManager(etebase); + await collection.delete(); + await colMgr.upload(collection); + + history.push(routeResolver.getRoute("collections")); + } + + function onCancel() { + history.goBack(); + } + return ( + + Import + + + + + { + const colUid = match.params.colUid; + const collection = cachedCollections.find((x) => x.collection.uid === colUid); + if (!collection) { + return (); + } + + return ( + + + + + + Members + + + Journal view + + + ); + }} + /> ); }