summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2017-03-15 11:45:08 +0100
committerKali Kaneko (leap communications) <kali@leap.se>2017-03-15 19:09:47 +0100
commit5cbd979690212ac414bd7d399ec5183190fbf6f2 (patch)
tree2c3d8cff6451f5c4dce4198d25aaeaeee3f39e7f
parent03b874769c14a6dd8675cace24b11517848cebce (diff)
[feat] minimal addressbook ui
-rw-r--r--ui/app/components/addressbook/addressbook.less43
-rw-r--r--ui/app/components/addressbook/index.js37
-rw-r--r--ui/app/components/addressbook/key_list_item.js119
-rw-r--r--ui/app/components/main_panel/email_section.js131
-rw-r--r--ui/app/components/main_panel/imap_button.js2
-rw-r--r--ui/app/components/main_panel/main_panel.less3
-rw-r--r--ui/app/components/main_panel/section_layout.js24
-rw-r--r--ui/app/css/common.css4
8 files changed, 260 insertions, 103 deletions
diff --git a/ui/app/components/addressbook/addressbook.less b/ui/app/components/addressbook/addressbook.less
index 53e505b6..5ca45411 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 74c119d3..eeaab912 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" />&nbsp;
+ <Glyphicon glyph="menu-left" />&nbsp;
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 00000000..b8bdad36
--- /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 ddb2a09b..cab637be 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 98d8bad0..3d02d3f9 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 2f4156a0..10399bdc 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 10c1bc1d..3b06d681 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 a0d2c35f..7d8680f4 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