Jelajahi Sumber

Merge pull request #15909 from overleaf/ii-ide-page-prototype-review-panel-refresh-ranges

[web] React ide page refresh ranges

GitOrigin-RevId: 7f79b8f63869ee39fef9a101e6dcc56c39af8df7
ilkin-overleaf 2 tahun lalu
induk
melakukan
81f5a1308e

+ 47 - 8
services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts

@@ -1,5 +1,6 @@
 import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
 import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
 import { isEqual, cloneDeep } from 'lodash'
 import { isEqual, cloneDeep } from 'lodash'
+import usePersistedState from '@/shared/hooks/use-persisted-state'
 import useScopeValue from '../../../../../shared/hooks/use-scope-value'
 import useScopeValue from '../../../../../shared/hooks/use-scope-value'
 import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
 import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
 import useAsync from '@/shared/hooks/use-async'
 import useAsync from '@/shared/hooks/use-async'
@@ -130,9 +131,9 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
   const [nVisibleSelectedChanges] = useScopeValue<
   const [nVisibleSelectedChanges] = useScopeValue<
     ReviewPanel.Value<'nVisibleSelectedChanges'>
     ReviewPanel.Value<'nVisibleSelectedChanges'>
   >('reviewPanel.nVisibleSelectedChanges')
   >('reviewPanel.nVisibleSelectedChanges')
-  const [collapsed, setCollapsed] = useScopeValue<
+  const [collapsed, setCollapsed] = usePersistedState<
     ReviewPanel.Value<'collapsed'>
     ReviewPanel.Value<'collapsed'>
-  >('reviewPanel.overview.docsCollapsedState')
+  >(`docs_collapsed_state:${projectId}`, {}, false, true)
   const [commentThreads, setCommentThreads] = useState<
   const [commentThreads, setCommentThreads] = useState<
     ReviewPanel.Value<'commentThreads'>
     ReviewPanel.Value<'commentThreads'>
   >({})
   >({})
@@ -273,7 +274,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
   const getChangeTracker = useCallback(
   const getChangeTracker = useCallback(
     (docId: DocId) => {
     (docId: DocId) => {
       if (!rangesTrackers.current[docId]) {
       if (!rangesTrackers.current[docId]) {
-        rangesTrackers.current[docId] = new RangesTracker() as RangesTracker
+        rangesTrackers.current[docId] = new RangesTracker()
         rangesTrackers.current[docId].resolvedThreadIds = {
         rangesTrackers.current[docId].resolvedThreadIds = {
           ...resolvedThreadIds,
           ...resolvedThreadIds,
         }
         }
@@ -890,9 +891,47 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
     [onThreadDeleted, projectId]
     [onThreadDeleted, projectId]
   )
   )
 
 
-  const [refreshResolvedCommentsDropdown] = useScopeValue<
-    ReviewPanel.UpdaterFn<'refreshResolvedCommentsDropdown'>
-  >('refreshResolvedCommentsDropdown')
+  const refreshRanges = useCallback(() => {
+    type Doc = {
+      id: DocId
+      ranges: {
+        comments?: unknown[]
+        changes?: unknown[]
+      }
+    }
+
+    return getJSON<Doc[]>(`/project/${projectId}/ranges`)
+      .then(docs => {
+        setCollapsed(prevState => {
+          const collapsed = { ...prevState }
+          docs.forEach(doc => {
+            if (collapsed[doc.id] == null) {
+              collapsed[doc.id] = false
+            }
+          })
+          return collapsed
+        })
+
+        docs.forEach(async doc => {
+          if (doc.id !== currentDocumentId) {
+            // this is kept up to date in real-time, don't overwrite
+            const rangesTracker = getChangeTracker(doc.id)
+            rangesTracker.comments = doc.ranges?.comments ?? []
+            rangesTracker.changes = doc.ranges?.changes ?? []
+          }
+        })
+
+        return Promise.all(docs.map(doc => updateEntries(doc.id)))
+      })
+      .catch(debugConsole.error)
+  }, [
+    currentDocumentId,
+    getChangeTracker,
+    projectId,
+    setCollapsed,
+    updateEntries,
+  ])
+
   const [acceptChanges] =
   const [acceptChanges] =
     useScopeValue<ReviewPanel.UpdaterFn<'acceptChanges'>>('acceptChanges')
     useScopeValue<ReviewPanel.UpdaterFn<'acceptChanges'>>('acceptChanges')
   const [rejectChanges] =
   const [rejectChanges] =
@@ -1056,7 +1095,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
       submitNewComment,
       submitNewComment,
       deleteComment,
       deleteComment,
       unresolveComment,
       unresolveComment,
-      refreshResolvedCommentsDropdown,
+      refreshResolvedCommentsDropdown: refreshRanges,
       deleteThread,
       deleteThread,
       toggleTrackChangesForEveryone,
       toggleTrackChangesForEveryone,
       toggleTrackChangesForUser,
       toggleTrackChangesForUser,
@@ -1084,7 +1123,7 @@ function useReviewPanelState(): ReviewPanelStateReactIde {
       submitNewComment,
       submitNewComment,
       deleteComment,
       deleteComment,
       unresolveComment,
       unresolveComment,
-      refreshResolvedCommentsDropdown,
+      refreshRanges,
       deleteThread,
       deleteThread,
       toggleTrackChangesForEveryone,
       toggleTrackChangesForEveryone,
       toggleTrackChangesForUser,
       toggleTrackChangesForUser,

+ 0 - 2
services/web/frontend/js/features/ide-react/scope-adapters/review-panel-context-adapter.ts

@@ -1,7 +1,6 @@
 import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
 import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
 
 
 export default function populateReviewPanelScope(store: ReactScopeValueStore) {
 export default function populateReviewPanelScope(store: ReactScopeValueStore) {
-  store.set('reviewPanel.overview.docsCollapsedState', {})
   store.set('reviewPanel.overview.loading', false)
   store.set('reviewPanel.overview.loading', false)
   store.set('reviewPanel.nVisibleSelectedChanges', 0)
   store.set('reviewPanel.nVisibleSelectedChanges', 0)
   store.set('permissions', {
   store.set('permissions', {
@@ -18,7 +17,6 @@ export default function populateReviewPanelScope(store: ReactScopeValueStore) {
   store.set('deleteComment', () => {})
   store.set('deleteComment', () => {})
   store.set('gotoEntry', () => {})
   store.set('gotoEntry', () => {})
   store.set('saveEdit', () => {})
   store.set('saveEdit', () => {})
-  store.set('refreshResolvedCommentsDropdown', async () => {})
   store.set('acceptChanges', () => {})
   store.set('acceptChanges', () => {})
   store.set('rejectChanges', () => {})
   store.set('rejectChanges', () => {})
   store.set('bulkAcceptActions', () => {})
   store.set('bulkAcceptActions', () => {})

+ 4 - 1
services/web/frontend/js/features/source-editor/context/review-panel/types/review-panel-state.ts

@@ -1,6 +1,7 @@
 import {
 import {
   CommentId,
   CommentId,
   ReviewPanelCommentThreads,
   ReviewPanelCommentThreads,
+  ReviewPanelDocEntries,
   ReviewPanelEntries,
   ReviewPanelEntries,
   ReviewPanelUsers,
   ReviewPanelUsers,
   SubView,
   SubView,
@@ -72,7 +73,9 @@ export interface ReviewPanelState {
     ) => void
     ) => void
     unresolveComment: (threadId: ThreadId) => void
     unresolveComment: (threadId: ThreadId) => void
     deleteThread: (docId: DocId, threadId: ThreadId) => void
     deleteThread: (docId: DocId, threadId: ThreadId) => void
-    refreshResolvedCommentsDropdown: () => Promise<void>
+    refreshResolvedCommentsDropdown: () => Promise<
+      void | ReviewPanelDocEntries[]
+    >
     submitNewComment: (content: string) => Promise<void>
     submitNewComment: (content: string) => Promise<void>
     setEntryHover: React.Dispatch<React.SetStateAction<Value<'entryHover'>>>
     setEntryHover: React.Dispatch<React.SetStateAction<Value<'entryHover'>>>
     setIsAddingComment: React.Dispatch<
     setIsAddingComment: React.Dispatch<

+ 43 - 6
services/web/frontend/js/shared/hooks/use-persisted-state.ts

@@ -7,14 +7,51 @@ import {
 } from 'react'
 } from 'react'
 import _ from 'lodash'
 import _ from 'lodash'
 import localStorage from '../../infrastructure/local-storage'
 import localStorage from '../../infrastructure/local-storage'
+import { debugConsole } from '@/utils/debugging'
+
+const safeStringify = (value: unknown) => {
+  try {
+    return JSON.stringify(value)
+  } catch (e) {
+    debugConsole.error('double stringify exception', e)
+    return null
+  }
+}
+
+const safeParse = (value: string) => {
+  try {
+    return JSON.parse(value)
+  } catch (e) {
+    debugConsole.error('double parse exception', e)
+    return null
+  }
+}
 
 
 function usePersistedState<T = any>(
 function usePersistedState<T = any>(
   key: string,
   key: string,
   defaultValue?: T,
   defaultValue?: T,
-  listen = false
+  listen = false,
+  // The option below is for backward compatibility with Angular
+  // which sometimes stringifies the values twice
+  doubleStringifyAndParse = false
 ): [T, Dispatch<SetStateAction<T>>] {
 ): [T, Dispatch<SetStateAction<T>>] {
+  const getItem = useCallback(
+    (key: string) => {
+      const item = localStorage.getItem(key)
+      return doubleStringifyAndParse ? safeParse(item) : item
+    },
+    [doubleStringifyAndParse]
+  )
+  const setItem = useCallback(
+    (key: string, value: unknown) => {
+      const val = doubleStringifyAndParse ? safeStringify(value) : value
+      localStorage.setItem(key, val)
+    },
+    [doubleStringifyAndParse]
+  )
+
   const [value, setValue] = useState<T>(() => {
   const [value, setValue] = useState<T>(() => {
-    return localStorage.getItem(key) ?? defaultValue
+    return getItem(key) ?? defaultValue
   })
   })
 
 
   const updateFunction = useCallback(
   const updateFunction = useCallback(
@@ -27,13 +64,13 @@ function usePersistedState<T = any>(
         if (actualNewValue === defaultValue) {
         if (actualNewValue === defaultValue) {
           localStorage.removeItem(key)
           localStorage.removeItem(key)
         } else {
         } else {
-          localStorage.setItem(key, actualNewValue)
+          setItem(key, actualNewValue)
         }
         }
 
 
         return actualNewValue
         return actualNewValue
       })
       })
     },
     },
-    [key, defaultValue]
+    [key, defaultValue, setItem]
   )
   )
 
 
   useEffect(() => {
   useEffect(() => {
@@ -42,7 +79,7 @@ function usePersistedState<T = any>(
         if (event.key === key) {
         if (event.key === key) {
           // note: this value is read via getItem rather than from event.newValue
           // note: this value is read via getItem rather than from event.newValue
           // because getItem handles deserializing the JSON that's stored in localStorage.
           // because getItem handles deserializing the JSON that's stored in localStorage.
-          setValue(localStorage.getItem(key) ?? defaultValue)
+          setValue(getItem(key) ?? defaultValue)
         }
         }
       }
       }
 
 
@@ -52,7 +89,7 @@ function usePersistedState<T = any>(
         window.removeEventListener('storage', listener)
         window.removeEventListener('storage', listener)
       }
       }
     }
     }
-  }, [key, listen, defaultValue])
+  }, [defaultValue, key, listen, getItem])
 
 
   return [value, updateFunction]
   return [value, updateFunction]
 }
 }