perf: avoid uneccessary notes list item rerenders (#1904)

This commit is contained in:
Mo
2022-10-30 10:48:23 -05:00
committed by GitHub
parent 32f03d9470
commit 89927a3790
14 changed files with 208 additions and 68 deletions

View File

@@ -7,7 +7,7 @@ import { Component } from 'react'
export type PureComponentState = Partial<Record<string, any>> export type PureComponentState = Partial<Record<string, any>>
export type PureComponentProps = Partial<Record<string, any>> export type PureComponentProps = Partial<Record<string, any>>
export abstract class PureComponent<P = PureComponentProps, S = PureComponentState> extends Component<P, S> { export abstract class AbstractComponent<P = PureComponentProps, S = PureComponentState> extends Component<P, S> {
private unsubApp!: () => void private unsubApp!: () => void
private reactionDisposers: IReactionDisposer[] = [] private reactionDisposers: IReactionDisposer[] = []

View File

@@ -4,7 +4,7 @@ import { ApplicationEvent, Challenge, removeFromArray, WebAppEvent } from '@stan
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants/Constants' import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
import { alertDialog, RouteType } from '@standardnotes/ui-services' import { alertDialog, RouteType } from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import Navigation from '@/Components/Navigation/Navigation' import Navigation from '@/Components/Tags/Navigation'
import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView' import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView'
import Footer from '@/Components/Footer/Footer' import Footer from '@/Components/Footer/Footer'
import SessionsModal from '@/Components/SessionsModal/SessionsModal' import SessionsModal from '@/Components/SessionsModal/SessionsModal'

View File

@@ -40,6 +40,7 @@ const ContentList: FunctionComponent<Props> = ({
const { selectPreviousItem, selectNextItem } = selectionController const { selectPreviousItem, selectNextItem } = selectionController
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions
const { sortBy } = itemListController.displayOptions const { sortBy } = itemListController.displayOptions
const selectedTag = navigationController.selected
const onScroll: UIEventHandler = useCallback( const onScroll: UIEventHandler = useCallback(
(e) => { (e) => {
@@ -72,25 +73,27 @@ const ContentList: FunctionComponent<Props> = ({
[selectionController], [selectionController],
) )
const getTagsForItem = (item: ListableContentItem) => { const getTagsForItem = useCallback(
if (hideTags) { (item: ListableContentItem) => {
return [] if (hideTags) {
} return []
}
const selectedTag = navigationController.selected if (!selectedTag) {
if (!selectedTag) { return []
return [] }
}
const tags = application.getItemTags(item) const tags = application.getItemTags(item)
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1 const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
if (isNavigatingOnlyTag) { if (isNavigatingOnlyTag) {
return [] return []
} }
return tags return tags
} },
[hideTags, selectedTag, application],
)
return ( return (
<div <div

View File

@@ -1,8 +1,8 @@
import { ContentType } from '@standardnotes/snjs' import { ContentType } from '@standardnotes/snjs'
import { FunctionComponent } from 'react' import React, { FunctionComponent } from 'react'
import FileListItem from './FileListItem' import FileListItem from './FileListItem'
import NoteListItem from './NoteListItem' import NoteListItem from './NoteListItem'
import { AbstractListItemProps } from './Types/AbstractListItemProps' import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps'
const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => { const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
switch (props.item.content_type) { switch (props.item.content_type) {
@@ -15,4 +15,4 @@ const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
} }
} }
export default ContentListItem export default React.memo(ContentListItem, (a, b) => !doListItemPropsMeritRerender(a, b))

View File

@@ -13,6 +13,7 @@ import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent' import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
import ListItemNotePreviewText from './ListItemNotePreviewText' import ListItemNotePreviewText from './ListItemNotePreviewText'
import { ListItemTitle } from './ListItemTitle' import { ListItemTitle } from './ListItemTitle'
import { log, LoggingDomain } from '@/Logging'
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
application, application,
@@ -70,6 +71,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
useContextMenuEvent(listItemRef, openContextMenu) useContextMenuEvent(listItemRef, openContextMenu)
log(LoggingDomain.ItemsList, 'Rendering note list item', item.title)
return ( return (
<div <div
ref={listItemRef} ref={listItemRef}

View File

@@ -1,7 +1,7 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { FilesController } from '@/Controllers/FilesController' import { FilesController } from '@/Controllers/FilesController'
import { NotesController } from '@/Controllers/NotesController' import { NotesController } from '@/Controllers/NotesController'
import { SortableItem, SNTag } from '@standardnotes/snjs' import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs'
import { ListableContentItem } from './ListableContentItem' import { ListableContentItem } from './ListableContentItem'
export type AbstractListItemProps = { export type AbstractListItemProps = {
@@ -18,3 +18,89 @@ export type AbstractListItemProps = {
sortBy: keyof SortableItem | undefined sortBy: keyof SortableItem | undefined
tags: SNTag[] tags: SNTag[]
} }
export function doListItemPropsMeritRerender(previous: AbstractListItemProps, next: AbstractListItemProps): boolean {
const simpleComparison: (keyof AbstractListItemProps)[] = [
'onSelect',
'hideDate',
'hideIcon',
'hideTags',
'hidePreview',
'selected',
'sortBy',
]
for (const key of simpleComparison) {
if (previous[key] !== next[key]) {
return true
}
}
if (previous['item'] !== next['item']) {
if (doesItemChangeMeritRerender(previous['item'], next['item'])) {
return true
}
}
return doesTagsChangeMeritRerender(previous['tags'], next['tags'])
}
function doesTagsChangeMeritRerender(previous: SNTag[], next: SNTag[]): boolean {
if (previous === next) {
return false
}
if (previous.length !== next.length) {
return true
}
if (previous.length === 0 && next.length === 0) {
return false
}
if (Uuids(previous).sort().join() !== Uuids(next).sort().join()) {
return true
}
if (
previous
.map((t) => t.title)
.sort()
.join() !==
next
.map((t) => t.title)
.sort()
.join()
) {
return true
}
return false
}
function doesItemChangeMeritRerender(previous: ListableContentItem, next: ListableContentItem): boolean {
if (previous.uuid !== next.uuid) {
return true
}
const propertiesMeritingRerender: (keyof ListableContentItem)[] = [
'title',
'protected',
'updatedAtString',
'createdAtString',
'hidePreview',
'preview_html',
'preview_plain',
'archived',
'starred',
'pinned',
]
for (const key of propertiesMeritingRerender) {
if (previous[key] !== next[key]) {
return true
}
}
return false
}

View File

@@ -1,6 +1,6 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ApplicationGroup } from '@/Application/ApplicationGroup' import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { PureComponent } from '@/Components/Abstract/PureComponent' import { AbstractComponent } from '@/Components/Abstract/PureComponent'
import { destroyAllObjectProperties, preventRefreshing } from '@/Utils' import { destroyAllObjectProperties, preventRefreshing } from '@/Utils'
import { ApplicationEvent, ApplicationDescriptor, WebAppEvent } from '@standardnotes/snjs' import { ApplicationEvent, ApplicationDescriptor, WebAppEvent } from '@standardnotes/snjs'
import { import {
@@ -41,7 +41,7 @@ type State = {
arbitraryStatusMessage?: string arbitraryStatusMessage?: string
} }
class Footer extends PureComponent<Props, State> { class Footer extends AbstractComponent<Props, State> {
public user?: unknown public user?: unknown
private didCheckForOffline = false private didCheckForOffline = false
private completedInitialSync = false private completedInitialSync = false

View File

@@ -1,5 +1,5 @@
import { FileItem, FileViewController, NoteViewController } from '@standardnotes/snjs' import { FileItem, FileViewController, NoteViewController } from '@standardnotes/snjs'
import { PureComponent } from '@/Components/Abstract/PureComponent' import { AbstractComponent } from '@/Components/Abstract/PureComponent'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import MultipleSelectedNotes from '@/Components/MultipleSelectedNotes/MultipleSelectedNotes' import MultipleSelectedNotes from '@/Components/MultipleSelectedNotes/MultipleSelectedNotes'
import MultipleSelectedFiles from '../MultipleSelectedFiles/MultipleSelectedFiles' import MultipleSelectedFiles from '../MultipleSelectedFiles/MultipleSelectedFiles'
@@ -22,7 +22,7 @@ type Props = {
application: WebApplication application: WebApplication
} }
class NoteGroupView extends PureComponent<Props, State> { class NoteGroupView extends AbstractComponent<Props, State> {
private removeChangeObserver!: () => void private removeChangeObserver!: () => void
constructor(props: Props) { constructor(props: Props) {

View File

@@ -1,49 +1,50 @@
import { ChangeEventHandler, createRef, KeyboardEventHandler, RefObject } from 'react' import { AbstractComponent } from '@/Components/Abstract/PureComponent'
import ChangeEditorButton from '@/Components/ChangeEditor/ChangeEditorButton'
import ComponentView from '@/Components/ComponentView/ComponentView'
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
import PanelResizer, { PanelResizeType, PanelSide } from '@/Components/PanelResizer/PanelResizer'
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay'
import { ElementIds } from '@/Constants/ElementIDs'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
import { log, LoggingDomain } from '@/Logging'
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { import {
ApplicationEvent, ApplicationEvent,
isPayloadSourceRetrieved,
isPayloadSourceInternalChange,
ContentType,
SNComponent,
SNNote,
ComponentArea, ComponentArea,
PrefKey,
ComponentViewerInterface, ComponentViewerInterface,
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction, ContentType,
EditorFontSize,
EditorLineHeight,
isPayloadSourceInternalChange,
isPayloadSourceRetrieved,
NoteType,
NoteViewController, NoteViewController,
PayloadEmitSource, PayloadEmitSource,
PrefKey,
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
SNComponent,
SNNote,
WebAppEvent, WebAppEvent,
EditorLineHeight,
EditorFontSize,
NoteType,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils' import { confirmDialog, KeyboardKey, KeyboardModifier } from '@standardnotes/ui-services'
import { ChangeEventHandler, createRef, KeyboardEventHandler, RefObject } from 'react'
import { EditorEventSource } from '../../Types/EditorEventSource' import { EditorEventSource } from '../../Types/EditorEventSource'
import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services' import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings' import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
import { PureComponent } from '@/Components/Abstract/PureComponent' import LinkedItemsButton from '../LinkedItems/LinkedItemsButton'
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay' import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
import ComponentView from '@/Components/ComponentView/ComponentView'
import PanelResizer, { PanelSide, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { ElementIds } from '@/Constants/ElementIDs'
import ChangeEditorButton from '@/Components/ChangeEditor/ChangeEditorButton'
import EditingDisabledBanner from './EditingDisabledBanner' import EditingDisabledBanner from './EditingDisabledBanner'
import { reloadFont } from './FontFunctions'
import NoteStatusIndicator, { NoteStatus } from './NoteStatusIndicator'
import NoteViewFileDropTarget from './NoteViewFileDropTarget'
import { NoteViewProps } from './NoteViewProps'
import { import {
transactionForAssociateComponentWithCurrentNote, transactionForAssociateComponentWithCurrentNote,
transactionForDisassociateComponentWithCurrentNote, transactionForDisassociateComponentWithCurrentNote,
} from './TransactionFunctions' } from './TransactionFunctions'
import { reloadFont } from './FontFunctions'
import { NoteViewProps } from './NoteViewProps'
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
import { classNames } from '@/Utils/ConcatenateClassNames'
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
import NoteStatusIndicator, { NoteStatus } from './NoteStatusIndicator'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import LinkedItemsButton from '../LinkedItems/LinkedItemsButton'
import NoteViewFileDropTarget from './NoteViewFileDropTarget'
const MinimumStatusDuration = 400 const MinimumStatusDuration = 400
const TextareaDebounce = 100 const TextareaDebounce = 100
@@ -98,7 +99,7 @@ const PlaintextFontSizeMapping: Record<EditorFontSize, string> = {
Large: 'text-xl', Large: 'text-xl',
} }
class NoteView extends PureComponent<NoteViewProps, State> { class NoteView extends AbstractComponent<NoteViewProps, State> {
readonly controller!: NoteViewController readonly controller!: NoteViewController
private statusTimeout?: NodeJS.Timeout private statusTimeout?: NodeJS.Timeout
@@ -193,7 +194,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
;(this.onPanelResizeFinish as unknown) = undefined ;(this.onPanelResizeFinish as unknown) = undefined
;(this.stackComponentExpanded as unknown) = undefined ;(this.stackComponentExpanded as unknown) = undefined
;(this.toggleStackComponent as unknown) = undefined ;(this.toggleStackComponent as unknown) = undefined
;(this.onSystemEditorLoad as unknown) = undefined ;(this.onSystemEditorRef as unknown) = undefined
;(this.debounceReloadEditorComponent as unknown) = undefined ;(this.debounceReloadEditorComponent as unknown) = undefined
;(this.textAreaChangeDebounceSave as unknown) = undefined ;(this.textAreaChangeDebounceSave as unknown) = undefined
;(this.editorContentRef as unknown) = undefined ;(this.editorContentRef as unknown) = undefined
@@ -207,6 +208,24 @@ class NoteView extends PureComponent<NoteViewProps, State> {
return this.controller.item return this.controller.item
} }
override shouldComponentUpdate(_nextProps: Readonly<NoteViewProps>, nextState: Readonly<State>): boolean {
const complexObjects: (keyof State)[] = ['availableStackComponents', 'stackComponentViewers']
for (const key of Object.keys(nextState) as (keyof State)[]) {
if (complexObjects.includes(key)) {
continue
}
const prevValue = this.state[key]
const nextValue = nextState[key]
if (prevValue !== nextValue) {
log(LoggingDomain.NoteView, 'Rendering due to state change', key, prevValue, nextValue)
return true
}
}
return false
}
override componentDidMount(): void { override componentDidMount(): void {
super.componentDidMount() super.componentDidMount()
@@ -253,6 +272,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
onNoteInnerChange(note: SNNote, source: PayloadEmitSource): void { onNoteInnerChange(note: SNNote, source: PayloadEmitSource): void {
log(LoggingDomain.NoteView, 'On inner note change', PayloadEmitSource[source])
if (note.uuid !== this.note.uuid) { if (note.uuid !== this.note.uuid) {
throw Error('Editor received changes for non-current note') throw Error('Editor received changes for non-current note')
} }
@@ -431,12 +452,15 @@ class NoteView extends PureComponent<NoteViewProps, State> {
streamItems() { streamItems() {
this.removeComponentStreamObserver = this.application.streamItems(ContentType.Component, async ({ source }) => { this.removeComponentStreamObserver = this.application.streamItems(ContentType.Component, async ({ source }) => {
log(LoggingDomain.NoteView, 'On component stream observer', PayloadEmitSource[source])
if (isPayloadSourceInternalChange(source) || source === PayloadEmitSource.InitialObserverRegistrationPush) { if (isPayloadSourceInternalChange(source) || source === PayloadEmitSource.InitialObserverRegistrationPush) {
return return
} }
if (!this.note) { if (!this.note) {
return return
} }
await this.reloadStackComponents() await this.reloadStackComponents()
this.debounceReloadEditorComponent() this.debounceReloadEditorComponent()
}) })
@@ -454,6 +478,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
if (this.state.editorComponentViewerDidAlreadyReload && !force) { if (this.state.editorComponentViewerDidAlreadyReload && !force) {
return return
} }
const component = viewer.component const component = viewer.component
this.application.componentManager.destroyComponentViewer(viewer) this.application.componentManager.destroyComponentViewer(viewer)
this.setState( this.setState(
@@ -489,6 +514,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
async reloadEditorComponent() { async reloadEditorComponent() {
log(LoggingDomain.NoteView, 'Reload editor component')
if (this.state.showProtectedWarning) { if (this.state.showProtectedWarning) {
this.destroyCurrentEditorComponent() this.destroyCurrentEditorComponent()
return return
@@ -581,13 +607,16 @@ class NoteView extends PureComponent<NoteViewProps, State> {
onTextAreaChange: ChangeEventHandler<HTMLTextAreaElement> = ({ currentTarget }) => { onTextAreaChange: ChangeEventHandler<HTMLTextAreaElement> = ({ currentTarget }) => {
const text = currentTarget.value const text = currentTarget.value
this.setState({ this.setState({
editorText: text, editorText: text,
}) })
this.textAreaChangeDebounceSave() this.textAreaChangeDebounceSave()
} }
textAreaChangeDebounceSave = () => { textAreaChangeDebounceSave = () => {
log(LoggingDomain.NoteView, 'Performing save after debounce')
this.controller this.controller
.save({ .save({
editorValues: { editorValues: {
@@ -609,10 +638,14 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
onTitleChange: ChangeEventHandler<HTMLInputElement> = ({ currentTarget }) => { onTitleChange: ChangeEventHandler<HTMLInputElement> = ({ currentTarget }) => {
log(LoggingDomain.NoteView, 'Performing save after title change')
const title = currentTarget.value const title = currentTarget.value
this.setState({ this.setState({
editorTitle: title, editorTitle: title,
}) })
this.controller this.controller
.save({ .save({
editorValues: { editorValues: {
@@ -662,10 +695,12 @@ class NoteView extends PureComponent<NoteViewProps, State> {
this.application.alertService.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT).catch(console.error) this.application.alertService.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT).catch(console.error)
return return
} }
if (this.note.locked) { if (this.note.locked) {
this.application.alertService.alert(STRING_DELETE_LOCKED_ATTEMPT).catch(console.error) this.application.alertService.alert(STRING_DELETE_LOCKED_ATTEMPT).catch(console.error)
return return
} }
const title = this.note.title.length ? `'${this.note.title}'` : 'this note' const title = this.note.title.length ? `'${this.note.title}'` : 'this note'
const text = StringDeleteNote(title, permanently) const text = StringDeleteNote(title, permanently)
if ( if (
@@ -727,6 +762,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
async reloadPreferences() { async reloadPreferences() {
log(LoggingDomain.NoteView, 'Reload preferences')
const monospaceFont = this.application.getPreference( const monospaceFont = this.application.getPreference(
PrefKey.EditorMonospaceEnabled, PrefKey.EditorMonospaceEnabled,
PrefDefaults[PrefKey.EditorMonospaceEnabled], PrefDefaults[PrefKey.EditorMonospaceEnabled],
@@ -776,9 +812,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
} }
/** @components */
async reloadStackComponents() { async reloadStackComponents() {
log(LoggingDomain.NoteView, 'Reload stack components')
const stackComponents = sortAlphabetically( const stackComponents = sortAlphabetically(
this.application.componentManager this.application.componentManager
.componentsForArea(ComponentArea.EditorStack) .componentsForArea(ComponentArea.EditorStack)
@@ -844,10 +879,13 @@ class NoteView extends PureComponent<NoteViewProps, State> {
}) })
} }
onSystemEditorLoad = (ref: HTMLTextAreaElement | null) => { onSystemEditorRef = (ref: HTMLTextAreaElement | null) => {
if (this.removeTabObserver || !ref) { if (this.removeTabObserver || !ref) {
return return
} }
log(LoggingDomain.NoteView, 'On system editor ref')
/** /**
* Insert 4 spaces when a tab key is pressed, * Insert 4 spaces when a tab key is pressed,
* only used when inside of the text editor. * only used when inside of the text editor.
@@ -1070,7 +1108,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
onFocus={this.onContentFocus} onFocus={this.onContentFocus}
onBlur={this.onContentBlur} onBlur={this.onContentBlur}
readOnly={this.state.noteLocked} readOnly={this.state.noteLocked}
ref={(ref) => ref && this.onSystemEditorLoad(ref)} ref={(ref) => ref && this.onSystemEditorRef(ref)}
spellCheck={this.state.spellcheck} spellCheck={this.state.spellcheck}
value={this.state.editorText} value={this.state.editorText}
className={classNames( className={classNames(

View File

@@ -1,6 +1,6 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { createRef } from 'react' import { createRef } from 'react'
import { PureComponent } from '@/Components/Abstract/PureComponent' import { AbstractComponent } from '@/Components/Abstract/PureComponent'
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
import DecoratedPasswordInput from '../Input/DecoratedPasswordInput' import DecoratedPasswordInput from '../Input/DecoratedPasswordInput'
import ModalDialog from '../Shared/ModalDialog' import ModalDialog from '../Shared/ModalDialog'
@@ -38,7 +38,7 @@ type FormData = {
status?: string status?: string
} }
class PasswordWizard extends PureComponent<Props, State> { class PasswordWizard extends AbstractComponent<Props, State> {
private currentPasswordInput = createRef<HTMLInputElement>() private currentPasswordInput = createRef<HTMLInputElement>()
constructor(props: Props) { constructor(props: Props) {

View File

@@ -1,12 +1,12 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { PureComponent } from '@/Components/Abstract/PureComponent' import { AbstractComponent } from '@/Components/Abstract/PureComponent'
type Props = { type Props = {
application: WebApplication application: WebApplication
close: () => void close: () => void
} }
class SyncResolutionMenu extends PureComponent<Props> { class SyncResolutionMenu extends AbstractComponent<Props> {
constructor(props: Props) { constructor(props: Props) {
super(props, props.application) super(props, props.application)
} }

View File

@@ -28,6 +28,7 @@ import { mergeRefs } from '@/Hooks/mergeRefs'
import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider' import { useFileDragNDrop } from '../FileDragNDropProvider/FileDragNDropProvider'
import { LinkingController } from '@/Controllers/LinkingController' import { LinkingController } from '@/Controllers/LinkingController'
import { TagListSectionType } from './TagListSection' import { TagListSectionType } from './TagListSection'
import { log, LoggingDomain } from '@/Logging'
type Props = { type Props = {
tag: SNTag tag: SNTag
@@ -237,6 +238,8 @@ export const TagsListItem: FunctionComponent<Props> = observer(
} }
}, [addDragTarget, linkingController, removeDragTarget, tag]) }, [addDragTarget, linkingController, removeDragTarget, tag])
log(LoggingDomain.NavigationList, 'Rendering TagsListItem')
return ( return (
<> <>
<div <div

View File

@@ -1,15 +1,22 @@
import { log as utilsLog } from '@standardnotes/utils' import { log as utilsLog } from '@standardnotes/utils'
import { isDev } from './Utils'
export enum LoggingDomain { export enum LoggingDomain {
DailyNotes, DailyNotes,
NoteView,
ItemsList,
NavigationList,
} }
const LoggingStatus: Record<LoggingDomain, boolean> = { const LoggingStatus: Record<LoggingDomain, boolean> = {
[LoggingDomain.DailyNotes]: false, [LoggingDomain.DailyNotes]: false,
[LoggingDomain.NoteView]: false,
[LoggingDomain.ItemsList]: false,
[LoggingDomain.NavigationList]: false,
} }
export function log(domain: LoggingDomain, ...args: any[]): void { export function log(domain: LoggingDomain, ...args: any[]): void {
if (!LoggingStatus[domain]) { if (!isDev || !LoggingStatus[domain]) {
return return
} }