Przeglądaj źródła

Merge pull request #29899 from overleaf/mj-dark-mode-file-flash

[web] Avoid background color flash when switching files

GitOrigin-RevId: e5d2fbb631fd54d195b9cb51b2a9db584d205138
Mathias Jakobsen 8 miesięcy temu
rodzic
commit
7c1a225be4

+ 40 - 40
services/web/app/src/Features/Project/ProjectController.mjs

@@ -1308,49 +1308,49 @@ const defaultUserValues = () => ({
 })
 
 const THEME_LIST = [
-  'cobalt',
-  'dracula',
-  'eclipse',
-  'monokai',
-  'overleaf',
-  'overleaf_dark',
-  'textmate',
+  { name: 'cobalt', dark: true },
+  { name: 'dracula', dark: true },
+  { name: 'eclipse', dark: false },
+  { name: 'monokai', dark: true },
+  { name: 'overleaf', dark: false },
+  { name: 'overleaf_dark', dark: true },
+  { name: 'textmate', dark: false },
 ]
 
 const LEGACY_THEME_LIST = [
-  'ambiance',
-  'chaos',
-  'chrome',
-  'clouds',
-  'clouds_midnight',
-  'crimson_editor',
-  'dawn',
-  'dreamweaver',
-  'github',
-  'gob',
-  'gruvbox',
-  'idle_fingers',
-  'iplastic',
-  'katzenmilch',
-  'kr_theme',
-  'kuroir',
-  'merbivore',
-  'merbivore_soft',
-  'mono_industrial',
-  'nord_dark',
-  'pastel_on_dark',
-  'solarized_dark',
-  'solarized_light',
-  'sqlserver',
-  'terminal',
-  'tomorrow',
-  'tomorrow_night',
-  'tomorrow_night_blue',
-  'tomorrow_night_bright',
-  'tomorrow_night_eighties',
-  'twilight',
-  'vibrant_ink',
-  'xcode',
+  { name: 'ambiance', dark: true },
+  { name: 'chaos', dark: true },
+  { name: 'chrome', dark: false },
+  { name: 'clouds', dark: false },
+  { name: 'clouds_midnight', dark: true },
+  { name: 'crimson_editor', dark: false },
+  { name: 'dawn', dark: false },
+  { name: 'dreamweaver', dark: false },
+  { name: 'github', dark: false },
+  { name: 'gob', dark: true },
+  { name: 'gruvbox', dark: true },
+  { name: 'idle_fingers', dark: true },
+  { name: 'iplastic', dark: false },
+  { name: 'katzenmilch', dark: false },
+  { name: 'kr_theme', dark: true },
+  { name: 'kuroir', dark: false },
+  { name: 'merbivore', dark: true },
+  { name: 'merbivore_soft', dark: true },
+  { name: 'mono_industrial', dark: true },
+  { name: 'nord_dark', dark: true },
+  { name: 'pastel_on_dark', dark: true },
+  { name: 'solarized_dark', dark: true },
+  { name: 'solarized_light', dark: false },
+  { name: 'sqlserver', dark: false },
+  { name: 'terminal', dark: true },
+  { name: 'tomorrow', dark: false },
+  { name: 'tomorrow_night', dark: true },
+  { name: 'tomorrow_night_blue', dark: true },
+  { name: 'tomorrow_night_bright', dark: true },
+  { name: 'tomorrow_night_eighties', dark: true },
+  { name: 'twilight', dark: true },
+  { name: 'vibrant_ink', dark: true },
+  { name: 'xcode', dark: false },
 ]
 
 const ProjectController = {

+ 4 - 4
services/web/frontend/js/features/editor-left-menu/components/settings/settings-editor-theme.tsx

@@ -14,8 +14,8 @@ export default function SettingsEditorTheme() {
   const options = useMemo(() => {
     const editorThemeOptions: Array<Option> =
       editorThemes?.map(theme => ({
-        value: theme,
-        label: theme.replace(/_/g, ' '),
+        value: theme.name,
+        label: theme.name.replace(/_/g, ' '),
       })) ?? []
 
     const dividerOption: Option = {
@@ -26,8 +26,8 @@ export default function SettingsEditorTheme() {
 
     const legacyEditorThemeOptions: Array<Option> =
       legacyEditorThemes?.map(theme => ({
-        value: theme,
-        label: theme.replace(/_/g, ' ') + ' (Legacy)',
+        value: theme.name,
+        label: theme.name.replace(/_/g, ' ') + ' (Legacy)',
       })) ?? []
 
     return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]

+ 25 - 0
services/web/frontend/js/features/ide-react/components/editor/editor-loading-pane.tsx

@@ -0,0 +1,25 @@
+import { FC, useMemo } from 'react'
+import LoadingSpinner from '@/shared/components/loading-spinner'
+import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
+import getMeta from '@/utils/meta'
+import classNames from 'classnames'
+
+export const EditorLoadingPane: FC = () => {
+  const { editorTheme } = useProjectSettingsContext()
+  const isDark = useMemo(() => {
+    const themes = getMeta('ol-editorThemes') || []
+    const legacyThemes = getMeta('ol-legacyEditorThemes') || []
+    const selectedTheme =
+      themes.find(theme => theme.name === editorTheme) ||
+      legacyThemes.find(theme => theme.name === editorTheme)
+    return selectedTheme?.dark ?? false
+  }, [editorTheme])
+
+  return (
+    <div
+      className={classNames('loading-panel', { 'loading-panel-dark': isDark })}
+    >
+      <LoadingSpinner />
+    </div>
+  )
+}

+ 2 - 2
services/web/frontend/js/features/ide-react/components/editor/editor-pane.tsx

@@ -4,7 +4,7 @@ import SourceEditor from '@/features/source-editor/components/source-editor'
 import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
 import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
 import classNames from 'classnames'
-import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
+import { EditorLoadingPane } from '@/features/ide-react/components/editor/editor-loading-pane'
 import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
 import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
 import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
@@ -37,7 +37,7 @@ export const EditorPane: FC = () => {
           className="ide-react-editor-panel"
         >
           <SourceEditor />
-          {isLoading && <LoadingPane />}
+          {isLoading && <EditorLoadingPane />}
         </Panel>
 
         {showSymbolPalette && (

+ 0 - 10
services/web/frontend/js/features/ide-react/components/editor/loading-pane.tsx

@@ -1,10 +0,0 @@
-import { FC } from 'react'
-import LoadingSpinner from '@/shared/components/loading-spinner'
-
-export const LoadingPane: FC = () => {
-  return (
-    <div className="loading-panel">
-      <LoadingSpinner />
-    </div>
-  )
-}

+ 2 - 2
services/web/frontend/js/features/ide-redesign/components/editor.tsx

@@ -1,4 +1,4 @@
-import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
+import { EditorLoadingPane } from '@/features/ide-react/components/editor/editor-loading-pane'
 import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
 import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
 import classNames from 'classnames'
@@ -40,7 +40,7 @@ export const Editor = () => {
           className="ide-redesign-editor-panel"
         >
           <SourceEditor />
-          {isLoading && <LoadingPane />}
+          {isLoading && <EditorLoadingPane />}
         </Panel>
         {showSymbolPalette && (
           <>

+ 4 - 4
services/web/frontend/js/features/ide-redesign/components/settings/appearance-settings/editor-theme-setting.tsx

@@ -14,8 +14,8 @@ export default function EditorThemeSetting() {
   const options = useMemo(() => {
     const editorThemeOptions: Array<Option> =
       editorThemes?.map(theme => ({
-        value: theme,
-        label: theme.replace(/_/g, ' '),
+        value: theme.name,
+        label: theme.name.replace(/_/g, ' '),
       })) ?? []
 
     const dividerOption: Option = {
@@ -26,8 +26,8 @@ export default function EditorThemeSetting() {
 
     const legacyEditorThemeOptions: Array<Option> =
       legacyEditorThemes?.map(theme => ({
-        value: theme,
-        label: theme.replace(/_/g, ' ') + ' (Legacy)',
+        value: theme.name,
+        label: theme.name.replace(/_/g, ' ') + ' (Legacy)',
       })) ?? []
 
     return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]

+ 2 - 1
services/web/frontend/js/features/source-editor/extensions/theme.ts

@@ -287,7 +287,8 @@ const loadSelectedTheme = async (editorTheme: string) => {
     const themes = getMeta('ol-editorThemes') || []
     const legacyThemes = getMeta('ol-legacyEditorThemes') || []
     const themeExists =
-      themes.includes(editorTheme) || legacyThemes.includes(editorTheme)
+      themes.some(theme => theme.name === editorTheme) ||
+      legacyThemes.some(theme => theme.name === editorTheme)
     if (!themeExists) {
       editorTheme = 'textmate' // fallback to default if the theme is not found
     }

+ 2 - 2
services/web/frontend/js/utils/meta.ts

@@ -117,7 +117,7 @@ export interface Meta {
   'ol-domainCaptureEnabled': boolean | undefined
   'ol-domainCaptureTestURL': string | undefined
   'ol-dropbox': { error: boolean; registered: boolean }
-  'ol-editorThemes': string[]
+  'ol-editorThemes': { name: string; dark: boolean }[]
   'ol-email': string
   'ol-emailAddressLimit': number
   'ol-error': { name: string } | undefined
@@ -188,7 +188,7 @@ export interface Meta {
   'ol-labsExperiments': ActiveExperiment[] | undefined
   'ol-languages': SpellCheckLanguage[]
   'ol-learnedWords': string[]
-  'ol-legacyEditorThemes': string[]
+  'ol-legacyEditorThemes': { name: string; dark: boolean }[]
   'ol-licenseQuantity'?: number
   'ol-loadingText': string
   'ol-managedGroupSubscriptions': ManagedGroupSubscription[]

+ 5 - 0
services/web/frontend/stylesheets/pages/editor/file-view.scss

@@ -7,6 +7,11 @@
   .file-view .no-preview {
     color: var(--content-secondary-themed);
   }
+
+  .file-view .loading-panel {
+    color: var(--content-secondary-themed);
+    background: var(--bg-primary-themed);
+  }
 }
 
 .file-view {

+ 11 - 0
services/web/frontend/stylesheets/pages/editor/ide.scss

@@ -77,6 +77,17 @@ $editor-toggler-bg-dark-color: color.adjust(
       background-color: var(--bg-tertiary-themed);
     }
   }
+
+  .loading-panel {
+    background-color: var(--bg-light-primary);
+    color: var(--content-primary);
+
+    // Based on editor theme rather than overall theme
+    &.loading-panel-dark {
+      background-color: var(--bg-dark-primary);
+      color: var(--content-primary-dark);
+    }
+  }
 }
 
 .global-alerts {

+ 8 - 4
services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx

@@ -571,12 +571,16 @@ describe('<EditorLeftMenu />', function () {
       })
 
       it('shows editor theme menu correctly', function () {
-        const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
+        const editorThemes = [
+          { name: 'editortheme-1', dark: false },
+          { name: 'editortheme-2', dark: false },
+          { name: 'editortheme-3', dark: false },
+        ]
 
         const legacyEditorThemes = [
-          'legacytheme-1',
-          'legacytheme-2',
-          'legacytheme-3',
+          { name: 'legacytheme-1', dark: false },
+          { name: 'legacytheme-2', dark: false },
+          { name: 'legacytheme-3', dark: false },
         ]
 
         window.metaAttributesCache.set('ol-editorThemes', editorThemes)

+ 15 - 7
services/web/test/frontend/features/editor-left-menu/components/settings/settings-editor-theme.test.tsx

@@ -6,9 +6,17 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
 import { EditorProviders } from '../../../../helpers/editor-providers'
 
 describe('<SettingsEditorTheme />', function () {
-  const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
-
-  const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
+  const editorThemes = [
+    { name: 'editortheme-1', dark: false },
+    { name: 'editortheme-2', dark: false },
+    { name: 'editortheme-3', dark: false },
+  ]
+
+  const legacyEditorThemes = [
+    { name: 'legacytheme-1', dark: false },
+    { name: 'legacytheme-2', dark: false },
+    { name: 'legacytheme-3', dark: false },
+  ]
 
   beforeEach(function () {
     window.metaAttributesCache.set('ol-editorThemes', editorThemes)
@@ -31,15 +39,15 @@ describe('<SettingsEditorTheme />', function () {
     const select = screen.getByLabelText('Editor theme')
 
     for (const theme of editorThemes) {
-      const option = within(select).getByText(theme.replace(/_/g, ' '))
-      expect(option.getAttribute('value')).to.equal(theme)
+      const option = within(select).getByText(theme.name.replace(/_/g, ' '))
+      expect(option.getAttribute('value')).to.equal(theme.name)
     }
 
     for (const theme of legacyEditorThemes) {
       const option = within(select).getByText(
-        theme.replace(/_/g, ' ') + ' (Legacy)'
+        theme.name.replace(/_/g, ' ') + ' (Legacy)'
       )
-      expect(option.getAttribute('value')).to.equal(theme)
+      expect(option.getAttribute('value')).to.equal(theme.name)
     }
   })
 })

+ 10 - 2
services/web/test/frontend/features/settings-modal/settings-modal.test.tsx

@@ -60,8 +60,16 @@ describe('<SettingsModal />', function () {
     },
   ]
 
-  const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
-  const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
+  const editorThemes = [
+    { name: 'editortheme-1', dark: false },
+    { name: 'editortheme-2', dark: false },
+    { name: 'editortheme-3', dark: false },
+  ]
+  const legacyEditorThemes = [
+    { name: 'legacytheme-1', dark: false },
+    { name: 'legacytheme-2', dark: false },
+    { name: 'legacytheme-3', dark: false },
+  ]
 
   const imageNames: ImageName[] = [
     {

+ 16 - 8
services/web/test/frontend/features/settings-modal/settings/editor-theme-setting.test.tsx

@@ -7,8 +7,16 @@ import EditorThemeSetting from '@/features/ide-redesign/components/settings/appe
 import userEvent from '@testing-library/user-event'
 
 describe('<EditorThemeSetting />', function () {
-  const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
-  const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
+  const editorThemes = [
+    { name: 'editortheme-1', dark: false },
+    { name: 'editortheme-2', dark: false },
+    { name: 'editortheme-3', dark: false },
+  ]
+  const legacyEditorThemes = [
+    { name: 'legacytheme-1', dark: false },
+    { name: 'legacytheme-2', dark: false },
+    { name: 'legacytheme-3', dark: false },
+  ]
 
   beforeEach(function () {
     window.metaAttributesCache.set('ol-editorThemes', editorThemes)
@@ -39,25 +47,25 @@ describe('<EditorThemeSetting />', function () {
     const select = screen.getByLabelText('Editor theme')
 
     for (const theme of editorThemes) {
-      const option = within(select).getByText(theme.replace(/_/g, ' '))
-      expect(option.getAttribute('value')).to.equal(theme)
+      const option = within(select).getByText(theme.name.replace(/_/g, ' '))
+      expect(option.getAttribute('value')).to.equal(theme.name)
       await userEvent.selectOptions(select, [option])
       expect(
         saveSettingsMock.callHistory.called(`/user/settings`, {
-          body: { editorTheme: theme },
+          body: { editorTheme: theme.name },
         })
       ).to.be.true
     }
 
     for (const theme of legacyEditorThemes) {
       const option = within(select).getByText(
-        theme.replace(/_/g, ' ') + ' (Legacy)'
+        theme.name.replace(/_/g, ' ') + ' (Legacy)'
       )
-      expect(option.getAttribute('value')).to.equal(theme)
+      expect(option.getAttribute('value')).to.equal(theme.name)
       await userEvent.selectOptions(select, [option])
       expect(
         saveSettingsMock.callHistory.called(`/user/settings`, {
-          body: { editorTheme: theme },
+          body: { editorTheme: theme.name },
         })
       ).to.be.true
     }