chore: app group optimizations (#1027)
This commit is contained in:
23
app/assets/javascripts/UIModels/AppState/AbstractState.ts
Normal file
23
app/assets/javascripts/UIModels/AppState/AbstractState.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { DeinitSource } from '@standardnotes/snjs'
|
||||
import { WebApplication } from '../Application'
|
||||
|
||||
export function isStateDealloced(state: AbstractState): boolean {
|
||||
return state.dealloced == undefined || state.dealloced === true
|
||||
}
|
||||
|
||||
export abstract class AbstractState {
|
||||
application: WebApplication
|
||||
appState?: AbstractState
|
||||
dealloced = false
|
||||
|
||||
constructor(application: WebApplication, appState?: AbstractState) {
|
||||
this.application = application
|
||||
this.appState = appState
|
||||
}
|
||||
|
||||
deinit(_source: DeinitSource): void {
|
||||
this.dealloced = true
|
||||
;(this.application as unknown) = undefined
|
||||
;(this.appState as unknown) = undefined
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { isDev } from '@/Utils'
|
||||
import { destroyAllObjectProperties, isDev } from '@/Utils'
|
||||
import { action, computed, makeObservable, observable, runInAction } from 'mobx'
|
||||
import { ApplicationEvent, ContentType, SNNote, SNTag } from '@standardnotes/snjs'
|
||||
import { ApplicationEvent, ContentType, DeinitSource, SNNote, SNTag } from '@standardnotes/snjs'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { AccountMenuPane } from '@/Components/AccountMenu'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
type StructuredItemsCount = {
|
||||
notes: number
|
||||
@@ -11,7 +12,7 @@ type StructuredItemsCount = {
|
||||
archived: number
|
||||
}
|
||||
|
||||
export class AccountMenuState {
|
||||
export class AccountMenuState extends AbstractState {
|
||||
show = false
|
||||
signingOut = false
|
||||
otherSessionsSignOut = false
|
||||
@@ -26,7 +27,15 @@ export class AccountMenuState {
|
||||
shouldAnimateCloseMenu = false
|
||||
currentPane = AccountMenuPane.GeneralMenu
|
||||
|
||||
constructor(private application: WebApplication, private appEventListeners: (() => void)[]) {
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.notesAndTags as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
constructor(application: WebApplication, private appEventListeners: (() => void)[]) {
|
||||
super(application)
|
||||
makeObservable(this, {
|
||||
show: observable,
|
||||
signingOut: observable,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { storage, StorageKey } from '@/Services/LocalStorage'
|
||||
import { WebApplication, WebAppEvent } from '@/UIModels/Application'
|
||||
import { AccountMenuState } from '@/UIModels/AppState/AccountMenuState'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import { destroyAllObjectProperties, isDesktopApplication } from '@/Utils'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
ContentType,
|
||||
@@ -33,6 +33,7 @@ import { SubscriptionState } from './SubscriptionState'
|
||||
import { SyncState } from './SyncState'
|
||||
import { TagsState } from './TagsState'
|
||||
import { FilePreviewModalState } from './FilePreviewModalState'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export enum AppStateEvent {
|
||||
TagChanged,
|
||||
@@ -57,35 +58,34 @@ export enum EventSource {
|
||||
|
||||
type ObserverCallback = (event: AppStateEvent, data?: any) => Promise<void>
|
||||
|
||||
export class AppState {
|
||||
export class AppState extends AbstractState {
|
||||
readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures
|
||||
|
||||
application: WebApplication
|
||||
observers: ObserverCallback[] = []
|
||||
locked = true
|
||||
unsubApp: any
|
||||
unsubAppEventObserver!: () => void
|
||||
webAppEventDisposer?: () => void
|
||||
onVisibilityChange: any
|
||||
onVisibilityChange: () => void
|
||||
showBetaWarning: boolean
|
||||
|
||||
private multiEditorSupport = false
|
||||
|
||||
readonly quickSettingsMenu = new QuickSettingsState()
|
||||
readonly accountMenu: AccountMenuState
|
||||
readonly actionsMenu = new ActionsMenuState()
|
||||
readonly features: FeaturesState
|
||||
readonly filePreviewModal = new FilePreviewModalState()
|
||||
readonly files: FilesState
|
||||
readonly noAccountWarning: NoAccountWarningState
|
||||
readonly notes: NotesState
|
||||
readonly notesView: NotesViewState
|
||||
readonly noteTags: NoteTagsState
|
||||
readonly preferences = new PreferencesState()
|
||||
readonly purchaseFlow: PurchaseFlowState
|
||||
readonly noAccountWarning: NoAccountWarningState
|
||||
readonly noteTags: NoteTagsState
|
||||
readonly sync = new SyncState()
|
||||
readonly quickSettingsMenu = new QuickSettingsState()
|
||||
readonly searchOptions: SearchOptionsState
|
||||
readonly notes: NotesState
|
||||
readonly features: FeaturesState
|
||||
readonly tags: TagsState
|
||||
readonly notesView: NotesViewState
|
||||
readonly subscription: SubscriptionState
|
||||
readonly files: FilesState
|
||||
readonly filePreviewModal = new FilePreviewModalState()
|
||||
readonly sync = new SyncState()
|
||||
readonly tags: TagsState
|
||||
|
||||
isSessionsModalVisible = false
|
||||
|
||||
@@ -94,7 +94,8 @@ export class AppState {
|
||||
private readonly tagChangedDisposer: IReactionDisposer
|
||||
|
||||
constructor(application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
||||
this.application = application
|
||||
super(application)
|
||||
|
||||
this.notes = new NotesState(
|
||||
application,
|
||||
this,
|
||||
@@ -103,6 +104,7 @@ export class AppState {
|
||||
},
|
||||
this.appEventObserverRemovers,
|
||||
)
|
||||
|
||||
this.noteTags = new NoteTagsState(application, this, this.appEventObserverRemovers)
|
||||
this.features = new FeaturesState(application, this.appEventObserverRemovers)
|
||||
this.tags = new TagsState(application, this.appEventObserverRemovers, this.features)
|
||||
@@ -144,41 +146,72 @@ export class AppState {
|
||||
this.tagChangedDisposer = this.tagChangedNotifier()
|
||||
}
|
||||
|
||||
deinit(source: DeinitSource): void {
|
||||
override deinit(source: DeinitSource): void {
|
||||
super.deinit(source)
|
||||
|
||||
if (source === DeinitSource.SignOut) {
|
||||
storage.remove(StorageKey.ShowBetaWarning)
|
||||
this.noAccountWarning.reset()
|
||||
}
|
||||
;(this.application as unknown) = undefined
|
||||
this.actionsMenu.reset()
|
||||
this.unsubApp?.()
|
||||
this.unsubApp = undefined
|
||||
|
||||
this.unsubAppEventObserver?.()
|
||||
;(this.unsubAppEventObserver as unknown) = undefined
|
||||
this.observers.length = 0
|
||||
|
||||
this.appEventObserverRemovers.forEach((remover) => remover())
|
||||
this.appEventObserverRemovers.length = 0
|
||||
;(this.features as unknown) = undefined
|
||||
;(this.device as unknown) = undefined
|
||||
|
||||
this.webAppEventDisposer?.()
|
||||
this.webAppEventDisposer = undefined
|
||||
;(this.quickSettingsMenu as unknown) = undefined
|
||||
;(this.accountMenu as unknown) = undefined
|
||||
;(this.actionsMenu as unknown) = undefined
|
||||
;(this.filePreviewModal as unknown) = undefined
|
||||
;(this.preferences as unknown) = undefined
|
||||
;(this.purchaseFlow as unknown) = undefined
|
||||
;(this.noteTags as unknown) = undefined
|
||||
;(this.quickSettingsMenu as unknown) = undefined
|
||||
;(this.sync as unknown) = undefined
|
||||
;(this.searchOptions as unknown) = undefined
|
||||
;(this.notes as unknown) = undefined
|
||||
|
||||
this.actionsMenu.reset()
|
||||
;(this.actionsMenu as unknown) = undefined
|
||||
|
||||
this.features.deinit(source)
|
||||
;(this.features as unknown) = undefined
|
||||
;(this.tags as unknown) = undefined
|
||||
|
||||
this.accountMenu.deinit(source)
|
||||
;(this.accountMenu as unknown) = undefined
|
||||
|
||||
this.files.deinit(source)
|
||||
;(this.files as unknown) = undefined
|
||||
|
||||
this.noAccountWarning.deinit(source)
|
||||
;(this.noAccountWarning as unknown) = undefined
|
||||
|
||||
this.notes.deinit(source)
|
||||
;(this.notes as unknown) = undefined
|
||||
|
||||
this.notesView.deinit(source)
|
||||
;(this.notesView as unknown) = undefined
|
||||
|
||||
this.noteTags.deinit(source)
|
||||
;(this.noteTags as unknown) = undefined
|
||||
|
||||
this.purchaseFlow.deinit(source)
|
||||
;(this.purchaseFlow as unknown) = undefined
|
||||
|
||||
this.searchOptions.deinit(source)
|
||||
;(this.searchOptions as unknown) = undefined
|
||||
|
||||
this.subscription.deinit(source)
|
||||
;(this.subscription as unknown) = undefined
|
||||
|
||||
this.tags.deinit(source)
|
||||
;(this.tags as unknown) = undefined
|
||||
|
||||
document.removeEventListener('visibilitychange', this.onVisibilityChange)
|
||||
this.onVisibilityChange = undefined
|
||||
;(this.onVisibilityChange as unknown) = undefined
|
||||
|
||||
this.tagChangedDisposer()
|
||||
;(this.tagChangedDisposer as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
openSessionsModal(): void {
|
||||
@@ -333,7 +366,7 @@ export class AppState {
|
||||
}
|
||||
|
||||
addAppEventObserver() {
|
||||
this.unsubApp = this.application.addEventObserver(async (eventName) => {
|
||||
this.unsubAppEventObserver = this.application.addEventObserver(async (eventName) => {
|
||||
switch (eventName) {
|
||||
case ApplicationEvent.Started:
|
||||
this.locked = true
|
||||
@@ -370,11 +403,12 @@ export class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns A function that unregisters this observer */
|
||||
addObserver(callback: ObserverCallback) {
|
||||
addObserver(callback: ObserverCallback): () => void {
|
||||
this.observers.push(callback)
|
||||
|
||||
const thislessObservers = this.observers
|
||||
return () => {
|
||||
removeFromArray(this.observers, callback)
|
||||
removeFromArray(thislessObservers, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { ApplicationEvent, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import { ApplicationEvent, DeinitSource, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
|
||||
import { action, makeObservable, observable, runInAction, when } from 'mobx'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export class FeaturesState {
|
||||
export class FeaturesState extends AbstractState {
|
||||
hasFolders: boolean
|
||||
hasSmartViews: boolean
|
||||
hasFiles: boolean
|
||||
premiumAlertFeatureName: string | undefined
|
||||
|
||||
constructor(private application: WebApplication, appObservers: (() => void)[]) {
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.showPremiumAlert as unknown) = undefined
|
||||
;(this.closePremiumAlert as unknown) = undefined
|
||||
;(this.hasFolders as unknown) = undefined
|
||||
;(this.hasSmartViews as unknown) = undefined
|
||||
;(this.hasFiles as unknown) = undefined
|
||||
;(this.premiumAlertFeatureName as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
constructor(application: WebApplication, appObservers: (() => void)[]) {
|
||||
super(application)
|
||||
|
||||
this.hasFolders = this.isEntitledToFolders()
|
||||
this.hasSmartViews = this.isEntitledToSmartViews()
|
||||
this.hasFiles = this.isEntitledToFiles()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SNFile } from '@standardnotes/snjs/dist/@types'
|
||||
import { FileItem } from '@standardnotes/snjs'
|
||||
import { action, makeObservable, observable } from 'mobx'
|
||||
|
||||
export class FilePreviewModalState {
|
||||
isOpen = false
|
||||
currentFile: SNFile | undefined = undefined
|
||||
otherFiles: SNFile[] = []
|
||||
currentFile: FileItem | undefined = undefined
|
||||
otherFiles: FileItem[] = []
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
@@ -18,11 +18,11 @@ export class FilePreviewModalState {
|
||||
})
|
||||
}
|
||||
|
||||
setCurrentFile = (currentFile: SNFile) => {
|
||||
setCurrentFile = (currentFile: FileItem) => {
|
||||
this.currentFile = currentFile
|
||||
}
|
||||
|
||||
activate = (currentFile: SNFile, otherFiles: SNFile[]) => {
|
||||
activate = (currentFile: FileItem, otherFiles: FileItem[]) => {
|
||||
this.currentFile = currentFile
|
||||
this.otherFiles = otherFiles
|
||||
this.isOpen = true
|
||||
|
||||
@@ -7,14 +7,12 @@ import {
|
||||
ClassicFileSaver,
|
||||
parseFileName,
|
||||
} from '@standardnotes/filepicker'
|
||||
import { ClientDisplayableError, SNFile } from '@standardnotes/snjs'
|
||||
import { ClientDisplayableError, FileItem } from '@standardnotes/snjs'
|
||||
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/stylekit'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export class FilesState {
|
||||
constructor(private application: WebApplication) {}
|
||||
|
||||
public async downloadFile(file: SNFile): Promise<void> {
|
||||
export class FilesState extends AbstractState {
|
||||
public async downloadFile(file: FileItem): Promise<void> {
|
||||
let downloadingToastId = ''
|
||||
|
||||
try {
|
||||
@@ -102,7 +100,7 @@ export class FilesState {
|
||||
return
|
||||
}
|
||||
|
||||
const uploadedFiles: SNFile[] = []
|
||||
const uploadedFiles: FileItem[] = []
|
||||
|
||||
for (const file of selectedFiles) {
|
||||
if (!shouldUseStreamingReader && maxFileSize && file.size >= maxFileSize) {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { storage, StorageKey } from '@/Services/LocalStorage'
|
||||
import { SNApplication, ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { runInAction, makeObservable, observable, action } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export class NoAccountWarningState {
|
||||
export class NoAccountWarningState extends AbstractState {
|
||||
show: boolean
|
||||
constructor(application: SNApplication, appObservers: (() => void)[]) {
|
||||
|
||||
constructor(application: WebApplication, appObservers: (() => void)[]) {
|
||||
super(application)
|
||||
|
||||
this.show = application.hasAccount() ? false : storage.get(StorageKey.ShowNoAccountWarning) ?? true
|
||||
|
||||
appObservers.push(
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ElementIds } from '@/ElementIDs'
|
||||
import { ApplicationEvent, ContentType, PrefKey, SNNote, SNTag, UuidString } from '@standardnotes/snjs'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import { ApplicationEvent, ContentType, DeinitSource, PrefKey, SNNote, SNTag, UuidString } from '@standardnotes/snjs'
|
||||
import { action, computed, makeObservable, observable } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
import { AppState } from './AppState'
|
||||
|
||||
export class NoteTagsState {
|
||||
export class NoteTagsState extends AbstractState {
|
||||
autocompleteInputFocused = false
|
||||
autocompleteSearchQuery = ''
|
||||
autocompleteTagHintFocused = false
|
||||
@@ -15,7 +17,17 @@ export class NoteTagsState {
|
||||
tagsContainerMaxWidth: number | 'auto' = 0
|
||||
addNoteToParentFolders: boolean
|
||||
|
||||
constructor(private application: WebApplication, private appState: AppState, appEventListeners: (() => void)[]) {
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.tags as unknown) = undefined
|
||||
;(this.autocompleteTagResults as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
constructor(application: WebApplication, override appState: AppState, appEventListeners: (() => void)[]) {
|
||||
super(application, appState)
|
||||
|
||||
makeObservable(this, {
|
||||
autocompleteInputFocused: observable,
|
||||
autocompleteSearchQuery: observable,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import { confirmDialog } from '@/Services/AlertService'
|
||||
import { KeyboardModifier } from '@/Services/IOService'
|
||||
import { StringEmptyTrash, Strings, StringUtils } from '@/Strings'
|
||||
@@ -10,12 +11,14 @@ import {
|
||||
SNTag,
|
||||
ChallengeReason,
|
||||
NoteViewController,
|
||||
DeinitSource,
|
||||
} from '@standardnotes/snjs'
|
||||
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AppState } from './AppState'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export class NotesState {
|
||||
export class NotesState extends AbstractState {
|
||||
lastSelectedNote: SNNote | undefined
|
||||
selectedNotes: Record<UuidString, SNNote> = {}
|
||||
contextMenuOpen = false
|
||||
@@ -28,12 +31,23 @@ export class NotesState {
|
||||
showProtectedWarning = false
|
||||
showRevisionHistoryModal = false
|
||||
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.lastSelectedNote as unknown) = undefined
|
||||
;(this.selectedNotes as unknown) = undefined
|
||||
;(this.onActiveEditorChanged as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
constructor(
|
||||
private application: WebApplication,
|
||||
private appState: AppState,
|
||||
application: WebApplication,
|
||||
public override appState: AppState,
|
||||
private onActiveEditorChanged: () => Promise<void>,
|
||||
appEventListeners: (() => void)[],
|
||||
) {
|
||||
super(application, appState)
|
||||
|
||||
makeObservable(this, {
|
||||
selectedNotes: observable,
|
||||
contextMenuOpen: observable,
|
||||
@@ -75,6 +89,10 @@ export class NotesState {
|
||||
}
|
||||
|
||||
get selectedNotesCount(): number {
|
||||
if (this.dealloced) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Object.keys(this.selectedNotes).length
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
CollectionSort,
|
||||
CollectionSortProperty,
|
||||
ContentType,
|
||||
DeinitSource,
|
||||
findInArray,
|
||||
NotesDisplayCriteria,
|
||||
PrefKey,
|
||||
@@ -15,6 +17,7 @@ import {
|
||||
import { action, autorun, computed, makeObservable, observable, reaction } from 'mobx'
|
||||
import { AppState, AppStateEvent } from '.'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
const MIN_NOTE_CELL_HEIGHT = 51.0
|
||||
const DEFAULT_LIST_NUM_NOTES = 20
|
||||
@@ -34,7 +37,7 @@ export type DisplayOptions = {
|
||||
hideEditorIcon: boolean
|
||||
}
|
||||
|
||||
export class NotesViewState {
|
||||
export class NotesViewState extends AbstractState {
|
||||
completedFullSync = false
|
||||
noteFilterText = ''
|
||||
notes: SNNote[] = []
|
||||
@@ -59,22 +62,34 @@ export class NotesViewState {
|
||||
hideEditorIcon: false,
|
||||
}
|
||||
|
||||
constructor(private application: WebApplication, private appState: AppState, appObservers: (() => void)[]) {
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.noteFilterText as unknown) = undefined
|
||||
;(this.notes as unknown) = undefined
|
||||
;(this.renderedNotes as unknown) = undefined
|
||||
;(this.selectedNotes as unknown) = undefined
|
||||
;(window.onresize as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
constructor(application: WebApplication, override appState: AppState, appObservers: (() => void)[]) {
|
||||
super(application, appState)
|
||||
|
||||
this.resetPagination()
|
||||
|
||||
appObservers.push(
|
||||
application.streamItems<SNNote>(ContentType.Note, () => {
|
||||
this.reloadNotes()
|
||||
|
||||
const activeNote = this.appState.notes.activeNoteController?.note
|
||||
const activeNote = appState.notes.activeNoteController?.note
|
||||
|
||||
if (this.application.getAppState().notes.selectedNotesCount < 2) {
|
||||
if (appState.notes.selectedNotesCount < 2) {
|
||||
if (activeNote) {
|
||||
const browsingTrashedNotes =
|
||||
this.appState.selectedTag instanceof SmartView &&
|
||||
this.appState.selectedTag?.uuid === SystemViewId.TrashedNotes
|
||||
appState.selectedTag instanceof SmartView && appState.selectedTag?.uuid === SystemViewId.TrashedNotes
|
||||
|
||||
if (activeNote.trashed && !browsingTrashedNotes && !this.appState?.searchOptions.includeTrashed) {
|
||||
if (activeNote.trashed && !browsingTrashedNotes && !appState?.searchOptions.includeTrashed) {
|
||||
this.selectNextOrCreateNew()
|
||||
} else if (!this.selectedNotes[activeNote.uuid]) {
|
||||
this.selectNote(activeNote).catch(console.error)
|
||||
@@ -91,7 +106,7 @@ export class NotesViewState {
|
||||
this.reloadNotesDisplayOptions()
|
||||
this.reloadNotes()
|
||||
|
||||
if (this.appState.selectedTag && findInArray(tags, 'uuid', this.appState.selectedTag.uuid)) {
|
||||
if (appState.selectedTag && findInArray(tags, 'uuid', appState.selectedTag.uuid)) {
|
||||
/** Tag title could have changed */
|
||||
this.reloadPanelTitle()
|
||||
}
|
||||
@@ -100,7 +115,7 @@ export class NotesViewState {
|
||||
this.reloadPreferences()
|
||||
}, ApplicationEvent.PreferencesChanged),
|
||||
application.addEventObserver(async () => {
|
||||
this.appState.closeAllNoteControllers()
|
||||
appState.closeAllNoteControllers()
|
||||
this.selectFirstNote()
|
||||
this.setCompletedFullSync(false)
|
||||
}, ApplicationEvent.SignedIn),
|
||||
@@ -108,20 +123,22 @@ export class NotesViewState {
|
||||
this.reloadNotes()
|
||||
if (
|
||||
this.notes.length === 0 &&
|
||||
this.appState.selectedTag instanceof SmartView &&
|
||||
this.appState.selectedTag.uuid === SystemViewId.AllNotes &&
|
||||
appState.selectedTag instanceof SmartView &&
|
||||
appState.selectedTag.uuid === SystemViewId.AllNotes &&
|
||||
this.noteFilterText === '' &&
|
||||
!this.appState.notes.activeNoteController
|
||||
!appState.notes.activeNoteController
|
||||
) {
|
||||
this.createPlaceholderNote()?.catch(console.error)
|
||||
}
|
||||
this.setCompletedFullSync(true)
|
||||
}, ApplicationEvent.CompletedFullSync),
|
||||
|
||||
autorun(() => {
|
||||
if (appState.notes.selectedNotes) {
|
||||
this.syncSelectedNotes()
|
||||
}
|
||||
}),
|
||||
|
||||
reaction(
|
||||
() => [
|
||||
appState.searchOptions.includeProtectedContents,
|
||||
@@ -133,6 +150,7 @@ export class NotesViewState {
|
||||
this.reloadNotes()
|
||||
},
|
||||
),
|
||||
|
||||
appState.addObserver(async (eventName) => {
|
||||
if (eventName === AppStateEvent.TagChanged) {
|
||||
this.handleTagChange()
|
||||
@@ -414,6 +432,7 @@ export class NotesViewState {
|
||||
|
||||
selectPreviousNote = () => {
|
||||
const displayableNotes = this.notes
|
||||
|
||||
if (this.activeEditorNote) {
|
||||
const currentIndex = displayableNotes.indexOf(this.activeEditorNote)
|
||||
if (currentIndex - 1 >= 0) {
|
||||
@@ -426,11 +445,13 @@ export class NotesViewState {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
setNoteFilterText = (text: string) => {
|
||||
this.noteFilterText = text
|
||||
this.handleFilterTextChanged()
|
||||
}
|
||||
|
||||
syncSelectedNotes = () => {
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
|
||||
import { action, makeObservable, observable } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export enum PurchaseFlowPane {
|
||||
SignIn,
|
||||
CreateAccount,
|
||||
}
|
||||
|
||||
export class PurchaseFlowState {
|
||||
export class PurchaseFlowState extends AbstractState {
|
||||
isOpen = false
|
||||
currentPane = PurchaseFlowPane.CreateAccount
|
||||
|
||||
constructor(private application: WebApplication) {
|
||||
constructor(application: WebApplication) {
|
||||
super(application)
|
||||
|
||||
makeObservable(this, {
|
||||
isOpen: observable,
|
||||
currentPane: observable,
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { makeObservable, observable, action, runInAction } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
export class SearchOptionsState {
|
||||
export class SearchOptionsState extends AbstractState {
|
||||
includeProtectedContents = false
|
||||
includeArchived = false
|
||||
includeTrashed = false
|
||||
|
||||
constructor(private application: WebApplication, appObservers: (() => void)[]) {
|
||||
constructor(application: WebApplication, appObservers: (() => void)[]) {
|
||||
super(application)
|
||||
|
||||
makeObservable(this, {
|
||||
includeProtectedContents: observable,
|
||||
includeTrashed: observable,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { ApplicationEvent, ClientDisplayableError, convertTimestampToMilliseconds } from '@standardnotes/snjs'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
ClientDisplayableError,
|
||||
convertTimestampToMilliseconds,
|
||||
DeinitSource,
|
||||
} from '@standardnotes/snjs'
|
||||
import { action, computed, makeObservable, observable } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { AbstractState } from './AbstractState'
|
||||
|
||||
type Subscription = {
|
||||
planName: string
|
||||
@@ -14,11 +21,21 @@ type AvailableSubscriptions = {
|
||||
}
|
||||
}
|
||||
|
||||
export class SubscriptionState {
|
||||
export class SubscriptionState extends AbstractState {
|
||||
userSubscription: Subscription | undefined = undefined
|
||||
availableSubscriptions: AvailableSubscriptions | undefined = undefined
|
||||
|
||||
constructor(private application: WebApplication, appObservers: (() => void)[]) {
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.userSubscription as unknown) = undefined
|
||||
;(this.availableSubscriptions as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
constructor(application: WebApplication, appObservers: (() => void)[]) {
|
||||
super(application)
|
||||
|
||||
makeObservable(this, {
|
||||
userSubscription: observable,
|
||||
availableSubscriptions: observable,
|
||||
|
||||
@@ -12,10 +12,13 @@ import {
|
||||
UuidString,
|
||||
isSystemView,
|
||||
FindItem,
|
||||
DeinitSource,
|
||||
} from '@standardnotes/snjs'
|
||||
import { action, computed, makeAutoObservable, makeObservable, observable, runInAction } from 'mobx'
|
||||
import { WebApplication } from '../Application'
|
||||
import { FeaturesState } from './FeaturesState'
|
||||
import { AbstractState } from './AbstractState'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
|
||||
type AnyTag = SNTag | SmartView
|
||||
|
||||
@@ -56,7 +59,7 @@ const isValidFutureSiblings = (application: SNApplication, futureSiblings: SNTag
|
||||
return true
|
||||
}
|
||||
|
||||
export class TagsState {
|
||||
export class TagsState extends AbstractState {
|
||||
tags: SNTag[] = []
|
||||
smartViews: SmartView[] = []
|
||||
allNotesCount_ = 0
|
||||
@@ -75,7 +78,9 @@ export class TagsState {
|
||||
|
||||
private readonly tagsCountsState: TagsCountsState
|
||||
|
||||
constructor(private application: WebApplication, appEventListeners: (() => void)[], private features: FeaturesState) {
|
||||
constructor(application: WebApplication, appEventListeners: (() => void)[], private features: FeaturesState) {
|
||||
super(application)
|
||||
|
||||
this.tagsCountsState = new TagsCountsState(this.application)
|
||||
|
||||
this.selected_ = undefined
|
||||
@@ -164,6 +169,19 @@ export class TagsState {
|
||||
)
|
||||
}
|
||||
|
||||
override deinit(source: DeinitSource) {
|
||||
super.deinit(source)
|
||||
;(this.features as unknown) = undefined
|
||||
;(this.tags as unknown) = undefined
|
||||
;(this.smartViews as unknown) = undefined
|
||||
;(this.selected_ as unknown) = undefined
|
||||
;(this.previouslySelected_ as unknown) = undefined
|
||||
;(this.editing_ as unknown) = undefined
|
||||
;(this.addingSubtagTo as unknown) = undefined
|
||||
|
||||
destroyAllObjectProperties(this)
|
||||
}
|
||||
|
||||
async createSubtagAndAssignParent(parent: SNTag, title: string) {
|
||||
const hasEmptyTitle = title.length === 0
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Runtime,
|
||||
DesktopDeviceInterface,
|
||||
isDesktopDevice,
|
||||
DeinitMode,
|
||||
} from '@standardnotes/snjs'
|
||||
|
||||
type WebServices = {
|
||||
@@ -68,13 +69,10 @@ export class WebApplication extends SNApplication {
|
||||
this.iconsController = new IconsController()
|
||||
}
|
||||
|
||||
override deinit(source: DeinitSource): void {
|
||||
super.deinit(source)
|
||||
try {
|
||||
if (source === DeinitSource.AppGroupUnload) {
|
||||
this.getThemeService().deactivateAllThemes()
|
||||
}
|
||||
override deinit(mode: DeinitMode, source: DeinitSource): void {
|
||||
super.deinit(mode, source)
|
||||
|
||||
try {
|
||||
for (const service of Object.values(this.webServices)) {
|
||||
if (!service) {
|
||||
continue
|
||||
@@ -88,7 +86,10 @@ export class WebApplication extends SNApplication {
|
||||
}
|
||||
|
||||
this.webServices = {} as WebServices
|
||||
|
||||
this.noteControllerGroup.deinit()
|
||||
;(this.noteControllerGroup as unknown) = undefined
|
||||
|
||||
this.webEventObservers.length = 0
|
||||
} catch (error) {
|
||||
console.error('Error while deiniting application', error)
|
||||
|
||||
@@ -16,10 +16,47 @@ import { AutolockService } from '@/Services/AutolockService'
|
||||
import { ThemeManager } from '@/Services/ThemeManager'
|
||||
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
||||
|
||||
const createApplication = (
|
||||
descriptor: ApplicationDescriptor,
|
||||
deviceInterface: WebOrDesktopDevice,
|
||||
defaultSyncServerHost: string,
|
||||
device: WebOrDesktopDevice,
|
||||
runtime: Runtime,
|
||||
webSocketUrl: string,
|
||||
) => {
|
||||
const platform = getPlatform()
|
||||
|
||||
const application = new WebApplication(
|
||||
deviceInterface,
|
||||
platform,
|
||||
descriptor.identifier,
|
||||
defaultSyncServerHost,
|
||||
webSocketUrl,
|
||||
runtime,
|
||||
)
|
||||
|
||||
const appState = new AppState(application, device)
|
||||
const archiveService = new ArchiveManager(application)
|
||||
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
|
||||
const autolockService = new AutolockService(application, new InternalEventBus())
|
||||
const themeService = new ThemeManager(application)
|
||||
|
||||
application.setWebServices({
|
||||
appState,
|
||||
archiveService,
|
||||
desktopService: isDesktopDevice(device) ? new DesktopManager(application, device) : undefined,
|
||||
io,
|
||||
autolockService,
|
||||
themeService,
|
||||
})
|
||||
|
||||
return application
|
||||
}
|
||||
|
||||
export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
||||
constructor(
|
||||
private defaultSyncServerHost: string,
|
||||
private device: WebOrDesktopDevice,
|
||||
device: WebOrDesktopDevice,
|
||||
private runtime: Runtime,
|
||||
private webSocketUrl: string,
|
||||
) {
|
||||
@@ -27,8 +64,14 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
||||
}
|
||||
|
||||
override async initialize(): Promise<void> {
|
||||
const defaultSyncServerHost = this.defaultSyncServerHost
|
||||
const runtime = this.runtime
|
||||
const webSocketUrl = this.webSocketUrl
|
||||
|
||||
await super.initialize({
|
||||
applicationCreator: this.createApplication,
|
||||
applicationCreator: async (descriptor, device) => {
|
||||
return createApplication(descriptor, device, defaultSyncServerHost, device, runtime, webSocketUrl)
|
||||
},
|
||||
})
|
||||
|
||||
if (isDesktopApplication()) {
|
||||
@@ -38,37 +81,15 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
||||
}
|
||||
}
|
||||
|
||||
override handleAllWorkspacesSignedOut(): void {
|
||||
isDesktopDevice(this.deviceInterface) && this.deviceInterface.destroyAllData()
|
||||
override deinit() {
|
||||
super.deinit()
|
||||
|
||||
if (isDesktopApplication()) {
|
||||
delete window.webClient
|
||||
}
|
||||
}
|
||||
|
||||
private createApplication = (descriptor: ApplicationDescriptor, deviceInterface: WebOrDesktopDevice) => {
|
||||
const platform = getPlatform()
|
||||
|
||||
const application = new WebApplication(
|
||||
deviceInterface,
|
||||
platform,
|
||||
descriptor.identifier,
|
||||
this.defaultSyncServerHost,
|
||||
this.webSocketUrl,
|
||||
this.runtime,
|
||||
)
|
||||
|
||||
const appState = new AppState(application, this.device)
|
||||
const archiveService = new ArchiveManager(application)
|
||||
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
|
||||
const autolockService = new AutolockService(application, new InternalEventBus())
|
||||
const themeService = new ThemeManager(application)
|
||||
|
||||
application.setWebServices({
|
||||
appState,
|
||||
archiveService,
|
||||
desktopService: isDesktopDevice(this.device) ? new DesktopManager(application, this.device) : undefined,
|
||||
io,
|
||||
autolockService,
|
||||
themeService,
|
||||
})
|
||||
|
||||
return application
|
||||
override handleAllWorkspacesSignedOut(): void {
|
||||
isDesktopDevice(this.device) && this.device.destroyAllData()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user