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 8db7d3cd95..ffc00091d4 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 @@ -24,7 +24,6 @@ import { findDocEntityById, findFileRefEntityById, } from '@/features/ide-react/util/find-doc-entity-by-id' -import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter' import { useModalsContext } from '@/features/ide-react/context/modals-context' import { useTranslation } from 'react-i18next' import customLocalStorage from '@/infrastructure/local-storage' @@ -111,8 +110,6 @@ export const EditorManagerProvider: FC = ({ wantTrackChangesRef.current = wantTrackChanges }, [wantTrackChanges]) - const goToLineEmitter = useScopeEventEmitter('editor:gotoLine') - const { fileTreeData } = useFileTreeData() const [ignoringExternalUpdates, setIgnoringExternalUpdates] = useState(false) @@ -197,12 +194,11 @@ export const EditorManagerProvider: FC = ({ [currentDocumentId] ) - const jumpToLine = useCallback( - (options: GotoLineOptions) => { - goToLineEmitter(options) - }, - [goToLineEmitter] - ) + const jumpToLine = useCallback((options: GotoLineOptions) => { + window.dispatchEvent( + new CustomEvent('editor:gotoLine', { detail: options }) + ) + }, []) const attachErrorHandlerToDocument = useCallback( (doc: Doc, document: DocumentContainer) => { @@ -417,7 +413,9 @@ export const EditorManagerProvider: FC = ({ } } else if (hasGotoOffset(options)) { const jump = () => { - eventEmitter.emit('editor:gotoOffset', options) + window.dispatchEvent( + new CustomEvent('editor:gotoOffset', { detail: options }) + ) } if (isNewDoc) { diff --git a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx index cad238f53c..81d2d340a7 100644 --- a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx @@ -16,7 +16,6 @@ import { import { JoinProjectPayload } from '@/features/ide-react/connection/join-project-payload' import { useConnectionContext } from '@/features/ide-react/context/connection-context' import { postJSON } from '@/infrastructure/fetch-json' -import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter' import getMeta from '@/utils/meta' import { type PermissionsLevel } from '@/features/ide-react/types/permissions' import { useProjectContext } from '@/shared/context/project-context' @@ -48,9 +47,6 @@ export const IdeReactProvider: FC = ({ children }) => { const [permissionsLevel, setPermissionsLevel] = useState('readOnly') const [outOfSync, setOutOfSync] = useState(false) - const [scopeEventEmitter] = useState( - () => new ReactScopeEventEmitter(eventEmitter) - ) const [unstableStore] = useState(() => { const store = new ReactScopeValueStore() // Add dummy editor.ready key for Writefull, that relies on this calling @@ -165,11 +161,7 @@ export const IdeReactProvider: FC = ({ children }) => { return ( - + {children} diff --git a/services/web/frontend/js/features/ide-react/context/online-users-context.tsx b/services/web/frontend/js/features/ide-react/context/online-users-context.tsx index 84a1b7d71f..9216f57617 100644 --- a/services/web/frontend/js/features/ide-react/context/online-users-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/online-users-context.tsx @@ -15,8 +15,8 @@ import { Doc } from '../../../../../types/doc' import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { findDocEntityById } from '@/features/ide-react/util/find-doc-entity-by-id' import useSocketListener from '@/features/ide-react/hooks/use-socket-listener' +import useEventListener from '@/shared/hooks/use-event-listener' import { debugConsole } from '@/utils/debugging' -import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter' import { getHueForUserId } from '@/shared/utils/colors' import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context' @@ -199,21 +199,15 @@ export const OnlineUsersProvider: FC = ({ }, [eventEmitter, setAllValues, setOnlineUsers, socket]) // Track the position of the main cursor - useEffect(() => { - const handleCursorUpdate = ({ - detail: [position], - }: CustomEvent) => { + useEventListener( + 'cursor:editor:update', + useCallback((event: CustomEvent) => { + const position = event.detail if (position) { setCurrentPosition(position) } - } - - eventEmitter.on('cursor:editor:update', handleCursorUpdate) - - return () => { - eventEmitter.off('cursor:editor:update', handleCursorUpdate) - } - }, [cursorUpdateInterval, eventEmitter]) + }, []) + ) // Send the latest position to other clients when currentPosition changes useEffect(() => { 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 cfa0a56ceb..664121d42c 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 @@ -8,7 +8,6 @@ import { useMemo, useState, } from 'react' -import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter' import useEventListener from '@/shared/hooks/use-event-listener' import { isValidTeXFile } from '@/main/is-valid-tex-file' import localStorage from '@/infrastructure/local-storage' @@ -55,8 +54,6 @@ export const OutlineProvider: FC = ({ children }) => { const [ignoreNextScroll, setIgnoreNextScroll] = useState(false) const { sendEvent } = useEditorAnalytics() - const goToLineEmitter = useScopeEventEmitter('editor:gotoLine', true) - useEventListener( 'file-view:file-opened', useCallback(() => { @@ -106,14 +103,18 @@ export const OutlineProvider: FC = ({ children }) => { const jumpToLine = useCallback( (lineNumber: number, syncToPdf: boolean) => { setIgnoreNextScroll(true) - goToLineEmitter({ - gotoLine: lineNumber, - gotoColumn: 0, - syncToPdf, - }) + window.dispatchEvent( + new CustomEvent('editor:gotoLine', { + detail: { + gotoLine: lineNumber, + gotoColumn: 0, + syncToPdf, + }, + }) + ) sendEvent('outline-jump-to-line') }, - [goToLineEmitter, sendEvent] + [sendEvent] ) const highlightedLine = useMemo( 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 deleted file mode 100644 index e469dca8b8..0000000000 --- a/services/web/frontend/js/features/ide-react/scope-event-emitter/react-scope-event-emitter.ts +++ /dev/null @@ -1,37 +0,0 @@ -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: T, - broadcast: boolean, - ...detail: IdeEvents[T] - ) { - this.eventEmitter.dispatchEvent(new CustomEvent(eventName, { detail })) - } - - on( - eventName: T, - listener: (event: Event, ...args: IdeEvents[T]) => void - ) { - const wrappedListener = (event: CustomEvent) => { - listener(event, ...event.detail) - } - this.eventEmitter.addEventListener( - eventName, - wrappedListener as EventListener - ) - return () => { - this.eventEmitter.removeEventListener( - eventName, - wrappedListener as EventListener - ) - } - } -} diff --git a/services/web/frontend/js/features/outline/components/outline-container.tsx b/services/web/frontend/js/features/outline/components/outline-container.tsx index 71a27db753..1174ba750a 100644 --- a/services/web/frontend/js/features/outline/components/outline-container.tsx +++ b/services/web/frontend/js/features/outline/components/outline-container.tsx @@ -1,7 +1,6 @@ -import { FC, memo } from 'react' +import { FC, memo, useCallback } from 'react' import OutlinePane from '@/features/outline/components/outline-pane' import { useOutlineContext } from '@/features/ide-react/context/outline-context' -import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter' import useNestedOutline from '../hooks/use-nested-outline' export const OutlineContainer: FC = memo(() => { @@ -13,7 +12,9 @@ export const OutlineContainer: FC = memo(() => { toggleOutlineExpanded, } = useOutlineContext() - const outlineToggledEmitter = useScopeEventEmitter('outline-toggled') + const handleToggle = useCallback((isOpen: boolean) => { + window.dispatchEvent(new CustomEvent('outline-toggled', { detail: isOpen })) + }, []) const outline = useNestedOutline() @@ -21,7 +22,7 @@ export const OutlineContainer: FC = memo(() => {
{ syncToPdf() 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 451efdb65b..fe35a58c2d 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 @@ -1,8 +1,6 @@ import { useCallback, useEffect, useRef } from 'react' import { EditorState } from '@codemirror/state' -import useScopeEventEmitter from '../../../shared/hooks/use-scope-event-emitter' import useEventListener from '../../../shared/hooks/use-event-listener' -import useScopeEventListener from '../../../shared/hooks/use-scope-event-listener' import { createExtensions } from '../extensions' import { setEditorTheme, setOptionsTheme } from '../extensions/theme' import { @@ -504,13 +502,16 @@ function useCodeMirrorScope(view: EditorView) { settingsRef.current.referencesSearchMode = referencesSearchMode }, [referencesSearchMode]) - const emitSyncToPdf = useScopeEventEmitter('cursor:editor:syncToPdf') + const emitSyncToPdf = useCallback(() => { + window.dispatchEvent(new CustomEvent('cursor:editor:syncToPdf')) + }, []) // select and scroll to position on editor:gotoLine event (from synctex) - useScopeEventListener( + useEventListener( 'editor:gotoLine', useCallback( - (_event: any, options: GotoLineOptions) => { + (event: CustomEvent) => { + const options = event.detail setCursorLineAndScroll( view, options.gotoLine, @@ -526,42 +527,16 @@ function useCodeMirrorScope(view: EditorView) { ) // select and scroll to position on editor:gotoOffset event (from review panel) - useScopeEventListener( + useEventListener( 'editor:gotoOffset', useCallback( - (_event: any, options: GotoOffsetOptions) => { - setCursorPositionAndScroll(view, options.gotoOffset) + (event: CustomEvent) => { + setCursorPositionAndScroll(view, event.detail.gotoOffset) }, [view] ) ) - // dispatch 'cursor:editor:update' to Angular scope (for synctex and realtime) - const dispatchCursorUpdate = useScopeEventEmitter('cursor:editor:update') - - const handleCursorUpdate = useCallback( - (event: CustomEvent) => { - dispatchCursorUpdate(event.detail) - }, - [dispatchCursorUpdate] - ) - - // listen for 'cursor:editor:update' events from CodeMirror, and dispatch them to Angular - useEventListener('cursor:editor:update', handleCursorUpdate) - - // dispatch 'cursor:editor:update' to Angular scope (for outline) - const dispatchScrollUpdate = useScopeEventEmitter('scroll:editor:update') - - const handleScrollUpdate = useCallback( - (event: CustomEvent) => { - dispatchScrollUpdate(event.detail) - }, - [dispatchScrollUpdate] - ) - - // listen for 'cursor:editor:update' events from CodeMirror, and dispatch them to Angular - useEventListener('scroll:editor:update', handleScrollUpdate) - // enable the compile log linter a) when "Code Check" is off, b) when the project hasn't changed and isn't compiling. // the project "changed at" date is reset at the start of the compile, i.e. "the project hasn't changed", // but we don't want to display the compile log diagnostics from the previous compile. diff --git a/services/web/frontend/js/shared/context/ide-context.tsx b/services/web/frontend/js/shared/context/ide-context.tsx index 2ad2f30bae..51f45c1584 100644 --- a/services/web/frontend/js/shared/context/ide-context.tsx +++ b/services/web/frontend/js/shared/context/ide-context.tsx @@ -1,6 +1,5 @@ import { createContext, FC, useContext, useEffect, useMemo } from 'react' import { ScopeValueStore } from '../../../../types/ide/scope-value-store' -import { ScopeEventEmitter } from '../../../../types/ide/scope-event-emitter' import { Socket } from '@/features/ide-react/connection/types/socket' import { useUserSettingsContext } from './user-settings-context' import { userStyles } from '../utils/styles' @@ -11,7 +10,6 @@ export type Ide = { } type IdeContextValue = Ide & { - scopeEventEmitter: ScopeEventEmitter unstableStore: ScopeValueStore } @@ -20,10 +18,9 @@ export const IdeContext = createContext(undefined) export const IdeProvider: FC< React.PropsWithChildren<{ ide: Ide - scopeEventEmitter: ScopeEventEmitter unstableStore: ScopeValueStore }> -> = ({ ide, scopeEventEmitter, unstableStore, children }) => { +> = ({ ide, unstableStore, children }) => { /** * Expose unstableStore via `window.overleaf.unstable.store`, so it can be accessed by external extensions. * @@ -65,10 +62,9 @@ export const IdeProvider: FC< const value = useMemo(() => { return { ...ide, - scopeEventEmitter, unstableStore, } - }, [ide, scopeEventEmitter, unstableStore]) + }, [ide, unstableStore]) return {children} } diff --git a/services/web/frontend/js/shared/context/layout-context.tsx b/services/web/frontend/js/shared/context/layout-context.tsx index e6deb995bd..a8a9528946 100644 --- a/services/web/frontend/js/shared/context/layout-context.tsx +++ b/services/web/frontend/js/shared/context/layout-context.tsx @@ -16,7 +16,6 @@ import getMeta from '../../utils/meta' import { DetachRole } from './detach-context' import { debugConsole } from '@/utils/debugging' import { BinaryFile } from '@/features/file-view/types/binary-file' -import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter' import useEventListener from '@/shared/hooks/use-event-listener' import { isMac } from '@/shared/utils/os' import { sendSearchEvent } from '@/features/event-tracking/search-events' @@ -90,7 +89,6 @@ export const LayoutProvider: FC = ({ children }) => { // what to show in the "flat" view (editor or pdf) const [view, _setView] = useState('editor') const [openFile, setOpenFile] = useState(null) - const historyToggleEmitter = useScopeEventEmitter('history:toggle', true) const { isOpen: railIsOpen, setIsOpen: setRailIsOpen } = useRailContext() const [prevRailIsOpen, setPrevRailIsOpen] = useState(railIsOpen) // Whether we came from a file or a document when we left the ide @@ -102,7 +100,7 @@ export const LayoutProvider: FC = ({ children }) => { _setView(oldValue => { // ensure that the "history:toggle" event is broadcast when switching in or out of history view if (value === 'history' || oldValue === 'history') { - historyToggleEmitter() + window.dispatchEvent(new CustomEvent('history:toggle')) } if (value === 'history') { @@ -121,14 +119,7 @@ export const LayoutProvider: FC = ({ children }) => { return value }) }, - [ - _setView, - setRailIsOpen, - historyToggleEmitter, - prevRailIsOpen, - setPrevRailIsOpen, - railIsOpen, - ] + [_setView, setRailIsOpen, prevRailIsOpen, setPrevRailIsOpen, railIsOpen] ) const restoreView = useCallback(() => { 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 deleted file mode 100644 index 406b186df2..0000000000 --- a/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts +++ /dev/null @@ -1,18 +0,0 @@ -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: T, - broadcast = true -) { - const { scopeEventEmitter } = useIdeContext() - - return useCallback( - (...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 deleted file mode 100644 index 07bae7d5a8..0000000000 --- a/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts +++ /dev/null @@ -1,15 +0,0 @@ -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: T, - listener: (event: Event, ...args: IdeEvents[T]) => void -) { - const { scopeEventEmitter } = useIdeContext() - - useEffect(() => { - return scopeEventEmitter.on(eventName, listener) - }, [scopeEventEmitter, eventName, listener]) -} diff --git a/services/web/test/frontend/helpers/editor-providers.tsx b/services/web/test/frontend/helpers/editor-providers.tsx index 194e937fa9..7c7c4ca37c 100644 --- a/services/web/test/frontend/helpers/editor-providers.tsx +++ b/services/web/test/frontend/helpers/editor-providers.tsx @@ -14,7 +14,6 @@ import React, { import { IdeReactContext } from '@/features/ide-react/context/ide-react-context' import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter' import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store' -import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter' import { ConnectionContext } from '@/features/ide-react/context/connection-context' import { EditorOpenDocContext, @@ -398,14 +397,10 @@ const makeIdeReactProvider = ( })) const [ideContextValue] = useState(() => { - const scopeEventEmitter = new ReactScopeEventEmitter( - new IdeEventEmitter() - ) const unstableStore = new ReactScopeValueStore() return { socket, - scopeEventEmitter, unstableStore, } }) diff --git a/services/web/types/ide/scope-event-emitter.ts b/services/web/types/ide/scope-event-emitter.ts deleted file mode 100644 index 8be3aeb01b..0000000000 --- a/services/web/types/ide/scope-event-emitter.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter' - -export type ScopeEventName = keyof IdeEvents - -export interface ScopeEventEmitter { - emit: ( - eventName: T, - broadcast: boolean, - ...detail: IdeEvents[T] - ) => void - on: ( - eventName: T, - listener: (event: Event, ...args: IdeEvents[T]) => void - ) => () => void -}