mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
Merge pull request #31329 from overleaf/mj-dark-mode-notification
[web] Introduce notification for dashboard dark mode GitOrigin-RevId: da5045d412ddc87a4b18823a4a5fa3192fe15c89
This commit is contained in:
committed by
Copybot
parent
55c3b6b7ea
commit
498c89c6ed
@@ -31,6 +31,7 @@ const VALID_KEYS = [
|
|||||||
'old-editor-warning-tooltip',
|
'old-editor-warning-tooltip',
|
||||||
'old-editor-warning-tooltip-2',
|
'old-editor-warning-tooltip-2',
|
||||||
'workbench-rail-popover',
|
'workbench-rail-popover',
|
||||||
|
'themed-dashboard-intro',
|
||||||
]
|
]
|
||||||
|
|
||||||
async function completeTutorial(req, res, next) {
|
async function completeTutorial(req, res, next) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"Pricing": "",
|
"Pricing": "",
|
||||||
"Solutions": "",
|
"Solutions": "",
|
||||||
"a_custom_size_has_been_used_in_the_latex_code": "",
|
"a_custom_size_has_been_used_in_the_latex_code": "",
|
||||||
|
"a_dashboard_that_follows_your_lead": "",
|
||||||
"a_file_with_that_name_already_exists_and_will_be_overriden": "",
|
"a_file_with_that_name_already_exists_and_will_be_overriden": "",
|
||||||
"a_more_comprehensive_list_of_keyboard_shortcuts": "",
|
"a_more_comprehensive_list_of_keyboard_shortcuts": "",
|
||||||
"a_new_reference_was_added": "",
|
"a_new_reference_was_added": "",
|
||||||
@@ -601,6 +602,7 @@
|
|||||||
"failed_to_send_managed_user_invite_to_email": "",
|
"failed_to_send_managed_user_invite_to_email": "",
|
||||||
"failed_to_send_sso_link_invite_to_email": "",
|
"failed_to_send_sso_link_invite_to_email": "",
|
||||||
"fair_usage_policy_applies": "",
|
"fair_usage_policy_applies": "",
|
||||||
|
"fancy_going_dark": "",
|
||||||
"fast": "",
|
"fast": "",
|
||||||
"fast_draft": "",
|
"fast_draft": "",
|
||||||
"feature_enabled_or_disabled": "",
|
"feature_enabled_or_disabled": "",
|
||||||
@@ -761,6 +763,7 @@
|
|||||||
"go_to_writefull": "",
|
"go_to_writefull": "",
|
||||||
"good_news_you_already_purchased_this_add_on": "",
|
"good_news_you_already_purchased_this_add_on": "",
|
||||||
"good_news_you_are_already_receiving_this_add_on_via_writefull": "",
|
"good_news_you_are_already_receiving_this_add_on_via_writefull": "",
|
||||||
|
"got_it": "",
|
||||||
"got_questions": "",
|
"got_questions": "",
|
||||||
"group_admin": "",
|
"group_admin": "",
|
||||||
"group_audit_logs": "",
|
"group_audit_logs": "",
|
||||||
@@ -1091,6 +1094,7 @@
|
|||||||
"math_inline": "",
|
"math_inline": "",
|
||||||
"maximum_files_uploaded_together": "",
|
"maximum_files_uploaded_together": "",
|
||||||
"maybe_later": "",
|
"maybe_later": "",
|
||||||
|
"meet_the_new_dark_dashboard": "",
|
||||||
"members_added": "",
|
"members_added": "",
|
||||||
"members_management": "",
|
"members_management": "",
|
||||||
"mendeley": "",
|
"mendeley": "",
|
||||||
@@ -2219,11 +2223,15 @@
|
|||||||
"wed_love_you_to_stay": "",
|
"wed_love_you_to_stay": "",
|
||||||
"welcome_to_overleaf_opening_workspace": "",
|
"welcome_to_overleaf_opening_workspace": "",
|
||||||
"welcome_to_sl": "",
|
"welcome_to_sl": "",
|
||||||
|
"welcome_to_the_dark_side": "",
|
||||||
"well_be_here_when_youre_ready": "",
|
"well_be_here_when_youre_ready": "",
|
||||||
"were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "",
|
"were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "",
|
||||||
"were_performing_maintenance": "",
|
"were_performing_maintenance": "",
|
||||||
|
"weve_given_your_dashboard_a_sleek_new_dark_theme_for_more_comfortable_late_night_research_prefer_the_light_switch_back_anytime_right_here": "",
|
||||||
"weve_hit_a_problem_try_starting_a_new_chat": "",
|
"weve_hit_a_problem_try_starting_a_new_chat": "",
|
||||||
"weve_made_it_easier_to_find_and_use_the_tools_you_need_today": "",
|
"weve_made_it_easier_to_find_and_use_the_tools_you_need_today": "",
|
||||||
|
"weve_matched_your_dashboard_theme_to_your_editor_preferences_but_you_can_change_that_here_anytime": "",
|
||||||
|
"weve_set_your_dashboard_to_dark_mode_to_help_you_stay_focused_if_youre_a_fan_of_a_lighter_look_you_can_easily_switch_themes_here": "",
|
||||||
"what_did_you_find_most_helpful": "",
|
"what_did_you_find_most_helpful": "",
|
||||||
"what_do_you_need_help_with": "",
|
"what_do_you_need_help_with": "",
|
||||||
"what_does_this_mean_for_you": "",
|
"what_does_this_mean_for_you": "",
|
||||||
@@ -2308,6 +2316,7 @@
|
|||||||
"your_current_plan_gives_you": "",
|
"your_current_plan_gives_you": "",
|
||||||
"your_current_plan_supports_up_to_x_licenses": "",
|
"your_current_plan_supports_up_to_x_licenses": "",
|
||||||
"your_current_project_will_revert_to_the_version_from_time": "",
|
"your_current_project_will_revert_to_the_version_from_time": "",
|
||||||
|
"your_dashboard_is_set_to_match_your_system_theme_automatically_want_a_different_look_pick_your_favorite_theme_here": "",
|
||||||
"your_email_is_confirmed": "",
|
"your_email_is_confirmed": "",
|
||||||
"your_feedback_matters_answer_two_quick_questions": "",
|
"your_feedback_matters_answer_two_quick_questions": "",
|
||||||
"your_git_access_info": "",
|
"your_git_access_info": "",
|
||||||
|
|||||||
+2
-2
@@ -7,13 +7,13 @@ import { isVersionSelected } from '../../utils/history-details'
|
|||||||
import { useUserContext } from '../../../../shared/context/user-context'
|
import { useUserContext } from '../../../../shared/context/user-context'
|
||||||
import useDropdownActiveItem from '../../hooks/use-dropdown-active-item'
|
import useDropdownActiveItem from '../../hooks/use-dropdown-active-item'
|
||||||
import { useHistoryContext } from '../../context/history-context'
|
import { useHistoryContext } from '../../context/history-context'
|
||||||
import { useEditorContext } from '../../../../shared/context/editor-context'
|
|
||||||
import OLPopover from '@/shared/components/ol/ol-popover'
|
import OLPopover from '@/shared/components/ol/ol-popover'
|
||||||
import OLOverlay from '@/shared/components/ol/ol-overlay'
|
import OLOverlay from '@/shared/components/ol/ol-overlay'
|
||||||
import Close from '@/shared/components/close'
|
import Close from '@/shared/components/close'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import MaterialIcon from '@/shared/components/material-icon'
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
function AllHistoryList() {
|
function AllHistoryList() {
|
||||||
const { id: currentUserId } = useUserContext()
|
const { id: currentUserId } = useUserContext()
|
||||||
@@ -91,7 +91,7 @@ function AllHistoryList() {
|
|||||||
}
|
}
|
||||||
}, [updatesLoadingState])
|
}, [updatesLoadingState])
|
||||||
|
|
||||||
const { inactiveTutorials } = useEditorContext()
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
const {
|
const {
|
||||||
showPopup: showHistoryTutorial,
|
showPopup: showHistoryTutorial,
|
||||||
tryShowingPopup: tryShowingHistoryTutorial,
|
tryShowingPopup: tryShowingHistoryTutorial,
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import OLFormGroup from '@/shared/components/ol/ol-form-group'
|
|||||||
import OLIconButton from '@/shared/components/ol/ol-icon-button'
|
import OLIconButton from '@/shared/components/ol/ol-icon-button'
|
||||||
import { OLToast } from '@/shared/components/ol/ol-toast'
|
import { OLToast } from '@/shared/components/ol/ol-toast'
|
||||||
import { OLToastContainer } from '@/shared/components/ol/ol-toast-container'
|
import { OLToastContainer } from '@/shared/components/ol/ol-toast-container'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
|
||||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { sendMB } from '@/infrastructure/event-tracking'
|
import { sendMB } from '@/infrastructure/event-tracking'
|
||||||
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
|
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
type EditorSurveyPage = 'ease-of-use' | 'meets-my-needs' | 'thank-you'
|
type EditorSurveyPage = 'ease-of-use' | 'meets-my-needs' | 'thank-you'
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ const EditorSurveyContent = () => {
|
|||||||
const [easeOfUse, setEaseOfUse] = useState<number | null>(null)
|
const [easeOfUse, setEaseOfUse] = useState<number | null>(null)
|
||||||
const [meetsMyNeeds, setMeetsMyNeeds] = useState<number | null>(null)
|
const [meetsMyNeeds, setMeetsMyNeeds] = useState<number | null>(null)
|
||||||
const [page, setPage] = useState<EditorSurveyPage>('ease-of-use')
|
const [page, setPage] = useState<EditorSurveyPage>('ease-of-use')
|
||||||
const { inactiveTutorials } = useEditorContext()
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
const hasCompletedSurvey = inactiveTutorials.includes(TUTORIAL_KEY)
|
const hasCompletedSurvey = inactiveTutorials.includes(TUTORIAL_KEY)
|
||||||
const newEditor = useIsNewEditorEnabled()
|
const newEditor = useIsNewEditorEnabled()
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { CommandRegistryProvider } from './command-registry-context'
|
|||||||
import { NewEditorTourProvider } from '@/features/ide-redesign/contexts/new-editor-tour-context'
|
import { NewEditorTourProvider } from '@/features/ide-redesign/contexts/new-editor-tour-context'
|
||||||
import { EditorSelectionProvider } from '@/shared/context/editor-selection-context'
|
import { EditorSelectionProvider } from '@/shared/context/editor-selection-context'
|
||||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||||
|
import { TutorialProvider } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
const rootContextProviders = importOverleafModules('rootContextProviders') as {
|
const rootContextProviders = importOverleafModules('rootContextProviders') as {
|
||||||
import: { default: ElementType }
|
import: { default: ElementType }
|
||||||
@@ -76,6 +77,7 @@ export const ReactContextRoot: FC<
|
|||||||
UserFeaturesProvider,
|
UserFeaturesProvider,
|
||||||
NewEditorTourProvider,
|
NewEditorTourProvider,
|
||||||
EditorSelectionProvider,
|
EditorSelectionProvider,
|
||||||
|
TutorialProvider,
|
||||||
...providers,
|
...providers,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,47 +107,49 @@ export const ReactContextRoot: FC<
|
|||||||
<Providers.EditorViewProvider>
|
<Providers.EditorViewProvider>
|
||||||
<Providers.EditorOpenDocProvider>
|
<Providers.EditorOpenDocProvider>
|
||||||
<Providers.EditorProvider>
|
<Providers.EditorProvider>
|
||||||
<Providers.FileTreeDataProvider>
|
<Providers.TutorialProvider>
|
||||||
<Providers.FileTreePathProvider>
|
<Providers.FileTreeDataProvider>
|
||||||
<Providers.UserFeaturesProvider>
|
<Providers.FileTreePathProvider>
|
||||||
<Providers.PermissionsProvider>
|
<Providers.UserFeaturesProvider>
|
||||||
<Providers.RailProvider>
|
<Providers.PermissionsProvider>
|
||||||
<Providers.LayoutProvider>
|
<Providers.RailProvider>
|
||||||
<Providers.NewEditorTourProvider>
|
<Providers.LayoutProvider>
|
||||||
<Providers.ProjectSettingsProvider>
|
<Providers.NewEditorTourProvider>
|
||||||
<Providers.EditorManagerProvider>
|
<Providers.ProjectSettingsProvider>
|
||||||
<Providers.ReferencesProvider>
|
<Providers.EditorManagerProvider>
|
||||||
<Providers.LocalCompileProvider>
|
<Providers.ReferencesProvider>
|
||||||
<Providers.DetachCompileProvider>
|
<Providers.LocalCompileProvider>
|
||||||
<Providers.ChatProvider>
|
<Providers.DetachCompileProvider>
|
||||||
<Providers.FileTreeOpenProvider>
|
<Providers.ChatProvider>
|
||||||
<Providers.OnlineUsersProvider>
|
<Providers.FileTreeOpenProvider>
|
||||||
<Providers.MetadataProvider>
|
<Providers.OnlineUsersProvider>
|
||||||
<Providers.OutlineProvider>
|
<Providers.MetadataProvider>
|
||||||
<Providers.CommandRegistryProvider>
|
<Providers.OutlineProvider>
|
||||||
<Providers.EditorSelectionProvider>
|
<Providers.CommandRegistryProvider>
|
||||||
{
|
<Providers.EditorSelectionProvider>
|
||||||
childrenWrappedWithDynamicProviders
|
{
|
||||||
}
|
childrenWrappedWithDynamicProviders
|
||||||
</Providers.EditorSelectionProvider>
|
}
|
||||||
</Providers.CommandRegistryProvider>
|
</Providers.EditorSelectionProvider>
|
||||||
</Providers.OutlineProvider>
|
</Providers.CommandRegistryProvider>
|
||||||
</Providers.MetadataProvider>
|
</Providers.OutlineProvider>
|
||||||
</Providers.OnlineUsersProvider>
|
</Providers.MetadataProvider>
|
||||||
</Providers.FileTreeOpenProvider>
|
</Providers.OnlineUsersProvider>
|
||||||
</Providers.ChatProvider>
|
</Providers.FileTreeOpenProvider>
|
||||||
</Providers.DetachCompileProvider>
|
</Providers.ChatProvider>
|
||||||
</Providers.LocalCompileProvider>
|
</Providers.DetachCompileProvider>
|
||||||
</Providers.ReferencesProvider>
|
</Providers.LocalCompileProvider>
|
||||||
</Providers.EditorManagerProvider>
|
</Providers.ReferencesProvider>
|
||||||
</Providers.ProjectSettingsProvider>
|
</Providers.EditorManagerProvider>
|
||||||
</Providers.NewEditorTourProvider>
|
</Providers.ProjectSettingsProvider>
|
||||||
</Providers.LayoutProvider>
|
</Providers.NewEditorTourProvider>
|
||||||
</Providers.RailProvider>
|
</Providers.LayoutProvider>
|
||||||
</Providers.PermissionsProvider>
|
</Providers.RailProvider>
|
||||||
</Providers.UserFeaturesProvider>
|
</Providers.PermissionsProvider>
|
||||||
</Providers.FileTreePathProvider>
|
</Providers.UserFeaturesProvider>
|
||||||
</Providers.FileTreeDataProvider>
|
</Providers.FileTreePathProvider>
|
||||||
|
</Providers.FileTreeDataProvider>
|
||||||
|
</Providers.TutorialProvider>
|
||||||
</Providers.EditorProvider>
|
</Providers.EditorProvider>
|
||||||
</Providers.EditorOpenDocProvider>
|
</Providers.EditorOpenDocProvider>
|
||||||
</Providers.EditorViewProvider>
|
</Providers.EditorViewProvider>
|
||||||
|
|||||||
+2
-2
@@ -9,15 +9,15 @@ import { useCallback, useEffect, useState } from 'react'
|
|||||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
import OLButton from '@/shared/components/ol/ol-button'
|
import OLButton from '@/shared/components/ol/ol-button'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
|
||||||
import { useIsNewToNewEditor } from '../utils/new-editor-utils'
|
import { useIsNewToNewEditor } from '../utils/new-editor-utils'
|
||||||
import { useNewEditorTourContext } from '../contexts/new-editor-tour-context'
|
import { useNewEditorTourContext } from '../contexts/new-editor-tour-context'
|
||||||
import promoVideo from './new-editor-promo-video.mp4'
|
import promoVideo from './new-editor-promo-video.mp4'
|
||||||
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
const TUTORIAL_KEY = 'new-editor-intro-2'
|
const TUTORIAL_KEY = 'new-editor-intro-2'
|
||||||
|
|
||||||
export default function NewEditorOptOutIntroModal() {
|
export default function NewEditorOptOutIntroModal() {
|
||||||
const { inactiveTutorials } = useEditorContext()
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
const {
|
const {
|
||||||
tryShowingPopup,
|
tryShowingPopup,
|
||||||
showPopup: showModal,
|
showPopup: showModal,
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useSwitchEnableNewEditorState } from '../hooks/use-switch-enable-new-editor-state'
|
import { useSwitchEnableNewEditorState } from '../hooks/use-switch-enable-new-editor-state'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
|
||||||
import { canUseNewEditor } from '../utils/new-editor-utils'
|
import { canUseNewEditor } from '../utils/new-editor-utils'
|
||||||
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
const TUTORIAL_KEY = 'old-editor-warning-tooltip-2'
|
const TUTORIAL_KEY = 'old-editor-warning-tooltip-2'
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export default function OldEditorWarningTooltip({
|
|||||||
}: {
|
}: {
|
||||||
target: HTMLElement | null
|
target: HTMLElement | null
|
||||||
}) {
|
}) {
|
||||||
const { inactiveTutorials } = useEditorContext()
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Close from '@/shared/components/close'
|
import Close from '@/shared/components/close'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
@@ -26,7 +26,7 @@ export default function TooltipPromotion({
|
|||||||
placement?: OverlayProps['placement']
|
placement?: OverlayProps['placement']
|
||||||
splitTestName?: string
|
splitTestName?: string
|
||||||
}) {
|
}) {
|
||||||
const { inactiveTutorials } = useEditorContext()
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
const { showPopup, tryShowingPopup, hideUntilReload, dismissTutorial } =
|
const { showPopup, tryShowingPopup, hideUntilReload, dismissTutorial } =
|
||||||
useTutorial(tutorialKey, eventData)
|
useTutorial(tutorialKey, eventData)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,17 +1,17 @@
|
|||||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
|
||||||
import { useProjectContext } from '@/shared/context/project-context'
|
import { useProjectContext } from '@/shared/context/project-context'
|
||||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { onRollingBuild } from '@/shared/utils/rolling-build'
|
import { onRollingBuild } from '@/shared/utils/rolling-build'
|
||||||
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
export const TUTORIAL_KEY = 'rolling-compile-image-changed'
|
export const TUTORIAL_KEY = 'rolling-compile-image-changed'
|
||||||
|
|
||||||
const RollingCompileImageChangedAlert = () => {
|
const RollingCompileImageChangedAlert = () => {
|
||||||
const { completeTutorial } = useTutorial(TUTORIAL_KEY)
|
const { completeTutorial } = useTutorial(TUTORIAL_KEY)
|
||||||
const { project } = useProjectContext()
|
const { project } = useProjectContext()
|
||||||
const { inactiveTutorials } = useEditorContext()
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { DsNavStyleProvider } from '@/features/project-list/components/use-is-ds
|
|||||||
import CookieBanner from '@/shared/components/cookie-banner'
|
import CookieBanner from '@/shared/components/cookie-banner'
|
||||||
import useThemedPage from '@/shared/hooks/use-themed-page'
|
import useThemedPage from '@/shared/hooks/use-themed-page'
|
||||||
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
|
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
|
||||||
|
import { TutorialProvider } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
function ProjectListRoot() {
|
function ProjectListRoot() {
|
||||||
const { isReady } = useWaitForI18n()
|
const { isReady } = useWaitForI18n()
|
||||||
@@ -37,9 +38,11 @@ export function ProjectListRootInner() {
|
|||||||
<ProjectListProvider>
|
<ProjectListProvider>
|
||||||
<ColorPickerProvider>
|
<ColorPickerProvider>
|
||||||
<SplitTestProvider>
|
<SplitTestProvider>
|
||||||
<UserSettingsProvider>
|
<TutorialProvider>
|
||||||
<ProjectListPageContent />
|
<UserSettingsProvider>
|
||||||
</UserSettingsProvider>
|
<ProjectListPageContent />
|
||||||
|
</UserSettingsProvider>
|
||||||
|
</TutorialProvider>
|
||||||
</SplitTestProvider>
|
</SplitTestProvider>
|
||||||
</ColorPickerProvider>
|
</ColorPickerProvider>
|
||||||
</ProjectListProvider>
|
</ProjectListProvider>
|
||||||
|
|||||||
+25
-2
@@ -18,6 +18,8 @@ import { useScrolled } from '@/features/project-list/components/sidebar/use-scro
|
|||||||
import { useSendProjectListMB } from '@/features/project-list/components/project-list-events'
|
import { useSendProjectListMB } from '@/features/project-list/components/project-list-events'
|
||||||
import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav'
|
import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav'
|
||||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||||
|
import { ThemedProjectDashboardNotification } from './themed-project-dashboard-notification'
|
||||||
|
import { useThemedDashboardIntro } from './use-themed-dashboard-intro'
|
||||||
|
|
||||||
function SidebarDsNav() {
|
function SidebarDsNav() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -38,6 +40,12 @@ function SidebarDsNav() {
|
|||||||
) as NavbarDropdownItemData
|
) as NavbarDropdownItemData
|
||||||
const { containerRef, scrolledUp, scrolledDown } = useScrolled()
|
const { containerRef, scrolledUp, scrolledDown } = useScrolled()
|
||||||
const themedDsNav = useFeatureFlag('themed-project-dashboard')
|
const themedDsNav = useFeatureFlag('themed-project-dashboard')
|
||||||
|
const {
|
||||||
|
completeThemedDashboardIntro,
|
||||||
|
dismissThemedDashboardIntro,
|
||||||
|
targetRef,
|
||||||
|
showingThemedDashboardIntro,
|
||||||
|
} = useThemedDashboardIntro()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -131,6 +139,12 @@ function SidebarDsNav() {
|
|||||||
item: 'account',
|
item: 'account',
|
||||||
location: 'sidebar',
|
location: 'sidebar',
|
||||||
})
|
})
|
||||||
|
if (showingThemedDashboardIntro) {
|
||||||
|
completeThemedDashboardIntro({
|
||||||
|
action: 'complete',
|
||||||
|
event: 'promo-click',
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
role="menu"
|
role="menu"
|
||||||
@@ -144,7 +158,7 @@ function SidebarDsNav() {
|
|||||||
}}
|
}}
|
||||||
hidden={showAccountDropdown}
|
hidden={showAccountDropdown}
|
||||||
>
|
>
|
||||||
<div>
|
<div ref={targetRef}>
|
||||||
<User size={24} />
|
<User size={24} />
|
||||||
</div>
|
</div>
|
||||||
</OLTooltip>
|
</OLTooltip>
|
||||||
@@ -166,7 +180,16 @@ function SidebarDsNav() {
|
|||||||
/>
|
/>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<UserProvider>{contactUsModal}</UserProvider>
|
<UserProvider>
|
||||||
|
{themedDsNav && (
|
||||||
|
<ThemedProjectDashboardNotification
|
||||||
|
target={targetRef.current}
|
||||||
|
show={showingThemedDashboardIntro}
|
||||||
|
onDismiss={dismissThemedDashboardIntro}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{contactUsModal}
|
||||||
|
</UserProvider>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
import Close from '@/shared/components/close'
|
||||||
|
import OLButton from '@/shared/components/ol/ol-button'
|
||||||
|
import { useUserContext } from '@/shared/context/user-context'
|
||||||
|
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||||
|
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
|
||||||
|
import { Overlay, Popover } from 'react-bootstrap'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
// TODO: Update this before release
|
||||||
|
const NEW_USER_CUTOFF_DATE = new Date('2026-02-15')
|
||||||
|
|
||||||
|
type ThemedProjectDashboardNotificationProps = {
|
||||||
|
target: HTMLElement | null
|
||||||
|
show: boolean
|
||||||
|
onDismiss: () => void
|
||||||
|
}
|
||||||
|
export const ThemedProjectDashboardNotification = ({
|
||||||
|
target,
|
||||||
|
show,
|
||||||
|
onDismiss,
|
||||||
|
}: ThemedProjectDashboardNotificationProps) => {
|
||||||
|
const theme = useActiveOverallTheme('themed-project-dashboard')
|
||||||
|
const {
|
||||||
|
userSettings: { overallTheme },
|
||||||
|
} = useUserSettingsContext()
|
||||||
|
const { signUpDate: signUpDateString } = useUserContext()
|
||||||
|
const signUpDate = signUpDateString ? new Date(signUpDateString) : new Date(0)
|
||||||
|
const isNewUser = signUpDate > NEW_USER_CUTOFF_DATE
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let content
|
||||||
|
switch (true) {
|
||||||
|
case overallTheme === 'system':
|
||||||
|
content = {
|
||||||
|
header: t('a_dashboard_that_follows_your_lead'),
|
||||||
|
body: t(
|
||||||
|
'your_dashboard_is_set_to_match_your_system_theme_automatically_want_a_different_look_pick_your_favorite_theme_here'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case isNewUser && theme === 'dark':
|
||||||
|
content = {
|
||||||
|
header: t('meet_the_new_dark_dashboard'),
|
||||||
|
body: t(
|
||||||
|
'weve_set_your_dashboard_to_dark_mode_to_help_you_stay_focused_if_youre_a_fan_of_a_lighter_look_you_can_easily_switch_themes_here'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case theme === 'dark':
|
||||||
|
content = {
|
||||||
|
header: t('welcome_to_the_dark_side'),
|
||||||
|
body: t(
|
||||||
|
'weve_given_your_dashboard_a_sleek_new_dark_theme_for_more_comfortable_late_night_research_prefer_the_light_switch_back_anytime_right_here'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case theme === 'light':
|
||||||
|
content = {
|
||||||
|
header: t('fancy_going_dark'),
|
||||||
|
body: t(
|
||||||
|
'weve_matched_your_dashboard_theme_to_your_editor_preferences_but_you_can_change_that_here_anytime'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
content = { header: '', body: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content.header || !content.body) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Overlay show placement="right" target={target} onHide={onDismiss}>
|
||||||
|
<Popover className="themed-dashboard-intro-popover">
|
||||||
|
<Popover.Header>
|
||||||
|
{content.header}
|
||||||
|
<Close
|
||||||
|
variant={theme === 'light' ? 'dark' : 'light'}
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
/>
|
||||||
|
</Popover.Header>
|
||||||
|
<Popover.Body>
|
||||||
|
{content.body}
|
||||||
|
<div className="d-flex justify-content-end">
|
||||||
|
<OLButton onClick={onDismiss} variant="link">
|
||||||
|
{t('got_it')}
|
||||||
|
</OLButton>
|
||||||
|
</div>
|
||||||
|
</Popover.Body>
|
||||||
|
</Popover>
|
||||||
|
</Overlay>
|
||||||
|
)
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||||
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||||
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
const THEMED_DASHBOARD_TUTORIAL_KEY = 'themed-dashboard-intro'
|
||||||
|
|
||||||
|
export const useThemedDashboardIntro = () => {
|
||||||
|
const themedDsNav = useFeatureFlag('themed-project-dashboard')
|
||||||
|
const targetRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const { inactiveTutorials } = useTutorialContext()
|
||||||
|
const {
|
||||||
|
tryShowingPopup: tryShowingPopupThemedDashboardIntro,
|
||||||
|
showPopup: showingThemedDashboardIntro,
|
||||||
|
completeTutorial: completeThemedDashboardIntro,
|
||||||
|
dismissTutorial,
|
||||||
|
} = useTutorial(THEMED_DASHBOARD_TUTORIAL_KEY, {
|
||||||
|
name: THEMED_DASHBOARD_TUTORIAL_KEY,
|
||||||
|
})
|
||||||
|
const dismissThemedDashboardIntro = useCallback(() => {
|
||||||
|
dismissTutorial()
|
||||||
|
}, [dismissTutorial])
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
themedDsNav &&
|
||||||
|
!inactiveTutorials.includes(THEMED_DASHBOARD_TUTORIAL_KEY)
|
||||||
|
) {
|
||||||
|
tryShowingPopupThemedDashboardIntro()
|
||||||
|
}
|
||||||
|
}, [inactiveTutorials, tryShowingPopupThemedDashboardIntro, themedDsNav])
|
||||||
|
|
||||||
|
return {
|
||||||
|
targetRef,
|
||||||
|
showingThemedDashboardIntro,
|
||||||
|
completeThemedDashboardIntro,
|
||||||
|
dismissThemedDashboardIntro,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,10 +29,6 @@ export const EditorContext = createContext<
|
|||||||
isProjectOwner: boolean
|
isProjectOwner: boolean
|
||||||
isRestrictedTokenMember?: boolean
|
isRestrictedTokenMember?: boolean
|
||||||
isPendingEditor: boolean
|
isPendingEditor: boolean
|
||||||
deactivateTutorial: (tutorial: string) => void
|
|
||||||
inactiveTutorials: string[]
|
|
||||||
currentPopup: string | null
|
|
||||||
setCurrentPopup: Dispatch<SetStateAction<string | null>>
|
|
||||||
hasPremiumSuggestion: boolean
|
hasPremiumSuggestion: boolean
|
||||||
setHasPremiumSuggestion: (value: boolean) => void
|
setHasPremiumSuggestion: (value: boolean) => void
|
||||||
setPremiumSuggestionResetDate: (date: Date) => void
|
setPremiumSuggestionResetDate: (date: Date) => void
|
||||||
@@ -77,11 +73,6 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [inactiveTutorials, setInactiveTutorials] = useState(
|
|
||||||
() => getMeta('ol-inactiveTutorials') || []
|
|
||||||
)
|
|
||||||
|
|
||||||
const [currentPopup, setCurrentPopup] = useState<string | null>(null)
|
|
||||||
const [hasPremiumSuggestion, setHasPremiumSuggestion] = useState<boolean>(
|
const [hasPremiumSuggestion, setHasPremiumSuggestion] = useState<boolean>(
|
||||||
() => {
|
() => {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
@@ -111,13 +102,6 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
[members, userId]
|
[members, userId]
|
||||||
)
|
)
|
||||||
|
|
||||||
const deactivateTutorial = useCallback(
|
|
||||||
(tutorialKey: string) => {
|
|
||||||
setInactiveTutorials([...inactiveTutorials, tutorialKey])
|
|
||||||
},
|
|
||||||
[inactiveTutorials]
|
|
||||||
)
|
|
||||||
|
|
||||||
const renameProject = useCallback(
|
const renameProject = useCallback(
|
||||||
(newName: string) => {
|
(newName: string) => {
|
||||||
const oldName = projectName
|
const oldName = projectName
|
||||||
@@ -180,10 +164,6 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
isRestrictedTokenMember: getMeta('ol-isRestrictedTokenMember'),
|
isRestrictedTokenMember: getMeta('ol-isRestrictedTokenMember'),
|
||||||
isPendingEditor,
|
isPendingEditor,
|
||||||
insertSymbol,
|
insertSymbol,
|
||||||
inactiveTutorials,
|
|
||||||
deactivateTutorial,
|
|
||||||
currentPopup,
|
|
||||||
setCurrentPopup,
|
|
||||||
hasPremiumSuggestion,
|
hasPremiumSuggestion,
|
||||||
setHasPremiumSuggestion,
|
setHasPremiumSuggestion,
|
||||||
premiumSuggestionResetDate,
|
premiumSuggestionResetDate,
|
||||||
@@ -201,10 +181,6 @@ export const EditorProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
|||||||
renameProject,
|
renameProject,
|
||||||
isPendingEditor,
|
isPendingEditor,
|
||||||
insertSymbol,
|
insertSymbol,
|
||||||
inactiveTutorials,
|
|
||||||
deactivateTutorial,
|
|
||||||
currentPopup,
|
|
||||||
setCurrentPopup,
|
|
||||||
hasPremiumSuggestion,
|
hasPremiumSuggestion,
|
||||||
setHasPremiumSuggestion,
|
setHasPremiumSuggestion,
|
||||||
premiumSuggestionResetDate,
|
premiumSuggestionResetDate,
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import getMeta from '@/utils/meta'
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
Dispatch,
|
||||||
|
FC,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
|
export const TutorialContext = createContext<
|
||||||
|
| {
|
||||||
|
deactivateTutorial: (tutorial: string) => void
|
||||||
|
inactiveTutorials: string[]
|
||||||
|
currentPopup: string | null
|
||||||
|
setCurrentPopup: Dispatch<SetStateAction<string | null>>
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>(undefined)
|
||||||
|
|
||||||
|
export const TutorialProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||||
|
const [inactiveTutorials, setInactiveTutorials] = useState(
|
||||||
|
() => getMeta('ol-inactiveTutorials') || []
|
||||||
|
)
|
||||||
|
|
||||||
|
const [currentPopup, setCurrentPopup] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const deactivateTutorial = useCallback(
|
||||||
|
(tutorialKey: string) => {
|
||||||
|
setInactiveTutorials([...inactiveTutorials, tutorialKey])
|
||||||
|
},
|
||||||
|
[inactiveTutorials]
|
||||||
|
)
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
deactivateTutorial,
|
||||||
|
inactiveTutorials,
|
||||||
|
currentPopup,
|
||||||
|
setCurrentPopup,
|
||||||
|
}),
|
||||||
|
[deactivateTutorial, inactiveTutorials, currentPopup, setCurrentPopup]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TutorialContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</TutorialContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTutorialContext() {
|
||||||
|
const context = useContext(TutorialContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useTutorialContext is only available inside TutorialProvider'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { useCallback, useState } from 'react'
|
|||||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||||
import { postJSON } from '@/infrastructure/fetch-json'
|
import { postJSON } from '@/infrastructure/fetch-json'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
import { useEditorContext } from '@/shared/context/editor-context'
|
import { useTutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
type CompleteTutorialParams = {
|
type CompleteTutorialParams = {
|
||||||
event: string
|
event: string
|
||||||
@@ -22,7 +22,7 @@ const useTutorial = (
|
|||||||
const [showPopup, setShowPopup] = useState(false)
|
const [showPopup, setShowPopup] = useState(false)
|
||||||
|
|
||||||
const { deactivateTutorial, currentPopup, setCurrentPopup } =
|
const { deactivateTutorial, currentPopup, setCurrentPopup } =
|
||||||
useEditorContext()
|
useTutorialContext()
|
||||||
|
|
||||||
const completeTutorial = useCallback(
|
const completeTutorial = useCallback(
|
||||||
async (
|
async (
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
--theme-toggle-selected-background: var(--green-20);
|
--theme-toggle-selected-background: var(--green-20);
|
||||||
--ds-nav-content-bg-secondary: var(--bg-light-secondary);
|
--ds-nav-content-bg-secondary: var(--bg-light-secondary);
|
||||||
--table-icon-bg-hover: 27 34 44;
|
--table-icon-bg-hover: 27 34 44;
|
||||||
|
--themed-dashboard-popover-bg: var(--bg-dark-primary);
|
||||||
|
--themed-dashboard-popover-color: var(--content-primary-dark);
|
||||||
|
--themed-dashboard-popover-link-color: var(--link-ui-dark);
|
||||||
|
--themed-dashboard-popover-link-hover-color: var(--link-ui-hover-dark);
|
||||||
|
--themed-dashboard-popover-link-visited-color: var(--link-ui-visited-dark);
|
||||||
|
|
||||||
@include theme('default') {
|
@include theme('default') {
|
||||||
--ds-nav-active-bg: var(--green-70);
|
--ds-nav-active-bg: var(--green-70);
|
||||||
@@ -11,6 +16,11 @@
|
|||||||
--theme-toggle-selected-background: var(--green-70);
|
--theme-toggle-selected-background: var(--green-70);
|
||||||
--ds-nav-content-bg-secondary: var(--bg-dark-secondary);
|
--ds-nav-content-bg-secondary: var(--bg-dark-secondary);
|
||||||
--table-icon-bg-hover: 255 255 255;
|
--table-icon-bg-hover: 255 255 255;
|
||||||
|
--themed-dashboard-popover-bg: var(--bg-light-primary);
|
||||||
|
--themed-dashboard-popover-color: var(--content-primary);
|
||||||
|
--themed-dashboard-popover-link-color: var(--link-ui);
|
||||||
|
--themed-dashboard-popover-link-hover-color: var(--link-ui-hover);
|
||||||
|
--themed-dashboard-popover-link-visited-color: var(--link-ui-visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,3 +430,26 @@ body {
|
|||||||
background-color: var(--theme-toggle-selected-background);
|
background-color: var(--theme-toggle-selected-background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.themed-dashboard-intro-popover {
|
||||||
|
--bs-popover-bg: var(--themed-dashboard-popover-bg);
|
||||||
|
--bs-popover-header-bg: var(--themed-dashboard-popover-bg);
|
||||||
|
--bs-popover-body-color: var(--themed-dashboard-popover-color);
|
||||||
|
--bs-popover-header-color: var(--themed-dashboard-popover-color);
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
color: var(--bs-popover-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--themed-dashboard-popover-link-color);
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: var(--themed-dashboard-popover-link-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--themed-dashboard-popover-link-visited-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"Terms": "Terms",
|
"Terms": "Terms",
|
||||||
"Universities": "Universities",
|
"Universities": "Universities",
|
||||||
"a_custom_size_has_been_used_in_the_latex_code": "A custom size has been used in the LaTeX code.",
|
"a_custom_size_has_been_used_in_the_latex_code": "A custom size has been used in the LaTeX code.",
|
||||||
|
"a_dashboard_that_follows_your_lead": "A dashboard that follows your lead",
|
||||||
"a_file_with_that_name_already_exists_and_will_be_overriden": "A file with that name already exists. That file will be overwritten.",
|
"a_file_with_that_name_already_exists_and_will_be_overriden": "A file with that name already exists. That file will be overwritten.",
|
||||||
"a_more_comprehensive_list_of_keyboard_shortcuts": "A more comprehensive list of keyboard shortcuts can be found in <0>this __appName__ project template</0>",
|
"a_more_comprehensive_list_of_keyboard_shortcuts": "A more comprehensive list of keyboard shortcuts can be found in <0>this __appName__ project template</0>",
|
||||||
"a_new_reference_was_added": "A new reference was added",
|
"a_new_reference_was_added": "A new reference was added",
|
||||||
@@ -771,6 +772,7 @@
|
|||||||
"failed_to_send_managed_user_invite_to_email": "Failed to send Managed User invite to <0>__email__</0>. Please try again later.",
|
"failed_to_send_managed_user_invite_to_email": "Failed to send Managed User invite to <0>__email__</0>. Please try again later.",
|
||||||
"failed_to_send_sso_link_invite_to_email": "Failed to send SSO invite reminder to <0>__email__</0>. Please try again later.",
|
"failed_to_send_sso_link_invite_to_email": "Failed to send SSO invite reminder to <0>__email__</0>. Please try again later.",
|
||||||
"fair_usage_policy_applies": "Fair usage policy applies.",
|
"fair_usage_policy_applies": "Fair usage policy applies.",
|
||||||
|
"fancy_going_dark": "Fancy going dark?",
|
||||||
"faq_how_does_free_trial_works_answer": "You get full access to your chosen __appName__ plan during your __len__-day free trial. There is no obligation to continue beyond the trial. Your card will be charged at the end of your __len__ day trial unless you cancel before then. You can cancel via your subscription settings.",
|
"faq_how_does_free_trial_works_answer": "You get full access to your chosen __appName__ plan during your __len__-day free trial. There is no obligation to continue beyond the trial. Your card will be charged at the end of your __len__ day trial unless you cancel before then. You can cancel via your subscription settings.",
|
||||||
"fast": "Fast",
|
"fast": "Fast",
|
||||||
"fast_draft": "Fast [draft]",
|
"fast_draft": "Fast [draft]",
|
||||||
@@ -974,6 +976,7 @@
|
|||||||
"go_to_writefull": "Go to Writefull",
|
"go_to_writefull": "Go to Writefull",
|
||||||
"good_news_you_already_purchased_this_add_on": "Good news! You already have this add-on, so no need to pay again.",
|
"good_news_you_already_purchased_this_add_on": "Good news! You already have this add-on, so no need to pay again.",
|
||||||
"good_news_you_are_already_receiving_this_add_on_via_writefull": "Good news! You already have this add-on via your Writefull subscription. No need to pay again.",
|
"good_news_you_are_already_receiving_this_add_on_via_writefull": "Good news! You already have this add-on via your Writefull subscription. No need to pay again.",
|
||||||
|
"got_it": "Got it",
|
||||||
"got_questions": "Got questions?",
|
"got_questions": "Got questions?",
|
||||||
"great_for_getting_started": "Great for getting started",
|
"great_for_getting_started": "Great for getting started",
|
||||||
"great_for_small_teams_and_departments": "Great for small teams and departments",
|
"great_for_small_teams_and_departments": "Great for small teams and departments",
|
||||||
@@ -1413,6 +1416,7 @@
|
|||||||
"maximum_files_uploaded_together": "Maximum __max__ files uploaded together",
|
"maximum_files_uploaded_together": "Maximum __max__ files uploaded together",
|
||||||
"may": "May",
|
"may": "May",
|
||||||
"maybe_later": "Maybe later",
|
"maybe_later": "Maybe later",
|
||||||
|
"meet_the_new_dark_dashboard": "Meet the new dark dashboard",
|
||||||
"member_picker": "Select number of users for group plan",
|
"member_picker": "Select number of users for group plan",
|
||||||
"members_added": "Member(s) added.",
|
"members_added": "Member(s) added.",
|
||||||
"members_management": "Members management",
|
"members_management": "Members management",
|
||||||
@@ -2774,11 +2778,15 @@
|
|||||||
"wed_love_you_to_stay": "We’d love you to stay",
|
"wed_love_you_to_stay": "We’d love you to stay",
|
||||||
"welcome_to_overleaf_opening_workspace": "Welcome to Overleaf. Opening your workspace.",
|
"welcome_to_overleaf_opening_workspace": "Welcome to Overleaf. Opening your workspace.",
|
||||||
"welcome_to_sl": "Welcome to __appName__",
|
"welcome_to_sl": "Welcome to __appName__",
|
||||||
|
"welcome_to_the_dark_side": "Welcome to the dark side",
|
||||||
"well_be_here_when_youre_ready": "We’ll be here when you’re ready to dive back in! 🦆",
|
"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</0>. This means, as someone with edit access, your name and email address will be visible to the project owner and other editors.",
|
"were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "We’re making some <0>changes to project sharing</0>. 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.",
|
"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_given_your_dashboard_a_sleek_new_dark_theme_for_more_comfortable_late_night_research_prefer_the_light_switch_back_anytime_right_here": "We’ve given your dashboard a sleek new dark theme for more comfortable late-night research. Prefer the light? Switch back anytime right here.",
|
||||||
"weve_hit_a_problem_try_starting_a_new_chat": "We’ve hit a problem. Try starting a new chat.",
|
"weve_hit_a_problem_try_starting_a_new_chat": "We’ve hit a problem. Try starting a new chat.",
|
||||||
"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.",
|
"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.",
|
||||||
|
"weve_matched_your_dashboard_theme_to_your_editor_preferences_but_you_can_change_that_here_anytime": "We’ve matched your dashboard theme to your editor preferences, but you can change that here anytime.",
|
||||||
|
"weve_set_your_dashboard_to_dark_mode_to_help_you_stay_focused_if_youre_a_fan_of_a_lighter_look_you_can_easily_switch_themes_here": "We’ve set your dashboard to dark mode to help you stay focused. If you’re a fan of a lighter look, you can easily switch themes here.",
|
||||||
"what_did_you_find_most_helpful": "What did you find most helpful?",
|
"what_did_you_find_most_helpful": "What did you find most helpful?",
|
||||||
"what_do_you_need": "What do you need?",
|
"what_do_you_need": "What do you need?",
|
||||||
"what_do_you_need_help_with": "What do you need help with?",
|
"what_do_you_need_help_with": "What do you need help with?",
|
||||||
@@ -2876,6 +2884,7 @@
|
|||||||
"your_current_plan_gives_you": "By pausing your subscription, you’ll be able to access your premium features faster when you need them again.",
|
"your_current_plan_gives_you": "By pausing your subscription, you’ll be able to access your premium features faster when you need them again.",
|
||||||
"your_current_plan_supports_up_to_x_licenses": "Your current plan supports up to __users__ licenses.",
|
"your_current_plan_supports_up_to_x_licenses": "Your current plan supports up to __users__ licenses.",
|
||||||
"your_current_project_will_revert_to_the_version_from_time": "Your current project will revert to the version from __timestamp__",
|
"your_current_project_will_revert_to_the_version_from_time": "Your current project will revert to the version from __timestamp__",
|
||||||
|
"your_dashboard_is_set_to_match_your_system_theme_automatically_want_a_different_look_pick_your_favorite_theme_here": "Your dashboard is set to match your system theme automatically. Want a different look? Pick your favorite theme here.",
|
||||||
"your_email_is_confirmed": "Your email is confirmed.",
|
"your_email_is_confirmed": "Your email is confirmed.",
|
||||||
"your_feedback_matters_answer_two_quick_questions": "Your feedback matters! Answer two quick questions.",
|
"your_feedback_matters_answer_two_quick_questions": "Your feedback matters! Answer two quick questions.",
|
||||||
"your_git_access_info": "Your Git authentication tokens should be entered whenever you’re prompted for a password.",
|
"your_git_access_info": "Your Git authentication tokens should be entered whenever you’re prompted for a password.",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { EditorProviders } from '../../helpers/editor-providers'
|
|||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from '@codemirror/view'
|
||||||
import type { FC, PropsWithChildren } from 'react'
|
import type { FC, PropsWithChildren } from 'react'
|
||||||
import { EditorViewContext } from '@/features/ide-react/context/editor-view-context'
|
import { EditorViewContext } from '@/features/ide-react/context/editor-view-context'
|
||||||
|
import { TutorialProvider } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
describe('Workbench', { scrollBehavior: false }, function () {
|
describe('Workbench', { scrollBehavior: false }, function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@@ -34,7 +35,7 @@ describe('Workbench', { scrollBehavior: false }, function () {
|
|||||||
return (
|
return (
|
||||||
<EditorProviders
|
<EditorProviders
|
||||||
features={{ aiErrorAssistant: aiAssistEnabled }}
|
features={{ aiErrorAssistant: aiAssistEnabled }}
|
||||||
providers={{ EditorViewProvider }}
|
providers={{ EditorViewProvider, TutorialProvider }}
|
||||||
>
|
>
|
||||||
<div style={{ backgroundColor: '#1b222c' }}>{children}</div>
|
<div style={{ backgroundColor: '#1b222c' }}>{children}</div>
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import { DetachCompileContext } from '@/shared/context/detach-compile-context'
|
|||||||
import { type CompileContext } from '@/shared/context/local-compile-context'
|
import { type CompileContext } from '@/shared/context/local-compile-context'
|
||||||
import { EditorContext } from '@/shared/context/editor-context'
|
import { EditorContext } from '@/shared/context/editor-context'
|
||||||
import { Cobranding } from '@ol-types/cobranding'
|
import { Cobranding } from '@ol-types/cobranding'
|
||||||
|
import { TutorialContext } from '@/shared/context/tutorial-context'
|
||||||
|
|
||||||
// these constants can be imported in tests instead of
|
// these constants can be imported in tests instead of
|
||||||
// using magic strings
|
// using magic strings
|
||||||
@@ -248,6 +249,7 @@ export function EditorProviders({
|
|||||||
LayoutProvider: makeLayoutProvider(layoutContext),
|
LayoutProvider: makeLayoutProvider(layoutContext),
|
||||||
ProjectProvider: makeProjectProvider(project),
|
ProjectProvider: makeProjectProvider(project),
|
||||||
ReferencesProvider: makeReferencesProvider(),
|
ReferencesProvider: makeReferencesProvider(),
|
||||||
|
TutorialProvider: makeTutorialProvider(),
|
||||||
...providers,
|
...providers,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,10 +286,6 @@ export function makeEditorProvider({
|
|||||||
isProjectOwner,
|
isProjectOwner,
|
||||||
renameProject,
|
renameProject,
|
||||||
isPendingEditor: false,
|
isPendingEditor: false,
|
||||||
deactivateTutorial: () => {},
|
|
||||||
inactiveTutorials: [],
|
|
||||||
currentPopup: null,
|
|
||||||
setCurrentPopup: () => {},
|
|
||||||
hasPremiumSuggestion: false,
|
hasPremiumSuggestion: false,
|
||||||
setHasPremiumSuggestion: () => {},
|
setHasPremiumSuggestion: () => {},
|
||||||
premiumSuggestionResetDate: new Date(),
|
premiumSuggestionResetDate: new Date(),
|
||||||
@@ -307,6 +305,25 @@ export function makeEditorProvider({
|
|||||||
return EditorProvider
|
return EditorProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const makeTutorialProvider = (opts?: {
|
||||||
|
inactiveTutorials: string[]
|
||||||
|
}) => {
|
||||||
|
const TutorialProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const value = {
|
||||||
|
deactivateTutorial: () => {},
|
||||||
|
inactiveTutorials: opts?.inactiveTutorials ?? [],
|
||||||
|
currentPopup: null,
|
||||||
|
setCurrentPopup: () => {},
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TutorialContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</TutorialContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return TutorialProvider
|
||||||
|
}
|
||||||
|
|
||||||
const makeReferencesProvider = () => {
|
const makeReferencesProvider = () => {
|
||||||
const ReferencesProvider: FC<PropsWithChildren> = ({ children }) => {
|
const ReferencesProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user