refactor: pass sub controllers to controllers instead of passing global controller manager (#1061)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
9
app/assets/javascripts/Application/WebAppEvent.ts
Normal file
9
app/assets/javascripts/Application/WebAppEvent.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export enum WebAppEvent {
|
||||
NewUpdateAvailable = 'NewUpdateAvailable',
|
||||
EditorFocused = 'EditorFocused',
|
||||
BeganBackupDownload = 'BeganBackupDownload',
|
||||
EndedBackupDownload = 'EndedBackupDownload',
|
||||
PanelResized = 'PanelResized',
|
||||
WindowDidFocus = 'WindowDidFocus',
|
||||
WindowDidBlur = 'WindowDidBlur',
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -169,7 +169,7 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
|
||||
<Icon type="help" className={iconClassName} />
|
||||
Help & feedback
|
||||
</div>
|
||||
<span className="color-neutral">v{viewControllerManager.version}</span>
|
||||
<span className="color-neutral">v{application.version}</span>
|
||||
</MenuItem>
|
||||
{user ? (
|
||||
<>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum CrossControllerEvent {
|
||||
TagChanged = 'TagChanged',
|
||||
ActiveEditorChanged = 'ActiveEditorChanged',
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
204
app/assets/javascripts/Services/ViewControllerManager.ts
Normal file
204
app/assets/javascripts/Services/ViewControllerManager.ts
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export enum ViewControllerManagerEvent {
|
||||
TagChanged,
|
||||
ActiveEditorChanged,
|
||||
PanelResized,
|
||||
EditorFocused,
|
||||
BeganBackupDownload,
|
||||
EndedBackupDownload,
|
||||
WindowDidFocus,
|
||||
WindowDidBlur,
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ViewControllerManager'
|
||||
export * from './ViewControllerManagerEvent'
|
||||
1
app/assets/javascripts/Types/Disposer.ts
Normal file
1
app/assets/javascripts/Types/Disposer.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Disposer = () => void
|
||||
@@ -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",
|
||||
|
||||
45
yarn.lock
45
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user