Add a collections section.
parent
1b73899182
commit
96ae079145
src
Collections
Pim
SideMenu
|
@ -4,7 +4,7 @@
|
|||
import * as React from "react";
|
||||
import { List as ImmutableList } from "immutable";
|
||||
import { connect, useDispatch } from "react-redux";
|
||||
import { useHistory } from "react-router";
|
||||
import { useHistory, useLocation } from "react-router";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { MuiThemeProvider as ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; // v1.x
|
||||
import amber from "@material-ui/core/colors/amber";
|
||||
|
@ -67,9 +67,9 @@ export const routeResolver = new RouteResolver({
|
|||
new: "new",
|
||||
},
|
||||
},
|
||||
journals: {
|
||||
collections: {
|
||||
_id: {
|
||||
_base: ":journalUid",
|
||||
_base: ":colUid",
|
||||
edit: "edit",
|
||||
items: {
|
||||
_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}`;
|
||||
}
|
||||
|
||||
export function getDecryptCollectionsFunction(_colType: string) {
|
||||
export function getDecryptCollectionsFunction(_colType?: string) {
|
||||
return memoize(
|
||||
async function (collections: Etebase.Collection[]) {
|
||||
const entries: CachedCollection[] = [];
|
||||
|
|
|
@ -44,11 +44,11 @@ export default function SideMenu(props: PropsType) {
|
|||
loggedInItems = (
|
||||
<React.Fragment>
|
||||
<ListItem
|
||||
primaryText="Journals"
|
||||
primaryText="Collections"
|
||||
leftIcon={<ActionJournals />}
|
||||
onClick={() => {
|
||||
props.onCloseDrawerRequest();
|
||||
history.push(routeResolver.getRoute("journals"));
|
||||
history.push(routeResolver.getRoute("collections"));
|
||||
}}
|
||||
/>
|
||||
<ListItem
|
||||
|
@ -56,7 +56,7 @@ export default function SideMenu(props: PropsType) {
|
|||
leftIcon={<IconImport />}
|
||||
onClick={() => {
|
||||
props.onCloseDrawerRequest();
|
||||
history.push(routeResolver.getRoute("journals.import"));
|
||||
history.push(routeResolver.getRoute("collections.import"));
|
||||
}}
|
||||
/>
|
||||
<ListItem
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Route, Switch, Redirect, useHistory } from "react-router";
|
|||
import moment from "moment";
|
||||
import "moment/locale/en-gb";
|
||||
|
||||
import { List, Map } from "immutable";
|
||||
import { Map } from "immutable";
|
||||
|
||||
import { routeResolver } from "./App";
|
||||
|
||||
|
@ -18,13 +18,11 @@ import Container from "./widgets/Container";
|
|||
import ContactsMain from "./Contacts/Main";
|
||||
import CalendarsMain from "./Calendars/Main";
|
||||
import TasksMain from "./Tasks/Main";
|
||||
import CollectionsMain from "./Collections/Main";
|
||||
|
||||
import Journals from "./Journals";
|
||||
import Settings from "./Settings";
|
||||
import Debug from "./Debug";
|
||||
|
||||
import * as EteSync from "etesync";
|
||||
|
||||
import { SyncManager } from "./sync/SyncManager";
|
||||
|
||||
import { StoreState } from "./store";
|
||||
|
@ -32,20 +30,12 @@ import { performSync } from "./store/actions";
|
|||
import { useCredentials } from "./credentials";
|
||||
import PimNavigationTabs from "./Pim/NavigationTabs";
|
||||
|
||||
export interface SyncInfoJournal {
|
||||
journal: EteSync.Journal;
|
||||
journalEntries: List<EteSync.Entry>;
|
||||
collection: EteSync.CollectionInfo;
|
||||
entries: List<EteSync.SyncEntry>;
|
||||
}
|
||||
|
||||
export type SyncInfoJournal = any;
|
||||
export type SyncInfo = Map<string, SyncInfoJournal>;
|
||||
|
||||
export default function SyncGate() {
|
||||
const etebase = useCredentials();
|
||||
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 [loading, setLoading] = React.useState(true);
|
||||
|
||||
|
@ -69,9 +59,6 @@ export default function SyncGate() {
|
|||
// FIXME: Shouldn't be here
|
||||
moment.locale(settings.locale);
|
||||
|
||||
// FIXME: remove
|
||||
const etesync = etebase as any;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
|
@ -118,23 +105,11 @@ export default function SyncGate() {
|
|||
</Route>
|
||||
</Switch>
|
||||
</Route>
|
||||
{false && (
|
||||
<>
|
||||
<Route
|
||||
path={routeResolver.getRoute("journals")}
|
||||
render={({ location, history }) => (
|
||||
<Journals
|
||||
etesync={etesync}
|
||||
userInfo={userInfo}
|
||||
syncInfo={false as any}
|
||||
journals={journals}
|
||||
location={location}
|
||||
history={history}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Route
|
||||
path={routeResolver.getRoute("collections")}
|
||||
>
|
||||
<CollectionsMain />
|
||||
</Route>
|
||||
<Route
|
||||
path={routeResolver.getRoute("settings")}
|
||||
exact
|
||||
|
|
|
@ -57,10 +57,12 @@ export const getCollectionManager = memoize(function (etebase: Etebase.Account)
|
|||
});
|
||||
|
||||
// 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);
|
||||
return usePromiseMemo(
|
||||
getCollectionsByType(cachedCollections, colType, etebase),
|
||||
(colType) ?
|
||||
getCollectionsByType(cachedCollections, colType, etebase) :
|
||||
getCollections(cachedCollections, etebase),
|
||||
[etebase, cachedCollections, colType]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue