Files
standardnotes-app-web/packages/web/src/javascripts/Controllers/PaneController/PaneController.ts

274 lines
8.1 KiB
TypeScript

import {
TOGGLE_FOCUS_MODE_COMMAND,
TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
} from '@standardnotes/ui-services'
import { ApplicationEvent, InternalEventBus, PrefKey, removeFromArray } from '@standardnotes/snjs'
import { AppPaneId } from '../../Components/Panes/AppPaneMetadata'
import { isMobileScreen } from '@/Utils'
import { makeObservable, observable, action, computed } from 'mobx'
import { Disposer } from '@/Types/Disposer'
import { MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
import { WebApplication } from '@/Application/Application'
import { AbstractViewController } from '../Abstract/AbstractViewController'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { log, LoggingDomain } from '@/Logging'
import { PaneLayout } from './PaneLayout'
import { panesForLayout } from './panesForLayout'
import { getIsTabletOrMobileScreen } from '@/Hooks/useIsTabletOrMobileScreen'
const MinimumNavPanelWidth = PrefDefaults[PrefKey.TagsPanelWidth]
const MinimumNotesPanelWidth = PrefDefaults[PrefKey.NotesPanelWidth]
const FOCUS_MODE_CLASS_NAME = 'focus-mode'
const DISABLING_FOCUS_MODE_CLASS_NAME = 'disable-focus-mode'
const FOCUS_MODE_ANIMATION_DURATION = 1255
export class PaneController extends AbstractViewController {
isInMobileView = isMobileScreen()
protected disposers: Disposer[] = []
panes: AppPaneId[] = []
currentNavPanelWidth = 0
currentItemsPanelWidth = 0
focusModeEnabled = false
listPaneExplicitelyCollapsed = false
navigationPaneExplicitelyCollapsed = false
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
panes: observable,
isInMobileView: observable,
currentNavPanelWidth: observable,
currentItemsPanelWidth: observable,
focusModeEnabled: observable,
currentPane: computed,
previousPane: computed,
isListPaneCollapsed: computed,
isNavigationPaneCollapsed: computed,
setIsInMobileView: action,
toggleListPane: action,
toggleNavigationPane: action,
setCurrentItemsPanelWidth: action,
setCurrentNavPanelWidth: action,
presentPane: action,
dismissLastPane: action,
replacePanes: action,
popToPane: action,
removePane: action,
insertPaneAtIndex: action,
setPaneLayout: action,
setFocusModeEnabled: action,
})
this.setCurrentNavPanelWidth(application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth))
this.setCurrentItemsPanelWidth(application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth))
const screen = getIsTabletOrMobileScreen(application)
this.panes = screen.isTabletOrMobile
? [AppPaneId.Navigation, AppPaneId.Items]
: [AppPaneId.Navigation, AppPaneId.Items, AppPaneId.Editor]
const mediaQuery = window.matchMedia(MediaQueryBreakpoints.md)
if (mediaQuery?.addEventListener != undefined) {
mediaQuery.addEventListener('change', this.mediumScreenMQHandler)
} else {
mediaQuery.addListener(this.mediumScreenMQHandler)
}
this.disposers.push(
application.addEventObserver(async () => {
this.setCurrentNavPanelWidth(application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth))
this.setCurrentItemsPanelWidth(application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth))
}, ApplicationEvent.PreferencesChanged),
application.keyboardService.addCommandHandler({
command: TOGGLE_FOCUS_MODE_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
this.setFocusModeEnabled(!this.focusModeEnabled)
return true
},
}),
application.keyboardService.addCommandHandler({
command: TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
this.toggleListPane()
},
}),
application.keyboardService.addCommandHandler({
command: TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
this.toggleNavigationPane()
},
}),
)
}
setCurrentNavPanelWidth(width: number) {
this.currentNavPanelWidth = width
}
setCurrentItemsPanelWidth(width: number) {
this.currentItemsPanelWidth = width
}
deinit() {
super.deinit()
const mq = window.matchMedia(MediaQueryBreakpoints.md)
if (mq?.removeEventListener != undefined) {
mq.removeEventListener('change', this.mediumScreenMQHandler)
} else {
mq.removeListener(this.mediumScreenMQHandler)
}
}
get currentPane(): AppPaneId {
return this.panes[this.panes.length - 1] || this.panes[0]
}
get previousPane(): AppPaneId {
return this.panes[this.panes.length - 2] || this.panes[0]
}
mediumScreenMQHandler = (event: MediaQueryListEvent) => {
if (event.matches) {
this.setIsInMobileView(false)
} else {
this.setIsInMobileView(true)
}
}
setIsInMobileView = (isInMobileView: boolean) => {
this.isInMobileView = isInMobileView
}
setPaneLayout = (layout: PaneLayout) => {
log(LoggingDomain.Panes, 'Set pane layout', layout)
const panes = panesForLayout(layout, this.application)
if (panes.includes(AppPaneId.Items) && this.listPaneExplicitelyCollapsed) {
removeFromArray(panes, AppPaneId.Items)
}
if (panes.includes(AppPaneId.Navigation) && this.navigationPaneExplicitelyCollapsed) {
removeFromArray(panes, AppPaneId.Navigation)
}
this.replacePanes(panes)
}
replacePanes = (panes: AppPaneId[]) => {
log(LoggingDomain.Panes, 'Replacing panes', panes)
this.panes = panes
}
presentPane = (pane: AppPaneId) => {
log(LoggingDomain.Panes, 'Presenting pane', pane)
if (pane === this.currentPane) {
return
}
if (pane === AppPaneId.Items && this.currentPane === AppPaneId.Editor) {
this.dismissLastPane()
return
}
if (this.currentPane !== pane) {
this.panes.push(pane)
}
}
insertPaneAtIndex = (pane: AppPaneId, index: number) => {
log(LoggingDomain.Panes, 'Inserting pane', pane, 'at index', index)
this.panes.splice(index, 0, pane)
}
dismissLastPane = (): AppPaneId | undefined => {
log(LoggingDomain.Panes, 'Dismissing last pane')
return this.panes.pop()
}
removePane = (pane: AppPaneId) => {
log(LoggingDomain.Panes, 'Removing pane', pane)
removeFromArray(this.panes, pane)
}
popToPane = (pane: AppPaneId) => {
log(LoggingDomain.Panes, 'Popping to pane', pane)
let index = this.panes.length - 1
while (index >= 0) {
if (this.panes[index] === pane) {
break
}
this.dismissLastPane()
index--
}
}
toggleListPane = () => {
if (this.panes.includes(AppPaneId.Items)) {
this.removePane(AppPaneId.Items)
this.listPaneExplicitelyCollapsed = true
} else {
if (this.panes.includes(AppPaneId.Navigation)) {
this.insertPaneAtIndex(AppPaneId.Items, 1)
} else {
this.insertPaneAtIndex(AppPaneId.Items, 0)
}
this.listPaneExplicitelyCollapsed = false
}
}
toggleNavigationPane = () => {
if (this.panes.includes(AppPaneId.Navigation)) {
this.removePane(AppPaneId.Navigation)
this.navigationPaneExplicitelyCollapsed = true
} else {
this.insertPaneAtIndex(AppPaneId.Navigation, 0)
this.navigationPaneExplicitelyCollapsed = false
}
}
get isListPaneCollapsed() {
return !this.panes.includes(AppPaneId.Items)
}
get isNavigationPaneCollapsed() {
return !this.panes.includes(AppPaneId.Navigation)
}
setFocusModeEnabled = (enabled: boolean): void => {
this.focusModeEnabled = enabled
if (enabled) {
document.body.classList.add(FOCUS_MODE_CLASS_NAME)
return
}
if (document.body.classList.contains(FOCUS_MODE_CLASS_NAME)) {
document.body.classList.add(DISABLING_FOCUS_MODE_CLASS_NAME)
document.body.classList.remove(FOCUS_MODE_CLASS_NAME)
setTimeout(() => {
document.body.classList.remove(DISABLING_FOCUS_MODE_CLASS_NAME)
}, FOCUS_MODE_ANIMATION_DURATION)
}
}
}