Sfoglia il codice sorgente

[web] Remove instances of `$scope` and `_ide` (#26297)

* Remove `$scope` from `getMockIde`

* Replace `...getMockIde()` by `_id: projectId`

* Simplify stories decorator scope.tsx: less reliance on `window`

* Update helper editor-providers.jsx: pass data instead directly instead of using `window`

* Remove `cleanUpContext`

* Remove unused prop `clsiServerId`

* Update types to reflect unused properties

* Remove comment

* Add `ol-chatEnabled` in Storybook

* Revert moving `getMeta` outside of the component

This causes issues in Storybook

GitOrigin-RevId: dc2558ce814c2d738fb39450c57c104f4419efb8
Antoine Clausse 1 anno fa
parent
commit
3ba002460e

+ 1 - 0
services/web/.storybook/preview.tsx

@@ -13,6 +13,7 @@ import en from '../../../services/web/locales/en.json'
 function resetMeta() {
   window.metaAttributesCache = new Map()
   window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
+  window.metaAttributesCache.set('ol-chatEnabled', true)
   window.metaAttributesCache.set('ol-ExposedSettings', {
     adminEmail: 'placeholder@example.com',
     appName: 'Overleaf',

+ 0 - 1
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx

@@ -176,7 +176,6 @@ export default function FileTreeUploadDoc() {
         // close the modal when all the uploads completed successfully
         .on('complete', result => {
           if (!result.failed.length) {
-            // $scope.$emit('done', { name: name })
             cancel()
           }
         })

+ 2 - 3
services/web/frontend/js/features/ide-react/context/ide-react-context.tsx

@@ -16,7 +16,6 @@ import {
 } from '@/features/ide-react/create-ide-event-emitter'
 import { JoinProjectPayload } from '@/features/ide-react/connection/join-project-payload'
 import { useConnectionContext } from '@/features/ide-react/context/connection-context'
-import { getMockIde } from '@/shared/context/mock/mock-ide'
 import { populateEditorScope } from '@/features/ide-react/scope-adapters/editor-manager-context-adapter'
 import { postJSON } from '@/infrastructure/fetch-json'
 import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
@@ -157,11 +156,11 @@ export const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
 
   const ide = useMemo(() => {
     return {
-      ...getMockIde(),
+      _id: projectId,
       socket,
       reportError,
     }
-  }, [socket, reportError])
+  }, [projectId, socket, reportError])
 
   const value = useMemo(
     () => ({

+ 0 - 1
services/web/frontend/js/shared/context/ide-context.tsx

@@ -4,7 +4,6 @@ import { ScopeEventEmitter } from '../../../../types/ide/scope-event-emitter'
 import { Socket } from '@/features/ide-react/connection/types/socket'
 
 export type Ide = {
-  $scope: Record<string, any>
   socket: Socket
 }
 

+ 0 - 56
services/web/frontend/js/shared/context/mock/mock-ide.js

@@ -1,56 +0,0 @@
-import getMeta from '../../../utils/meta'
-
-// When rendered without Angular, ide isn't defined. In that case we use
-// a mock object that only has the required properties to pass proptypes
-// checks and the values needed for the app. In the longer term, the mock
-// object will replace ide completely.
-export const getMockIde = () => {
-  return {
-    _id: getMeta('ol-project_id'),
-    $scope: {
-      $on: () => {},
-      $watch: () => {},
-      $applyAsync: () => {},
-      user: {},
-      project: {
-        _id: getMeta('ol-project_id'),
-        name: getMeta('ol-projectName'),
-        rootDocId: '',
-        members: [],
-        invites: [],
-        features: {
-          collaborators: 0,
-          compileGroup: 'standard',
-          trackChangesVisible: false,
-          references: false,
-          mendeley: false,
-          zotero: false,
-        },
-        publicAccessLevel: '',
-        owner: {
-          _id: '',
-          email: '',
-        },
-      },
-      permissionsLevel: 'readOnly',
-      editor: {
-        sharejs_doc: null,
-        showSymbolPalette: false,
-        toggleSymbolPalette: () => {},
-      },
-      ui: {
-        view: 'pdf',
-        chatOpen: false,
-        reviewPanelOpen: false,
-        leftMenuShown: false,
-        pdfLayout: 'flat',
-      },
-      pdf: {
-        uncompiled: true,
-        logEntryAnnotations: {},
-      },
-      settings: { syntaxValidation: false, pdfViewer: 'pdfjs' },
-      hasLintingError: false,
-    },
-  }
-}

+ 62 - 84
services/web/frontend/stories/decorators/scope.tsx

@@ -1,5 +1,4 @@
 import React, { FC, useEffect, useState } from 'react'
-import { get } from 'lodash'
 import { User, UserId } from '../../../types/user'
 import { Project } from '../../../types/project'
 import {
@@ -24,86 +23,68 @@ import { ReactContextRoot } from '@/features/ide-react/context/react-context-roo
 
 const scopeWatchers: [string, (value: any) => void][] = []
 
-const initialize = () => {
-  const user: User = {
-    id: 'story-user' as UserId,
-    email: 'story-user@example.com',
-    allowedFreeTrial: true,
-    features: { dropbox: true, symbolPalette: true },
-  }
-
-  const project: Project = {
-    _id: '63e21c07946dd8c76505f85a',
-    name: 'A Project',
-    features: { mendeley: true, zotero: true, referencesSearch: true },
-    tokens: {},
-    owner: {
-      _id: 'a-user',
-      email: 'stories@overleaf.com',
-    },
-    members: [],
-    invites: [],
-    rootDoc_id: '5e74f1a7ce17ae0041dfd056',
-    rootFolder: [
-      {
-        _id: 'root-folder-id',
-        name: 'rootFolder',
-        docs: [
-          { _id: 'test-file-id', name: 'testfile.tex' },
-          { _id: 'test-bib-file-id', name: 'testsources.bib' },
-        ],
-        fileRefs: [{ _id: 'test-image-id', name: 'frog.jpg', hash: '42' }],
-        folders: [],
-      },
-    ],
-  }
-
-  const scope = {
-    user,
-    project,
-    $watch: (key: string, callback: () => void) => {
-      scopeWatchers.push([key, callback])
-    },
-    $applyAsync: (callback: () => void) => {
-      window.setTimeout(() => {
-        callback()
-        for (const [key, watcher] of scopeWatchers) {
-          watcher(get(ide.$scope, key))
-        }
-      }, 0)
-    },
-    $on: () => {
-      //
-    },
-    $broadcast: () => {},
-    ui: {
-      chatOpen: true,
-      pdfLayout: 'flat',
-    },
-    settings: {
-      pdfViewer: 'js',
-      syntaxValidation: true,
+const user: User = {
+  id: 'story-user' as UserId,
+  email: 'story-user@example.com',
+  allowedFreeTrial: true,
+  features: { dropbox: true, symbolPalette: true },
+}
+
+const project: Project = {
+  _id: '63e21c07946dd8c76505f85a',
+  name: 'A Project',
+  features: { mendeley: true, zotero: true, referencesSearch: true },
+  tokens: {},
+  owner: {
+    _id: 'a-user',
+    email: 'stories@overleaf.com',
+  },
+  members: [],
+  invites: [],
+  rootDoc_id: '5e74f1a7ce17ae0041dfd056',
+  rootFolder: [
+    {
+      _id: 'root-folder-id',
+      name: 'rootFolder',
+      docs: [
+        { _id: 'test-file-id', name: 'testfile.tex' },
+        { _id: 'test-bib-file-id', name: 'testsources.bib' },
+      ],
+      fileRefs: [{ _id: 'test-image-id', name: 'frog.jpg', hash: '42' }],
+      folders: [],
     },
-    editor: {
-      richText: false,
-      sharejs_doc: {
-        doc_id: 'test-doc',
-        getSnapshot: () => 'some doc content',
-        hasBufferedOps: () => false,
-      },
-      open_doc_name: 'testfile.tex',
+  ],
+}
+
+const initialScope = {
+  user,
+  project,
+  ui: {
+    chatOpen: true,
+    pdfLayout: 'flat',
+  },
+  settings: {
+    pdfViewer: 'js',
+    syntaxValidation: true,
+  },
+  editor: {
+    richText: false,
+    sharejs_doc: {
+      doc_id: 'test-doc',
+      getSnapshot: () => 'some doc content',
+      hasBufferedOps: () => false,
     },
-    hasLintingError: false,
-    permissionsLevel: 'owner',
-  }
+    open_doc_name: 'testfile.tex',
+  },
+  hasLintingError: false,
+  permissionsLevel: 'owner',
+}
 
-  const ide = {
-    $scope: scope,
-    socket: new SocketIOShim.SocketShimNoop(
-      new SocketIOMock()
-    ) as unknown as Socket,
-  }
+const socket = new SocketIOShim.SocketShimNoop(
+  new SocketIOMock()
+) as unknown as Socket
 
+const initializeMetaTags = () => {
   // window.metaAttributesCache is reset in preview.tsx
   window.metaAttributesCache.set('ol-user', user)
   window.metaAttributesCache.set('ol-project_id', project._id)
@@ -111,8 +92,6 @@ const initialize = () => {
     'ol-gitBridgePublicBaseUrl',
     'https://git.stories.com'
   )
-
-  window._ide = ide
 }
 
 type ScopeDecoratorOptions = {
@@ -125,7 +104,7 @@ export const ScopeDecorator = (
   opts: ScopeDecoratorOptions = { mockCompileOnLoad: true },
   meta: Record<string, any> = {}
 ) => {
-  initialize()
+  initializeMetaTags()
 
   // mock compile on load
   useFetchMock(fetchMock => {
@@ -171,7 +150,7 @@ const ConnectionProvider: FC<React.PropsWithChildren> = ({ children }) => {
       error: '',
     }
     return {
-      socket: window._ide.socket as Socket,
+      socket,
       connectionState,
       isConnected: true,
       isStillReconnecting: false,
@@ -215,9 +194,8 @@ const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
   }))
 
   const [ideContextValue] = useState(() => {
-    const ide = window._ide
     const scopeStore = createReactScopeValueStore(projectId)
-    for (const [key, value] of Object.entries(ide.$scope)) {
+    for (const [key, value] of Object.entries(initialScope)) {
       scopeStore.set(key, value)
     }
     const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter())
@@ -231,7 +209,7 @@ const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
     }
 
     return {
-      ...ide,
+      socket,
       scopeStore,
       scopeEventEmitter,
     }

+ 1 - 6
services/web/test/frontend/features/chat/components/chat-pane.test.jsx

@@ -7,10 +7,7 @@ import {
 import fetchMock from 'fetch-mock'
 
 import ChatPane from '../../../../../frontend/js/features/chat/components/chat-pane'
-import {
-  cleanUpContext,
-  renderWithEditorContext,
-} from '../../../helpers/render-with-context'
+import { renderWithEditorContext } from '../../../helpers/render-with-context'
 import { stubMathJax, tearDownMathJaxStubs } from './stubs'
 
 describe('<ChatPane />', function () {
@@ -47,8 +44,6 @@ describe('<ChatPane />', function () {
 
   beforeEach(function () {
     fetchMock.removeRoutes().clearHistory()
-    cleanUpContext()
-
     stubMathJax()
   })
 

+ 0 - 2
services/web/test/frontend/features/chat/context/chat-context.test.jsx

@@ -9,7 +9,6 @@ import {
   useChatContext,
   chatClientIdGenerator,
 } from '@/features/chat/context/chat-context'
-import { cleanUpContext } from '../../../helpers/render-with-context'
 import { stubMathJax, tearDownMathJaxStubs } from '../components/stubs'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 import { EditorProviders } from '../../../helpers/editor-providers'
@@ -24,7 +23,6 @@ describe('ChatContext', function () {
 
   beforeEach(function () {
     fetchMock.removeRoutes().clearHistory()
-    cleanUpContext()
 
     stubMathJax()
 

+ 1 - 5
services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx

@@ -5,10 +5,7 @@ import fetchMock from 'fetch-mock'
 import userEvent from '@testing-library/user-event'
 
 import ShareProjectModal from '../../../../../frontend/js/features/share-project-modal/components/share-project-modal'
-import {
-  renderWithEditorContext,
-  cleanUpContext,
-} from '../../../helpers/render-with-context'
+import { renderWithEditorContext } from '../../../helpers/render-with-context'
 import {
   EditorProviders,
   USER_EMAIL,
@@ -100,7 +97,6 @@ describe('<ShareProjectModal/>', function () {
   afterEach(function () {
     this.locationWrapperSandbox.restore()
     fetchMock.removeRoutes().clearHistory()
-    cleanUpContext()
   })
 
   it('renders the modal', async function () {

+ 5 - 5
services/web/test/frontend/features/word-count-modal/components/word-count-modal.spec.tsx

@@ -12,7 +12,7 @@ describe('<WordCountModal />', function () {
     })
 
     cy.mount(
-      <EditorProviders projectId="project-1" clsiServerId="clsi-server-1">
+      <EditorProviders projectId="project-1">
         <WordCountModal show handleHide={cy.stub()} />
       </EditorProviders>
     )
@@ -30,7 +30,7 @@ describe('<WordCountModal />', function () {
     })
 
     cy.mount(
-      <EditorProviders projectId="project-1" clsiServerId="clsi-server-1">
+      <EditorProviders projectId="project-1">
         <WordCountModal show handleHide={cy.stub()} />
       </EditorProviders>
     )
@@ -48,7 +48,7 @@ describe('<WordCountModal />', function () {
     })
 
     cy.mount(
-      <EditorProviders projectId="project-1" clsiServerId="clsi-server-1">
+      <EditorProviders projectId="project-1">
         <WordCountModal show handleHide={cy.stub()} />
       </EditorProviders>
     )
@@ -64,7 +64,7 @@ describe('<WordCountModal />', function () {
     })
 
     cy.mount(
-      <EditorProviders projectId="project-1" clsiServerId="clsi-server-1">
+      <EditorProviders projectId="project-1">
         <WordCountModal show handleHide={cy.stub()} />
       </EditorProviders>
     )
@@ -87,7 +87,7 @@ describe('<WordCountModal />', function () {
     })
 
     cy.mount(
-      <EditorProviders projectId="project-1" clsiServerId="clsi-server-1">
+      <EditorProviders projectId="project-1">
         <WordCountModal show handleHide={cy.stub()} />
       </EditorProviders>
     )

+ 85 - 107
services/web/test/frontend/helpers/editor-providers.jsx

@@ -1,7 +1,6 @@
 // Disable prop type checks for test harnesses
 /* eslint-disable react/prop-types */
-import sinon from 'sinon'
-import { get, merge } from 'lodash'
+import { merge } from 'lodash'
 import { SocketIOMock } from '@/ide/connection/SocketIoShim'
 import { IdeContext } from '@/shared/context/ide-context'
 import React, { useEffect, useState } from 'react'
@@ -48,8 +47,7 @@ export function EditorProviders({
   compiler = 'pdflatex',
   socket = new SocketIOMock(),
   isRestrictedTokenMember = false,
-  clsiServerId = '1234',
-  scope = {},
+  scope: defaultScope = {},
   features = {
     referencesSearch: true,
   },
@@ -71,18 +69,6 @@ export function EditorProviders({
     },
   ],
   ui = { view: 'editor', pdfLayout: 'sideBySide', chatOpen: true },
-  fileTreeManager = {
-    findEntityById: () => null,
-    findEntityByPath: () => null,
-    getEntityPath: () => '',
-    getRootDocDirname: () => '',
-    getPreviewByPath: path => ({ url: path, extension: 'png' }),
-  },
-  editorManager = {
-    getCurrentDocumentId: () => 'foo',
-    getCurrentDocValue: () => {},
-    openDoc: sinon.stub(),
-  },
   userSettings = {},
   providers = {},
 }) {
@@ -99,7 +85,7 @@ export function EditorProviders({
     merge({}, defaultUserSettings, userSettings)
   )
 
-  const $scope = merge(
+  const scope = merge(
     {
       user,
       editor: {
@@ -123,25 +109,11 @@ export function EditorProviders({
         compiler,
       },
       ui,
-      $watch: (path, callback) => {
-        callback(get($scope, path))
-        return () => null
-      },
-      $on: sinon.stub(),
-      $applyAsync: sinon.stub(),
       permissionsLevel,
     },
-    scope
+    defaultScope
   )
 
-  window._ide = {
-    $scope,
-    socket,
-    clsiServerId,
-    editorManager,
-    fileTreeManager,
-  }
-
   // Add details for useUserContext
   window.metaAttributesCache.set('ol-user', { ...user, features })
   window.metaAttributesCache.set('ol-project_id', projectId)
@@ -149,8 +121,8 @@ export function EditorProviders({
   return (
     <ReactContextRoot
       providers={{
-        ConnectionProvider,
-        IdeReactProvider,
+        ConnectionProvider: makeConnectionProvider(socket),
+        IdeReactProvider: makeIdeReactProvider(scope, socket),
         ...providers,
       }}
     >
@@ -159,79 +131,85 @@ export function EditorProviders({
   )
 }
 
-const ConnectionProvider = ({ children }) => {
-  const [value] = useState(() => ({
-    socket: window._ide.socket,
-    connectionState: {
-      readyState: WebSocket.OPEN,
-      forceDisconnected: false,
-      inactiveDisconnect: false,
-      reconnectAt: null,
-      forcedDisconnectDelay: 0,
-      lastConnectionAttempt: 0,
-      error: '',
-    },
-    isConnected: true,
-    isStillReconnecting: false,
-    secondsUntilReconnect: () => 0,
-    tryReconnectNow: () => {},
-    registerUserActivity: () => {},
-    disconnect: () => {},
-  }))
-
-  return (
-    <ConnectionContext.Provider value={value}>
-      {children}
-    </ConnectionContext.Provider>
-  )
-}
-
-const IdeReactProvider = ({ children }) => {
-  const [startedFreeTrial, setStartedFreeTrial] = useState(false)
-
-  const [ideReactContextValue] = useState(() => ({
-    projectId: PROJECT_ID,
-    eventEmitter: new IdeEventEmitter(),
-    startedFreeTrial,
-    setStartedFreeTrial,
-    reportError: () => {},
-    projectJoined: true,
-  }))
-
-  const [ideContextValue] = useState(() => {
-    const ide = window._ide
-
-    const scopeStore = createReactScopeValueStore(PROJECT_ID)
-    for (const [key, value] of Object.entries(ide.$scope)) {
-      // TODO: path for nested entries
-      scopeStore.set(key, value)
-    }
-    scopeStore.set('editor.sharejs_doc', ide.$scope.editor.sharejs_doc)
-    scopeStore.set('ui.chatOpen', ide.$scope.ui.chatOpen)
-    const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter())
-
-    return {
-      ...ide,
-      scopeStore,
-      scopeEventEmitter,
-    }
-  })
-
-  useEffect(() => {
-    window.overleaf = {
-      ...window.overleaf,
-      unstable: {
-        ...window.overleaf?.unstable,
-        store: ideContextValue.scopeStore,
+const makeConnectionProvider = socket => {
+  const ConnectionProvider = ({ children }) => {
+    const [value] = useState(() => ({
+      socket,
+      connectionState: {
+        readyState: WebSocket.OPEN,
+        forceDisconnected: false,
+        inactiveDisconnect: false,
+        reconnectAt: null,
+        forcedDisconnectDelay: 0,
+        lastConnectionAttempt: 0,
+        error: '',
       },
-    }
-  }, [ideContextValue.scopeStore])
-
-  return (
-    <IdeReactContext.Provider value={ideReactContextValue}>
-      <IdeContext.Provider value={ideContextValue}>
+      isConnected: true,
+      isStillReconnecting: false,
+      secondsUntilReconnect: () => 0,
+      tryReconnectNow: () => {},
+      registerUserActivity: () => {},
+      disconnect: () => {},
+    }))
+
+    return (
+      <ConnectionContext.Provider value={value}>
         {children}
-      </IdeContext.Provider>
-    </IdeReactContext.Provider>
-  )
+      </ConnectionContext.Provider>
+    )
+  }
+  return ConnectionProvider
+}
+
+const makeIdeReactProvider = (scope, socket) => {
+  const IdeReactProvider = ({ children }) => {
+    const [startedFreeTrial, setStartedFreeTrial] = useState(false)
+
+    const [ideReactContextValue] = useState(() => ({
+      projectId: PROJECT_ID,
+      eventEmitter: new IdeEventEmitter(),
+      startedFreeTrial,
+      setStartedFreeTrial,
+      reportError: () => {},
+      projectJoined: true,
+    }))
+
+    const [ideContextValue] = useState(() => {
+      const scopeStore = createReactScopeValueStore(PROJECT_ID)
+      for (const [key, value] of Object.entries(scope)) {
+        // TODO: path for nested entries
+        scopeStore.set(key, value)
+      }
+      scopeStore.set('editor.sharejs_doc', scope.editor.sharejs_doc)
+      scopeStore.set('ui.chatOpen', scope.ui.chatOpen)
+      const scopeEventEmitter = new ReactScopeEventEmitter(
+        new IdeEventEmitter()
+      )
+
+      return {
+        socket,
+        scopeStore,
+        scopeEventEmitter,
+      }
+    })
+
+    useEffect(() => {
+      window.overleaf = {
+        ...window.overleaf,
+        unstable: {
+          ...window.overleaf?.unstable,
+          store: ideContextValue.scopeStore,
+        },
+      }
+    }, [ideContextValue.scopeStore])
+
+    return (
+      <IdeReactContext.Provider value={ideReactContextValue}>
+        <IdeContext.Provider value={ideContextValue}>
+          {children}
+        </IdeContext.Provider>
+      </IdeReactContext.Provider>
+    )
+  }
+  return IdeReactProvider
 }

+ 0 - 4
services/web/test/frontend/helpers/render-with-context.jsx

@@ -18,7 +18,3 @@ export function renderWithEditorContext(
     ...renderOptions,
   })
 }
-
-export function cleanUpContext() {
-  delete window._ide
-}

+ 0 - 9
services/web/types/window.ts

@@ -1,20 +1,11 @@
 import 'recurly__recurly-js'
 import { ScopeValueStore } from './ide/scope-value-store'
 import { MetaAttributesCache } from '@/utils/meta'
-import { Socket } from '@/features/ide-react/connection/types/socket'
 
 declare global {
   // eslint-disable-next-line no-unused-vars
   interface Window {
     metaAttributesCache: MetaAttributesCache
-    _ide: Record<string, unknown> & {
-      $scope: Record<string, unknown> & {
-        pdf?: {
-          logEntryAnnotations: Record<string, unknown>
-        }
-      }
-      socket: Socket
-    }
     MathJax: Record<string, any>
     // For react-google-recaptcha
     recaptchaOptions?: {