mirror of
https://github.com/mue/mue.git
synced 2026-07-14 04:24:01 +02:00
feat: implement lazy loading for components and add loading states for improved user experience
This commit is contained in:
56
src/features/misc/components/SettingsLoader.jsx
Normal file
56
src/features/misc/components/SettingsLoader.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import './SettingsLoader.scss';
|
||||
|
||||
const SettingsLoader = () => {
|
||||
return (
|
||||
<div className="settings-loader">
|
||||
<div className="loader-header-section">
|
||||
<div className="loader-header-left">
|
||||
<div className="loader-title shimmer"></div>
|
||||
</div>
|
||||
<div className="loader-header-right">
|
||||
<div className="loader-button shimmer"></div>
|
||||
<div className="loader-button shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="loader-section">
|
||||
<div className="loader-section-title shimmer"></div>
|
||||
<div className="loader-section-subtitle shimmer"></div>
|
||||
</div>
|
||||
|
||||
<div className="loader-options">
|
||||
<div className="loader-option-row">
|
||||
<div className="loader-option-left">
|
||||
<div className="loader-option-label shimmer"></div>
|
||||
<div className="loader-option-desc shimmer"></div>
|
||||
</div>
|
||||
<div className="loader-toggle shimmer"></div>
|
||||
</div>
|
||||
|
||||
<div className="loader-option-row">
|
||||
<div className="loader-option-left">
|
||||
<div className="loader-option-label shimmer"></div>
|
||||
<div className="loader-option-desc shimmer"></div>
|
||||
</div>
|
||||
<div className="loader-toggle shimmer"></div>
|
||||
</div>
|
||||
|
||||
<div className="loader-option-row">
|
||||
<div className="loader-option-left">
|
||||
<div className="loader-option-label shimmer"></div>
|
||||
</div>
|
||||
<div className="loader-select shimmer"></div>
|
||||
</div>
|
||||
|
||||
<div className="loader-option-row">
|
||||
<div className="loader-option-left">
|
||||
<div className="loader-option-label shimmer"></div>
|
||||
</div>
|
||||
<div className="loader-slider shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsLoader;
|
||||
158
src/features/misc/components/SettingsLoader.scss
Normal file
158
src/features/misc/components/SettingsLoader.scss
Normal file
@@ -0,0 +1,158 @@
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.settings-loader {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
position: relative;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.08) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-header-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25px 30px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
margin-bottom: 20px;
|
||||
|
||||
.loader-header-left {
|
||||
.loader-title {
|
||||
width: 180px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-header-right {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
.loader-button {
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loader-section {
|
||||
padding: 20px 30px;
|
||||
margin-bottom: 25px;
|
||||
|
||||
.loader-section-title {
|
||||
width: 220px;
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.loader-section-subtitle {
|
||||
width: 420px;
|
||||
height: 16px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-options {
|
||||
padding: 0 30px 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
|
||||
.loader-option-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
|
||||
.loader-option-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
max-width: 65%;
|
||||
|
||||
.loader-option-label {
|
||||
width: 160px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.loader-option-desc {
|
||||
width: 320px;
|
||||
height: 14px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-toggle {
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.loader-select {
|
||||
width: 200px;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.loader-slider {
|
||||
width: 280px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.light {
|
||||
.settings-loader {
|
||||
.shimmer {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
|
||||
&::after {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(0, 0, 0, 0.08) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.loader-header-section {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/features/misc/components/WelcomeLoader.jsx
Normal file
19
src/features/misc/components/WelcomeLoader.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import './WelcomeLoader.scss';
|
||||
|
||||
const WelcomeLoader = () => {
|
||||
return (
|
||||
<div className="welcome-loader">
|
||||
<div className="welcome-loader-content">
|
||||
<div className="loader-logo shimmer"></div>
|
||||
<div className="loader-text-line large shimmer"></div>
|
||||
<div className="loader-text-line medium shimmer"></div>
|
||||
<div className="loader-button-group">
|
||||
<div className="loader-button shimmer"></div>
|
||||
<div className="loader-button shimmer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WelcomeLoader;
|
||||
85
src/features/misc/components/WelcomeLoader.scss
Normal file
85
src/features/misc/components/WelcomeLoader.scss
Normal file
@@ -0,0 +1,85 @@
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -468px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 468px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
|
||||
.shimmer {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0.05) 0%,
|
||||
rgba(255, 255, 255, 0.15) 50%,
|
||||
rgba(255, 255, 255, 0.05) 100%
|
||||
);
|
||||
background-size: 800px 100px;
|
||||
animation: shimmer 2s infinite linear;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.welcome-loader-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
padding: 40px;
|
||||
|
||||
.loader-logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.loader-text-line {
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.large {
|
||||
width: 350px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
width: 280px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-button-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
|
||||
.loader-button {
|
||||
width: 120px;
|
||||
height: 45px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.light {
|
||||
.welcome-loader {
|
||||
.shimmer {
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgba(0, 0, 0, 0.05) 0%,
|
||||
rgba(0, 0, 0, 0.15) 50%,
|
||||
rgba(0, 0, 0, 0.05) 100%
|
||||
);
|
||||
background-size: 800px 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import variables from 'config/variables';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, lazy, Suspense } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import { MainModal } from 'components/Elements';
|
||||
import Navbar from '../../navbar/Navbar';
|
||||
import Preview from '../../helpers/preview/Preview';
|
||||
import WelcomeLoader from '../components/WelcomeLoader';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
import { parseDeepLink, shouldAutoOpenModal, updateHash } from 'utils/deepLinking';
|
||||
import { install } from 'utils/marketplace';
|
||||
|
||||
import Welcome from 'features/welcome/Welcome';
|
||||
const Welcome = lazy(() => import('features/welcome/Welcome'));
|
||||
|
||||
const DEFAULT_PACK_ID = '0c8a5bdebd13';
|
||||
|
||||
@@ -177,7 +178,9 @@ const Modals = () => {
|
||||
shouldCloseOnOverlayClick={false}
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<Welcome modalClose={() => closeWelcome()} modalSkip={() => previewWelcome()} />
|
||||
<Suspense fallback={<WelcomeLoader />}>
|
||||
<Welcome modalClose={() => closeWelcome()} modalSkip={() => previewWelcome()} />
|
||||
</Suspense>
|
||||
</Modal>
|
||||
{preview && <Preview setup={() => window.location.reload()} />}
|
||||
</>
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, useMemo, lazy, Suspense } from 'react';
|
||||
import { useT } from 'contexts/TranslationContext';
|
||||
|
||||
import Tabs from 'components/Elements/MainModal/backend/Tabs';
|
||||
import SettingsLoader from '../components/SettingsLoader';
|
||||
import './Settings.scss';
|
||||
|
||||
import { NavbarOptions } from 'features/navbar';
|
||||
import { GreetingOptions } from 'features/greeting';
|
||||
import { TimeOptions, DateOptions } from 'features/time';
|
||||
import { QuickLinksOptions } from 'features/quicklinks';
|
||||
import { QuoteOptions } from 'features/quote';
|
||||
import { MessageOptions } from 'features/message';
|
||||
import { BackgroundOptions } from 'features/background';
|
||||
import { SearchOptions } from 'features/search';
|
||||
import { WeatherOptions } from 'features/weather';
|
||||
import { Stats } from 'features/stats';
|
||||
import {
|
||||
About,
|
||||
AdvancedOptions,
|
||||
AppearanceOptions,
|
||||
Changelog,
|
||||
ExperimentalOptions,
|
||||
LanguageOptions,
|
||||
Overview,
|
||||
} from '../sections';
|
||||
const NavbarOptions = lazy(() => import('features/navbar').then((m) => ({ default: m.NavbarOptions })));
|
||||
const GreetingOptions = lazy(() => import('features/greeting').then((m) => ({ default: m.GreetingOptions })));
|
||||
const TimeOptions = lazy(() => import('features/time').then((m) => ({ default: m.TimeOptions })));
|
||||
const DateOptions = lazy(() => import('features/time').then((m) => ({ default: m.DateOptions })));
|
||||
const QuickLinksOptions = lazy(() => import('features/quicklinks').then((m) => ({ default: m.QuickLinksOptions })));
|
||||
const QuoteOptions = lazy(() => import('features/quote').then((m) => ({ default: m.QuoteOptions })));
|
||||
const MessageOptions = lazy(() => import('features/message').then((m) => ({ default: m.MessageOptions })));
|
||||
const BackgroundOptions = lazy(() => import('features/background').then((m) => ({ default: m.BackgroundOptions })));
|
||||
const SearchOptions = lazy(() => import('features/search').then((m) => ({ default: m.SearchOptions })));
|
||||
const WeatherOptions = lazy(() => import('features/weather').then((m) => ({ default: m.WeatherOptions })));
|
||||
const Stats = lazy(() => import('features/stats').then((m) => ({ default: m.Stats })));
|
||||
const About = lazy(() => import('../sections').then((m) => ({ default: m.About })));
|
||||
const AdvancedOptions = lazy(() => import('../sections').then((m) => ({ default: m.AdvancedOptions })));
|
||||
const AppearanceOptions = lazy(() => import('../sections').then((m) => ({ default: m.AppearanceOptions })));
|
||||
const Changelog = lazy(() => import('../sections').then((m) => ({ default: m.Changelog })));
|
||||
const ExperimentalOptions = lazy(() => import('../sections').then((m) => ({ default: m.ExperimentalOptions })));
|
||||
const LanguageOptions = lazy(() => import('../sections').then((m) => ({ default: m.LanguageOptions })));
|
||||
const Overview = lazy(() => import('../sections').then((m) => ({ default: m.Overview })));
|
||||
|
||||
const sections = [
|
||||
{ label: 'modals.main.settings.sections.order.title', name: 'order', component: Overview },
|
||||
@@ -114,11 +115,15 @@ function Settings(props) {
|
||||
>
|
||||
{translatedSections.map(({ label, name, component: Component, translatedLabel }) => (
|
||||
<div key={name} label={translatedLabel} name={name}>
|
||||
<Component
|
||||
currentSubSection={props.currentSubSection}
|
||||
onSubSectionChange={props.onSubSectionChange}
|
||||
sectionName={name}
|
||||
/>
|
||||
<Suspense fallback={<SettingsLoader />}>
|
||||
<div className="settings-content-wrapper">
|
||||
<Component
|
||||
currentSubSection={props.currentSubSection}
|
||||
onSubSectionChange={props.onSubSectionChange}
|
||||
sectionName={name}
|
||||
/>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
13
src/features/misc/views/Settings.scss
Normal file
13
src/features/misc/views/Settings.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@keyframes settingsFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-content-wrapper {
|
||||
animation: settingsFadeIn 0.3s ease-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
@@ -1,83 +1,50 @@
|
||||
import variables from 'config/variables';
|
||||
|
||||
import * as ar from 'i18n/locales/achievements/ar.json';
|
||||
import * as arz from 'i18n/locales/achievements/arz.json';
|
||||
import * as az from 'i18n/locales/achievements/az.json';
|
||||
import * as azb from 'i18n/locales/achievements/azb.json';
|
||||
import * as bn from 'i18n/locales/achievements/bn.json';
|
||||
import * as de_DE from 'i18n/locales/achievements/de_DE.json';
|
||||
import * as el from 'i18n/locales/achievements/el.json';
|
||||
import * as en_GB from 'i18n/locales/achievements/en_GB.json';
|
||||
import * as en_US from 'i18n/locales/achievements/en_US.json';
|
||||
import * as es from 'i18n/locales/achievements/es.json';
|
||||
import * as es_419 from 'i18n/locales/achievements/es_419.json';
|
||||
import * as et from 'i18n/locales/achievements/et.json';
|
||||
import * as fa from 'i18n/locales/achievements/fa.json';
|
||||
import * as fr from 'i18n/locales/achievements/fr.json';
|
||||
import * as hu from 'i18n/locales/achievements/hu.json';
|
||||
import * as id_ID from 'i18n/locales/achievements/id_ID.json';
|
||||
import * as ja from 'i18n/locales/achievements/ja.json';
|
||||
import * as lt from 'i18n/locales/achievements/lt.json';
|
||||
import * as lv from 'i18n/locales/achievements/lv.json';
|
||||
import * as nl from 'i18n/locales/achievements/nl.json';
|
||||
import * as no from 'i18n/locales/achievements/no.json';
|
||||
import * as peo from 'i18n/locales/achievements/peo.json';
|
||||
import * as pt from 'i18n/locales/achievements/pt.json';
|
||||
import * as pt_BR from 'i18n/locales/achievements/pt_BR.json';
|
||||
import * as ru from 'i18n/locales/achievements/ru.json';
|
||||
import * as sl from 'i18n/locales/achievements/sl.json';
|
||||
import * as sv from 'i18n/locales/achievements/sv.json';
|
||||
import * as ta from 'i18n/locales/achievements/ta.json';
|
||||
import * as tr_TR from 'i18n/locales/achievements/tr_TR.json';
|
||||
import * as uk from 'i18n/locales/achievements/uk.json';
|
||||
import * as vi from 'i18n/locales/achievements/vi.json';
|
||||
import * as zh_CN from 'i18n/locales/achievements/zh_CN.json';
|
||||
import * as zh_Hant from 'i18n/locales/achievements/zh_Hant.json';
|
||||
|
||||
import achievements from 'utils/data/achievements.json';
|
||||
|
||||
import { checkAchievements, newAchievements } from './condition';
|
||||
|
||||
const translations = {
|
||||
ar,
|
||||
arz,
|
||||
az,
|
||||
azb,
|
||||
bn,
|
||||
de_DE,
|
||||
el,
|
||||
en_GB,
|
||||
en_US,
|
||||
es,
|
||||
es_419,
|
||||
et,
|
||||
fa,
|
||||
fr,
|
||||
hu,
|
||||
id_ID,
|
||||
ja,
|
||||
lt,
|
||||
lv,
|
||||
nl,
|
||||
no,
|
||||
peo,
|
||||
pt,
|
||||
pt_BR,
|
||||
ru,
|
||||
sl,
|
||||
sv,
|
||||
ta,
|
||||
tr_TR,
|
||||
uk,
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_Hant,
|
||||
};
|
||||
const achievementLocaleLoaders = import.meta.glob('../../../i18n/locales/achievements/*.json');
|
||||
const loadedAchievementTranslations = {};
|
||||
|
||||
// todo: clean this up and condition.js too
|
||||
function getLocalisedAchievementData(id) {
|
||||
const localised = translations[variables.languagecode][id] ||
|
||||
translations.en_GB[id] || { name: id, description: '' };
|
||||
async function loadAchievementTranslation(locale) {
|
||||
if (loadedAchievementTranslations[locale]) {
|
||||
return loadedAchievementTranslations[locale];
|
||||
}
|
||||
|
||||
const path = `../../../i18n/locales/achievements/${locale}.json`;
|
||||
const loader = achievementLocaleLoaders[path];
|
||||
|
||||
if (!loader) {
|
||||
console.warn(`Achievement translation not found for: ${locale}, falling back to en_GB`);
|
||||
return loadAchievementTranslation('en_GB');
|
||||
}
|
||||
|
||||
const module = await loader();
|
||||
const data = module.default;
|
||||
loadedAchievementTranslations[locale] = data;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getLocalisedAchievementData(id) {
|
||||
const locale = variables.languagecode;
|
||||
|
||||
let translations = loadedAchievementTranslations[locale];
|
||||
if (!translations) {
|
||||
translations = await loadAchievementTranslation(locale);
|
||||
}
|
||||
|
||||
let localised = translations[id];
|
||||
|
||||
if (!localised && locale !== 'en_GB') {
|
||||
const fallbackTranslations = loadedAchievementTranslations.en_GB || await loadAchievementTranslation('en_GB');
|
||||
localised = fallbackTranslations[id];
|
||||
}
|
||||
|
||||
if (!localised) {
|
||||
return { name: id, description: '' };
|
||||
}
|
||||
|
||||
return { name: localised.name, description: localised.description };
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ export default class Stats {
|
||||
}
|
||||
|
||||
const newAchievement = newAchievements(stats);
|
||||
newAchievement.forEach((achievement) => {
|
||||
for (const achievement of newAchievement) {
|
||||
if (achievement) {
|
||||
const { name } = getLocalisedAchievementData(achievement.id);
|
||||
const { name } = await getLocalisedAchievementData(achievement.id);
|
||||
toast.info(
|
||||
`🏆 ${variables.getMessage('modals.main.settings.sections.stats.achievement_unlocked', { name: name })}`,
|
||||
{
|
||||
@@ -20,7 +20,7 @@ export default class Stats {
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,7 +62,13 @@ const Stats = () => {
|
||||
};
|
||||
|
||||
const AchievementElement = ({ id, achieved, timestamp }) => {
|
||||
const { name, description } = getLocalisedAchievementData(id);
|
||||
const [achievementData, setAchievementData] = useState({ name: '', description: '' });
|
||||
|
||||
useEffect(() => {
|
||||
getLocalisedAchievementData(id).then((data) => {
|
||||
setAchievementData(data);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div className="achievement">
|
||||
@@ -73,8 +79,8 @@ const Stats = () => {
|
||||
<MdAccessTime /> {new Date(timestamp).toLocaleDateString()}
|
||||
</span>
|
||||
)}
|
||||
<span className="achievementTitle">{name}</span>
|
||||
<span className="subtitle">{achieved ? description : '?????'}</span>
|
||||
<span className="achievementTitle">{achievementData.name}</span>
|
||||
<span className="subtitle">{achieved ? achievementData.description : '?????'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -99,10 +99,6 @@
|
||||
"name": "Português (Brasil)",
|
||||
"value": "pt_BR"
|
||||
},
|
||||
{
|
||||
"name": "Português (Portugal)",
|
||||
"value": "pt_PT"
|
||||
},
|
||||
{
|
||||
"name": "Slovenščina",
|
||||
"value": "sl"
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import variables from 'config/variables';
|
||||
import ExperimentalInit from 'utils/experimental';
|
||||
|
||||
function loadGoogleFontAsync(fontFamily, id) {
|
||||
const fontUrl = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap`;
|
||||
|
||||
const existingLink = document.getElementById(id);
|
||||
if (existingLink) {
|
||||
existingLink.remove();
|
||||
}
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = fontUrl;
|
||||
link.id = id;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* It loads the settings from localStorage and applies them to the page.
|
||||
* @param hotreload - boolean
|
||||
@@ -100,14 +115,17 @@ export function loadSettings(hotreload) {
|
||||
const widgetFontWeight = localStorage.getItem('widgetFontWeight') || '400';
|
||||
const widgetFontStyle = localStorage.getItem('widgetFontStyle') || 'normal';
|
||||
|
||||
const fontFamily = widgetFont.replace(/ /g, '+');
|
||||
const googleFontUrl = `@import url('https://fonts.googleapis.com/css2?family=${fontFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');`;
|
||||
loadGoogleFontAsync(widgetFont, 'customwidgetfont-link');
|
||||
|
||||
const existingStyle = document.getElementById('customwidgetfont');
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
}
|
||||
|
||||
document.head.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`
|
||||
<style id='customwidgetfont'>
|
||||
${googleFontUrl}
|
||||
.greeting, .clock, .quote, .quoteauthor, .date, .weather, .navbar, #center {
|
||||
font-family: '${widgetFont}', 'Lexend Deca', 'Inter', sans-serif !important;
|
||||
font-weight: ${widgetFontWeight};
|
||||
@@ -123,14 +141,17 @@ export function loadSettings(hotreload) {
|
||||
const settingsFontWeight = localStorage.getItem('settingsFontWeight') || '400';
|
||||
const settingsFontStyle = localStorage.getItem('settingsFontStyle') || 'normal';
|
||||
|
||||
const fontFamily = settingsFont.replace(/ /g, '+');
|
||||
const googleFontUrl = `@import url('https://fonts.googleapis.com/css2?family=${fontFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');`;
|
||||
loadGoogleFontAsync(settingsFont, 'customsettingsfont-link');
|
||||
|
||||
const existingStyle = document.getElementById('customsettingsfont');
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
}
|
||||
|
||||
document.head.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`
|
||||
<style id='customsettingsfont'>
|
||||
${googleFontUrl}
|
||||
.modal-content, .settings, .preferences, .marketplace, .welcome {
|
||||
font-family: '${settingsFont}', 'Lexend Deca', 'Inter', sans-serif !important;
|
||||
font-weight: ${settingsFontWeight};
|
||||
@@ -148,14 +169,11 @@ export function loadSettings(hotreload) {
|
||||
const greetingColor = localStorage.getItem('greetingColor');
|
||||
|
||||
if (greetingFont || greetingFontWeight || greetingFontStyle || greetingColor) {
|
||||
let styleContent = '';
|
||||
|
||||
if (greetingFont) {
|
||||
const fontFamily = greetingFont.replace(/ /g, '+');
|
||||
styleContent += `@import url('https://fonts.googleapis.com/css2?family=${fontFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');\n`;
|
||||
loadGoogleFontAsync(greetingFont, 'customgreetingfont-link');
|
||||
}
|
||||
|
||||
styleContent += '.greeting {';
|
||||
let styleContent = '.greeting {';
|
||||
if (greetingFont) {
|
||||
styleContent += `font-family: '${greetingFont}', 'Lexend Deca', 'Inter', sans-serif !important;`;
|
||||
}
|
||||
@@ -170,6 +188,11 @@ export function loadSettings(hotreload) {
|
||||
}
|
||||
styleContent += '}';
|
||||
|
||||
const existingStyle = document.getElementById('customgreetingfont');
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
}
|
||||
|
||||
document.head.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`<style id='customgreetingfont'>${styleContent}</style>`,
|
||||
@@ -182,14 +205,11 @@ export function loadSettings(hotreload) {
|
||||
const clockColor = localStorage.getItem('clockColor');
|
||||
|
||||
if (clockFont || clockFontWeight || clockFontStyle || clockColor) {
|
||||
let styleContent = '';
|
||||
|
||||
if (clockFont) {
|
||||
const fontFamily = clockFont.replace(/ /g, '+');
|
||||
styleContent += `@import url('https://fonts.googleapis.com/css2?family=${fontFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');\n`;
|
||||
loadGoogleFontAsync(clockFont, 'customclockfont-link');
|
||||
}
|
||||
|
||||
styleContent += '.clock, .date {';
|
||||
let styleContent = '.clock, .date {';
|
||||
if (clockFont) {
|
||||
styleContent += `font-family: '${clockFont}', 'Lexend Deca', 'Inter', sans-serif !important;`;
|
||||
}
|
||||
@@ -204,6 +224,11 @@ export function loadSettings(hotreload) {
|
||||
}
|
||||
styleContent += '}';
|
||||
|
||||
const existingStyle = document.getElementById('customclockfont');
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
}
|
||||
|
||||
document.head.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`<style id='customclockfont'>${styleContent}</style>`,
|
||||
@@ -216,14 +241,11 @@ export function loadSettings(hotreload) {
|
||||
const quoteColor = localStorage.getItem('quoteColor');
|
||||
|
||||
if (quoteFont || quoteFontWeight || quoteFontStyle || quoteColor) {
|
||||
let styleContent = '';
|
||||
|
||||
if (quoteFont) {
|
||||
const fontFamily = quoteFont.replace(/ /g, '+');
|
||||
styleContent += `@import url('https://fonts.googleapis.com/css2?family=${fontFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');\n`;
|
||||
loadGoogleFontAsync(quoteFont, 'customquotefont-link');
|
||||
}
|
||||
|
||||
styleContent += '.quote, .quoteauthor {';
|
||||
let styleContent = '.quote, .quoteauthor {';
|
||||
if (quoteFont) {
|
||||
styleContent += `font-family: '${quoteFont}', 'Lexend Deca', 'Inter', sans-serif !important;`;
|
||||
}
|
||||
@@ -238,6 +260,11 @@ export function loadSettings(hotreload) {
|
||||
}
|
||||
styleContent += '}';
|
||||
|
||||
const existingStyle = document.getElementById('customquotefont');
|
||||
if (existingStyle) {
|
||||
existingStyle.remove();
|
||||
}
|
||||
|
||||
document.head.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`<style id='customquotefont'>${styleContent}</style>`,
|
||||
|
||||
@@ -123,7 +123,13 @@ export default defineConfig(({ command, mode }) => {
|
||||
sourcemap: !isProd,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: undefined,
|
||||
manualChunks: {
|
||||
'vendor-react': ['react', 'react-dom', 'react-modal'],
|
||||
'vendor-icons': ['react-icons'],
|
||||
'vendor-dnd': ['@dnd-kit/core', '@dnd-kit/sortable', '@dnd-kit/utilities'],
|
||||
'vendor-ui': ['react-toastify', '@floating-ui/react-dom'],
|
||||
'i18n': ['@eartharoid/i18n'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user