Files
standardnotes-app-web/app/assets/javascripts/Services/ViewControllerManager/ViewControllerManager.ts

299 lines
11 KiB
TypeScript

import { storage, StorageKey } from '@/Services/LocalStorage'
import { WebApplication, WebAppEvent } from '@/Application/Application'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { destroyAllObjectProperties, isDesktopApplication } from '@/Utils'
import {
ApplicationEvent,
ContentType,
DeinitSource,
PrefKey,
SNTag,
removeFromArray,
WebOrDesktopDeviceInterface,
} from '@standardnotes/snjs'
import { action, makeObservable, observable } from 'mobx'
import { ActionsMenuController } from '../../Controllers/ActionsMenuController'
import { FeaturesController } from '../../Controllers/FeaturesController'
import { FilesController } from '../../Controllers/FilesController'
import { NotesController } from '../../Controllers/NotesController'
import { ItemListController } from '../../Controllers/ItemList/ItemListController'
import { NoteTagsController } from '../../Controllers/NoteTagsController'
import { NoAccountWarningController } from '../../Controllers/NoAccountWarningController'
import { PreferencesController } from '../../Controllers/PreferencesController'
import { PurchaseFlowController } from '../../Controllers/PurchaseFlow/PurchaseFlowController'
import { QuickSettingsController } from '../../Controllers/QuickSettingsController'
import { SearchOptionsController } from '../../Controllers/SearchOptionsController'
import { SubscriptionController } from '../../Controllers/Subscription/SubscriptionController'
import { SyncStatusController } from '../../Controllers/SyncStatusController'
import { TagsController } from '../../Controllers/Navigation/TagsController'
import { FilePreviewModalController } from '../../Controllers/FilePreviewModalController'
import { SelectedItemsController } from '../../Controllers/SelectedItemsController'
import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem'
import { ViewControllerManagerEvent } from './ViewControllerManagerEvent'
import { EditorEventSource } from '../../Typings/EditorEventSource'
import { PanelResizedData } from '../../Typings/PanelResizedData'
type ObserverCallback = (event: ViewControllerManagerEvent, data?: unknown) => Promise<void>
export class ViewControllerManager {
readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures
observers: ObserverCallback[] = []
locked = true
unsubAppEventObserver!: () => void
webAppEventDisposer?: () => void
onVisibilityChange: () => void
showBetaWarning: boolean
dealloced = false
readonly accountMenuController: AccountMenuController
readonly actionsMenuController = new ActionsMenuController()
readonly featuresController: FeaturesController
readonly filePreviewModalController = new FilePreviewModalController()
readonly filesController: FilesController
readonly noAccountWarningController: NoAccountWarningController
readonly notesController: NotesController
readonly contentListController: ItemListController
readonly noteTagsController: NoteTagsController
readonly preferencesController = new PreferencesController()
readonly purchaseFlowController: PurchaseFlowController
readonly quickSettingsMenuController = new QuickSettingsController()
readonly searchOptionsController: SearchOptionsController
readonly subscriptionController: SubscriptionController
readonly syncStatusController = new SyncStatusController()
readonly navigationController: TagsController
readonly selectionController: SelectedItemsController
isSessionsModalVisible = false
private appEventObserverRemovers: (() => void)[] = []
constructor(public application: WebApplication, private device: WebOrDesktopDeviceInterface) {
this.selectionController = new SelectedItemsController(application, this, this.appEventObserverRemovers)
this.notesController = new NotesController(
application,
this,
async () => {
await this.notifyEvent(ViewControllerManagerEvent.ActiveEditorChanged)
},
this.appEventObserverRemovers,
)
this.featuresController = new FeaturesController(application, this.appEventObserverRemovers)
this.navigationController = new TagsController(
application,
this,
this.appEventObserverRemovers,
this.featuresController,
)
this.searchOptionsController = new SearchOptionsController(application, this.appEventObserverRemovers)
this.contentListController = new ItemListController(application, this, this.appEventObserverRemovers)
this.noteTagsController = new NoteTagsController(application, this, this.appEventObserverRemovers)
this.noAccountWarningController = new NoAccountWarningController(application, this.appEventObserverRemovers)
this.accountMenuController = new AccountMenuController(application, this.appEventObserverRemovers)
this.subscriptionController = new SubscriptionController(application, this.appEventObserverRemovers)
this.purchaseFlowController = new PurchaseFlowController(application)
this.filesController = new FilesController(application, this, this.appEventObserverRemovers)
this.addAppEventObserver()
this.onVisibilityChange = () => {
const visible = document.visibilityState === 'visible'
const event = visible ? ViewControllerManagerEvent.WindowDidFocus : ViewControllerManagerEvent.WindowDidBlur
this.notifyEvent(event).catch(console.error)
}
this.registerVisibilityObservers()
if (this.device.appVersion.includes('-beta')) {
this.showBetaWarning = storage.get(StorageKey.ShowBetaWarning) ?? true
} else {
this.showBetaWarning = false
}
makeObservable(this, {
showBetaWarning: observable,
isSessionsModalVisible: observable,
preferencesController: observable,
enableBetaWarning: action,
disableBetaWarning: action,
openSessionsModal: action,
closeSessionsModal: action,
})
}
deinit(source: DeinitSource): void {
this.dealloced = true
;(this.application as unknown) = undefined
if (source === DeinitSource.SignOut) {
storage.remove(StorageKey.ShowBetaWarning)
this.noAccountWarningController.reset()
}
this.unsubAppEventObserver?.()
;(this.unsubAppEventObserver as unknown) = undefined
this.observers.length = 0
this.appEventObserverRemovers.forEach((remover) => remover())
this.appEventObserverRemovers.length = 0
;(this.device as unknown) = undefined
this.webAppEventDisposer?.()
this.webAppEventDisposer = undefined
;(this.filePreviewModalController as unknown) = undefined
;(this.preferencesController as unknown) = undefined
;(this.quickSettingsMenuController as unknown) = undefined
;(this.syncStatusController as unknown) = undefined
this.actionsMenuController.reset()
;(this.actionsMenuController as unknown) = undefined
this.featuresController.deinit(source)
;(this.featuresController as unknown) = undefined
this.accountMenuController.deinit(source)
;(this.accountMenuController as unknown) = undefined
this.filesController.deinit(source)
;(this.filesController as unknown) = undefined
this.noAccountWarningController.deinit(source)
;(this.noAccountWarningController as unknown) = undefined
this.notesController.deinit(source)
;(this.notesController as unknown) = undefined
this.contentListController.deinit(source)
;(this.contentListController as unknown) = undefined
this.noteTagsController.deinit(source)
;(this.noteTagsController as unknown) = undefined
this.purchaseFlowController.deinit(source)
;(this.purchaseFlowController as unknown) = undefined
this.searchOptionsController.deinit(source)
;(this.searchOptionsController as unknown) = undefined
this.subscriptionController.deinit(source)
;(this.subscriptionController as unknown) = undefined
this.navigationController.deinit(source)
;(this.navigationController as unknown) = undefined
document.removeEventListener('visibilitychange', this.onVisibilityChange)
;(this.onVisibilityChange as unknown) = undefined
destroyAllObjectProperties(this)
}
openSessionsModal(): void {
this.isSessionsModalVisible = true
}
closeSessionsModal(): void {
this.isSessionsModalVisible = false
}
disableBetaWarning() {
this.showBetaWarning = false
storage.set(StorageKey.ShowBetaWarning, false)
}
enableBetaWarning() {
this.showBetaWarning = true
storage.set(StorageKey.ShowBetaWarning, true)
}
public get version(): string {
return this.device.appVersion
}
isGlobalSpellcheckEnabled(): boolean {
return this.application.getPreference(PrefKey.EditorSpellcheck, true)
}
async toggleGlobalSpellcheck() {
const currentValue = this.isGlobalSpellcheckEnabled()
return this.application.setPreference(PrefKey.EditorSpellcheck, !currentValue)
}
addAppEventObserver() {
this.unsubAppEventObserver = this.application.addEventObserver(async (eventName) => {
switch (eventName) {
case ApplicationEvent.Started:
this.locked = true
break
case ApplicationEvent.Launched:
this.locked = false
if (window.location.search.includes('purchase=true')) {
this.purchaseFlowController.openPurchaseFlow()
}
break
case ApplicationEvent.SyncStatusChanged:
this.syncStatusController.update(this.application.sync.getSyncStatus())
break
}
})
}
isLocked() {
return this.locked
}
registerVisibilityObservers() {
if (isDesktopApplication()) {
this.webAppEventDisposer = this.application.addWebEventObserver((event) => {
if (event === WebAppEvent.DesktopWindowGainedFocus) {
this.notifyEvent(ViewControllerManagerEvent.WindowDidFocus).catch(console.error)
} else if (event === WebAppEvent.DesktopWindowLostFocus) {
this.notifyEvent(ViewControllerManagerEvent.WindowDidBlur).catch(console.error)
}
})
} else {
/* Tab visibility listener, web only */
document.addEventListener('visibilitychange', this.onVisibilityChange)
}
}
addObserver(callback: ObserverCallback): () => void {
this.observers.push(callback)
const thislessObservers = this.observers
return () => {
removeFromArray(thislessObservers, callback)
}
}
async notifyEvent(eventName: ViewControllerManagerEvent, data?: unknown) {
for (const callback of this.observers) {
await callback(eventName, data)
}
}
/** Returns the tags that are referncing this note */
public getItemTags(item: ListableContentItem) {
return this.application.items.itemsReferencingItem(item).filter((ref) => {
return ref.content_type === ContentType.Tag
}) as SNTag[]
}
panelDidResize(name: string, collapsed: boolean) {
const data: PanelResizedData = {
panel: name,
collapsed: collapsed,
}
this.notifyEvent(ViewControllerManagerEvent.PanelResized, data).catch(console.error)
}
editorDidFocus(eventSource: EditorEventSource) {
this.notifyEvent(ViewControllerManagerEvent.EditorFocused, { eventSource: eventSource }).catch(console.error)
}
beganBackupDownload() {
this.notifyEvent(ViewControllerManagerEvent.BeganBackupDownload).catch(console.error)
}
endedBackupDownload(success: boolean) {
this.notifyEvent(ViewControllerManagerEvent.EndedBackupDownload, { success: success }).catch(console.error)
}
}