Merge pull request #31950 from overleaf/mj-themed-project-load

[web] Add themed project loading screen

GitOrigin-RevId: b73d8c825b5ab04ede1c9dde10a6891be63758a4
This commit is contained in:
Mathias Jakobsen
2026-03-05 11:00:23 +00:00
committed by Copybot
parent d023f721b7
commit ada0922988
8 changed files with 121 additions and 29 deletions
@@ -880,6 +880,10 @@ const _ProjectController = {
user
)
const initialLoadingScreenTheme = getInitialLoadingScreenTheme(
userSettings?.overallTheme
)
res.render(template, {
title: project.name,
priority_title: true,
@@ -916,6 +920,7 @@ const _ProjectController = {
isMemberOfGroupSubscription: userIsMemberOfGroupSubscription,
hasInstitutionLicence: userHasInstitutionLicence,
},
initialLoadingScreenTheme,
userSettings,
labsExperiments: user.labsExperiments ?? [],
privilegeLevel,
@@ -1275,6 +1280,19 @@ const _ProjectController = {
},
}
function getInitialLoadingScreenTheme(overallThemeSetting) {
switch (overallThemeSetting) {
case 'light-':
return 'light'
case '':
return 'dark'
case 'system':
return 'system'
default:
return 'dark'
}
}
const defaultSettingsForAnonymousUser = userId => ({
id: userId,
ace: {
+1 -1
View File
@@ -14,7 +14,7 @@ block entrypointVar
block content
//- TODO: remove `main` once it's no longer needed by browser extensions
main#ide-root
.loading-screen
div(class=['loading-screen', `loading-screen-init-${initialLoadingScreenTheme}`])
.loading-screen-brand-container
.loading-screen-brand(style='height: 20%')
h3.loading-screen-label #{translate("loading")}
@@ -5,6 +5,7 @@ import getMeta from '@/utils/meta'
import { useConnectionContext } from '../context/connection-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { LoadingError, LoadingErrorProps } from './loading-error'
import useThemedPage from '@/shared/hooks/use-themed-page'
type Part = 'initial' | 'render' | 'connection' | 'translations' | 'project'
@@ -22,7 +23,7 @@ export const Loading: FC<{
setLoaded: (value: boolean) => void
}> = ({ setLoaded }) => {
const [loadedParts, setLoadedParts] = useState(initialParts)
useThemedPage()
const progress = (loadedParts.size / totalParts.size) * 100
useEffect(() => {
@@ -111,28 +111,25 @@ function TokenAccessRoot() {
}
return (
<div>
<div>
<a
href="/project"
// TODO: class name
style={{ fontSize: '2rem', marginLeft: '1rem', color: '#ddd' }}
>
<div className="token-access-container">
<div className="token-access-action-header">
<a href="/project" className="token-access-home-link">
<MaterialIcon type="arrow_left_alt" style={{ fontSize: 'inherit' }} />
</a>
</div>
<div className="token-access-content">
{mode === 'access-attempt' && (
<AccessAttemptScreen
accessError={accessError}
inflight={inflight}
loadingScreenBrandHeight={loadingScreenBrandHeight}
/>
)}
{mode === 'access-attempt' && (
<AccessAttemptScreen
accessError={accessError}
inflight={inflight}
loadingScreenBrandHeight={loadingScreenBrandHeight}
/>
)}
{V1ImportDataScreen && mode === 'v1Import' && v1ImportData && (
<V1ImportDataScreen v1ImportData={v1ImportData} />
)}
{V1ImportDataScreen && mode === 'v1Import' && v1ImportData && (
<V1ImportDataScreen v1ImportData={v1ImportData} />
)}
</div>
</div>
)
}
@@ -1,10 +1,10 @@
import { useEffect } from 'react'
import { useLayoutEffect } from 'react'
import { useActiveOverallTheme } from './use-active-overall-theme'
export default function useThemedPage(featureFlag?: string) {
const activeOverallTheme = useActiveOverallTheme(featureFlag)
useEffect(() => {
useLayoutEffect(() => {
// Sets the body's data-theme attribute for theming
document.body.dataset.theme =
activeOverallTheme === 'dark' ? 'default' : 'light'
@@ -57,3 +57,4 @@
@import 'wiki';
@import 'templates';
@import 'notification-settings';
@import 'token-access';
@@ -1,5 +1,45 @@
@use 'sass:math';
@mixin loading-screen-dark {
--loading-screen-ol-logo-empty-url: url(../../../../public/img/ol-brand/overleaf-o-grey.svg);
--loading-screen-ol-logo-full-url: url(../../../../public/img/ol-brand/overleaf-o-white.svg);
--loading-screen-bg-color: var(--bg-dark-primary);
--loading-screen-color: var(--content-secondary-dark);
--loading-screen-danger-color: var(--content-danger-dark);
}
@mixin loading-screen-light {
--loading-screen-ol-logo-empty-url: url(../../../../public/img/ol-brand/overleaf-o-grey.svg);
--loading-screen-ol-logo-full-url: url(../../../../public/img/ol-brand/overleaf-o.svg);
--loading-screen-bg-color: var(--bg-light-primary);
--loading-screen-color: var(--content-secondary);
--loading-screen-danger-color: var(--content-danger);
}
:root {
@include loading-screen-light;
@include theme('default') {
@include loading-screen-dark;
}
.loading-screen-init-system {
@include loading-screen-light;
@media (prefers-color-scheme: dark) {
@include loading-screen-dark;
}
}
.loading-screen-init-light {
@include loading-screen-light;
}
.loading-screen-init-dark {
@include loading-screen-dark;
}
}
@keyframes blink {
0% {
opacity: 0.2;
@@ -21,7 +61,7 @@
align-items: center;
width: 100%;
height: 100%;
background-color: #fff;
background-color: var(--loading-screen-bg-color);
.loading-screen-brand-container {
min-width: 200px;
@@ -31,8 +71,7 @@
position: relative;
padding-top: math.percentage(math.div(150, 130)); // dimensions of the SVG
height: 0;
background: url(../../../../public/img/ol-brand/overleaf-o-grey.svg)
no-repeat bottom / 100%;
background: var(--loading-screen-ol-logo-empty-url) no-repeat bottom / 100%;
&::after {
content: '';
@@ -41,8 +80,7 @@
right: 0;
bottom: 0;
left: 0;
background: url(../../../../public/img/ol-brand/overleaf-o.svg) no-repeat
bottom / 100%;
background: var(--loading-screen-ol-logo-full-url) no-repeat bottom / 100%;
transition: height 0.5s;
}
}
@@ -52,7 +90,7 @@
padding-top: var(--spacing-09);
font-family: $font-family-serif;
font-size: var(--font-size-07);
color: var(--content-secondary);
color: var(--loading-screen-color);
}
.loading-screen-ellip {
@@ -70,6 +108,6 @@
.loading-screen-error {
margin: 0;
padding-top: var(--spacing-06);
color: var(--content-danger);
color: var(--loading-screen-danger-color);
}
}
@@ -0,0 +1,37 @@
#token-access-page {
height: 100vh;
height: 100dvh;
}
@include theme('default') {
.token-access-content {
@include dark-bg;
}
}
.token-access-container {
display: flex;
flex-direction: column;
height: 100%;
}
.token-access-action-header {
background-color: var(--bg-primary-themed);
flex: 0 0 auto;
}
.token-access-content {
flex: 1 1 auto;
color: var(--content-primary-themed);
h1,
h2 {
color: var(--content-primary-themed);
}
}
.token-access-home-link {
font-size: 2rem;
margin-left: 1rem;
color: var(--content-primary-themed);
}