Implement adding new collections and editing existing

master
Tom Hacohen 4 years ago
parent 96ae079145
commit 3ebaf35f49

@ -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<FormErrors>({});
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
const [info, setInfo] = React.useState<Etebase.CollectionMetadata>();
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 <React.Fragment />;
}
async function onSubmit(e: React.FormEvent<any>) {
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 = (
<ColorPicker
defaultColor={defaultColor}
color={selectedColor}
onChange={(color: string) => setSelectedColor(color)}
error={errors.color}
/>
);
break;
}
return (
<>
<AppBarOverride title={pageTitle} />
<Container style={{ maxWidth: "30rem" }}>
<form onSubmit={onSubmit}>
<FormControl disabled={props.collection !== undefined} style={styles.fullWidth}>
<InputLabel>
Collection type
</InputLabel>
<Select
name="type"
required
value={info.type}
onChange={(event: React.ChangeEvent<{ value: string }>) => setInfo({ ...info, type: event.target.value })}
>
{Object.keys(colTypes).map((x) => (
<MenuItem key={x} value={x}>{colTypes[x]}</MenuItem>
))}
</Select>
</FormControl>
<TextField
name="name"
required
label="Name of this collection"
value={info.name}
onChange={(event: React.ChangeEvent<{ value: string }>) => setInfo({ ...info, name: event.target.value })}
style={styles.fullWidth}
margin="normal"
error={!!errors.name}
helperText={errors.name}
/>
<TextField
name="description"
label="Description (optional)"
value={info.description}
onChange={(event: React.ChangeEvent<{ value: string }>) => setInfo({ ...info, description: event.target.value })}
style={styles.fullWidth}
margin="normal"
/>
{collectionColorBox}
<div style={styles.submit}>
<Button
variant="contained"
onClick={onCancel}
>
<IconCancel style={{ marginRight: 8 }} />
Cancel
</Button>
{props.collection &&
<Button
variant="contained"
style={{ marginLeft: 15, backgroundColor: colors.red[500], color: "white" }}
onClick={onDeleteRequest}
>
<IconDelete style={{ marginRight: 8 }} />
Delete
</Button>
}
<Button
type="submit"
variant="contained"
color="secondary"
style={{ marginLeft: 15 }}
>
<IconSave style={{ marginRight: 8 }} />
Save
</Button>
</div>
</form>
</Container>
<ConfirmationDialog
title="Delete Confirmation"
labelOk="Delete"
open={showDeleteDialog}
onOk={() => onDelete(props.collection?.collection!)}
onCancel={() => setShowDeleteDialog(false)}
>
Are you sure you would like to delete this journal?
</ConfirmationDialog>
</>
);
}

@ -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<void> {
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 (
<Switch>
<Route
@ -47,6 +70,60 @@ export default function CollectionsMain() {
)}
/>
</Route>
<Route
path={routeResolver.getRoute("collections.import")}
exact
>
Import
</Route>
<Route
path={routeResolver.getRoute("collections.new")}
exact
>
<CollectionEdit
onSave={onSave}
onDelete={onDelete}
onCancel={onCancel}
/>
</Route>
<Route
path={routeResolver.getRoute("collections._id")}
render={({ match }) => {
const colUid = match.params.colUid;
const collection = cachedCollections.find((x) => x.collection.uid === colUid);
if (!collection) {
return (<PageNotFound />);
}
return (
<Switch>
<Route
path={routeResolver.getRoute("collections._id.edit")}
exact
>
<CollectionEdit
collection={collection}
onSave={onSave}
onDelete={onDelete}
onCancel={onCancel}
/>
</Route>
<Route
path={routeResolver.getRoute("collections._id.members")}
exact
>
Members
</Route>
<Route
path={routeResolver.getRoute("collections._id")}
exact
>
Journal view
</Route>
</Switch>
);
}}
/>
</Switch>
);
}

Loading…
Cancel
Save