Add a collections section.

master
Tom Hacohen 4 years ago
parent 1b73899182
commit 96ae079145

@ -4,7 +4,7 @@
import * as React from "react"; import * as React from "react";
import { List as ImmutableList } from "immutable"; import { List as ImmutableList } from "immutable";
import { connect, useDispatch } from "react-redux"; import { connect, useDispatch } from "react-redux";
import { useHistory } from "react-router"; import { useHistory, useLocation } from "react-router";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { MuiThemeProvider as ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; // v1.x import { MuiThemeProvider as ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; // v1.x
import amber from "@material-ui/core/colors/amber"; import amber from "@material-ui/core/colors/amber";
@ -67,9 +67,9 @@ export const routeResolver = new RouteResolver({
new: "new", new: "new",
}, },
}, },
journals: { collections: {
_id: { _id: {
_base: ":journalUid", _base: ":colUid",
edit: "edit", edit: "edit",
items: { items: {
_id: { _id: {

@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from "react";
import { Link, useHistory } from "react-router-dom";
import IconButton from "@material-ui/core/IconButton";
import IconAdd from "@material-ui/icons/Add";
import ContactsIcon from "@material-ui/icons/Contacts";
import CalendarTodayIcon from "@material-ui/icons/CalendarToday";
import FormatListBulletedIcon from "@material-ui/icons/FormatListBulleted";
import { List, ListItem } from "../widgets/List";
import AppBarOverride from "../widgets/AppBarOverride";
import Container from "../widgets/Container";
import { routeResolver } from "../App";
import { CachedCollection } from "../Pim/helpers";
interface PropsType {
collections: CachedCollection[];
}
export default function CollectionList(props: PropsType) {
const history = useHistory();
const collectionMap = {
"etebase.vcard": [],
"etebase.vevent": [],
"etebase.vtodo": [],
};
function colClicked(colUid: string) {
history.push(routeResolver.getRoute("collections._id", { colUid }));
}
for (const col of props.collections) {
if (collectionMap[col.metadata.type]) {
const colorBox = undefined; // FIXME
collectionMap[col.metadata.type].push((
<ListItem key={col.collection.uid} rightIcon={colorBox} insetChildren
onClick={() => colClicked(col.collection.uid)}>
{col.metadata.name}
</ListItem>
));
}
}
return (
<Container>
<AppBarOverride title="Collections">
<IconButton
component={Link}
title="New"
{...{ to: routeResolver.getRoute("collections.new") }}
>
<IconAdd />
</IconButton>
</AppBarOverride>
<List>
<ListItem
primaryText="Address Books"
leftIcon={<ContactsIcon />}
nestedItems={collectionMap["etebase.vcard"]}
/>
<ListItem
primaryText="Calendars"
leftIcon={<CalendarTodayIcon />}
nestedItems={collectionMap["etebase.vevent"]}
/>
<ListItem
primaryText="Tasks"
leftIcon={<FormatListBulletedIcon />}
nestedItems={collectionMap["etebase.vtodo"]}
/>
</List>
</Container>
);
}

@ -0,0 +1,53 @@
// 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 { useCredentials } from "../credentials";
import { useCollections } from "../etebase-helpers";
import { routeResolver } from "../App";
import LoadingIndicator from "../widgets/LoadingIndicator";
import { CachedCollection, getDecryptCollectionsFunction, PimFab } from "../Pim/helpers";
import CollectionList from "./CollectionList";
const decryptCollections = getDecryptCollectionsFunction();
export default function CollectionsMain() {
const [cachedCollections, setCachedCollections] = React.useState<CachedCollection[]>();
const history = useHistory();
const etebase = useCredentials()!;
const collections = useCollections(etebase);
React.useEffect(() => {
if (collections) {
decryptCollections(collections)
.then((entries) => setCachedCollections(entries));
// FIXME: handle failure to decrypt collections
}
}, [collections]);
if (!cachedCollections) {
return (
<LoadingIndicator />
);
}
return (
<Switch>
<Route
path={routeResolver.getRoute("collections")}
exact
>
<CollectionList collections={cachedCollections} />
<PimFab
onClick={() => history.push(
routeResolver.getRoute("collections.new")
)}
/>
</Route>
</Switch>
);
}

@ -21,7 +21,7 @@ export function getItemNavigationUid(item: PimType) {
return `${item.collectionUid}|${item.itemUid}`; return `${item.collectionUid}|${item.itemUid}`;
} }
export function getDecryptCollectionsFunction(_colType: string) { export function getDecryptCollectionsFunction(_colType?: string) {
return memoize( return memoize(
async function (collections: Etebase.Collection[]) { async function (collections: Etebase.Collection[]) {
const entries: CachedCollection[] = []; const entries: CachedCollection[] = [];

@ -44,11 +44,11 @@ export default function SideMenu(props: PropsType) {
loggedInItems = ( loggedInItems = (
<React.Fragment> <React.Fragment>
<ListItem <ListItem
primaryText="Journals" primaryText="Collections"
leftIcon={<ActionJournals />} leftIcon={<ActionJournals />}
onClick={() => { onClick={() => {
props.onCloseDrawerRequest(); props.onCloseDrawerRequest();
history.push(routeResolver.getRoute("journals")); history.push(routeResolver.getRoute("collections"));
}} }}
/> />
<ListItem <ListItem
@ -56,7 +56,7 @@ export default function SideMenu(props: PropsType) {
leftIcon={<IconImport />} leftIcon={<IconImport />}
onClick={() => { onClick={() => {
props.onCloseDrawerRequest(); props.onCloseDrawerRequest();
history.push(routeResolver.getRoute("journals.import")); history.push(routeResolver.getRoute("collections.import"));
}} }}
/> />
<ListItem <ListItem

@ -8,7 +8,7 @@ import { Route, Switch, Redirect, useHistory } from "react-router";
import moment from "moment"; import moment from "moment";
import "moment/locale/en-gb"; import "moment/locale/en-gb";
import { List, Map } from "immutable"; import { Map } from "immutable";
import { routeResolver } from "./App"; import { routeResolver } from "./App";
@ -18,13 +18,11 @@ import Container from "./widgets/Container";
import ContactsMain from "./Contacts/Main"; import ContactsMain from "./Contacts/Main";
import CalendarsMain from "./Calendars/Main"; import CalendarsMain from "./Calendars/Main";
import TasksMain from "./Tasks/Main"; import TasksMain from "./Tasks/Main";
import CollectionsMain from "./Collections/Main";
import Journals from "./Journals";
import Settings from "./Settings"; import Settings from "./Settings";
import Debug from "./Debug"; import Debug from "./Debug";
import * as EteSync from "etesync";
import { SyncManager } from "./sync/SyncManager"; import { SyncManager } from "./sync/SyncManager";
import { StoreState } from "./store"; import { StoreState } from "./store";
@ -32,20 +30,12 @@ import { performSync } from "./store/actions";
import { useCredentials } from "./credentials"; import { useCredentials } from "./credentials";
import PimNavigationTabs from "./Pim/NavigationTabs"; import PimNavigationTabs from "./Pim/NavigationTabs";
export interface SyncInfoJournal { export type SyncInfoJournal = any;
journal: EteSync.Journal;
journalEntries: List<EteSync.Entry>;
collection: EteSync.CollectionInfo;
entries: List<EteSync.SyncEntry>;
}
export type SyncInfo = Map<string, SyncInfoJournal>; export type SyncInfo = Map<string, SyncInfoJournal>;
export default function SyncGate() { export default function SyncGate() {
const etebase = useCredentials(); const etebase = useCredentials();
const settings = useSelector((state: StoreState) => state.settings); const settings = useSelector((state: StoreState) => state.settings);
const userInfo = useSelector((state: StoreState) => state.cache.userInfo);
const journals = useSelector((state: StoreState) => state.cache.journals);
const dispatch = useDispatch(); const dispatch = useDispatch();
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
@ -69,9 +59,6 @@ export default function SyncGate() {
// FIXME: Shouldn't be here // FIXME: Shouldn't be here
moment.locale(settings.locale); moment.locale(settings.locale);
// FIXME: remove
const etesync = etebase as any;
return ( return (
<Switch> <Switch>
<Route <Route
@ -118,23 +105,11 @@ export default function SyncGate() {
</Route> </Route>
</Switch> </Switch>
</Route> </Route>
{false && ( <Route
<> path={routeResolver.getRoute("collections")}
<Route >
path={routeResolver.getRoute("journals")} <CollectionsMain />
render={({ location, history }) => ( </Route>
<Journals
etesync={etesync}
userInfo={userInfo}
syncInfo={false as any}
journals={journals}
location={location}
history={history}
/>
)}
/>
</>
)}
<Route <Route
path={routeResolver.getRoute("settings")} path={routeResolver.getRoute("settings")}
exact exact

@ -57,10 +57,12 @@ export const getCollectionManager = memoize(function (etebase: Etebase.Account)
}); });
// React specific stuff // React specific stuff
export function useCollections(etebase: Etebase.Account, colType: string) { export function useCollections(etebase: Etebase.Account, colType?: string) {
const cachedCollections = useSelector((state: StoreState) => state.cache2.collections); const cachedCollections = useSelector((state: StoreState) => state.cache2.collections);
return usePromiseMemo( return usePromiseMemo(
getCollectionsByType(cachedCollections, colType, etebase), (colType) ?
getCollectionsByType(cachedCollections, colType, etebase) :
getCollections(cachedCollections, etebase),
[etebase, cachedCollections, colType] [etebase, cachedCollections, colType]
); );
} }

Loading…
Cancel
Save