From cd64ba9b619fe631003eed3f54b3ea87868f8d2e Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 6 Jun 2017 20:58:19 +0000 Subject: [PATCH] Allow running app either with node or react-native --- CliClient/.gitignore | 3 +- CliClient/app/src/base-model.js | 224 ---------- CliClient/app/src/base-service.js | 13 - CliClient/app/src/components/action-button.js | 73 ---- CliClient/app/src/components/checkbox.js | 44 -- CliClient/app/src/components/folder-list.js | 27 -- CliClient/app/src/components/item-list.js | 77 ---- CliClient/app/src/components/note-list.js | 26 -- CliClient/app/src/components/screen-header.js | 120 ------ .../app/src/components/screens/folder.js | 71 ---- .../app/src/components/screens/folders.js | 35 -- .../app/src/components/screens/loading.js | 31 -- CliClient/app/src/components/screens/login.js | 92 ----- CliClient/app/src/components/screens/note.js | 112 ----- CliClient/app/src/components/screens/notes.js | 82 ---- .../app/src/components/side-menu-content.js | 78 ---- CliClient/app/src/components/side-menu.js | 16 - CliClient/app/src/database.js | 390 ------------------ CliClient/app/src/geolocation.js | 34 -- CliClient/app/src/locale.js | 9 - CliClient/app/src/log.js | 40 -- CliClient/app/src/main.js | 24 -- CliClient/app/src/models/change.js | 106 ----- CliClient/app/src/models/folder.js | 91 ---- CliClient/app/src/models/note.js | 88 ---- CliClient/app/src/models/session.js | 11 - CliClient/app/src/models/setting.js | 124 ------ CliClient/app/src/package.json | 1 - CliClient/app/src/promise-chain.js | 10 - CliClient/app/src/registry.js | 47 --- CliClient/app/src/root.js | 300 -------------- .../app/src/services/note-folder-service.js | 69 ---- CliClient/app/src/services/session-service.js | 15 - CliClient/app/src/synchronizer.js | 181 -------- CliClient/app/src/uuid.js | 11 - CliClient/app/src/web-api.js | 133 ------ CliClient/run.sh | 7 +- ReactNativeClient/package.json | 2 + ReactNativeClient/src/database.js | 10 +- ReactNativeClient/src/env.js | 7 + ReactNativeClient/src/root.js | 4 +- ReactNativeClient/src/shim.js | 6 + ReactNativeClient/src/web-api.js | 8 + joplin.sublime-project | 3 + 44 files changed, 41 insertions(+), 2814 deletions(-) delete mode 100644 CliClient/app/src/base-model.js delete mode 100644 CliClient/app/src/base-service.js delete mode 100644 CliClient/app/src/components/action-button.js delete mode 100644 CliClient/app/src/components/checkbox.js delete mode 100644 CliClient/app/src/components/folder-list.js delete mode 100644 CliClient/app/src/components/item-list.js delete mode 100644 CliClient/app/src/components/note-list.js delete mode 100644 CliClient/app/src/components/screen-header.js delete mode 100644 CliClient/app/src/components/screens/folder.js delete mode 100644 CliClient/app/src/components/screens/folders.js delete mode 100644 CliClient/app/src/components/screens/loading.js delete mode 100644 CliClient/app/src/components/screens/login.js delete mode 100644 CliClient/app/src/components/screens/note.js delete mode 100644 CliClient/app/src/components/screens/notes.js delete mode 100644 CliClient/app/src/components/side-menu-content.js delete mode 100644 CliClient/app/src/components/side-menu.js delete mode 100644 CliClient/app/src/database.js delete mode 100644 CliClient/app/src/geolocation.js delete mode 100644 CliClient/app/src/locale.js delete mode 100644 CliClient/app/src/log.js delete mode 100644 CliClient/app/src/main.js delete mode 100644 CliClient/app/src/models/change.js delete mode 100644 CliClient/app/src/models/folder.js delete mode 100644 CliClient/app/src/models/note.js delete mode 100644 CliClient/app/src/models/session.js delete mode 100644 CliClient/app/src/models/setting.js delete mode 100644 CliClient/app/src/package.json delete mode 100644 CliClient/app/src/promise-chain.js delete mode 100644 CliClient/app/src/registry.js delete mode 100644 CliClient/app/src/root.js delete mode 100644 CliClient/app/src/services/note-folder-service.js delete mode 100644 CliClient/app/src/services/session-service.js delete mode 100644 CliClient/app/src/synchronizer.js delete mode 100644 CliClient/app/src/uuid.js delete mode 100644 CliClient/app/src/web-api.js mode change 100644 => 100755 CliClient/run.sh create mode 100644 ReactNativeClient/src/env.js create mode 100644 ReactNativeClient/src/shim.js diff --git a/CliClient/.gitignore b/CliClient/.gitignore index 16d8d68f4c..f9ac9fa56a 100644 --- a/CliClient/.gitignore +++ b/CliClient/.gitignore @@ -1,2 +1,3 @@ build/ -node_modules/ \ No newline at end of file +node_modules/ +app/src \ No newline at end of file diff --git a/CliClient/app/src/base-model.js b/CliClient/app/src/base-model.js deleted file mode 100644 index d4d9691b36..0000000000 --- a/CliClient/app/src/base-model.js +++ /dev/null @@ -1,224 +0,0 @@ -import { Log } from 'src/log.js'; -import { Database } from 'src/database.js'; -import { uuid } from 'src/uuid.js'; - -class BaseModel { - - static tableName() { - throw new Error('Must be overriden'); - } - - static useUuid() { - return false; - } - - static itemType() { - throw new Error('Must be overriden'); - } - - static trackChanges() { - return false; - } - - static byId(items, id) { - for (let i = 0; i < items.length; i++) { - if (items[i].id == id) return items[i]; - } - return null; - } - - static hasField(name) { - let fields = this.fieldNames(); - return fields.indexOf(name) >= 0; - } - - static fieldNames() { - return this.db().tableFieldNames(this.tableName()); - } - - static fields() { - return this.db().tableFields(this.tableName()); - } - - static new() { - let fields = this.fields(); - let output = {}; - for (let i = 0; i < fields.length; i++) { - let f = fields[i]; - output[f.name] = f.default; - } - return output; - } - - static fromApiResult(apiResult) { - let fieldNames = this.fieldNames(); - let output = {}; - for (let i = 0; i < fieldNames.length; i++) { - let f = fieldNames[i]; - output[f] = f in apiResult ? apiResult[f] : null; - } - return output; - } - - static modOptions(options) { - if (!options) { - options = {}; - } else { - options = Object.assign({}, options); - } - if (!('trackChanges' in options)) options.trackChanges = true; - if (!('isNew' in options)) options.isNew = 'auto'; - return options; - } - - static load(id) { - return this.db().selectOne('SELECT * FROM ' + this.tableName() + ' WHERE id = ?', [id]); - } - - static applyPatch(model, patch) { - model = Object.assign({}, model); - for (let n in patch) { - if (!patch.hasOwnProperty(n)) continue; - model[n] = patch[n]; - } - return model; - } - - static diffObjects(oldModel, newModel) { - let output = {}; - for (let n in newModel) { - if (!newModel.hasOwnProperty(n)) continue; - if (!(n in oldModel) || newModel[n] !== oldModel[n]) { - output[n] = newModel[n]; - } - } - return output; - } - - static saveQuery(o, isNew = 'auto') { - if (isNew == 'auto') isNew = !o.id; - - let temp = {} - let fieldNames = this.fieldNames(); - for (let i = 0; i < fieldNames.length; i++) { - let n = fieldNames[i]; - if (n in o) temp[n] = o[n]; - } - o = temp; - - let query = ''; - let itemId = o.id; - - if (!o.updated_time && this.hasField('updated_time')) { - o.updated_time = Math.round((new Date()).getTime() / 1000); - } - - if (isNew) { - if (this.useUuid() && !o.id) { - o = Object.assign({}, o); - itemId = uuid.create(); - o.id = itemId; - } - - if (!o.created_time && this.hasField('created_time')) { - o.created_time = Math.round((new Date()).getTime() / 1000); - } - - query = Database.insertQuery(this.tableName(), o); - } else { - let where = { id: o.id }; - let temp = Object.assign({}, o); - delete temp.id; - query = Database.updateQuery(this.tableName(), temp, where); - } - - query.id = itemId; - - Log.info('Saving', o); - - return query; - } - - static save(o, options = null) { - options = this.modOptions(options); - - let isNew = options.isNew == 'auto' ? !o.id : options.isNew; - let query = this.saveQuery(o, isNew); - - return this.db().transaction((tx) => { - tx.executeSql(query.sql, query.params); - - if (options.trackChanges && this.trackChanges()) { - // Cannot import this class the normal way due to cyclical dependencies between Change and BaseModel - // which are not handled by React Native. - const { Change } = require('src/models/change.js'); - - if (isNew) { - let change = Change.newChange(); - change.type = Change.TYPE_CREATE; - change.item_id = query.id; - change.item_type = this.itemType(); - - let changeQuery = Change.saveQuery(change); - tx.executeSql(changeQuery.sql, changeQuery.params); - } else { - for (let n in o) { - if (!o.hasOwnProperty(n)) continue; - if (n == 'id') continue; - - let change = Change.newChange(); - change.type = Change.TYPE_UPDATE; - change.item_id = query.id; - change.item_type = this.itemType(); - change.item_field = n; - - let changeQuery = Change.saveQuery(change); - tx.executeSql(changeQuery.sql, changeQuery.params); - } - } - } - }).then((r) => { - o = Object.assign({}, o); - o.id = query.id; - return o; - }).catch((error) => { - Log.error('Cannot save model', error); - }); - } - - static delete(id, options = null) { - options = this.modOptions(options); - - if (!id) { - Log.warn('Cannot delete object without an ID'); - return; - } - - return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => { - if (options.trackChanges && this.trackChanges()) { - const { Change } = require('src/models/change.js'); - - let change = Change.newChange(); - change.type = Change.TYPE_DELETE; - change.item_id = id; - change.item_type = this.itemType(); - - return Change.save(change); - } - }); - } - - static db() { - if (!this.db_) throw new Error('Accessing database before it has been initialised'); - return this.db_; - } - -} - -BaseModel.ITEM_TYPE_NOTE = 1; -BaseModel.ITEM_TYPE_FOLDER = 2; -BaseModel.tableInfo_ = null; -BaseModel.tableKeys_ = null; -BaseModel.db_ = null; - -export { BaseModel }; \ No newline at end of file diff --git a/CliClient/app/src/base-service.js b/CliClient/app/src/base-service.js deleted file mode 100644 index d6d0fcd8a2..0000000000 --- a/CliClient/app/src/base-service.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Registry } from 'src/registry.js'; - -class BaseService { - - constructor() {} - - api() { - return Registry.api(); - } - -} - -export { BaseService }; \ No newline at end of file diff --git a/CliClient/app/src/components/action-button.js b/CliClient/app/src/components/action-button.js deleted file mode 100644 index 171a57b5fd..0000000000 --- a/CliClient/app/src/components/action-button.js +++ /dev/null @@ -1,73 +0,0 @@ -import React, { Component } from 'react'; -import { StyleSheet } from 'react-native'; -import Icon from 'react-native-vector-icons/Ionicons'; -import ReactNativeActionButton from 'react-native-action-button'; -import { connect } from 'react-redux' -import { Log } from 'src/log.js' - -const styles = StyleSheet.create({ - actionButtonIcon: { - fontSize: 20, - height: 22, - color: 'white', - }, -}); - -class ActionButtonComponent extends React.Component { - - newTodo_press() { - this.props.dispatch({ - type: 'Navigation/NAVIGATE', - routeName: 'Note', - noteId: null, - folderId: this.props.parentFolderId, - itemType: 'todo', - }); - } - - newNote_press() { - this.props.dispatch({ - type: 'Navigation/NAVIGATE', - routeName: 'Note', - noteId: null, - folderId: this.props.parentFolderId, - itemType: 'note', - }); - } - - newFolder_press() { - this.props.dispatch({ - type: 'Navigation/NAVIGATE', - routeName: 'Folder', - folderId: null, - }); - } - - render() { - return ( - - - { this.newTodo_press() }}> - - - - { this.newNote_press() }}> - - - - { this.newFolder_press() }}> - - - - - ); - } -} - -const ActionButton = connect( - (state) => { - return {}; - } -)(ActionButtonComponent) - -export { ActionButton }; \ No newline at end of file diff --git a/CliClient/app/src/components/checkbox.js b/CliClient/app/src/components/checkbox.js deleted file mode 100644 index 9a876d7e7c..0000000000 --- a/CliClient/app/src/components/checkbox.js +++ /dev/null @@ -1,44 +0,0 @@ -import React, { Component } from 'react'; -import { StyleSheet, TouchableHighlight } from 'react-native'; -import Icon from 'react-native-vector-icons/Ionicons'; - -const styles = StyleSheet.create({ - checkboxIcon: { - fontSize: 20, - height: 22, - marginRight: 10, - }, -}); - -class Checkbox extends Component { - - constructor() { - super(); - this.state = { - checked: false, - } - } - - componentWillMount() { - this.state = { checked: this.props.checked }; - } - - onPress() { - let newChecked = !this.state.checked; - this.setState({ checked: newChecked }); - if (this.props.onChange) this.props.onChange(newChecked); - } - - render() { - const iconName = this.state.checked ? 'md-checkbox-outline' : 'md-square-outline'; - - return ( - { this.onPress() }} style={{justifyContent: 'center', alignItems: 'center'}}> - - - ); - } - -} - -export { Checkbox }; \ No newline at end of file diff --git a/CliClient/app/src/components/folder-list.js b/CliClient/app/src/components/folder-list.js deleted file mode 100644 index e1a56ab5d0..0000000000 --- a/CliClient/app/src/components/folder-list.js +++ /dev/null @@ -1,27 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux' -import { ListView, Text, TouchableHighlight } from 'react-native'; -import { Log } from 'src/log.js'; -import { ItemListComponent } from 'src/components/item-list.js'; -import { Note } from 'src/models/note.js'; -import { Folder } from 'src/models/folder.js'; -import { _ } from 'src/locale.js'; -import { NoteFolderService } from 'src/services/note-folder-service.js'; - -class FolderListComponent extends ItemListComponent { - - listView_itemPress(folderId) { - NoteFolderService.openNoteList(folderId); - } - -} - -const FolderList = connect( - (state) => { - return { - items: state.folders, - }; - } -)(FolderListComponent) - -export { FolderList }; \ No newline at end of file diff --git a/CliClient/app/src/components/item-list.js b/CliClient/app/src/components/item-list.js deleted file mode 100644 index e4eacb82b7..0000000000 --- a/CliClient/app/src/components/item-list.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux' -import { ListView, Text, TouchableHighlight, Switch, View } from 'react-native'; -import { Log } from 'src/log.js'; -import { _ } from 'src/locale.js'; -import { Checkbox } from 'src/components/checkbox.js'; -import { NoteFolderService } from 'src/services/note-folder-service.js'; -import { Note } from 'src/models/note.js'; - -class ItemListComponent extends Component { - - constructor() { - super(); - const ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => { return r1 !== r2; } - }); - this.state = { - dataSource: ds, - items: [], - selectedItemIds: [], - }; - } - - componentWillMount() { - const newDataSource = this.state.dataSource.cloneWithRows(this.props.items); - this.state = { dataSource: newDataSource }; - } - - componentWillReceiveProps(newProps) { - // https://stackoverflow.com/questions/38186114/react-native-redux-and-listview - this.setState({ - dataSource: this.state.dataSource.cloneWithRows(newProps.items), - }); - } - - todoCheckbox_change(itemId, checked) { - NoteFolderService.setField('note', itemId, 'todo_completed', checked); - - // Note.load(itemId).then((oldNote) => { - // let newNote = Object.assign({}, oldNote); - // newNote.todo_completed = checked; - // return NoteFolderService.save('note', newNote, oldNote); - // }); - } - - listView_itemPress(itemId) {} - - render() { - let renderRow = (item) => { - let onPress = () => { - this.listView_itemPress(item.id); - } - let onLongPress = () => { - this.listView_itemLongPress(item.id); - } - - return ( - - - { !!Number(item.is_todo) && { this.todoCheckbox_change(item.id, checked) }}/> }{item.title} [{item.id}] - - - ); - } - - // `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39 - return ( - - ); - } -} - -export { ItemListComponent }; \ No newline at end of file diff --git a/CliClient/app/src/components/note-list.js b/CliClient/app/src/components/note-list.js deleted file mode 100644 index 077ef9f526..0000000000 --- a/CliClient/app/src/components/note-list.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux' -import { ListView, Text, TouchableHighlight } from 'react-native'; -import { Log } from 'src/log.js'; -import { ItemListComponent } from 'src/components/item-list.js'; -import { _ } from 'src/locale.js'; - -class NoteListComponent extends ItemListComponent { - - listView_itemPress(noteId) { - this.props.dispatch({ - type: 'Navigation/NAVIGATE', - routeName: 'Note', - noteId: noteId, - }); - } - -} - -const NoteList = connect( - (state) => { - return { items: state.notes }; - } -)(NoteListComponent) - -export { NoteList }; \ No newline at end of file diff --git a/CliClient/app/src/components/screen-header.js b/CliClient/app/src/components/screen-header.js deleted file mode 100644 index b95facefdc..0000000000 --- a/CliClient/app/src/components/screen-header.js +++ /dev/null @@ -1,120 +0,0 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux' -import { View, Text, Button, StyleSheet } from 'react-native'; -import { Log } from 'src/log.js'; -import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu'; -import { _ } from 'src/locale.js'; -import { Setting } from 'src/models/setting.js'; - -const styles = StyleSheet.create({ - divider: { - marginVertical: 5, - marginHorizontal: 2, - borderBottomWidth: 1, - borderColor: '#ccc' - }, -}); - -class ScreenHeaderComponent extends Component { - - showBackButton() { - // Note: this is hardcoded for now because navigation.state doesn't tell whether - // it's possible to go back or not. Maybe it's possible to get this information - // from somewhere else. - return this.props.navState.routeName != 'Notes'; - } - - sideMenuButton_press() { - this.props.dispatch({ type: 'SIDE_MENU_TOGGLE' }); - } - - backButton_press() { - this.props.dispatch({ type: 'Navigation/BACK' }); - } - - menu_select(value) { - if (typeof(value) == 'function') { - value(); - } - } - - menu_login() { - this.props.dispatch({ - type: 'Navigation/NAVIGATE', - routeName: 'Login', - }); - } - - menu_logout() { - let user = { email: null, session: null }; - Setting.setObject('user', user); - this.props.dispatch({ - type: 'USER_SET', - user: user, - }); - } - - render() { - let key = 0; - let menuOptionComponents = []; - for (let i = 0; i < this.props.menuOptions.length; i++) { - let o = this.props.menuOptions[i]; - menuOptionComponents.push( - - {o.title} - ); - } - - if (menuOptionComponents.length) { - menuOptionComponents.push(); - } - - if (this.props.user && this.props.user.session) { - menuOptionComponents.push( - - {_('Logout')} - ); - } else { - menuOptionComponents.push( - - {_('Login')} - ); - } - - menuOptionComponents.push( - - {_('Configuration')} - ); - - let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName); - - return ( - - - {title} - - - - - - { menuOptionComponents } - - - - ); - } - -} - -ScreenHeaderComponent.defaultProps = { - menuOptions: [], -}; - -const ScreenHeader = connect( - (state) => { - return { user: state.user }; - } -)(ScreenHeaderComponent) - -export { ScreenHeader }; \ No newline at end of file diff --git a/CliClient/app/src/components/screens/folder.js b/CliClient/app/src/components/screens/folder.js deleted file mode 100644 index b7f4072068..0000000000 --- a/CliClient/app/src/components/screens/folder.js +++ /dev/null @@ -1,71 +0,0 @@ -import React, { Component } from 'react'; -import { View, Button, TextInput } from 'react-native'; -import { connect } from 'react-redux' -import { Log } from 'src/log.js' -import { Folder } from 'src/models/folder.js' -import { ScreenHeader } from 'src/components/screen-header.js'; -import { NoteFolderService } from 'src/services/note-folder-service.js'; - -class FolderScreenComponent extends React.Component { - - static navigationOptions(options) { - return { header: null }; - } - - constructor() { - super(); - this.state = { folder: Folder.new() }; - this.originalFolder = null; - } - - componentWillMount() { - if (!this.props.folderId) { - this.setState({ folder: Folder.new() }); - } else { - Folder.load(this.props.folderId).then((folder) => { - this.originalFolder = Object.assign({}, folder); - this.setState({ folder: folder }); - }); - } - } - - folderComponent_change(propName, propValue) { - this.setState((prevState, props) => { - let folder = Object.assign({}, prevState.folder); - folder[propName] = propValue; - return { folder: folder } - }); - } - - title_changeText(text) { - this.folderComponent_change('title', text); - } - - saveFolderButton_press() { - NoteFolderService.save('folder', this.state.folder, this.originalFolder).then((folder) => { - this.originalFolder = Object.assign({}, folder); - this.setState({ folder: folder }); - }); - } - - render() { - return ( - - - -