299 lines
11 KiB
TypeScript
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 '../../Types/EditorEventSource'
|
|
import { PanelResizedData } from '../../Types/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)
|
|
}
|
|
}
|