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 ? ( +
) 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__. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide.", "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",