diff --git a/.eslintignore b/.eslintignore
index fef8e6b32..fe0098417 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -176,6 +176,7 @@ ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
+ReactNativeClient/lib/hooks/usePropsDebugger.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
@@ -183,6 +184,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/services/CommandService.js
+ReactNativeClient/lib/services/debug/populateDatabase.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
diff --git a/.gitignore b/.gitignore
index e392a018e..499227ffe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -169,6 +169,7 @@ ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
+ReactNativeClient/lib/hooks/usePropsDebugger.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
@@ -176,6 +177,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/services/CommandService.js
+ReactNativeClient/lib/services/debug/populateDatabase.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
diff --git a/ElectronClient/app.js b/ElectronClient/app.js
index 9a53d832e..e32e603c4 100644
--- a/ElectronClient/app.js
+++ b/ElectronClient/app.js
@@ -34,6 +34,7 @@ const KeymapService = require('lib/services/KeymapService').default;
const TemplateUtils = require('lib/TemplateUtils');
const CssUtils = require('lib/CssUtils');
const resourceEditWatcherReducer = require('lib/services/ResourceEditWatcher/reducer').default;
+// const populateDatabase = require('lib/services/debug/populateDatabase').default;
const versionInfo = require('lib/versionInfo').default;
const commands = [
@@ -1109,7 +1110,7 @@ class Application extends BaseApplication {
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (err) {
- bridge().showErrorMessageBox(err.message);
+ reg.logger().error(err.message);
}
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
@@ -1267,6 +1268,8 @@ class Application extends BaseApplication {
};
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
+
+ // await populateDatabase(reg.db());
}
}
diff --git a/ElectronClient/gui/SideBar/SideBar.tsx b/ElectronClient/gui/SideBar/SideBar.tsx
index d36ac59e6..b73fc77c0 100644
--- a/ElectronClient/gui/SideBar/SideBar.tsx
+++ b/ElectronClient/gui/SideBar/SideBar.tsx
@@ -45,6 +45,50 @@ const commands = [
require('./commands/focusElementSideBar'),
];
+function ExpandIcon(props:any) {
+ const theme = themeStyle(props.themeId);
+ const style:any = { width: 16, maxWidth: 16, opacity: 0.5, fontSize: Math.round(theme.toolbarIconSize * 0.8), display: 'flex', justifyContent: 'center' };
+ if (!props.isVisible) style.visibility = 'hidden';
+ return ;
+}
+
+function ExpandLink(props:any) {
+ return props.hasChildren ? (
+
+
+
+ ) : (
+
+ );
+}
+
+function FolderItem(props:any) {
+ const { hasChildren, isExpanded, depth, selected, folderId, folderTitle, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_ } = props;
+
+ return (
+
+
+ {
+ folderItem_click(folderId);
+ }}
+ onDoubleClick={onFolderToggleClick_}
+ >
+ {folderTitle} { noteCount && {noteCount}}
+
+
+ );
+}
+
class SideBarComponent extends React.Component {
private folderItemsOrder_:any[] = [];
@@ -68,8 +112,18 @@ class SideBarComponent extends React.Component {
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
this.header_contextMenu = this.header_contextMenu.bind(this);
this.onAddFolderButtonClick = this.onAddFolderButtonClick.bind(this);
+ this.folderItem_click = this.folderItem_click.bind(this);
}
+ // componentDidUpdate(prevProps:any, _prevState:any) {
+ // const props = this.props as any;
+ // for (const k in this.props) {
+ // if (prevProps[k] !== props[k]) {
+ // console.info('Props', k, props[k]);
+ // }
+ // }
+ // }
+
onFolderDragStart_(event:any) {
const folderId = event.currentTarget.getAttribute('data-folder-id');
if (!folderId) return;
@@ -257,10 +311,10 @@ class SideBarComponent extends React.Component {
menu.popup(bridge().window());
}
- folderItem_click(folder:any) {
+ folderItem_click(folderId:string) {
this.props.dispatch({
type: 'FOLDER_SELECT',
- id: folder ? folder.id : null,
+ id: folderId ? folderId : null,
});
}
@@ -321,41 +375,26 @@ class SideBarComponent extends React.Component {
}
renderFolderItem(folder:any, selected:boolean, hasChildren:boolean, depth:number) {
- const isExpanded = this.props.collapsedFolderIds.indexOf(folder.id) < 0;
- const expandIcon = this.renderExpandIcon(isExpanded, hasChildren);
- const expandLink = hasChildren ? (
-
- {expandIcon}
-
- ) : (
- {expandIcon}
- );
-
const anchorRef = this.anchorItemRef('folder', folder.id);
- const noteCount = folder.note_count ? this.renderNoteCount(folder.note_count) : '';
- return (
-
- {expandLink}
- this.itemContextMenu(event)}
- data-folder-id={folder.id}
- onClick={() => {
- this.folderItem_click(folder);
- }}
- onDoubleClick={this.onFolderToggleClick_}
- >
- {Folder.displayTitle(folder)} {noteCount}
-
-
- );
+ return ;
}
renderTag(tag:any, selected:boolean) {
diff --git a/ReactNativeClient/lib/database.js b/ReactNativeClient/lib/database.js
index a6d296fe9..32d9db6cc 100644
--- a/ReactNativeClient/lib/database.js
+++ b/ReactNativeClient/lib/database.js
@@ -9,6 +9,8 @@ class Database {
this.logger_ = new Logger();
this.logExcludedQueryTypes_ = [];
this.batchTransactionMutex_ = new Mutex();
+ this.profilingEnabled_ = false;
+ this.queryId_ = 1;
}
setLogExcludedQueryTypes(v) {
@@ -71,10 +73,30 @@ class Database {
let waitTime = 50;
let totalWaitTime = 0;
+ const callStartTime = Date.now();
+ let profilingTimeoutId = null;
while (true) {
try {
this.logQuery(sql, params);
+
+ const queryId = this.queryId_++;
+ if (this.profilingEnabled_) {
+ console.info(`SQL START ${queryId}`, sql, params);
+
+ profilingTimeoutId = setInterval(() => {
+ console.warn(`SQL ${queryId} has been running for ${Date.now() - callStartTime}: ${sql}`);
+ }, 3000);
+ }
+
const result = await this.driver()[callName](sql, params);
+
+ if (this.profilingEnabled_) {
+ clearInterval(profilingTimeoutId);
+ profilingTimeoutId = null;
+ const elapsed = Date.now() - callStartTime;
+ if (elapsed > 10) console.info(`SQL END ${queryId}`, elapsed, sql, params);
+ }
+
return result; // No exception was thrown
} catch (error) {
if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) {
@@ -89,6 +111,8 @@ class Database {
} else {
throw this.sqliteErrorToJsError(error, sql, params);
}
+ } finally {
+ if (profilingTimeoutId) clearInterval(profilingTimeoutId);
}
}
}
diff --git a/ReactNativeClient/lib/hooks/usePropsDebugger.ts b/ReactNativeClient/lib/hooks/usePropsDebugger.ts
new file mode 100644
index 000000000..a4de438fa
--- /dev/null
+++ b/ReactNativeClient/lib/hooks/usePropsDebugger.ts
@@ -0,0 +1,13 @@
+import useEffectDebugger from './useEffectDebugger';
+
+export default function usePropsDebugger(effectHook:any, props:any) {
+ const dependencies:any[] = [];
+ const dependencyNames:string[] = [];
+
+ for (const k in props) {
+ dependencies.push(props[k]);
+ dependencyNames.push(k);
+ }
+
+ useEffectDebugger(effectHook, dependencies, dependencyNames);
+}
diff --git a/ReactNativeClient/lib/models/BaseItem.js b/ReactNativeClient/lib/models/BaseItem.js
index 08bd89d1f..ffe2702bd 100644
--- a/ReactNativeClient/lib/models/BaseItem.js
+++ b/ReactNativeClient/lib/models/BaseItem.js
@@ -156,6 +156,8 @@ class BaseItem extends BaseModel {
}
static async loadItemsByIds(ids) {
+ if (!ids.length) return [];
+
const classes = this.syncItemClassNames();
let output = [];
for (let i = 0; i < classes.length; i++) {
diff --git a/ReactNativeClient/lib/models/Tag.js b/ReactNativeClient/lib/models/Tag.js
index 2bb506fa3..11dd720c4 100644
--- a/ReactNativeClient/lib/models/Tag.js
+++ b/ReactNativeClient/lib/models/Tag.js
@@ -109,6 +109,7 @@ class Tag extends BaseItem {
static async tagsByNoteId(noteId) {
const tagIds = await NoteTag.tagIdsByNoteId(noteId);
+ if (!tagIds.length) return [];
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
}
diff --git a/ReactNativeClient/lib/services/debug/populateDatabase.ts b/ReactNativeClient/lib/services/debug/populateDatabase.ts
new file mode 100644
index 000000000..5a1a43002
--- /dev/null
+++ b/ReactNativeClient/lib/services/debug/populateDatabase.ts
@@ -0,0 +1,56 @@
+const Folder = require('lib/models/Folder');
+const Note = require('lib/models/Note');
+
+function randomIndex(array:any[]):number {
+ return Math.round(Math.random() * (array.length - 1));
+}
+
+export default async function populateDatabase(db:any) {
+ await db.clearForTesting();
+
+ const folderCount = 2000;
+ const noteCount = 20000;
+
+ const createdFolderIds:string[] = [];
+ const createdNoteIds:string[] = [];
+
+ for (let i = 0; i < folderCount; i++) {
+ const folder:any = {
+ title: `folder${i}`,
+ };
+
+ const isRoot = Math.random() <= 0.1 || i === 0;
+
+ if (!isRoot) {
+ const parentIndex = randomIndex(createdFolderIds);
+ folder.parent_id = createdFolderIds[parentIndex];
+ }
+
+ const savedFolder = await Folder.save(folder);
+ createdFolderIds.push(savedFolder.id);
+
+ console.info(`Folders: ${i} / ${folderCount}`);
+ }
+
+ let noteBatch = [];
+ for (let i = 0; i < noteCount; i++) {
+ const note:any = { title: `note${i}`, body: `This is note num. ${i}` };
+ const parentIndex = randomIndex(createdFolderIds);
+ note.parent_id = createdFolderIds[parentIndex];
+
+ noteBatch.push(Note.save(note, { dispatchUpdateAction: false }).then((savedNote:any) => {
+ createdNoteIds.push(savedNote.id);
+ console.info(`Notes: ${i} / ${noteCount}`);
+ }));
+
+ if (noteBatch.length > 1000) {
+ await Promise.all(noteBatch);
+ noteBatch = [];
+ }
+ }
+
+ if (noteBatch.length) {
+ await Promise.all(noteBatch);
+ noteBatch = [];
+ }
+}