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

Update editor switching behaviour for new user split test

GitOrigin-RevId: 61ef678ba216323d283bda4cc77d8c465b8c87df
This commit is contained in:
David
2025-09-08 09:16:55 +01:00
committed by Copybot
parent 7928452c6a
commit 213b645875
15 changed files with 197 additions and 33 deletions

View File

@@ -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": "",

View File

@@ -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>
</>
)

View File

@@ -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"
/>
)
}

View File

@@ -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 }

View File

@@ -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>
)

View File

@@ -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}
/>
)
}

View File

@@ -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>
)
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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 (
<>

View File

@@ -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

View File

@@ -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(() => {

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -1419,6 +1419,9 @@
"neither_agree_nor_disagree": "Neither agree nor disagree",
"new_compile_domain_notice": "Weve 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",