diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 1b19c7e8a0..f5127a41b3 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/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": "",
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx
index 66c4901bce..f692c6e7fa 100644
--- a/services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx
+++ b/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() {
+
>
)
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-new-editor.tsx
new file mode 100644
index 0000000000..c57ec12924
--- /dev/null
+++ b/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 (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx
index 41d9fad26a..5dfaccde59 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx
+++ b/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 }
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx
index 5c27d16b58..360f6d0a47 100644
--- a/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx
+++ b/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 (
{
size="sm"
variant="secondary"
>
- {t('try_the_new_editor')}
+ {loading ? (
+
+ ) : (
+ t('try_the_new_editor')
+ )}
)
diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/editor-settings/new-editor-setting.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/editor-settings/new-editor-setting.tsx
new file mode 100644
index 0000000000..e1ba4149ae
--- /dev/null
+++ b/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 (
+
+ {t('new_editor_experience')}
+ {t('beta')}
+
+ }
+ description={t('new_editor_info')}
+ checked={enabled}
+ onChange={handleToggle}
+ />
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/setting.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/setting.tsx
index cf240737ce..088bb39cac 100644
--- a/services/web/frontend/js/features/ide-redesign/components/settings/setting.tsx
+++ b/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({
{description}
)}
- {children}
+ {children}
)
}
diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/settings-tab-pane.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/settings-tab-pane.tsx
index 0a0ad4cdff..f83f811d90 100644
--- a/services/web/frontend/js/features/ide-redesign/components/settings/settings-tab-pane.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/settings/settings-tab-pane.tsx
@@ -9,9 +9,10 @@ export default function SettingsTabPane({ tab }: { tab: SettingsTab }) {
{sections.map(section => (
- {section.settings.map(({ key, component }) => (
- {component}
- ))}
+ {section.settings.map(
+ ({ key, component, hidden }) =>
+ !hidden && {component}
+ )}
))}
diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/toggle-setting.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/toggle-setting.tsx
index 50ca19ed9f..da367f309b 100644
--- a/services/web/frontend/js/features/ide-redesign/components/settings/toggle-setting.tsx
+++ b/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
diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx
index b7b0831233..9071960efa 100644
--- a/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx
+++ b/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 (
<>
diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx
index 39ffdc3485..1561a1f1b3 100644
--- a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx
+++ b/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}
/>
-
-
-
-
+ {showEditorSwitchMenuOption && (
+ <>
+
+
+
+
+ >
+ )}
= ({
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 = ({
key: 'lineHeight',
component: ,
},
+ {
+ key: 'newEditor',
+ component: ,
+ hidden: !showEditorSwitch,
+ },
],
},
],
@@ -226,7 +234,7 @@ export const SettingsModalProvider: FC = ({
href: '/user/subscription',
},
],
- [t]
+ [t, showEditorSwitch]
)
const settingToTabMap = useMemo(() => {
diff --git a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
index 761f6f1109..5403cc8b1a 100644
--- a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
+++ b/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()
diff --git a/services/web/frontend/stylesheets/pages/editor/settings.scss b/services/web/frontend/stylesheets/pages/editor/settings.scss
index a89fe46148..85816bdcb8 100644
--- a/services/web/frontend/stylesheets/pages/editor/settings.scss
+++ b/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);
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 78c9df0bbc..d940da394a 100644
--- a/services/web/locales/en.json
+++ b/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 guide1>.",
"new_create_tables_and_equations": "NEW! 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",