1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-15 02:36:36 +00:00

refactor file structure

This commit is contained in:
Rokt33r
2015-12-25 05:41:10 +09:00
parent 152e4129b2
commit 325ae00eeb
53 changed files with 10 additions and 386 deletions

View File

@@ -0,0 +1,108 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import linkState from 'boost/linkState'
import { createFolder } from 'boost/actions'
import store from 'boost/store'
import FolderMark from 'boost/components/FolderMark'
export default class CreateNewFolder extends React.Component {
constructor (props) {
super(props)
this.state = {
name: '',
color: Math.round(Math.random() * 7),
alert: null
}
}
componentDidMount () {
ReactDOM.findDOMNode(this.refs.folderName).focus()
}
handleCloseButton (e) {
this.props.close()
}
handleConfirmButton (e) {
this.setState({alert: null}, () => {
let { close } = this.props
let { name, color } = this.state
let input = {
name,
color
}
try {
store.dispatch(createFolder(input))
} catch (e) {
this.setState({alert: {
type: 'error',
message: e.message
}})
return
}
close()
})
}
handleColorClick (colorIndex) {
return e => {
this.setState({
color: colorIndex
})
}
}
handleKeyDown (e) {
if (e.keyCode === 13) {
this.handleConfirmButton()
}
}
render () {
let alert = this.state.alert
let alertElement = alert != null ? (
<p className={`alert ${alert.type}`}>
{alert.message}
</p>
) : null
let colorIndexes = []
for (let i = 0; i < 8; i++) {
colorIndexes.push(i)
}
let colorElements = colorIndexes.map(index => {
let className = 'option'
if (index === this.state.color) className += ' active'
return (
<span className={className} key={index} onClick={e => this.handleColorClick(index)(e)}>
<FolderMark color={index}/>
</span>
)
})
return (
<div className='CreateNewFolder modal'>
<button onClick={e => this.handleCloseButton(e)} className='closeBtn'><i className='fa fa-fw fa-times'/></button>
<div className='title'>Create new folder</div>
<input ref='folderName' onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
<div className='colorSelect'>
{colorElements}
</div>
{alertElement}
<button onClick={e => this.handleConfirmButton(e)} className='confirmBtn'>Create</button>
</div>
)
}
}
CreateNewFolder.propTypes = {
close: PropTypes.func
}
CreateNewFolder.prototype.linkState = linkState

View File

@@ -0,0 +1,255 @@
import React, { PropTypes } from 'react'
import ProfileImage from 'boost/components/ProfileImage'
import { searchUser, createTeam, setMember, deleteMember } from 'boost/api'
import linkState from 'boost/linkState'
import Select from 'react-select'
function getUsers (input, cb) {
searchUser(input)
.then(function (res) {
let users = res.body
cb(null, {
options: users.map(user => {
return { value: user.name, label: user.name }
}),
complete: false
})
})
.catch(function (err) {
console.error(err)
})
}
export default class CreateNewTeam extends React.Component {
constructor (props) {
super(props)
this.state = {
create: {
name: '',
alert: null
},
select: {
team: null,
newMember: null,
alert: null
},
currentTab: 'create',
currentUser: JSON.parse(localStorage.getItem('currentUser'))
}
}
handleCloseClick (e) {
this.props.close()
}
handleContinueClick (e) {
let createState = this.state.create
createState.isSending = true
createState.alert = {
type: 'info',
message: 'sending...'
}
this.setState({create: createState})
function onTeamCreate (res) {
let createState = this.state.create
createState.isSending = false
createState.alert = null
let selectState = this.state.select
selectState.team = res.body
this.setState({
currentTab: 'select',
create: createState,
select: {
team: res.body
}
})
}
function onError (err) {
let errorMessage = err.response != null ? err.response.body.message : 'Can\'t connect to API server.'
let createState = this.state.create
createState.isSending = false
createState.alert = {
type: 'error',
message: errorMessage
}
this.setState({
create: createState
})
}
createTeam({name: this.state.create.name})
.then(onTeamCreate.bind(this))
.catch(onError.bind(this))
}
renderCreateTab () {
let createState = this.state.create
let alertEl = createState.alert != null ? (
<p className={['alert'].concat([createState.alert.type]).join(' ')}>{createState.alert.message}</p>
) : null
return (
<div className='createTab'>
<div className='title'>Create new team</div>
<input valueLink={this.linkState('create.name')} className='ipt' type='text' placeholder='Enter your team name'/>
{alertEl}
<button onClick={e => this.handleContinueClick(e)} disabled={createState.isSending} className='confirmBtn'>Continue <i className='fa fa-arrow-right fa-fw'/></button>
</div>
)
}
handleNewMemberChange (value) {
let selectState = this.state.select
selectState.newMember = value
this.setState({select: selectState})
}
handleClickAddMemberButton (e) {
let selectState = this.state.select
let input = {
name: selectState.newMember,
role: 'member'
}
setMember(selectState.team.id, input)
.then(res => {
let selectState = this.state.select
let team = res.body
team.Members = team.Members.sort((a, b) => {
return new Date(a._pivot_createdAt) - new Date(b._pivot_createdAt)
})
selectState.team = team
selectState.newMember = ''
this.setState({select: selectState})
})
.catch(err => {
if (err.status != null) throw err
else console.error(err)
})
}
handleMemberDeleteButtonClick (name) {
let selectState = this.state.select
let input = {
name: name
}
return e => {
deleteMember(selectState.team.id, input)
.then(res => {
let selectState = this.state.select
let team = res.body
team.Members = team.Members.sort((a, b) => {
return new Date(a._pivot_createdAt) - new Date(b._pivot_createdAt)
})
selectState.team = team
selectState.newMember = ''
this.setState({select: selectState})
})
.catch(err => {
console.log(err, err.response)
if (err.status != null) throw err
else console.error(err)
})
}
}
handleMemberRoleChange (name) {
return function (e) {
let selectState = this.state.select
let input = {
name: name,
role: e.target.value
}
setMember(selectState.team.id, input)
.then(res => {
console.log(res.body)
})
.catch(err => {
if (err.status != null) throw err
else console.error(err)
})
}.bind(this)
}
renderSelectTab () {
let selectState = this.state.select
let membersEl = selectState.team.Members.map(member => {
let isCurrentUser = this.state.currentUser.id === member.id
return (
<li key={'user-' + member.id}>
<ProfileImage className='userPhoto' email={member.email} size='30'/>
<div className='userInfo'>
<div className='userName'>{`${member.profileName} (${member.name})`}</div>
<div className='userEmail'>{member.email}</div>
</div>
<div className='userControl'>
<select onChange={e => this.handleMemberRoleChange(member.name)(e)} disabled={isCurrentUser} value={member._pivot_role} className='userRole'>
<option value='owner'>Owner</option>
<option value='member'>Member</option>
</select>
<button onClick={e => this.handleMemberDeleteButtonClick(member.name)(e)} disabled={isCurrentUser}><i className='fa fa-times fa-fw'/></button>
</div>
</li>
)
})
return (
<div className='selectTab'>
<div className='title'>Select member</div>
<div className='memberForm'>
<Select
className='memberName'
autoload={false}
asyncOptions={getUsers}
onChange={val => this.handleNewMemberChange(val)}
value={selectState.newMember}
/>
<button onClick={e => this.handleClickAddMemberButton(e)} className='addMemberBtn'>add</button>
</div>
<ul className='memberList'>
{membersEl}
</ul>
<button onClick={e => this.props.close(e)}className='confirmBtn'>Done</button>
</div>
)
}
render () {
let currentTab = this.state.currentTab === 'create'
? this.renderCreateTab()
: this.renderSelectTab()
return (
<div className='CreateNewTeam modal'>
<button onClick={e => this.handleCloseClick(e)} className='closeBtn'><i className='fa fa-fw fa-times'/></button>
{currentTab}
<div className='tabNav'>
<i className={'fa fa-circle fa-fw' + (this.state.currentTab === 'create' ? ' active' : '')}/>
<i className={'fa fa-circle fa-fw' + (this.state.currentTab === 'select' ? ' active' : '')}/>
</div>
</div>
)
}
}
CreateNewTeam.propTypes = {
close: PropTypes.func
}
CreateNewTeam.prototype.linkState = linkState

View File

@@ -0,0 +1,41 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import store from 'boost/store'
import { unlockStatus, clearNewArticle } from 'boost/actions'
export default class EditedAlert extends React.Component {
componentDidMount () {
ReactDOM.findDOMNode(this.refs.no).focus()
}
handleNoButtonClick (e) {
this.props.close()
}
handleYesButtonClick (e) {
store.dispatch(unlockStatus())
store.dispatch(this.props.action)
store.dispatch(clearNewArticle())
this.props.close()
}
render () {
return (
<div className='EditedAlert modal'>
<div className='title'>Your article is still editing!</div>
<div className='message'>Do you really want to leave without finishing?</div>
<div className='control'>
<button ref='no' onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
<button ref='yes' onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
</div>
</div>
)
}
}
EditedAlert.propTypes = {
action: PropTypes.object,
close: PropTypes.func
}

View File

@@ -0,0 +1,146 @@
import React, { PropTypes } from 'react'
import linkState from 'boost/linkState'
import { updateUser } from 'boost/actions'
const electron = require('electron')
const ipc = electron.ipcRenderer
const remote = electron.remote
export default class AppSettingTab extends React.Component {
constructor (props) {
super(props)
let keymap = remote.getGlobal('keymap')
let userName = props.user != null ? props.user.name : null
this.state = {
user: {
name: userName,
alert: null
},
userAlert: null,
keymap: {
toggleFinder: keymap.toggleFinder
},
keymapAlert: null
}
}
componentDidMount () {
this.handleSettingDone = () => {
this.setState({keymapAlert: {
type: 'success',
message: 'Successfully done!'
}})
}
this.handleSettingError = err => {
this.setState({keymapAlert: {
type: 'error',
message: err.message != null ? err.message : 'Error occurs!'
}})
}
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
}
componentWillUnmount () {
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
}
submitHotKey () {
ipc.send('hotkeyUpdated', {
toggleFinder: this.state.keymap.toggleFinder
})
}
handleSaveButtonClick (e) {
this.submitHotKey()
}
handleKeyDown (e) {
if (e.keyCode === 13) {
this.submitHotKey()
}
}
handleNameSaveButtonClick (e) {
let { dispatch } = this.props
dispatch(updateUser({name: this.state.user.name}))
this.setState({
userAlert: {
type: 'success',
message: 'Successfully done!'
}
})
}
render () {
let keymapAlert = this.state.keymapAlert
let keymapAlertElement = keymapAlert != null
? (
<p className={`alert ${keymapAlert.type}`}>
{keymapAlert.message}
</p>
) : null
let userAlert = this.state.userAlert
let userAlertElement = userAlert != null
? (
<p className={`alert ${userAlert.type}`}>
{userAlert.message}
</p>
) : null
return (
<div className='AppSettingTab content'>
<div className='section'>
<div className='sectionTitle'>User's info</div>
<div className='sectionInput'>
<label>User name</label>
<input valueLink={this.linkState('user.name')} type='text'/>
</div>
<div className='sectionConfirm'>
<button onClick={e => this.handleNameSaveButtonClick(e)}>Save</button>
{userAlertElement}
</div>
</div>
<div className='section'>
<div className='sectionTitle'>Hotkey</div>
<div className='sectionInput'>
<label>Toggle Finder(popup)</label>
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/>
</div>
<div className='sectionConfirm'>
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
{keymapAlertElement}
</div>
<div className='description'>
<ul>
<li><code>0</code> to <code>9</code></li>
<li><code>A</code> to <code>Z</code></li>
<li><code>F1</code> to <code>F24</code></li>
<li>Punctuations like <code>~</code>, <code>!</code>, <code>@</code>, <code>#</code>, <code>$</code>, etc.</li>
<li><code>Plus</code></li>
<li><code>Space</code></li>
<li><code>Backspace</code></li>
<li><code>Delete</code></li>
<li><code>Insert</code></li>
<li><code>Return</code> (or <code>Enter</code> as alias)</li>
<li><code>Up</code>, <code>Down</code>, <code>Left</code> and <code>Right</code></li>
<li><code>Home</code> and <code>End</code></li>
<li><code>PageUp</code> and <code>PageDown</code></li>
<li><code>Escape</code> (or <code>Esc</code> for short)</li>
<li><code>VolumeUp</code>, <code>VolumeDown</code> and <code>VolumeMute</code></li>
<li><code>MediaNextTrack</code>, <code>MediaPreviousTrack</code>, <code>MediaStop</code> and <code>MediaPlayPause</code></li>
</ul>
</div>
</div>
</div>
)
}
}
AppSettingTab.prototype.linkState = linkState
AppSettingTab.propTypes = {
dispatch: PropTypes.func
}

View File

@@ -0,0 +1,123 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { getClientKey } from 'boost/activityRecord'
import linkState from 'boost/linkState'
import _ from 'lodash'
import { request, WEB_URL } from 'boost/api'
const FORM_MODE = 'FORM_MODE'
const DONE_MODE = 'DONE_MODE'
export default class ContactTab extends React.Component {
constructor (props) {
super(props)
this.state = {
title: '',
content: '',
email: '',
mode: FORM_MODE,
alert: null
}
}
componentDidMount () {
let titleInput = ReactDOM.findDOMNode(this.refs.title)
if (titleInput != null) titleInput.focus()
}
handleBackButtonClick (e) {
this.setState({
mode: FORM_MODE
})
}
handleSendButtonClick (e) {
let input = _.pick(this.state, ['title', 'content', 'email'])
input.clientKey = getClientKey()
this.setState({
alert: {
type: 'info',
message: 'Sending...'
}
}, () => {
request.post(WEB_URL + 'apis/inquiry')
.send(input)
.then(res => {
console.log('sent')
this.setState({
title: '',
content: '',
mode: DONE_MODE,
alert: null
})
})
.catch(err => {
if (err.code === 'ECONNREFUSED') {
this.setState({
alert: {
type: 'error',
message: 'Can\'t connect to API server.'
}
})
} else {
console.error(err)
this.setState({
alert: {
type: 'error',
message: err.message
}
})
}
})
})
}
render () {
switch (this.state.mode) {
case DONE_MODE:
return (
<div className='ContactTab content done'>
<div className='message'>
<i className='checkIcon fa fa-check-circle'/><br/>
Your message has been sent successfully!!
</div>
<div className='control'>
<button onClick={e => this.handleBackButtonClick(e)}>Back to Contact form</button>
</div>
</div>
)
case FORM_MODE:
default:
let alertElement = this.state.alert != null
? (
<div className={'alert ' + this.state.alert.type}>{this.state.alert.message}</div>
)
: null
return (
<div className='ContactTab content form'>
<div className='title'>Contact form</div>
<div className='description'>
Your feedback is highly appreciated and will help us to improve our app. :D
</div>
<div className='iptGroup'>
<input ref='title' valueLink={this.linkState('title')} placeholder='Title' type='text'/>
</div>
<div className='iptGroup'>
<textarea valueLink={this.linkState('content')} placeholder='Content'/>
</div>
<div className='iptGroup'>
<input valueLink={this.linkState('email')} placeholder='E-mail (Optional)' type='email'/>
</div>
<div className='formControl'>
<button onClick={e => this.handleSendButtonClick(e)} className='primary'>Send</button>
{alertElement}
</div>
</div>
)
}
}
}
ContactTab.prototype.linkState = linkState

View File

@@ -0,0 +1,187 @@
import React, { PropTypes } from 'react'
import linkState from 'boost/linkState'
import FolderMark from 'boost/components/FolderMark'
import store from 'boost/store'
import { updateFolder, destroyFolder, replaceFolder } from 'boost/actions'
const IDLE = 'IDLE'
const EDIT = 'EDIT'
const DELETE = 'DELETE'
export default class FolderRow extends React.Component {
constructor (props) {
super(props)
this.state = {
mode: IDLE
}
}
handleUpClick (e) {
let { index } = this.props
if (index > 0) {
store.dispatch(replaceFolder(index, index - 1))
}
}
handleDownClick (e) {
let { index, count } = this.props
if (index < count - 1) {
store.dispatch(replaceFolder(index, index + 1))
}
}
handleCancelButtonClick (e) {
this.setState({
mode: IDLE
})
}
handleEditButtonClick (e) {
this.setState({
mode: EDIT,
name: this.props.folder.name,
color: this.props.folder.color,
isColorEditing: false
})
}
handleDeleteButtonClick (e) {
this.setState({mode: DELETE})
}
handleNameInputKeyDown (e) {
if (e.keyCode === 13) {
this.handleSaveButtonClick()
}
}
handleColorSelectClick (e) {
this.setState({
isColorEditing: true
})
}
handleColorButtonClick (index) {
return e => {
this.setState({
color: index,
isColorEditing: false
})
}
}
handleSaveButtonClick (e) {
let { folder, setAlert } = this.props
setAlert(null, () => {
let input = {
name: this.state.name,
color: this.state.color
}
folder = Object.assign({}, folder, input)
try {
store.dispatch(updateFolder(folder))
this.setState({
mode: IDLE
})
} catch (e) {
console.error(e)
setAlert({
type: 'error',
message: e.message
})
}
})
}
handleDeleteConfirmButtonClick (e) {
let { folder } = this.props
store.dispatch(destroyFolder(folder.key))
}
render () {
let folder = this.props.folder
switch (this.state.mode) {
case EDIT:
let colorIndexes = []
for (let i = 0; i < 8; i++) {
colorIndexes.push(i)
}
let colorOptions = colorIndexes.map(index => {
let className = this.state.color === index
? 'active'
: null
return (
<button onClick={e => this.handleColorButtonClick(index)(e)} className={className} key={index}>
<FolderMark color={index}/>
</button>
)
})
return (
<div className='FolderRow edit'>
<div className='folderColor'>
<button onClick={e => this.handleColorSelectClick(e)} className='select'>
<FolderMark color={this.state.color}/>
</button>
{this.state.isColorEditing
? (
<div className='options'>
<div className='label'>Color select</div>
{colorOptions}
</div>
)
: null
}
</div>
<div className='folderName'>
<input onKeyDown={e => this.handleNameInputKeyDown(e)} valueLink={this.linkState('name')} type='text'/>
</div>
<div className='folderControl'>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
</div>
</div>
)
case DELETE:
return (
<div className='FolderRow delete'>
<div className='folderDeleteLabel'>Are you sure to delete <strong>{folder.name}</strong> folder?</div>
<div className='folderControl'>
<button onClick={e => this.handleDeleteConfirmButtonClick(e)} className='primary'>Sure</button>
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
</div>
</div>
)
case IDLE:
default:
return (
<div className='FolderRow'>
<div className='sortBtns'>
<button onClick={e => this.handleUpClick(e)}><i className='fa fa-sort-up fa-fw'/></button>
<button onClick={e => this.handleDownClick(e)}><i className='fa fa-sort-down fa-fw'/></button>
</div>
<div className='folderColor'><FolderMark color={folder.color}/></div>
<div className='folderName'>{folder.name}</div>
<div className='folderControl'>
<button onClick={e => this.handleEditButtonClick(e)}><i className='fa fa-fw fa-edit'/></button>
<button onClick={e => this.handleDeleteButtonClick(e)}><i className='fa fa-fw fa-close'/></button>
</div>
</div>
)
}
}
}
FolderRow.propTypes = {
folder: PropTypes.shape(),
index: PropTypes.number,
count: PropTypes.number,
setAlert: PropTypes.func
}
FolderRow.prototype.linkState = linkState

View File

@@ -0,0 +1,99 @@
import React, { PropTypes } from 'react'
import FolderRow from './FolderRow'
import linkState from 'boost/linkState'
import { createFolder } from 'boost/actions'
export default class FolderSettingTab extends React.Component {
constructor (props) {
super(props)
this.state = {
name: ''
}
}
handleNewFolderNameKeyDown (e) {
if (e.keyCode === 13) {
this.handleSaveButtonClick()
}
}
handleSaveButtonClick (e) {
this.setState({alert: null}, () => {
if (this.state.name.trim().length === 0) return false
let { dispatch } = this.props
try {
dispatch(createFolder({
name: this.state.name
}))
} catch (e) {
this.setState({alert: {
type: 'error',
message: e.message
}})
return
}
this.setState({name: ''})
})
}
setAlert (alert, cb) {
this.setState({alert: alert}, cb)
}
render () {
let { folders } = this.props
let folderElements = folders.map((folder, index) => {
return (
<FolderRow
key={'folder-' + folder.key}
folder={folder}
index={index}
count={folders.length}
setAlert={(alert, cb) => this.setAlert(alert, cb)}
/>
)
})
let alert = this.state.alert
let alertElement = alert != null ? (
<p className={`alert ${alert.type}`}>
{alert.message}
</p>
) : null
return (
<div className='FolderSettingTab content'>
<div className='section'>
<div className='sectionTitle'>Manage folder</div>
<div className='folderTable'>
<div className='folderHeader'>
<div className='folderName'>Folder</div>
<div className='folderControl'>Edit/Delete</div>
</div>
{folderElements}
<div className='newFolder'>
<div className='folderName'>
<input onKeyDown={e => this.handleNewFolderNameKeyDown(e)} valueLink={this.linkState('name')} type='text' placeholder='New Folder'/>
</div>
<div className='folderControl'>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Add</button>
</div>
</div>
{alertElement}
</div>
</div>
</div>
)
}
}
FolderSettingTab.propTypes = {
folders: PropTypes.array,
dispatch: PropTypes.func
}
FolderSettingTab.prototype.linkState = linkState

View File

@@ -0,0 +1,11 @@
import React, { PropTypes } from 'react'
export default class HelpTab extends React.Component {
render () {
return (
<div className='content help'>
Comming soon
</div>
)
}
}

View File

@@ -0,0 +1,106 @@
import React, { PropTypes } from 'react'
import ProfileImage from 'boost/components/ProfileImage'
import api from 'boost/api'
const IDLE = 'IDLE'
const DELETE = 'DELETE'
export default class MemberRow extends React.Component {
constructor (props) {
super(props)
this.state = {
mode: IDLE
}
}
handleMemberRoleChange (e) {
let input = {
name: this.props.member.name,
role: e.target.value
}
api.setMember(this.props.team.id, input)
.then(res => {
console.log(res.body)
})
.catch(err => {
if (err.status != null) throw err
else console.error(err)
})
}
handleDeleteButtonClick (e) {
this.setState({mode: DELETE})
}
handleCancelButtonClick (e) {
this.setState({mode: IDLE})
}
handleDeleteConfirmButtonClick (e) {
let input = {
name: this.props.member.name
}
api.deleteMember(this.props.team.id, input)
.then(res => {
console.log(res.body)
})
.catch(err => {
if (err.status != null) throw err
else console.error(err)
})
}
render () {
let member = this.props.member
let currentUser = this.props.currentUser
let isDisabled = (currentUser.id === member.id)
switch (this.state.mode) {
case DELETE:
return (
<li className='MemberRow edit'>
<div className='colDescription'>
Are you sure to remove <strong>{member.profileName}</strong> ?
</div>
<div className='colDeleteConfirm'>
<button className='deleteButton primary' onClick={e => this.handleDeleteConfirmButtonClick(e)}>Sure</button>
<button className='deleteButton' onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
</div>
</li>
)
case IDLE:
default:
return (
<li className='MemberRow'>
<div className='colUserName'>
<ProfileImage className='userPhoto' email={member.email} size='30'/>
<div className='userInfo'>
<div className='userName'>{`${member.profileName} (${member.name})`}</div>
<div className='userEmail'>{member.email}</div>
</div>
</div>
<div className='colRole'>
<select onChange={e => this.handleMemberRoleChange(e)} disabled={isDisabled} value={member._pivot_role} className='userRole'>
<option value='owner'>Owner</option>
<option value='member'>Member</option>
</select>
</div>
<div className='colDelete'>
<button className='deleteButton' onClick={e => this.handleDeleteButtonClick(e)} disabled={isDisabled}><i className='fa fa-times fa-fw'/></button>
</div>
</li>
)
}
}
}
MemberRow.propTypes = {
member: PropTypes.shape(),
currentUser: PropTypes.shape(),
team: PropTypes.shape({
id: PropTypes.number
})
}

View File

@@ -0,0 +1,149 @@
import React, { PropTypes } from 'react'
import ProfileImage from 'boost/components/ProfileImage'
import Select from 'react-select'
import api from 'boost/api'
import _ from 'lodash'
import MemberRow from './MemberRow'
function getUsers (input, cb) {
api.searchUser(input)
.then(function (res) {
let users = res.body
cb(null, {
options: users.map(user => {
return { value: user.name, label: user.name }
}),
complete: false
})
})
.catch(function (err) {
console.error(err)
})
}
export default class MemberSettingTab extends React.Component {
constructor (props) {
super(props)
this.state = {
newMember: ''
}
}
getCurrentTeam (props) {
if (props == null) props = this.props
return _.findWhere(props.teams, {id: props.currentTeamId})
}
handleTeamSelectChange (e) {
this.props.switchTeam(e.target.value)
}
handleNewMemberChange (value) {
this.setState({newMember: value})
}
handleClickAddMemberButton (e) {
let team = this.getCurrentTeam()
if (team == null || team.userType !== 'team') return null
let input = {
name: this.state.newMember,
role: 'member'
}
api.setMember(team.id, input)
.then(res => {
console.log(res.body)
})
.catch(err => {
if (err.status != null) throw err
else console.error(err)
})
}
renderTeamOptions () {
return this.props.teams.map(team => {
return (
<option key={'team-' + team.id} value={team.id}>{team.name}</option>)
})
}
render () {
console.log(this.props.teams)
let team = this.getCurrentTeam()
if (team == null || team.userType === 'person') {
return this.renderNoTeam()
}
let membersEl = team.Members.map(member => (
<MemberRow key={'user-' + member.id} member={member} team={team} currentUser={this.props.currentUser}/>
))
return (
<div className='MemberSettingTab content'>
<div className='header'>
<span>Setting of</span>
<select
value={this.props.currentTeamId}
onChange={e => this.handleTeamSelectChange(e)}
className='teamSelect'>
{this.renderTeamOptions()}
</select>
</div>
<div className='membersTableSection section'>
<div className='sectionTitle'>Members</div>
<div className='addMember'>
<div className='addMemberLabel'>Add member</div>
<div className='addMemberControl'>
<Select
className='memberName'
placeholder='Input username to add'
autoload={false}
asyncOptions={getUsers}
onChange={val => this.handleNewMemberChange(val)}
value={this.state.newMember}
/>
<button onClick={e => this.handleClickAddMemberButton(e)} className='addMemberBtn'>add</button>
</div>
</div>
<ul className='memberList'>
<li className='header'>
<div className='colUserName'>Username</div>
<div className='colRole'>Role</div>
<div className='colDelete'>Delete</div>
</li>
{membersEl}
</ul>
</div>
</div>
)
}
renderNoTeam () {
return (
<div className='TeamSettingTab content'>
<div className='header'>
<span>Setting of</span>
<select
value={this.props.currentTeamId}
onChange={e => this.handleTeamSelectChange(e)}
className='teamSelect'>
{this.renderTeamOptions()}
</select>
</div>
<div className='section'>Please select a team</div>
</div>
)
}
}
MemberSettingTab.propTypes = {
currentUser: PropTypes.shape(),
teams: PropTypes.array,
currentTeamId: PropTypes.number,
switchTeam: PropTypes.func
}

View File

@@ -0,0 +1,171 @@
import React, { PropTypes } from 'react'
import _ from 'lodash'
import linkState from 'boost/linkState'
import api from 'boost/api'
export default class TeamSettingTab extends React.Component {
constructor (props) {
super(props)
let team = this.getCurrentTeam(props)
this.state = {
teamName: team != null ? team.profileName : '',
deleteConfirm: false,
alert: null
}
}
componentWillReceiveProps (nextProps) {
let team = this.getCurrentTeam(nextProps)
this.setState({
teamName: team != null ? team.profileName : '',
deleteConfirm: false
})
}
getCurrentTeam (props) {
if (props == null) props = this.props
return _.findWhere(props.teams, {id: props.currentTeamId})
}
handleTeamSelectChange (e) {
this.props.switchTeam(e.target.value)
}
handleSaveButtonClick (e) {
let input = {
profileName: this.state.teamName
}
let alert = {
type: 'info',
message: 'Sending...'
}
this.setState({alert}, () => {
api.updateTeamInfo(this.props.currentTeamId, input)
.then(res => {
console.log(res.body)
let alert = {
type: 'success',
message: 'Successfully done!'
}
this.setState({alert})
})
.catch(err => {
var message
if (err.status != null) {
message = err.response.body.message
} else if (err.code === 'ECONNREFUSED') {
message = 'Can\'t connect to API server.'
} else throw err
let alert = {
type: 'error',
message: message
}
this.setState({alert})
})
})
}
handleDeleteConfirmButtonClick (e) {
api.destroyTeam(this.props.currentTeamId)
.then(res => {
console.log(res.body)
})
.catch(err => {
let message
if (err.status != null) {
message = err.response.body.message
} else if (err.code === 'ECONNREFUSED') {
message = 'Can\'t connect to API server.'
} else throw err
console.log(message)
})
}
renderTeamOptions () {
return this.props.teams.map(team => {
return (
<option key={'team-' + team.id} value={team.id}>{team.name}</option>)
})
}
render () {
let team = this.getCurrentTeam()
if (team == null || team.userType === 'person') {
return this.renderNoTeam()
}
return (
<div className='TeamSettingTab content'>
<div className='header'>
<span>Setting of</span>
<select
value={this.props.currentTeamId}
onChange={e => this.handleTeamSelectChange(e)}
className='teamSelect'>
{this.renderTeamOptions()}
</select>
</div>
<div className='section'>
<div className='sectionTitle'>Team profile</div>
<div className='sectionInput'>
<label className='label'>Team Name</label>
<input valueLink={this.linkState('teamName')} type='text'/>
</div>
<div className='sectionConfirm'>
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
{this.state.alert != null
? (
<div className={'alert ' + this.state.alert.type}>{this.state.alert.message}</div>
)
: null}
</div>
</div>
{!this.state.deleteConfirm
? (
<div className='section teamDelete'>
<label>Delete this team</label>
<button onClick={e => this.setState({deleteConfirm: true})} className='deleteBtn'><i className='fa fa-fw fa-trash'/> Delete</button>
</div>
)
: (
<div className='section teamDeleteConfirm'>
<label>Are you sure to delete this team?</label>
<button onClick={e => this.setState({deleteConfirm: false})}>Cancel</button>
<button onClick={e => this.handleDeleteConfirmButtonClick(e)} className='deleteBtn'><i className='fa fa-fw fa-check'/> Sure</button>
</div>
)}
</div>
)
}
renderNoTeam () {
return (
<div className='TeamSettingTab content'>
<div className='header'>
<span>Setting of</span>
<select
value={this.props.currentTeamId}
onChange={e => this.handleTeamSelectChange(e)}
className='teamSelect'>
{this.renderTeamOptions()}
</select>
</div>
<div className='section'>Please select a team</div>
</div>
)
}
}
TeamSettingTab.propTypes = {
currentTeamId: PropTypes.number,
teams: PropTypes.array,
switchTeam: PropTypes.func
}
TeamSettingTab.prototype.linkState = linkState

View File

@@ -0,0 +1,122 @@
import React, { PropTypes } from 'react'
import { connect, Provider } from 'react-redux'
import linkState from 'boost/linkState'
import store from 'boost/store'
import AppSettingTab from './Preference/AppSettingTab'
import HelpTab from './Preference/HelpTab'
import FolderSettingTab from './Preference/FolderSettingTab'
import ContactTab from './Preference/ContactTab'
import { closeModal } from 'boost/modal'
const APP = 'APP'
const HELP = 'HELP'
const FOLDER = 'FOLDER'
const CONTACT = 'CONTACT'
class Preferences extends React.Component {
constructor (props) {
super(props)
this.state = {
currentTab: APP
}
}
switchTeam (teamId) {
this.setState({currentTeamId: teamId})
}
handleNavButtonClick (tab) {
return e => {
this.setState({currentTab: tab})
}
}
render () {
let content = this.renderContent()
let tabs = [
{target: APP, label: 'Preferences'},
{target: FOLDER, label: 'Manage folder'},
{target: CONTACT, label: 'Contact form'}
]
let navButtons = tabs.map(tab => (
<button key={tab.target} onClick={e => this.handleNavButtonClick(tab.target)(e)} className={this.state.currentTab === tab.target ? 'active' : ''}>{tab.label}</button>
))
return (
<div className='Preferences modal'>
<div className='header'>
<div className='title'>Setting</div>
<button onClick={e => closeModal()} className='closeBtn'>Done</button>
</div>
<div className='nav'>
{navButtons}
</div>
{content}
</div>
)
}
renderContent () {
let { user, folders, dispatch } = this.props
switch (this.state.currentTab) {
case HELP:
return (<HelpTab/>)
case FOLDER:
return (
<FolderSettingTab
dispatch={dispatch}
folders={folders}
/>
)
case CONTACT:
return (
<ContactTab/>
)
case APP:
default:
return (
<AppSettingTab
user={user}
dispatch={dispatch}
/>
)
}
}
}
Preferences.propTypes = {
user: PropTypes.shape({
name: PropTypes.string
}),
folders: PropTypes.array,
dispatch: PropTypes.func
}
Preferences.prototype.linkState = linkState
function remap (state) {
let { user, folders, status } = state
return {
user,
folders,
status
}
}
let RootComponent = connect(remap)(Preferences)
export default class PreferencesModal extends React.Component {
render () {
return (
<Provider store={store}>
<RootComponent/>
</Provider>
)
}
}

View File

@@ -0,0 +1,115 @@
import React, { PropTypes } from 'react'
import MarkdownPreview from 'boost/components/MarkdownPreview'
import CodeEditor from 'boost/components/CodeEditor'
export default class Tutorial extends React.Component {
constructor (props) {
super(props)
this.state = {
slideIndex: 0
}
}
handlePriorSlideClick () {
if (this.state.slideIndex > 0) this.setState({slideIndex: this.state.slideIndex - 1})
}
handleNextSlideClick () {
if (this.state.slideIndex < 4) this.setState({slideIndex: this.state.slideIndex + 1})
}
startButtonClick (e) {
this.props.close()
}
render () {
let content = this.renderContent(this.state.slideIndex)
let dotElements = []
for (let i = 0; i < 5; i++) {
dotElements.push(<i key={i} className={'fa fa-fw fa-circle' + (i === this.state.slideIndex ? ' active' : '')}/>)
}
return (
<div className='Tutorial modal'>
<button onClick={e => this.handlePriorSlideClick()} className={'priorBtn' + (this.state.slideIndex === 0 ? ' hide' : '')}>
<i className='fa fa-fw fa-angle-left'/>
</button>
<button onClick={e => this.handleNextSlideClick()} className={'nextBtn' + (this.state.slideIndex === 4 ? ' hide' : '')}>
<i className='fa fa-fw fa-angle-right'/>
</button>
{content}
<div className='dots'>
{dotElements}
</div>
</div>
)
}
renderContent (index) {
switch (index) {
case 0:
return (<div className='slide slide0'>
<div className='title'>Welcome to Boost</div>
<div className='content'>
Boost is a brand new note app for software<br/>
Don't waste time cleaning up your data.<br/>
devote that time to more creative work.<br/>
Hack your memory.
</div>
</div>)
case 1:
let content = '## Boost is a note app for engineer.\n\n - Write with markdown\n - Stylize beautiful'
return (<div className='slide slide1'>
<div className='title'>Write with Markdown</div>
<div className='content'>
Markdown is available.<br/>
Your notes will be stylized beautifully and quickly.
<div className='markdown'>
<pre className='left'>{content}</pre>
<MarkdownPreview className='right' content={content}/>
</div>
</div>
</div>)
case 2:
let code = 'import shell from \'shell\'\r\nvar React = require(\'react\')\r\nvar { PropTypes } = React\r\nimport markdown from \'boost\/markdown\'\r\nvar ReactDOM = require(\'react-dom\')\r\n\r\nfunction handleAnchorClick (e) {\r\n shell.openExternal(e.target.href)\r\n e.preventDefault()\r\n}\r\n\r\nexport default class MarkdownPreview extends React.Component {\r\n componentDidMount () {\r\n this.addListener()\r\n }\r\n\r\n componentDidUpdate () {\r\n this.addListener()\r\n }\r\n\r\n componentWillUnmount () {\r\n this.removeListener()\r\n }'
return (<div className='slide slide2'>
<div className='title'>Beautiful code highlighting</div>
<div className='content'>
Boost supports code syntax highlighting.<br/>
There are more than 100 different type of language.
<div className='code'>
<CodeEditor readOnly mode='jsx' code={code}/>
</div>
</div>
</div>)
case 3:
return (<div className='slide slide3'>
<div className='title'>Easy to access with Finder</div>
<div className='content'>
The Finder helps you organize all of the files and documents.<br/>
There is a short-cut key [control + shift + tab] to open the Finder.<br/>
It is available to save your articles on the Clipboard<br/>
by selecting your file with pressing Enter key,<br/>
and to paste the contents of the Clipboard with [{process.platform === 'darwin' ? 'Command' : 'Control'}-V]
<img width='480' src='../../resources/finder.png'/>
</div>
</div>)
case 4:
return (<div className='slide slide4'>
<div className='title'>Are you ready?</div>
<div className='content'>
<button onClick={e => this.startButtonClick(e)}>Start<br/>Boost</button>
</div>
</div>)
default:
return null
}
}
}
Tutorial.propTypes = {
close: PropTypes.func
}