148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
import { useCallback, useEffect } from 'react'
|
|
import type { MutableRefObject } from 'react'
|
|
import type { SidebarSelection, VaultEntry } from '../types'
|
|
import type { NoteListFilter } from '../utils/noteListHelpers'
|
|
import { trackEvent } from '../lib/telemetry'
|
|
import {
|
|
focusNoteListContainer,
|
|
isEditableElement,
|
|
isEditorEscapeTarget,
|
|
popNeighborhoodHistory,
|
|
pushNeighborhoodHistory,
|
|
resolveNeighborhoodSelection,
|
|
shouldProcessNeighborhoodEscape,
|
|
} from '../utils/neighborhoodHistory'
|
|
|
|
interface SetSelectionOptions {
|
|
preserveNeighborhoodHistory?: boolean
|
|
}
|
|
|
|
type SetSelection = (selection: SidebarSelection, options?: SetSelectionOptions) => void
|
|
|
|
interface NeighborhoodRefs {
|
|
neighborhoodHistoryRef: MutableRefObject<SidebarSelection[]>
|
|
selectionRef: MutableRefObject<SidebarSelection>
|
|
}
|
|
|
|
interface UseNeighborhoodEntryOptions extends NeighborhoodRefs {
|
|
setSelection: SetSelection
|
|
}
|
|
|
|
interface UseSelectionSanitizerOptions extends NeighborhoodRefs {
|
|
effectiveSelection: SidebarSelection
|
|
selection: SidebarSelection
|
|
setNoteListFilter: (filter: NoteListFilter) => void
|
|
setSelection: (selection: SidebarSelection) => void
|
|
}
|
|
|
|
interface UseNeighborhoodHistoryBackOptions {
|
|
neighborhoodHistoryRef: MutableRefObject<SidebarSelection[]>
|
|
setSelection: SetSelection
|
|
}
|
|
|
|
interface UseNeighborhoodEscapeOptions {
|
|
onBack: () => boolean
|
|
selectionRef: MutableRefObject<SidebarSelection>
|
|
shouldBlockNeighborhoodEscape: boolean
|
|
}
|
|
|
|
function focusNoteListOnNextFrame(): void {
|
|
requestAnimationFrame(() => {
|
|
focusNoteListContainer(document)
|
|
})
|
|
}
|
|
|
|
export function useNeighborhoodEntry({
|
|
neighborhoodHistoryRef,
|
|
selectionRef,
|
|
setSelection,
|
|
}: UseNeighborhoodEntryOptions) {
|
|
return useCallback((entry: VaultEntry) => {
|
|
const currentSelection = selectionRef.current
|
|
const nextSelection = resolveNeighborhoodSelection(currentSelection, entry)
|
|
trackEvent('neighborhood_mode_toggled', { action: nextSelection.action })
|
|
|
|
if (nextSelection.action === 'exit') {
|
|
const { previousSelection, nextHistory } = popNeighborhoodHistory(neighborhoodHistoryRef.current)
|
|
neighborhoodHistoryRef.current = nextHistory
|
|
setSelection(previousSelection ?? nextSelection.selection, previousSelection ? { preserveNeighborhoodHistory: true } : undefined)
|
|
focusNoteListOnNextFrame()
|
|
return
|
|
}
|
|
|
|
neighborhoodHistoryRef.current = pushNeighborhoodHistory(
|
|
neighborhoodHistoryRef.current,
|
|
currentSelection,
|
|
nextSelection.selection,
|
|
)
|
|
setSelection(nextSelection.selection, { preserveNeighborhoodHistory: true })
|
|
}, [neighborhoodHistoryRef, selectionRef, setSelection])
|
|
}
|
|
|
|
export function useSelectionSanitizer({
|
|
effectiveSelection,
|
|
neighborhoodHistoryRef,
|
|
selection,
|
|
selectionRef,
|
|
setNoteListFilter,
|
|
setSelection,
|
|
}: UseSelectionSanitizerOptions): void {
|
|
useEffect(() => {
|
|
selectionRef.current = effectiveSelection
|
|
}, [effectiveSelection, selectionRef])
|
|
|
|
useEffect(() => {
|
|
if (effectiveSelection === selection) return
|
|
|
|
if (effectiveSelection.kind !== 'entity') {
|
|
neighborhoodHistoryRef.current = []
|
|
}
|
|
setSelection(effectiveSelection)
|
|
setNoteListFilter('open')
|
|
}, [effectiveSelection, neighborhoodHistoryRef, selection, setNoteListFilter, setSelection])
|
|
}
|
|
|
|
export function useNeighborhoodHistoryBack({
|
|
neighborhoodHistoryRef,
|
|
setSelection,
|
|
}: UseNeighborhoodHistoryBackOptions) {
|
|
return useCallback(() => {
|
|
const { previousSelection, nextHistory } = popNeighborhoodHistory(neighborhoodHistoryRef.current)
|
|
if (!previousSelection) return false
|
|
|
|
neighborhoodHistoryRef.current = nextHistory
|
|
setSelection(previousSelection, { preserveNeighborhoodHistory: true })
|
|
focusNoteListOnNextFrame()
|
|
return true
|
|
}, [neighborhoodHistoryRef, setSelection])
|
|
}
|
|
|
|
export function useNeighborhoodEscape({
|
|
onBack,
|
|
selectionRef,
|
|
shouldBlockNeighborhoodEscape,
|
|
}: UseNeighborhoodEscapeOptions): void {
|
|
useEffect(() => {
|
|
const handleWindowKeyDown = (event: KeyboardEvent) => {
|
|
if (!shouldProcessNeighborhoodEscape(event, selectionRef.current, shouldBlockNeighborhoodEscape)) return
|
|
|
|
const activeElement = document.activeElement
|
|
if (isEditorEscapeTarget(activeElement)) {
|
|
event.preventDefault()
|
|
activeElement.blur()
|
|
focusNoteListOnNextFrame()
|
|
return
|
|
}
|
|
|
|
if (isEditableElement(activeElement)) return
|
|
|
|
if (onBack()) {
|
|
event.preventDefault()
|
|
}
|
|
}
|
|
|
|
window.addEventListener('keydown', handleWindowKeyDown)
|
|
return () => window.removeEventListener('keydown', handleWindowKeyDown)
|
|
}, [onBack, selectionRef, shouldBlockNeighborhoodEscape])
|
|
}
|