Explorar o código

Merge pull request #32884 from overleaf/em-editor-switch-by-filetype

[web] Split editor mode preference by file type

GitOrigin-RevId: 2574623c9c1c88cc66de72c19cffb4428a632e96
Eric Mc Sween hai 3 meses
pai
achega
628e05a278

+ 44 - 13
services/web/frontend/js/features/ide-react/context/editor-properties-context.tsx

@@ -6,18 +6,21 @@ import {
   SetStateAction,
   useCallback,
   useContext,
+  useEffect,
   useState,
 } from 'react'
 import customLocalStorage from '@/infrastructure/local-storage'
-import usePersistedState from '@/shared/hooks/use-persisted-state'
 import getMeta from '@/utils/meta'
 import { useUnstableStoreSync } from '@/shared/hooks/use-unstable-store-sync'
 import { sendMB } from '@/infrastructure/event-tracking'
+import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
+import { getVisualEditorStorageKey } from '@/features/source-editor/utils/visual-editor'
 
 // Context value type
 export type EditorPropertiesContextValue = {
   showVisual: boolean
   setShowVisual: Dispatch<SetStateAction<boolean>>
+  showVisualForFile: (filename: string) => boolean
   showSymbolPalette: boolean
   setShowSymbolPalette: Dispatch<SetStateAction<boolean>>
   toggleSymbolPalette: () => void
@@ -35,31 +38,58 @@ export const EditorPropertiesContext = createContext<
   EditorPropertiesContextValue | undefined
 >(undefined)
 
-function showVisualFallbackValue() {
+function migrateTexVisualMode(): boolean {
   const projectId = getMeta('ol-project_id')
   const editorModeKey = `editor.mode.${projectId}`
   const editorModeVal = customLocalStorage.getItem(editorModeKey)
 
   if (editorModeVal) {
-    // clean up the old key
     customLocalStorage.removeItem(editorModeKey)
+    return editorModeVal === 'rich-text'
   }
 
-  return editorModeVal === 'rich-text'
+  return false
+}
+
+export function showVisualForFile(filename: string): boolean {
+  const key = getVisualEditorStorageKey(filename)
+  const stored = customLocalStorage.getItem(key)
+  if (stored !== null) {
+    return stored === 'visual'
+  }
+  if (key === 'editor.lastUsedMode') {
+    return migrateTexVisualMode()
+  }
+  return false
 }
 
 export const EditorPropertiesProvider: FC<PropsWithChildren> = ({
   children,
 }) => {
-  const [showVisual, setShowVisual] = usePersistedState(
-    `editor.lastUsedMode`,
-    showVisualFallbackValue(),
-    {
-      converter: {
-        toPersisted: showVisual => (showVisual ? 'visual' : 'code'),
-        fromPersisted: mode => mode === 'visual',
-      },
-    }
+  const { openDocName } = useEditorOpenDocContext()
+
+  const [showVisual, setShowVisualState] = useState(() =>
+    openDocName != null ? showVisualForFile(openDocName) : false
+  )
+
+  useEffect(() => {
+    setShowVisualState(
+      openDocName != null ? showVisualForFile(openDocName) : false
+    )
+  }, [openDocName])
+
+  const setShowVisual: Dispatch<SetStateAction<boolean>> = useCallback(
+    value => {
+      setShowVisualState(prev => {
+        const actual = typeof value === 'function' ? value(prev) : value
+        if (openDocName != null) {
+          const key = getVisualEditorStorageKey(openDocName)
+          customLocalStorage.setItem(key, actual ? 'visual' : 'code')
+        }
+        return actual
+      })
+    },
+    [openDocName]
   )
 
   // Sync the showVisual state with the exposed store
@@ -83,6 +113,7 @@ export const EditorPropertiesProvider: FC<PropsWithChildren> = ({
   const value = {
     showVisual,
     setShowVisual,
+    showVisualForFile,
     showSymbolPalette,
     setShowSymbolPalette,
     toggleSymbolPalette,

+ 4 - 3
services/web/frontend/js/features/ide-react/context/file-tree-open-context.tsx

@@ -49,7 +49,7 @@ export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
   const { eventEmitter, projectJoined } = useIdeReactContext()
   const { openDocWithId, openInitialDoc } = useEditorManagerContext()
   const { currentDocumentId } = useEditorOpenDocContext()
-  const { showVisual } = useEditorPropertiesContext()
+  const { showVisualForFile } = useEditorPropertiesContext()
   const { setOpenFile } = useLayoutContext()
   const [openEntity, setOpenEntity] = useState<
     FileTreeDocumentFindResult | FileTreeFileRefFindResult | null
@@ -93,7 +93,8 @@ export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
 
       setOpenEntity(selected)
       const editorMode =
-        isVisualEditorAvailable(selected.entity.name) && showVisual
+        isVisualEditorAvailable(selected.entity.name) &&
+        showVisualForFile(selected.entity.name)
           ? 'visual'
           : 'code'
 
@@ -127,7 +128,7 @@ export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
         window.dispatchEvent(new CustomEvent('file-view:file-opened'))
       }
     },
-    [fileTreeReady, openDocWithId, projectOwner, setOpenFile, showVisual]
+    [fileTreeReady, openDocWithId, projectOwner, setOpenFile, showVisualForFile]
   )
 
   const handleFileTreeDelete = useCallback(

+ 4 - 4
services/web/frontend/js/features/ide-react/context/react-context-root.tsx

@@ -103,9 +103,9 @@ export const ReactContextRoot: FC<
                 <Providers.UserProvider>
                   <Providers.SnapshotProvider>
                     <Providers.DetachProvider>
-                      <Providers.EditorPropertiesProvider>
+                      <Providers.EditorOpenDocProvider>
                         <Providers.EditorViewProvider>
-                          <Providers.EditorOpenDocProvider>
+                          <Providers.EditorPropertiesProvider>
                             <Providers.EditorProvider>
                               <Providers.TutorialProvider>
                                 <Providers.FileTreeDataProvider>
@@ -151,9 +151,9 @@ export const ReactContextRoot: FC<
                                 </Providers.FileTreeDataProvider>
                               </Providers.TutorialProvider>
                             </Providers.EditorProvider>
-                          </Providers.EditorOpenDocProvider>
+                          </Providers.EditorPropertiesProvider>
                         </Providers.EditorViewProvider>
-                      </Providers.EditorPropertiesProvider>
+                      </Providers.EditorOpenDocProvider>
                     </Providers.DetachProvider>
                   </Providers.SnapshotProvider>
                 </Providers.UserProvider>

+ 10 - 0
services/web/frontend/js/features/source-editor/utils/visual-editor.ts

@@ -27,3 +27,13 @@ export function getVisualEditorComponent(filename: string) {
   }
   return null
 }
+
+export function getVisualEditorStorageKey(filename: string): string {
+  for (const provider of visualEditorProviders) {
+    if (provider.import.isVisualEditorAvailable(filename)) {
+      const id = provider.import.id
+      return id != null ? `editor.lastUsedMode.${id}` : 'editor.lastUsedMode'
+    }
+  }
+  return 'editor.lastUsedMode'
+}

+ 1 - 0
services/web/frontend/stories/source-editor/source-editor.stories.tsx

@@ -92,6 +92,7 @@ const VisualEditorPropertiesProvider: FC<React.PropsWithChildren> = ({
   const value = {
     showVisual,
     setShowVisual,
+    showVisualForFile: () => showVisual,
     showSymbolPalette: true,
     setShowSymbolPalette: () => undefined,
     toggleSymbolPalette: () => undefined,

+ 1 - 0
services/web/test/frontend/helpers/editor-providers.tsx

@@ -636,6 +636,7 @@ export function makeEditorPropertiesProvider(
     const value = {
       showVisual,
       setShowVisual,
+      showVisualForFile: () => showVisual,
       showSymbolPalette,
       setShowSymbolPalette,
       toggleSymbolPalette,