diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs
index b4a834e3e8..08481d9e4a 100644
--- a/services/web/app/src/Features/Project/ProjectController.mjs
+++ b/services/web/app/src/Features/Project/ProjectController.mjs
@@ -50,6 +50,7 @@ import UserGetter from '../User/UserGetter.js'
import { isStandaloneAiAddOnPlanCode } from '../Subscription/AiHelper.js'
import SubscriptionController from '../Subscription/SubscriptionController.mjs'
import { formatCurrency } from '../../util/currency.js'
+import UserSettingsHelper from './UserSettingsHelper.mjs'
const { ObjectId } = mongodb
/**
@@ -811,22 +812,7 @@ const _ProjectController = {
isMemberOfGroupSubscription: userIsMemberOfGroupSubscription,
hasInstitutionLicence: userHasInstitutionLicence,
},
- userSettings: {
- mode: user.ace.mode,
- editorTheme: user.ace.theme,
- fontSize: user.ace.fontSize,
- autoComplete: user.ace.autoComplete,
- autoPairDelimiters: user.ace.autoPairDelimiters,
- pdfViewer: user.ace.pdfViewer,
- syntaxValidation: user.ace.syntaxValidation,
- fontFamily: user.ace.fontFamily || 'lucida',
- lineHeight: user.ace.lineHeight || 'normal',
- overallTheme: user.ace.overallTheme,
- mathPreview: user.ace.mathPreview,
- breadcrumbs: user.ace.breadcrumbs,
- referencesSearchMode: user.ace.referencesSearchMode,
- enableNewEditor: user.ace.enableNewEditor ?? true,
- },
+ userSettings: UserSettingsHelper.buildUserSettings(user),
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 ad03dd24ed..26220d4816 100644
--- a/services/web/app/src/Features/Project/ProjectListController.mjs
+++ b/services/web/app/src/Features/Project/ProjectListController.mjs
@@ -31,6 +31,7 @@ import SubscriptionHelper from '../Subscription/SubscriptionHelper.js'
import PermissionsManager from '../Authorization/PermissionsManager.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.js'
import { OnboardingDataCollection } from '../../models/OnboardingDataCollection.js'
+import UserSettingsHelper from './UserSettingsHelper.mjs'
/**
* @import { GetProjectsRequest, GetProjectsResponse, AllUsersProjects, MongoProject, FormattedProject, MongoTag } from "./types"
@@ -164,7 +165,7 @@ async function projectListPage(req, res, next) {
})
const user = await User.findById(
userId,
- `email emails features alphaProgram betaProgram lastPrimaryEmailCheck lastActive signUpDate refProviders${
+ `email emails features alphaProgram betaProgram lastPrimaryEmailCheck lastActive signUpDate ace refProviders${
isSaas ? ' enrollment writefull completedTutorials aiErrorAssistant' : ''
}`
)
@@ -537,6 +538,12 @@ async function projectListPage(req, res, next) {
}
}
+ await SplitTestHandler.promises.getAssignment(
+ req,
+ res,
+ 'themed-project-dashboard'
+ )
+
res.render('project/list-react', {
title: 'your_projects',
usersBestSubscription,
@@ -545,6 +552,7 @@ async function projectListPage(req, res, next) {
user,
userAffiliations,
userEmails,
+ userSettings: UserSettingsHelper.buildUserSettings(user),
reconfirmedViaSAML,
allInReconfirmNotificationPeriods,
survey,
diff --git a/services/web/app/src/Features/Project/UserSettingsHelper.mjs b/services/web/app/src/Features/Project/UserSettingsHelper.mjs
new file mode 100644
index 0000000000..e4c6440c0d
--- /dev/null
+++ b/services/web/app/src/Features/Project/UserSettingsHelper.mjs
@@ -0,0 +1,22 @@
+function buildUserSettings(user) {
+ return {
+ mode: user.ace.mode,
+ editorTheme: user.ace.theme,
+ fontSize: user.ace.fontSize,
+ autoComplete: user.ace.autoComplete,
+ autoPairDelimiters: user.ace.autoPairDelimiters,
+ pdfViewer: user.ace.pdfViewer,
+ syntaxValidation: user.ace.syntaxValidation,
+ fontFamily: user.ace.fontFamily || 'lucida',
+ lineHeight: user.ace.lineHeight || 'normal',
+ overallTheme: user.ace.overallTheme,
+ mathPreview: user.ace.mathPreview,
+ breadcrumbs: user.ace.breadcrumbs,
+ referencesSearchMode: user.ace.referencesSearchMode,
+ enableNewEditor: user.ace.enableNewEditor ?? true,
+ }
+}
+
+export default {
+ buildUserSettings,
+}
diff --git a/services/web/app/views/project/list-react.pug b/services/web/app/views/project/list-react.pug
index da1ee05108..ba29486146 100644
--- a/services/web/app/views/project/list-react.pug
+++ b/services/web/app/views/project/list-react.pug
@@ -33,6 +33,7 @@ block append meta
meta(name='ol-survey' data-type='json' content=survey)
meta(name='ol-tags' data-type='json' content=tags)
meta(name='ol-portalTemplates' data-type='json' content=portalTemplates)
+ meta(name='ol-userSettings' data-type='json' content=userSettings)
meta(
name='ol-prefetchedProjectsBlob'
data-type='json'
diff --git a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-overall-theme.tsx b/services/web/frontend/js/features/editor-left-menu/hooks/use-set-overall-theme.tsx
index a026da61e5..94f6b741fe 100644
--- a/services/web/frontend/js/features/editor-left-menu/hooks/use-set-overall-theme.tsx
+++ b/services/web/frontend/js/features/editor-left-menu/hooks/use-set-overall-theme.tsx
@@ -1,10 +1,9 @@
-import { useCallback, useEffect } from 'react'
+import { useCallback } from 'react'
import _ from 'lodash'
import { saveUserSettings } from '../utils/api'
import { UserSettings } from '../../../../../types/user-settings'
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import getMeta from '@/utils/meta'
-import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
export default function useSetOverallTheme() {
const { userSettings, setUserSettings } = useUserSettingsContext()
@@ -16,13 +15,6 @@ export default function useSetOverallTheme() {
},
[setUserSettings]
)
- const activeOverallTheme = useActiveOverallTheme()
-
- useEffect(() => {
- // Sets the body's data-theme attribute for theming
- document.body.dataset.theme =
- activeOverallTheme === 'dark' ? 'default' : 'light'
- }, [activeOverallTheme])
return useCallback(
(newOverallTheme: UserSettings['overallTheme']) => {
diff --git a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx
index 488d5476a7..daf2e814f4 100644
--- a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx
+++ b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx
@@ -17,6 +17,7 @@ import {
import EditorSurvey from '../editor-survey'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useStatusFavicon } from '@/features/ide-react/hooks/use-status-favicon'
+import useThemedPage from '@/shared/hooks/use-themed-page'
const MainLayoutNew = lazy(
() => import('@/features/ide-redesign/components/main-layout')
@@ -32,6 +33,7 @@ export default function IdePage() {
useRegisterUserActivity() // record activity and ensure connection when user is active
useHasLintingError() // pass editor:lint hasLintingError to the compiler
useStatusFavicon() // update the favicon based on the compile status
+ useThemedPage() // set the page theme based on user settings
const newEditor = useIsNewEditorEnabled()
const canAccessNewEditor = canUseNewEditor()
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 d57d0eada4..60041059e8 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
@@ -76,9 +76,9 @@ export const ReactContextRoot: FC<
-
-
-
+
+
+
@@ -128,9 +128,9 @@ export const ReactContextRoot: FC<
-
-
-
+
+
+
diff --git a/services/web/frontend/js/features/project-list/components/project-list-root.tsx b/services/web/frontend/js/features/project-list/components/project-list-root.tsx
index 1b3701ace0..3cb12533c7 100644
--- a/services/web/frontend/js/features/project-list/components/project-list-root.tsx
+++ b/services/web/frontend/js/features/project-list/components/project-list-root.tsx
@@ -19,6 +19,8 @@ import WelcomePageContent from '@/features/project-list/components/welcome-page-
import { ProjectListDsNav } from '@/features/project-list/components/project-list-ds-nav'
import { DsNavStyleProvider } from '@/features/project-list/components/use-is-ds-nav'
import CookieBanner from '@/shared/components/cookie-banner'
+import useThemedPage from '@/shared/hooks/use-themed-page'
+import { UserSettingsProvider } from '@/shared/context/user-settings-context'
function ProjectListRoot() {
const { isReady } = useWaitForI18n()
@@ -35,7 +37,9 @@ export function ProjectListRootInner() {
-
+
+
+
@@ -70,6 +74,7 @@ function DefaultPageContentWrapper({ children }: { children: ReactNode }) {
}
function ProjectListPageContent() {
+ useThemedPage('themed-project-dashboard')
const { totalProjectsCount, isLoading, loadProgress } =
useProjectListContext()
diff --git a/services/web/frontend/js/shared/context/ide-context.tsx b/services/web/frontend/js/shared/context/ide-context.tsx
index f882680583..52c5e85bab 100644
--- a/services/web/frontend/js/shared/context/ide-context.tsx
+++ b/services/web/frontend/js/shared/context/ide-context.tsx
@@ -2,6 +2,10 @@ import { createContext, FC, useContext, useEffect, useMemo } from 'react'
import { ScopeValueStore } from '../../../../types/ide/scope-value-store'
import { ScopeEventEmitter } from '../../../../types/ide/scope-event-emitter'
import { Socket } from '@/features/ide-react/connection/types/socket'
+import { useUserSettingsContext } from './user-settings-context'
+import { userStyles } from '../utils/styles'
+import { canUseNewEditor } from '@/features/ide-redesign/utils/new-editor-utils'
+import { useActiveOverallTheme } from '../hooks/use-active-overall-theme'
export type Ide = {
socket: Socket
@@ -44,6 +48,21 @@ export const IdeProvider: FC<
}
}, [unstableStore])
+ const { userSettings } = useUserSettingsContext()
+ const activeOverallTheme = useActiveOverallTheme()
+
+ useEffect(() => {
+ const { fontFamily, lineHeight } = userStyles(userSettings)
+ unstableStore.set('settings', {
+ overallTheme: activeOverallTheme,
+ keybindings: userSettings.mode === 'none' ? 'default' : userSettings.mode,
+ fontFamily,
+ lineHeight,
+ fontSize: userSettings.fontSize,
+ isNewEditor: canUseNewEditor() && userSettings.enableNewEditor,
+ })
+ }, [unstableStore, userSettings, activeOverallTheme])
+
const value = useMemo(() => {
return {
...ide,
diff --git a/services/web/frontend/js/shared/context/user-settings-context.tsx b/services/web/frontend/js/shared/context/user-settings-context.tsx
index cc18c896fa..3ea8257ff0 100644
--- a/services/web/frontend/js/shared/context/user-settings-context.tsx
+++ b/services/web/frontend/js/shared/context/user-settings-context.tsx
@@ -6,14 +6,9 @@ import {
SetStateAction,
FC,
useState,
- useEffect,
} from 'react'
-
import { UserSettings } from '../../../../types/user-settings'
import getMeta from '@/utils/meta'
-import { userStyles } from '../utils/styles'
-import { canUseNewEditor } from '@/features/ide-redesign/utils/new-editor-utils'
-import { useIdeContext } from '@/shared/context/ide-context'
const defaultSettings: UserSettings = {
pdfViewer: 'pdfjs',
@@ -50,20 +45,6 @@ export const UserSettingsProvider: FC = ({
() => getMeta('ol-userSettings') || defaultSettings
)
- // update the global scope 'settings' value, for extensions
- const { unstableStore } = useIdeContext()
- useEffect(() => {
- const { fontFamily, lineHeight } = userStyles(userSettings)
- unstableStore.set('settings', {
- overallTheme: userSettings.overallTheme === 'light-' ? 'light' : 'dark',
- keybindings: userSettings.mode === 'none' ? 'default' : userSettings.mode,
- fontFamily,
- lineHeight,
- fontSize: userSettings.fontSize,
- isNewEditor: canUseNewEditor() && userSettings.enableNewEditor,
- })
- }, [unstableStore, userSettings])
-
const value = useMemo(
() => ({
userSettings,
diff --git a/services/web/frontend/js/shared/hooks/use-themed-page.tsx b/services/web/frontend/js/shared/hooks/use-themed-page.tsx
new file mode 100644
index 0000000000..bbebb95d92
--- /dev/null
+++ b/services/web/frontend/js/shared/hooks/use-themed-page.tsx
@@ -0,0 +1,20 @@
+import { useEffect } from 'react'
+import { useActiveOverallTheme } from './use-active-overall-theme'
+import { useSplitTestContext } from '../context/split-test-context'
+
+export default function useThemedPage(featureFlag?: string) {
+ const { splitTestVariants } = useSplitTestContext()
+
+ let activeOverallTheme = useActiveOverallTheme()
+
+ // Override theme if feature flag is provided and not enabled
+ if (featureFlag && splitTestVariants[featureFlag] !== 'enabled') {
+ activeOverallTheme = 'light'
+ }
+
+ useEffect(() => {
+ // Sets the body's data-theme attribute for theming
+ document.body.dataset.theme =
+ activeOverallTheme === 'dark' ? 'default' : 'light'
+ }, [activeOverallTheme])
+}
diff --git a/services/web/test/unit/src/Project/ProjectListController.test.mjs b/services/web/test/unit/src/Project/ProjectListController.test.mjs
index e1d55b820c..f060fc6eca 100644
--- a/services/web/test/unit/src/Project/ProjectListController.test.mjs
+++ b/services/web/test/unit/src/Project/ProjectListController.test.mjs
@@ -23,6 +23,16 @@ describe('ProjectListController', function () {
lastActive: new Date(2),
signUpDate: new Date(1),
lastLoginIp: '111.111.111.112',
+ ace: {
+ syntaxValidation: true,
+ pdfViewer: 'pdfjs',
+ spellCheckLanguage: 'en',
+ autoPairDelimiters: true,
+ autoComplete: true,
+ fontSize: 12,
+ theme: 'textmate',
+ mode: 'none',
+ },
}
ctx.users = {
'user-1': {