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 = []; + } +}