1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Change folder from RN

This commit is contained in:
Laurent Cozic 2017-07-16 17:06:05 +01:00
parent 24f61177d1
commit c2ba2105ff
17 changed files with 139 additions and 64 deletions

View File

@ -260,8 +260,8 @@ class Application {
} else if (syncTarget == 'memory') {
fileApi = new FileApi('joplin', new FileApiDriverMemory());
fileApi.setLogger(this.logger_);
} else if (syncTarget == 'file') {
let syncDir = Setting.value('sync.local.path');
} else if (syncTarget == 'filesystem') {
let syncDir = Setting.value('sync.filesystem.path');
if (!syncDir) syncDir = Setting.value('profileDir') + '/sync';
this.vorpal().log(_('Synchronizing with directory "%s"', syncDir));
await fs.mkdirp(syncDir, 0o755);

View File

@ -1,4 +1,6 @@
import { BaseCommand } from './base-command.js';
import { Database } from 'lib/database.js';
import { Setting } from 'lib/models/setting.js';
import { _ } from 'lib/locale.js';
import { ReportService } from 'lib/services/report.js';
@ -14,7 +16,7 @@ class Command extends BaseCommand {
async action(args) {
let service = new ReportService();
let report = await service.status();
let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target')));
for (let i = 0; i < report.length; i++) {
let section = report[i];

View File

@ -42,7 +42,7 @@ async function createClients() {
for (let clientId = 0; clientId < 2; clientId++) {
let client = createClient(clientId);
promises.push(fs.remove(client.profileDir));
promises.push(execCommand(client, 'config sync.target local').then(() => { return execCommand(client, 'config sync.local.path ' + syncDir); }));
promises.push(execCommand(client, 'config sync.target filesystem').then(() => { return execCommand(client, 'config sync.filesystem.path ' + syncDir); }));
output.push(client);
}

View File

@ -7,7 +7,7 @@
"url": "https://github.com/laurent22/joplin"
},
"url": "git://github.com/laurent22/joplin.git",
"version": "0.8.43",
"version": "0.8.44",
"bin": {
"joplin": "./main_launcher.js"
},

View File

@ -75,8 +75,6 @@ describe('Synchronizer', function() {
let note = await Note.save({ title: "un", parent_id: folder.id });
await synchronizer().start();
await sleep(0.1);
await Note.save({ title: "un UPDATE", id: note.id });
let all = await allItems();

View File

@ -90,8 +90,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion 16
targetSdkVersion 22
versionCode 16
versionName "0.9.3"
versionCode 18
versionName "0.9.5"
ndk {
abiFilters "armeabi-v7a", "x86"
}

View File

@ -218,6 +218,7 @@ class BaseModel {
}
query.id = modelId;
query.modObject = o;
return query;
}
@ -241,6 +242,8 @@ class BaseModel {
return this.db().transactionExecBatch(queries).then(() => {
o = Object.assign({}, o);
o.id = modelId;
if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time;
if ('created_time' in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time;
o = this.addModelMd(o);
return this.filter(o);
}).catch((error) => {

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native';
import { View, Text, Button, StyleSheet, TouchableOpacity, Picker } from 'react-native';
import { Log } from 'lib/log.js';
import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu';
import { _ } from 'lib/locale.js';
@ -150,14 +150,33 @@ class ScreenHeaderComponent extends Component {
<Text>{_('Status')}</Text>
</MenuOption>);
const createTitleComponent = () => {
const p = this.props.titlePicker;
if (p) {
let items = [];
for (let i = 0; i < p.items.length; i++) {
let item = p.items[i];
items.push(<Picker.Item label={item.label} value={item.value} key={item.value} />);
}
return (
<Picker style={{height: 30, flex:1}} selectedValue={p.selectedValue} onValueChange={(itemValue, itemIndex) => { if (p.onValueChange) p.onValueChange(itemValue, itemIndex); }}>
{ items }
</Picker>
);
} else {
let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName);
return <Text style={{ flex:1, marginLeft: 10 }}>{title}</Text>
}
}
const titleComp = createTitleComponent();
return (
<View style={{ flexDirection: 'row', paddingLeft: 10, paddingTop: 10, paddingBottom: 10, paddingRight: 0, backgroundColor: '#ffffff', alignItems: 'center' }} >
{ sideMenuButton(styles, () => this.sideMenuButton_press()) }
{ backButton(styles, () => this.backButton_press(), !this.props.historyCanGoBack) }
{ saveButton(styles, () => { if (this.props.onSaveButtonPress) this.props.onSaveButtonPress() }, this.props.saveButtonDisabled === true, this.props.showSaveButton === true) }
<Text style={{ flex:1, marginLeft: 10 }} >{title}</Text>
{ titleComp }
<Menu onSelect={(value) => this.menu_select(value)}>
<MenuTrigger>
<Text style={{ fontSize: 25 }}> &#8942; </Text>

View File

@ -103,8 +103,12 @@ class NoteScreenComponent extends BaseScreenComponent {
return Folder.load(folderId);
}
async refreshFolder() {
async refreshFolder(folderId = null) {
if (!folderId) {
this.setState({ folder: await this.currentFolder() });
} else {
this.setState({ folder: await Folder.load(folderId) });
}
}
noteComponent_change(propName, propValue) {
@ -183,23 +187,27 @@ class NoteScreenComponent extends BaseScreenComponent {
];
}
async todoCheckbox_change(checked) {
async saveOneProperty(name, value) {
let note = Object.assign({}, this.state.note);
const todoCompleted = checked ? time.unixMs() : 0;
if (note.id) {
note = await Note.save({ id: note.id, todo_completed: todoCompleted });
let toSave = { id: note.id };
toSave[name] = value;
toSave = await Note.save(toSave);
note[name] = toSave[name];
this.setState({
lastSavedNote: Object.assign({}, note),
note: note,
});
} else {
note.todo_completed = todoCompleted;
note[name] = value;
this.setState({ note: note });
}
}
async todoCheckbox_change(checked) {
return this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
}
render() {
@ -271,9 +279,6 @@ class NoteScreenComponent extends BaseScreenComponent {
);
}
let headerTitle = ''
if (folder) headerTitle = folder.title;
const renderActionButton = () => {
let buttons = [];
@ -290,6 +295,15 @@ class NoteScreenComponent extends BaseScreenComponent {
return <ActionButton multiStates={true} buttons={buttons} buttonIndex={0} />
}
const titlePickerItems = () => {
let output = [];
for (let i = 0; i < this.props.folders.length; i++) {
let f = this.props.folders[i];
output.push({ label: f.title, value: f.id });
}
return output;
}
const actionButtonComp = renderActionButton();
let showSaveButton = this.state.mode == 'edit';
@ -298,7 +312,23 @@ class NoteScreenComponent extends BaseScreenComponent {
return (
<View style={this.styles().screen}>
<ScreenHeader
title={headerTitle}
titlePicker={{
items: titlePickerItems(),
selectedValue: folder ? folder.id : null,
onValueChange: async (itemValue, itemIndex) => {
let note = Object.assign({}, this.state.note);
if (note.id) await Note.moveToFolder(note.id, itemValue);
note.parent_id = itemValue;
const folder = await Folder.load(note.parent_id);
this.setState({
lastSavedNote: Object.assign({}, note),
note: note,
folder: folder,
});
}
}}
navState={this.props.navigation.state}
menuOptions={this.menuOptions()}
showSaveButton={showSaveButton}
@ -324,6 +354,7 @@ const NoteScreen = connect(
noteId: state.selectedNoteId,
folderId: state.selectedFolderId,
itemType: state.selectedItemType,
folders: state.folders,
};
}
)(NoteScreenComponent)

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { ListView, View, Text, Button } from 'react-native';
import { Setting } from 'lib/models/setting.js';
import { connect } from 'react-redux'
import { Log } from 'lib/log.js'
import { reg } from 'lib/registry.js'
@ -7,6 +8,7 @@ import { ScreenHeader } from 'lib/components/screen-header.js';
import { time } from 'lib/time-utils'
import { Logger } from 'lib/logger.js';
import { BaseItem } from 'lib/models/base-item.js';
import { Database } from 'lib/database.js';
import { Folder } from 'lib/models/folder.js';
import { ReportService } from 'lib/services/report.js';
import { _ } from 'lib/locale.js';
@ -31,7 +33,7 @@ class StatusScreenComponent extends BaseScreenComponent {
async resfreshScreen() {
let service = new ReportService();
let report = await service.status();
let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target')));
this.setState({ report: report });
}

View File

@ -151,7 +151,7 @@ class Database {
}
if (type == 'syncTarget') {
if (s == 'memory') return 1;
if (s == 'file') return 2;
if (s == 'filesystem') return 2;
if (s == 'onedrive') return 3;
}
throw new Error('Unknown enum type or value: ' + type + ', ' + s);
@ -160,7 +160,7 @@ class Database {
static enumName(type, id) {
if (type == 'syncTarget') {
if (id === 1) return 'memory';
if (id === 2) return 'file';
if (id === 2) return 'filesystem';
if (id === 3) return 'onedrive';
}
throw new Error('Unknown enum type or id: ' + type + ', ' + id);

View File

@ -10,7 +10,7 @@ class FileApiDriverLocal {
}
syncTargetName() {
return 'file';
return 'filesystem';
}
fsErrorToJsError_(error) {

View File

@ -192,6 +192,17 @@ class JoplinDatabase extends Database {
for (let initLoopCount = 1; initLoopCount <= 2; initLoopCount++) {
try {
// await this.exec('DROP TABLE folders');
// await this.exec('DROP TABLE notes');
// await this.exec('DROP TABLE deleted_items');
// await this.exec('DROP TABLE tags');
// await this.exec('DROP TABLE note_tags');
// await this.exec('DROP TABLE resources');
// await this.exec('DROP TABLE settings');
// await this.exec('DROP TABLE table_fields');
// await this.exec('DROP TABLE version');
// await this.exec('DROP TABLE sync_items');
let row = await this.selectOne('SELECT * FROM version LIMIT 1');
this.logger().info('Current database version', row);

View File

@ -32,14 +32,14 @@ class BaseItem extends BaseModel {
throw new Error('Invalid class name: ' + name);
}
static async syncedCount() {
// TODO
return 0;
// const ItemClass = this.itemClass(this.modelType());
// let sql = 'SELECT count(*) as total FROM `' + ItemClass.tableName() + '` WHERE updated_time <= sync_time';
// if (this.modelType() == BaseModel.TYPE_NOTE) sql += ' AND is_conflict = 0';
// const r = await this.db().selectOne(sql);
// return r.total;
static async syncedCount(syncTarget) {
const ItemClass = this.itemClass(this.modelType());
const itemType = ItemClass.modelType();
// The fact that we don't check if the item_id still exist in the corresponding item table, means
// that the returned number might be innaccurate (for example if a sync operation was cancelled)
const sql = 'SELECT count(*) as total FROM sync_items WHERE sync_target = ? AND item_type = ?';
const r = await this.db().selectOne(sql, [ syncTarget, itemType ]);
return r.total;
}
static systemPath(itemOrId) {
@ -279,23 +279,6 @@ class BaseItem extends BaseModel {
}
throw new Error('Unreachable');
//return this.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
// let items = await this.getClass('Folder').modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit);
// if (items.length) return { hasMore: true, items: items };
// items = await this.getClass('Resource').modelSelectAll('SELECT * FROM resources WHERE sync_time < updated_time LIMIT ' + limit);
// if (items.length) return { hasMore: true, items: items };
// items = await this.getClass('Note').modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit);
// if (items.length) return { hasMore: true, items: items };
// items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit);
// if (items.length) return { hasMore: true, items: items };
// items = await this.getClass('NoteTag').modelSelectAll('SELECT * FROM note_tags WHERE sync_time < updated_time LIMIT ' + limit);
// return { hasMore: items.length >= limit, items: items };
}
static syncItemClassNames() {
@ -341,7 +324,10 @@ class BaseItem extends BaseModel {
const className = classNames[i];
const ItemClass = this.getClass(className);
queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (SELECT id FROM ' + ItemClass.tableName() + ')');
let selectSql = 'SELECT id FROM ' + ItemClass.tableName();
if (ItemClass.modelType() == this.TYPE_NOTE) selectSql += ' WHERE is_conflict = 0';
queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (' + selectSql + ')');
}
await this.db().transactionExecBatch(queries);

View File

@ -146,7 +146,7 @@ class Setting extends BaseModel {
Setting.defaults_ = {
'activeFolderId': { value: '', type: 'string', public: false },
'sync.onedrive.auth': { value: '', type: 'string', public: false },
'sync.local.path': { value: '', type: 'string', public: true },
'sync.filesystem.path': { value: '', type: 'string', public: true },
'sync.target': { value: 'onedrive', type: 'string', public: true },
'editor': { value: '', type: 'string', public: true },
};

View File

@ -6,7 +6,7 @@ import { _ } from 'lib/locale.js';
class ReportService {
async syncStatus() {
async syncStatus(syncTarget) {
let output = {
items: {},
total: {},
@ -19,8 +19,7 @@ class ReportService {
let ItemClass = BaseItem.getClass(d.className);
let o = {
total: await ItemClass.count(),
// synced: await ItemClass.syncedCount(), // TODO
synced: 0,
synced: await ItemClass.syncedCount(syncTarget),
};
output.items[d.className] = o;
itemCount += o.total;
@ -47,8 +46,8 @@ class ReportService {
return output;
}
async status() {
let r = await this.syncStatus();
async status(syncTarget) {
let r = await this.syncStatus(syncTarget);
let sections = [];
let section = {};

View File

@ -68,6 +68,17 @@ function historyCanGoBackTo(route) {
return true;
}
function reducerActionsAreSame(a1, a2) {
if (Object.getOwnPropertyNames(a1).length !== Object.getOwnPropertyNames(a2).length) return false;
for (let n in a1) {
if (!a1.hasOwnProperty(n)) continue;
if (a1[n] !== a2[n]) return false;
}
return true;
}
const reducer = (state = defaultState, action) => {
reg.logger().info('Reducer action', action.type);
@ -116,10 +127,16 @@ const reducer = (state = defaultState, action) => {
newState.selectedItemType = action.itemType;
}
newState.route = action;
// If the route *name* is the same (even if the other parameters are different), we
// overwrite the last route in the history with the current one. If the route name
// is different, we push a new history entry.
if (currentRouteName == action.routeName) {
if (navHistory.length) navHistory[navHistory.length - 1] = action;
// If the current screen is already the requested screen, don't do anything
} else {
newState.route = action;
if (action.routeName == 'Welcome') navHistory = [];
navHistory.push(action);
}
@ -151,20 +168,27 @@ const reducer = (state = defaultState, action) => {
// update it within the note array if it already exists.
case 'NOTES_UPDATE_ONE':
if (action.note.parent_id != state.selectedFolderId) break;
const modNote = action.note;
let newNotes = state.notes.splice(0);
var found = false;
for (let i = 0; i < newNotes.length; i++) {
let n = newNotes[i];
if (n.id == action.note.id) {
if (n.id == modNote.id) {
if (!('parent_id' in modNote) || modNote.parent_id == n.parent_id) {
// Merge the properties that have changed (in modNote) into
// the object we already have.
newNotes[i] = Object.assign(newNotes[i], action.note);
} else {
newNotes.splice(i, 1);
}
found = true;
break;
}
}
if (!found) newNotes.push(action.note);
if (!found && ('parent_id' in modNote) && modNote.parent_id == state.selectedFolderId) newNotes.push(modNote);
newNotes = Note.sortNotes(newNotes, state.notesOrder);
newState = Object.assign({}, state);