Skip to content
Merged

Pr 4168 #4227

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added ui/desktop/public/background.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
192 changes: 192 additions & 0 deletions ui/desktop/src/components/BackgroundImageFix.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import React, { useEffect } from 'react';

/**
* BackgroundImageFix Component
*
* A clean implementation to ensure background image and overlay display correctly.
* This component injects CSS directly into the document head to ensure proper z-index
* and positioning of background elements.
*/
const BackgroundImageFix: React.FC = () => {
useEffect(() => {
// Create a style element
const styleElement = document.createElement('style');
styleElement.id = 'background-image-fix-styles';

// Define CSS that ensures the background image and overlay are displayed correctly
const css = `
/* Reset any existing background styles that might interfere */
.fixed.inset-0.-z-10,
.fixed.inset-0.-z-9,
.fixed.inset-0.-z-8,
.fixed.inset-0.-z-5,
.fixed.inset-0.-z-1,
[style*="z-index: -10"],
[style*="z-index: -9"],
[style*="z-index: -8"],
[style*="z-index: -5"],
[style*="z-index: -1"] {
z-index: auto !important;
}

/* Remove any background gradients from the app container */
#root > div,
.bg-background-muted,
.animate-gradient-slow,
[class*="bg-gradient"] {
background: none !important;
background-image: none !important;
}

/* Make headers transparent in the sessions view */
.sticky.top-0.z-10.bg-background-default\/80,
.sticky.top-0.z-10.bg-background-default,
.sticky.top-0.z-10,
.bg-background-default\/80.backdrop-blur-md {
background: transparent !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}

/* Make session history headers transparent */
.text-text-muted {
background: transparent !important;
}

/* Root background container - lowest layer */
#root-background-container {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1000;
pointer-events: none;
}

/* Background image layer */
#app-background-image {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-image: url('/background.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
z-index: -900;
}

/* Blur overlay layer */
#app-background-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background-color: rgba(24, 24, 27, 0.5);
transition: background-color 0.5s ease;
z-index: -800;
}

/* Ensure app content is above background */
#root > div {
position: relative;
z-index: 1;
}
`;

// Add the CSS to the style element
styleElement.textContent = css;

// Append the style element to the head
document.head.appendChild(styleElement);

// Create the background container and elements
const backgroundContainer = document.createElement('div');
backgroundContainer.id = 'root-background-container';

const backgroundImage = document.createElement('div');
backgroundImage.id = 'app-background-image';

const backgroundOverlay = document.createElement('div');
backgroundOverlay.id = 'app-background-overlay';

// Assemble the elements
backgroundContainer.appendChild(backgroundImage);
backgroundContainer.appendChild(backgroundOverlay);

// Insert the background container as the first child of the body
document.body.insertBefore(backgroundContainer, document.body.firstChild);

// Find and remove any gradient backgrounds in the application
const removeGradientBackgrounds = () => {
// Target elements with gradient backgrounds
const gradientElements = document.querySelectorAll('[class*="bg-gradient"], .animate-gradient-slow');
gradientElements.forEach(element => {
if (element instanceof HTMLElement) {
element.style.background = 'none';
element.style.backgroundImage = 'none';
}
});

// Specifically target GlobalBackground components
const globalBackgrounds = document.querySelectorAll('.fixed.inset-0.-z-10');
globalBackgrounds.forEach(element => {
if (element instanceof HTMLElement) {
element.style.display = 'none';
}
});

// Make session headers transparent
const sessionHeaders = document.querySelectorAll('.sticky.top-0.z-10, .bg-background-default\\/80.backdrop-blur-md');
sessionHeaders.forEach(element => {
if (element instanceof HTMLElement) {
element.style.background = 'transparent';
element.style.backdropFilter = 'none';
element.style.webkitBackdropFilter = 'none';
}
});
};

// Run immediately and set up an observer to catch any dynamically added elements
removeGradientBackgrounds();

const observer = new MutationObserver((mutations) => {
removeGradientBackgrounds();
});

observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style']
});

// Cleanup function
return () => {
// Disconnect the observer
observer.disconnect();

// Remove the style element
const styleToRemove = document.getElementById('background-image-fix-styles');
if (styleToRemove) {
document.head.removeChild(styleToRemove);
}

// Remove the background container
const containerToRemove = document.getElementById('root-background-container');
if (containerToRemove) {
document.body.removeChild(containerToRemove);
}
};
}, []);

// This component doesn't render anything visible
return null;
};

export default BackgroundImageFix;
203 changes: 203 additions & 0 deletions ui/desktop/src/components/GlobalBlurOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import React, { useEffect, useState } from 'react';
import { useFocusMode } from '../contexts/FocusModeContext';

/**
* GlobalBlurOverlay Component
*
* A reusable component that provides a consistent glassmorphism effect across the application.
* This component:
* 1. Renders a background image from app settings
* 2. Applies a blur effect with theme-aware styling
* 3. Adjusts opacity based on focus mode state
* 4. Handles theme changes automatically
*
* The component is designed to be mounted once at the application level to ensure
* consistent styling across all views.
*/
const GlobalBlurOverlay: React.FC = () => {
const { isInFocusMode } = useFocusMode();
const [isDarkTheme, setIsDarkTheme] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const [backgroundImage, setBackgroundImage] = useState<string | null>(null);
const [backgroundId, setBackgroundId] = useState<string>('default-gradient');

// Update theme detection when it changes
useEffect(() => {
const updateTheme = () => {
setIsDarkTheme(document.documentElement.classList.contains('dark'));
};

// Initial check
updateTheme();

// Set up observer to detect theme changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
updateTheme();
}
});
});

observer.observe(document.documentElement, { attributes: true });

// Load background settings
const savedBackground = localStorage.getItem('dashboard-background');
const savedCustomImage = localStorage.getItem('dashboard-custom-image');

if (savedBackground) {
setBackgroundId(savedBackground);
}

if (savedBackground === 'custom-image' && savedCustomImage) {
setBackgroundImage(savedCustomImage);

// Preload the custom image
const img = new Image();
img.onload = () => {
console.log("Custom background image loaded successfully");
setImageLoaded(true);
};
img.onerror = (e) => {
console.error("Failed to load custom background image:", e);
};
img.src = savedCustomImage;
} else {
// If not using custom image, mark as loaded
setImageLoaded(true);
}

// Listen for background changes
const handleBackgroundChange = (e: CustomEvent) => {
console.log("Background changed:", e.detail);
setBackgroundId(e.detail.backgroundId);

if (e.detail.backgroundId === 'custom-image' && e.detail.customImage) {
setBackgroundImage(e.detail.customImage);
setImageLoaded(true);
} else {
setBackgroundImage(null);
}
};

window.addEventListener('dashboard-background-changed', handleBackgroundChange as EventListener);

return () => {
observer.disconnect();
window.removeEventListener('dashboard-background-changed', handleBackgroundChange as EventListener);
};
}, []);

// Fixed blur intensity
const blurIntensity = 20; // Consistent blur for chat mode

// Determine background color based on focus mode and theme
// Using more grey-tinted overlays to match the home page
const backgroundColor = isInFocusMode
? (isDarkTheme ? 'rgba(24, 24, 27, 0.8)' : 'rgba(245, 245, 250, 0.8)') // 80% opacity in focus mode
: (isDarkTheme ? 'rgba(24, 24, 27, 0.5)' : 'rgba(245, 245, 250, 0.5)'); // 50% opacity in normal mode

// Determine background style based on settings
const getBackgroundStyle = () => {
// If using custom image, return image style
if (backgroundId === 'custom-image' && backgroundImage) {
return {
backgroundImage: `url(${backgroundImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
};
}

// Fallback to default image
return {
backgroundImage: `url('/background.jpg')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
};
};

// Create a div element to append to the body
useEffect(() => {
// Create background and blur overlay elements
const backgroundDiv = document.createElement('div');
const blurDiv = document.createElement('div');

// Set styles for background image
Object.assign(backgroundDiv.style, {
position: 'fixed',
top: '0',
right: '0',
bottom: '0',
left: '0',
zIndex: '-8',
...getBackgroundStyle(),
opacity: imageLoaded ? '1' : '0',
transition: 'opacity 0.5s ease-in-out',
});

// Set styles for blur overlay
Object.assign(blurDiv.style, {
position: 'fixed',
top: '0',
right: '0',
bottom: '0',
left: '0',
zIndex: '-5',
backdropFilter: `blur(${blurIntensity}px)`,
backgroundColor: backgroundColor,
transition: 'background-color 0.5s ease',
pointerEvents: 'none',
});

// Append elements to body
document.body.appendChild(backgroundDiv);
document.body.appendChild(blurDiv);

// Debug info
if (process.env.NODE_ENV === 'development') {
const debugDiv = document.createElement('div');
Object.assign(debugDiv.style, {
position: 'fixed',
bottom: '16px',
right: '16px',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
color: 'white',
padding: '8px',
borderRadius: '4px',
fontSize: '12px',
zIndex: '50',
});

debugDiv.innerHTML = `
Image Loaded: ${imageLoaded ? 'Yes' : 'No'}<br />
Background ID: ${backgroundId}<br />
Custom Image: ${backgroundImage ? 'Yes' : 'No'}<br />
Dark Theme: ${isDarkTheme ? 'Yes' : 'No'}<br />
Focus Mode: ${isInFocusMode ? 'Yes' : 'No'}<br />
Overlay Color: ${backgroundColor}
`;

document.body.appendChild(debugDiv);
}

// Cleanup function
return () => {
document.body.removeChild(backgroundDiv);
document.body.removeChild(blurDiv);

if (process.env.NODE_ENV === 'development') {
const debugElement = document.body.querySelector('[style*="position: fixed"][style*="bottom: 16px"][style*="right: 16px"]');
if (debugElement) {
document.body.removeChild(debugElement);
}
}
};
}, [backgroundId, backgroundImage, imageLoaded, isDarkTheme, isInFocusMode, backgroundColor]);

// Return null since we're appending directly to the body
return null;
};

export default GlobalBlurOverlay;
Loading