You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-06 09:19:22 +02:00
Desktop: Optimised sidebar rendering speed
This commit is contained in:
@@ -176,6 +176,7 @@ ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
|
|||||||
ReactNativeClient/lib/hooks/useEffectDebugger.js
|
ReactNativeClient/lib/hooks/useEffectDebugger.js
|
||||||
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
|
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
|
||||||
ReactNativeClient/lib/hooks/usePrevious.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/checkbox.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.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/JoplinServerApi.js
|
||||||
ReactNativeClient/lib/ntpDate.js
|
ReactNativeClient/lib/ntpDate.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
|
ReactNativeClient/lib/services/debug/populateDatabase.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainService.js
|
ReactNativeClient/lib/services/keychain/KeychainService.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
|
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -169,6 +169,7 @@ ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
|
|||||||
ReactNativeClient/lib/hooks/useEffectDebugger.js
|
ReactNativeClient/lib/hooks/useEffectDebugger.js
|
||||||
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
|
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
|
||||||
ReactNativeClient/lib/hooks/usePrevious.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/checkbox.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.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/JoplinServerApi.js
|
||||||
ReactNativeClient/lib/ntpDate.js
|
ReactNativeClient/lib/ntpDate.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
|
ReactNativeClient/lib/services/debug/populateDatabase.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainService.js
|
ReactNativeClient/lib/services/keychain/KeychainService.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
|
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const KeymapService = require('lib/services/KeymapService').default;
|
|||||||
const TemplateUtils = require('lib/TemplateUtils');
|
const TemplateUtils = require('lib/TemplateUtils');
|
||||||
const CssUtils = require('lib/CssUtils');
|
const CssUtils = require('lib/CssUtils');
|
||||||
const resourceEditWatcherReducer = require('lib/services/ResourceEditWatcher/reducer').default;
|
const resourceEditWatcherReducer = require('lib/services/ResourceEditWatcher/reducer').default;
|
||||||
|
// const populateDatabase = require('lib/services/debug/populateDatabase').default;
|
||||||
const versionInfo = require('lib/versionInfo').default;
|
const versionInfo = require('lib/versionInfo').default;
|
||||||
|
|
||||||
const commands = [
|
const commands = [
|
||||||
@@ -1109,7 +1110,7 @@ class Application extends BaseApplication {
|
|||||||
try {
|
try {
|
||||||
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
|
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
bridge().showErrorMessageBox(err.message);
|
reg.logger().error(err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
|
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
|
||||||
@@ -1267,6 +1268,8 @@ class Application extends BaseApplication {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
|
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
|
||||||
|
|
||||||
|
// await populateDatabase(reg.db());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,50 @@ const commands = [
|
|||||||
require('./commands/focusElementSideBar'),
|
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 <i className={props.isExpanded ? 'fas fa-caret-down' : 'fas fa-caret-right'} style={style}></i>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExpandLink(props:any) {
|
||||||
|
return props.hasChildren ? (
|
||||||
|
<StyledExpandLink href="#" data-folder-id={props.folderId} onClick={props.onFolderToggleClick_}>
|
||||||
|
<ExpandIcon themeId={props.themeId} isVisible={true} isExpanded={props.isExpanded}/>
|
||||||
|
</StyledExpandLink>
|
||||||
|
) : (
|
||||||
|
<StyledExpandLink><ExpandIcon themeId={props.themeId} isVisible={false} isExpanded={false}/></StyledExpandLink>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FolderItem(props:any) {
|
||||||
|
const { hasChildren, isExpanded, depth, selected, folderId, folderTitle, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_ } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
|
||||||
|
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
|
||||||
|
<StyledListItemAnchor
|
||||||
|
ref={anchorRef}
|
||||||
|
className="list-item"
|
||||||
|
isConflictFolder={folderId === Folder.conflictFolderId()}
|
||||||
|
href="#"
|
||||||
|
selected={selected}
|
||||||
|
data-id={folderId}
|
||||||
|
data-type={BaseModel.TYPE_FOLDER}
|
||||||
|
onContextMenu={itemContextMenu}
|
||||||
|
data-folder-id={folderId}
|
||||||
|
onClick={() => {
|
||||||
|
folderItem_click(folderId);
|
||||||
|
}}
|
||||||
|
onDoubleClick={onFolderToggleClick_}
|
||||||
|
>
|
||||||
|
{folderTitle} { noteCount && <StyledNoteCount>{noteCount}</StyledNoteCount>}
|
||||||
|
</StyledListItemAnchor>
|
||||||
|
</StyledListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class SideBarComponent extends React.Component<Props, State> {
|
class SideBarComponent extends React.Component<Props, State> {
|
||||||
|
|
||||||
private folderItemsOrder_:any[] = [];
|
private folderItemsOrder_:any[] = [];
|
||||||
@@ -68,8 +112,18 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
|
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
|
||||||
this.header_contextMenu = this.header_contextMenu.bind(this);
|
this.header_contextMenu = this.header_contextMenu.bind(this);
|
||||||
this.onAddFolderButtonClick = this.onAddFolderButtonClick.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) {
|
onFolderDragStart_(event:any) {
|
||||||
const folderId = event.currentTarget.getAttribute('data-folder-id');
|
const folderId = event.currentTarget.getAttribute('data-folder-id');
|
||||||
if (!folderId) return;
|
if (!folderId) return;
|
||||||
@@ -257,10 +311,10 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
menu.popup(bridge().window());
|
menu.popup(bridge().window());
|
||||||
}
|
}
|
||||||
|
|
||||||
folderItem_click(folder:any) {
|
folderItem_click(folderId:string) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'FOLDER_SELECT',
|
type: 'FOLDER_SELECT',
|
||||||
id: folder ? folder.id : null,
|
id: folderId ? folderId : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,41 +375,26 @@ class SideBarComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderFolderItem(folder:any, selected:boolean, hasChildren:boolean, depth:number) {
|
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 ? (
|
|
||||||
<StyledExpandLink href="#" data-folder-id={folder.id} onClick={this.onFolderToggleClick_}>
|
|
||||||
{expandIcon}
|
|
||||||
</StyledExpandLink>
|
|
||||||
) : (
|
|
||||||
<StyledExpandLink>{expandIcon}</StyledExpandLink>
|
|
||||||
);
|
|
||||||
|
|
||||||
const anchorRef = this.anchorItemRef('folder', folder.id);
|
const anchorRef = this.anchorItemRef('folder', folder.id);
|
||||||
const noteCount = folder.note_count ? this.renderNoteCount(folder.note_count) : '';
|
|
||||||
|
|
||||||
return (
|
return <FolderItem
|
||||||
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth}`} key={folder.id} onDragStart={this.onFolderDragStart_} onDragOver={this.onFolderDragOver_} onDrop={this.onFolderDrop_} draggable={true} data-folder-id={folder.id}>
|
key={folder.id}
|
||||||
{expandLink}
|
folderId={folder.id}
|
||||||
<StyledListItemAnchor
|
folderTitle={Folder.displayTitle(folder)}
|
||||||
ref={anchorRef}
|
themeId={this.props.themeId}
|
||||||
className="list-item"
|
depth={depth}
|
||||||
isConflictFolder={folder.id === Folder.conflictFolderId()}
|
selected={selected}
|
||||||
href="#"
|
isExpanded={this.props.collapsedFolderIds.indexOf(folder.id) < 0}
|
||||||
selected={selected}
|
hasChildren={hasChildren}
|
||||||
data-id={folder.id}
|
anchorRef={anchorRef}
|
||||||
data-type={BaseModel.TYPE_FOLDER}
|
noteCount={folder.note_count}
|
||||||
onContextMenu={(event:any) => this.itemContextMenu(event)}
|
onFolderDragStart_={this.onFolderDragStart_}
|
||||||
data-folder-id={folder.id}
|
onFolderDragOver_={this.onFolderDragOver_}
|
||||||
onClick={() => {
|
onFolderDrop_={this.onFolderDrop_}
|
||||||
this.folderItem_click(folder);
|
itemContextMenu={this.itemContextMenu}
|
||||||
}}
|
folderItem_click={this.folderItem_click}
|
||||||
onDoubleClick={this.onFolderToggleClick_}
|
onFolderToggleClick_={this.onFolderToggleClick_}
|
||||||
>
|
/>;
|
||||||
{Folder.displayTitle(folder)} {noteCount}
|
|
||||||
</StyledListItemAnchor>
|
|
||||||
</StyledListItem>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTag(tag:any, selected:boolean) {
|
renderTag(tag:any, selected:boolean) {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ class Database {
|
|||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
this.logExcludedQueryTypes_ = [];
|
this.logExcludedQueryTypes_ = [];
|
||||||
this.batchTransactionMutex_ = new Mutex();
|
this.batchTransactionMutex_ = new Mutex();
|
||||||
|
this.profilingEnabled_ = false;
|
||||||
|
this.queryId_ = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogExcludedQueryTypes(v) {
|
setLogExcludedQueryTypes(v) {
|
||||||
@@ -71,10 +73,30 @@ class Database {
|
|||||||
|
|
||||||
let waitTime = 50;
|
let waitTime = 50;
|
||||||
let totalWaitTime = 0;
|
let totalWaitTime = 0;
|
||||||
|
const callStartTime = Date.now();
|
||||||
|
let profilingTimeoutId = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
this.logQuery(sql, params);
|
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);
|
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
|
return result; // No exception was thrown
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) {
|
if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) {
|
||||||
@@ -89,6 +111,8 @@ class Database {
|
|||||||
} else {
|
} else {
|
||||||
throw this.sqliteErrorToJsError(error, sql, params);
|
throw this.sqliteErrorToJsError(error, sql, params);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (profilingTimeoutId) clearInterval(profilingTimeoutId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
ReactNativeClient/lib/hooks/usePropsDebugger.ts
Normal file
13
ReactNativeClient/lib/hooks/usePropsDebugger.ts
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -156,6 +156,8 @@ class BaseItem extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async loadItemsByIds(ids) {
|
static async loadItemsByIds(ids) {
|
||||||
|
if (!ids.length) return [];
|
||||||
|
|
||||||
const classes = this.syncItemClassNames();
|
const classes = this.syncItemClassNames();
|
||||||
let output = [];
|
let output = [];
|
||||||
for (let i = 0; i < classes.length; i++) {
|
for (let i = 0; i < classes.length; i++) {
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class Tag extends BaseItem {
|
|||||||
|
|
||||||
static async tagsByNoteId(noteId) {
|
static async tagsByNoteId(noteId) {
|
||||||
const tagIds = await NoteTag.tagIdsByNoteId(noteId);
|
const tagIds = await NoteTag.tagIdsByNoteId(noteId);
|
||||||
|
if (!tagIds.length) return [];
|
||||||
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
|
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
56
ReactNativeClient/lib/services/debug/populateDatabase.ts
Normal file
56
ReactNativeClient/lib/services/debug/populateDatabase.ts
Normal file
@@ -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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user