diff --git a/.gitignore b/.gitignore index 58b72d5..f4f791f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,4 @@ jspm_packages # Optional REPL history .node_repl_history -dist +app/dist diff --git a/app/modules/EntrySelector.jsx b/app/components/EntrySelector.jsx similarity index 100% rename from app/modules/EntrySelector.jsx rename to app/components/EntrySelector.jsx diff --git a/app/modules/LibraryNav.jsx b/app/components/LibraryNav.jsx similarity index 52% rename from app/modules/LibraryNav.jsx rename to app/components/LibraryNav.jsx index 0577679..e972b6f 100644 --- a/app/modules/LibraryNav.jsx +++ b/app/components/LibraryNav.jsx @@ -15,7 +15,17 @@ 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 { SelectableContainerEnhance } from 'material-ui/lib/hoc/selectable-enhance' +import { SelectableContainerEnhance } from '../enhance/SelectableEnhance' + +import uuid from 'node-uuid' +import path from 'path-extra' + +import * as utils from 'utils' +import glob from 'glob' + +import fs from 'fs' +import mkdirp from 'mkdirp' +import jsfile from 'jsonfile' const colors = styles.Colors @@ -28,38 +38,45 @@ function eventFire(el, etype){ el.dispatchEvent(evObj); } } +String.prototype.trunc = String.prototype.trunc || function(n){ + return (this.length > n) ? this.substr(0, n-1)+'...' : this; +}; -const {AppBar, - AppCanvas, - FontIcon, - IconButton, - EnhancedButton, - NavigationClose, - Menu, - MenuItem, - Mixins, - RaisedButton, - FlatButton, - Popover, - Badge, - TextField, - Dialog, - Styles, - Tab, - Tabs, - Paper} = mui +const { + AppBar, + AppCanvas, + FontIcon, + IconButton, + EnhancedButton, + NavigationClose, + Menu, + MenuItem, + Mixins, + RaisedButton, + FlatButton, + Popover, + Badge, + TextField, + Dialog, + Styles, + Tab, + Tabs, + Paper} = mui let SelectableList = SelectableContainerEnhance(List) function wrapState(ComposedComponent) { const StateWrapper = React.createClass({ getInitialState() { - return {selectedIndex: 0}; + return {selectedIndex: -1}; + }, + setIndex(i, func){ + this.setState({ + selectedIndex: i, + }, func); }, handleUpdateSelectedIndex(e, index) { - this.setState({ - selectedIndex: index, - }); + this.setIndex(index); }, render() { return ( @@ -118,24 +135,38 @@ export default class LibraryNav extends React.Component { ], notebooks: [ - {'state': 'editing', 'title': '', 'notes': 0}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10}, - {'state': 'displaying', 'title': 'FieldNotes', 'notes': 10} ] } this.sortNotebooks(true) + this.getNotebooks() } + getNotebooks = () => { + var dataPath = utils.getAppDataPath() + var notebooks = glob.sync(path.join(dataPath, '*.qvnotebook')) + for(var i=0; i { let atitle = a.title.toLowerCase() let btitle = b.title.toLowerCase() @@ -147,10 +178,10 @@ export default class LibraryNav extends React.Component { return 0 }; - sortNotebooks = (notset) => { + sortNotebooks = (dontSet, func) => { this.state.notebooks.sort(this.compareNotebooks) - if (!notset){ - this.setState({notebooks: this.state.notebooks}) + if (!dontSet){ + this.setState({notebooks: this.state.notebooks}, func) } }; @@ -188,16 +219,71 @@ export default class LibraryNav extends React.Component { } + scrollToRenamedNotebook = (original) => { + var newIndex = 0 + for (var j = 0; j < this.state.notebooks.length; j++){ + var n = this.state.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 = nbitem.offset().top - cont.offset().top + cont.scrollTop() - cont.height()/2; + $('#notebook-list').animate({ + scrollTop: newPos + }) + }) + } + } + + }; + + + createNewNotebook = (notebook, callback) => { + var notePath = utils.getNotebookPath(notebook) + mkdirp(notePath, (err) => { + if(err){ + console.log('There was an error creating the directory '+notePath) + console.log(err) + } + else{ + if (callback){ + callback() + } + this.createNotebookMeta(notebook) + } + }) + }; + + createNotebookMeta = (notebook) => { + 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) + } + }) + + }; + newNotebookUnfocus = (i) => { var nb = this.state.notebooks[i] + var tf = this.refs["textField"+i] var notebookName = tf.getValue() if (notebookName){ nb.title = notebookName nb.state = 'displaying' this.setState({notebooks: this.state.notebooks}) - this.sortNotebooks() + this.sortNotebooks(false, ()=>{ + this.scrollToRenamedNotebook(nb) + }) + this.createNotebookMeta(nb) } else if(nb.title){ nb.state = 'displaying' @@ -206,11 +292,23 @@ export default class LibraryNav extends React.Component { }; addNotebookTapped = () => { - var nbs = this.state.notebooks - nbs.splice(0, 0, {'state': 'editing', - 'title': '', - 'notes': 0}) - this.setState({notebooks: nbs}) + var nb_uuid = uuid.v4().toUpperCase() + + var nb = { + 'state': 'editing', + 'title': '', + 'uuid': nb_uuid, + 'notes': 0 + } + + this.createNewNotebook(nb, () => { + var nbs = this.state.notebooks + nbs.splice(0, 0, nb) + this.setState({notebooks: nbs}, () => { + this.refs['textField0'].focus() + }) + }) + }; newNotebookTyped = (i) => { @@ -218,6 +316,7 @@ export default class LibraryNav extends React.Component { }; menuItemClicked = (i, ev) => { + this.refs.notebookList.setIndex(-1) var item = this.state.navItems[i] var type = 'leftClick' var nativeEvent = ev.nativeEvent @@ -232,14 +331,9 @@ export default class LibraryNav extends React.Component { var nbs = this.state.notebooks nbs[i].state = 'editing' this.setState({notebooks: nbs}, () => { - console.log(i) - var nbsx = this.state.notebooks - this.props.closeContextMenu() - //this.refs['textField'+i].focus() - //$(ReactDOM.findDOMNode(this.refs['listItem'+i])).click() - //console.log(node.find(':input')) - //node.find(':input')[0].focus() + this.refs['textField'+i].focus() }) + this.props.closeContextMenu() } deleteTapped = (i) => { @@ -276,12 +370,63 @@ export default class LibraryNav extends React.Component { this.props.updateContextMenu(this.contextMenuItems(i)) this.props.openContextMenu(x, y) } + this.refs.mainList.setIndex(-1) + }; + + notebookList = () => { + return (
+ + {this.state.notebooks.map((notebook, i) =>{ + var l = null + + if (notebook.state == 'editing'){ + l = } + rightIcon={}> + + + + + } + else{ + l = } + rightIcon={} + /> + } + return l + })} +
) + + }; + + notebookIconTapped = (i, ev) => { }; render(){ return (
- + {this.state.navItems.map((item, i) => { return
- +
NoteBooks
}> -
- - {this.state.notebooks.map((notebook, i) =>{ - var l = null - - if (notebook.state == 'editing'){ - l = } - leftIcon={} - rightIcon={} - /> - } - else{ - l = } - rightIcon={} - /> - } - return l - })} -
- + {this.notebookList()} +
) diff --git a/app/containers/App.jsx b/app/containers/App.jsx new file mode 100644 index 0000000..2d3c35b --- /dev/null +++ b/app/containers/App.jsx @@ -0,0 +1,122 @@ +import React from 'react' +import getMuiTheme from 'material-ui/lib/styles/getMuiTheme' +import LibraryNav from 'LibraryNav' +import Styles from 'material-ui/lib/styles' +import Rethink from 'rethinkdbdash' +import mui from 'material-ui' +import * as ContextMenuActions from '../actions' + +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + + +const { + Popover, + Menu, + MenuItem} = mui + +const DefaultRawTheme = Styles.LightRawTheme + +let r = Rethink({ + db: 'technote', + servers: [ + {host: '162.243.255.144', + port: 28015} + ]}) + +function createTables(){ + r.tableCreate('notes').run() + .then(function(){ + r.table('notes').indexCreate('account_id').run() + }) + .error(function(){}) + + r.tableCreate('accounts').run().error(function(){}) +} + +class App extends React.Component { + constructor(props, context){ + super(props, context) + createTables() + this.state = { + entries: [], + } + } + + static get childContextTypes(){ + return {muiTheme: React.PropTypes.object} + } + + getChildContext() { + return { + muiTheme: getMuiTheme(DefaultRawTheme) + } + } + + entriesTapped = () => { + r.table('notes').getAll('jyapayne@gmail.com', {index: 'account_id'}).run().then( + function(notes){ + } + ) + }; + + handleRequestClose = () => { + this.props.contextMenuActions.closeContextMenu() + }; + + render() { + + const { contextMenu, contextMenuActions } = this.props + return ( +
+
+ + + {contextMenu.items.map(function (el, i){ + return el; + })} + + + + +
+ ) + } +} + +App.propTypes = { + contextMenu: React.PropTypes.object.isRequired, + contextMenuActions: React.PropTypes.object.isRequired +} + +function mapStateToProps(state) { + return { + contextMenu: state.contextMenu + } +} + +function mapDispatchToProps(dispatch) { + return { + contextMenuActions: bindActionCreators(ContextMenuActions, dispatch) + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(App) diff --git a/app/enhance/SelectableEnhance.jsx b/app/enhance/SelectableEnhance.jsx new file mode 100644 index 0000000..30b8f7d --- /dev/null +++ b/app/enhance/SelectableEnhance.jsx @@ -0,0 +1,142 @@ +import React from 'react'; +import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; +import StylePropable from 'material-ui/lib/mixins/style-propable'; +import ColorManipulator from 'material-ui/lib/utils/color-manipulator'; + +export const SelectableContainerEnhance = (Component) => { + const composed = React.createClass({ + + displayName: `Selectable${Component.displayName}`, + + propTypes: { + children: React.PropTypes.node, + selectedItemStyle: React.PropTypes.object, + valueLink: React.PropTypes.shape({ + value: React.PropTypes.any, + requestChange: React.PropTypes.func, + }).isRequired, + }, + + contextTypes: { + muiTheme: React.PropTypes.object, + }, + + childContextTypes: { + muiTheme: React.PropTypes.object, + }, + + mixins: [ + StylePropable, + ], + + getInitialState() { + return { + muiTheme: this.context.muiTheme || getMuiTheme(), + }; + }, + + getChildContext() { + return { + muiTheme: this.state.muiTheme, + }; + }, + + componentWillReceiveProps(nextProps, nextContext) { + let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; + this.setState({muiTheme: newMuiTheme}); + }, + + getValueLink: function(props) { + return props.valueLink || { + value: props.value, + requestChange: props.onChange, + }; + }, + + extendChild(child, styles, selectedItemStyle) { + if (child && child.type && child.type.displayName === 'ListItem') { + let selected = this.isChildSelected(child, this.props); + let selectedChildrenStyles = {}; + if (selected) { + selectedChildrenStyles = this.mergeStyles(styles, selectedItemStyle); + } + + let mergedChildrenStyles = this.mergeStyles(child.props.style || {}, selectedChildrenStyles); + + this.keyIndex += 1; + + return React.cloneElement(child, { + onTouchTap: (e) => { + this.handleItemTouchTap(e, child); + if (child.props.onTouchTap) { + child.props.onTouchTap(e); + } + }, + key: this.keyIndex, + style: mergedChildrenStyles, + nestedItems: child.props.nestedItems.map((child) => this.extendChild(child, styles, selectedItemStyle)), + initiallyOpen: this.isInitiallyOpen(child), + }); + } else { + var Type = child.type + return {React.Children.map(child.props.children, (childx) => this.extendChild(childx, styles, selectedItemStyle))}; + } + }, + + isInitiallyOpen(child) { + if (child.props.initiallyOpen) { + return child.props.initiallyOpen; + } + return this.hasSelectedDescendant(false, child); + }, + + hasSelectedDescendant(previousValue, child) { + if (React.isValidElement(child) && child.props.nestedItems && child.props.nestedItems.length > 0) { + return child.props.nestedItems.reduce(this.hasSelectedDescendant, previousValue); + } + return previousValue || this.isChildSelected(child, this.props); + }, + + isChildSelected(child, props) { + let itemValue = this.getValueLink(props).value; + let childValue = child.props.value; + + return (itemValue === childValue); + }, + + handleItemTouchTap(e, item) { + let valueLink = this.getValueLink(this.props); + let itemValue = item.props.value; + let menuValue = valueLink.value; + if ( itemValue !== menuValue) { + valueLink.requestChange(e, itemValue); + } + }, + + render() { + const {children, selectedItemStyle} = this.props; + this.keyIndex = 0; + let styles = {}; + + if (!selectedItemStyle) { + let textColor = this.state.muiTheme.rawTheme.palette.textColor; + let selectedColor = ColorManipulator.fade(textColor, 0.2); + styles = { + backgroundColor: selectedColor, + }; + } + + let newChildren = React.Children.map(children, (child) => this.extendChild(child, styles, selectedItemStyle)); + + return ( + + {newChildren} + + ); + }, + }); + + return composed; +}; + +export default SelectableContainerEnhance; diff --git a/app/store/configureStore.jsx b/app/store/configureStore.jsx new file mode 100644 index 0000000..f5cc691 --- /dev/null +++ b/app/store/configureStore.jsx @@ -0,0 +1,8 @@ +import { createStore } from 'redux' +import rootReducer from '../reducers' + +export default function configureStore(initialState) { + const store = createStore(rootReducer, initialState) + return store +} + diff --git a/app/utils.jsx b/app/utils.jsx new file mode 100644 index 0000000..d785862 --- /dev/null +++ b/app/utils.jsx @@ -0,0 +1,10 @@ +import path from 'path-extra' + +export function getAppDataPath(){ + return path.datadir(APP_NAME) +} + +export function getNotebookPath(notebook){ + var notePath = getAppDataPath() + return path.join(notePath, notebook.uuid+'.qvnotebook') +} diff --git a/index.html b/index.html index 72abcf6..a862791 100644 --- a/index.html +++ b/index.html @@ -13,6 +13,7 @@
- + diff --git a/index.jsx b/index.jsx new file mode 100644 index 0000000..6a283d6 --- /dev/null +++ b/index.jsx @@ -0,0 +1,17 @@ +import React from 'react' +import injectTapEventPlugin from "react-tap-event-plugin" +injectTapEventPlugin() +import { render } from 'react-dom' +import { Provider } from 'react-redux' +import App from './app/containers/App' +import configureStore from './app/store/configureStore' + +const store = configureStore() + +render( + + + , + document.getElementById('main') +) + diff --git a/package.json b/package.json index 8caee7d..a9027d1 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,14 @@ "linux-x64": true }, "dependencies": { + "glob": "^7.0.0", + "jsonfile": "^2.2.3", "material-ui": "^0.14.4", "materialize-css": "^0.97.5", + "mkdirp": "^0.5.1", "moment": "^2.11.2", + "node-uuid": "^1.4.7", + "path-extra": "^3.0.0", "react": "^0.14.7", "react-dom": "^0.14.7", "react-quill": "^0.4.1", diff --git a/style.css b/style.css index dd76873..38a83e3 100644 --- a/style.css +++ b/style.css @@ -11,6 +11,11 @@ body, html{ .noselect{ -webkit-user-select: none; } + +.list > div:first-child{ + -webkit-user-select: none; +} + .inline{ display: inline-block; vertical-align: top; diff --git a/webpack.config.js b/webpack.config.js index 8dbbaf6..061afe5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,9 +1,9 @@ var path = require('path'); module.exports = { - entry: './app/main.jsx', + entry: './index.jsx', output: { - path: __dirname + "/dist", + path: __dirname + "/app/dist", filename: "bundle.js", sourceMapFilename: 'bundle.map' }, @@ -23,7 +23,8 @@ module.exports = { resolve:{ extensions: ['', '.js', '.jsx'], root: [ - path.resolve('./app/modules') + path.resolve('./app/components'), + path.resolve('./app') ] } };