You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

522 lines
14 KiB
TypeScript

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 { red500, fullWhite } from 'material-ui/styles/colors';
import IconDelete from 'material-ui/svg-icons/action/delete';
import IconAdd from 'material-ui/svg-icons/content/add';
import IconClear from 'material-ui/svg-icons/content/clear';
import IconCancel from 'material-ui/svg-icons/content/clear';
import IconSave from 'material-ui/svg-icons/content/save';
import ConfirmationDialog from '../widgets/ConfirmationDialog';
import * as uuid from 'uuid';
import * as ICAL from 'ical.js';
import * as EteSync from '../api/EteSync';
import { ContactType } from '../pim-types';
const telTypes = [
{type: 'Home'},
{type: 'Work'},
{type: 'Cell'},
{type: 'Other'},
];
const emailTypes = telTypes;
const addressTypes = [
{type: 'Home'},
{type: 'Work'},
{type: 'Other'},
];
const imppTypes = [
{type: 'Jabber'},
{type: 'Hangouts'},
{type: 'Other'},
];
const TypeSelector = (props: any) => {
const types = props.types as {type: string}[];
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;
types: {type: string}[];
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: React.ChangeEvent<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}
types={props.types}
onChange={
(contact: object, key: number, payload: any) => props.onChange(props.name, payload, props.value.value)
}
/>
</React.Fragment>
);
};
class ContactEdit extends React.PureComponent {
state: {
uid: string,
fn: string;
phone: ValueType[];
email: ValueType[];
address: ValueType[];
impp: ValueType[];
org: string;
note: string;
title: string;
journalUid: string;
showDeleteDialog: boolean;
};
props: {
collections: Array<EteSync.CollectionInfo>,
initialCollection?: string,
item?: ContactType,
onSave: (contact: ContactType, journalUid: string, originalContact?: ContactType) => void;
onDelete: (contact: ContactType, journalUid: string) => void;
onCancel: () => void;
};
constructor(props: any) {
super(props);
this.state = {
uid: '',
fn: '',
phone: [new ValueType()],
email: [new ValueType()],
address: [new ValueType()],
impp: [new ValueType()],
org: '',
note: '',
title: '',
journalUid: '',
showDeleteDialog: false,
};
if (this.props.item !== undefined) {
const contact = this.props.item;
this.state.uid = contact.uid;
this.state.fn = contact.fn ? contact.fn : '';
// FIXME: Am I really getting all the values this way?
const propToValueType = (comp: ICAL.Component, propName: string) => (
comp.getAllProperties(propName).map((prop) => (
new ValueType(
prop.toJSON()[1].type,
prop.getFirstValue()
)
))
);
this.state.phone = propToValueType(contact.comp, 'tel');
this.state.email = propToValueType(contact.comp, 'email');
this.state.address = propToValueType(contact.comp, 'adr');
this.state.impp = propToValueType(contact.comp, 'impp');
const propToStringType = (comp: ICAL.Component, propName: string) => {
const val = comp.getFirstPropertyValue(propName);
return val ? val : '';
};
this.state.org = propToStringType(contact.comp, 'org');
this.state.title = propToStringType(contact.comp, 'title');
this.state.note = propToStringType(contact.comp, 'note');
} else {
this.state.uid = uuid.v4();
}
if (props.initialCollection) {
this.state.journalUid = props.initialCollection;
} else 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);
this.onDeleteRequest = this.onDeleteRequest.bind(this);
}
componentWillReceiveProps(nextProps: any) {
if ((this.props.collections !== nextProps.collections) ||
(this.props.initialCollection !== nextProps.initialCollection)) {
if (nextProps.initialCollection) {
this.state.journalUid = nextProps.initialCollection;
} else if (nextProps.collections[0]) {
this.state.journalUid = nextProps.collections[0].uid;
}
}
}
addValueType(name: string, _type?: string) {
const type = _type ? _type : 'home';
this.setState((prevState, props) => {
let newArray = prevState[name].slice(0);
newArray.push(new ValueType(type));
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: React.FormEvent<any>) {
e.preventDefault();
let contact = (this.props.item) ?
this.props.item.clone()
:
new ContactType(new ICAL.Component(['vcard', [], []]))
;
let comp = contact.comp;
comp.updatePropertyWithValue('prodid', '-//iCal.js EteSync Web');
comp.updatePropertyWithValue('version', '4.0');
comp.updatePropertyWithValue('uid', this.state.uid);
comp.updatePropertyWithValue('fn', this.state.fn);
comp.updatePropertyWithValue('rev', ICAL.Time.now());
function setProperties(name: string, source: ValueType[]) {
comp.removeAllProperties(name);
source.forEach((x) => {
if (x.value === '') {
return;
}
let prop = new ICAL.Property(name, comp);
prop.setParameter('type', x.type);
prop.setValue(x.value);
comp.addProperty(prop);
});
}
setProperties('tel', this.state.phone);
setProperties('email', this.state.email);
setProperties('adr', this.state.address);
setProperties('impp', this.state.impp.map((x) => (
{type: x.type, value: x.type + ':' + x.value}
)));
function setProperty(name: string, value: string) {
comp.removeAllProperties(name);
if (value !== '') {
comp.updatePropertyWithValue(name, value);
}
}
setProperty('org', this.state.org);
setProperty('title', this.state.title);
setProperty('note', this.state.note);
this.props.onSave(contact, this.state.journalUid, this.props.item);
}
onDeleteRequest() {
this.setState({
showDeleteDialog: true
});
}
render() {
const styles = {
form: {
},
fullWidth: {
width: '100%',
boxSizing: 'border-box',
},
submit: {
marginTop: 40,
marginBottom: 20,
textAlign: 'right',
},
};
return (
<React.Fragment>
<h2>
{this.props.item ? '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.item !== 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('phone')}
tooltip="Add phone number"
>
<IconAdd />
</IconButton>
</div>
{this.state.phone.map((x, idx) => (
<ValueTypeComponent
key={idx}
name="phone"
hintText="Phone"
types={telTypes}
value={this.state.phone[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('email')}
tooltip="Add email address"
>
<IconAdd />
</IconButton>
</div>
{this.state.email.map((x, idx) => (
<ValueTypeComponent
key={idx}
name="email"
hintText="Email"
types={emailTypes}
value={this.state.email[idx]}
onClearRequest={(name: string) => this.removeValueType(name, idx)}
onChange={(name: string, type: string, value: string) => (
this.handleValueTypeChange(name, idx, {type, value})
)}
/>
))}
<div>
IMPP
<IconButton
onClick={() => this.addValueType('impp', 'jabber')}
tooltip="Add impp address"
>
<IconAdd />
</IconButton>
</div>
{this.state.impp.map((x, idx) => (
<ValueTypeComponent
key={idx}
name="impp"
hintText="IMPP"
types={imppTypes}
value={this.state.impp[idx]}
onClearRequest={(name: string) => this.removeValueType(name, idx)}
onChange={(name: string, type: string, value: string) => (
this.handleValueTypeChange(name, idx, {type, value})
)}
/>
))}
<div>
Addresses
<IconButton
onClick={() => this.addValueType('address')}
tooltip="Add address"
>
<IconAdd />
</IconButton>
</div>
{this.state.address.map((x, idx) => (
<ValueTypeComponent
key={idx}
name="address"
hintText="Address"
types={addressTypes}
value={this.state.address[idx]}
onClearRequest={(name: string) => this.removeValueType(name, idx)}
onChange={(name: string, type: string, value: string) => (
this.handleValueTypeChange(name, idx, {type, value})
)}
/>
))}
<TextField
name="org"
hintText="Organization"
style={styles.fullWidth}
value={this.state.org}
onChange={this.handleInputChange}
/>
<TextField
name="title"
hintText="Title"
style={styles.fullWidth}
value={this.state.title}
onChange={this.handleInputChange}
/>
<TextField
name="note"
multiLine={true}
hintText="Note"
style={styles.fullWidth}
value={this.state.note}
onChange={this.handleInputChange}
/>
<div style={styles.submit}>
<RaisedButton
label="Cancel"
onClick={this.props.onCancel}
icon={<IconCancel />}
/>
{this.props.item &&
<RaisedButton
label="Delete"
labelColor={fullWhite}
backgroundColor={red500}
style={{marginLeft: 15}}
icon={<IconDelete color={fullWhite} />}
onClick={this.onDeleteRequest}
/>
}
<RaisedButton
type="submit"
label="Save"
secondary={true}
icon={<IconSave />}
style={{marginLeft: 15}}
/>
</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>
<ConfirmationDialog
title="Delete Confirmation"
labelOk="Delete"
open={this.state.showDeleteDialog}
onOk={() => this.props.onDelete(this.props.item!, this.props.initialCollection!)}
onCancel={() => this.setState({showDeleteDialog: false})}
>
Are you sure you would like to delete this contact?
</ConfirmationDialog>
</React.Fragment>
);
}
}
export default ContactEdit;