TechNote/app/components/LibraryNav.jsx
Joey Payne a705d8cd3f Fix navigation issues with deleting and clicking
Deleting a notebook would only work on the first item because
array.slice works differently than intended.

When adding a new notebook, it now focuses on that notebook and loads
the proper notes. Same when deleting a notebook.
2016-03-15 08:14:48 -06:00

456 lines
15 KiB
JavaScript

import React from 'react'
import ReactDOM from 'react-dom'
import mui from 'material-ui'
import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'
import styles from 'material-ui/lib/styles'
import List from 'material-ui/lib/lists/list'
import ListItem from 'material-ui/lib/lists/list-item'
import ActionGrade from 'material-ui/lib/svg-icons/action/grade'
import NoteBook from 'material-ui/lib/svg-icons/action/class'
import History from 'material-ui/lib/svg-icons/action/history'
import AddCircleOutline from 'material-ui/lib/svg-icons/content/add-circle-outline'
import Folder from 'material-ui/lib/svg-icons/file/folder'
import Edit from 'material-ui/lib/svg-icons/editor/mode-edit'
import Delete from 'material-ui/lib/svg-icons/action/delete'
import Divider from 'material-ui/lib/divider'
import SelectableList from 'SelectableList'
import uuid from 'node-uuid'
import path from 'path-extra'
import * as utils from 'utils'
import glob from 'glob'
import mkdirp from 'mkdirp'
import jsfile from 'jsonfile'
import rmdir from 'rimraf'
const colors = styles.Colors
String.prototype.trunc = String.prototype.trunc || function(n){
return (this.length > n) ? this.substr(0, n-1)+'...' : this;
};
const {
IconButton,
MenuItem,
Badge,
TextField,
Styles,
Paper
} = mui
const DefaultRawTheme = Styles.LightRawTheme
export default class LibraryNav extends React.Component {
constructor(props, context){
super(props, context)
this.state = {
open: true,
}
this.getNotebooks()
const { store } = this.context
store.subscribe(this.stateChanged)
}
getNotebooks = () => {
var dataPath = utils.getAppDataPath()
var notebooks = glob.sync(path.join(dataPath, '!(Entries|Trash).qvnotebook'))
for(var i=0; i<notebooks.length; i++){
var nbFile = notebooks[i]
var obj = jsfile.readFileSync(path.join(nbFile, 'meta.json'))
var notes = glob.sync(path.join(nbFile, '*.qvnote'))
var nb = {
'title': obj.name,
'uuid': obj.uuid,
'notes': notes.length,
'path': nbFile
}
if(nb.title == ''){
nb.state = 'editing'
}
else{
nb.state = 'displaying'
}
this.props.addNotebook(nb)
}
this.props.sortNotebooks(utils.compareNotebooks)
};
static get childContextTypes(){
return {muiTheme: React.PropTypes.object}
}
getChildContext() {
return {
muiTheme: getMuiTheme(DefaultRawTheme)
}
}
scrollToRenamedNotebook = (original) => {
var newIndex = 0
for (var j = 0; j < this.props.navigation.notebooks.length; j++){
var n = this.props.navigation.notebooks[j]
if (n == original){
newIndex = j
this.refs.notebookList.setIndex(j, () => {
var nbitem = $(ReactDOM.findDOMNode(this.refs[n.title+newIndex]))
var cont = $('#notebook-list')
var newPos = 0
if(typeof nbitem.offset() != 'undefined'){
newPos = nbitem.offset().top - cont.offset().top + cont.scrollTop() - cont.height()/2;
}
$('#notebook-list').animate({
scrollTop: newPos
})
})
}
}
};
initDefaultNotebookPath = (notebook) => {
var nbPath = utils.getNotebookPath(notebook)
var dir = mkdirp.sync(nbPath)
var notePath = utils.getNotebookPath(notebook)
var meta = {
'name': notebook.title,
'uuid': notebook.uuid
}
var metaPath = path.join(nbPath, 'meta.json')
var t = jsfile.writeFileSync(metaPath, meta)
};
createNotebookPath = (notebook, callback) => {
mkdirp(notebook.path, (err) => {
if(err){
console.log('There was an error creating the directory '+notebook.path)
console.log(err)
}
else{
this.props.addNotebook(notebook)
this.createNotebookMeta(notebook, callback)
}
})
};
createNewNotebook = (callback) => {
var nbUuid = uuid.v4().toUpperCase()
var nbPath = utils.getNotebookPathFromUUID(nbUuid)
var notebook = {
'state': 'editing',
'title': '',
'uuid': nbUuid,
'path': nbPath,
'notes': 0
}
this.createNotebookPath(notebook, callback)
};
createNotebookMeta = (notebook, callback) => {
var notePath = utils.getNotebookPath(notebook)
var meta = {
'name': notebook.title,
'uuid': notebook.uuid
}
var metaPath = path.join(notePath, 'meta.json')
jsfile.writeFile(metaPath, meta, (err) => {
if(err){
console.log(err)
}
if(utils.isFunction(callback)){
callback(notebook, err)
}
})
};
newNotebookUnfocus = (i) => {
var nb = this.props.navigation.notebooks[i]
var tf = this.refs["textField"+i]
var notebookName = tf.getValue()
if (notebookName){
this.refs.mainList.setIndex(-1)
nb.title = notebookName
nb.state = 'displaying'
this.props.updateNotebook(nb, i, () => {
this.props.sortNotebooks(this.compareNotebooks, ()=>{
this.scrollToRenamedNotebook(nb)
})
})
this.createNotebookMeta(nb)
}
else if(nb.title){
nb.state = 'displaying'
this.props.updateNotebook(nb, i)
}
};
addNotebookTapped = (callback) => {
if(!utils.isFunction(callback)){
callback = (notebook) => {
if(this.refs['textField0']){
this.refs['textField0'].focus()
}
this.refs.mainList.setIndex(-1)
this.refs.notebookList.setIndex(0)
this.props.updateSelection(notebook)
}
}
this.createNewNotebook(callback)
};
newNotebookTyped = (i) => {
};
menuItemClicked = (i, ev) => {
this.refs.notebookList.setIndex(-1)
var item = this.props.navigation.menuItems[i]
var type = 'leftClick'
var nativeEvent = ev.nativeEvent
if(nativeEvent.button == 2){
//Right click
type = 'rightClick'
}
if (item.isNotebook){
var notebook = utils.loadNotebookByName(item.name)
notebook = utils.updateObject(item, notebook)
this.props.updateSelection(notebook)
}
else if(item.glob){
this.props.updateSelection(item)
}
// Process any callbacks attached to the menu
var clickedCallbacks = this.props.navigation.clickedCallbacks
for(var i=0; i<clickedCallbacks.length; i++){
var callback = clickedCallbacks[i]
callback(i, item, type, ev)
}
};
renameTapped = (i) => {
var nbs = this.props.navigation.notebooks
nbs[i].state = 'editing'
this.props.updateNotebook(nbs[i], i, () => {
this.refs['textField'+i].focus()
})
this.props.closeContextMenu()
}
deleteTapped = (i, callback) => {
var nbs = this.props.navigation.notebooks
var nb = nbs.slice(i, i+1)[0]
rmdir(nb.path, (err)=>{
if(err){
console.log(err)
}
this.props.removeNotebook(i, () =>{
if(utils.isFunction(callback)){
callback(nb, err)
}
var newNbs = this.props.navigation.notebooks
var selection = null
if(newNbs.length > i){
selection = newNbs[i]
}
else if(newNbs.length > 0){
selection = newNbs[newNbs.length-1]
}
if(selection){
this.props.updateSelection(selection)
}
})
})
this.props.closeContextMenu()
}
contextMenuItems = (i) => {
return [
<MenuItem
key='rename'
primaryText="Rename"
leftIcon={<Edit />}
onTouchTap={this.renameTapped.bind(this, i)}/>,
<Divider key='div1'/>,
<MenuItem
key='delete'
primaryText="Delete"
onTouchTap={this.deleteTapped.bind(this, i)}
leftIcon={<Delete />} />
]
};
noteBookTapped = (i, ev) => {
var nativeEvent = ev.nativeEvent
if(nativeEvent.button == 2){
//Right click
var x = nativeEvent.pageX
var y = nativeEvent.pageY
this.props.openContextMenu(x, y)
this.props.updateContextMenu(this.contextMenuItems(i))
this.props.updateSelection(this.props.navigation.notebooks[i])
}
else{
this.props.updateSelection(this.props.navigation.notebooks[i])
}
this.refs.mainList.setIndex(-1)
};
preventEventProp = (ev) => {
ev.stopPropagation();
};
notebookList = () => {
return (<div id="notebook-list">
{this.props.navigation.notebooks.map((notebook, i) =>{
var l = null
if (notebook.state == 'editing'){
l = <ListItem
key={notebook.uuid || i}
value={i}
innerDivStyle={{'paddingBottom': 0,
'paddingTop': 0}}
onTouchTap={this.noteBookTapped.bind(this, i)}
leftIcon={<NoteBook color={colors.grey500}/>}
rightIcon={<Badge badgeContent={notebook.notes}/>}>
<TextField
ref={"textField"+i}
fullWidth={true}
style={{maxWidth: 120}}
hintText={notebook.title.trunc(14) || "Notebook Name"}
underlineShow={false}
onBlur={this.newNotebookUnfocus.bind(this, i)}
onEnterKeyDown={this.newNotebookUnfocus.bind(this, i)}
/>
</ListItem>
}
else{
l = <ListItem
key={notebook.uuid || i}
value={i}
primaryText={notebook.title.trunc(14)}
tooltip={notebook.title}
ref={notebook.title+i}
onTouchTap={this.noteBookTapped.bind(this, i)}
leftIcon={
<IconButton
onClick={this.preventEventProp}
tooltip={notebook.title}
touch={true}
onTouchTap={this.notebookIconTapped.bind(this, i)}
style={{padding: 0}}
>
<NoteBook
color={colors.grey500}/>
</IconButton>
}
rightIcon={<Badge
style={{'padding': 0}}
badgeContent={notebook.notes} />}
/>
}
return l
})}
</div>)
};
notebookIconTapped = (i, ev) => {
ev.stopPropagation()
};
stateChanged = () => {
const { store } = this.context
const state = store.getState()
if(utils.isFunction(state.navigation.callback)){
state.navigation.callback(state)
}
};
render(){
return (
<div id={this.props.id} className={this.props.className+" leftnav noselect"} open={this.state.open}>
<SelectableList
ref='mainList'
className="list"
initialIndex={0}
id="main-nav"
selectedItemStyle={{backgroundColor: colors.lightBlue100}}
subheader="Library">
{this.props.navigation.menuItems.map((item, i) => {
return <ListItem
primaryText={item.name}
ref={item.name}
key={i}
leftIcon={item.icon}
rightIcon={<Badge badgeContent={item.notes} />}
value={i}
onTouchTap={this.menuItemClicked.bind(this, i)}/>;
})}
</SelectableList>
<Divider />
<SelectableList
id="nblist"
className="list"
ref='notebookList'
selectedItemStyle={{backgroundColor: colors.lightBlue100}}
subheader={<div>
<div className="inline">NoteBooks</div>
<IconButton
onTouchTap={this.addNotebookTapped}
ref="addNotebookBtn"
tooltip="Add New Notebook"
touch={true}
style={{'zIndex': 1000}}
className="right inline">
<AddCircleOutline
color={colors.grey500}/>
</IconButton>
</div>}>
{this.notebookList()}
</SelectableList>
</div>
)
}
}
LibraryNav.contextTypes = {
store: React.PropTypes.object
}
LibraryNav.defaultProps = {
closeContextMenu: () => {},
updateSelection: () => {}
};