summaryrefslogtreecommitdiff
path: root/www/app/components/main_panel
diff options
context:
space:
mode:
Diffstat (limited to 'www/app/components/main_panel')
-rw-r--r--www/app/components/main_panel/account_list.js113
-rw-r--r--www/app/components/main_panel/email_section.js0
-rw-r--r--www/app/components/main_panel/index.js57
-rw-r--r--www/app/components/main_panel/main_panel.less208
-rw-r--r--www/app/components/main_panel/section_layout.js59
-rw-r--r--www/app/components/main_panel/user_section.js71
-rw-r--r--www/app/components/main_panel/vpn_section.js0
7 files changed, 508 insertions, 0 deletions
diff --git a/www/app/components/main_panel/account_list.js b/www/app/components/main_panel/account_list.js
new file mode 100644
index 00000000..d0ef092f
--- /dev/null
+++ b/www/app/components/main_panel/account_list.js
@@ -0,0 +1,113 @@
+import React from 'react'
+import {Button, ButtonGroup, ButtonToolbar, Glyphicon} from 'react-bootstrap'
+
+import App from 'app'
+import Account from 'models/account'
+
+export default class AccountList extends React.Component {
+
+ static get defaultProps() {return{
+ account: null,
+ accounts: [],
+ onAdd: null,
+ onRemove: null,
+ onSelect: null
+ }}
+
+ constructor(props) {
+ super(props)
+
+ this.state = {
+ mode: 'expanded'
+ }
+
+ // prebind:
+ this.select = this.select.bind(this)
+ this.add = this.add.bind(this)
+ this.remove = this.remove.bind(this)
+ this.expand = this.expand.bind(this)
+ this.collapse = this.collapse.bind(this)
+ }
+
+ select(e) {
+ let account = this.props.accounts.find(
+ account => account.id == e.currentTarget.dataset.id
+ )
+ if (this.props.onSelect) {
+ this.props.onSelect(account)
+ }
+ }
+
+ add() {
+ App.show('wizard')
+ }
+
+ remove() {
+ }
+
+ expand() {
+ this.setState({mode: 'expanded'})
+ }
+
+ collapse() {
+ this.setState({mode: 'collapsed'})
+ }
+
+ render() {
+ let style = {}
+ let expandButton = null
+ let plusminusButtons = null
+
+ if (this.state.mode == 'expanded') {
+ expandButton = (
+ <Button onClick={this.collapse} className="expander btn-inverse btn-flat pull-right">
+ <Glyphicon glyph="triangle-left" />
+ </Button>
+ )
+ plusminusButtons = (
+ <ButtonGroup style={style}>
+ <Button onClick={this.add} className="btn-inverse">
+ <Glyphicon glyph="plus" />
+ </Button>
+ <Button disabled={this.props.account == null} onClick={this.remove} className="btn-inverse">
+ <Glyphicon glyph="minus" />
+ </Button>
+ </ButtonGroup>
+ )
+ } else {
+ style.width = '60px'
+ expandButton = (
+ <Button onClick={this.expand} className="expander btn-inverse btn-flat pull-right">
+ <Glyphicon glyph="triangle-right" />
+ </Button>
+ )
+ }
+
+ let items = this.props.accounts.map((account, i) => {
+ let className = account == this.props.account ? 'active' : 'inactive'
+ return (
+ <li key={i} className={className} onClick={this.select} data-id={account.id}>
+ <span className="username">{account.userpart}</span>
+ <span className="domain">{account.domain}</span>
+ <span className="arc top"></span>
+ <span className="arc bottom"></span>
+ </li>
+ )
+ })
+
+
+ return (
+ <div className="accounts" style={style}>
+ <ul>
+ {items}
+ </ul>
+ <ButtonToolbar>
+ {plusminusButtons}
+ {expandButton}
+ </ButtonToolbar>
+ </div>
+ )
+ }
+
+
+}
diff --git a/www/app/components/main_panel/email_section.js b/www/app/components/main_panel/email_section.js
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/www/app/components/main_panel/email_section.js
diff --git a/www/app/components/main_panel/index.js b/www/app/components/main_panel/index.js
new file mode 100644
index 00000000..910d58b6
--- /dev/null
+++ b/www/app/components/main_panel/index.js
@@ -0,0 +1,57 @@
+//
+// The main panel manages the current account and the list of available accounts
+//
+// It displays multiple sections, one for each service.
+//
+
+import React from 'react'
+import App from 'app'
+import Login from 'components/login'
+
+import './main_panel.less'
+import AccountList from './account_list'
+import UserSection from './user_section'
+
+export default class MainPanel extends React.Component {
+
+ static get defaultProps() {return{
+ initialAccount: null
+ }}
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ account: null,
+ accounts: []
+ }
+ this.activateAccount = this.activateAccount.bind(this)
+ }
+
+ componentWillMount() {
+ if (this.props.initialAccount) {
+ this.setState({
+ account: this.props.initialAccount,
+ accounts: [this.props.initialAccount]
+ })
+ }
+ }
+
+ activateAccount(account) {
+ this.setState({
+ account: account,
+ accounts: [account]
+ })
+ }
+
+ render() {
+ return (
+ <div className="main-panel">
+ <AccountList account={this.state.account} accounts={this.state.accounts} onSelect={this.activateAccount} />
+ <div className="body">
+ <UserSection account={this.state.account} onLogin={this.activateAccount} onLogout={this.activateAccount}/>
+ </div>
+ </div>
+ )
+ }
+
+}
diff --git a/www/app/components/main_panel/main_panel.less b/www/app/components/main_panel/main_panel.less
new file mode 100644
index 00000000..3172bba5
--- /dev/null
+++ b/www/app/components/main_panel/main_panel.less
@@ -0,0 +1,208 @@
+// The space around account entries:
+@accounts-padding: 8px;
+@accounts-width: 200px;
+
+//
+// LAYOUT
+//
+
+.main-panel {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+
+ > .body {
+ flex: 1 1 auto;
+ overflow: auto;
+ }
+
+ .accounts {
+ flex: 0 0 auto;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ display: flex;
+ flex-direction: column;
+ ul {
+ flex: 1 1 1000px;
+ }
+ .btn-toolbar {
+ flex: 0 0 auto;
+ }
+
+ }
+
+}
+
+//
+// Style
+//
+
+.main-panel > .body {
+ padding: 20px;
+}
+
+.main-panel .accounts {
+ background-color: #333;
+ width: @accounts-width;
+ padding: @accounts-padding;
+ padding-right: 0px;
+}
+
+.main-panel .accounts ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.main-panel .accounts li {
+ position: relative;
+ cursor: pointer;
+ color: white;
+ padding: 15px;
+ background-color: #444;
+ margin-bottom: @accounts-padding;
+ border-top-left-radius: @accounts-padding;
+ border-bottom-left-radius: @accounts-padding;
+ z-index: 100;
+}
+
+.main-panel .accounts li span.domain {
+ display: block;
+ font-weight: bold;
+ //margin-left: 40px;
+}
+
+.main-panel .accounts li span.username {
+ display: block;
+ //margin-left: 40px;
+ //line-height: 7px;
+ //margin-bottom: 4px;
+}
+
+/*.main-panel .accounts li span.icon {
+ display: block;
+ height: 32px;
+ width: 32px;
+ background-color: #999;
+ float: left;
+}
+*/
+
+.main-panel .accounts li.active {
+ background-color: white;
+ color: #333;
+}
+
+.main-panel .accounts li.active span.arc {
+ display: block;
+ height: @accounts-padding;
+ width: @accounts-padding;
+ background-color: white;
+ position: absolute;
+ right: 0;
+}
+
+.main-panel .accounts li.active span.arc.top {
+ top: 0;
+ margin-top: -@accounts-padding;
+}
+.main-panel .accounts li.active span.arc.bottom {
+ bottom: 0;
+ margin-bottom: -@accounts-padding;
+}
+.main-panel .accounts li.active span.arc:after {
+ display: block;
+ content: "";
+ border-radius: 100%;
+ height: 0px;
+ width: 0px;
+ margin-left: -@accounts-padding;
+}
+.main-panel .accounts li.active span.arc.top:after {
+ border: @accounts-padding solid transparent;
+ border-right: @accounts-padding solid #333;
+ margin-top: -@accounts-padding;
+ transform: rotate(45deg);
+}
+.main-panel .accounts li.active span.arc.bottom:after {
+ border: @accounts-padding solid #333;
+}
+
+.main-panel .accounts .btn.expander {
+ margin-right: @accounts-padding;
+}
+
+
+//
+// SECTIONS
+//
+
+@icon-size: 32px;
+@status-size: 24px;
+@section-padding: 10px;
+
+// service sections layout
+
+.main-panel .service-section {
+ display: flex;
+ flex-direction: row;
+ > .icon {
+ flex: 0 0 auto;
+ }
+ > .body {
+ flex: 1 1 auto;
+ }
+ > .buttons {
+ flex: 0 0 auto;
+ }
+ > .status {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ }
+}
+
+.main-panel .service-section div {
+ //outline: 1px solid rgba(0,0,0,0.1);
+}
+
+// service sections style
+
+.main-panel .service-section {
+ background: #f6f6f6;
+ border-radius: 4px;
+ padding: 10px;
+ &.wide-margin {
+ padding: 20px 20px 20px 10px; // arbitrary, looks nice
+ }
+ > .icon {
+ padding-right: @section-padding;
+ img {
+ width: @icon-size;
+ height: @icon-size;
+ }
+ }
+ > .body {
+ h1 {
+ margin: 0;
+ padding: 0;
+ font-size: @icon-size - 10;
+ line-height: @icon-size;
+ }
+ }
+ > .buttons {
+ padding-left: 10px;
+ }
+ > .status {
+ padding-left: @section-padding;
+ width: @section-padding + @status-size;
+ img {
+ width: @status-size;
+ height: @status-size;
+ }
+ }
+}
+
diff --git a/www/app/components/main_panel/section_layout.js b/www/app/components/main_panel/section_layout.js
new file mode 100644
index 00000000..e7c6f2ab
--- /dev/null
+++ b/www/app/components/main_panel/section_layout.js
@@ -0,0 +1,59 @@
+//
+// This is the layout for a service section in the main window.
+// It does not do anything except for arrange items using css and html.
+//
+
+import React from 'react'
+
+export default class SectionLayout extends React.Component {
+
+ static get defaultProps() {return{
+ icon: null,
+ buttons: null,
+ status: null,
+ className: "",
+ style: {}
+ }}
+
+ constructor(props) {
+ super(props)
+ }
+
+ render() {
+ let className = ["service-section", this.props.className].join(' ')
+ let status = null
+ let icon = null
+ let buttons = null
+
+ if (this.props.status) {
+ status = (
+ <div className="status">
+ <img src={'img/' + this.props.status + '.svg' } />
+ </div>
+ )
+ }
+ if (this.props.icon) {
+ icon = (
+ <div className="icon">
+ <img src={'img/' + this.props.icon + '.svg'} />
+ </div>
+ )
+ }
+ if (this.props.buttons)
+ buttons = (
+ <div className="buttons">
+ {this.props.buttons}
+ </div>
+ )
+ return(
+ <div className={className} style={this.props.style}>
+ {icon}
+ <div className="body">
+ {this.props.children}
+ </div>
+ {buttons}
+ {status}
+ </div>
+ )
+ }
+}
diff --git a/www/app/components/main_panel/user_section.js b/www/app/components/main_panel/user_section.js
new file mode 100644
index 00000000..0b4ba136
--- /dev/null
+++ b/www/app/components/main_panel/user_section.js
@@ -0,0 +1,71 @@
+import React from 'react'
+import { Button, Glyphicon, Alert } from 'react-bootstrap'
+import SectionLayout from './section_layout'
+import Login from 'components/login'
+import Spinner from 'components/spinner'
+import Account from 'models/account'
+
+import bitmask from 'lib/bitmask'
+
+export default class UserSection extends React.Component {
+
+ static get defaultProps() {return{
+ account: null,
+ onLogout: null,
+ onLogin: null
+ }}
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ error: null,
+ loading: false
+ }
+ this.logout = this.logout.bind(this)
+ }
+
+ logout() {
+ this.setState({loading: true})
+ this.props.account.logout().then(
+ account => {
+ this.setState({error: null, loading: false})
+ if (this.props.onLogout) {
+ this.props.onLogout(account)
+ }
+ }, error => {
+ this.setState({error: error, loading: false})
+ }
+ )
+ }
+
+ render () {
+ let message = null
+ if (this.state.error) {
+ // style may be: success, warning, danger, info
+ message = (
+ <Alert bsStyle="danger">{this.state.error}</Alert>
+ )
+ }
+
+ if (this.props.account.authenticated) {
+ let button = null
+ if (this.state.loading) {
+ button = <Button disabled={true}><Spinner /></Button>
+ } else {
+ button = <Button onClick={this.logout}>Log Out</Button>
+ }
+ return (
+ <SectionLayout icon="user" buttons={button} status="on">
+ <h1>{this.props.account.address}</h1>
+ {message}
+ </SectionLayout>
+ )
+ } else {
+ return (
+ <SectionLayout icon="user" className="wide-margin">
+ <Login onLogin={this.props.onLogin} domain={this.props.account.domain} />
+ </SectionLayout>
+ )
+ }
+ }
+}
diff --git a/www/app/components/main_panel/vpn_section.js b/www/app/components/main_panel/vpn_section.js
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/www/app/components/main_panel/vpn_section.js