refactor: pass sub controllers to controllers instead of passing global controller manager (#1061)

This commit is contained in:
Mo
2022-06-01 12:56:30 -05:00
committed by GitHub
parent 721cf8df35
commit a87e3b98e2
40 changed files with 672 additions and 591 deletions

View File

@@ -17,8 +17,15 @@ import {
DesktopDeviceInterface,
isDesktopDevice,
DeinitMode,
PrefKey,
SNTag,
ContentType,
DecryptedItemInterface,
} from '@standardnotes/snjs'
import { makeObservable, observable } from 'mobx'
import { PanelResizedData } from '@/Types/PanelResizedData'
import { WebAppEvent } from './WebAppEvent'
import { isDesktopApplication } from '@/Utils'
type WebServices = {
viewControllerManager: ViewControllerManager
@@ -29,19 +36,14 @@ type WebServices = {
io: IOService
}
export enum WebAppEvent {
NewUpdateAvailable = 'NewUpdateAvailable',
DesktopWindowGainedFocus = 'DesktopWindowGainedFocus',
DesktopWindowLostFocus = 'DesktopWindowLostFocus',
}
export type WebEventObserver = (event: WebAppEvent) => void
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
export class WebApplication extends SNApplication {
private webServices!: WebServices
private webEventObservers: WebEventObserver[] = []
public noteControllerGroup: NoteGroupController
public iconsController: IconsController
private onVisibilityChange: () => void
constructor(
deviceInterface: WebOrDesktopDevice,
@@ -70,6 +72,16 @@ export class WebApplication extends SNApplication {
deviceInterface.setApplication(this)
this.noteControllerGroup = new NoteGroupController(this)
this.iconsController = new IconsController()
this.onVisibilityChange = () => {
const visible = document.visibilityState === 'visible'
const event = visible ? WebAppEvent.WindowDidFocus : WebAppEvent.WindowDidBlur
this.notifyWebEvent(event)
}
if (!isDesktopApplication()) {
document.addEventListener('visibilitychange', this.onVisibilityChange)
}
}
override deinit(mode: DeinitMode, source: DeinitSource): void {
@@ -94,6 +106,9 @@ export class WebApplication extends SNApplication {
;(this.noteControllerGroup as unknown) = undefined
this.webEventObservers.length = 0
document.removeEventListener('visibilitychange', this.onVisibilityChange)
;(this.onVisibilityChange as unknown) = undefined
} catch (error) {
console.error('Error while deiniting application', error)
}
@@ -105,17 +120,26 @@ export class WebApplication extends SNApplication {
public addWebEventObserver(observer: WebEventObserver): () => void {
this.webEventObservers.push(observer)
return () => {
removeFromArray(this.webEventObservers, observer)
}
}
public notifyWebEvent(event: WebAppEvent): void {
public notifyWebEvent(event: WebAppEvent, data?: unknown): void {
for (const observer of this.webEventObservers) {
observer(event)
observer(event, data)
}
}
publishPanelDidResizeEvent(name: string, collapsed: boolean) {
const data: PanelResizedData = {
panel: name,
collapsed: collapsed,
}
this.notifyWebEvent(WebAppEvent.PanelResized, data)
}
public getViewControllerManager(): ViewControllerManager {
return this.webServices.viewControllerManager
}
@@ -163,4 +187,23 @@ export class WebApplication extends SNApplication {
return this.user.signOut()
}
isGlobalSpellcheckEnabled(): boolean {
return this.getPreference(PrefKey.EditorSpellcheck, true)
}
public getItemTags(item: DecryptedItemInterface) {
return this.items.itemsReferencingItem(item).filter((ref) => {
return ref.content_type === ContentType.Tag
}) as SNTag[]
}
public get version(): string {
return (this.deviceInterface as WebOrDesktopDevice).appVersion
}
async toggleGlobalSpellcheck() {
const currentValue = this.isGlobalSpellcheckEnabled()
return this.setPreference(PrefKey.EditorSpellcheck, !currentValue)
}
}

View File

@@ -0,0 +1,9 @@
export enum WebAppEvent {
NewUpdateAvailable = 'NewUpdateAvailable',
EditorFocused = 'EditorFocused',
BeganBackupDownload = 'BeganBackupDownload',
EndedBackupDownload = 'EndedBackupDownload',
PanelResized = 'PanelResized',
WindowDidFocus = 'WindowDidFocus',
WindowDidBlur = 'WindowDidBlur',
}

View File

@@ -1,6 +1,6 @@
import { ApplicationEvent } from '@standardnotes/snjs'
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager, ViewControllerManagerEvent } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { autorun, IReactionDisposer, IReactionPublic } from 'mobx'
import { Component } from 'react'
@@ -9,7 +9,6 @@ export type PureComponentProps = Partial<Record<string, any>>
export abstract class PureComponent<P = PureComponentProps, S = PureComponentState> extends Component<P, S> {
private unsubApp!: () => void
private unsubState!: () => void
private reactionDisposers: IReactionDisposer[] = []
constructor(props: P, protected application: WebApplication) {
@@ -18,18 +17,17 @@ export abstract class PureComponent<P = PureComponentProps, S = PureComponentSta
override componentDidMount() {
this.addAppEventObserver()
this.addViewControllerManagerObserver()
}
deinit(): void {
this.unsubApp?.()
this.unsubState?.()
for (const disposer of this.reactionDisposers) {
disposer()
}
this.reactionDisposers.length = 0
;(this.unsubApp as unknown) = undefined
;(this.unsubState as unknown) = undefined
;(this.application as unknown) = undefined
;(this.props as unknown) = undefined
;(this.state as unknown) = undefined
@@ -47,16 +45,6 @@ export abstract class PureComponent<P = PureComponentProps, S = PureComponentSta
this.reactionDisposers.push(autorun(view))
}
addViewControllerManagerObserver() {
this.unsubState = this.application.getViewControllerManager().addObserver(async (eventName, data) => {
this.onViewControllerManagerEvent(eventName, data)
})
}
onViewControllerManagerEvent(_eventName: ViewControllerManagerEvent, _data: unknown) {
/** Optional override */
}
addAppEventObserver() {
if (this.application.isStarted()) {
this.onAppStart().catch(console.error)

View File

@@ -169,7 +169,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
<Icon type="help" className={iconClassName} />
Help &amp; feedback
</div>
<span className="color-neutral">v{viewControllerManager.version}</span>
<span className="color-neutral">v{application.version}</span>
</MenuItem>
{user ? (
<>

View File

@@ -1,10 +1,10 @@
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { getPlatformString, getWindowUrlParams } from '@/Utils'
import { ViewControllerManagerEvent } from '@/Services/ViewControllerManager'
import { ApplicationEvent, Challenge, removeFromArray } from '@standardnotes/snjs'
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
import { alertDialog } from '@/Services/AlertService'
import { WebApplication } from '@/Application/Application'
import { WebAppEvent } from '@/Application/WebAppEvent'
import Navigation from '@/Components/Navigation/Navigation'
import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView'
import Footer from '@/Components/Footer/Footer'
@@ -120,8 +120,8 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
}, [application, onAppLaunch, onAppStart])
useEffect(() => {
const removeObserver = application.getViewControllerManager().addObserver(async (eventName, data) => {
if (eventName === ViewControllerManagerEvent.PanelResized) {
const removeObserver = application.addWebEventObserver(async (eventName, data) => {
if (eventName === WebAppEvent.PanelResized) {
const { panel, collapsed } = data as PanelResizedData
let appClass = ''
if (panel === PANEL_NAME_NOTES && collapsed) {
@@ -131,7 +131,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
appClass += ' collapsed-navigation'
}
setAppClass(appClass)
} else if (eventName === ViewControllerManagerEvent.WindowDidFocus) {
} else if (eventName === WebAppEvent.WindowDidFocus) {
if (!(await application.isLocked())) {
application.sync.sync().catch(console.error)
}

View File

@@ -90,7 +90,7 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
const transactions: TransactionalMutation[] = []
await application.getViewControllerManager().contentListController.insertCurrentIfTemplate()
await application.getViewControllerManager().itemListController.insertCurrentIfTemplate()
if (note.locked) {
application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT).catch(console.error)

View File

@@ -23,10 +23,10 @@ const ContentList: FunctionComponent<Props> = ({
selectedItems,
paginate,
}) => {
const { selectPreviousItem, selectNextItem } = viewControllerManager.contentListController
const { selectPreviousItem, selectNextItem } = viewControllerManager.itemListController
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } =
viewControllerManager.contentListController.webDisplayOptions
const { sortBy } = viewControllerManager.contentListController.displayOptions
viewControllerManager.itemListController.webDisplayOptions
const { sortBy } = viewControllerManager.itemListController.displayOptions
const onScroll: UIEventHandler = useCallback(
(e) => {

View File

@@ -15,7 +15,7 @@ const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
return []
}
const tags = props.viewControllerManager.getItemTags(props.item)
const tags = props.application.getItemTags(props.item)
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
if (isNavigatingOnlyTag) {

View File

@@ -46,7 +46,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
paginate,
panelWidth,
createNewNote,
} = viewControllerManager.contentListController
} = viewControllerManager.itemListController
const { selectedItems } = viewControllerManager.selectionController
@@ -143,7 +143,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth()
viewControllerManager.panelDidResize(PANEL_NAME_NOTES, isCollapsed)
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed)
},
[viewControllerManager, application],
)

View File

@@ -1,4 +1,5 @@
import { WebAppEvent, WebApplication } from '@/Application/Application'
import { WebApplication } from '@/Application/Application'
import { WebAppEvent } from '@/Application/WebAppEvent'
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { PureComponent } from '@/Components/Abstract/PureComponent'
import { destroyAllObjectProperties, preventRefreshing } from '@/Utils'
@@ -12,7 +13,6 @@ import {
} from '@/Constants/Strings'
import { alertDialog, confirmDialog } from '@/Services/AlertService'
import AccountMenu from '@/Components/AccountMenu/AccountMenu'
import { ViewControllerManagerEvent } from '@/Services/ViewControllerManager'
import Icon from '@/Components/Icon/Icon'
import QuickSettingsMenu from '@/Components/QuickSettingsMenu/QuickSettingsMenu'
import SyncResolutionMenu from '@/Components/SyncResolutionMenu/SyncResolutionMenu'
@@ -64,9 +64,34 @@ class Footer extends PureComponent<Props, State> {
showQuickSettingsMenu: false,
}
this.webEventListenerDestroyer = props.application.addWebEventObserver((event) => {
if (event === WebAppEvent.NewUpdateAvailable) {
this.onNewUpdateAvailable()
this.webEventListenerDestroyer = props.application.addWebEventObserver((event, data) => {
const statusService = this.application.status
switch (event) {
case WebAppEvent.NewUpdateAvailable:
this.onNewUpdateAvailable()
break
case WebAppEvent.EditorFocused:
if ((data as any).eventSource === EditorEventSource.UserInteraction) {
this.closeAccountMenu()
}
break
case WebAppEvent.BeganBackupDownload:
statusService.setMessage('Saving local backup…')
break
case WebAppEvent.EndedBackupDownload: {
const successMessage = 'Successfully saved backup.'
const errorMessage = 'Unable to save local backup.'
statusService.setMessage((data as any).success ? successMessage : errorMessage)
const twoSeconds = 2000
setTimeout(() => {
if (statusService.message === successMessage || statusService.message === errorMessage) {
statusService.setMessage('')
}
}, twoSeconds)
break
}
}
})
}
@@ -133,33 +158,6 @@ class Footer extends PureComponent<Props, State> {
})
}
override onViewControllerManagerEvent(eventName: ViewControllerManagerEvent, data: any) {
const statusService = this.application.status
switch (eventName) {
case ViewControllerManagerEvent.EditorFocused:
if (data.eventSource === EditorEventSource.UserInteraction) {
this.closeAccountMenu()
}
break
case ViewControllerManagerEvent.BeganBackupDownload:
statusService.setMessage('Saving local backup…')
break
case ViewControllerManagerEvent.EndedBackupDownload: {
const successMessage = 'Successfully saved backup.'
const errorMessage = 'Unable to save local backup.'
statusService.setMessage(data.success ? successMessage : errorMessage)
const twoSeconds = 2000
setTimeout(() => {
if (statusService.message === successMessage || statusService.message === errorMessage) {
statusService.setMessage('')
}
}, twoSeconds)
break
}
}
}
override async onAppKeyChange() {
super.onAppKeyChange().catch(console.error)
this.reloadPasscodeStatus().catch(console.error)

View File

@@ -33,7 +33,7 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.setPreference(PrefKey.TagsPanelWidth, width).catch(console.error)
viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth()
viewControllerManager.panelDidResize(PANEL_NAME_NAVIGATION, isCollapsed)
application.publishPanelDidResizeEvent(PANEL_NAME_NAVIGATION, isCollapsed)
},
[application, viewControllerManager],
)

View File

@@ -35,6 +35,7 @@ import {
} from './TransactionFunctions'
import { reloadFont } from './FontFunctions'
import { NoteViewProps } from './NoteViewProps'
import { WebAppEvent } from '@/Application/WebAppEvent'
const MINIMUM_STATUS_DURATION = 400
const TEXTAREA_DEBOUNCE = 100
@@ -588,7 +589,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
onContentFocus = () => {
if (this.lastEditorFocusEventSource) {
this.application.getViewControllerManager().editorDidFocus(this.lastEditorFocusEventSource)
this.application.notifyWebEvent(WebAppEvent.EditorFocused, { eventSource: this.lastEditorFocusEventSource })
}
this.lastEditorFocusEventSource = undefined
}

View File

@@ -57,7 +57,7 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
const toggleSpellcheck = () => {
setSpellcheck(!spellcheck)
application.getViewControllerManager().toggleGlobalSpellcheck().catch(console.error)
application.toggleGlobalSpellcheck().catch(console.error)
}
useEffect(() => {

View File

@@ -1,14 +1,14 @@
import Icon from '@/Components/Icon/Icon'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { TagsController } from '@/Controllers/Navigation/TagsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import { useDrop } from 'react-dnd'
import { DropItem, DropProps, ItemTypes } from './DragNDrop'
type Props = {
tagsState: TagsController
tagsState: NavigationController
featuresState: FeaturesController
}

View File

@@ -1,6 +1,6 @@
import Icon from '@/Components/Icon/Icon'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { TagsController } from '@/Controllers/Navigation/TagsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import '@reach/tooltip/styles.css'
import { SmartView, SystemViewId, IconType, isSystemView } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
@@ -16,7 +16,7 @@ import {
type Props = {
view: SmartView
tagsState: TagsController
tagsState: NavigationController
features: FeaturesController
}

View File

@@ -3,7 +3,7 @@ import { TAG_FOLDERS_FEATURE_NAME } from '@/Constants/Constants'
import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { KeyboardKey } from '@/Services/IOService'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { TagsController } from '@/Controllers/Navigation/TagsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import '@reach/tooltip/styles.css'
import { SNTag } from '@standardnotes/snjs'
import { computed } from 'mobx'
@@ -23,7 +23,7 @@ import { DropItem, DropProps, ItemTypes } from './DragNDrop'
type Props = {
tag: SNTag
tagsState: TagsController
tagsState: NavigationController
features: FeaturesController
level: number
onContextMenu: (tag: SNTag, posX: number, posY: number) => void

View File

@@ -1,11 +1,11 @@
import IconButton from '@/Components/Button/IconButton'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { TagsController } from '@/Controllers/Navigation/TagsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
type Props = {
tags: TagsController
tags: NavigationController
features: FeaturesController
}

View File

@@ -1,14 +1,27 @@
import { DeinitSource } from '@standardnotes/snjs'
import { CrossControllerEvent } from '../CrossControllerEvent'
import { InternalEventBus, InternalEventPublishStrategy } from '@standardnotes/snjs'
import { WebApplication } from '../../Application/Application'
import { Disposer } from '@/Types/Disposer'
export abstract class AbstractViewController {
dealloced = false
protected disposers: Disposer[] = []
constructor(public application: WebApplication, public viewControllerManager?: AbstractViewController) {}
constructor(public application: WebApplication, protected eventBus: InternalEventBus) {}
deinit(_source: DeinitSource): void {
protected async publishEventSync(name: CrossControllerEvent): Promise<void> {
await this.eventBus.publishSync({ type: name, payload: undefined }, InternalEventPublishStrategy.SEQUENCE)
}
deinit(): void {
this.dealloced = true
;(this.application as unknown) = undefined
;(this.viewControllerManager as unknown) = undefined
;(this.eventBus as unknown) = undefined
for (const disposer of this.disposers) {
disposer()
}
;(this.disposers as unknown) = undefined
}
}

View File

@@ -1,5 +1,3 @@
import { AbstractViewController } from './AbstractViewController'
export function isControllerDealloced(state: AbstractViewController): boolean {
return state.dealloced == undefined || state.dealloced === true
export function isControllerDealloced(controller: { dealloced: boolean }): boolean {
return controller.dealloced == undefined || controller.dealloced === true
}

View File

@@ -1,6 +1,6 @@
import { destroyAllObjectProperties, isDev } from '@/Utils'
import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { ApplicationEvent, ContentType, DeinitSource, SNNote, SNTag } from '@standardnotes/snjs'
import { ApplicationEvent, ContentType, InternalEventBus, SNNote, SNTag } from '@standardnotes/snjs'
import { WebApplication } from '@/Application/Application'
import { AccountMenuPane } from '@/Components/AccountMenu/AccountMenuPane'
import { AbstractViewController } from '../Abstract/AbstractViewController'
@@ -21,15 +21,16 @@ export class AccountMenuController extends AbstractViewController {
shouldAnimateCloseMenu = false
currentPane = AccountMenuPane.GeneralMenu
override deinit(source: DeinitSource) {
super.deinit(source)
override deinit() {
super.deinit()
;(this.notesAndTags as unknown) = undefined
destroyAllObjectProperties(this)
}
constructor(application: WebApplication, private appEventListeners: (() => void)[]) {
super(application)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
show: observable,
signingOut: observable,
@@ -60,12 +61,7 @@ export class AccountMenuController extends AbstractViewController {
notesAndTagsCount: computed,
})
this.addAppLaunchedEventObserver()
this.streamNotesAndTags()
}
addAppLaunchedEventObserver = (): void => {
this.appEventListeners.push(
this.disposers.push(
this.application.addEventObserver(async () => {
runInAction(() => {
if (isDev && window.devAccountServer) {
@@ -77,10 +73,8 @@ export class AccountMenuController extends AbstractViewController {
})
}, ApplicationEvent.Launched),
)
}
streamNotesAndTags = (): void => {
this.appEventListeners.push(
this.disposers.push(
this.application.streamItems([ContentType.Note, ContentType.Tag], () => {
runInAction(() => {
this.notesAndTags = this.application.items.getItems([ContentType.Note, ContentType.Tag])

View File

@@ -0,0 +1,4 @@
export enum CrossControllerEvent {
TagChanged = 'TagChanged',
ActiveEditorChanged = 'ActiveEditorChanged',
}

View File

@@ -1,6 +1,6 @@
import { WebApplication } from '@/Application/Application'
import { destroyAllObjectProperties } from '@/Utils'
import { ApplicationEvent, DeinitSource, FeatureIdentifier, FeatureStatus } from '@standardnotes/snjs'
import { ApplicationEvent, FeatureIdentifier, FeatureStatus, InternalEventBus } from '@standardnotes/snjs'
import { action, makeObservable, observable, runInAction, when } from 'mobx'
import { AbstractViewController } from './Abstract/AbstractViewController'
@@ -10,8 +10,8 @@ export class FeaturesController extends AbstractViewController {
hasFiles: boolean
premiumAlertFeatureName: string | undefined
override deinit(source: DeinitSource) {
super.deinit(source)
override deinit() {
super.deinit()
;(this.showPremiumAlert as unknown) = undefined
;(this.closePremiumAlert as unknown) = undefined
;(this.hasFolders as unknown) = undefined
@@ -22,8 +22,8 @@ export class FeaturesController extends AbstractViewController {
destroyAllObjectProperties(this)
}
constructor(application: WebApplication, appObservers: (() => void)[]) {
super(application)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
this.hasFolders = this.isEntitledToFolders()
this.hasSmartViews = this.isEntitledToSmartViews()
@@ -43,7 +43,7 @@ export class FeaturesController extends AbstractViewController {
this.showPremiumAlert = this.showPremiumAlert.bind(this)
this.closePremiumAlert = this.closePremiumAlert.bind(this)
appObservers.push(
this.disposers.push(
application.addEventObserver(async (event) => {
switch (event) {
case ApplicationEvent.FeaturesUpdated:

View File

@@ -1,3 +1,4 @@
import { FilePreviewModalController } from './FilePreviewModalController'
import {
PopoverFileItemAction,
PopoverFileItemActionType,
@@ -13,12 +14,13 @@ import {
ClassicFileSaver,
parseFileName,
} from '@standardnotes/filepicker'
import { ChallengeReason, ClientDisplayableError, ContentType, FileItem } from '@standardnotes/snjs'
import { ChallengeReason, ClientDisplayableError, ContentType, FileItem, InternalEventBus } from '@standardnotes/snjs'
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/stylekit'
import { action, computed, makeObservable, observable, reaction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { ViewControllerManager } from '../Services/ViewControllerManager/ViewControllerManager'
import { NotesController } from './NotesController'
import { SelectedItemsController } from './SelectedItemsController'
const UnprotectedFileActions = [PopoverFileItemActionType.ToggleFileProtection]
const NonMutatingFileActions = [PopoverFileItemActionType.DownloadFile, PopoverFileItemActionType.PreviewFile]
@@ -31,12 +33,21 @@ export class FilesController extends AbstractViewController {
showFileContextMenu = false
fileContextMenuLocation: FileContextMenuLocation = { x: 0, y: 0 }
override deinit(): void {
super.deinit()
;(this.notesController as unknown) = undefined
;(this.selectionController as unknown) = undefined
;(this.filePreviewModalController as unknown) = undefined
}
constructor(
application: WebApplication,
override viewControllerManager: ViewControllerManager,
appObservers: (() => void)[],
private notesController: NotesController,
private selectionController: SelectedItemsController,
private filePreviewModalController: FilePreviewModalController,
eventBus: InternalEventBus,
) {
super(application, viewControllerManager)
super(application, eventBus)
makeObservable(this, {
allFiles: observable,
@@ -52,13 +63,16 @@ export class FilesController extends AbstractViewController {
setFileContextMenuLocation: action,
})
appObservers.push(
this.disposers.push(
application.streamItems(ContentType.File, () => {
this.reloadAllFiles()
this.reloadAttachedFiles()
}),
)
this.disposers.push(
reaction(
() => viewControllerManager.notesController.selectedNotes,
() => notesController.selectedNotes,
() => {
this.reloadAttachedFiles()
},
@@ -67,7 +81,7 @@ export class FilesController extends AbstractViewController {
}
get selectedFiles(): FileItem[] {
return this.viewControllerManager.selectionController.getSelectedItems<FileItem>(ContentType.File)
return this.selectionController.getSelectedItems<FileItem>(ContentType.File)
}
setShowFileContextMenu = (enabled: boolean) => {
@@ -83,7 +97,7 @@ export class FilesController extends AbstractViewController {
}
reloadAttachedFiles = () => {
const note = this.viewControllerManager.notesController.firstSelectedNote
const note = this.notesController.firstSelectedNote
if (note) {
this.attachedFiles = this.application.items.getFilesForNote(note)
}
@@ -109,7 +123,7 @@ export class FilesController extends AbstractViewController {
}
attachFileToNote = async (file: FileItem) => {
const note = this.viewControllerManager.notesController.firstSelectedNote
const note = this.notesController.firstSelectedNote
if (!note) {
addToast({
type: ToastType.Error,
@@ -122,7 +136,7 @@ export class FilesController extends AbstractViewController {
}
detachFileFromNote = async (file: FileItem) => {
const note = this.viewControllerManager.notesController.firstSelectedNote
const note = this.notesController.firstSelectedNote
if (!note) {
addToast({
type: ToastType.Error,
@@ -197,7 +211,7 @@ export class FilesController extends AbstractViewController {
await this.renameFile(file, action.payload.name)
break
case PopoverFileItemActionType.PreviewFile:
this.viewControllerManager.filePreviewModalController.activate(
this.filePreviewModalController.activate(
file,
currentTab === PopoverTabs.AllFiles ? this.allFiles : this.attachedFiles,
)

View File

@@ -4,7 +4,6 @@ import {
ApplicationEvent,
CollectionSort,
ContentType,
DeinitSource,
findInArray,
NoteViewController,
PrefKey,
@@ -13,13 +12,21 @@ import {
SNTag,
SystemViewId,
DisplayOptions,
InternalEventBus,
InternalEventHandlerInterface,
InternalEventInterface,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
import { WebApplication } from '../../Application/Application'
import { AbstractViewController } from '../Abstract/AbstractViewController'
import { ViewControllerManager } from '../../Services/ViewControllerManager/ViewControllerManager'
import { ViewControllerManagerEvent } from '../../Services/ViewControllerManager/ViewControllerManagerEvent'
import { WebDisplayOptions } from './WebDisplayOptions'
import { NavigationController } from '../Navigation/NavigationController'
import { CrossControllerEvent } from '../CrossControllerEvent'
import { SearchOptionsController } from '../SearchOptionsController'
import { SelectedItemsController } from '../SelectedItemsController'
import { NotesController } from '../NotesController'
import { NoteTagsController } from '../NoteTagsController'
import { WebAppEvent } from '@/Application/WebAppEvent'
const MinNoteCellHeight = 51.0
const DefaultListNumNotes = 20
@@ -27,7 +34,7 @@ const ElementIdSearchBar = 'search-bar'
const ElementIdScrollContainer = 'notes-scrollable'
const SupportsFileSelectionState = false
export class ItemListController extends AbstractViewController {
export class ItemListController extends AbstractViewController implements InternalEventHandlerInterface {
completedFullSync = false
noteFilterText = ''
notes: SNNote[] = []
@@ -55,11 +62,16 @@ export class ItemListController extends AbstractViewController {
}
private reloadItemsPromise?: Promise<unknown>
override deinit(source: DeinitSource) {
super.deinit(source)
override deinit() {
super.deinit()
;(this.noteFilterText as unknown) = undefined
;(this.notes as unknown) = undefined
;(this.renderedItems as unknown) = undefined
;(this.navigationController as unknown) = undefined
;(this.searchOptionsController as unknown) = undefined
;(this.selectionController as unknown) = undefined
;(this.notesController as unknown) = undefined
;(this.noteTagsController as unknown) = undefined
;(window.onresize as unknown) = undefined
destroyAllObjectProperties(this)
@@ -67,18 +79,27 @@ export class ItemListController extends AbstractViewController {
constructor(
application: WebApplication,
override viewControllerManager: ViewControllerManager,
appObservers: (() => void)[],
private navigationController: NavigationController,
private searchOptionsController: SearchOptionsController,
private selectionController: SelectedItemsController,
private notesController: NotesController,
private noteTagsController: NoteTagsController,
eventBus: InternalEventBus,
) {
super(application, viewControllerManager)
super(application, eventBus)
eventBus.addEventHandler(this, CrossControllerEvent.TagChanged)
eventBus.addEventHandler(this, CrossControllerEvent.ActiveEditorChanged)
this.resetPagination()
appObservers.push(
this.disposers.push(
application.streamItems<SNNote>(ContentType.Note, () => {
void this.reloadItems()
}),
)
this.disposers.push(
application.streamItems<SNTag>([ContentType.Tag], async ({ changed, inserted }) => {
const tags = [...changed, ...inserted]
@@ -87,28 +108,34 @@ export class ItemListController extends AbstractViewController {
void this.reloadItems()
if (
viewControllerManager.navigationController.selected &&
findInArray(tags, 'uuid', viewControllerManager.navigationController.selected.uuid)
) {
if (this.navigationController.selected && findInArray(tags, 'uuid', this.navigationController.selected.uuid)) {
/** Tag title could have changed */
this.reloadPanelTitle()
}
}),
)
this.disposers.push(
application.addEventObserver(async () => {
void this.reloadPreferences()
}, ApplicationEvent.PreferencesChanged),
)
this.disposers.push(
application.addEventObserver(async () => {
this.application.noteControllerGroup.closeAllNoteControllers()
void this.selectFirstItem()
this.setCompletedFullSync(false)
}, ApplicationEvent.SignedIn),
)
this.disposers.push(
application.addEventObserver(async () => {
void this.reloadItems().then(() => {
if (
this.notes.length === 0 &&
viewControllerManager.navigationController.selected instanceof SmartView &&
viewControllerManager.navigationController.selected.uuid === SystemViewId.AllNotes &&
this.navigationController.selected instanceof SmartView &&
this.navigationController.selected.uuid === SystemViewId.AllNotes &&
this.noteFilterText === '' &&
!this.getActiveNoteController()
) {
@@ -117,28 +144,28 @@ export class ItemListController extends AbstractViewController {
})
this.setCompletedFullSync(true)
}, ApplicationEvent.CompletedFullSync),
)
this.disposers.push(
application.addWebEventObserver((webEvent) => {
if (webEvent === WebAppEvent.EditorFocused) {
this.setShowDisplayOptionsMenu(false)
}
}),
)
this.disposers.push(
reaction(
() => [
viewControllerManager.searchOptionsController.includeProtectedContents,
viewControllerManager.searchOptionsController.includeArchived,
viewControllerManager.searchOptionsController.includeTrashed,
this.searchOptionsController.includeProtectedContents,
this.searchOptionsController.includeArchived,
this.searchOptionsController.includeTrashed,
],
() => {
this.reloadNotesDisplayOptions()
void this.reloadItems()
},
),
viewControllerManager.addObserver(async (eventName) => {
if (eventName === ViewControllerManagerEvent.TagChanged) {
this.handleTagChange()
} else if (eventName === ViewControllerManagerEvent.ActiveEditorChanged) {
this.handleEditorChange().catch(console.error)
} else if (eventName === ViewControllerManagerEvent.EditorFocused) {
this.setShowDisplayOptionsMenu(false)
}
}),
)
makeObservable(this, {
@@ -170,6 +197,14 @@ export class ItemListController extends AbstractViewController {
}
}
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === CrossControllerEvent.TagChanged) {
this.handleTagChange()
} else if (event.type === CrossControllerEvent.ActiveEditorChanged) {
this.handleEditorChange().catch(console.error)
}
}
public getActiveNoteController(): NoteViewController | undefined {
return this.application.noteControllerGroup.activeNoteViewController
}
@@ -200,8 +235,8 @@ export class ItemListController extends AbstractViewController {
if (this.isFiltering) {
const resultCount = this.notes.length
title = `${resultCount} search results`
} else if (this.viewControllerManager.navigationController.selected) {
title = `${this.viewControllerManager.navigationController.selected.title}`
} else if (this.navigationController.selected) {
title = `${this.navigationController.selected.title}`
}
this.panelTitle = title
@@ -218,7 +253,7 @@ export class ItemListController extends AbstractViewController {
}
private async performReloadItems() {
const tag = this.viewControllerManager.navigationController.selected
const tag = this.navigationController.selected
if (!tag) {
return
}
@@ -241,17 +276,16 @@ export class ItemListController extends AbstractViewController {
}
private async recomputeSelectionAfterItemsReload() {
const viewControllerManager = this.viewControllerManager
const activeController = this.getActiveNoteController()
const activeNote = activeController?.note
const isSearching = this.noteFilterText.length > 0
const hasMultipleItemsSelected = viewControllerManager.selectionController.selectedItemsCount >= 2
const hasMultipleItemsSelected = this.selectionController.selectedItemsCount >= 2
if (hasMultipleItemsSelected) {
return
}
const selectedItem = Object.values(viewControllerManager.selectionController.selectedItems)[0]
const selectedItem = Object.values(this.selectionController.selectedItems)[0]
const isSelectedItemFile =
this.items.includes(selectedItem) && selectedItem && selectedItem.content_type === ContentType.File
@@ -280,25 +314,25 @@ export class ItemListController extends AbstractViewController {
}
const showTrashedNotes =
(viewControllerManager.navigationController.selected instanceof SmartView &&
viewControllerManager.navigationController.selected?.uuid === SystemViewId.TrashedNotes) ||
viewControllerManager?.searchOptionsController.includeTrashed
(this.navigationController.selected instanceof SmartView &&
this.navigationController.selected?.uuid === SystemViewId.TrashedNotes) ||
this.searchOptionsController.includeTrashed
const showArchivedNotes =
(viewControllerManager.navigationController.selected instanceof SmartView &&
viewControllerManager.navigationController.selected.uuid === SystemViewId.ArchivedNotes) ||
viewControllerManager.searchOptionsController.includeArchived ||
(this.navigationController.selected instanceof SmartView &&
this.navigationController.selected.uuid === SystemViewId.ArchivedNotes) ||
this.searchOptionsController.includeArchived ||
this.application.getPreference(PrefKey.NotesShowArchived, false)
if ((activeNote.trashed && !showTrashedNotes) || (activeNote.archived && !showArchivedNotes)) {
await this.selectNextItemOrCreateNewNote()
} else if (!this.viewControllerManager.selectionController.selectedItems[activeNote.uuid]) {
await this.viewControllerManager.selectionController.selectItem(activeNote.uuid).catch(console.error)
} else if (!this.selectionController.selectedItems[activeNote.uuid]) {
await this.selectionController.selectItem(activeNote.uuid).catch(console.error)
}
}
reloadNotesDisplayOptions = () => {
const tag = this.viewControllerManager.navigationController.selected
const tag = this.navigationController.selected
const searchText = this.noteFilterText.toLowerCase()
const isSearching = searchText.length
@@ -306,8 +340,8 @@ export class ItemListController extends AbstractViewController {
let includeTrashed: boolean
if (isSearching) {
includeArchived = this.viewControllerManager.searchOptionsController.includeArchived
includeTrashed = this.viewControllerManager.searchOptionsController.includeTrashed
includeArchived = this.searchOptionsController.includeArchived
includeTrashed = this.searchOptionsController.includeTrashed
} else {
includeArchived = this.displayOptions.includeArchived ?? false
includeTrashed = this.displayOptions.includeTrashed ?? false
@@ -324,7 +358,7 @@ export class ItemListController extends AbstractViewController {
includeProtected: this.displayOptions.includeProtected,
searchQuery: {
query: searchText,
includeProtectedNoteText: this.viewControllerManager.searchOptionsController.includeProtectedContents,
includeProtectedNoteText: this.searchOptionsController.includeProtectedContents,
},
}
@@ -387,13 +421,10 @@ export class ItemListController extends AbstractViewController {
}
createNewNote = async () => {
this.viewControllerManager.notesController.unselectNotes()
this.notesController.unselectNotes()
if (
this.viewControllerManager.navigationController.isInSmartView() &&
!this.viewControllerManager.navigationController.isInHomeView()
) {
await this.viewControllerManager.navigationController.selectHomeNavigationView()
if (this.navigationController.isInSmartView() && !this.navigationController.isInHomeView()) {
await this.navigationController.selectHomeNavigationView()
}
let title = `Note ${this.notes.length + 1}`
@@ -401,16 +432,13 @@ export class ItemListController extends AbstractViewController {
title = this.noteFilterText
}
await this.viewControllerManager.notesController.createNewNoteController(title)
await this.notesController.createNewNoteController(title)
this.viewControllerManager.noteTagsController.reloadTagsForCurrentNote()
this.noteTagsController.reloadTagsForCurrentNote()
}
createPlaceholderNote = () => {
if (
this.viewControllerManager.navigationController.isInSmartView() &&
!this.viewControllerManager.navigationController.isInHomeView()
) {
if (this.navigationController.isInSmartView() && !this.navigationController.isInHomeView()) {
return
}
@@ -487,7 +515,7 @@ export class ItemListController extends AbstractViewController {
},
{ userTriggered = false, scrollIntoView = true },
): Promise<void> => {
await this.viewControllerManager.selectionController.selectItem(item.uuid, userTriggered)
await this.selectionController.selectItem(item.uuid, userTriggered)
if (scrollIntoView) {
const itemElement = document.getElementById(item.uuid)
@@ -514,7 +542,7 @@ export class ItemListController extends AbstractViewController {
const displayableItems = this.items
const currentIndex = displayableItems.findIndex((candidate) => {
return candidate.uuid === this.viewControllerManager.selectionController.lastSelectedItem?.uuid
return candidate.uuid === this.selectionController.lastSelectedItem?.uuid
})
let nextIndex = currentIndex + 1
@@ -554,11 +582,11 @@ export class ItemListController extends AbstractViewController {
selectPreviousItem = () => {
const displayableItems = this.items
if (!this.viewControllerManager.selectionController.lastSelectedItem) {
if (!this.selectionController.lastSelectedItem) {
return
}
const currentIndex = displayableItems.indexOf(this.viewControllerManager.selectionController.lastSelectedItem)
const currentIndex = displayableItems.indexOf(this.selectionController.lastSelectedItem)
let previousIndex = currentIndex - 1

View File

@@ -11,20 +11,20 @@ import {
UuidString,
isSystemView,
FindItem,
DeinitSource,
SystemViewId,
InternalEventBus,
InternalEventPublishStrategy,
} from '@standardnotes/snjs'
import { action, computed, makeAutoObservable, makeObservable, observable, runInAction } from 'mobx'
import { WebApplication } from '../../Application/Application'
import { FeaturesController } from '../FeaturesController'
import { AbstractViewController } from '../Abstract/AbstractViewController'
import { destroyAllObjectProperties } from '@/Utils'
import { ViewControllerManager } from '../../Services/ViewControllerManager/ViewControllerManager'
import { ViewControllerManagerEvent } from '../../Services/ViewControllerManager/ViewControllerManagerEvent'
import { isValidFutureSiblings, rootTags, tagSiblings } from './Utils'
import { AnyTag } from './AnyTagType'
import { CrossControllerEvent } from '../CrossControllerEvent'
export class TagsController extends AbstractViewController {
export class NavigationController extends AbstractViewController {
tags: SNTag[] = []
smartViews: SmartView[] = []
allNotesCount_ = 0
@@ -43,13 +43,8 @@ export class TagsController extends AbstractViewController {
private readonly tagsCountsState: TagsCountsState
constructor(
application: WebApplication,
override viewControllerManager: ViewControllerManager,
appEventListeners: (() => void)[],
private features: FeaturesController,
) {
super(application)
constructor(application: WebApplication, private featuresController: FeaturesController, eventBus: InternalEventBus) {
super(application, eventBus)
this.tagsCountsState = new TagsCountsState(this.application)
@@ -99,7 +94,7 @@ export class TagsController extends AbstractViewController {
setContextMenuMaxHeight: action,
})
appEventListeners.push(
this.disposers.push(
this.application.streamItems([ContentType.Tag, ContentType.SmartView], ({ changed, removed }) => {
runInAction(() => {
this.tags = this.application.items.getDisplayableTags()
@@ -131,7 +126,7 @@ export class TagsController extends AbstractViewController {
}),
)
appEventListeners.push(
this.disposers.push(
this.application.items.addNoteCountChangeObserver((tagUuid) => {
if (!tagUuid) {
this.setAllNotesCount(this.application.items.allCountableNotesCount())
@@ -145,15 +140,16 @@ export class TagsController extends AbstractViewController {
)
}
override deinit(source: DeinitSource) {
super.deinit(source)
;(this.features as unknown) = undefined
override deinit() {
super.deinit()
;(this.featuresController 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
;(this.featuresController as unknown) = undefined
destroyAllObjectProperties(this)
}
@@ -369,10 +365,13 @@ export class TagsController extends AbstractViewController {
return
}
await this.viewControllerManager.notifyEvent(ViewControllerManagerEvent.TagChanged, {
tag,
previousTag: this.previouslySelected_,
})
await this.eventBus.publishSync(
{
type: CrossControllerEvent.TagChanged,
payload: { tag, previousTag: this.previouslySelected_ },
},
InternalEventPublishStrategy.SEQUENCE,
)
}
public async selectHomeNavigationView(): Promise<void> {
@@ -473,8 +472,8 @@ export class TagsController extends AbstractViewController {
const isSmartViewTitle = this.application.items.isSmartViewTitle(newTitle)
if (isSmartViewTitle) {
if (!this.features.hasSmartViews) {
await this.features.showPremiumAlert(SMART_TAGS_FEATURE_NAME)
if (!this.featuresController.hasSmartViews) {
await this.featuresController.showPremiumAlert(SMART_TAGS_FEATURE_NAME)
return
}
}

View File

@@ -1,5 +1,5 @@
import { storage, StorageKey } from '@/Services/LocalStorage'
import { ApplicationEvent } from '@standardnotes/snjs'
import { ApplicationEvent, InternalEventBus } from '@standardnotes/snjs'
import { runInAction, makeObservable, observable, action } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
@@ -7,17 +7,20 @@ import { AbstractViewController } from './Abstract/AbstractViewController'
export class NoAccountWarningController extends AbstractViewController {
show: boolean
constructor(application: WebApplication, appObservers: (() => void)[]) {
super(application)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
this.show = application.hasAccount() ? false : storage.get(StorageKey.ShowNoAccountWarning) ?? true
appObservers.push(
this.disposers.push(
application.addEventObserver(async () => {
runInAction(() => {
this.show = false
})
}, ApplicationEvent.SignedIn),
)
this.disposers.push(
application.addEventObserver(async () => {
if (application.hasAccount()) {
runInAction(() => {

View File

@@ -1,10 +1,18 @@
import { ElementIds } from '@/Constants/ElementIDs'
import { destroyAllObjectProperties } from '@/Utils'
import { ApplicationEvent, ContentType, DeinitSource, PrefKey, SNNote, SNTag, UuidString } from '@standardnotes/snjs'
import {
ApplicationEvent,
ContentType,
InternalEventBus,
PrefKey,
SNNote,
SNTag,
UuidString,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { ViewControllerManager } from '../Services/ViewControllerManager/ViewControllerManager'
import { ItemListController } from './ItemList/ItemListController'
export class NoteTagsController extends AbstractViewController {
autocompleteInputFocused = false
@@ -16,21 +24,19 @@ export class NoteTagsController extends AbstractViewController {
tags: SNTag[] = []
tagsContainerMaxWidth: number | 'auto' = 0
addNoteToParentFolders: boolean
private itemListController!: ItemListController
override deinit(source: DeinitSource) {
super.deinit(source)
override deinit() {
super.deinit()
;(this.tags as unknown) = undefined
;(this.autocompleteTagResults as unknown) = undefined
;(this.itemListController as unknown) = undefined
destroyAllObjectProperties(this)
}
constructor(
application: WebApplication,
override viewControllerManager: ViewControllerManager,
appEventListeners: (() => void)[],
) {
super(application, viewControllerManager)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
autocompleteInputFocused: observable,
@@ -55,13 +61,17 @@ export class NoteTagsController extends AbstractViewController {
})
this.addNoteToParentFolders = application.getPreference(PrefKey.NoteAddToParentFolders, true)
}
appEventListeners.push(
application.streamItems(ContentType.Tag, () => {
public setServicestPostConstruction(itemListController: ItemListController) {
this.itemListController = itemListController
this.disposers.push(
this.application.streamItems(ContentType.Tag, () => {
this.reloadTagsForCurrentNote()
}),
application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => {
this.addNoteToParentFolders = application.getPreference(PrefKey.NoteAddToParentFolders, true)
this.application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => {
this.addNoteToParentFolders = this.application.getPreference(PrefKey.NoteAddToParentFolders, true)
}),
)
}
@@ -151,7 +161,7 @@ export class NoteTagsController extends AbstractViewController {
searchActiveNoteAutocompleteTags(): void {
const newResults = this.application.items.searchTags(
this.autocompleteSearchQuery,
this.viewControllerManager.contentListController.activeControllerNote,
this.itemListController.activeControllerNote,
)
this.setAutocompleteTagResults(newResults)
}
@@ -161,7 +171,7 @@ export class NoteTagsController extends AbstractViewController {
}
reloadTagsForCurrentNote(): void {
const activeNote = this.viewControllerManager.contentListController.activeControllerNote
const activeNote = this.itemListController.activeControllerNote
if (activeNote) {
const tags = this.application.items.getSortedTagsForNote(activeNote)
@@ -177,7 +187,7 @@ export class NoteTagsController extends AbstractViewController {
}
async addTagToActiveNote(tag: SNTag): Promise<void> {
const activeNote = this.viewControllerManager.contentListController.activeControllerNote
const activeNote = this.itemListController.activeControllerNote
if (activeNote) {
await this.application.items.addTagToNote(activeNote, tag, this.addNoteToParentFolders)
@@ -187,7 +197,7 @@ export class NoteTagsController extends AbstractViewController {
}
async removeTagFromActiveNote(tag: SNTag): Promise<void> {
const activeNote = this.viewControllerManager.contentListController.activeControllerNote
const activeNote = this.itemListController.activeControllerNote
if (activeNote) {
await this.application.mutator.changeItem(tag, (mutator) => {

View File

@@ -2,11 +2,15 @@ import { destroyAllObjectProperties } from '@/Utils'
import { confirmDialog } from '@/Services/AlertService'
import { StringEmptyTrash, Strings, StringUtils } from '@/Constants/Strings'
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
import { SNNote, NoteMutator, ContentType, SNTag, DeinitSource, TagMutator } from '@standardnotes/snjs'
import { SNNote, NoteMutator, ContentType, SNTag, TagMutator, InternalEventBus } from '@standardnotes/snjs'
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { ViewControllerManager } from '../Services/ViewControllerManager/ViewControllerManager'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { SelectedItemsController } from './SelectedItemsController'
import { ItemListController } from './ItemList/ItemListController'
import { NoteTagsController } from './NoteTagsController'
import { NavigationController } from './Navigation/NavigationController'
import { CrossControllerEvent } from './CrossControllerEvent'
export class NotesController extends AbstractViewController {
lastSelectedNote: SNNote | undefined
@@ -19,22 +23,27 @@ export class NotesController extends AbstractViewController {
contextMenuMaxHeight: number | 'auto' = 'auto'
showProtectedWarning = false
showRevisionHistoryModal = false
private itemListController!: ItemListController
override deinit(source: DeinitSource) {
super.deinit(source)
override deinit() {
super.deinit()
;(this.lastSelectedNote as unknown) = undefined
;(this.onActiveEditorChanged as unknown) = undefined
;(this.selectionController as unknown) = undefined
;(this.noteTagsController as unknown) = undefined
;(this.navigationController as unknown) = undefined
;(this.itemListController as unknown) = undefined
destroyAllObjectProperties(this)
}
constructor(
application: WebApplication,
public override viewControllerManager: ViewControllerManager,
private onActiveEditorChanged: () => Promise<void>,
appEventListeners: (() => void)[],
private selectionController: SelectedItemsController,
private noteTagsController: NoteTagsController,
private navigationController: NavigationController,
eventBus: InternalEventBus,
) {
super(application, viewControllerManager)
super(application, eventBus)
makeObservable(this, {
contextMenuOpen: observable,
@@ -55,17 +64,21 @@ export class NotesController extends AbstractViewController {
setShowRevisionHistoryModal: action,
unselectNotes: action,
})
}
appEventListeners.push(
application.streamItems<SNNote>(ContentType.Note, ({ changed, inserted, removed }) => {
public setServicestPostConstruction(itemListController: ItemListController) {
this.itemListController = itemListController
this.disposers.push(
this.application.streamItems<SNNote>(ContentType.Note, ({ changed, inserted, removed }) => {
runInAction(() => {
for (const removedNote of removed) {
this.viewControllerManager.selectionController.deselectItem(removedNote)
this.selectionController.deselectItem(removedNote)
}
for (const note of [...changed, ...inserted]) {
if (this.viewControllerManager.selectionController.isItemSelected(note)) {
this.viewControllerManager.selectionController.updateReferenceOfSelectedItem(note)
if (this.selectionController.isItemSelected(note)) {
this.selectionController.updateReferenceOfSelectedItem(note)
}
}
})
@@ -80,7 +93,7 @@ export class NotesController extends AbstractViewController {
for (const selectedId of selectedUuids) {
if (!activeNoteUuids.includes(selectedId)) {
this.viewControllerManager.selectionController.deselectItem({ uuid: selectedId })
this.selectionController.deselectItem({ uuid: selectedId })
}
}
}),
@@ -88,7 +101,7 @@ export class NotesController extends AbstractViewController {
}
public get selectedNotes(): SNNote[] {
return this.viewControllerManager.selectionController.getSelectedItems<SNNote>(ContentType.Note)
return this.selectionController.getSelectedItems<SNNote>(ContentType.Note)
}
get firstSelectedNote(): SNNote | undefined {
@@ -108,7 +121,7 @@ export class NotesController extends AbstractViewController {
}
async openNote(noteUuid: string): Promise<void> {
if (this.viewControllerManager.contentListController.activeControllerNote?.uuid === noteUuid) {
if (this.itemListController.activeControllerNote?.uuid === noteUuid) {
return
}
@@ -120,13 +133,13 @@ export class NotesController extends AbstractViewController {
await this.application.noteControllerGroup.createNoteController(noteUuid)
this.viewControllerManager.noteTagsController.reloadTagsForCurrentNote()
this.noteTagsController.reloadTagsForCurrentNote()
await this.onActiveEditorChanged()
await this.publishEventSync(CrossControllerEvent.ActiveEditorChanged)
}
async createNewNoteController(title?: string) {
const selectedTag = this.viewControllerManager.navigationController.selected
const selectedTag = this.navigationController.selected
const activeRegularTagUuid = selectedTag && selectedTag instanceof SNTag ? selectedTag.uuid : undefined
@@ -262,7 +275,7 @@ export class NotesController extends AbstractViewController {
if (permanently) {
for (const note of this.getSelectedNotesList()) {
await this.application.mutator.deleteItem(note)
this.viewControllerManager.selectionController.deselectItem(note)
this.selectionController.deselectItem(note)
}
} else {
await this.changeSelectedNotes((mutator) => {
@@ -294,7 +307,7 @@ export class NotesController extends AbstractViewController {
})
runInAction(() => {
this.viewControllerManager.selectionController.setSelectedItems({})
this.selectionController.setSelectedItems({})
this.contextMenuOpen = false
})
}
@@ -311,11 +324,11 @@ export class NotesController extends AbstractViewController {
}
unselectNotes(): void {
this.viewControllerManager.selectionController.setSelectedItems({})
this.selectionController.setSelectedItems({})
}
getSpellcheckStateForNote(note: SNNote) {
return note.spellcheck != undefined ? note.spellcheck : this.viewControllerManager.isGlobalSpellcheckEnabled()
return note.spellcheck != undefined ? note.spellcheck : this.application.isGlobalSpellcheckEnabled()
}
async toggleGlobalSpellcheckForNote(note: SNNote) {
@@ -358,7 +371,7 @@ export class NotesController extends AbstractViewController {
isTagInSelectedNotes(tag: SNTag): boolean {
const selectedNotes = this.getSelectedNotesList()
return selectedNotes.every((note) =>
this.viewControllerManager.getItemTags(note).find((noteTag) => noteTag.uuid === tag.uuid),
this.application.getItemTags(note).find((noteTag) => noteTag.uuid === tag.uuid),
)
}

View File

@@ -1,4 +1,5 @@
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowFunctions'
import { InternalEventBus } from '@standardnotes/snjs'
import { action, makeObservable, observable } from 'mobx'
import { WebApplication } from '../../Application/Application'
import { AbstractViewController } from '../Abstract/AbstractViewController'
@@ -8,8 +9,8 @@ export class PurchaseFlowController extends AbstractViewController {
isOpen = false
currentPane = PurchaseFlowPane.CreateAccount
constructor(application: WebApplication) {
super(application)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
isOpen: observable,

View File

@@ -1,4 +1,4 @@
import { ApplicationEvent } from '@standardnotes/snjs'
import { ApplicationEvent, InternalEventBus } from '@standardnotes/snjs'
import { makeObservable, observable, action, runInAction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
@@ -8,8 +8,8 @@ export class SearchOptionsController extends AbstractViewController {
includeArchived = false
includeTrashed = false
constructor(application: WebApplication, appObservers: (() => void)[]) {
super(application)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
includeProtectedContents: observable,
@@ -22,7 +22,7 @@ export class SearchOptionsController extends AbstractViewController {
refreshIncludeProtectedContents: action,
})
appObservers.push(
this.disposers.push(
this.application.addEventObserver(async () => {
this.refreshIncludeProtectedContents()
}, ApplicationEvent.UnprotectedSessionBegan),

View File

@@ -1,22 +1,35 @@
import { ListableContentItem } from '@/Components/ContentListView/Types/ListableContentItem'
import { ChallengeReason, ContentType, KeyboardModifier, FileItem, SNNote, UuidString } from '@standardnotes/snjs'
import {
ChallengeReason,
ContentType,
KeyboardModifier,
FileItem,
SNNote,
UuidString,
InternalEventBus,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable, runInAction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { ViewControllerManager } from '../Services/ViewControllerManager/ViewControllerManager'
import { ItemListController } from './ItemList/ItemListController'
import { NotesController } from './NotesController'
type SelectedItems = Record<UuidString, ListableContentItem>
export class SelectedItemsController extends AbstractViewController {
lastSelectedItem: ListableContentItem | undefined
selectedItems: SelectedItems = {}
private itemListController!: ItemListController
private notesController!: NotesController
constructor(
application: WebApplication,
override viewControllerManager: ViewControllerManager,
appObservers: (() => void)[],
) {
super(application)
override deinit(): void {
super.deinit()
;(this.itemListController as unknown) = undefined
;(this.notesController as unknown) = undefined
}
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
selectedItems: observable,
@@ -26,9 +39,14 @@ export class SelectedItemsController extends AbstractViewController {
selectItem: action,
setSelectedItems: action,
})
}
appObservers.push(
application.streamItems<SNNote | FileItem>(
public setServicestPostConstruction(itemListController: ItemListController, notesController: NotesController) {
this.itemListController = itemListController
this.notesController = notesController
this.disposers.push(
this.application.streamItems<SNNote | FileItem>(
[ContentType.Note, ContentType.File],
({ changed, inserted, removed }) => {
runInAction(() => {
@@ -82,7 +100,7 @@ export class SelectedItemsController extends AbstractViewController {
}
private selectItemsRange = async (selectedItem: ListableContentItem): Promise<void> => {
const items = this.viewControllerManager.contentListController.renderedItems
const items = this.itemListController.renderedItems
const lastSelectedItemIndex = items.findIndex((item) => item.uuid == this.lastSelectedItem?.uuid)
const selectedItemIndex = items.findIndex((item) => item.uuid == selectedItem.uuid)
@@ -171,7 +189,7 @@ export class SelectedItemsController extends AbstractViewController {
if (this.selectedItemsCount === 1) {
const item = Object.values(this.selectedItems)[0]
if (item.content_type === ContentType.Note) {
await this.viewControllerManager.notesController.openNote(item.uuid)
await this.notesController.openNote(item.uuid)
}
}

View File

@@ -3,7 +3,7 @@ import {
ApplicationEvent,
ClientDisplayableError,
convertTimestampToMilliseconds,
DeinitSource,
InternalEventBus,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable } from 'mobx'
import { WebApplication } from '../../Application/Application'
@@ -15,16 +15,16 @@ export class SubscriptionController extends AbstractViewController {
userSubscription: Subscription | undefined = undefined
availableSubscriptions: AvailableSubscriptions | undefined = undefined
override deinit(source: DeinitSource) {
super.deinit(source)
override deinit() {
super.deinit()
;(this.userSubscription as unknown) = undefined
;(this.availableSubscriptions as unknown) = undefined
destroyAllObjectProperties(this)
}
constructor(application: WebApplication, appObservers: (() => void)[]) {
super(application)
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
userSubscription: observable,
@@ -39,15 +39,21 @@ export class SubscriptionController extends AbstractViewController {
setAvailableSubscriptions: action,
})
appObservers.push(
this.disposers.push(
application.addEventObserver(async () => {
if (application.hasAccount()) {
this.getSubscriptionInfo().catch(console.error)
}
}, ApplicationEvent.Launched),
)
this.disposers.push(
application.addEventObserver(async () => {
this.getSubscriptionInfo().catch(console.error)
}, ApplicationEvent.SignedIn),
)
this.disposers.push(
application.addEventObserver(async () => {
this.getSubscriptionInfo().catch(console.error)
}, ApplicationEvent.UserRolesChanged),

View File

@@ -13,7 +13,8 @@ import {
DesktopClientRequiresWebMethods,
DesktopDeviceInterface,
} from '@standardnotes/snjs'
import { WebAppEvent, WebApplication } from '@/Application/Application'
import { WebApplication } from '@/Application/Application'
import { WebAppEvent } from '@/Application/WebAppEvent'
export class DesktopManager
extends ApplicationService
@@ -106,11 +107,11 @@ export class DesktopManager
}
windowGainedFocus(): void {
this.webApplication.notifyWebEvent(WebAppEvent.DesktopWindowGainedFocus)
this.webApplication.notifyWebEvent(WebAppEvent.WindowDidFocus)
}
windowLostFocus(): void {
this.webApplication.notifyWebEvent(WebAppEvent.DesktopWindowLostFocus)
this.webApplication.notifyWebEvent(WebAppEvent.WindowDidBlur)
}
async onComponentInstallationComplete(componentData: DecryptedTransferPayload<ComponentContent>, error: unknown) {
@@ -155,10 +156,10 @@ export class DesktopManager
}
didBeginBackup() {
this.webApplication.getViewControllerManager().beganBackupDownload()
this.webApplication.notifyWebEvent(WebAppEvent.BeganBackupDownload)
}
didFinishBackup(success: boolean) {
this.webApplication.getViewControllerManager().endedBackupDownload(success)
this.webApplication.notifyWebEvent(WebAppEvent.EndedBackupDownload, { success })
}
}

View File

@@ -0,0 +1,204 @@
import { storage, StorageKey } from '@/Services/LocalStorage'
import { WebApplication } from '@/Application/Application'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { destroyAllObjectProperties } from '@/Utils'
import { ApplicationEvent, DeinitSource, WebOrDesktopDeviceInterface, InternalEventBus } 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 { NavigationController } from '../Controllers/Navigation/NavigationController'
import { FilePreviewModalController } from '../Controllers/FilePreviewModalController'
import { SelectedItemsController } from '../Controllers/SelectedItemsController'
export class ViewControllerManager {
readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures
private unsubAppEventObserver!: () => void
showBetaWarning: boolean
public 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 itemListController: 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: NavigationController
readonly selectionController: SelectedItemsController
public isSessionsModalVisible = false
private appEventObserverRemovers: (() => void)[] = []
private eventBus: InternalEventBus
constructor(public application: WebApplication, private device: WebOrDesktopDeviceInterface) {
this.eventBus = new InternalEventBus()
this.selectionController = new SelectedItemsController(application, this.eventBus)
this.noteTagsController = new NoteTagsController(application, this.eventBus)
this.featuresController = new FeaturesController(application, this.eventBus)
this.navigationController = new NavigationController(application, this.featuresController, this.eventBus)
this.notesController = new NotesController(
application,
this.selectionController,
this.noteTagsController,
this.navigationController,
this.eventBus,
)
this.searchOptionsController = new SearchOptionsController(application, this.eventBus)
this.itemListController = new ItemListController(
application,
this.navigationController,
this.searchOptionsController,
this.selectionController,
this.notesController,
this.noteTagsController,
this.eventBus,
)
this.notesController.setServicestPostConstruction(this.itemListController)
this.noteTagsController.setServicestPostConstruction(this.itemListController)
this.selectionController.setServicestPostConstruction(this.itemListController, this.notesController)
this.noAccountWarningController = new NoAccountWarningController(application, this.eventBus)
this.accountMenuController = new AccountMenuController(application, this.eventBus)
this.subscriptionController = new SubscriptionController(application, this.eventBus)
this.purchaseFlowController = new PurchaseFlowController(application, this.eventBus)
this.filesController = new FilesController(
application,
this.notesController,
this.selectionController,
this.filePreviewModalController,
this.eventBus,
)
this.addAppEventObserver()
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,
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.appEventObserverRemovers.forEach((remover) => remover())
this.appEventObserverRemovers.length = 0
;(this.device as unknown) = 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()
;(this.featuresController as unknown) = undefined
this.accountMenuController.deinit()
;(this.accountMenuController as unknown) = undefined
this.filesController.deinit()
;(this.filesController as unknown) = undefined
this.noAccountWarningController.deinit()
;(this.noAccountWarningController as unknown) = undefined
this.notesController.deinit()
;(this.notesController as unknown) = undefined
this.itemListController.deinit()
;(this.itemListController as unknown) = undefined
this.noteTagsController.deinit()
;(this.noteTagsController as unknown) = undefined
this.purchaseFlowController.deinit()
;(this.purchaseFlowController as unknown) = undefined
this.searchOptionsController.deinit()
;(this.searchOptionsController as unknown) = undefined
this.subscriptionController.deinit()
;(this.subscriptionController as unknown) = undefined
this.navigationController.deinit()
;(this.navigationController as unknown) = undefined
destroyAllObjectProperties(this)
}
openSessionsModal(): void {
this.isSessionsModalVisible = true
}
closeSessionsModal(): void {
this.isSessionsModalVisible = false
}
addAppEventObserver() {
this.unsubAppEventObserver = this.application.addEventObserver(async (eventName) => {
switch (eventName) {
case ApplicationEvent.Launched:
if (window.location.search.includes('purchase=true')) {
this.purchaseFlowController.openPurchaseFlow()
}
break
case ApplicationEvent.SyncStatusChanged:
this.syncStatusController.update(this.application.sync.getSyncStatus())
break
}
})
}
}

View File

@@ -1,298 +0,0 @@
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)
}
}

View File

@@ -1,10 +0,0 @@
export enum ViewControllerManagerEvent {
TagChanged,
ActiveEditorChanged,
PanelResized,
EditorFocused,
BeganBackupDownload,
EndedBackupDownload,
WindowDidFocus,
WindowDidBlur,
}

View File

@@ -1,2 +0,0 @@
export * from './ViewControllerManager'
export * from './ViewControllerManagerEvent'

View File

@@ -0,0 +1 @@
export type Disposer = () => void

View File

@@ -73,10 +73,10 @@
"@standardnotes/components": "1.8.2",
"@standardnotes/filepicker": "1.16.2",
"@standardnotes/icons": "^1.1.8",
"@standardnotes/services": "^1.13.3",
"@standardnotes/sncrypto-web": "1.10.1",
"@standardnotes/snjs": "^2.114.5",
"@standardnotes/stylekit": "5.29.3",
"@standardnotes/services": "^1.13.6",
"@zip.js/zip.js": "^2.4.10",
"mobx": "^6.5.0",
"mobx-react-lite": "^3.3.0",

View File

@@ -2366,6 +2366,14 @@
"@standardnotes/common" "^1.22.0"
jsonwebtoken "^8.5.1"
"@standardnotes/auth@^3.19.2":
version "3.19.2"
resolved "https://registry.yarnpkg.com/@standardnotes/auth/-/auth-3.19.2.tgz#a2758cdde588190eebd30e567fe7756c6823adcd"
integrity sha512-m7MvSN2BHHh8+FZ3tUe6IpoPGzu/I1lmQF8snlYfuBmSxVdwVLTYhIbFAjfi4PST/Rx3FXnaoMnfJSR0k+OmWw==
dependencies:
"@standardnotes/common" "^1.22.0"
jsonwebtoken "^8.5.1"
"@standardnotes/common@^1.22.0":
version "1.22.0"
resolved "https://registry.yarnpkg.com/@standardnotes/common/-/common-1.22.0.tgz#397604fb4b92901bac276940a2647509b70a7ad2"
@@ -2420,6 +2428,14 @@
"@standardnotes/auth" "^3.19.1"
"@standardnotes/common" "^1.22.0"
"@standardnotes/features@^1.44.6":
version "1.44.6"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.44.6.tgz#1a7872a7e79026a553d3670f8610b2b79c1f5fa4"
integrity sha512-iP0oR4bb16Rx0kSspl0R8rSKY38hF59ExaoMIREf0MGH8WLjwDJyILafGfhxv8NjMeRtpIIXKbK+TokM6A5ZXg==
dependencies:
"@standardnotes/auth" "^3.19.2"
"@standardnotes/common" "^1.22.0"
"@standardnotes/filepicker@1.16.2":
version "1.16.2"
resolved "https://registry.yarnpkg.com/@standardnotes/filepicker/-/filepicker-1.16.2.tgz#d6fda94b5578f30e6b4f792c874e3eb11ce58453"
@@ -2473,6 +2489,15 @@
"@standardnotes/responses" "^1.6.28"
"@standardnotes/utils" "^1.6.10"
"@standardnotes/models@^1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@standardnotes/models/-/models-1.11.1.tgz#d622b7cffd7ee4faebcd564d7ae880b372ec7be8"
integrity sha512-XKXoV8Pi5iuzrjUGWs4grvxn3m2BQyt49Br+euToOkgvZvW5HIiaCGLwAtvP5S3d3ecgD5EvbLzGJIFEs5F4rw==
dependencies:
"@standardnotes/features" "^1.44.6"
"@standardnotes/responses" "^1.6.29"
"@standardnotes/utils" "^1.6.10"
"@standardnotes/responses@^1.6.27":
version "1.6.27"
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.6.27.tgz#3a440090e5cee09f2980df5cc57a60f76adff735"
@@ -2491,6 +2516,15 @@
"@standardnotes/common" "^1.22.0"
"@standardnotes/features" "^1.44.5"
"@standardnotes/responses@^1.6.29":
version "1.6.29"
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.6.29.tgz#fac5875bb84e382d7b1acf14c0af77876fe5c41d"
integrity sha512-BWrkR6gIWD+dC9/a+ii/pDzWtIlAsgZWICTwKZ34jBgjPSO1svewcHzXwBXILKZd6JvrUf6pglmSt3I9fmsHaQ==
dependencies:
"@standardnotes/auth" "^3.19.2"
"@standardnotes/common" "^1.22.0"
"@standardnotes/features" "^1.44.6"
"@standardnotes/services@^1.13.3":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.13.3.tgz#b857a6ed42b15d40c2e9d08b41739f5fb6b0d3e0"
@@ -2513,6 +2547,17 @@
"@standardnotes/responses" "^1.6.28"
"@standardnotes/utils" "^1.6.10"
"@standardnotes/services@^1.13.6":
version "1.13.6"
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.13.6.tgz#9ab4d0c3ed0ad3693ba54ac360f4fdaad08f7410"
integrity sha512-Vt/hptzJK4D6qUPp/cdhOLlRT57uf3CDjDUwyfnELXDyU8mFTVvS/M1deD1PecNWodsfLH7aDWeMWTKApP6LKg==
dependencies:
"@standardnotes/auth" "^3.19.2"
"@standardnotes/common" "^1.22.0"
"@standardnotes/models" "^1.11.1"
"@standardnotes/responses" "^1.6.29"
"@standardnotes/utils" "^1.6.10"
"@standardnotes/settings@^1.14.3":
version "1.14.3"
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.14.3.tgz#021085e8c383a9893a2c49daa74cc0754ccd67b5"