ソースを参照

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

Improve scope event types

GitOrigin-RevId: 5327c56a14244a2513748d3bcbac04413d104e12
Alf Eaton 1 年間 前
コミット
797686939f

+ 3 - 8
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)
           })
         }
       }

+ 5 - 1
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]

+ 3 - 2
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]

+ 12 - 6
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<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,

+ 6 - 5
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) => {

+ 25 - 18
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')

+ 4 - 3
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<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]

+ 4 - 3
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<T extends ScopeEventName>(
+  eventName: T,
+  listener: (event: Event, ...args: IdeEvents[T]) => void
 ) {
   const { scopeEventEmitter } = useIdeContext()
 

+ 6 - 6
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: <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
 }