From 797686939ff44ec8713e1a78f4e7a44da7a3e2de Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 4 Feb 2025 09:06:28 +0000 Subject: [PATCH] Merge pull request #23284 from overleaf/ae-scope-event-types Improve scope event types GitOrigin-RevId: 5327c56a14244a2513748d3bcbac04413d104e12 --- .../context/editor-manager-context.tsx | 11 ++--- .../ide-react/context/outline-context.tsx | 6 ++- .../ide-react/create-ide-event-emitter.ts | 5 ++- .../react-scope-event-emitter.ts | 18 +++++--- .../components/pdf-synctex-controls.jsx | 11 ++--- .../hooks/use-codemirror-scope.ts | 43 +++++++++++-------- .../shared/hooks/use-scope-event-emitter.ts | 7 +-- .../shared/hooks/use-scope-event-listener.ts | 7 +-- services/web/types/ide/scope-event-emitter.ts | 12 +++--- 9 files changed, 68 insertions(+), 52 deletions(-) diff --git a/services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx b/services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx index b2a6c4374e..91de3cbdcd 100644 --- a/services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/editor-manager-context.tsx @@ -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) }) } } diff --git a/services/web/frontend/js/features/ide-react/context/outline-context.tsx b/services/web/frontend/js/features/ide-react/context/outline-context.tsx index ac64033897..ddba020677 100644 --- a/services/web/frontend/js/features/ide-react/context/outline-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/outline-context.tsx @@ -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] diff --git a/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts b/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts index ed20e380c4..8fa7a500ed 100644 --- a/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts +++ b/services/web/frontend/js/features/ide-react/create-ide-event-emitter.ts @@ -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] diff --git a/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts b/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts index 88609409c2..e469dca8b8 100644 --- a/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts +++ b/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts @@ -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( + 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) => { - listener({}, ...event.detail) + on( + eventName: T, + listener: (event: Event, ...args: IdeEvents[T]) => void + ) { + const wrappedListener = (event: CustomEvent) => { + listener(event, ...event.detail) } this.eventEmitter.addEventListener( eventName, diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx index 7ecb66a9a2..c7c28688a0 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.jsx @@ -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) => { diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts index 86d5ebd104..a7de085755 100644 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts +++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts @@ -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') diff --git a/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts b/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts index 788fce9ab4..406b186df2 100644 --- a/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts +++ b/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts @@ -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( + eventName: T, broadcast = true ) { const { scopeEventEmitter } = useIdeContext() return useCallback( - (...detail: unknown[]) => { + (...detail: IdeEvents[T]) => { scopeEventEmitter.emit(eventName, broadcast, ...detail) }, [scopeEventEmitter, eventName, broadcast] diff --git a/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts b/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts index 1c3b59df12..07bae7d5a8 100644 --- a/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts +++ b/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts @@ -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( + eventName: T, + listener: (event: Event, ...args: IdeEvents[T]) => void ) { const { scopeEventEmitter } = useIdeContext() diff --git a/services/web/types/ide/scope-event-emitter.ts b/services/web/types/ide/scope-event-emitter.ts index 7dbfae21a0..8be3aeb01b 100644 --- a/services/web/types/ide/scope-event-emitter.ts +++ b/services/web/types/ide/scope-event-emitter.ts @@ -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: ( + eventName: T, broadcast: boolean, - ...detail: unknown[] + ...detail: IdeEvents[T] ) => void - on: ( - eventName: ScopeEventName, - listener: (...args: unknown[]) => void + on: ( + eventName: T, + listener: (event: Event, ...args: IdeEvents[T]) => void ) => () => void }