From feb946acfba24f7763e8700de5ecf69b3235a235 Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Sat, 21 Sep 2024 10:28:33 -0700 Subject: [PATCH] Chore: Mobile: Search screen: Use stronger types, try to prevent multiple concurrent attempts to update the result list (#11075) --- packages/app-mobile/components/app-nav.tsx | 4 +- .../app-mobile/components/screens/search.tsx | 86 +++++++++++-------- packages/app-mobile/root.tsx | 2 +- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/packages/app-mobile/components/app-nav.tsx b/packages/app-mobile/components/app-nav.tsx index 00af22fcfff..61089b0f3f9 100644 --- a/packages/app-mobile/components/app-nav.tsx +++ b/packages/app-mobile/components/app-nav.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import NotesScreen from './screens/Notes'; -const { SearchScreen } = require('./screens/search.js'); +import SearchScreen from './screens/search'; import { Component } from 'react'; import { KeyboardAvoidingView, Keyboard, Platform, View, KeyboardEvent, Dimensions, EmitterSubscription } from 'react-native'; import { AppState } from '../utils/types'; @@ -116,7 +116,7 @@ class AppNavComponent extends Component { style={style} > - {searchScreenLoaded && } + {searchScreenLoaded && } {!notesScreenVisible && !searchScreenVisible && } diff --git a/packages/app-mobile/components/screens/search.tsx b/packages/app-mobile/components/screens/search.tsx index 92bb444ab69..52460a5ba0e 100644 --- a/packages/app-mobile/components/screens/search.tsx +++ b/packages/app-mobile/components/screens/search.tsx @@ -1,37 +1,53 @@ -const React = require('react'); +import * as React from 'react'; import { StyleSheet, View, TextInput, FlatList, TouchableHighlight } from 'react-native'; -const { connect } = require('react-redux'); +import { connect } from 'react-redux'; import ScreenHeader from '../ScreenHeader'; const Icon = require('react-native-vector-icons/Ionicons').default; import { _ } from '@joplin/lib/locale'; import Note from '@joplin/lib/models/Note'; import NoteItem from '../NoteItem'; -const { BaseScreenComponent } = require('../base-screen'); +import { BaseScreenComponent } from '../base-screen'; import { themeStyle } from '../global-style'; const DialogBox = require('react-native-dialogbox').default; import SearchEngineUtils from '@joplin/lib/services/search/SearchEngineUtils'; import SearchEngine from '@joplin/lib/services/search/SearchEngine'; import { AppState } from '../../utils/types'; import { NoteEntity } from '@joplin/lib/services/database/types'; +import AsyncActionQueue from '@joplin/lib/AsyncActionQueue'; +import { Dispatch } from 'redux'; -class SearchScreenComponent extends BaseScreenComponent { +interface Props { + themeId: number; + query: string; + visible: boolean; + dispatch: Dispatch; + + noteSelectionEnabled: boolean; + ftsEnabled: number; +} + +interface State { + query: string; + notes: NoteEntity[]; +} + +class SearchScreenComponent extends BaseScreenComponent { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactor of old code from before rule was applied. + public dialogbox: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - private state: any = null; private isMounted_ = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - private styles_: any = {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - private scheduleSearchTimer_: any = null; + private styles_: Record = {}; + private searchActionQueue_ = new AsyncActionQueue(200); public static navigationOptions() { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied return { header: null } as any; } - public constructor() { - super(); + public constructor(props: Props) { + super(props); this.state = { query: '', notes: [], @@ -44,8 +60,7 @@ class SearchScreenComponent extends BaseScreenComponent { if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId]; this.styles_ = {}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - const styles: any = { + const styleSheet = StyleSheet.create({ body: { flex: 1, }, @@ -55,21 +70,22 @@ class SearchScreenComponent extends BaseScreenComponent { borderWidth: 1, borderColor: theme.dividerColor, }, - }; - - styles.searchTextInput = { ...theme.lineInput }; - styles.searchTextInput.paddingLeft = theme.marginLeft; - styles.searchTextInput.flex = 1; - styles.searchTextInput.backgroundColor = theme.backgroundColor; - styles.searchTextInput.color = theme.color; - - styles.clearIcon = { ...theme.icon }; - styles.clearIcon.color = theme.colorFaded; - styles.clearIcon.paddingRight = theme.marginRight; - styles.clearIcon.backgroundColor = theme.backgroundColor; - - this.styles_[this.props.themeId] = StyleSheet.create(styles); - return this.styles_[this.props.themeId]; + searchTextInput: { + ...theme.lineInput, + paddingLeft: theme.marginLeft, + flex: 1, + backgroundColor: theme.backgroundColor, + color: theme.color, + }, + clearIcon: { + ...theme.icon, + color: theme.colorFaded, + paddingRight: theme.marginRight, + backgroundColor: theme.backgroundColor, + }, + }); + this.styles_[this.props.themeId] = styleSheet; + return styleSheet; } public componentDidMount() { @@ -98,7 +114,7 @@ class SearchScreenComponent extends BaseScreenComponent { let notes: NoteEntity[] = []; if (query) { - if (this.props.settings['db.ftsEnabled']) { + if (this.props.ftsEnabled) { const r = await SearchEngineUtils.notesForQuery(query, true, { appendWildCards: true }); notes = r.notes; } else { @@ -130,12 +146,11 @@ class SearchScreenComponent extends BaseScreenComponent { } public scheduleSearch() { - if (this.scheduleSearchTimer_) clearTimeout(this.scheduleSearchTimer_); + this.searchActionQueue_.push(() => this.refreshSearch(this.state.query)); + } - this.scheduleSearchTimer_ = setTimeout(() => { - this.scheduleSearchTimer_ = null; - void this.refreshSearch(this.state.query); - }, 200); + public onComponentWillUnmount() { + void this.searchActionQueue_.reset(); } private searchTextInput_changeText(text: string) { @@ -215,7 +230,8 @@ const SearchScreen = connect((state: AppState) => { themeId: state.settings.theme, settings: state.settings, noteSelectionEnabled: state.noteSelectionEnabled, + ftsEnabled: state.settings['db.ftsEnabled'], }; })(SearchScreenComponent); -module.exports = { SearchScreen }; +export default SearchScreen; diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index 38c2bf1de9f..b23e4986d93 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -61,7 +61,7 @@ import ConfigScreen from './components/screens/ConfigScreen/ConfigScreen'; const { FolderScreen } = require('./components/screens/folder.js'); import LogScreen from './components/screens/LogScreen'; import StatusScreen from './components/screens/status'; -const { SearchScreen } = require('./components/screens/search.js'); +import SearchScreen from './components/screens/search'; const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js'); import EncryptionConfigScreen from './components/screens/encryption-config'; const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');