Initial version of contact editing.

master
Tom Hacohen 7 years ago
parent 9521e3dae3
commit 6d76559d08

@ -0,0 +1,299 @@
import * as React from 'react';
import IconButton from 'material-ui/IconButton';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import IconAdd from 'material-ui/svg-icons/content/add';
import IconClear from 'material-ui/svg-icons/content/clear';
import * as uuid from 'uuid';
import * as ICAL from 'ical.js';
import * as EteSync from './api/EteSync';
import { ContactType } from './pim-types';
const TypeSelector = (props: any) => {
const types = [
{type: 'Home'},
{type: 'Work'},
{type: 'Other'},
];
return (
<SelectField
style={props.style}
value={props.value}
onChange={props.onChange}
>
{types.map((x) => (
<MenuItem key={x.type} value={x.type.toLowerCase()} primaryText={x.type} />
))}
</SelectField>
);
};
class ValueType {
type: string;
value: string;
constructor(type?: string, value?: string) {
this.type = type ? type : 'home';
this.value = value ? value : '';
}
}
interface ValueTypeComponentProps {
type?: string;
style?: object;
name: string;
hintText: string;
value: ValueType;
onClearRequest: (name: string) => void;
onChange: (name: string, type: string, value: string) => void;
}
const ValueTypeComponent = (props: ValueTypeComponentProps) => {
return (
<React.Fragment>
<TextField
type={props.type}
name={props.name}
hintText={props.hintText}
style={props.style}
value={props.value.value}
onChange={(event: any) => props.onChange(props.name, props.value.type, event.target.value)}
/>
<IconButton
onClick={() => props.onClearRequest(props.name)}
tooltip="Remove"
>
<IconClear />
</IconButton>
<TypeSelector
value={props.value.type}
onChange={
(contact: object, key: number, payload: any) => props.onChange(props.name, payload, props.value.value)
}
/>
</React.Fragment>
);
};
class ContactEdit extends React.Component {
state: {
uid: string,
fn: string;
phones: ValueType[];
emails: ValueType[];
journalUid: string;
};
props: {
collections: Array<EteSync.CollectionInfo>,
initialCollection?: string,
contact?: ContactType,
onSave: (contact: ContactType, journalUid: string, originalContact?: ContactType) => void;
};
constructor(props: any) {
super(props);
this.state = {
uid: '',
fn: '',
phones: [new ValueType()],
emails: [new ValueType()],
journalUid: '',
};
if (this.props.contact !== undefined) {
const contact = this.props.contact;
this.state.uid = contact.uid;
this.state.fn = contact.fn ? contact.fn : '';
} else {
this.state.uid = uuid.v4();
}
if (props.collections[0]) {
this.state.journalUid = props.collections[0].uid;
}
this.onSubmit = this.onSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleValueTypeChange = this.handleValueTypeChange.bind(this);
this.addValueType = this.addValueType.bind(this);
this.removeValueType = this.removeValueType.bind(this);
}
componentWillReceiveProps(nextProps: any) {
if (this.props.collections !== nextProps.collections) {
if (nextProps.collections[0]) {
this.setState({journalUid: nextProps.collections[0].uid});
}
}
}
addValueType(name: string) {
this.setState((prevState, props) => {
let newArray = prevState[name].slice(0);
newArray.push(new ValueType());
return {
...prevState,
[name]: newArray,
};
});
}
removeValueType(name: string, idx: number) {
this.setState((prevState, props) => {
let newArray = prevState[name].slice(0);
newArray.splice(idx, 1);
return {
...prevState,
[name]: newArray,
};
});
}
handleValueTypeChange(name: string, idx: number, value: ValueType) {
this.setState((prevState, props) => {
let newArray = prevState[name].slice(0);
newArray[idx] = value;
return {
...prevState,
[name]: newArray,
};
});
}
handleChange(name: string, value: string) {
this.setState({
[name]: value
});
}
handleInputChange(contact: any) {
const name = contact.target.name;
const value = contact.target.value;
this.handleChange(name, value);
}
onSubmit(e: any) {
e.prcontactDefault();
let contact = new ContactType(new ICAL.Component(['vcard', []]));
// contact.uid = this.state.uid;
// contact.fn = this.state.fn;
this.props.onSave(contact, this.state.journalUid, this.props.contact);
}
render() {
const styles = {
form: {
},
fullWidth: {
width: '100%',
boxSizing: 'border-box',
},
submit: {
marginTop: 40,
textAlign: 'right',
},
};
return (
<React.Fragment>
<h2>
{this.props.contact ? 'Edit Contact' : 'New Contact'}
</h2>
<form style={styles.form} onSubmit={this.onSubmit}>
<SelectField
style={styles.fullWidth}
value={this.state.journalUid}
floatingLabelText="Saving to"
disabled={this.props.contact !== undefined}
onChange={(contact: object, key: number, payload: any) => this.handleChange('journalUid', payload)}
>
{this.props.collections.map((x) => (
<MenuItem key={x.uid} value={x.uid} primaryText={x.displayName} />
))}
</SelectField>
<TextField
name="fn"
hintText="Name"
style={styles.fullWidth}
value={this.state.fn}
onChange={this.handleInputChange}
/>
<div>
Phone numbers
<IconButton
onClick={() => this.addValueType('phones')}
tooltip="Add phone number"
>
<IconAdd />
</IconButton>
</div>
{this.state.phones.map((x, idx) => (
<ValueTypeComponent
key={idx}
name="phones"
hintText="Phone"
value={this.state.phones[idx]}
onClearRequest={(name: string) => this.removeValueType(name, idx)}
onChange={(name: string, type: string, value: string) => (
this.handleValueTypeChange(name, idx, {type, value})
)}
/>
))}
<div>
Emails
<IconButton
onClick={() => this.addValueType('emails')}
tooltip="Add email address"
>
<IconAdd />
</IconButton>
</div>
{this.state.emails.map((x, idx) => (
<ValueTypeComponent
key={idx}
name="emails"
hintText="Email"
value={this.state.emails[idx]}
onClearRequest={(name: string) => this.removeValueType(name, idx)}
onChange={(name: string, type: string, value: string) => (
this.handleValueTypeChange(name, idx, {type, value})
)}
/>
))}
<div style={styles.submit}>
<RaisedButton
type="submit"
label="Save"
secondary={true}
/>
</div>
<div>
Not all types are supported at the moment. If you are editing a contact,
the unsupported types will be copied as is.
</div>
</form>
</React.Fragment>
);
}
}
export default ContactEdit;

@ -5,10 +5,11 @@ import IconEdit from 'material-ui/svg-icons/editor/mode-edit';
import * as EteSync from './api/EteSync'; import * as EteSync from './api/EteSync';
import { EventType } from './pim-types'; import { ContactType, EventType } from './pim-types';
import Container from './Container'; import Container from './Container';
import ContactEdit from './ContactEdit';
import Contact from './Contact'; import Contact from './Contact';
import EventEdit from './EventEdit'; import EventEdit from './EventEdit';
import Event from './Event'; import Event from './Event';
@ -61,9 +62,31 @@ class Pim extends React.Component {
}); });
} }
onContactSave(contact: ContactType, journalUid: string, originalContact?: ContactType) {
const journal = this.props.journals.find((x) => (x.uid === journalUid));
if (journal === undefined) {
return;
}
const entries = this.props.entries[journal.uid];
if (entries.value === null) {
return;
}
let action = (originalContact === undefined) ? EteSync.SyncEntryAction.Add : EteSync.SyncEntryAction.Change;
let saveContact = store.dispatch(
createJournalEntry(this.props.etesync, journal, entries.value, action, contact.toIcal()));
(saveContact as any).then(() => {
this.props.history.goBack();
});
}
render() { render() {
const derived = this.props.etesync.encryptionKey; const derived = this.props.etesync.encryptionKey;
let collectionsAddressBook: Array<EteSync.CollectionInfo> = [];
let collectionsCalendar: Array<EteSync.CollectionInfo> = []; let collectionsCalendar: Array<EteSync.CollectionInfo> = [];
let syncEntriesCalendar = []; let syncEntriesCalendar = [];
let syncEntriesAddressBook = []; let syncEntriesAddressBook = [];
@ -93,6 +116,7 @@ class Pim extends React.Component {
if (collectionInfo.type === 'ADDRESS_BOOK') { if (collectionInfo.type === 'ADDRESS_BOOK') {
syncEntriesAddressBook.push(syncEntriesToItemMap(collectionInfo, syncEntries)); syncEntriesAddressBook.push(syncEntriesToItemMap(collectionInfo, syncEntries));
collectionsAddressBook.push(collectionInfo);
} else if (collectionInfo.type === 'CALENDAR') { } else if (collectionInfo.type === 'CALENDAR') {
syncEntriesCalendar.push(syncEntriesToCalendarItemMap(collectionInfo, syncEntries)); syncEntriesCalendar.push(syncEntriesToCalendarItemMap(collectionInfo, syncEntries));
collectionsCalendar.push(collectionInfo); collectionsCalendar.push(collectionInfo);
@ -117,10 +141,44 @@ class Pim extends React.Component {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('pim.contacts._id')} path={routeResolver.getRoute('pim.contacts.new')}
exact={true} exact={true}
render={({match}) => ( render={({match}) => (
<Container style={{maxWidth: 400}}>
<ContactEdit collections={collectionsAddressBook} onSave={this.onContactSave} />
</Container>
)}
/>
<Route
path={routeResolver.getRoute('pim.contacts._id.edit')}
exact={true}
render={({match}) => (
<Container style={{maxWidth: 400}}>
<ContactEdit
contact={addressBookItems[match.params.contactUid]}
collections={collectionsAddressBook}
onSave={this.onContactSave}
/>
</Container>
)}
/>
<Route
path={routeResolver.getRoute('pim.contacts._id')}
exact={true}
render={({match, history}) => (
<Container> <Container>
<div style={{textAlign: 'right'}}>
<RaisedButton
label="Edit"
secondary={true}
icon={<IconEdit />}
onClick={() =>
history.push(routeResolver.getRoute(
'pim.contacts._id.edit',
{contactUid: match.params.contactUid}))
}
/>
</div>
<Contact contact={addressBookItems[match.params.contactUid]} /> <Contact contact={addressBookItems[match.params.contactUid]} />
</Container> </Container>
)} )}

@ -35,6 +35,10 @@ export class ContactType {
this.comp = comp; this.comp = comp;
} }
toIcal() {
return this.comp.toString();
}
get uid() { get uid() {
return this.comp.getFirstPropertyValue('uid'); return this.comp.getFirstPropertyValue('uid');
} }

Loading…
Cancel
Save