Skip to content

Hackape/settings #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/commands/commandBindings/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export default {
$d(Modal.showModal('CommandPalette'))
},

'global:show_settings': c => {
$d(Modal.showModal({type: 'Settings', position: 'center'}))
},

'modal:dismiss': (c) => {
$d(Modal.dismissModal())
}
Expand Down
2 changes: 1 addition & 1 deletion app/commands/keymaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export default {
'cmd+alt+3': 'editor:split_pane_vertical_3',
'cmd+alt+shift+3': 'editor:split_pane_horizontal_3',
'cmd+alt+4': 'editor:split_pane_vertical_4',
// 'cmd+p': 'global:find_file',
'cmd+comma': 'global:show_settings',
}
4 changes: 2 additions & 2 deletions app/commands/lib/keycodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ var keyCodeToKey = {
90: 'z',
186: ';',
187: '=',
188: ',',
188: 'comma',
189: '-',
190: '.',
191: '/',
Expand Down Expand Up @@ -119,7 +119,7 @@ var keyToKeyCode = {
'z': 90,
';': 186,
'=': 187,
',': 188,
',': 188, 'comma': 188,
'-': 189,
'.': 190,
'/': 191,
Expand Down
2 changes: 1 addition & 1 deletion app/components/AceEditor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@ class AceEditor extends Component {
}
}

AceEditor = connect(null, null)(AceEditor);
AceEditor = connect(state => ({setting: state.SettingState.tabs.EDITOR }), null)(AceEditor);

export default AceEditor;
11 changes: 10 additions & 1 deletion app/components/Modal/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import _ from 'lodash'

export const MODAL_SHOW = 'MODAL_SHOW'
export const showModal = promiseActionMixin(
createAction(MODAL_SHOW, (modalType, content) => ({id: _.uniqueId(), modalType, content}))
createAction(MODAL_SHOW, (modalConfig, content) => {
switch (typeof modalConfig) {
case 'object':
return {...modalConfig, id: _.uniqueId()}
case 'string':
return {type: modalConfig, id: _.uniqueId(), content}
default:
return {type: ''}
}
})
)

export const MODAL_DISMISS = 'MODAL_DISMISS'
Expand Down
17 changes: 12 additions & 5 deletions app/components/Modal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import {
Prompt,
Confirm,
SettingsView,
CommandPalette,
GitCommitView,
GitStashView,
Expand All @@ -22,11 +23,14 @@ var ModalContainer = (props) => {
{ props.stack.map(modalConfig => {
const {id, isActive, showBackdrop, position} = modalConfig;
return isActive
? <div key={id} className={cx('modal-container', position,
{'show-backdrop': showBackdrop})} >
? <div key={id} className={cx(
position,
'modal-container',
{'show-backdrop': showBackdrop}
)} >
<Modal {...modalConfig} />
<div className='backdrop'
onClick={e=>dispatch({type:'MODAL_DISMISS'})}></div>
onClick={e=>dispatch({type:'MODAL_DISMISS'})} />
</div>
: null
}) }
Expand All @@ -41,10 +45,10 @@ class Modal extends Component {
}

render() {
const {modalType, content} = this.props
const {type, content} = this.props

var modalContent = function () {
switch (modalType) {
switch (type) {
case 'GitCommit':
return <GitCommitView {...this.props} />

Expand Down Expand Up @@ -75,6 +79,9 @@ class Modal extends Component {
case 'CommandPalette':
return <CommandPalette {...this.props} />

case 'Settings':
return <SettingsView {...this.props} />

default:
return content
}
Expand Down
1 change: 1 addition & 0 deletions app/components/Modal/modals/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export GitResetView from '../../Git/modals/reset';
export GitTagView from '../../Git/modals/tag';
export GitMergeView from '../../Git/modals/merge';
export GitNewBranchView from '../../Git/modals/newBranch';
export SettingsView from '../../Setting';
8 changes: 3 additions & 5 deletions app/components/Modal/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ const baseModal = {
}

const ModalReducer = handleActions({
[MODAL_SHOW]: (state, {payload: {id, modalType, content}, meta}) => {
[MODAL_SHOW]: (state, {payload: modalConfig, meta}) => {
let newModal = {
...baseModal,
isActive: true,
id,
modalType,
meta,
content
...modalConfig,
meta
}
return {
...state,
Expand Down
9 changes: 9 additions & 0 deletions app/components/Setting/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createAction } from 'redux-actions'

export const SETTING_ACTIVATE_TAB = 'SETTING_ACTIVATE_TAB'
export const activateSettingTab = createAction(SETTING_ACTIVATE_TAB)

export const SETTING_UPDATE_FIELD = 'SETTING_UPDATE_FIELD'
export const updateSettingItem = createAction(SETTING_UPDATE_FIELD,
(domain, fieldName, value) => ({domain, fieldName, value})
)
136 changes: 136 additions & 0 deletions app/components/Setting/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* @flow weak */
import React, { Component } from 'react'
import { bindActionCreator, bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import cx from 'classnames'
import _ from 'lodash'

import * as SettingActions from './actions'

let FormGroupFactory = ({domain, settingItem, dispatch}) => {
let formComponent
let updateSettingItem = e => {
let value = (() => {
switch (e.target.type) {
case 'checkbox':
return e.target.checked
case 'number':
return Number(e.target.value)
case 'text':
return e.target.value
}
})()

return dispatch(
SettingActions.updateSettingItem(domain, settingItem.name, value)
)
}

if (settingItem.options && _.isArray(settingItem.options)) {
return (
<div className='form-group'>
<label>{settingItem.name}</label>
<select className='form-control'
onChange={updateSettingItem}
value={settingItem.value} >
{settingItem.options.map(option =>
_.isObject(option) ?
<option key={option.value} value={option.value}>{option.name}</option>
: <option key={option} value={option}>{option}</option>
)}
</select>
</div>)
} else if (_.isBoolean(settingItem.value)) {
return (
<div className='form-group'>
<div className='checkbox'>
<label>
<input type='checkbox'
onChange={updateSettingItem} checked={settingItem.value} />
<strong>{settingItem.name}</strong>
</label>

</div>
</div>)
} else {
return (
<div className='form-group'>
<label>{settingItem.name}</label>
<input className='form-control'
type={_.isNumber(settingItem.value) ? 'number' : 'text'}
min='1'
onChange={updateSettingItem}
value={settingItem.value} />
</div>)
}
}
FormGroupFactory = connect(null)(FormGroupFactory)

const GeneralSettings = (props) => {
return (
<div>
<h2 className='settings-content-header'>General Settings</h2>
{props.items.map(settingItem =>
<FormGroupFactory
key={settingItem.name}
domain='GENERAL'
settingItem={settingItem} />
)}
</div>
)
}

const EditorSettings = (props) => {
return (
<div>
<h2 className='settings-content-header'>Editor Settings</h2>
{props.items.map(settingItem =>
<FormGroupFactory
key={settingItem.name}
domain='EDITOR'
settingItem={settingItem} />
)}
</div>
)
}


const SettingsContent = ({content}) => {
switch (content.id) {
case 'GENERAL':
default:
return <GeneralSettings {...content} />
case 'EDITOR':
return <EditorSettings {...content} />
}
}

let SettingsView = (props) => {
const {activeTabId, tabIds, tabs, activateSettingTab} = props

return (
<div className='settings-container'>
<div className='settings-header'>
<div className='tab-bar-header'>Settings</div>
<ul className='tab-bar-tabs'>
{tabIds.map(tabId =>
<li key={tabId}
className={cx('tab-bar-item', {'active': tabId === activeTabId})}
onClick={e => activateSettingTab(tabId)} >{tabId}</li>
)}
</ul>
</div>
<div className='settings-content' style={{overflow:'scroll'}}>
<div className='settings-content-container'>
<SettingsContent content={tabs[activeTabId]} />
</div>
</div>
</div>
)
}
SettingsView = connect(
state => (state.SettingState),
dispatch => bindActionCreators(SettingActions, dispatch)
)(SettingsView)

export default SettingsView
109 changes: 109 additions & 0 deletions app/components/Setting/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* @flow weak */
import { handleActions } from 'redux-actions'
import { OrderedMap } from 'immutable'
import {
SETTING_ACTIVATE_TAB,
SETTING_UPDATE_FIELD
} from './actions'

import ace from 'brace'
import 'brace/ext/themelist'
const aceThemes = ace.acequire('ace/ext/themelist')
.themes.map((t, i) => ({value: t.theme, name: t.caption}))


let SettingState = {
activeTabId: 'EDITOR',
tabIds: ['GENERAL', 'EDITOR'],
tabs: {
'GENERAL': {
id: 'GENERAL',
items: [{
name: 'Language',
value: 'English',
options: ['English', 'Chinese']
}, {
name: 'Theme',
value: 'Light',
options: ['Light', 'Dark']
}]
},
'EDITOR': {
id: 'EDITOR',
items: [{
name: 'Keyboard Mode',
value: 'Default',
options: ['Default', 'Vim', 'Emacs']
}, {
name: 'Font Size',
value: 14
}, {
name: 'Font Family',
value: 'Consolas',
options: ['Consolas', 'Courier', 'Courier New', 'Menlo']
}, {
name: 'Editor Theme',
value: 'Default',
options: aceThemes
}, {
name: 'Charset',
value: 'utf8',
options: [
{name: 'Unicode (UTF-8)', value: 'utf8'},
{name: '中文简体 (GB18030)', value: 'gb18030'},
{name: '中文繁体 (Big5-HKSCS)', value: 'big5'}
]
}, {
name: 'Soft Tab',
value: true
}, {
name: 'Tab Size',
value: 4,
options: [1,2,3,4,5,6,7,8]
}, {
name: 'Auto Save',
value: true
}, {
name: 'Auto Wrap',
value: false
}, {
name: 'Live Auto Completion',
value: true
}, {
name: 'Snippets',
value: false
}]
}
}
}

export default handleActions({
[SETTING_ACTIVATE_TAB]: (state, action) => {
return {
...state,
activeTabId: action.payload
}
},

[SETTING_UPDATE_FIELD]: (state, action) => {
const {domain, fieldName, value} = action.payload

return {
...state,
tabs: {
...state.tabs,
[domain]: {
...state.tabs[domain],
items: state.tabs[domain].items.map(settingItem => {
if (settingItem.name === fieldName) {
return {...settingItem, value}
} else {
return settingItem
}
})
}
}
}

}
}, SettingState)
Loading