diff options
author | elijah <elijah@riseup.net> | 2017-03-15 11:45:08 +0100 |
---|---|---|
committer | Kali Kaneko (leap communications) <kali@leap.se> | 2017-03-15 19:09:47 +0100 |
commit | 5cbd979690212ac414bd7d399ec5183190fbf6f2 (patch) | |
tree | 2c3d8cff6451f5c4dce4198d25aaeaeee3f39e7f /ui | |
parent | 03b874769c14a6dd8675cace24b11517848cebce (diff) |
[feat] minimal addressbook ui
Diffstat (limited to 'ui')
-rw-r--r-- | ui/app/components/addressbook/addressbook.less | 43 | ||||
-rw-r--r-- | ui/app/components/addressbook/index.js | 37 | ||||
-rw-r--r-- | ui/app/components/addressbook/key_list_item.js | 119 | ||||
-rw-r--r-- | ui/app/components/main_panel/email_section.js | 131 | ||||
-rw-r--r-- | ui/app/components/main_panel/imap_button.js | 2 | ||||
-rw-r--r-- | ui/app/components/main_panel/main_panel.less | 3 | ||||
-rw-r--r-- | ui/app/components/main_panel/section_layout.js | 24 | ||||
-rw-r--r-- | ui/app/css/common.css | 4 |
8 files changed, 260 insertions, 103 deletions
diff --git a/ui/app/components/addressbook/addressbook.less b/ui/app/components/addressbook/addressbook.less index 53e505b..5ca4541 100644 --- a/ui/app/components/addressbook/addressbook.less +++ b/ui/app/components/addressbook/addressbook.less @@ -1,6 +1,12 @@ +@left-margin: 30px; + .body { - // background-color: #333; +} + +.header .title { + line-height: 32px; + padding-left: 15px; } .darkBg { @@ -30,5 +36,38 @@ .lightFg { border-radius: 4px; background-color: #fff; - padding: 15px; +} + +.key-list-item { + border-bottom: 1px solid #ccc; + .top-row { + &:hover { + background-color: #eee; + } + padding: 15px; + font-weight: bold; + font-size: 1.2em; + &:first-child { + border-radius: 4px 4px 0 0; + } + } + .expander { + width: @left-margin; + float: left; + .glyphicon {} + } + .address { + margin-left: @left-margin; + word-break: break-all; + } + .details { + margin-left: @left-margin; + padding: 15px; + } + .btn-toolbar { + margin-top: 15px; + } + .labels .label { + margin-left: 6px; + } }
\ No newline at end of file diff --git a/ui/app/components/addressbook/index.js b/ui/app/components/addressbook/index.js index 74c119d..eeaab91 100644 --- a/ui/app/components/addressbook/index.js +++ b/ui/app/components/addressbook/index.js @@ -2,14 +2,15 @@ // Interface to the key manager // - import React from 'react' import App from 'app' -import { ButtonToolbar, Button, Glyphicon, Alert } from 'react-bootstrap' +import { Button, Glyphicon, Alert } from 'react-bootstrap' import {VerticalLayout, Row} from 'components/layout' -import bitmask from 'lib/bitmask' +import Spinner from 'components/spinner' +import KeyListItem from './key_list_item' import './addressbook.less' +import bitmask from 'lib/bitmask' export default class Addressbook extends React.Component { @@ -21,16 +22,17 @@ export default class Addressbook extends React.Component { super(props) this.state = { keys: null, + loading: true, errorMsg: "" } this.close = this.close.bind(this) } componentWillMount() { - bitmask.keys.list(true).then(keys => { - this.setState({keys: keys}) + bitmask.keys.list(this.props.account.id, false).then(keys => { + this.setState({keys: keys, loading: false}) }, error => { - this.setState({errorMsg: error}) + this.setState({keys: null, loading: false, errorMsg: error}) }) } @@ -41,6 +43,11 @@ export default class Addressbook extends React.Component { render() { let alert = null let keyList = null + let spinner = null + + if (this.state.loading) { + spinner = <Spinner /> + } if (this.state.errorMsg) { alert = ( @@ -48,11 +55,15 @@ export default class Addressbook extends React.Component { ) } - keyList = <b>list of keys goes here</b> + if (this.state.keys) { + keyList = this.state.keys.map((theKey, i) => { + return <KeyListItem key={i} data={theKey} account={this.props.account} /> + }) + } let buttons = ( <Button onClick={this.close} className="btn-inverse"> - <Glyphicon glyph="remove" /> + <Glyphicon glyph="menu-left" /> Close </Button> ) @@ -60,17 +71,19 @@ export default class Addressbook extends React.Component { let page = ( <VerticalLayout className="darkBg"> <Row className="header" size="shrink" gutter="8px"> - <div className="pull-right"> + <div className="pull-left"> {buttons} </div> <div className="title"> - {this.props.account.address} - <h1>Addressbook</h1> + {this.props.account.address} / Addressbook </div> </Row> <Row className="lightFg" size="expand"> {alert} - {keyList} + {spinner} + <div className="key-list"> + {keyList} + </div> </Row> </VerticalLayout> ) diff --git a/ui/app/components/addressbook/key_list_item.js b/ui/app/components/addressbook/key_list_item.js new file mode 100644 index 0000000..b8bdad3 --- /dev/null +++ b/ui/app/components/addressbook/key_list_item.js @@ -0,0 +1,119 @@ +// +// A key in the list of keys +// + +import React from 'react' +import { Glyphicon, Button, ButtonToolbar, Label } from 'react-bootstrap' +import bitmask from 'lib/bitmask' + +export default class KeyListItem extends React.Component { + + static get defaultProps() {return{ + data: null, // the key as an Object + account: null // the account as an Account + }} + + constructor(props) { + super(props) + this.state = { + expanded: false, + hidden: false, + deleting: false, + error: null + } + this.toggle = this.toggle.bind(this) + this.delete = this.delete.bind(this) + } + + toggle() { + this.setState({expanded: !this.state.expanded}) + } + + delete() { + if (this.props.account.address == this.props.data.address) { + return // please do not delete your own key + } + + bitmask.keys.del(this.props.account.id, this.props.data.address, false).then( + key => { + this.setState({hidden: true}) + }, + error => { + this.setState({error: error}) + } + ) + } + + render() { + if (this.state.hidden) { + return <div></div> + } else if (!this.props.data) { + return ( + <div className="key-list-item"> + NO KEY + </div> + ) + } + + let details = null + let expander = null + let alert = null + let deleteButton = null + let labelArray = [] + let labels = null + let glyph = this.state.expanded ? 'triangle-bottom' : 'triangle-right' + + if (this.state.error) { + alert = ( + <Alert bsStyle="danger">{this.state.error}</Alert> + ) + } + + if (this.props.account.address != this.props.data.address) { + deleteButton = <Button onClick={this.delete}>Delete</Button> + } else { + labelArray.push(<Label key="owner" bsStyle="primary">Mine</Label>) + labelArray.push(<Label key="level" bsStyle="success">Verified</Label>) + } + + if (!this.props.data.encr_used) { + labelArray.push(<Label key="used" bsStyle="default">Never Used</Label>) + } + + if (this.state.expanded) { + details = ( + <div className="details"> + {alert} + Security Identifier: {this.props.data.fingerprint}<br /> + Last Updated: {this.props.data.refreshed_at}<br /> + Expires On: {this.props.data.expiry_date}<br /> + <ButtonToolbar> + <Button onClick={this.toggle}>Close</Button> + {deleteButton} + </ButtonToolbar> + </div> + ) + } + + expander = ( + <div className="expander"> + <Glyphicon glyph={glyph} /> + </div> + ) + + return ( + <div className="key-list-item"> + <div className="top-row clickable" onClick={this.toggle} > + <div className="labels pull-right"> + {labelArray} + </div> + {expander} + <div className="address"> + {this.props.data.address} + </div> + </div> + {details} + </div> + ) + } +} diff --git a/ui/app/components/main_panel/email_section.js b/ui/app/components/main_panel/email_section.js index ddb2a09..cab637b 100644 --- a/ui/app/components/main_panel/email_section.js +++ b/ui/app/components/main_panel/email_section.js @@ -9,50 +9,6 @@ import Spinner from 'components/spinner' import bitmask from 'lib/bitmask' import App from 'app' -const GENERAL_NOTICES = [ - "KEYMANAGER_KEY_FOUND", // (address) - "KEYMANAGER_KEY_NOT_FOUND", // (address) - "KEYMANAGER_LOOKING_FOR_KEY", // (address) - "KEYMANAGER_DONE_UPLOADING_KEYS", // (address) - - "SMTP_START_ENCRYPT_AND_SIGN", // (from_addr) - "SMTP_END_ENCRYPT_AND_SIGN", // (from_addr) - "SMTP_START_SIGN", // (from_addr) - "SMTP_END_SIGN", // (from_addr) - "SMTP_SEND_MESSAGE_START", // (from_addr) - "SMTP_SEND_MESSAGE_SUCCESS" // (from_addr) -] - -const ACCOUNT_NOTICES = [ - "IMAP_CLIENT_LOGIN", // (username) - - "MAIL_FETCHED_INCOMING", // (userid) - "MAIL_MSG_DECRYPTED", // (userid) - "MAIL_MSG_DELETED_INCOMING", // (userid) - "MAIL_MSG_PROCESSING", // (userid) - "MAIL_MSG_SAVED_LOCALLY", // (userid) - - "SMTP_RECIPIENT_ACCEPTED_ENCRYPTED", // (userid, dest) - "SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED", // (userid, dest) - "SMTP_RECIPIENT_REJECTED", // (userid, dest) - "SMTP_SEND_MESSAGE_ERROR" // (userid, dest) -] - -const STATUSES = [ - "KEYMANAGER_FINISHED_KEY_GENERATION", // (address) - "KEYMANAGER_STARTED_KEY_GENERATION", // (address) - "SMTP_SERVICE_STARTED", - "MAIL_UNREAD_MESSAGES", // (userid, number) - "IMAP_SERVICE_STARTED" -] - -const STATUS_ERRORS = [ - "IMAP_SERVICE_FAILED_TO_START", - "IMAP_UNHANDLED_ERROR", - "SMTP_SERVICE_FAILED_TO_START", - "SMTP_CONNECTION_LOST", // (userid, dest) -] - export default class EmailSection extends React.Component { static get defaultProps() {return{ @@ -62,32 +18,42 @@ export default class EmailSection extends React.Component { constructor(props) { super(props) this.state = { - status: 'unknown', // on, off, unknown, wait, disabled, error - messages: [], - expanded: true + status: 'unknown', // API produces: on, off, starting, stopping, failure + keys: null, // API produces: null, sync, generating, found + message: null, + expanded: false } - this.expand = this.expand.bind(this) - this.openKeys = this.openKeys.bind(this) - this.openApp = this.openApp.bind(this) - this.openPrefs = this.openPrefs.bind(this) - this.logEvent = this.logEvent.bind(this) + this.expand = this.expand.bind(this) + this.openKeys = this.openKeys.bind(this) + this.updateStatus = this.updateStatus.bind(this) } componentWillMount() { - let events = [].concat(GENERAL_NOTICES, ACCOUNT_NOTICES, STATUSES, STATUS_ERRORS) - for (let event of events) { - bitmask.events.register(event, this.logEvent) - } - bitmask.mail.status(this.props.account.id).then(status => { - this.setState({ - status: status.status, - error: status.error - }) - }) + this.updateStatus(this.props.account.address) + bitmask.events.register("MAIL_STATUS_CHANGED", this.updateStatus) } - logEvent(event, msg) { - console.log("EVENT: " + event, msg) + componentWillUnmount() { + bitmask.events.unregister("MAIL_STATUS_CHANGED") + } + + updateStatus(address) { + bitmask.mail.status(this.props.account.id).then( + status => { + console.log("STATUS CHANGED", status) + this.setState({ + keys: status.keys, + status: status.status, + error: status.error + }) + }, + error => { + this.setState({ + error: error, + status: "error" + }) + } + ) } openKeys() { @@ -95,7 +61,7 @@ export default class EmailSection extends React.Component { } openPixelated() { - if (bitmaskBrowser) { + if(typeof bitmaskBrowser !== 'undefined') { // we are inside a qtwebkit page that exports the object bitmaskBrowser.openPixelated(); } else { @@ -103,44 +69,53 @@ export default class EmailSection extends React.Component { } } - openApp() {} - - openPrefs() {} - expand() { this.setState({expanded: !this.state.expanded}) } render () { let message = null + let keyMessage = null + let expanded = this.state.expanded + if (this.state.error) { // style may be: success, warning, danger, info message = ( <Alert bsStyle="danger">{this.state.error}</Alert> ) + expanded = true + } + if (this.state.keys) { + if (this.state.keys == "sync") { + keyMessage = <Alert bsStyle="info">Downloading identity files</Alert> + expanded = true + } else if (this.state.keys == "generating") { + keyMessage = <Alert bsStyle="info">Preparing your identity (this may take a long time)</Alert> + expanded = true + } } - let button = null + let addyButton = <Button disabled={this.state.status != 'on'} onClick={this.openKeys}>Addressbook</Button> + let mailButton = <Button disabled={this.state.status != 'on'} onClick={this.openPixelated}>Open Mail</Button> + let imapButton = <IMAPButton account={this.props.account} /> let body = null let header = <h1>Mail</h1> - if (this.state.status == 'on') { - // FIXME disabling until #8792 is fixed - // button = <Button onClick={this.openKeys}>Addressbook</Button> - } + if (this.state.status == 'disabled') { header = <h1>Mail Disabled</h1> } - if (this.state.expanded) { + if (expanded || keyMessage || message) { body = (<div> {message} + {keyMessage} <ButtonToolbar> - <Button onClick={this.openPixelated}>Open Mail</Button> - <IMAPButton account={this.props.account} /> + {addyButton} + {imapButton} </ButtonToolbar> </div>) } return ( <SectionLayout icon="envelope" status={this.state.status} - onExpand={this.expand} buttons={button} header={header} body={body} /> + onExpand={this.expand} buttons={mailButton} header={header} body={body} /> ) } } diff --git a/ui/app/components/main_panel/imap_button.js b/ui/app/components/main_panel/imap_button.js index 98d8bad..3d02d3f 100644 --- a/ui/app/components/main_panel/imap_button.js +++ b/ui/app/components/main_panel/imap_button.js @@ -11,7 +11,7 @@ export default class IMAPButton extends React.Component { static get defaultProps() {return{ account: null, - title: "Connect Mail Client" + title: "Configure A Mail Client" }} constructor(props) { diff --git a/ui/app/components/main_panel/main_panel.less b/ui/app/components/main_panel/main_panel.less index 2f4156a..10399bd 100644 --- a/ui/app/components/main_panel/main_panel.less +++ b/ui/app/components/main_panel/main_panel.less @@ -222,9 +222,8 @@ background: #e3e3e3; border-top-left-radius: 6px; border-bottom-left-radius: 6px; - &:hover.clickable { + &:hover { background: #cfcfcf; - cursor: pointer; } } .icon { diff --git a/ui/app/components/main_panel/section_layout.js b/ui/app/components/main_panel/section_layout.js index 10c1bc1..3b06d68 100644 --- a/ui/app/components/main_panel/section_layout.js +++ b/ui/app/components/main_panel/section_layout.js @@ -11,7 +11,8 @@ export default class SectionLayout extends React.Component { static get defaultProps() {return{ icon: null, // icon name buttons: null, // button content - status: null, // must be one of: on, off, unknown, wait, disabled + status: null, // must be one of: on, off, unknown, wait, disabled, + // starting, stopping, failure header: null, // the first line content body: null, // expanded content message: null, // alert content @@ -26,14 +27,23 @@ export default class SectionLayout extends React.Component { render() { let className = ["service-section", this.props.className].join(' ') - let status = null + let statusIcon = null let icon = null let buttons = null let expander = null let body = null + let status = this.props.status + + if (status == "starting") { + status = "wait" + } else if (status == "stopping") { + status = "wait" + } else if (status == "failure") { + status = "error" + } if (this.props.onExpand) { - let glyph = this.props.body ? 'triangle-top' : 'triangle-bottom' + let glyph = this.props.body ? 'triangle-bottom' : 'triangle-right' expander = ( <div className="expander clickable" onClick={this.props.onExpand}> <Glyphicon glyph={glyph} /> @@ -44,10 +54,10 @@ export default class SectionLayout extends React.Component { <div className="expander" /> ) } - if (this.props.status) { - status = ( + if (status) { + statusIcon = ( <div className="status"> - <img src={'img/' + this.props.status + '.svg' } /> + <img src={'img/' + status + '.svg' } /> </div> ) } @@ -88,7 +98,7 @@ export default class SectionLayout extends React.Component { </div> {body} </div> - {status} + {statusIcon} </div> </div> ) diff --git a/ui/app/css/common.css b/ui/app/css/common.css index a0d2c35..7d8680f 100644 --- a/ui/app/css/common.css +++ b/ui/app/css/common.css @@ -119,4 +119,6 @@ body { h1.first { margin-top: 0px } -.vspacer {margin-top: 20px }
\ No newline at end of file +.vspacer {margin-top: 20px } + +.clickable {cursor: pointer;}
\ No newline at end of file |