Преглед изворни кода

Merge pull request #28298 from overleaf/dp-editor-switch-split-test

Update editor switching behaviour for new user split test

GitOrigin-RevId: 61ef678ba216323d283bda4cc77d8c465b8c87df
David пре 11 месеци
родитељ
комит
213b645875

+ 3 - 0
services/web/frontend/extracted-translations.json

@@ -1090,6 +1090,9 @@
   "neither_agree_nor_disagree": "",
   "new_compile_domain_notice": "",
   "new_create_tables_and_equations": "",
+  "new_editor": "",
+  "new_editor_experience": "",
+  "new_editor_info": "",
   "new_file": "",
   "new_folder": "",
   "new_look_and_feel": "",

+ 2 - 0
services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx

@@ -19,6 +19,7 @@ import SettingsMathPreview from './settings/settings-math-preview'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import { ElementType } from 'react'
 import OLForm from '@/shared/components/ol/ol-form'
+import SettingsNewEditor from './settings/settings-new-editor'
 
 const moduleSettings: Array<{
   import: { default: ElementType }
@@ -56,6 +57,7 @@ export default function SettingsMenu() {
         <SettingsFontFamily />
         <SettingsLineHeight />
         <SettingsPdfViewer />
+        <SettingsNewEditor />
       </OLForm>
     </>
   )

+ 47 - 0
services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx

@@ -0,0 +1,47 @@
+import { useTranslation } from 'react-i18next'
+import SettingsMenuSelect from './settings-menu-select'
+import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use-switch-enable-new-editor-state'
+import { useLayoutContext } from '@/shared/context/layout-context'
+import { useCallback } from 'react'
+import {
+  canUseNewEditorViaNewUserFeatureFlag,
+  useIsNewEditorEnabled,
+} from '@/features/ide-redesign/utils/new-editor-utils'
+
+export default function SettingsNewEditor() {
+  const { t } = useTranslation()
+  const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
+  const { setLeftMenuShown } = useLayoutContext()
+  const enabled = useIsNewEditorEnabled()
+  const show = canUseNewEditorViaNewUserFeatureFlag()
+
+  const onChange = useCallback(
+    (newValue: boolean) => {
+      setEditorRedesignStatus(newValue).then(() => setLeftMenuShown(false))
+    },
+    [setEditorRedesignStatus, setLeftMenuShown]
+  )
+
+  if (!show) {
+    return null
+  }
+
+  return (
+    <SettingsMenuSelect
+      onChange={onChange}
+      value={enabled}
+      options={[
+        {
+          value: true,
+          label: t('on'),
+        },
+        {
+          value: false,
+          label: t('off'),
+        },
+      ]}
+      label={t('new_editor')}
+      name="new-editor-setting"
+    />
+  )
+}

+ 1 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx

@@ -15,10 +15,10 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac
 import BackToEditorButton from './back-to-editor-button'
 import getMeta from '@/utils/meta'
 import { isSplitTestEnabled } from '@/utils/splitTestUtils'
-import { canUseNewEditor } from '@/features/ide-redesign/utils/new-editor-utils'
 import TryNewEditorButton from '../try-new-editor-button'
 import { OnlineUser } from '@/features/ide-react/context/online-users-context'
 import { Cobranding } from '../../../../../types/cobranding'
+import { canUseNewEditor } from '@/features/ide-redesign/utils/new-editor-utils'
 
 const [publishModalModules] = importOverleafModules('publishModal') as {
   import: { default: ElementType }

+ 22 - 3
services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx

@@ -2,14 +2,24 @@ import { useCallback } from 'react'
 import OLButton from '../../shared/components/ol/ol-button'
 import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign-switcher-context'
 import { useTranslation } from 'react-i18next'
+import { canUseNewEditorViaPrimaryFeatureFlag } from '../ide-redesign/utils/new-editor-utils'
+import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state'
+import { Spinner } from 'react-bootstrap'
 
 const TryNewEditorButton = () => {
   const { t } = useTranslation()
   const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
+  const showModal = canUseNewEditorViaPrimaryFeatureFlag()
+  const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState()
 
   const onClick = useCallback(() => {
-    setShowSwitcherModal(true)
-  }, [setShowSwitcherModal])
+    if (showModal) {
+      setShowSwitcherModal(true)
+    } else {
+      setEditorRedesignStatus(true)
+    }
+  }, [setShowSwitcherModal, showModal, setEditorRedesignStatus])
+
   return (
     <div className="d-flex align-items-center">
       <OLButton
@@ -18,7 +28,16 @@ const TryNewEditorButton = () => {
         size="sm"
         variant="secondary"
       >
-        {t('try_the_new_editor')}
+        {loading ? (
+          <Spinner
+            animation="border"
+            aria-hidden="true"
+            size="sm"
+            role="status"
+          />
+        ) : (
+          t('try_the_new_editor')
+        )}
       </OLButton>
     </div>
   )

+ 31 - 0
services/web/frontend/js/features/ide-redesign/components/settings/editor-settings/new-editor-setting.tsx

@@ -0,0 +1,31 @@
+import ToggleSetting from '../toggle-setting'
+import { useTranslation } from 'react-i18next'
+import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use-switch-enable-new-editor-state'
+import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
+import { useCallback } from 'react'
+import { useLayoutContext } from '@/shared/context/layout-context'
+
+export default function NewEditorSetting() {
+  const { t } = useTranslation()
+  const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
+  const { setLeftMenuShown } = useLayoutContext()
+  const enabled = useIsNewEditorEnabled()
+  const handleToggle = useCallback(() => {
+    setEditorRedesignStatus(!enabled).then(() => setLeftMenuShown(false))
+  }, [enabled, setEditorRedesignStatus, setLeftMenuShown])
+
+  return (
+    <ToggleSetting
+      id="new-editor-setting"
+      label={
+        <div className="ide-setting-new-editor">
+          {t('new_editor_experience')}
+          <div className="ide-setting-beta-tag">{t('beta')}</div>
+        </div>
+      }
+      description={t('new_editor_info')}
+      checked={enabled}
+      onChange={handleToggle}
+    />
+  )
+}

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

@@ -1,10 +1,12 @@
+import React from 'react'
+
 export default function Setting({
   label,
   controlId,
   children,
   description = undefined,
 }: {
-  label: string
+  label: React.ReactNode
   description: string | undefined
   controlId: string
   children: React.ReactNode
@@ -19,7 +21,7 @@ export default function Setting({
           <div className="ide-setting-description">{description}</div>
         )}
       </div>
-      {children}
+      <div className="ide-setting-input">{children}</div>
     </div>
   )
 }

+ 4 - 3
services/web/frontend/js/features/ide-redesign/components/settings/settings-tab-pane.tsx

@@ -9,9 +9,10 @@ export default function SettingsTabPane({ tab }: { tab: SettingsTab }) {
     <TabPane eventKey={key} key={key}>
       {sections.map(section => (
         <SettingsSection key={section.key} title={section.title}>
-          {section.settings.map(({ key, component }) => (
-            <Fragment key={key}>{component}</Fragment>
-          ))}
+          {section.settings.map(
+            ({ key, component, hidden }) =>
+              !hidden && <Fragment key={key}>{component}</Fragment>
+          )}
         </SettingsSection>
       ))}
     </TabPane>

+ 2 - 1
services/web/frontend/js/features/ide-redesign/components/settings/toggle-setting.tsx

@@ -1,3 +1,4 @@
+import React from 'react'
 import Setting from './setting'
 import OLFormSwitch from '@/shared/components/ol/ol-form-switch'
 
@@ -10,7 +11,7 @@ export default function ToggleSetting({
   disabled,
 }: {
   id: string
-  label: string
+  label: React.ReactNode
   description: string
   checked: boolean | undefined
   onChange: (newValue: boolean) => void

+ 6 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx

@@ -4,6 +4,7 @@ import OLTooltip from '@/shared/components/ol/ol-tooltip'
 import { useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
 import { GiveFeedbackLink } from './give-feedback-link'
+import { useIsNewEditorEnabledViaPrimaryFeatureFlag } from '../../utils/new-editor-utils'
 
 export const BetaActions = () => {
   const { t } = useTranslation()
@@ -11,6 +12,11 @@ export const BetaActions = () => {
   const openEditorRedesignSwitcherModal = useCallback(() => {
     setShowSwitcherModal(true)
   }, [setShowSwitcherModal])
+  const showBetaActions = useIsNewEditorEnabledViaPrimaryFeatureFlag()
+
+  if (!showBetaActions) {
+    return null
+  }
 
   return (
     <>

+ 20 - 14
services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx

@@ -30,6 +30,7 @@ import { useSurveyUrl } from '../../hooks/use-survey-url'
 import getMeta from '@/utils/meta'
 import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/components/editor-clone-project-modal-wrapper'
 import useOpenProject from '@/shared/hooks/use-open-project'
+import { canUseNewEditorViaPrimaryFeatureFlag } from '../../utils/new-editor-utils'
 
 export const ToolbarMenuBar = () => {
   const { t } = useTranslation()
@@ -43,6 +44,7 @@ export const ToolbarMenuBar = () => {
   const [showWordCountModal, setShowWordCountModal] = useState(false)
   const [showCloneProjectModal, setShowCloneProjectModal] = useState(false)
   const openProject = useOpenProject()
+  const showEditorSwitchMenuOption = canUseNewEditorViaPrimaryFeatureFlag()
 
   const anonymous = getMeta('ol-anonymous')
 
@@ -283,20 +285,24 @@ export const ToolbarMenuBar = () => {
             title={t('contact_us')}
             onClick={openContactUsModal}
           />
-          <MenuBarOption
-            eventKey="give_feedback"
-            title={t('give_feedback')}
-            href={surveyURL}
-            target="_blank"
-            rel="noopener noreferrer"
-          />
-          <DropdownDivider />
-          <SwitchToOldEditorMenuBarOption />
-          <MenuBarOption
-            eventKey="whats_new"
-            title="What's new?"
-            onClick={openEditorRedesignSwitcherModal}
-          />
+          {showEditorSwitchMenuOption && (
+            <>
+              <MenuBarOption
+                eventKey="give_feedback"
+                title={t('give_feedback')}
+                href={surveyURL}
+                target="_blank"
+                rel="noopener noreferrer"
+              />
+              <DropdownDivider />
+              <SwitchToOldEditorMenuBarOption />
+              <MenuBarOption
+                eventKey="whats_new"
+                title="What's new?"
+                onClick={openEditorRedesignSwitcherModal}
+              />
+            </>
+          )}
         </MenuBarDropdown>
       </MenuBar>
       <WordCountModal

+ 9 - 1
services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx

@@ -24,6 +24,8 @@ import LineHeightSetting from '../components/settings/appearance-settings/line-h
 import FontFamilySetting from '../components/settings/appearance-settings/font-family-setting'
 import { AvailableUnfilledIcon } from '@/shared/components/material-icon'
 import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
+import NewEditorSetting from '../components/settings/editor-settings/new-editor-setting'
+import { canUseNewEditorViaNewUserFeatureFlag } from '../utils/new-editor-utils'
 
 const [referenceSearchSettingModule] = importOverleafModules(
   'referenceSearchSetting'
@@ -75,6 +77,7 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
   children,
 }) => {
   const { t } = useTranslation()
+  const showEditorSwitch = canUseNewEditorViaNewUserFeatureFlag()
 
   // TODO ide-redesign-cleanup: Rename this field and move it directly into this context
   const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
@@ -209,6 +212,11 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
                 key: 'lineHeight',
                 component: <LineHeightSetting />,
               },
+              {
+                key: 'newEditor',
+                component: <NewEditorSetting />,
+                hidden: !showEditorSwitch,
+              },
             ],
           },
         ],
@@ -226,7 +234,7 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
         href: '/user/subscription',
       },
     ],
-    [t]
+    [t, showEditorSwitch]
   )
 
   const settingToTabMap = useMemo(() => {

+ 21 - 8
services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts

@@ -14,32 +14,45 @@ const isNewUser = () => {
   return createdAt > NEW_USER_CUTOFF_DATE
 }
 
-export const canUseNewEditor = () => {
+export const canUseNewEditorViaPrimaryFeatureFlag = () => {
+  return isSplitTestEnabled('editor-redesign')
+}
+
+export const canUseNewEditorViaNewUserFeatureFlag = () => {
   const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
 
-  const canUseNewEditorViaPrimaryFeatureFlag =
-    isSplitTestEnabled('editor-redesign')
-  const canUseNewEditorViaNewUserFeatureFlag =
+  return (
+    !canUseNewEditorViaPrimaryFeatureFlag() &&
     isNewUser() &&
     (newUserTestVariant === 'new-editor' ||
       newUserTestVariant === 'new-editor-old-logs')
+  )
+}
+
+export const canUseNewEditor = () => {
   return (
-    canUseNewEditorViaPrimaryFeatureFlag || canUseNewEditorViaNewUserFeatureFlag
+    canUseNewEditorViaPrimaryFeatureFlag() ||
+    canUseNewEditorViaNewUserFeatureFlag()
   )
 }
 
 const canUseNewLogs = () => {
   const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
-  const canUseNewLogsViaPrimaryFeatureFlag =
-    isSplitTestEnabled('editor-redesign')
   const canUseNewLogsViaNewUserFeatureFlag =
     isNewUser() && newUserTestVariant === 'new-editor'
 
   return (
-    canUseNewLogsViaPrimaryFeatureFlag || canUseNewLogsViaNewUserFeatureFlag
+    canUseNewEditorViaPrimaryFeatureFlag() || canUseNewLogsViaNewUserFeatureFlag
   )
 }
 
+export const useIsNewEditorEnabledViaPrimaryFeatureFlag = () => {
+  const { userSettings } = useUserSettingsContext()
+  const hasAccess = canUseNewEditorViaPrimaryFeatureFlag()
+  const enabled = userSettings.enableNewEditor
+  return hasAccess && enabled
+}
+
 export const useIsNewEditorEnabled = () => {
   const { userSettings } = useUserSettingsContext()
   const hasAccess = canUseNewEditor()

+ 22 - 0
services/web/frontend/stylesheets/pages/editor/settings.scss

@@ -91,3 +91,25 @@
   color: var(--content-secondary);
   font-size: var(--font-size-02);
 }
+
+.ide-setting-input {
+  min-width: 160px;
+  display: flex;
+  justify-content: flex-end;
+  margin-left: var(--spacing-06);
+}
+
+.ide-setting-new-editor {
+  display: flex;
+  gap: var(--spacing-04);
+}
+
+.ide-setting-beta-tag {
+  font-size: var(--font-size-01);
+  line-height: var(--line-height-01);
+  color: var(--green-60);
+  background: var(--bg-accent-03);
+  border: 1px solid var(--green-50);
+  border-radius: var(--border-radius-full);
+  padding: var(--spacing-01) var(--spacing-03);
+}

+ 3 - 0
services/web/locales/en.json

@@ -1419,6 +1419,9 @@
   "neither_agree_nor_disagree": "Neither agree nor disagree",
   "new_compile_domain_notice": "We’ve recently migrated PDF downloads to a new domain. Something might be blocking your browser from accessing that new domain, <0>__compilesUserContentDomain__</0>. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide</1>.",
   "new_create_tables_and_equations": "NEW! <sparkle/> Create tables and equations in seconds",
+  "new_editor": "New editor",
+  "new_editor_experience": "New editor experience",
+  "new_editor_info": "Our new editor is currently in beta. Disabling this option will change your experience to the old Overleaf editor.",
   "new_file": "New file",
   "new_folder": "New folder",
   "new_look_and_feel": "New look and feel",