Add support for tasks.

master
Tom Hacohen 6 years ago
parent d11180fed6
commit 2fd674a456

@ -37,7 +37,10 @@ const muiTheme = createMuiTheme({
dark: lightBlue.A700,
contrastText: 'white',
},
}
},
typography: {
useNextVariants: true,
},
});
export const routeResolver = new RouteResolver({
@ -59,6 +62,14 @@ export const routeResolver = new RouteResolver({
},
new: 'new',
},
tasks: {
_id: {
_base: ':itemUid',
edit: 'edit',
log: 'log',
},
new: 'new',
},
},
journals: {
_id: {

@ -10,6 +10,7 @@ import SearchableAddressBook from '../components/SearchableAddressBook';
import Contact from '../components/Contact';
import Calendar from '../components/Calendar';
import Event from '../components/Event';
import TaskList from '../components/TaskList';
import AppBarOverride from '../widgets/AppBarOverride';
import Container from '../widgets/Container';
@ -17,7 +18,7 @@ import Container from '../widgets/Container';
import JournalEntries from '../components/JournalEntries';
import journalView from './journalView';
import { syncEntriesToItemMap, syncEntriesToCalendarItemMap } from '../journal-processors';
import { syncEntriesToItemMap, syncEntriesToEventItemMap, syncEntriesToTaskItemMap } from '../journal-processors';
import { SyncInfo, SyncInfoJournal } from '../SyncGate';
@ -39,6 +40,7 @@ interface PropsTypeInner extends PropsType {
const JournalAddressBook = journalView(SearchableAddressBook, Contact);
const PersistCalendar = historyPersistor('Calendar')(Calendar);
const JournalCalendar = journalView(PersistCalendar, Event);
const JournalTaskList = journalView(TaskList, Event);
class Journal extends React.Component<PropsTypeInner> {
state: {
@ -65,17 +67,23 @@ class Journal extends React.Component<PropsTypeInner> {
let itemsTitle: string;
let itemsView: JSX.Element;
if (collectionInfo.type === 'CALENDAR') {
itemsView =
<JournalCalendar journal={journal} entries={syncEntriesToCalendarItemMap(collectionInfo, syncEntries)} />;
itemsView = (
<JournalCalendar
journal={journal}
entries={syncEntriesToEventItemMap(collectionInfo, syncEntries)}
/>);
itemsTitle = 'Events';
} else if (collectionInfo.type === 'ADDRESS_BOOK') {
itemsView =
<JournalAddressBook journal={journal} entries={syncEntriesToItemMap(collectionInfo, syncEntries)} />;
itemsTitle = 'Contacts';
} else if (collectionInfo.type === 'TASKS') {
itemsView = <div>Task preview is not yet supported</div>;
itemsView = (
<JournalTaskList
journal={journal}
entries={syncEntriesToTaskItemMap(collectionInfo, syncEntries)}
/>);
itemsTitle = 'Tasks';
journalOnly = true;
} else {
itemsView = <div>Preview is not supported for this journal type</div>;
itemsTitle = 'Items';

@ -13,8 +13,9 @@ import Container from '../widgets/Container';
import SearchableAddressBook from '../components/SearchableAddressBook';
import Calendar from '../components/Calendar';
import TaskList from '../components/TaskList';
import { EventType, ContactType } from '../pim-types';
import { EventType, ContactType, TaskType } from '../pim-types';
import { routeResolver } from '../App';
@ -22,12 +23,14 @@ import { historyPersistor } from '../persist-state-history';
const addressBookTitle = 'Address Book';
const calendarTitle = 'Calendar';
const tasksTitle = 'Tasks';
const PersistCalendar = historyPersistor('Calendar')(Calendar);
interface PropsType {
contacts: Array<ContactType>;
events: Array<EventType>;
tasks: Array<TaskType>;
location?: Location;
history?: History;
theme: Theme;
@ -42,6 +45,7 @@ class PimMain extends React.PureComponent<PropsType> {
super(props);
this.state = {tab: 1};
this.eventClicked = this.eventClicked.bind(this);
this.taskClicked = this.taskClicked.bind(this);
this.contactClicked = this.contactClicked.bind(this);
this.floatingButtonClicked = this.floatingButtonClicked.bind(this);
this.newEvent = this.newEvent.bind(this);
@ -54,6 +58,13 @@ class PimMain extends React.PureComponent<PropsType> {
routeResolver.getRoute('pim.events._id', { itemUid: uid }));
}
taskClicked(event: ICAL.Event) {
const uid = event.uid;
this.props.history!.push(
routeResolver.getRoute('pim.tasks._id', { itemUid: uid }));
}
contactClicked(contact: ContactType) {
const uid = contact.uid;
@ -106,24 +117,33 @@ class PimMain extends React.PureComponent<PropsType> {
<Tab
label={calendarTitle}
/>
<Tab
label={tasksTitle}
/>
</Tabs>
{ tab === 0 &&
<Container>
<Container>
{ tab === 0 &&
<SearchableAddressBook entries={this.props.contacts} onItemClick={this.contactClicked} />
</Container>
}
{ tab === 1 &&
<Container>
}
{ tab === 1 &&
<PersistCalendar
entries={this.props.events}
onItemClick={this.eventClicked}
onSlotClick={this.newEvent}
/>
</Container>
}
}
{ tab === 2 &&
<TaskList
entries={this.props.tasks}
onItemClick={this.taskClicked}
/>
}
</Container>
<Fab
color="primary"
disabled={tab === 2}
style={style.floatingButton}
onClick={this.floatingButtonClicked}
>

@ -16,7 +16,7 @@ import { pure } from 'recompose';
import { History } from 'history';
import { PimType, ContactType, EventType } from '../pim-types';
import { PimType, ContactType, EventType, TaskType } from '../pim-types';
import Container from '../widgets/Container';
@ -36,7 +36,7 @@ import { SyncInfo } from '../SyncGate';
import { createJournalEntry } from '../etesync-helpers';
import { syncEntriesToItemMap, syncEntriesToCalendarItemMap } from '../journal-processors';
import { syncEntriesToItemMap, syncEntriesToEventItemMap, syncEntriesToTaskItemMap } from '../journal-processors';
function objValues(obj: any) {
return Object.keys(obj).map((x) => obj[x]);
@ -47,8 +47,10 @@ const itemsSelector = createSelector(
(syncInfo) => {
let collectionsAddressBook: Array<EteSync.CollectionInfo> = [];
let collectionsCalendar: Array<EteSync.CollectionInfo> = [];
let collectionsTaskList: Array<EteSync.CollectionInfo> = [];
let addressBookItems: {[key: string]: ContactType} = {};
let calendarItems: {[key: string]: EventType} = {};
let taskListItems: {[key: string]: TaskType} = {};
syncInfo.forEach(
(syncJournal) => {
const syncEntries = syncJournal.entries;
@ -59,13 +61,18 @@ const itemsSelector = createSelector(
addressBookItems = syncEntriesToItemMap(collectionInfo, syncEntries, addressBookItems);
collectionsAddressBook.push(collectionInfo);
} else if (collectionInfo.type === 'CALENDAR') {
calendarItems = syncEntriesToCalendarItemMap(collectionInfo, syncEntries, calendarItems);
calendarItems = syncEntriesToEventItemMap(collectionInfo, syncEntries, calendarItems);
collectionsCalendar.push(collectionInfo);
} else if (collectionInfo.type === 'TASKS') {
taskListItems = syncEntriesToTaskItemMap(collectionInfo, syncEntries, taskListItems);
collectionsTaskList.push(collectionInfo);
}
}
);
return { collectionsAddressBook, collectionsCalendar, addressBookItems, calendarItems };
return {
collectionsAddressBook, collectionsCalendar, collectionsTaskList, addressBookItems, calendarItems, taskListItems
};
},
);
@ -188,6 +195,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
<Button
color="secondary"
variant="contained"
disabled={!props.componentEdit}
className={classes.button}
style={{marginLeft: 15}}
onClick={() =>
@ -300,7 +308,7 @@ class Pim extends React.PureComponent {
}
render() {
const { collectionsAddressBook, collectionsCalendar, addressBookItems, calendarItems } = itemsSelector(this.props);
const { collectionsAddressBook, collectionsCalendar, collectionsTaskList, addressBookItems, calendarItems, taskListItems } = itemsSelector(this.props);
return (
<Switch>
@ -311,6 +319,7 @@ class Pim extends React.PureComponent {
<PimMain
contacts={objValues(addressBookItems)}
events={objValues(calendarItems)}
tasks={objValues(taskListItems)}
history={history}
/>
)}
@ -347,6 +356,22 @@ class Pim extends React.PureComponent {
/>
)}
/>
<Route
path={routeResolver.getRoute('pim.tasks')}
render={() => (
<CollectionRoutes
syncInfo={this.props.syncInfo}
routePrefix="pim.tasks"
collections={collectionsTaskList}
items={taskListItems}
componentEdit={undefined}
componentView={Event}
onItemSave={this.onItemSave}
onItemDelete={this.onItemDelete}
onItemCancel={this.onCancel}
/>
)}
/>
</Switch>
);
}

@ -0,0 +1,73 @@
import * as React from 'react';
import { pure } from 'recompose';
import { createSelector } from 'reselect';
import { List, ListItem } from '../widgets/List';
import { TaskType } from '../pim-types';
const TaskListItem = pure((_props: any) => {
const {
entry,
onClick,
} = _props;
const title = entry.title;
return (
<ListItem
primaryText={title}
onClick={() => onClick(entry)}
/>
);
});
const sortSelector = createSelector(
(entries: Array<TaskType>) => entries,
(entries) => {
return entries.sort((_a, _b) => {
const a = _a.title;
const b = _b.title;
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
},
);
class TaskList extends React.PureComponent {
props: {
entries: Array<TaskType>,
onItemClick: (contact: TaskType) => void,
};
render() {
const entries = this.props.entries.filter((x) => !x.completed);
const sortedEntries = sortSelector(entries);
let itemList = sortedEntries.map((entry, idx, array) => {
const uid = entry.uid;
return (
<TaskListItem
key={uid}
entry={entry}
onClick={this.props.onItemClick}
/>
);
});
return (
<List>
{itemList}
</List>
);
}
}
export default TaskList;

@ -2,7 +2,7 @@ import { List } from 'immutable';
import * as ICAL from 'ical.js';
import { EventType, ContactType } from './pim-types';
import { EventType, ContactType, TaskType } from './pim-types';
import * as EteSync from './api/EteSync';
@ -50,14 +50,15 @@ function colorIntToHtml(color: number) {
((alpha > 0) ? toHex(alpha) : '');
}
export function syncEntriesToCalendarItemMap(
collection: EteSync.CollectionInfo, entries: List<EteSync.SyncEntry>, base: {[key: string]: EventType} = {}) {
function syncEntriesToCalendarItemMap<T extends EventType>(
ItemType: any,
collection: EteSync.CollectionInfo, entries: List<EteSync.SyncEntry>, base: {[key: string]: T} = {}) {
let items = base;
const color = colorIntToHtml(collection.color);
entries.forEach((syncEntry) => {
let comp = EventType.fromVCalendar(new ICAL.Component(ICAL.parse(syncEntry.content)));
let comp = ItemType.fromVCalendar(new ICAL.Component(ICAL.parse(syncEntry.content)));
if (comp === null) {
return;
@ -80,3 +81,13 @@ export function syncEntriesToCalendarItemMap(
return items;
}
export function syncEntriesToEventItemMap(
collection: EteSync.CollectionInfo, entries: List<EteSync.SyncEntry>, base: {[key: string]: EventType} = {}) {
return syncEntriesToCalendarItemMap<EventType>(EventType, collection, entries, base);
}
export function syncEntriesToTaskItemMap(
collection: EteSync.CollectionInfo, entries: List<EteSync.SyncEntry>, base: {[key: string]: TaskType} = {}) {
return syncEntriesToCalendarItemMap<TaskType>(TaskType, collection, entries, base);
}

@ -53,20 +53,16 @@ export class EventType extends ICAL.Event implements PimType {
}
}
export class TaskType extends ICAL.Event implements PimType {
export class TaskType extends EventType {
color: string;
static fromVCalendar(comp: ICAL.Component) {
return new EventType(comp.getFirstSubcomponent('vtodo'));
return new TaskType(comp.getFirstSubcomponent('vtodo'));
}
toIcal() {
let comp = new ICAL.Component(['vcalendar', [], []]);
comp.updatePropertyWithValue('prodid', '-//iCal.js EteSync Web');
comp.updatePropertyWithValue('version', '4.0');
comp.addSubcomponent(this.component);
return comp.toString();
get completed() {
const status = this.component.getFirstPropertyValue('status');
return status === 'COMPLETED';
}
clone() {

Loading…
Cancel
Save