From 27a0959f30107e7c2fda703c0b0f7cb8ed5b238a Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Fri, 8 Dec 2023 02:12:23 -0800 Subject: [PATCH] Chore: Refactor Note.tsx and note-screen-shared.tsx to improve type safety (#9467) --- .../app-mobile/components/screens/Note.tsx | 96 +++++++++++++++---- .../components/shared/note-screen-shared.ts | 34 ++++--- packages/lib/services/NavService.ts | 10 +- 3 files changed, 108 insertions(+), 32 deletions(-) diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index 0be3d3a0c..f6d03056f 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -22,7 +22,7 @@ import Folder from '@joplin/lib/models/Folder'; const Clipboard = require('@react-native-community/clipboard').default; const md5 = require('md5'); const { BackButtonService } = require('../../services/back-button.js'); -import NavService from '@joplin/lib/services/NavService'; +import NavService, { OnNavigateCallback as OnNavigateCallback } from '@joplin/lib/services/NavService'; import BaseModel from '@joplin/lib/BaseModel'; import ActionButton from '../ActionButton'; const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils'); @@ -34,17 +34,17 @@ const { Checkbox } = require('../checkbox.js'); import { _, currentLocale } from '@joplin/lib/locale'; import { reg } from '@joplin/lib/registry'; import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; -const { BaseScreenComponent } = require('../base-screen'); +import { BaseScreenComponent } from '../base-screen'; const { themeStyle, editorFont } = require('../global-style.js'); const { dialogs } = require('../../utils/dialogs.js'); const DialogBox = require('react-native-dialogbox').default; import ImageResizer from '@bam.tech/react-native-image-resizer'; -import shared from '@joplin/lib/components/shared/note-screen-shared'; +import shared, { BaseNoteScreenComponent } from '@joplin/lib/components/shared/note-screen-shared'; import { Asset, ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker'; import SelectDateTimeDialog from '../SelectDateTimeDialog'; import ShareExtension from '../../utils/ShareExtension.js'; import CameraView from '../CameraView'; -import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types'; +import { FolderEntity, NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types'; import Logger from '@joplin/utils/Logger'; import ImageEditor from '../NoteEditor/ImageEditor/ImageEditor'; import promptRestoreAutosave from '../NoteEditor/ImageEditor/promptRestoreAutosave'; @@ -54,6 +54,8 @@ import { voskEnabled } from '../../services/voiceTyping/vosk'; import { isSupportedLanguage } from '../../services/voiceTyping/vosk.android'; import { ChangeEvent as EditorChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events'; import { join } from 'path'; +import { Dispatch } from 'redux'; +import { RefObject } from 'react'; const urlUtils = require('@joplin/lib/urlUtils'); const emptyArray: any[] = []; @@ -114,20 +116,84 @@ const pickDocument = async (multiple: boolean): Promise => { return result; }; -class NoteScreenComponent extends BaseScreenComponent { +interface Props { + provisionalNoteIds: string[]; + dispatch: Dispatch; + noteId: string; + useEditorBeta: boolean; + themeId: number; + editorFontSize: number; + editorFont: number; // e.g. Setting.FONT_MENLO + showSideMenu: boolean; + searchQuery: string[]; + ftsEnabled: boolean; + highlightedWords: string[]; + noteHash: string; + toolbarEnabled: boolean; +} + +interface State { + note: any; + mode: 'view'|'edit'; + readOnly: boolean; + folder: FolderEntity|null; + lastSavedNote: any; + isLoading: boolean; + titleTextInputHeight: number; + alarmDialogShown: boolean; + heightBumpView: number; + noteTagDialogShown: boolean; + fromShare: boolean; + showCamera: boolean; + showImageEditor: boolean; + imageEditorResource: ResourceEntity; + imageEditorResourceFilepath: string; + noteResources: Record; + newAndNoTitleChangeNoteId: boolean|null; + + HACK_webviewLoadingState: number; + + undoRedoButtonState: { + canUndo: boolean; + canRedo: boolean; + }; + + voiceTypingDialogShown: boolean; +} + +class NoteScreenComponent extends BaseScreenComponent implements BaseNoteScreenComponent { // This isn't in this.state because we don't want changing scroll to trigger // a re-render. private lastBodyScroll: number|undefined = undefined; + private saveActionQueues_: any; + private doFocusUpdate_: boolean; + private styles_: any; + private editorRef: any; + private titleTextFieldRef: RefObject; + private navHandler: OnNavigateCallback; + private backHandler: ()=> Promise; + private undoRedoService_: UndoRedoService; + private noteTagDialog_closeRequested: any; + private onJoplinLinkClick_: any; + private refreshResource: (resource: any, noteBody?: string)=> Promise; + private selection: any; + private menuOptionsCache_: Record; + private focusUpdateIID_: any; + private folderPickerOptions_: any; + public dialogbox: any; + public static navigationOptions(): any { return { header: null }; } - public constructor() { - super(); + public constructor(props: Props) { + super(props); + this.state = { note: Note.new(), mode: 'view', + readOnly: false, folder: null, lastSavedNote: null, isLoading: true, @@ -140,6 +206,8 @@ class NoteScreenComponent extends BaseScreenComponent { showImageEditor: false, imageEditorResource: null, noteResources: {}, + imageEditorResourceFilepath: null, + newAndNoTitleChangeNoteId: null, // HACK: For reasons I can't explain, when the WebView is present, the TextInput initially does not display (It's just a white rectangle with // no visible text). It will only appear when tapping it or doing certain action like selecting text on the webview. The bug started to @@ -164,8 +232,6 @@ class NoteScreenComponent extends BaseScreenComponent { this.doFocusUpdate_ = false; - this.saveButtonHasBeenShown_ = false; - this.styles_ = {}; this.editorRef = React.createRef(); @@ -341,7 +407,7 @@ class NoteScreenComponent extends BaseScreenComponent { } } - private async undoRedo(type: string) { + private async undoRedo(type: 'undo'|'redo') { const undoState = await this.undoRedoService_[type](this.undoState()); if (!undoState) return; @@ -811,7 +877,7 @@ class NoteScreenComponent extends BaseScreenComponent { this.setState({ note: newNote }); - this.refreshResource(resource, newNote.body); + void this.refreshResource(resource, newNote.body); this.scheduleSave(); @@ -1281,8 +1347,8 @@ class NoteScreenComponent extends BaseScreenComponent { let fieldToFocus = this.state.note.is_todo ? 'title' : 'body'; if (this.state.mode === 'view') fieldToFocus = ''; - if (fieldToFocus === 'title' && this.refs.titleTextField) { - this.refs.titleTextField.focus(); + if (fieldToFocus === 'title' && this.titleTextFieldRef.current) { + this.titleTextFieldRef.current.focus(); } // if (fieldToFocus === 'body' && this.markdownEditorRef.current) { // if (this.markdownEditorRef.current) { @@ -1504,8 +1570,6 @@ class NoteScreenComponent extends BaseScreenComponent { const showSaveButton = false; // this.state.mode === 'edit' || this.isModified() || this.saveButtonHasBeenShown_; const saveButtonDisabled = true;// !this.isModified(); - if (showSaveButton) this.saveButtonHasBeenShown_ = true; - const titleContainerStyle = isTodo ? this.styles().titleContainerTodo : this.styles().titleContainer; const dueDate = Note.dueDateObject(note); @@ -1514,7 +1578,7 @@ class NoteScreenComponent extends BaseScreenComponent { {isTodo && } void; + + scheduleFocusUpdate(): void; + attachFile(asset: any, fileType: any): void; + lastLoadedNoteId_?: string; +} + interface Shared { noteExists?: (noteId: string)=> Promise; handleNoteDeletedWhileEditing_?: (note: NoteEntity)=> Promise; - saveNoteButton_press?: (comp: any, folderId: string, options: any)=> Promise; - saveOneProperty?: (comp: any, name: string, value: any)=> void; - noteComponent_change?: (comp: any, propName: string, propValue: any)=> void; + saveNoteButton_press?: (comp: BaseNoteScreenComponent, folderId: string, options: any)=> Promise; + saveOneProperty?: (comp: BaseNoteScreenComponent, name: string, value: any)=> void; + noteComponent_change?: (comp: BaseNoteScreenComponent, propName: string, propValue: any)=> void; clearResourceCache?: ()=> void; attachedResources?: (noteBody: string)=> Promise; - isModified?: (comp: any)=> boolean; - initState?: (comp: any)=> void; - toggleIsTodo_onPress?: (comp: any)=> void; + isModified?: (comp: BaseNoteScreenComponent)=> boolean; + initState?: (comp: BaseNoteScreenComponent)=> Promise; + toggleIsTodo_onPress?: (comp: BaseNoteScreenComponent)=> void; toggleCheckboxRange?: (ipcMessage: string, noteBody: string)=> any; toggleCheckbox?: (ipcMessage: string, noteBody: string)=> string; installResourceHandling?: (refreshResourceHandler: any)=> void; @@ -54,7 +64,7 @@ shared.handleNoteDeletedWhileEditing_ = async (note: NoteEntity) => { return Note.load(newNote.id); }; -shared.saveNoteButton_press = async function(comp: any, folderId: string = null, options: any = null) { +shared.saveNoteButton_press = async function(comp: BaseNoteScreenComponent, folderId: string = null, options: any = null) { options = { autoTitle: true, ...options }; const releaseMutex = await saveNoteMutex_.acquire(); @@ -150,7 +160,7 @@ shared.saveNoteButton_press = async function(comp: any, folderId: string = null, releaseMutex(); }; -shared.saveOneProperty = async function(comp: any, name: string, value: any) { +shared.saveOneProperty = async function(comp: BaseNoteScreenComponent, name: string, value: any) { let note = { ...comp.state.note }; const recreatedNote = await shared.handleNoteDeletedWhileEditing_(note); @@ -167,7 +177,7 @@ shared.saveOneProperty = async function(comp: any, name: string, value: any) { }); }; -shared.noteComponent_change = function(comp: any, propName: string, propValue: any) { +shared.noteComponent_change = function(comp: BaseNoteScreenComponent, propName: string, propValue: any) { const newState: any = {}; const note = { ...comp.state.note }; @@ -211,14 +221,14 @@ shared.attachedResources = async function(noteBody: string) { return output; }; -shared.isModified = function(comp: any) { +shared.isModified = function(comp: BaseNoteScreenComponent) { if (!comp.state.note || !comp.state.lastSavedNote) return false; const diff = BaseModel.diffObjects(comp.state.lastSavedNote, comp.state.note); delete diff.type_; return !!Object.getOwnPropertyNames(diff).length; }; -shared.initState = async function(comp: any) { +shared.initState = async function(comp: BaseNoteScreenComponent) { const isProvisionalNote = comp.props.provisionalNoteIds.includes(comp.props.noteId); const note = await Note.load(comp.props.noteId); @@ -268,7 +278,7 @@ shared.initState = async function(comp: any) { comp.lastLoadedNoteId_ = note.id; }; -shared.toggleIsTodo_onPress = function(comp: any) { +shared.toggleIsTodo_onPress = function(comp: BaseNoteScreenComponent) { const newNote = Note.toggleIsTodo(comp.state.note); const newState = { note: newNote }; comp.setState(newState); diff --git a/packages/lib/services/NavService.ts b/packages/lib/services/NavService.ts index ea7783d51..4b0b168ac 100644 --- a/packages/lib/services/NavService.ts +++ b/packages/lib/services/NavService.ts @@ -1,9 +1,10 @@ +export type OnNavigateCallback = ()=> Promise; + export default class NavService { // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied public static dispatch: Function = () => {}; - // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied - private static handlers_: Function[] = []; + private static handlers_: OnNavigateCallback[] = []; public static async go(routeName: string) { if (this.handlers_.length) { @@ -15,10 +16,11 @@ export default class NavService { type: 'NAV_GO', routeName: routeName, }); + return false; } // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied - public static addHandler(handler: Function) { + public static addHandler(handler: OnNavigateCallback) { for (let i = this.handlers_.length - 1; i >= 0; i--) { const h = this.handlers_[i]; if (h === handler) return; @@ -28,7 +30,7 @@ export default class NavService { } // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied - public static removeHandler(hanlder: Function) { + public static removeHandler(hanlder: OnNavigateCallback) { for (let i = this.handlers_.length - 1; i >= 0; i--) { const h = this.handlers_[i]; if (h === hanlder) this.handlers_.splice(i, 1);