Skip to content

Commit b713cd9

Browse files
authored
feat: enhance loader with dynamic logo selection and quality optimization (#1431)
* feat: enhance loader with dynamic logo selection and quality optimization - Add JavaScript to dynamically select loader image from available formats - Support multiple image formats (jpg, jpeg, png, gif, webp, svg) - Add 'Loading...' text inside SVG fallback image - Optimize image rendering quality with crisp-edges and high-quality settings - Improve CSS layout with flexbox for better centering Signed-off-by: Oleksii Orel <[email protected]>
1 parent 0e066a3 commit b713cd9

File tree

5 files changed

+128
-16
lines changed

5 files changed

+128
-16
lines changed

packages/dashboard-frontend/assets/branding/branding.css

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,39 @@
11
/* Add your branding customizations here. */
22
.main-page-loader {
33
position: absolute;
4-
top: 0;
5-
right: 0;
6-
bottom: 0;
7-
left: 0;
84
z-index: 80;
5+
inset: 0;
6+
97
margin: auto;
10-
color: #fff;
11-
font-size: 24px;
8+
129
font-family: Helvetica, Arial, sans-serif;
10+
font-size: 24px;
11+
color: #fff;
12+
1313
background-color: #000;
1414
}
1515

16-
.main-page-loader .ide-page-loader-content img {
16+
.main-page-loader .ide-page-loader-content {
1717
position: absolute;
18-
top: 0;
19-
right: 0;
20-
bottom: 0;
21-
left: 0;
18+
inset: 0;
19+
20+
display: flex;
21+
flex-direction: column;
22+
gap: 20px;
23+
align-items: center;
24+
justify-content: center;
25+
}
26+
27+
.main-page-loader .ide-page-loader-content img {
2228
max-width: 140px;
2329
max-height: 140px;
24-
margin: auto;
30+
31+
object-fit: contain;
32+
33+
image-rendering: -webkit-optimize-contrast;
34+
image-rendering: crisp-edges;
35+
image-rendering: high-quality;
36+
2537
animation-name: opacity;
2638
animation-duration: 2s;
2739
animation-timing-function: ease-in-out;

packages/dashboard-frontend/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626
<div id="ui-container" style="height: 100%;">
2727
<div class="main-page-loader">
2828
<div class="ide-page-loader-content">
29-
<img src="./assets/branding/loader.svg" alt="Loading..."/>
29+
<img src="data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20200%2050%22%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20text-anchor%3D%22middle%22%20dominant-baseline%3D%22middle%22%20fill%3D%22%23fff%22%20font-family%3D%22Helvetica%2C%20Arial%2C%20sans-serif%22%20font-size%3D%2224%22%3ELoading...%3C%2Ftext%3E%3C%2Fsvg%3E" alt="Loading..."/>
3030
</div>
3131
</div>
3232
</div>
33+
<script src="/dashboard/static/preload/branding-loader.js"></script>
3334
</body>
3435
</html>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2018-2025 Red Hat, Inc.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat, Inc. - initial API and implementation
11+
*/
12+
13+
/**
14+
* Base path to the branding assets directory.
15+
* Used for loading the appropriate loader image format.
16+
*/
17+
const BRANDING_ASSETS_PATH = '/dashboard/assets/branding/';
18+
19+
/**
20+
* Dynamically loads the appropriate loader image based on availability.
21+
* Checks formats in priority order: jpg, jpeg, png, gif, webp, svg (fallback).
22+
* The SVG format is used as the default fallback if no other format is available.
23+
*
24+
* @param basePath - The base path to the branding assets directory
25+
* @param selector - CSS selector for the image element (default: '.ide-page-loader-content img')
26+
*/
27+
function loadBrandingLogo(
28+
basePath: string,
29+
selector: string = '.ide-page-loader-content img',
30+
): void {
31+
const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'] as const;
32+
const imgElement = document.querySelector<HTMLImageElement>(selector);
33+
34+
if (!imgElement) {
35+
return;
36+
}
37+
38+
/**
39+
* Attempts to load an image format by checking if it exists via HEAD request.
40+
* If successful, updates the image source and stops further checks.
41+
*/
42+
async function tryLoadFormat(format: string): Promise<boolean> {
43+
if (!imgElement) {
44+
return false;
45+
}
46+
try {
47+
const response = await fetch(`${basePath}loader.${format}`, { method: 'HEAD' });
48+
if (response.ok) {
49+
imgElement.src = `${basePath}loader.${format}`;
50+
return true;
51+
}
52+
} catch {
53+
// Silently continue to next format if fetch fails
54+
}
55+
return false;
56+
}
57+
58+
/**
59+
* Iterates through all formats and loads the first available one.
60+
* SVG is guaranteed to be checked as the last fallback.
61+
*/
62+
async function loadFirstAvailable(): Promise<void> {
63+
for (const format of imageFormats) {
64+
const loaded = await tryLoadFormat(format);
65+
if (loaded) {
66+
return;
67+
}
68+
}
69+
}
70+
71+
loadFirstAvailable();
72+
}
73+
74+
/**
75+
* Initializes the branding logo loader when the window is fully loaded.
76+
* This ensures the DOM is ready and the image element exists.
77+
*/
78+
function initBrandingLoader(basePath: string): void {
79+
if (document.readyState === 'loading') {
80+
window.addEventListener('load', () => {
81+
loadBrandingLogo(basePath);
82+
});
83+
} else {
84+
// DOM is already loaded, execute immediately
85+
loadBrandingLogo(basePath);
86+
}
87+
}
88+
89+
// Initialize branding loader with the configured base path
90+
initBrandingLoader(BRANDING_ASSETS_PATH);

packages/dashboard-frontend/static/loader.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@
1919
<meta http-equiv="X-UA-Compatible" content="ie=edge">
2020
<link rel="shortcut icon">
2121
<link rel="stylesheet" type="text/css" href="/dashboard/assets/branding/branding.css">
22+
<title></title>
2223
</head>
2324
<body>
2425
<div style="height: 100%;">
2526
<div class="main-page-loader">
2627
<div class="ide-page-loader-content">
27-
<img src="/dashboard/assets/branding/loader.svg" alt="Loading..."/>
28+
<img src="data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20200%2050%22%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20text-anchor%3D%22middle%22%20dominant-baseline%3D%22middle%22%20fill%3D%22%23fff%22%20font-family%3D%22Helvetica%2C%20Arial%2C%20sans-serif%22%20font-size%3D%2224%22%3ELoading...%3C%2Ftext%3E%3C%2Fsvg%3E" alt="Loading..."/>
2829
</div>
2930
</div>
3031
</div>
32+
<script src="/dashboard/static/preload/branding-loader.js"></script>
3133
</body>
3234
</html>

packages/dashboard-frontend/webpack.config.common.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const config = {
2020
client: path.join(__dirname, 'src/index.tsx'),
2121
'service-worker': path.join(__dirname, 'src/service-worker.ts'),
2222
'accept-factory-link': path.join(__dirname, 'src/preload/index.ts'),
23+
'branding-loader': path.join(__dirname, 'src/preload/brandingLoader.ts'),
2324
},
2425
output: {
2526
path: path.join(__dirname, 'lib', 'public/dashboard'),
@@ -31,6 +32,9 @@ const config = {
3132
if (pathData.chunk.name === 'service-worker') {
3233
return '[name].js';
3334
}
35+
if (pathData.chunk.name === 'branding-loader') {
36+
return 'static/preload/[name].js';
37+
}
3438
return '[name].[fullhash:8].js';
3539
},
3640
chunkFilename: '[name].[chunkhash].js',
@@ -40,8 +44,11 @@ const config = {
4044
optimization: {
4145
splitChunks: {
4246
chunks: (chunk) => {
43-
// exclude `accept-factory-link` from being split
44-
return chunk.name !== 'accept-factory-link';
47+
// exclude preload chunks from being split (they should be standalone)
48+
return (
49+
chunk.name !== 'accept-factory-link' &&
50+
chunk.name !== 'branding-loader'
51+
);
4552
},
4653
maxAsyncRequests: 30,
4754
maxInitialRequests: 30,

0 commit comments

Comments
 (0)