mirror of
https://github.com/mue/mue.git
synced 2026-06-18 06:27:36 +02:00
Compare commits
1 Commits
dev
...
feature/ov
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05cc274b27 |
@@ -73,9 +73,7 @@ export async function fetchAPIImageData(excludedPun = null) {
|
||||
* Gets background data based on current configuration
|
||||
*/
|
||||
export async function getBackgroundData() {
|
||||
const isOffline =
|
||||
localStorage.getItem('offlineMode') === 'true' ||
|
||||
localStorage.getItem('showWelcome') === 'true';
|
||||
const isOffline = localStorage.getItem('offlineMode') === 'true';
|
||||
|
||||
// Handle favourited background
|
||||
const fav = parseJSON('favourite');
|
||||
|
||||
@@ -8,9 +8,7 @@ import { getBackgroundFilterStyle, getBackgroundOverlayStyle } from '../api/back
|
||||
export function useBackgroundEvents(backgroundData, refreshBackground) {
|
||||
useEffect(() => {
|
||||
const handleEvent = (event) => {
|
||||
if (event === 'welcomeLanguage') {
|
||||
localStorage.setItem('welcomeImage', JSON.stringify(backgroundData));
|
||||
} else if (event === 'background') {
|
||||
if (event === 'background') {
|
||||
handleVisibilityToggle();
|
||||
} else if (['marketplacebackgrounduninstall', 'backgroundwelcome', 'backgroundrefresh'].includes(event)) {
|
||||
refreshBackground();
|
||||
|
||||
@@ -12,16 +12,6 @@ export function useBackgroundLoader(updateBackground, resetBackground) {
|
||||
isLoadingRef.current = true;
|
||||
|
||||
try {
|
||||
// Check for welcome tab first
|
||||
const welcomeTab = localStorage.getItem('welcomeTab');
|
||||
if (welcomeTab) {
|
||||
const welcomeImage = localStorage.getItem('welcomeImage');
|
||||
if (welcomeImage) {
|
||||
updateBackground(JSON.parse(welcomeImage));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = await getBackgroundData();
|
||||
if (data) {
|
||||
updateBackground(data);
|
||||
|
||||
@@ -124,18 +124,19 @@ const Modals = () => {
|
||||
|
||||
const closeWelcome = async () => {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
localStorage.setItem('justCompletedWelcome', 'true');
|
||||
setWelcomeModal(false);
|
||||
|
||||
await tryInstallDefaultPack();
|
||||
|
||||
EventBus.emit('refresh', 'widgetsWelcomeDone');
|
||||
EventBus.emit('refresh', 'widgets');
|
||||
EventBus.emit('refresh', 'backgroundwelcome');
|
||||
};
|
||||
|
||||
const previewWelcome = () => {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
localStorage.setItem('welcomePreview', true);
|
||||
localStorage.setItem('justCompletedWelcome', 'true');
|
||||
setWelcomeModal(false);
|
||||
setPreview(true);
|
||||
EventBus.emit('refresh', 'widgetsWelcome');
|
||||
@@ -181,7 +182,7 @@ const Modals = () => {
|
||||
onRequestClose={() => closeWelcome()}
|
||||
isOpen={welcomeModal}
|
||||
className="Modal welcomemodal mainModal"
|
||||
overlayClassName="Overlay mainModal"
|
||||
overlayClassName="Overlay welcomeOverlay"
|
||||
shouldCloseOnOverlayClick={false}
|
||||
ariaHideApp={false}
|
||||
>
|
||||
|
||||
@@ -19,8 +19,9 @@ const Weather = lazy(() => import('../../weather/Weather'));
|
||||
|
||||
const Widgets = () => {
|
||||
const online = localStorage.getItem('offlineMode') === 'false';
|
||||
const [order, setOrder] = useState(JSON.parse(localStorage.getItem('order')));
|
||||
const [order, setOrder] = useState(JSON.parse(localStorage.getItem('order')) || []);
|
||||
const [welcome, setWelcome] = useState(localStorage.getItem('showWelcome'));
|
||||
const [fadeIn, setFadeIn] = useState(false);
|
||||
|
||||
const enabled = (key) => {
|
||||
return localStorage.getItem(key) === 'true';
|
||||
@@ -43,7 +44,7 @@ const Widgets = () => {
|
||||
const handleRefresh = (data) => {
|
||||
switch (data) {
|
||||
case 'widgets':
|
||||
return setOrder(JSON.parse(localStorage.getItem('order')));
|
||||
return setOrder(JSON.parse(localStorage.getItem('order')) || []);
|
||||
case 'widgetsWelcome':
|
||||
setWelcome(localStorage.getItem('showWelcome'));
|
||||
localStorage.setItem('showWelcome', true);
|
||||
@@ -54,6 +55,15 @@ const Widgets = () => {
|
||||
case 'widgetsWelcomeDone':
|
||||
setWelcome(localStorage.getItem('showWelcome'));
|
||||
window.onbeforeunload = null;
|
||||
|
||||
// Check if user just completed welcome
|
||||
if (localStorage.getItem('justCompletedWelcome') === 'true') {
|
||||
setFadeIn(true);
|
||||
// Clear the flag after animations complete
|
||||
setTimeout(() => {
|
||||
localStorage.removeItem('justCompletedWelcome');
|
||||
}, 2000);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -66,11 +76,15 @@ const Widgets = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// don't show when welcome is there
|
||||
return welcome !== 'false' ? (
|
||||
<WidgetsLayout />
|
||||
) : (
|
||||
<WidgetsLayout>
|
||||
// Determine class based on state
|
||||
const getClassName = () => {
|
||||
if (welcome !== 'false') return 'behind-welcome';
|
||||
if (fadeIn) return 'fade-in-widgets';
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetsLayout className={getClassName()}>
|
||||
<Suspense fallback={<></>}>
|
||||
{enabled('searchBar') && <Search />}
|
||||
{order.map((element, key) => (
|
||||
|
||||
@@ -1,141 +1,16 @@
|
||||
// Importing necessary libraries and components
|
||||
import { useState, useEffect } from 'react';
|
||||
import variables from 'config/variables';
|
||||
import { MdArrowBackIosNew, MdArrowForwardIos, MdOutlinePreview } from 'react-icons/md';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
import { ProgressBar, AsideImage } from './components/Elements';
|
||||
import { Button } from 'components/Elements';
|
||||
import { Wrapper, Panel } from './components/Layout';
|
||||
import { SimpleWelcome } from './components/Sections';
|
||||
|
||||
import './welcome.scss';
|
||||
|
||||
import {
|
||||
Intro,
|
||||
ChooseLanguage,
|
||||
ImportSettings,
|
||||
ThemeSelection,
|
||||
StyleSelection,
|
||||
PrivacyOptions,
|
||||
Final,
|
||||
} from './components/Sections';
|
||||
|
||||
// WelcomeModal component
|
||||
function WelcomeModal({ modalClose, modalSkip }) {
|
||||
// State variables
|
||||
const [currentTab, setCurrentTab] = useState(0);
|
||||
const [buttonText, setButtonText] = useState(variables.getMessage('modals.welcome.buttons.next'));
|
||||
const [importedSettings, setImportedSettings] = useState([]);
|
||||
const finalTab = 6;
|
||||
|
||||
// useEffect hook to handle tab changes
|
||||
useEffect(() => {
|
||||
// Get the current welcome tab from local storage
|
||||
const welcomeTab = localStorage.getItem('welcomeTab');
|
||||
if (welcomeTab) {
|
||||
const tab = Number(welcomeTab);
|
||||
setCurrentTab(tab);
|
||||
setButtonText(
|
||||
tab !== finalTab + 1
|
||||
? variables.getMessage('modals.welcome.buttons.next')
|
||||
: variables.getMessage('modals.welcome.buttons.finish'),
|
||||
);
|
||||
}
|
||||
}, [finalTab]);
|
||||
|
||||
// Function to update the current tab and button text
|
||||
const updateTabAndButtonText = (newTab) => {
|
||||
setCurrentTab(newTab);
|
||||
setButtonText(
|
||||
newTab !== finalTab
|
||||
? variables.getMessage('modals.welcome.buttons.next')
|
||||
: variables.getMessage('modals.welcome.buttons.finish'),
|
||||
);
|
||||
|
||||
localStorage.setItem('bgtransition', true);
|
||||
localStorage.removeItem('welcomeTab');
|
||||
};
|
||||
|
||||
// Functions to navigate to the previous and next tabs
|
||||
const prevTab = () => {
|
||||
updateTabAndButtonText(currentTab - 1);
|
||||
};
|
||||
|
||||
const nextTab = () => {
|
||||
if (buttonText === variables.getMessage('modals.welcome.buttons.finish')) {
|
||||
modalClose();
|
||||
return;
|
||||
}
|
||||
updateTabAndButtonText(currentTab + 1);
|
||||
};
|
||||
|
||||
// Function to switch to a specific tab
|
||||
const switchToTab = (tab) => {
|
||||
updateTabAndButtonText(tab);
|
||||
};
|
||||
|
||||
// Navigation component
|
||||
const Navigation = () => {
|
||||
return (
|
||||
<div className="welcomeButtons">
|
||||
{currentTab !== 0 ? (
|
||||
<Button
|
||||
type="settings"
|
||||
onClick={() => prevTab()}
|
||||
icon={<MdArrowBackIosNew />}
|
||||
label={variables.getMessage('modals.welcome.buttons.previous')}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
type="settings"
|
||||
onClick={() => modalSkip()}
|
||||
icon={<MdOutlinePreview />}
|
||||
label={variables.getMessage('modals.welcome.buttons.preview')}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
type="settings"
|
||||
onClick={() => nextTab()}
|
||||
icon={<MdArrowForwardIos />}
|
||||
label={buttonText}
|
||||
iconPlacement={'right'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Mapping of tab numbers to components
|
||||
const tabComponents = {
|
||||
0: <Intro />,
|
||||
1: <ChooseLanguage />,
|
||||
2: <ImportSettings setImportedSettings={setImportedSettings} switchTab={switchToTab} />,
|
||||
3: <ThemeSelection />,
|
||||
4: <StyleSelection />,
|
||||
5: <PrivacyOptions />,
|
||||
6: (
|
||||
<Final currentTab={currentTab} switchTab={switchToTab} importedSettings={importedSettings} />
|
||||
),
|
||||
};
|
||||
|
||||
// Current tab component
|
||||
const CurrentTab = tabComponents[currentTab] || <Intro />;
|
||||
|
||||
// Render the WelcomeModal component
|
||||
// Render the simplified welcome component
|
||||
return (
|
||||
<Wrapper>
|
||||
<Panel type="aside">
|
||||
<AsideImage currentTab={currentTab} />
|
||||
<ProgressBar numberOfTabs={finalTab + 1} currentTab={currentTab} switchTab={switchToTab} />
|
||||
</Panel>
|
||||
<Panel type="content">
|
||||
{CurrentTab}
|
||||
<Navigation
|
||||
currentTab={currentTab}
|
||||
changeTab={switchToTab}
|
||||
buttonText={buttonText}
|
||||
modalSkip={modalSkip}
|
||||
/>
|
||||
<Panel type="content" className="simpleWelcome">
|
||||
<SimpleWelcome modalClose={modalClose} modalSkip={modalSkip} />
|
||||
</Panel>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
const images = [
|
||||
'/src/assets/icons/undraw_celebration.svg',
|
||||
'/src/assets/icons/undraw_around_the_world_modified.svg',
|
||||
'/src/assets/icons/undraw_add_files_modified.svg',
|
||||
'/src/assets/icons/undraw_dark_mode.svg',
|
||||
'/src/assets/icons/undraw_making_art.svg',
|
||||
'/src/assets/icons/undraw_private_data_modified.svg',
|
||||
'/src/assets/icons/undraw_upgrade_modified.svg',
|
||||
];
|
||||
|
||||
function AsideImage({ currentTab }) {
|
||||
const altTexts = [
|
||||
'Celebration icon',
|
||||
'Around the world icon',
|
||||
'Add files icon',
|
||||
'Dark mode icon',
|
||||
'Making art icon',
|
||||
'Private data icon',
|
||||
'Upgrade icon',
|
||||
];
|
||||
return (
|
||||
<img
|
||||
className="showcaseimg"
|
||||
alt={altTexts[currentTab]}
|
||||
draggable={false}
|
||||
src={images[currentTab]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { AsideImage as default, AsideImage };
|
||||
@@ -1 +0,0 @@
|
||||
export * from './AsideImage';
|
||||
@@ -1,33 +0,0 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
const Step = memo(({ isActive, index, onClick }) => {
|
||||
const className = isActive ? 'step active' : 'step';
|
||||
|
||||
return (
|
||||
<div className={className} onClick={onClick}>
|
||||
<span>{index + 1}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Step.displayName = 'Step';
|
||||
|
||||
function ProgressBar({ numberOfTabs, currentTab, switchTab }) {
|
||||
return (
|
||||
<div className="progressbar">
|
||||
{Array.from({ length: numberOfTabs }, (_, index) => (
|
||||
<Step
|
||||
key={index}
|
||||
isActive={index === currentTab}
|
||||
index={index}
|
||||
onClick={() => switchTab(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const MemoizedProgressBar = memo(ProgressBar);
|
||||
|
||||
export default MemoizedProgressBar;
|
||||
export { MemoizedProgressBar as ProgressBar };
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ProgressBar';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './ProgressBar';
|
||||
export * from './AsideImage';
|
||||
@@ -1,124 +0,0 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { MdOutlineOpenInNew } from 'react-icons/md';
|
||||
import languages from '@/i18n/languages.json';
|
||||
import translationPercentages from '@/i18n/translationPercentages.json';
|
||||
import { useT, useTranslation } from 'contexts/TranslationContext';
|
||||
import variables from 'config/variables';
|
||||
|
||||
import { Radio, SearchInput } from 'components/Form/Settings';
|
||||
import { Header, Content } from '../Layout';
|
||||
|
||||
function ChooseLanguage() {
|
||||
const t = useT();
|
||||
const { language: currentLanguage, changeLanguage } = useTranslation();
|
||||
const title = t('modals.welcome.sections.language.title');
|
||||
const description = t('modals.welcome.sections.language.description');
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const languageOptions = useMemo(() => {
|
||||
const currentLanguageISO = currentLanguage.replace('_', '-');
|
||||
const displayNames = new Intl.DisplayNames([currentLanguageISO], { type: 'language' });
|
||||
|
||||
const mappedLanguages = languages.map((lang) => {
|
||||
const nativeName = lang.name;
|
||||
const isoCode = lang.value.replace('_', '-');
|
||||
const percentage = translationPercentages[lang.value]?.percent || 0;
|
||||
|
||||
let translatedName;
|
||||
try {
|
||||
translatedName = displayNames.of(isoCode);
|
||||
if (translatedName) {
|
||||
translatedName = translatedName.split(' (')[0];
|
||||
}
|
||||
} catch {
|
||||
translatedName = nativeName;
|
||||
}
|
||||
|
||||
const displayName =
|
||||
!translatedName || translatedName === nativeName ? (
|
||||
<>
|
||||
{nativeName} <span style={{ color: '#999', fontSize: '0.85em' }}>({percentage}%)</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{nativeName}{' '}
|
||||
<span style={{ color: '#999', fontSize: '0.85em' }}>
|
||||
({translatedName} • {percentage}%)
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
value: lang.value,
|
||||
nativeName,
|
||||
percentage,
|
||||
searchText: `${nativeName} ${translatedName || ''}`.toLowerCase(),
|
||||
};
|
||||
});
|
||||
|
||||
// Sort alphabetically by native name
|
||||
return mappedLanguages.sort((a, b) => a.nativeName.localeCompare(b.nativeName));
|
||||
}, [currentLanguage]);
|
||||
|
||||
// Filter languages based on search query
|
||||
const filteredLanguages = useMemo(() => {
|
||||
if (!searchQuery.trim()) return languageOptions;
|
||||
const query = searchQuery.toLowerCase();
|
||||
return languageOptions.filter((lang) => lang.searchText.includes(query));
|
||||
}, [languageOptions, searchQuery]);
|
||||
|
||||
// Detect system language
|
||||
const systemLanguage = useMemo(() => {
|
||||
const browserLang = navigator.language.replace('-', '_');
|
||||
return (
|
||||
languages.find((l) => l.value === browserLang) ||
|
||||
languages.find((l) => l.value.startsWith(browserLang.split('_')[0]))
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Header
|
||||
title={title}
|
||||
subtitle={
|
||||
<>
|
||||
{description}{' '}
|
||||
<a
|
||||
href={variables.constants.TRANSLATIONS_URL}
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: '0.2em' }}
|
||||
>
|
||||
GitHub <MdOutlineOpenInNew />
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{systemLanguage && systemLanguage.value !== currentLanguage && (
|
||||
<button
|
||||
className="uploadbg"
|
||||
onClick={() => changeLanguage(systemLanguage.value)}
|
||||
style={{ marginBottom: 12 }}
|
||||
>
|
||||
{t('modals.main.settings.sections.language.use_system')} ({systemLanguage.name})
|
||||
</button>
|
||||
)}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<SearchInput
|
||||
placeholder={t('modals.main.settings.sections.language.search')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div className="languageSettings">
|
||||
<Radio name="language" options={filteredLanguages} category="welcomeLanguage" />
|
||||
</div>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { ChooseLanguage as default, ChooseLanguage };
|
||||
@@ -1,43 +0,0 @@
|
||||
import variables from 'config/variables';
|
||||
import languages from '@/i18n/languages.json';
|
||||
import { Header, Content } from '../Layout';
|
||||
|
||||
function Final(props) {
|
||||
return (
|
||||
<Content>
|
||||
<Header
|
||||
title={variables.getMessage('modals.welcome.sections.final.title')}
|
||||
subtitle={variables.getMessage('modals.welcome.sections.final.description')}
|
||||
/>
|
||||
<span className="title">{variables.getMessage('modals.welcome.sections.final.changes')}</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.welcome.sections.final.changes_description')}
|
||||
</span>
|
||||
<div className="themesToggleArea themesToggleAreaWelcome">
|
||||
<div className="toggle" onClick={() => props.switchTab(1)}>
|
||||
<span>
|
||||
{variables.getMessage('modals.main.settings.sections.language.title')}:{' '}
|
||||
{languages.find((i) => i.value === localStorage.getItem('language')).name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="toggle" onClick={() => props.switchTab(3)}>
|
||||
<span>
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.theme.title')}:{' '}
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.theme.' + localStorage.getItem('theme'),
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{props.importedSettings.length !== 0 && (
|
||||
<div className="toggle" onClick={() => props.switchTab(2)}>
|
||||
{variables.getMessage('modals.welcome.sections.final.imported', {
|
||||
amount: props.importedSettings.length,
|
||||
})}{' '}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { Final as default, Final };
|
||||
@@ -1,66 +0,0 @@
|
||||
import variables from 'config/variables';
|
||||
import { FileUpload } from 'components/Form/Settings';
|
||||
import { MdCloudUpload } from 'react-icons/md';
|
||||
import { importSettings as importSettingsFunction } from 'utils/settings';
|
||||
import { Header, Content } from '../Layout';
|
||||
import default_settings from 'utils/data/default_settings.json';
|
||||
|
||||
function ImportSettings(props) {
|
||||
const importSettings = (e) => {
|
||||
importSettingsFunction(e, true);
|
||||
|
||||
const settings = [];
|
||||
const data = JSON.parse(e);
|
||||
Object.keys(data).forEach((setting) => {
|
||||
// language and theme already shown, the others are only used internally
|
||||
if (
|
||||
setting === 'language' ||
|
||||
setting === 'theme' ||
|
||||
setting === 'firstRun' ||
|
||||
setting === 'showWelcome' ||
|
||||
setting === 'showReminder'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultSetting = default_settings.find((i) => i.name === setting);
|
||||
if (defaultSetting !== undefined) {
|
||||
if (data[setting] === String(defaultSetting.value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
settings.push({
|
||||
name: setting,
|
||||
value: data[setting],
|
||||
});
|
||||
});
|
||||
|
||||
props.setImportedSettings(settings);
|
||||
props.switchTab(6);
|
||||
};
|
||||
return (
|
||||
<Content>
|
||||
<Header
|
||||
title={variables.getMessage('modals.welcome.sections.settings.title')}
|
||||
subtitle={variables.getMessage('modals.welcome.sections.settings.description')}
|
||||
/>
|
||||
<button className="upload" onClick={() => document.getElementById('file-input').click()}>
|
||||
<MdCloudUpload />
|
||||
<span>{variables.getMessage('modals.main.settings.buttons.import')}</span>
|
||||
</button>
|
||||
<FileUpload
|
||||
id="file-input"
|
||||
accept="application/json"
|
||||
type="settings"
|
||||
loadFunction={(e) => importSettings(e)}
|
||||
/>
|
||||
<span className="title">{variables.getMessage('modals.welcome.tip')}</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.welcome.sections.settings.tip')}
|
||||
</span>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { ImportSettings as default, ImportSettings };
|
||||
@@ -1,91 +0,0 @@
|
||||
import variables from 'config/variables';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
import { Header, Content } from '../Layout';
|
||||
import { MdOutlineWavingHand, MdOpenInNew } from 'react-icons/md';
|
||||
import { FaDiscord, FaGithub } from 'react-icons/fa';
|
||||
|
||||
const DISCORD_LINK = 'https://discord.gg/' + variables.constants.DISCORD_SERVER;
|
||||
const GITHUB_LINK =
|
||||
'https://github.com/' + variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME;
|
||||
|
||||
function WelcomeNotice({ config }) {
|
||||
const { icon: Icon, title, subtitle, link } = config;
|
||||
return (
|
||||
<div className="welcomeNotice">
|
||||
<div className="icon">
|
||||
<Icon />
|
||||
</div>
|
||||
<div className="text">
|
||||
<span className="title">{title}</span>
|
||||
<span className="subtitle">{subtitle}</span>
|
||||
</div>
|
||||
{link && (
|
||||
<a href={link} target="_blank" rel="noopener noreferrer">
|
||||
<MdOpenInNew />
|
||||
{variables.getMessage('modals.welcome.sections.intro.notices.github_open')}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Intro() {
|
||||
const [welcomeImage, setWelcomeImage] = useState(0);
|
||||
|
||||
const updateWelcomeImage = useCallback(() => {
|
||||
setWelcomeImage((prevWelcomeImage) => (prevWelcomeImage < 3 ? prevWelcomeImage + 1 : 0));
|
||||
}, []);
|
||||
|
||||
const ShareYourMue = (
|
||||
<div className="examples">
|
||||
<img
|
||||
src={`/src/assets/welcome-images/example${welcomeImage + 1}.webp`}
|
||||
alt="Example Mue setup"
|
||||
draggable={false}
|
||||
/>
|
||||
<span className="shareYourMue">#shareyourmue</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(updateWelcomeImage, 3000);
|
||||
return () => clearInterval(timer);
|
||||
}, [updateWelcomeImage]);
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Header title={variables.getMessage('modals.welcome.sections.intro.title')} />
|
||||
{ShareYourMue}
|
||||
<WelcomeNotice
|
||||
config={{
|
||||
icon: MdOutlineWavingHand,
|
||||
title: variables.getMessage('modals.welcome.sections.intro.title'),
|
||||
subtitle: variables.getMessage('modals.welcome.sections.intro.description'),
|
||||
}}
|
||||
/>
|
||||
<WelcomeNotice
|
||||
config={{
|
||||
icon: FaDiscord,
|
||||
title: variables.getMessage('modals.welcome.sections.intro.notices.discord_title'),
|
||||
subtitle: variables.getMessage(
|
||||
'modals.welcome.sections.intro.notices.discord_description',
|
||||
),
|
||||
link: DISCORD_LINK,
|
||||
}}
|
||||
/>
|
||||
<WelcomeNotice
|
||||
config={{
|
||||
icon: FaGithub,
|
||||
title: variables.getMessage('modals.welcome.sections.intro.notices.github_title'),
|
||||
subtitle: variables.getMessage(
|
||||
'modals.welcome.sections.intro.notices.github_description',
|
||||
),
|
||||
link: GITHUB_LINK,
|
||||
}}
|
||||
/>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { Intro as default, Intro };
|
||||
@@ -1,63 +0,0 @@
|
||||
import variables from 'config/variables';
|
||||
import { MdOutlineOpenInNew } from 'react-icons/md';
|
||||
import { Checkbox } from 'components/Form/Settings';
|
||||
import { Header, Content } from '../Layout';
|
||||
|
||||
function OfflineMode() {
|
||||
return (
|
||||
<>
|
||||
<Checkbox
|
||||
name="offlineMode"
|
||||
text={variables.getMessage('modals.main.settings.sections.advanced.offline_mode')}
|
||||
element=".other"
|
||||
/>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.welcome.sections.privacy.offline_mode_description')}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Links() {
|
||||
return (
|
||||
<>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.welcome.sections.privacy.links.title')}
|
||||
</span>
|
||||
<a
|
||||
className="link"
|
||||
href={variables.constants.PRIVACY_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{variables.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}
|
||||
<MdOutlineOpenInNew />
|
||||
</a>
|
||||
|
||||
<a
|
||||
className="link"
|
||||
href={`https://github.com/${variables.constants.ORG_NAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{variables.getMessage('modals.welcome.sections.privacy.links.source_code')}
|
||||
<MdOutlineOpenInNew />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PrivacyOptions() {
|
||||
return (
|
||||
<Content>
|
||||
<Header
|
||||
title={variables.getMessage('modals.welcome.sections.privacy.title')}
|
||||
subtitle={variables.getMessage('modals.welcome.sections.privacy.description')}
|
||||
/>
|
||||
<OfflineMode />
|
||||
<Links />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { PrivacyOptions as default, PrivacyOptions };
|
||||
104
src/features/welcome/components/Sections/SimpleWelcome.jsx
Normal file
104
src/features/welcome/components/Sections/SimpleWelcome.jsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import variables from 'config/variables';
|
||||
import { useState } from 'react';
|
||||
import { Button } from 'components/Elements';
|
||||
import EventBus from 'utils/eventbus';
|
||||
import { FaDiscord, FaGithub, FaTwitter, FaEnvelope } from 'react-icons/fa';
|
||||
|
||||
function SimpleWelcome({ modalClose, modalSkip }) {
|
||||
const [name, setName] = useState('');
|
||||
|
||||
const handleNameChange = (e) => {
|
||||
const newName = e.target.value;
|
||||
setName(newName);
|
||||
|
||||
// Live update localStorage and trigger greeting refresh
|
||||
if (newName.trim()) {
|
||||
localStorage.setItem('greetingName', newName.trim());
|
||||
} else {
|
||||
localStorage.removeItem('greetingName');
|
||||
}
|
||||
EventBus.emit('refresh', 'greeting');
|
||||
};
|
||||
|
||||
const handleContinue = () => {
|
||||
// Name is already saved via live updates
|
||||
modalClose();
|
||||
};
|
||||
|
||||
const handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleContinue();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="simpleWelcomeContainer">
|
||||
<div className="welcomeContent">
|
||||
<div className="welcomeLogoSection">
|
||||
<img
|
||||
src="src/assets/icons/mue_about.png"
|
||||
alt="Mue"
|
||||
className="mueLogo"
|
||||
draggable={false}
|
||||
/>
|
||||
{/* <h1 className="welcomeTitle">Welcome</h1> */}
|
||||
</div>
|
||||
|
||||
<div className="welcomeNameSection">
|
||||
<label className="nameLabel">
|
||||
{variables.getMessage('modals.welcome.sections.intro.name_label')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="nameInput"
|
||||
placeholder={variables.getMessage('modals.welcome.sections.intro.name_placeholder')}
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
onKeyPress={handleKeyPress}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="welcomeActions">
|
||||
<button className="skipButton" onClick={modalSkip}>
|
||||
{variables.getMessage('modals.welcome.buttons.skip')}
|
||||
</button>
|
||||
<Button
|
||||
type="settings"
|
||||
label={variables.getMessage('modals.welcome.buttons.continue')}
|
||||
onClick={handleContinue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="welcomeCopyright">
|
||||
<div className="icons">
|
||||
<a
|
||||
href={`https://discord.com/${variables.constants.DISCORD_HANDLE}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaDiscord />
|
||||
</a>
|
||||
<a
|
||||
href={`https://github.com/${variables.constants.GITHUB_HANDLE}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaGithub />
|
||||
</a>
|
||||
<a
|
||||
href={`https://twitter.com/${variables.constants.TWITTER_HANDLE}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaTwitter />
|
||||
</a>
|
||||
</div>
|
||||
<span>© {variables.getMessage('branding.author')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { SimpleWelcome as default, SimpleWelcome };
|
||||
@@ -1,53 +0,0 @@
|
||||
import variables from 'config/variables';
|
||||
import { MdArchive, MdOutlineWhatshot } from 'react-icons/md';
|
||||
import { useState } from 'react';
|
||||
import { Header, Content } from '../Layout';
|
||||
|
||||
const STYLES = {
|
||||
NEW: 'new',
|
||||
LEGACY: 'legacy',
|
||||
};
|
||||
|
||||
const StyleSelection = () => {
|
||||
const widgetStyle = localStorage.getItem('widgetStyle') || STYLES.NEW;
|
||||
const [style, setStyle] = useState(widgetStyle);
|
||||
|
||||
const changeStyle = (type) => {
|
||||
setStyle(type);
|
||||
localStorage.setItem('widgetStyle', type);
|
||||
};
|
||||
|
||||
const styleMapping = {
|
||||
[STYLES.LEGACY]: {
|
||||
className: style === STYLES.LEGACY ? 'toggle legacyStyle active' : 'toggle legacyStyle',
|
||||
icon: <MdArchive />,
|
||||
text: variables.getMessage('modals.welcome.sections.style.legacy'),
|
||||
},
|
||||
[STYLES.NEW]: {
|
||||
className: style === STYLES.NEW ? 'toggle newStyle active' : 'toggle newStyle',
|
||||
icon: <MdOutlineWhatshot />,
|
||||
text: variables.getMessage('modals.welcome.sections.style.modern'),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Header
|
||||
title={variables.getMessage('modals.welcome.sections.style.title')}
|
||||
subtitle={variables.getMessage('modals.welcome.sections.style.description')}
|
||||
/>
|
||||
<div className="themesToggleArea">
|
||||
<div className="options">
|
||||
{Object.entries(styleMapping).map(([type, { className, icon, text }]) => (
|
||||
<div className={className} onClick={() => changeStyle(type)} key={type}>
|
||||
{icon}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export { StyleSelection as default, StyleSelection };
|
||||
@@ -1,72 +0,0 @@
|
||||
import variables from 'config/variables';
|
||||
import { useState } from 'react';
|
||||
import { MdAutoAwesome, MdLightMode, MdDarkMode } from 'react-icons/md';
|
||||
import { loadSettings } from 'utils/settings';
|
||||
import { Header, Content } from '../Layout';
|
||||
|
||||
const THEMES = {
|
||||
AUTO: 'auto',
|
||||
LIGHT: 'light',
|
||||
DARK: 'dark',
|
||||
};
|
||||
|
||||
function ThemeSelection() {
|
||||
const currentTheme = localStorage.getItem('theme') || THEMES.AUTO;
|
||||
const [theme, setTheme] = useState(currentTheme);
|
||||
|
||||
const changeTheme = (type) => {
|
||||
setTheme(type);
|
||||
localStorage.setItem('theme', type);
|
||||
loadSettings(true);
|
||||
};
|
||||
|
||||
const themeMapping = {
|
||||
[THEMES.AUTO]: {
|
||||
className: theme === THEMES.AUTO ? 'toggle auto active' : 'toggle auto',
|
||||
icon: <MdAutoAwesome />,
|
||||
text: variables.getMessage('modals.main.settings.sections.appearance.theme.auto'),
|
||||
},
|
||||
[THEMES.LIGHT]: {
|
||||
className: theme === THEMES.LIGHT ? 'toggle lightTheme active' : 'toggle lightTheme',
|
||||
icon: <MdLightMode />,
|
||||
text: variables.getMessage('modals.main.settings.sections.appearance.theme.light'),
|
||||
},
|
||||
[THEMES.DARK]: {
|
||||
className: theme === THEMES.DARK ? 'toggle darkTheme active' : 'toggle darkTheme',
|
||||
icon: <MdDarkMode />,
|
||||
text: variables.getMessage('modals.main.settings.sections.appearance.theme.dark'),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Content>
|
||||
<Header
|
||||
title={variables.getMessage('modals.welcome.sections.theme.title')}
|
||||
subtitle={variables.getMessage('modals.welcome.sections.theme.description')}
|
||||
/>
|
||||
<div className="themesToggleArea">
|
||||
<div
|
||||
className={themeMapping[THEMES.AUTO].className}
|
||||
onClick={() => changeTheme(THEMES.AUTO)}
|
||||
>
|
||||
{themeMapping[THEMES.AUTO].icon}
|
||||
<span>{themeMapping[THEMES.AUTO].text}</span>
|
||||
</div>
|
||||
<div className="options">
|
||||
{Object.entries(themeMapping)
|
||||
.filter(([type]) => type !== THEMES.AUTO)
|
||||
.map(([type, { className, icon, text }]) => (
|
||||
<div className={className} onClick={() => changeTheme(type)} key={type}>
|
||||
{icon}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<span className="title">{variables.getMessage('modals.welcome.tip')}</span>
|
||||
<span className="subtitle">{variables.getMessage('modals.welcome.sections.theme.tip')}</span>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
export { ThemeSelection as default, ThemeSelection };
|
||||
@@ -1,7 +1 @@
|
||||
export * from './Intro';
|
||||
export * from './ChooseLanguage';
|
||||
export * from './ImportSettings';
|
||||
export * from './ThemeSelection';
|
||||
export * from './StyleSelection';
|
||||
export * from './PrivacyOptions';
|
||||
export * from './Final';
|
||||
export * from './SimpleWelcome';
|
||||
|
||||
@@ -3,7 +3,34 @@
|
||||
|
||||
.welcomemodal {
|
||||
@include themed {
|
||||
background-color: t($modal-background);
|
||||
background-color: t($modal);
|
||||
}
|
||||
|
||||
// Disable opening animation - appear instantly
|
||||
&.ReactModal__Content {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Smooth fade out on close
|
||||
&.ReactModal__Content--before-close {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeOverlay {
|
||||
background-color: rgba(0, 0, 0, 0.9) !important;
|
||||
|
||||
// Instant appear, fade out on close
|
||||
&.ReactModal__Overlay {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
&.ReactModal__Overlay--before-close {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +38,6 @@
|
||||
@include themed {
|
||||
background-color: t($modal-background);
|
||||
}
|
||||
|
||||
.MuiFormControlLabel-root {
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -27,10 +53,8 @@
|
||||
|
||||
height: 80vh;
|
||||
width: clamp(60vw, 1200px, 90vw);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(1, 1fr);
|
||||
grid-gap: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
section.aside {
|
||||
display: flex;
|
||||
@@ -48,6 +72,14 @@
|
||||
justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
|
||||
&.simpleWelcome {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
@@ -57,35 +89,208 @@
|
||||
}
|
||||
}
|
||||
|
||||
.progressbar {
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
text-align: center;
|
||||
display: inline;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-size: 18px;
|
||||
.mueLogo {
|
||||
width: 50px;
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: inline-block;
|
||||
border-bottom: 2px solid grey;
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
transition: 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
border-radius: 10px 10px 0 0;
|
||||
.simpleWelcomeContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: #dd4038;
|
||||
border-radius: 10px;
|
||||
border-bottom: 2px solid #dd4038;
|
||||
@include themed {
|
||||
background-color: t($modal);
|
||||
}
|
||||
|
||||
.welcomeContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.welcomeLogoSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
.welcomeTitle {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
letter-spacing: -0.02em;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background: #d21a11;
|
||||
border-bottom: 2px solid #d21a11;
|
||||
border-radius: 10px;
|
||||
.welcomeNameSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
.nameLabel {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.7;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.nameInput {
|
||||
width: 100%;
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: rgba(210, 26, 17, 0.5);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 0 0 3px rgba(210, 26, 17, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeActions {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
|
||||
.skipButton {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding: 0.75rem 1rem;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
button:not(.skipButton) {
|
||||
min-width: 120px;
|
||||
padding: 0.75rem 2rem;
|
||||
font-size: 1rem;
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeCopyright {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
font-size: 1rem;
|
||||
opacity: 0.4;
|
||||
font-weight: 300;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
|
||||
gap: 0.25rem;
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--modal-text);
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
scale: 1.1;
|
||||
@include themed() {
|
||||
color: t($link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeNameInput {
|
||||
margin: 2rem 0;
|
||||
text-align: center;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.3);
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-bottom-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.helperText {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -657,7 +657,11 @@
|
||||
"sections": {
|
||||
"intro": {
|
||||
"title": "Welcome to Mue Tab",
|
||||
"subtitle": "Your new tab, your way",
|
||||
"description": "Thank you for installing Mue, we hope you enjoy your time with our extension.",
|
||||
"name_label": "So, what should we call you?",
|
||||
"name_placeholder": "Name",
|
||||
"name_helper": "We'll use this in your tab",
|
||||
"notices": {
|
||||
"discord_title": "Join our Discord",
|
||||
"discord_description": "Talk with the Mue community and developers",
|
||||
@@ -710,7 +714,9 @@
|
||||
"preview": "Preview",
|
||||
"previous": "Previous",
|
||||
"close": "Close",
|
||||
"finish": "Finish"
|
||||
"finish": "Finish",
|
||||
"continue": "Continue",
|
||||
"skip": "Skip"
|
||||
},
|
||||
"preview": {
|
||||
"description": "You are currently in preview mode. Settings will be reset on closing this tab.",
|
||||
|
||||
@@ -34,6 +34,56 @@ body {
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
animation: fadeIn 1s;
|
||||
|
||||
&.widgets-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.fade-in-widgets {
|
||||
animation: none;
|
||||
|
||||
> * {
|
||||
opacity: 0;
|
||||
animation: fadeInWidget 0.8s ease-out forwards;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
&:nth-child(4) {
|
||||
animation-delay: 0.9s;
|
||||
}
|
||||
&:nth-child(5) {
|
||||
animation-delay: 1.1s;
|
||||
}
|
||||
&:nth-child(6) {
|
||||
animation-delay: 1.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Widgets visible behind welcome modal - no animation, just present
|
||||
&.behind-welcome {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInWidget {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&.no-textBorder {
|
||||
|
||||
Reference in New Issue
Block a user