diff --git a/apps/remix-ide/src/app/components/preload.tsx b/apps/remix-ide/src/app/components/preload.tsx index 3b430413f86..a16889606bb 100644 --- a/apps/remix-ide/src/app/components/preload.tsx +++ b/apps/remix-ide/src/app/components/preload.tsx @@ -1,10 +1,10 @@ import { RemixApp } from '@remix-ui/app' import axios from 'axios' -import React, { useState, useEffect, useRef, useContext } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' +import React, { useState, useEffect, useRef } from 'react' import { useTracking, TrackingProvider } from '../contexts/TrackingContext' import { TrackingFunction } from '../utils/TrackingFunction' import * as packageJson from '../../../../../package.json' +import * as remixDesktopPackageJson from '../../../../../apps/remixdesktop/package.json' import { fileSystem, fileSystems } from '../files/fileSystem' import { indexedDBFileSystem } from '../files/filesystems/indexedDB' import { localStorageFS } from '../files/filesystems/localStorage' @@ -28,6 +28,7 @@ export const Preload = (props: PreloadProps) => { const remixFileSystems = useRef(new fileSystems()) const remixIndexedDB = useRef(new indexedDBFileSystem()) const localStorageFileSystem = useRef(new localStorageFS()) + const version = isElectron() ? remixDesktopPackageJson.version : packageJson.version // url parameters to e2e test the fallbacks and error warnings const testmigrationFallback = useRef( window.location.hash.includes('e2e_testmigration_fallback=true') && window.location.host === '127.0.0.1:8080' && window.location.protocol === 'http:' @@ -95,14 +96,20 @@ export const Preload = (props: PreloadProps) => { } } - useEffect (() => { - if (isElectron()){ + useEffect(() => { + // Remove pre-splash as soon as React preloader mounts + try { + const splash = document.getElementById('pre-splash') + if (splash && splash.parentNode) splash.parentNode.removeChild(splash) + } catch (_) { /* noop */ } + + if (isElectron()) { loadAppComponent() return } async function loadStorage() { - ;(await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) || trackMatomoEvent?.({ category: 'Storage', action: 'error', name: 'indexedDB not supported', isClick: false }) - ;(await remixFileSystems.current.addFileSystem(localStorageFileSystem.current)) || trackMatomoEvent?.({ category: 'Storage', action: 'error', name: 'localstorage not supported', isClick: false }) + ; (await remixFileSystems.current.addFileSystem(remixIndexedDB.current)) || trackMatomoEvent?.({ category: 'Storage', action: 'error', name: 'indexedDB not supported', isClick: false }) + ; (await remixFileSystems.current.addFileSystem(localStorageFileSystem.current)) || trackMatomoEvent?.({ category: 'Storage', action: 'error', name: 'localstorage not supported', isClick: false }) await testmigration() remixIndexedDB.current.loaded && (await remixIndexedDB.current.checkWorkspaces()) localStorageFileSystem.current.loaded && (await localStorageFileSystem.current.checkWorkspaces()) @@ -125,6 +132,7 @@ export const Preload = (props: PreloadProps) => { } catch (e) { console.log(e) } + return () => { abortController.abort(); }; @@ -132,82 +140,74 @@ export const Preload = (props: PreloadProps) => { return ( <> -
-
- {logo} -
- REMIX IDE -
- v{packageJson.version} +
+
+
+ Remix logo +
REMIX IDE
+
v{version}
-
- {!supported ? ( -
- Your browser does not support any of the filesystems required by Remix. Either change the settings in your browser or use a supported browser. -
- ) : null} - {error ? ( -
- An unknown error has occurred while loading the application. -

- Doing a hard refresh might fix this issue:

-
- Windows:

- Chrome: CTRL + F5 or CTRL + Reload Button -

- Firefox: CTRL + SHIFT + R or CTRL + F5

-
-
- MacOS:

- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button

+ {!supported ? ( +
+ Your browser does not support any of the filesystems required by Remix. Either change the settings in your browser or use a supported browser.
-
- Linux:

- Chrome & FireFox: CTRL + SHIFT + R

+ ) : null} + {error ? ( +
+ An unknown error has occurred while loading the application. +

+ Doing a hard refresh might fix this issue:

+
+ Windows:

- Chrome: CTRL + F5 or CTRL + Reload Button +

- Firefox: CTRL + SHIFT + R or CTRL + F5

+
+
+ MacOS:

- Chrome & FireFox: CMD + SHIFT + R or SHIFT + Reload Button

+
+
+ Linux:

- Chrome & FireFox: CTRL + SHIFT + R

+
-
- ) : null} - {showDownloader ? ( -
- This app will be updated now. Please download a backup of your files now to make sure you don't lose your work. -

- You don't need to do anything else, your files will be available when the app loads. -
{ - await downloadBackup() - }} - data-id="downloadbackup-btn" - className="btn btn-primary mt-1" - > - download backup -
-
{ - await migrateAndLoad() - }} - data-id="skipbackup-btn" - className="btn btn-primary mt-1" - > - skip backup + ) : null} + {showDownloader ? ( +
+ This app will be updated now. Please download a backup of your files now to make sure you don't lose your work. +

+ You don't need to do anything else, your files will be available when the app loads. +
{ + await downloadBackup() + }} + data-id="downloadbackup-btn" + className="btn btn-primary mt-1" + > + download backup +
+
{ + await migrateAndLoad() + }} + data-id="skipbackup-btn" + className="btn btn-primary mt-1" + > + skip backup +
-
- ) : null} - {supported && !error && !showDownloader ? ( -
-
- + ) : null} + {supported && !error && !showDownloader ? ( +
+
- { tip &&
-
DID YOU KNOW
- {tip} -
} -
- ) : null} + ) : null} +
+
+ { tip &&
+
DID YOU KNOW
+ {tip} +
} +
) } -const logo = ( - - - - - -) diff --git a/apps/remix-ide/src/app/components/styles/preload.css b/apps/remix-ide/src/app/components/styles/preload.css index 031263b43fa..3442d4a8d52 100644 --- a/apps/remix-ide/src/app/components/styles/preload.css +++ b/apps/remix-ide/src/app/components/styles/preload.css @@ -1,9 +1,20 @@ .preload-container { + position: relative; /* allow absolute positioning inside */ + display: flex; + flex-direction: column; + justify-content: center; /* centers .preload-main vertically */ + align-items: center; /* centers .preload-main horizontally */ + height: 100vh; +} + +/* Center the main content while allowing a footer-like tips area below */ +.preload-main { display: flex; flex-direction: column; - justify-content: center; align-items: center; - height: 100vh; + justify-content: center; + width: 100%; + position: relative; } .preload-info-container { @@ -20,5 +31,57 @@ .preload-logo { min-width: 200px; max-width: 240px; - padding-bottom: 1.5rem !important; } + +/* Match index.html splash spinner exactly using theme variables */ +.pre-splash-spinner { + width: 24px; + height: 24px; + border: 3px solid var(--bs-border-color-translucent, rgba(255,255,255,0.15)); + border-top-color: var(--bs-primary, #3b82f6); + border-radius: 50%; + animation: pre-splash-spin 1s linear infinite; + display: inline-block; +} + +@keyframes pre-splash-spin { to { transform: rotate(360deg); } } + +/* Match index.html splash typography */ +.preload-title { + margin-top: 12px; + font-size: 18px; + letter-spacing: 0.06em; + color: var(--bs-emphasis-color, #e6ecf8); + text-align: center; +} + +.preload-sub { + margin-top: 4px; + font-size: 13px; + color: var(--bs-secondary-color, #9aa7bd); + text-align: center; +} + +/* Keep tips concise and stable in height */ +.remix_tips { + max-width: 800px; + margin: 0 auto; +} + +.remix_tips span { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.preload-bottom.opt-out { + position: absolute; + top: 50%; + left: 0; + right: 0; + text-align: center; + transform: translateY(calc(50% + 60px)); /* 50% moves it to center, additional offset moves it below the content */ + margin-top: 40px; +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/theme-module.ts b/apps/remix-ide/src/app/tabs/theme-module.ts index b3b88aaddc5..d026d4116cc 100644 --- a/apps/remix-ide/src/app/tabs/theme-module.ts +++ b/apps/remix-ide/src/app/tabs/theme-module.ts @@ -93,18 +93,34 @@ export class ThemeModule extends Plugin { initTheme(callback?: () => void): void { // callback is setTimeOut in app.js which is always passed if (callback) this.initCallback = callback if (this.active) { - document.getElementById('theme-link') ? document.getElementById('theme-link').remove() : null + const existingThemeLink = document.getElementById('theme-link') + if (existingThemeLink) existingThemeLink.remove() const nextTheme = this.themes[this.active] // Theme document.documentElement.style.setProperty('--theme', nextTheme.quality) - const theme = document.createElement('link') - theme.setAttribute('rel', 'stylesheet') - theme.setAttribute('href', nextTheme.url) - theme.setAttribute('id', 'theme-link') - theme.addEventListener('load', () => { - if (callback) callback() - }) - document.head.insertBefore(theme, document.head.firstChild) + // Reuse preloaded theme link if present to avoid double loading + const preloaded = document.getElementById('pre-theme-css') as HTMLLinkElement | null + if (preloaded) { + preloaded.id = 'theme-link' + const desired = new URL(nextTheme.url, document.baseURI).href + const current = preloaded.href + if (current !== desired) { + preloaded.addEventListener('load', () => { if (callback) callback() }, { once: true }) + preloaded.href = nextTheme.url + } else { + // Already loaded desired theme + if (callback) callback() + } + } else { + const theme = document.createElement('link') + theme.setAttribute('rel', 'stylesheet') + theme.setAttribute('href', nextTheme.url) + theme.setAttribute('id', 'theme-link') + theme.addEventListener('load', () => { + if (callback) callback() + }) + document.head.insertBefore(theme, document.head.firstChild) + } //if (callback) callback() } } diff --git a/apps/remix-ide/src/assets/js/pre-splash.js b/apps/remix-ide/src/assets/js/pre-splash.js new file mode 100644 index 00000000000..d3da623dfe8 --- /dev/null +++ b/apps/remix-ide/src/assets/js/pre-splash.js @@ -0,0 +1,49 @@ +(function(){ + try { + // 1) Determine theme from Remix config stored in localStorage + var raw = localStorage.getItem('config-v0.8:.remix.config'); + var theme = ''; + if (raw) { + try { + var cfg = JSON.parse(raw); + theme = String((cfg && (cfg['settings/theme'] || (cfg.settings && (cfg.settings.theme || '')))) || '').toLowerCase(); + } catch (_) { theme = ''; } + } + + var isLight = theme.indexOf('light') !== -1; + document.documentElement.setAttribute('data-pre-theme', isLight ? 'light' : 'dark'); + + // 2) Ensure the actual theme stylesheet is loaded ASAP (two themes supported) + var existing = document.getElementById('pre-theme-css'); + if (!existing) { + var href = isLight + ? 'assets/css/themes/remix-light_powaqg.css' + : 'assets/css/themes/remix-dark_tvx1s2.css'; + var link = document.createElement('link'); + link.id = 'pre-theme-css'; + link.rel = 'stylesheet'; + link.href = href; + document.head.appendChild(link); + } + + // 3) Refine splash content for Electron + OS without waiting for bundles + var ua = navigator.userAgent || ''; + var low = ua.toLowerCase(); + var isElectron = low.indexOf('electron') !== -1; + var title = document.getElementById('pre-splash-title'); + var sub = document.getElementById('pre-splash-sub'); + if (isElectron) { + if (title) title.textContent = 'Remix Desktop'; + if (sub) { + var os = low.indexOf('mac') !== -1 ? 'macOS' : (low.indexOf('win') !== -1 ? 'Windows' : (low.indexOf('linux') !== -1 ? 'Linux' : '')); + sub.textContent = os ? ('Loading… ' + os) : 'Loading…'; + } + } else { + // Web: match Preload case for consistency + if (title) title.textContent = 'REMIX IDE'; + } + } catch (e) { + // Last-resort fallback theme + try { document.documentElement.setAttribute('data-pre-theme','dark'); } catch(_) {} + } +})(); diff --git a/apps/remix-ide/src/index.html b/apps/remix-ide/src/index.html index 2702f3af95f..490741b60b6 100644 --- a/apps/remix-ide/src/index.html +++ b/apps/remix-ide/src/index.html @@ -31,10 +31,70 @@ + + + + + + + + + +