TechNote/app/components/LibraryNav.jsx
Joey Payne 7684e53c47 Add ability to add notes to notebooks
Notes are added to the selected notebook on the left and they
appropriately update the numbers next to the labels.

Notes will also load based on notebook/menu item selection.
2016-03-11 15:21:50 -07:00

528 lines
17 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.navItems = [
{
'name': 'Entries',
'isNotebook': true,
'icon': <img src="images/note.svg"/>,
'clicked': this.props.entriesTapped || this.entriesTapped
},
{
'name': 'Starred',
'notes': 0,
'icon': <ActionGrade color={colors.amberA700}/>,
'clicked': this.props.starredTapped || this.starredTapped
},
{
'name': 'Recents',
'notes': 0,
'icon': <History color="#4BAE4E"/>,
'clicked': this.props.recentsTapped || this.recentsTapped
},
{
'name': 'Trash',
'isNotebook': true,
'icon': <Delete color={colors.grey500}/>,
'clicked': this.props.trashTapped || this.trashTapped
},
{
'name': 'All Notes',
'notes': 0,
'glob': '*.qvnotebook/*.qvnote',
'icon': <Folder color="#FFCC5F" />,
'clicked': this.props.allNotesTapped || this.allNotesTapped
}
]
this.loadDefaultNotebooks()
this.getNotebooks()
const { store } = this.context
store.subscribe(this.stateChanged)
}
loadDefaultNotebooks = () => {
var notebooks = this.navItems
for(var i=0; i<notebooks.length; i++){
var nb = notebooks[i]
if(nb.isNotebook){
var temp = {
title: nb.name,
uuid: nb.name,
notes: 0
}
this.initDefaultNotebookPath(temp)
var loaded = utils.loadNotebookByName(nb.name)
nb.title = loaded.title
nb.uuid = loaded.uuid
nb.path = loaded.path
nb.notes = loaded.notes
}
else if(nb.glob){
var dataPath = utils.getAppDataPath()
var notes = glob.sync(path.join(dataPath, nb.glob))
nb.title = nb.name
nb.uuid = nb.name
nb.notes = notes.length
}
if(i==0){
this.props.updateSelection(nb)
}
this.props.addMenuItem(nb)
}
};
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)
}
}
entriesTapped = (i, item, type, ev) => {
};
starredTapped = (i, item, type, ev) => {
};
recentsTapped = (i, item, type, ev) => {
};
trashTapped = (i, item, type, ev) => {
};
allNotesTapped = (i, item, type, ev) => {
};
blank(){
}
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 = () => {
if(this.refs['textField0']){
this.refs['textField0'].focus()
}
}
}
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)
}
item.clicked(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, 1)[0]
rmdir(nb.path, (err)=>{
if(err){
console.log(err)
}
this.props.removeNotebook(i, () =>{
if(utils.isFunction(callback)){
callback(nb, err)
}
})
})
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.updateContextMenu(this.contextMenuItems(i))
this.props.openContextMenu(x, y)
}
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: () => {}
};