diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index 9ae4d5d6e4..ec4005025b 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -864,6 +864,12 @@ const _ProjectController = { !userHasPremiumSub && !userInNonIndividualSub + const userSettings = await UserSettingsHelper.buildUserSettings( + req, + res, + user + ) + res.render(template, { title: project.name, priority_title: true, @@ -902,7 +908,7 @@ const _ProjectController = { isMemberOfGroupSubscription: userIsMemberOfGroupSubscription, hasInstitutionLicence: userHasInstitutionLicence, }, - userSettings: UserSettingsHelper.buildUserSettings(user), + userSettings, labsExperiments: user.labsExperiments ?? [], privilegeLevel, anonymous, diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs index 0cfb78a099..dfb165f535 100644 --- a/services/web/app/src/Features/Project/ProjectListController.mjs +++ b/services/web/app/src/Features/Project/ProjectListController.mjs @@ -542,6 +542,12 @@ async function projectListPage(req, res, next) { 'themed-project-dashboard' ) + const userSettings = await UserSettingsHelper.buildUserSettings( + req, + res, + user + ) + res.render('project/list-react', { title: 'your_projects', usersBestSubscription, @@ -550,7 +556,7 @@ async function projectListPage(req, res, next) { user, userAffiliations, userEmails, - userSettings: UserSettingsHelper.buildUserSettings(user), + userSettings, reconfirmedViaSAML, allInReconfirmNotificationPeriods, survey, diff --git a/services/web/app/src/Features/Project/UserSettingsHelper.mjs b/services/web/app/src/Features/Project/UserSettingsHelper.mjs index e4c6440c0d..9d95420824 100644 --- a/services/web/app/src/Features/Project/UserSettingsHelper.mjs +++ b/services/web/app/src/Features/Project/UserSettingsHelper.mjs @@ -1,4 +1,38 @@ -function buildUserSettings(user) { +import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs' + +// Copied from services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts +const SPLIT_TEST_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025 +const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 10, 12, 12, 0, 0)) // 12pm GMT on November 12, 2025 + +async function getEnableNewEditorDefault(req, res, user) { + if (req.query['existing-user-override'] === 'true') { + return false + } + + if (req.query['skip-new-user-check'] === 'true') { + return true + } + + if (user.signUpDate >= NEW_USER_CUTOFF_DATE) { + return true + } + + if (user.signUpDate >= SPLIT_TEST_USER_CUTOFF_DATE) { + const assignment = await SplitTestHandler.promises.getAssignment( + req, + res, + 'editor-redesign-new-users' + ) + + return assignment.variant !== 'default' + } + + return false +} + +async function buildUserSettings(req, res, user) { + const defaultEnableNewEditor = await getEnableNewEditorDefault(req, res, user) + return { mode: user.ace.mode, editorTheme: user.ace.theme, @@ -13,7 +47,7 @@ function buildUserSettings(user) { mathPreview: user.ace.mathPreview, breadcrumbs: user.ace.breadcrumbs, referencesSearchMode: user.ace.referencesSearchMode, - enableNewEditor: user.ace.enableNewEditor ?? true, + enableNewEditor: user.ace.enableNewEditor ?? defaultEnableNewEditor, } } diff --git a/services/web/app/src/Features/Tutorial/TutorialController.mjs b/services/web/app/src/Features/Tutorial/TutorialController.mjs index 3de1706047..8513a186be 100644 --- a/services/web/app/src/Features/Tutorial/TutorialController.mjs +++ b/services/web/app/src/Features/Tutorial/TutorialController.mjs @@ -24,6 +24,8 @@ const VALID_KEYS = [ 'rolling-compile-image-changed', 'groups-enterprise-banner', 'groups-enterprise-banner-repeat', + 'new-editor-opt-in', + 'new-editor-intro', ] async function completeTutorial(req, res, next) { diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 78e69f1dd4..ef301a8a01 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -182,12 +182,11 @@ "back_to_subscription": "", "back_to_your_projects": "", "basic_compile_time": "", + "be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "", "before_you_use_error_assistant": "", - "beta": "", "beta_program_already_participating": "", "beta_program_benefits": "", "beta_program_not_participating": "", - "beta_program_the_new_overleaf_editor": "", "billed_annually_at": "", "billed_monthly_at": "", "billed_yearly": "", @@ -240,6 +239,7 @@ "center": "", "change": "", "change_currency": "", + "change_how_you_see_the_editor": "", "change_language": "", "change_or_cancel-cancel": "", "change_or_cancel-change": "", @@ -581,6 +581,7 @@ "expires": "", "expires_in_days": "", "expires_on": "", + "explore_what_s_new": "", "export_csv": "", "export_project_to_github": "", "failed_to_send_group_invite_to_email": "", @@ -615,11 +616,13 @@ "files_selected": "", "filter_projects": "", "find": "", + "find_and_fix_errors_faster": "", "find_out_more": "", "find_out_more_about_institution_login": "", "find_out_more_about_the_file_outline": "", "find_out_more_nt": "", "finding_a_fix": "", + "finish": "", "first_name": "", "fit_to_height": "", "fit_to_width": "", @@ -848,7 +851,6 @@ "imported_from_zotero_at_date": "", "importing": "", "importing_and_merging_changes_in_github": "", - "improved_dark_mode": "", "in_order_to_match_institutional_metadata_2": "", "in_order_to_match_institutional_metadata_associated": "", "include_caption": "", @@ -887,6 +889,7 @@ "integrations": "", "integrations_like_github": "", "interested_in_cheaper_personal_plan": "", + "introducing_overleafs_new_look": "", "invalid_confirmation_code": "", "invalid_email": "", "invalid_file_name": "", @@ -1112,15 +1115,11 @@ "new_compile_domain_notice": "", "new_compiles_in_this_project_will_automatically_use_the_newest_version": "", "new_create_tables_and_equations": "", - "new_editor": "", - "new_editor_experience": "", - "new_editor_info": "", + "new_editor_look": "", + "new_error_logs_make_it_easier_to_find_whats_wrong": "", "new_file": "", "new_folder": "", - "new_look_and_feel": "", - "new_look_and_placement_of_the_settings": "", "new_name": "", - "new_navigation_introducing_left_hand_side_rail_and_top_menus": "", "new_password": "", "new_project": "", "new_subscription_will_be_billed_immediately": "", @@ -1165,6 +1164,7 @@ "not_a_student": "", "not_managed": "", "not_now": "", + "not_sure_about_switching_yet": "", "notification": "", "notification_personal_and_group_subscriptions": "", "notification_project_invite_accepted_message": "", @@ -1217,6 +1217,7 @@ "overleaf_labs": "", "overleaf_logo": "", "overleafs_functionality_meets_my_needs": "", + "overleafs_new_look_is_here": "", "overview": "", "overwrite": "", "overwriting_the_original_folder": "", @@ -1397,6 +1398,7 @@ "read_lines_from_path": "", "read_more": "", "read_more_about_managed_users": "", + "read_more_about_the_new_editor": "", "read_only_dropbox_sync_message": "", "read_only_token": "", "read_write_token": "", @@ -1504,7 +1506,6 @@ "revert_pending_plan_change": "", "review": "", "review_panel": "", - "review_panel_and_error_logs_moved_to_the_left": "", "reviewer": "", "reviewer_dropbox_sync_message": "", "reviewing": "", @@ -1621,6 +1622,7 @@ "setup_another_account_under_a_personal_email_address": "", "share": "", "share_feedback": "", + "share_feedback_on_the_new_editor": "", "share_project": "", "shared_with_you": "", "sharelatex_beta_program": "", @@ -1648,6 +1650,7 @@ "showing_x_results_of_total": "", "sign_up": "", "simple_search_mode": "", + "simplified_working_starts_here": "", "single_sign_on_sso": "", "size": "", "something_not_right": "", @@ -1775,10 +1778,10 @@ "sure_you_want_to_change_plan": "", "sure_you_want_to_delete": "", "sure_you_want_to_leave_group": "", + "switch_between_dark_and_light_mode": "", "switch_compile_mode_for_faster_draft_compilation": "", + "switch_easily_between_your_files_comments_track_changes_and_more": "", "switch_to_editor": "", - "switch_to_new_editor": "", - "switch_to_old_editor": "", "switch_to_pdf": "", "switch_to_personal_email_to_keep_your_accounts_separate": "", "switch_to_standard_plan": "", @@ -1836,7 +1839,7 @@ "the_following_folder_already_exists_in_this_project": "", "the_following_folder_already_exists_in_this_project_plural": "", "the_latex_engine_used_for_compiling": "", - "the_new_overleaf_editor_try_now_in_beta": "", + "the_new_overleaf_editor_info": "", "the_next_payment_will_be_collected_on": "", "the_original_text_has_changed": "", "the_overleaf_color_scheme": "", @@ -1867,7 +1870,6 @@ "this_experiment_isnt_accepting_new_participants": "", "this_field_is_required": "", "this_grants_access_to_features_2": "", - "this_is_a_beta_release_for_the_new_overleaf_editor": "", "this_is_a_new_feature": "", "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "", "this_organization_is_tax_exempt": "", @@ -1996,10 +1998,12 @@ "try_for_free": "", "try_it_for_free": "", "try_now": "", + "try_out_the_new_editor_now": "", "try_premium_for_free": "", "try_recompile_project_or_troubleshoot": "", "try_relinking_provider": "", - "try_the_new_editor": "", + "try_the_new_editor_design": "", + "try_the_new_look": "", "try_to_compile_despite_errors": "", "turn_off": "", "turn_off_link_sharing": "", @@ -2159,13 +2163,12 @@ "well_be_here_when_youre_ready": "", "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "", "were_performing_maintenance": "", - "weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready": "", + "weve_made_it_easier_to_find_and_use_the_tools_you_need_today": "", "what_did_you_find_most_helpful": "", "what_do_you_need_help_with": "", "what_does_this_mean_for_you": "", "what_happens_when_sso_is_enabled": "", "what_should_we_call_you": "", - "whats_different": "", "when_you_tick_the_include_caption_box": "", "why_not_pause_instead": "", "wide": "", @@ -2215,7 +2218,6 @@ "you_can_select_or_invite_collaborator": "", "you_can_select_or_invite_collaborator_plural": "", "you_can_still_use_your_premium_features": "", - "you_can_switch_back_to_the_old_editor_at_any_time": "", "you_cant_add_or_change_password_due_to_sso": "", "you_cant_join_this_group_subscription": "", "you_dont_have_any_add_ons_on_your_account": "", 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 index 59a377f03c..ea3b86fa06 100644 --- 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 @@ -4,22 +4,27 @@ import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use import { useLayoutContext } from '@/shared/context/layout-context' import { useCallback } from 'react' import { - canUseNewEditorAsNewUser, + canUseNewEditor, useIsNewEditorEnabled, } from '@/features/ide-redesign/utils/new-editor-utils' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' export default function SettingsNewEditor() { const { t } = useTranslation() const { setEditorRedesignStatus } = useSwitchEnableNewEditorState() const { setLeftMenuShown } = useLayoutContext() const enabled = useIsNewEditorEnabled() - const show = canUseNewEditorAsNewUser() + const show = canUseNewEditor() + const { sendEvent } = useEditorAnalytics() const onChange = useCallback( (newValue: boolean) => { + sendEvent('switch-to-new-editor', { + location: 'left-menu', + }) setEditorRedesignStatus(newValue).then(() => setLeftMenuShown(false)) }, - [setEditorRedesignStatus, setLeftMenuShown] + [setEditorRedesignStatus, setLeftMenuShown, sendEvent] ) if (!show) { @@ -40,7 +45,7 @@ export default function SettingsNewEditor() { label: t('off'), }, ]} - label={t('new_editor')} + label={t('new_editor_look')} name="new-editor-setting" /> ) 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 7bfdc034de..8a835ab2a1 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 @@ -1,34 +1,33 @@ 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 { canUseNewEditorAsExistingUser } from '../ide-redesign/utils/new-editor-utils' import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state' +import MaterialIcon from '@/shared/components/material-icon' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' const TryNewEditorButton = () => { const { t } = useTranslation() - const { setShowSwitcherModal } = useIdeRedesignSwitcherContext() - const showModal = canUseNewEditorAsExistingUser() const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState() + const { sendEvent } = useEditorAnalytics() const onClick = useCallback(() => { - if (showModal) { - setShowSwitcherModal(true) - } else { - setEditorRedesignStatus(true) - } - }, [setShowSwitcherModal, showModal, setEditorRedesignStatus]) + sendEvent('switch-to-new-editor', { + location: 'toolbar', + }) + setEditorRedesignStatus(true) + }, [setEditorRedesignStatus, sendEvent]) return (
- {t('try_the_new_editor')} + + {t('try_the_new_editor_design')}
) diff --git a/services/web/frontend/js/features/ide-react/components/modals/modals.tsx b/services/web/frontend/js/features/ide-react/components/modals/modals.tsx index 0272331458..b71447dad5 100644 --- a/services/web/frontend/js/features/ide-react/components/modals/modals.tsx +++ b/services/web/frontend/js/features/ide-react/components/modals/modals.tsx @@ -2,10 +2,8 @@ import { memo } from 'react' import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected' import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs' import SystemMessages from '@/shared/components/system-messages' -import { - IdeRedesignSwitcherModal, - IdeRedesignIntroModal, -} from '@/features/ide-redesign/components/switcher-modal/beta-modal' +import NewEditorPromoModal from '@/features/ide-redesign/components/new-editor-promo-modal' +import NewEditorIntroModal from '@/features/ide-redesign/components/new-editor-intro-modal' export const Modals = memo(() => { return ( @@ -13,8 +11,8 @@ export const Modals = memo(() => { - - + + ) }) diff --git a/services/web/frontend/js/features/ide-react/context/ide-redesign-switcher-context.tsx b/services/web/frontend/js/features/ide-react/context/ide-redesign-switcher-context.tsx deleted file mode 100644 index 6eb8cd6d0b..0000000000 --- a/services/web/frontend/js/features/ide-react/context/ide-redesign-switcher-context.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { - createContext, - Dispatch, - FC, - SetStateAction, - useContext, - useState, -} from 'react' - -type IdeRedesignSwitcherContextValue = { - showSwitcherModal: boolean - setShowSwitcherModal: Dispatch> -} - -export const IdeRedesignSwitcherContext = createContext< - IdeRedesignSwitcherContextValue | undefined ->(undefined) - -export const IdeRedesignSwitcherProvider: FC = ({ - children, -}) => { - const [showSwitcherModal, setShowSwitcherModal] = useState(false) - - return ( - - {children} - - ) -} - -export const useIdeRedesignSwitcherContext = () => { - const context = useContext(IdeRedesignSwitcherContext) - if (!context) { - throw new Error( - 'useIdeRedesignSwitcherContext is only available inside IdeRedesignSwitcherProvider' - ) - } - return context -} diff --git a/services/web/frontend/js/features/ide-react/context/react-context-root.tsx b/services/web/frontend/js/features/ide-react/context/react-context-root.tsx index 1f5d6fba7d..febe2956c9 100644 --- a/services/web/frontend/js/features/ide-react/context/react-context-root.tsx +++ b/services/web/frontend/js/features/ide-react/context/react-context-root.tsx @@ -28,8 +28,8 @@ import { SplitTestProvider } from '@/shared/context/split-test-context' import { UserProvider } from '@/shared/context/user-context' import { UserFeaturesProvider } from '@/shared/context/user-features-context' import { UserSettingsProvider } from '@/shared/context/user-settings-context' -import { IdeRedesignSwitcherProvider } from './ide-redesign-switcher-context' import { CommandRegistryProvider } from './command-registry-context' +import { NewEditorTourProvider } from '@/features/ide-redesign/contexts/new-editor-tour-context' import { EditorSelectionProvider } from '@/shared/context/editor-selection-context' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' @@ -72,9 +72,9 @@ export const ReactContextRoot: FC< SplitTestProvider, UserProvider, UserSettingsProvider, - IdeRedesignSwitcherProvider, CommandRegistryProvider, UserFeaturesProvider, + NewEditorTourProvider, EditorSelectionProvider, ...providers, } @@ -111,17 +111,17 @@ export const ReactContextRoot: FC< - - - - - - - - - - - + + + + + + + + + + + { @@ -129,17 +129,17 @@ export const ReactContextRoot: FC< } - - - - - - - - - - - + + + + + + + + + + + diff --git a/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-logs-tooltip.tsx b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-logs-tooltip.tsx new file mode 100644 index 0000000000..6d22656d0d --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-logs-tooltip.tsx @@ -0,0 +1,21 @@ +import { useTranslation } from 'react-i18next' +import EditorTourTooltip from './editor-tour-tooltip' + +export default function EditorTourLogsTooltip({ + target, +}: { + target: HTMLElement | null +}) { + const { t } = useTranslation() + + return ( + + {t('new_error_logs_make_it_easier_to_find_whats_wrong')} + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-rail-tooltip.tsx b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-rail-tooltip.tsx new file mode 100644 index 0000000000..85fae1055b --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-rail-tooltip.tsx @@ -0,0 +1,21 @@ +import { useTranslation } from 'react-i18next' +import EditorTourTooltip from './editor-tour-tooltip' + +export default function EditorTourRailTooltip({ + target, +}: { + target: HTMLElement | null +}) { + const { t } = useTranslation() + + return ( + + {t('switch_easily_between_your_files_comments_track_changes_and_more')} + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-switch-back-tooltip.tsx b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-switch-back-tooltip.tsx new file mode 100644 index 0000000000..805065a1d3 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-switch-back-tooltip.tsx @@ -0,0 +1,33 @@ +import { Trans, useTranslation } from 'react-i18next' +import EditorTourTooltip from './editor-tour-tooltip' + +export default function EditorTourSwitchBackTooltip({ + target, +}: { + target: HTMLElement | null +}) { + const { t } = useTranslation() + + return ( + + , + , + ]} + /> + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-theme-tooltip.tsx b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-theme-tooltip.tsx new file mode 100644 index 0000000000..1c8adadad1 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-theme-tooltip.tsx @@ -0,0 +1,24 @@ +import { Trans, useTranslation } from 'react-i18next' +import EditorTourTooltip from './editor-tour-tooltip' + +export default function EditorTourThemeTooltip({ + target, +}: { + target: HTMLElement | null +}) { + const { t } = useTranslation() + + return ( + + }} + /> + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-tooltip.tsx b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-tooltip.tsx new file mode 100644 index 0000000000..2c63a2c51d --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/editor-tour/editor-tour-tooltip.tsx @@ -0,0 +1,77 @@ +import { Overlay, OverlayProps, Popover } from 'react-bootstrap' +import { + NewEditorTourStage, + useNewEditorTourContext, +} from '../../contexts/new-editor-tour-context' +import Close from '@/shared/components/close' +import OLButton from '@/shared/components/ol/ol-button' +import { useTranslation } from 'react-i18next' +import { useEffect } from 'react' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' + +export default function EditorTourTooltip({ + children, + target, + header, + stage, + placement, +}: { + children: React.ReactNode + target: HTMLElement | null + header: string + stage: NewEditorTourStage + placement?: OverlayProps['placement'] +}) { + const { t } = useTranslation() + const { + shouldShowTourStage, + dismissTour, + goToNextStage, + stageNumber, + totalStages, + finishTour, + } = useNewEditorTourContext() + const { sendEvent } = useEditorAnalytics() + + const show = shouldShowTourStage(stage) + + useEffect(() => { + if (show) { + sendEvent('new-editor-tour-shown', { stage: stageNumber }) + } + }, [show, stageNumber, sendEvent]) + + const isFinalStage = stageNumber === totalStages + + if (!show) { + return null + } + + return ( + + + + {header} + + + + {children} +
+
+ {stageNumber}/{totalStages} +
+ {isFinalStage ? ( + + {t('finish')} + + ) : ( + + {t('next')} + + )} +
+
+
+
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx b/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx index effff8f2c8..4e2436c283 100644 --- a/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx @@ -31,7 +31,6 @@ export default function MainLayout() { pdfIsOpen: isPdfOpen, pdfPanelRef, } = usePdfPane() - const { view, pdfLayout } = useLayoutContext() const editorIsOpen = diff --git a/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx new file mode 100644 index 0000000000..bf722d369f --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx @@ -0,0 +1,80 @@ +import { + OLModal, + OLModalBody, + OLModalFooter, + OLModalHeader, + OLModalTitle, +} from '@/shared/components/ol/ol-modal' +import { useCallback, useEffect, useState } from 'react' +import useTutorial from '@/shared/hooks/promotions/use-tutorial' +import OLButton from '@/shared/components/ol/ol-button' +import { useTranslation } from 'react-i18next' +import { useEditorContext } from '@/shared/context/editor-context' +import { useIsNewEditorEnabledAsExistingUser } from '../utils/new-editor-utils' +import { useNewEditorTourContext } from '../contexts/new-editor-tour-context' +import promoVideo from './new-editor-promo-video.mp4' + +const TUTORIAL_KEY = 'new-editor-intro' + +export default function NewEditorIntroModal() { + const { inactiveTutorials } = useEditorContext() + const { + tryShowingPopup, + showPopup: showModal, + dismissTutorial, + completeTutorial, + clearPopup, + } = useTutorial(TUTORIAL_KEY, { + name: TUTORIAL_KEY, + }) + const { startTour } = useNewEditorTourContext() + + const { t } = useTranslation() + + const canShow = useIsNewEditorEnabledAsExistingUser() + const [hasShown, setHasShown] = useState(false) + + useEffect(() => { + if (canShow && !hasShown && !inactiveTutorials.includes(TUTORIAL_KEY)) { + tryShowingPopup('notification-prompt') + setHasShown(true) + } + }, [tryShowingPopup, inactiveTutorials, canShow, hasShown]) + + const startProductTour = useCallback(() => { + completeTutorial({ event: 'notification-click', action: 'complete' }) + startTour() + clearPopup() + }, [completeTutorial, startTour, clearPopup]) + + const closeModal = useCallback(() => { + dismissTutorial('notification-dismiss') + clearPopup() + }, [dismissTutorial, clearPopup]) + + if (!canShow) { + return null + } + + return ( + + + {t('introducing_overleafs_new_look')} + + + {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + +
+ {t('weve_made_it_easier_to_find_and_use_the_tools_you_need_today')} +
+
+ + + {t('explore_what_s_new')} + + +
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx new file mode 100644 index 0000000000..573b899908 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx @@ -0,0 +1,102 @@ +import { + OLModal, + OLModalBody, + OLModalFooter, + OLModalHeader, + OLModalTitle, +} from '@/shared/components/ol/ol-modal' +import { useSwitchEnableNewEditorState } from '../hooks/use-switch-enable-new-editor-state' +import { useCallback, useEffect, useState } from 'react' +import useTutorial from '@/shared/hooks/promotions/use-tutorial' +import OLButton from '@/shared/components/ol/ol-button' +import { Trans, useTranslation } from 'react-i18next' +import { useEditorContext } from '@/shared/context/editor-context' +import { + canUseNewEditorAsExistingUser, + useIsNewEditorEnabled, +} from '../utils/new-editor-utils' +import promoVideo from './new-editor-promo-video.mp4' + +const TUTORIAL_KEY = 'new-editor-opt-in' + +export default function NewEditorPromoModal() { + const { inactiveTutorials } = useEditorContext() + const { + tryShowingPopup, + showPopup: showModal, + dismissTutorial, + completeTutorial, + clearPopup, + } = useTutorial(TUTORIAL_KEY, { + name: TUTORIAL_KEY, + }) + const { setEditorRedesignStatus } = useSwitchEnableNewEditorState() + const { t } = useTranslation() + + const newEditor = useIsNewEditorEnabled() + const canShow = canUseNewEditorAsExistingUser() && !newEditor + const [hasShown, setHasShown] = useState(false) + + useEffect(() => { + if (canShow && !hasShown && !inactiveTutorials.includes(TUTORIAL_KEY)) { + tryShowingPopup('notification-prompt') + setHasShown(true) + } + }, [tryShowingPopup, inactiveTutorials, canShow, hasShown]) + + const switchToNewEditor = useCallback(() => { + setEditorRedesignStatus(true) + completeTutorial({ event: 'notification-click', action: 'complete' }) + clearPopup() + }, [setEditorRedesignStatus, completeTutorial, clearPopup]) + + const closeModal = useCallback(() => { + dismissTutorial('notification-dismiss') + clearPopup() + }, [dismissTutorial, clearPopup]) + + if (!canShow) { + return null + } + + return ( + + + {t('overleafs_new_look_is_here')} + + +
+ {t( + 'be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor' + )} +
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + +
+ , + ]} + /> +
+
+ + + {t('not_now')} + + + {t('try_the_new_look')} + + +
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-video.mp4 b/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-video.mp4 new file mode 100644 index 0000000000..0a6e8af7ee Binary files /dev/null and b/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-video.mp4 differ diff --git a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx index 654b32c1f1..ccaeeda527 100644 --- a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react' +import { memo, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import OLButtonToolbar from '@/shared/components/ol/ol-button-toolbar' import PdfCompileButton from '@/features/pdf-preview/components/pdf-compile-button' @@ -6,10 +6,18 @@ import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybri import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control' import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-editor-button' import PdfHybridLogsButton from '@/features/pdf-preview/components/pdf-hybrid-logs-button' +import EditorTourLogsTooltip from '../editor-tour/editor-tour-logs-tooltip' function PdfPreviewHybridToolbar() { const { t } = useTranslation() + const [logsButtonElt, setLogsButtonElt] = useState(null) + const logsButtonRef = useCallback((node: HTMLButtonElement) => { + if (node !== null) { + setLogsButtonElt(node) + } + }, []) + // TODO: add detached pdf logic return (
- + +
diff --git a/services/web/frontend/js/features/ide-redesign/components/rail/rail-action-element.tsx b/services/web/frontend/js/features/ide-redesign/components/rail/rail-action-element.tsx index 74cc861496..76ecb4c610 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail/rail-action-element.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail/rail-action-element.tsx @@ -1,12 +1,13 @@ import MaterialIcon, { AvailableUnfilledIcon, } from '@/shared/components/material-icon' -import { ReactElement, useCallback } from 'react' +import { forwardRef, ReactElement, useCallback } from 'react' import { Dropdown, DropdownToggle, } from '@/shared/components/dropdown/dropdown-menu' import OLTooltip from '@/shared/components/ol/ol-tooltip' +import { DropdownToggle as BS5DropdownToggle } from 'react-bootstrap' type RailActionButton = { key: string @@ -14,6 +15,7 @@ type RailActionButton = { title: string action: () => void hide?: boolean + ref?: React.Ref } type RailDropdown = { @@ -22,66 +24,75 @@ type RailDropdown = { title: string dropdown: ReactElement hide?: boolean + ref?: React.Ref } export type RailAction = RailDropdown | RailActionButton -export default function RailActionElement({ action }: { action: RailAction }) { - const onActionClick = useCallback(() => { - if ('action' in action) { - action.action() +const RailActionElement = forwardRef( + ({ action }, ref) => { + const onActionClick = useCallback(() => { + if ('action' in action) { + action.action() + } + }, [action]) + + if (action.hide) { + return null } - }, [action]) - if (action.hide) { - return null - } - - if ('dropdown' in action) { - return ( - + if ('dropdown' in action) { + return ( + + + + } + id={`rail-dropdown-btn-${action.key}`} + className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown" + as="button" + aria-label={action.title} + > + + + + + {action.dropdown} + + ) + } else { + return ( - - - - - + - {action.dropdown} - - ) - } else { - return ( - - - - ) + ) + } } -} +) + +RailActionElement.displayName = 'RailActionElement' + +export default RailActionElement diff --git a/services/web/frontend/js/features/ide-redesign/components/rail/rail-help-dropdown.tsx b/services/web/frontend/js/features/ide-redesign/components/rail/rail-help-dropdown.tsx index 7aefb51ea9..2e376d3212 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail/rail-help-dropdown.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail/rail-help-dropdown.tsx @@ -2,7 +2,6 @@ import getMeta from '@/utils/meta' import { useTranslation } from 'react-i18next' import { useRailContext } from '../../contexts/rail-context' import { useCallback } from 'react' -import { useSurveyUrl } from '../../hooks/use-survey-url' import { DropdownDivider, DropdownItem, @@ -19,7 +18,6 @@ export default function RailHelpDropdown() { const openContactUsModal = useCallback(() => { setActiveModal('contact-us') }, [setActiveModal]) - const surveyURL = useSurveyUrl() return ( @@ -40,14 +38,6 @@ export default function RailHelpDropdown() { {t('contact_us')} )} - - {t('give_feedback')} - ) } diff --git a/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx index 6fc4e0b006..4e04667bc6 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Nav, TabContainer } from 'react-bootstrap' import { useLayoutContext } from '@/shared/context/layout-context' @@ -26,7 +26,10 @@ import RailResizeHandle from './rail-resize-handle' import RailModals from './rail-modals' import RailOverflowDropdown from './rail-overflow-dropdown' import useRailOverflow from '../../hooks/use-rail-overflow' +import EditorTourRailTooltip from '../editor-tour/editor-tour-rail-tooltip' import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' +import EditorTourThemeTooltip from '../editor-tour/editor-tour-theme-tooltip' +import EditorTourSwitchBackTooltip from '../editor-tour/editor-tour-switch-back-tooltip' const moduleRailEntries = ( importOverleafModules('railEntries') as { @@ -47,6 +50,9 @@ export const RailLayout = () => { const isHistoryView = view === 'history' + const fileTreeRef = useRef(null) + const settingsRef = useRef(null) + const railTabs: RailElement[] = useMemo( () => [ { @@ -57,6 +63,7 @@ export const RailLayout = () => { // NOTE: We always need to mount the file tree on first load // since it is responsible for opening the initial document. mountOnFirstLoad: true, + ref: fileTreeRef, }, { key: 'full-project-search', @@ -108,6 +115,7 @@ export const RailLayout = () => { sendEvent('rail-click', { tab: 'settings' }) setLeftMenuShown(true) }, + ref: settingsRef, }, ], [setLeftMenuShown, t, sendEvent] @@ -219,7 +227,7 @@ export const RailLayout = () => { .filter(({ hide }) => typeof hide === 'function' ? !hide() : !hide ) - .map(({ icon, key, indicator, title, disabled }) => ( + .map(({ icon, key, indicator, title, disabled, ref }) => ( { indicator={indicator} title={title} disabled={disabled} + ref={ref} /> ))}
+ + + { + sendEvent('switch-to-old-editor', { location: 'settings-modal' }) setEditorRedesignStatus(!enabled).then(() => setLeftMenuShown(false)) - }, [enabled, setEditorRedesignStatus, setLeftMenuShown]) + }, [enabled, setEditorRedesignStatus, setLeftMenuShown, sendEvent]) return ( - {t('new_editor_experience')} -
{t('beta')}
-
+ label={t('new_editor_look')} + description={ + <> +
{t('the_new_overleaf_editor_info')}
+ + {t('share_feedback_on_the_new_editor')} + + } - 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 088bb39cac..82ff0986e5 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 @@ -7,7 +7,7 @@ export default function Setting({ description = undefined, }: { label: React.ReactNode - description: string | undefined + description: React.ReactNode | undefined controlId: string children: React.ReactNode }) { 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 da367f309b..958f0d92dc 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 @@ -12,7 +12,7 @@ export default function ToggleSetting({ }: { id: string label: React.ReactNode - description: string + description: React.ReactNode checked: boolean | undefined onChange: (newValue: boolean) => void disabled?: boolean diff --git a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx deleted file mode 100644 index 43dad33c8b..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/beta-modal.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context' -import OLButton from '@/shared/components/ol/ol-button' -import { - OLModal, - OLModalBody, - OLModalFooter, - OLModalHeader, - OLModalTitle, -} from '@/shared/components/ol/ol-modal' -import { FC, useCallback, useEffect } from 'react' -import { - canUseNewEditor, - useIsNewEditorEnabled, - useIsNewEditorEnabledAsExistingUser, -} from '../../utils/new-editor-utils' -import Notification from '@/shared/components/notification' -import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state' -import { useTranslation } from 'react-i18next' -import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' -import { useSurveyUrl } from '../../hooks/use-survey-url' -import useTutorial from '@/shared/hooks/promotions/use-tutorial' -import { useEditorContext } from '@/shared/context/editor-context' - -const TUTORIAL_KEY = 'ide-redesign-beta-intro' - -export const IdeRedesignIntroModal: FC = () => { - const { t } = useTranslation() - const { inactiveTutorials } = useEditorContext() - const { showPopup, tryShowingPopup, dismissTutorial } = useTutorial( - TUTORIAL_KEY, - { - name: TUTORIAL_KEY, - } - ) - const hasAccess = useIsNewEditorEnabledAsExistingUser() - - useEffect(() => { - if (!hasAccess) return - if (!inactiveTutorials.includes(TUTORIAL_KEY)) { - tryShowingPopup() - } - }, [tryShowingPopup, inactiveTutorials, hasAccess]) - - if (!hasAccess) { - return null - } - - return ( - - - - {t('the_new_overleaf_editor_try_now_in_beta')} - - - -

- {t( - 'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready' - )}{' '} - {t('you_can_switch_back_to_the_old_editor_at_any_time')} -

- -
- - - {t('close')} - - -
- ) -} - -export const IdeRedesignSwitcherModal = () => { - const { t } = useTranslation() - const { showSwitcherModal, setShowSwitcherModal } = - useIdeRedesignSwitcherContext() - const onHide = useCallback( - () => setShowSwitcherModal(false), - [setShowSwitcherModal] - ) - const { loading, error, setEditorRedesignStatus } = - useSwitchEnableNewEditorState() - const enabled = useIsNewEditorEnabled() - const hasAccess = canUseNewEditor() - if (!hasAccess) { - return null - } - - const Content = enabled - ? SwitcherModalContentEnabled - : SwitcherModalContentDisabled - - return ( - - - - {enabled - ? t('beta_program_the_new_overleaf_editor') - : t('the_new_overleaf_editor_try_now_in_beta')} - - - {error && } - - - ) -} - -type ModalContentProps = { - setEditorRedesignStatus: (enabled: boolean) => Promise - hide: () => void - loading: boolean -} - -const SwitcherModalContentEnabled: FC = ({ - setEditorRedesignStatus, - hide, - loading, -}) => { - const { t } = useTranslation() - const { sendEvent } = useEditorAnalytics() - const disable = useCallback(() => { - sendEvent('editor-redesign-toggle', { - action: 'disable', - location: 'modal', - }) - setEditorRedesignStatus(false) - .then(hide) - .catch(() => { - // do nothing, we're already showing the error - }) - }, [setEditorRedesignStatus, hide, sendEvent]) - - const surveyURL = useSurveyUrl() - - return ( - <> - -

- {t( - 'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready' - )} -

- -
- - - {t('switch_to_old_editor')} - - - {t('cancel')} - - - {t('give_feedback')} - - - - ) -} - -const SwitcherModalContentDisabled: FC = ({ - setEditorRedesignStatus, - hide, - loading, -}) => { - const { t } = useTranslation() - const { sendEvent } = useEditorAnalytics() - const enable = useCallback(() => { - sendEvent('editor-redesign-toggle', { - action: 'enable', - location: 'modal', - }) - setEditorRedesignStatus(true) - .then(hide) - .catch(() => { - // do nothing, we're already showing the error - }) - }, [setEditorRedesignStatus, hide, sendEvent]) - return ( - <> - -

- {t( - 'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready' - )} -

- - -
- - - {t('cancel')} - - - {t('switch_to_new_editor')} - - - - ) -} - -const SwitcherWhatsDifferent = () => { - const { t } = useTranslation() - - return ( -
-

{t('whats_different')}

-
    -
  • {t('new_look_and_feel')}
  • -
  • - {t('new_navigation_introducing_left_hand_side_rail_and_top_menus')} -
  • -
  • {t('new_look_and_placement_of_the_settings')}
  • -
  • {t('improved_dark_mode')}
  • -
  • {t('review_panel_and_error_logs_moved_to_the_left')}
  • -
-
- ) -} - -const LeavingNote = () => { - const { t } = useTranslation() - - return

{t('you_can_switch_back_to_the_old_editor_at_any_time')}

-} 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 deleted file mode 100644 index a45f33ad92..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/beta-actions.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context' -import OLButton from '@/shared/components/ol/ol-button' -import OLTooltip from '@/shared/components/ol/ol-tooltip' -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import { GiveFeedbackLink } from './give-feedback-link' -import { useIsNewEditorEnabledAsExistingUser } from '../../utils/new-editor-utils' - -export const BetaActions = () => { - const { t } = useTranslation() - const { setShowSwitcherModal } = useIdeRedesignSwitcherContext() - const openEditorRedesignSwitcherModal = useCallback(() => { - setShowSwitcherModal(true) - }, [setShowSwitcherModal]) - const showBetaActions = useIsNewEditorEnabledAsExistingUser() - - if (!showBetaActions) { - return null - } - - return ( - <> -
- - - {t('beta')} - - -
- - - ) -} diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/give-feedback-link.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/give-feedback-link.tsx deleted file mode 100644 index 74019485a3..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/give-feedback-link.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useSurveyUrl } from '../../hooks/use-survey-url' - -export const GiveFeedbackLink = () => { - const { t } = useTranslation() - const surveyURL = useSurveyUrl() - - 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 7669230745..51dc92eae8 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 @@ -8,11 +8,7 @@ import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown' import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option' import { useTranslation } from 'react-i18next' import ChangeLayoutOptions from './change-layout-options' -import { MouseEventHandler, useCallback, useMemo, useState } from 'react' -import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context' -import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state' -import MaterialIcon from '@/shared/components/material-icon' -import OLSpinner from '@/shared/components/ol/ol-spinner' +import { useCallback, useMemo, useState } from 'react' import { useLayoutContext } from '@/shared/context/layout-context' import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider' import CommandDropdown, { @@ -24,27 +20,20 @@ import { useRailContext } from '../../contexts/rail-context' import WordCountModal from '@/features/word-count-modal/components/word-count-modal' import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' -import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context' -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 { canUseNewEditorAsExistingUser } from '../../utils/new-editor-utils' export const ToolbarMenuBar = () => { const { t } = useTranslation() - const { setShowSwitcherModal } = useIdeRedesignSwitcherContext() - const openEditorRedesignSwitcherModal = useCallback(() => { - setShowSwitcherModal(true) - }, [setShowSwitcherModal]) + const { setView, view } = useLayoutContext() const { pdfUrl } = useCompileContext() const wordCountEnabled = pdfUrl || isSplitTestEnabled('word-count-client') const [showWordCountModal, setShowWordCountModal] = useState(false) const [showCloneProjectModal, setShowCloneProjectModal] = useState(false) const openProject = useOpenProject() - const showEditorSwitchMenuOption = canUseNewEditorAsExistingUser() const anonymous = getMeta('ol-anonymous') @@ -213,8 +202,6 @@ export const ToolbarMenuBar = () => { setActiveModal('contact-us') }, [setActiveModal]) - const surveyURL = useSurveyUrl() - return ( <> { title={t('contact_us')} onClick={openContactUsModal} /> - {showEditorSwitchMenuOption && ( - <> - - - - - - )} { ) } - -const SwitchToOldEditorMenuBarOption = () => { - const { loading, error, setEditorRedesignStatus } = - useSwitchEnableNewEditorState() - const { sendEvent } = useEditorAnalytics() - - const disable: MouseEventHandler = useCallback( - event => { - // Don't close the dropdown - event.stopPropagation() - sendEvent('editor-redesign-toggle', { - action: 'disable', - location: 'menu-bar', - }) - setEditorRedesignStatus(false) - }, - [setEditorRedesignStatus, sendEvent] - ) - let icon = null - if (loading) { - icon = - } else if (error) { - icon = - } - return ( - - ) -} diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx index a885a44f01..ee44004bcd 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx @@ -15,7 +15,6 @@ import importOverleafModules from '../../../../../macros/import-overleaf-module. import UpgradeButton from './upgrade-button' import getMeta from '@/utils/meta' import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context' -import { BetaActions } from './beta-actions' const [publishModalModules] = importOverleafModules('publishModal') const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton @@ -54,7 +53,6 @@ export const Toolbar = () => {
- {!isRestrictedTokenMember && } diff --git a/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx b/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx index 0ae88bf0bd..b5c43c9859 100644 --- a/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx @@ -44,6 +44,10 @@ export default function TooltipPromotion({ hideUntilReload() }, [hideUntilReload]) + const onDismiss = useCallback(() => { + dismissTutorial() + }, [dismissTutorial]) + if (!target || !isInSplitTestIfNeeded) { return null } @@ -60,13 +64,13 @@ export default function TooltipPromotion({ {header && ( {header} - + )} {content} - {!header && } + {!header && } diff --git a/services/web/frontend/js/features/ide-redesign/contexts/new-editor-tour-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/new-editor-tour-context.tsx new file mode 100644 index 0000000000..5ac5c14979 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/contexts/new-editor-tour-context.tsx @@ -0,0 +1,114 @@ +import { useLayoutContext } from '@/shared/context/layout-context' +import { + createContext, + FC, + useCallback, + useContext, + useMemo, + useState, +} from 'react' + +export type NewEditorTourStage = 'rail' | 'logs' | 'theme' | 'switch-back' + +const NewEditorTourContext = createContext< + | { + stage: NewEditorTourStage + stageNumber: number + totalStages: number + shouldShowTourStage: (tourStage: NewEditorTourStage) => boolean + startTour: () => void + goToNextStage: () => void + finishTour: () => void + dismissTour: () => void + } + | undefined +>(undefined) + +const STAGES: NewEditorTourStage[] = ['rail', 'logs', 'theme', 'switch-back'] +const EDITOR_ONLY_STAGES: NewEditorTourStage[] = [ + 'rail', + 'theme', + 'switch-back', +] + +export const NewEditorTourProvider: FC = ({ + children, +}) => { + const [stage, setStage] = useState('rail') + const [showTour, setShowTour] = useState(false) + const { view, pdfLayout } = useLayoutContext() + const pdfIsOpen = pdfLayout === 'sideBySide' || view === 'pdf' + + const stagesToShow = useMemo( + () => (pdfIsOpen ? STAGES : EDITOR_ONLY_STAGES), + [pdfIsOpen] + ) + + const startTour = useCallback(() => { + setShowTour(true) + }, []) + + const stageNumber = useMemo( + () => stagesToShow.indexOf(stage) + 1, + [stage, stagesToShow] + ) + const totalStages = stagesToShow.length + + const goToNextStage = useCallback(() => { + setStage(stagesToShow[stageNumber]) + }, [stageNumber, stagesToShow]) + + const dismissTour = useCallback(() => { + setShowTour(false) + }, []) + + const finishTour = useCallback(() => { + setShowTour(false) + }, []) + + const shouldShowTourStage = useCallback( + (tourStage: NewEditorTourStage) => { + return showTour && stage === tourStage + }, + [showTour, stage] + ) + + const value = useMemo( + () => ({ + stage, + stageNumber, + totalStages, + shouldShowTourStage, + startTour, + goToNextStage, + finishTour, + dismissTour, + }), + [ + stage, + stageNumber, + totalStages, + shouldShowTourStage, + startTour, + goToNextStage, + finishTour, + dismissTour, + ] + ) + + return ( + + {children} + + ) +} + +export const useNewEditorTourContext = () => { + const context = useContext(NewEditorTourContext) + if (!context) { + throw new Error( + 'useNewEditorTourContext is only available inside RailProvider' + ) + } + return context +} diff --git a/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx index 832d2ade5a..1f28163761 100644 --- a/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx +++ b/services/web/frontend/js/features/ide-redesign/contexts/settings-modal-context.tsx @@ -25,7 +25,6 @@ import FontFamilySetting from '../components/settings/appearance-settings/font-f 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 { canUseNewEditorAsNewUser } from '../utils/new-editor-utils' const [referenceSearchSettingModule] = importOverleafModules( 'referenceSearchSetting' @@ -77,7 +76,6 @@ export const SettingsModalProvider: FC = ({ children, }) => { const { t } = useTranslation() - const showEditorSwitch = canUseNewEditorAsNewUser() // TODO ide-redesign-cleanup: Rename this field and move it directly into this context const { leftMenuShown, setLeftMenuShown } = useLayoutContext() @@ -215,7 +213,6 @@ export const SettingsModalProvider: FC = ({ { key: 'newEditor', component: , - hidden: !showEditorSwitch, }, ], }, @@ -234,7 +231,7 @@ export const SettingsModalProvider: FC = ({ href: '/user/subscription', }, ], - [t, showEditorSwitch] + [t] ) const settingToTabMap = useMemo(() => { diff --git a/services/web/frontend/js/features/ide-redesign/hooks/use-survey-url.tsx b/services/web/frontend/js/features/ide-redesign/hooks/use-survey-url.tsx deleted file mode 100644 index 68233d8f76..0000000000 --- a/services/web/frontend/js/features/ide-redesign/hooks/use-survey-url.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useSplitTest } from '@/shared/context/split-test-context' - -export const useSurveyUrl = () => { - const splitTestConfig = useSplitTest('editor-redesign') - - return ( - splitTestConfig.info?.badgeInfo?.url || - 'https://forms.gle/NGkALNUiMbanjp3Q7' - ) -} diff --git a/services/web/frontend/js/features/ide-redesign/utils/rail-types.ts b/services/web/frontend/js/features/ide-redesign/utils/rail-types.ts index 694908b34a..9963027a52 100644 --- a/services/web/frontend/js/features/ide-redesign/utils/rail-types.ts +++ b/services/web/frontend/js/features/ide-redesign/utils/rail-types.ts @@ -11,4 +11,5 @@ export type RailElement = { hide?: boolean | (() => boolean) disabled?: boolean mountOnFirstLoad?: boolean + ref?: React.RefObject } diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx index 93f5c29a59..baf287f0e9 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback } from 'react' +import { forwardRef, memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' @@ -7,7 +7,7 @@ import OLTooltip from '@/shared/components/ol/ol-tooltip' import OLButton from '@/shared/components/ol/ol-button' import OLBadge from '@/shared/components/ol/ol-badge' -function PdfHybridLogsButton() { +const PdfHybridLogsButton = forwardRef((_, ref) => { const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } = useCompileContext() @@ -32,6 +32,7 @@ function PdfHybridLogsButton() { overlayProps={{ placement: 'bottom' }} > ) -} +}) + +PdfHybridLogsButton.displayName = 'PdfHybridLogsButton' export default memo(PdfHybridLogsButton) diff --git a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx index bb97d65780..b74a3ca736 100644 --- a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx +++ b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx @@ -19,7 +19,7 @@ const useTutorial = ( action = 'complete', ...rest }: { - event: 'promo-click' | 'promo-dismiss' + event: string action: 'complete' | 'postpone' } & Record) => { eventTracking.sendMB(event, { ...eventData, ...rest }) @@ -34,12 +34,15 @@ const useTutorial = ( [deactivateTutorial, eventData, tutorialKey] ) - const dismissTutorial = useCallback(async () => { - await completeTutorial({ - event: 'promo-dismiss', - action: 'complete', - }) - }, [completeTutorial]) + const dismissTutorial = useCallback( + async (eventName: string = 'promo-dismiss') => { + await completeTutorial({ + event: eventName, + action: 'complete', + }) + }, + [completeTutorial] + ) const maybeLater = useCallback(async () => { await completeTutorial({ diff --git a/services/web/frontend/stylesheets/pages/all.scss b/services/web/frontend/stylesheets/pages/all.scss index 4da6287685..623e0d3cb6 100644 --- a/services/web/frontend/stylesheets/pages/all.scss +++ b/services/web/frontend/stylesheets/pages/all.scss @@ -7,7 +7,6 @@ @import 'sidebar-v2-dash-pane'; @import 'editor/ide'; @import 'editor/ide-redesign'; -@import 'editor/ide-redesign-switcher-modal'; @import 'editor/rail'; @import 'editor/settings'; @import 'editor/toolbar'; @@ -36,6 +35,8 @@ @import 'editor/math-preview'; @import 'editor/references-search'; @import 'editor/editor-survey'; +@import 'editor/editor-tour-tooltip'; +@import 'editor/new-editor-promo-modal'; @import 'error-pages'; @import 'website-redesign'; @import 'group-settings'; diff --git a/services/web/frontend/stylesheets/pages/editor/editor-tour-tooltip.scss b/services/web/frontend/stylesheets/pages/editor/editor-tour-tooltip.scss new file mode 100644 index 0000000000..91f8801ecc --- /dev/null +++ b/services/web/frontend/stylesheets/pages/editor/editor-tour-tooltip.scss @@ -0,0 +1,43 @@ +.editor-tour-tooltip { + --bs-popover-bg: var(--bg-light-primary); + --bs-popover-header-bg: var(--bg-light-primary); + --bs-popover-body-color: var(--content-primary); + --bs-popover-header-color: var(--content-primary); + --editor-tour-tooltip-link-color: var(--link-ui); + --editor-tour-tooltip-link-hover-color: var(--link-ui-hover); + --editor-tour-tooltip-link-visited-color: var(--link-ui-visited); + + a { + color: var(--editor-tour-tooltip-link-color); + + &:visited { + color: var(--editor-tour-tooltip-link-hover-color); + } + + &:hover { + color: var(--editor-tour-tooltip-link-visited-color); + } + } +} + +@include theme('light') { + .editor-tour-tooltip { + --bs-popover-bg: var(--bg-dark-primary); + --bs-popover-header-bg: var(--bg-dark-primary); + --bs-popover-body-color: var(--content-primary-dark); + --bs-popover-header-color: var(--content-primary-dark); + --editor-tour-tooltip-link-color: var(--link-ui-dark); + --editor-tour-tooltip-link-hover-color: var(--link-ui-hover-dark); + --editor-tour-tooltip-link-visited-color: var(--link-ui-visited-dark); + } +} + +.editor-tour-tooltip-footer { + display: flex; + justify-content: space-between; + align-items: center; + + .btn-link { + color: var(--bs-popover-body-color); + } +} diff --git a/services/web/frontend/stylesheets/pages/editor/ide-redesign-switcher-modal.scss b/services/web/frontend/stylesheets/pages/editor/ide-redesign-switcher-modal.scss deleted file mode 100644 index 5603092fec..0000000000 --- a/services/web/frontend/stylesheets/pages/editor/ide-redesign-switcher-modal.scss +++ /dev/null @@ -1,32 +0,0 @@ -.ide-redesign-switcher-modal .modal-content { - color: var(--content-primary); - font-size: var(--font-size-03); - line-height: var(--line-height-03); - - p { - margin-bottom: 0; - } - - .ide-redesign-switcher-modal-whats-new { - background-color: var(--bg-light-secondary); - border: 1px solid var(--border-divider); - padding: var(--spacing-05); - margin: var(--spacing-05) 0; - - hr { - margin: var(--spacing-04) 0; - } - - h4 { - margin-top: 0; - } - - ul { - margin-bottom: 0; - - li:not(:last-child) { - margin-bottom: var(--spacing-04); - } - } - } -} diff --git a/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss b/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss new file mode 100644 index 0000000000..093735a150 --- /dev/null +++ b/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss @@ -0,0 +1,11 @@ +.new-editor-promo-modal-body, +.new-editor-intro-modal-body { + display: flex; + flex-direction: column; + gap: var(--spacing-04); + + video { + border-radius: var(--border-radius-base); + width: 100%; + } +} diff --git a/services/web/frontend/stylesheets/pages/editor/settings.scss b/services/web/frontend/stylesheets/pages/editor/settings.scss index 85816bdcb8..61af7d9035 100644 --- a/services/web/frontend/stylesheets/pages/editor/settings.scss +++ b/services/web/frontend/stylesheets/pages/editor/settings.scss @@ -98,18 +98,3 @@ 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/frontend/stylesheets/pages/editor/toolbar.scss b/services/web/frontend/stylesheets/pages/editor/toolbar.scss index 8f4639643c..412d97a177 100644 --- a/services/web/frontend/stylesheets/pages/editor/toolbar.scss +++ b/services/web/frontend/stylesheets/pages/editor/toolbar.scss @@ -540,3 +540,9 @@ font-size: var(--font-size-01); margin-right: var(--spacing-04); } + +.try-new-editor-button { + .button-content { + gap: var(--spacing-02); + } +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index bfc86b0f9a..7d1994d833 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -233,6 +233,7 @@ "basic": "Basic", "basic_compile_time": "Basic compile time", "basic_compile_timeout_on_fast_servers": "Basic compile timeout on fast servers", + "be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "Be one of the first to try out the improved __appName__ editor design, bringing you a cleaner, less cluttered interface to help you focus on what matters—your work.", "before_you_use_error_assistant": "Before you use Error Assist", "beta": "Beta", "beta_feature_badge": "Beta feature badge", @@ -242,7 +243,6 @@ "beta_program_not_participating": "You are not enrolled in the beta program", "beta_program_opt_in_action": "Opt-in to beta program", "beta_program_opt_out_action": "Opt-out of beta program", - "beta_program_the_new_overleaf_editor": "Beta program: the new Overleaf editor", "bibliographies": "Bibliographies", "billed_annually_at": "Billed annually at <0>__price__ <1>(includes plan and any add-ons)", "billed_monthly_at": "Billed monthly at <0>__price__ <1>(includes plan and any add-ons)", @@ -307,6 +307,7 @@ "certificate": "Certificate", "change": "Change", "change_currency": "Change currency", + "change_how_you_see_the_editor": "Change how you see the editor using the updated Appearance settings.", "change_language": "Change language", "change_or_cancel-cancel": "cancel", "change_or_cancel-change": "Change", @@ -749,6 +750,7 @@ "expires_on": "Expires: __date__", "expiry": "Expiry Date", "explore_all_plans": "Explore all plans", + "explore_what_s_new": "Explore what’s new", "export_csv": "Export CSV", "export_project_to_github": "Export Project to GitHub", "failed_to_send_group_invite_to_email": "Failed to send Group invite to <0>__email__. Please try again later.", @@ -792,11 +794,13 @@ "filter_projects": "Filter projects", "filters": "Filters", "find": "Find", + "find_and_fix_errors_faster": "Find and fix errors faster", "find_out_more": "Find out More", "find_out_more_about_institution_login": "Find out more about institutional login", "find_out_more_about_the_file_outline": "Find out more about the file outline", "find_out_more_nt": "Find out more.", "finding_a_fix": "Finding a fix", + "finish": "Finish", "first_name": "First name", "fit_to_height": "Fit to height", "fit_to_width": "Fit to width", @@ -1072,7 +1076,6 @@ "imported_from_zotero_at_date": "Imported from Zotero at __formattedDate__ __relativeDate__", "importing": "Importing", "importing_and_merging_changes_in_github": "Importing and merging changes in GitHub", - "improved_dark_mode": "Improved dark mode", "in_order_to_have_a_secure_account_make_sure_your_password": "To help keep your account secure, make sure your new password:", "in_order_to_match_institutional_metadata_2": "In order to match your institutional metadata, we’ve linked your account using <0>__email__.", "in_order_to_match_institutional_metadata_associated": "In order to match your institutional metadata, your account is associated with the email __email__.", @@ -1123,6 +1126,7 @@ "integrations": "Integrations", "integrations_like_github": "Integrations like GitHub Sync", "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__ Personal plan?", + "introducing_overleafs_new_look": "Introducing __appName__’s new look", "invalid_certificate": "Invalid certificate. Please check the certificate and try again.", "invalid_confirmation_code": "That didn’t work. Please check the code and try again.", "invalid_email": "An email address is invalid", @@ -1438,15 +1442,11 @@ "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_compiles_in_this_project_will_automatically_use_the_newest_version": "New compiles in this project will automatically use the newest version. <0>Learn how to change compiler settings", "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_editor_look": "New editor look", + "new_error_logs_make_it_easier_to_find_whats_wrong": "New error logs make it easier to find what’s wrong and fix your document, so you can get compiling again.", "new_file": "New file", "new_folder": "New folder", - "new_look_and_feel": "New look and feel", - "new_look_and_placement_of_the_settings": "New look and placement of the settings", "new_name": "New name", - "new_navigation_introducing_left_hand_side_rail_and_top_menus": "New navigation - introducing left-hand side rail and top menus", "new_password": "New password", "new_project": "New project", "new_snippet_project": "Untitled", @@ -1507,6 +1507,7 @@ "not_managed": "Not managed", "not_now": "Not now", "not_registered": "Not registered", + "not_sure_about_switching_yet": "Not sure about switching yet?", "note_features_under_development": "<0>Please note that features in this program are still being tested and actively developed. This means that they might <0>change, be <0>removed or <0>become part of a premium plan", "notification": "Notification", "notification_features_upgraded_by_affiliation": "Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to all of Overleaf’s Professional features.", @@ -1588,6 +1589,7 @@ "overleaf_plans_and_pricing": "overleaf plans and pricing", "overleaf_template_gallery": "overleaf template gallery", "overleafs_functionality_meets_my_needs": "Overleaf’s functionality meets my needs.", + "overleafs_new_look_is_here": "__appName__’s new look is here", "overview": "Overview", "overwrite": "Overwrite", "overwriting_the_original_folder": "Overwriting the original folder will delete it and all the files it contains.", @@ -1809,6 +1811,7 @@ "read_lines_from_path": "Read lines from __path__", "read_more": "Read more", "read_more_about_managed_users": "Read more about managed users", + "read_more_about_the_new_editor": "<0>Read more about the new editor design, or temporarily switch back to the old editor using the <1>Appearance settings.", "read_only_dropbox_sync_message": "As a read-only viewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not sync back to Overleaf.", "read_only_token": "Read-Only Token", "read_write_token": "Read-Write Token", @@ -1951,7 +1954,6 @@ "revert_pending_plan_change": "Revert scheduled plan change", "review": "Review", "review_panel": "Review panel", - "review_panel_and_error_logs_moved_to_the_left": "Review panel and error logs moved to the left", "reviewer": "Reviewer", "reviewer_dropbox_sync_message": "As a reviewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not sync back to Overleaf.", "reviewing": "Reviewing", @@ -2085,6 +2087,7 @@ "setup_another_account_under_a_personal_email_address": "Set up another Overleaf account under a personal email address.", "share": "Share", "share_feedback": "Share feedback", + "share_feedback_on_the_new_editor": "Share feedback on the new editor look.", "share_project": "Share Project", "shared_with_you": "Shared with you", "sharelatex_beta_program": "__appName__ beta program", @@ -2114,6 +2117,7 @@ "sign_up_for_free": "Sign up for free", "sign_up_for_free_account": "Sign up for a free account and receive regular updates", "simple_search_mode": "Simple search", + "simplified_working_starts_here": "Simplified working starts here", "single_sign_on_sso": "Single Sign-On (SSO)", "site_description": "An online LaTeX editor that’s easy to use. No installation, real-time collaboration, version control, hundreds of LaTeX templates, and more.", "site_wide_option_available": "Site-wide option available", @@ -2267,10 +2271,10 @@ "sure_you_want_to_delete": "Are you sure you want to permanently delete the following files?", "sure_you_want_to_leave_group": "Are you sure you want to leave this group?", "sv": "Swedish", + "switch_between_dark_and_light_mode": "Switch between dark and light mode", "switch_compile_mode_for_faster_draft_compilation": "Switch compile mode for faster draft compilation", + "switch_easily_between_your_files_comments_track_changes_and_more": "Switch easily between your files, comments, track changes, and more in the new left-hand menu.", "switch_to_editor": "Switch to editor", - "switch_to_new_editor": "Switch to new editor", - "switch_to_old_editor": "Switch to old editor", "switch_to_pdf": "Switch to PDF", "switch_to_personal_email_to_keep_your_accounts_separate": "Switch to a personal email to keep your accounts separate.", "switch_to_standard_plan": "Switch to Standard plan", @@ -2346,8 +2350,7 @@ "the_following_folder_already_exists_in_this_project": "The following folder already exists in this project:", "the_following_folder_already_exists_in_this_project_plural": "The following folders already exist in this project:", "the_latex_engine_used_for_compiling": "The LaTeX engine used for compiling", - "the_new_overleaf_editor_try_now_in_beta": "The new Overleaf editor — try now in beta", - "the_next_payment_will_be_collected_on": "The next payment will be collected on __date__.", + "the_new_overleaf_editor_info": "__appName__’s new look is here. Disabling this option will switch you back to the old editor design.", "the_original_text_has_changed": "The original text has changed, so this suggestion can’t be applied", "the_overleaf_color_scheme": "The __appName__ color scheme", "the_primary_file_for_compiling_your_project": "The primary file for compiling your project", @@ -2385,7 +2388,6 @@ "this_experiment_isnt_accepting_new_participants": "This experiment isn’t accepting new participants.", "this_field_is_required": "This field is required", "this_grants_access_to_features_2": "This grants you access to <0>__appName__ <0>__featureType__ features.", - "this_is_a_beta_release_for_the_new_overleaf_editor": "This is a beta release for the new Overleaf editor. You can switch back to the old editor at any time.", "this_is_a_new_feature": "This is a new feature", "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "This is the file that references pulled from your reference manager will be added to.", "this_is_your_template": "This is your template from your project", @@ -2520,10 +2522,12 @@ "try_for_free": "Try for free", "try_it_for_free": "Try it for free", "try_now": "Try Now", + "try_out_the_new_editor_now": "Try out the new design now (you can switch back at any time), or <0>read more about the changes we’re making.", "try_premium_for_free": "Try Premium for free", "try_recompile_project_or_troubleshoot": "Please try recompiling the project from scratch, and if that doesn’t help, follow our <0>troubleshooting guide.", "try_relinking_provider": "It looks like you need to re-link your __provider__ account.", - "try_the_new_editor": "Try the new editor", + "try_the_new_editor_design": "Try the new editor design", + "try_the_new_look": "Try the new look", "try_to_compile_despite_errors": "Try to compile despite errors", "turn_off": "Turn off", "turn_off_link_sharing": "Turn off link sharing", @@ -2701,14 +2705,13 @@ "well_be_here_when_youre_ready": "We’ll be here when you’re ready to dive back in! 🦆", "were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "We’re making some <0>changes to project sharing. This means, as someone with edit access, your name and email address will be visible to the project owner and other editors.", "were_performing_maintenance": "We’re performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.", - "weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready": "We’ve redesigned our editor to make it easier to use and future ready. It’s now in beta, so try it out and give us your feedback.", + "weve_made_it_easier_to_find_and_use_the_tools_you_need_today": "The new editor design makes it easier to find and use the tools you need today, while making space for the new features you’ll love tomorrow.", "what_did_you_find_most_helpful": "What did you find most helpful?", "what_do_you_need": "What do you need?", "what_do_you_need_help_with": "What do you need help with?", "what_does_this_mean_for_you": "This means:", "what_happens_when_sso_is_enabled": "What happens when SSO is enabled?", "what_should_we_call_you": "What should we call you?", - "whats_different": "What’s different?", "when_you_join_labs": "When you join Labs, you can choose which experiments you want to be part of. Once you’ve done that, you can use Overleaf as normal, but you’ll see any labs features marked with this badge:", "when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.", "why_latex": "Why LaTeX?", @@ -2766,7 +2769,6 @@ "you_can_select_or_invite_collaborator": "You can select or invite __count__ collaborator on your current plan. Upgrade to add more editors or reviewers.", "you_can_select_or_invite_collaborator_plural": "You can select or invite __count__ collaborators on your current plan. Upgrade to add more editors or reviewers.", "you_can_still_use_your_premium_features": "You can still use your premium features until the pause becomes active.", - "you_can_switch_back_to_the_old_editor_at_any_time": "You can switch back to the old editor at any time.", "you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO).", "you_cant_join_this_group_subscription": "You can’t join this group subscription", "you_cant_reset_password_due_to_sso": "You can’t reset your password because your group or organization uses SSO. <0>Log in with SSO.",