Merge pull request #23284 from overleaf/ae-scope-event-types

Improve scope event types

GitOrigin-RevId: 5327c56a14244a2513748d3bcbac04413d104e12
This commit is contained in:
Alf Eaton
2025-02-04 09:06:28 +00:00
committed by Copybot
parent 4b1babd4ea
commit 797686939f
9 changed files with 68 additions and 52 deletions

View File

@@ -33,7 +33,7 @@ import { Update } from '@/features/history/services/types/update'
import { useDebugDiffTracker } from '../hooks/use-debug-diff-tracker'
import { useEditorContext } from '@/shared/context/editor-context'
interface GotoOffsetOptions {
export interface GotoOffsetOptions {
gotoOffset: number
}
@@ -243,12 +243,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
const jumpToLine = useCallback(
(options: GotoLineOptions) => {
goToLineEmitter(
options.gotoLine,
options.gotoColumn ?? 0,
options.syncToPdf ?? false,
options.selectionLength
)
goToLineEmitter(options)
},
[goToLineEmitter]
)
@@ -460,7 +455,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
}
} else if (hasGotoOffset(options)) {
window.setTimeout(() => {
eventEmitter.emit('editor:gotoOffset', options.gotoOffset)
eventEmitter.emit('editor:gotoOffset', options)
})
}
}

View File

@@ -102,7 +102,11 @@ export const OutlineProvider: FC = ({ children }) => {
const jumpToLine = useCallback(
(lineNumber: number, syncToPdf: boolean) => {
setIgnoreNextScroll(true)
goToLineEmitter(lineNumber, 0, syncToPdf)
goToLineEmitter({
gotoLine: lineNumber,
gotoColumn: 0,
syncToPdf,
})
eventTracking.sendMB('outline-jump-to-line')
},
[goToLineEmitter]

View File

@@ -2,6 +2,7 @@ import { Project } from '../../../../types/project'
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
import { ShareJsDoc } from '@/features/ide-react/editor/share-js-doc'
import { GotoLineOptions } from '@/features/ide-react/types/goto-line-options'
import { GotoOffsetOptions } from '@/features/ide-react/context/editor-manager-context'
import { CursorPosition } from '@/features/ide-react/types/cursor-position'
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
@@ -13,12 +14,12 @@ export type IdeEvents = {
'doc:opened': []
'ide:opAcknowledged': [{ doc_id: string; op: any }]
'store-doc-position': []
'editor:gotoOffset': [gotoOffset: number]
'editor:gotoOffset': [options: GotoOffsetOptions]
'editor:gotoLine': [options: GotoLineOptions]
'cursor:editor:update': [position: CursorPosition]
'outline-toggled': [isOpen: boolean]
'cursor:editor:syncToPdf': []
'scroll:editor:update': []
'scroll:editor:update': [middleVisibleLine?: number]
'comment:start_adding': []
'history:toggle': []
'entity:deleted': [entity: FileTreeFindResult]

View File

@@ -2,20 +2,26 @@ import {
ScopeEventEmitter,
ScopeEventName,
} from '../../../../../types/ide/scope-event-emitter'
import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
export class ReactScopeEventEmitter implements ScopeEventEmitter {
// eslint-disable-next-line no-useless-constructor
constructor(private readonly eventEmitter: EventTarget) {}
emit(eventName: ScopeEventName, broadcast: boolean, ...detail: unknown[]) {
emit<T extends ScopeEventName>(
eventName: T,
broadcast: boolean,
...detail: IdeEvents[T]
) {
this.eventEmitter.dispatchEvent(new CustomEvent(eventName, { detail }))
}
on(eventName: ScopeEventName, listener: (...args: unknown[]) => void) {
// A listener attached via useScopeEventListener expects an event as the
// first parameter. We don't have one, so just provide an empty object
const wrappedListener = (event: CustomEvent<unknown[]>) => {
listener({}, ...event.detail)
on<T extends ScopeEventName>(
eventName: T,
listener: (event: Event, ...args: IdeEvents[T]) => void
) {
const wrappedListener = (event: CustomEvent<IdeEvents[T]>) => {
listener(event, ...event.detail)
}
this.eventEmitter.addEventListener(
eventName,

View File

@@ -309,11 +309,12 @@ function PdfSynctexControls() {
cursorPositionRef.current = cursorPosition
}, [cursorPosition])
const handleSyncToPdf = useCallback(() => {
syncToPdf(cursorPositionRef.current)
}, [syncToPdf])
useScopeEventListener('cursor:editor:syncToPdf', handleSyncToPdf)
useScopeEventListener(
'cursor:editor:syncToPdf',
useCallback(() => {
syncToPdf(cursorPositionRef.current)
}, [syncToPdf])
)
const _syncToCode = useCallback(
(position, visualOffset = 0) => {

View File

@@ -467,28 +467,35 @@ function useCodeMirrorScope(view: EditorView) {
const emitSyncToPdf = useScopeEventEmitter('cursor:editor:syncToPdf')
const handleGoToLine = useCallback(
(event, lineNumber, columnNumber, syncToPdf, selectionLength) => {
setCursorLineAndScroll(view, lineNumber, columnNumber, selectionLength)
if (syncToPdf) {
emitSyncToPdf()
}
},
[emitSyncToPdf, view]
)
// select and scroll to position on editor:gotoLine event (from synctex)
useScopeEventListener('editor:gotoLine', handleGoToLine)
const handleGoToOffset = useCallback(
(event, offset) => {
setCursorPositionAndScroll(view, offset)
},
[view]
useScopeEventListener(
'editor:gotoLine',
useCallback(
(_event, options) => {
setCursorLineAndScroll(
view,
options.gotoLine,
options.gotoColumn,
options.selectionLength
)
if (options.syncToPdf) {
emitSyncToPdf()
}
},
[emitSyncToPdf, view]
)
)
// select and scroll to position on editor:gotoOffset event (from review panel)
useScopeEventListener('editor:gotoOffset', handleGoToOffset)
useScopeEventListener(
'editor:gotoOffset',
useCallback(
(_event, options) => {
setCursorPositionAndScroll(view, options.gotoOffset)
},
[view]
)
)
// dispatch 'cursor:editor:update' to Angular scope (for synctex and realtime)
const dispatchCursorUpdate = useScopeEventEmitter('cursor:editor:update')

View File

@@ -1,15 +1,16 @@
import { useCallback } from 'react'
import { useIdeContext } from '../context/ide-context'
import { ScopeEventName } from '../../../../types/ide/scope-event-emitter'
import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
export default function useScopeEventEmitter(
eventName: ScopeEventName,
export default function useScopeEventEmitter<T extends ScopeEventName>(
eventName: T,
broadcast = true
) {
const { scopeEventEmitter } = useIdeContext()
return useCallback(
(...detail: unknown[]) => {
(...detail: IdeEvents[T]) => {
scopeEventEmitter.emit(eventName, broadcast, ...detail)
},
[scopeEventEmitter, eventName, broadcast]

View File

@@ -1,10 +1,11 @@
import { useEffect } from 'react'
import { useIdeContext } from '../context/ide-context'
import { ScopeEventName } from '../../../../types/ide/scope-event-emitter'
import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
export default function useScopeEventListener(
eventName: ScopeEventName,
listener: (...args: unknown[]) => void
export default function useScopeEventListener<T extends ScopeEventName>(
eventName: T,
listener: (event: Event, ...args: IdeEvents[T]) => void
) {
const { scopeEventEmitter } = useIdeContext()

View File

@@ -3,13 +3,13 @@ import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
export type ScopeEventName = keyof IdeEvents
export interface ScopeEventEmitter {
emit: (
eventName: ScopeEventName,
emit: <T extends ScopeEventName>(
eventName: T,
broadcast: boolean,
...detail: unknown[]
...detail: IdeEvents[T]
) => void
on: (
eventName: ScopeEventName,
listener: (...args: unknown[]) => void
on: <T extends ScopeEventName>(
eventName: T,
listener: (event: Event, ...args: IdeEvents[T]) => void
) => () => void
}