mirror of
https://github.com/mue/mue.git
synced 2026-07-20 15:34:10 +02:00
refactor: Move misc options to different folder
This commit is contained in:
383
src/features/misc/sections/About.jsx
Normal file
383
src/features/misc/sections/About.jsx
Normal file
@@ -0,0 +1,383 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { MdEmail, MdContactPage } from 'react-icons/md';
|
||||
import { FaDiscord, FaTwitter } from 'react-icons/fa';
|
||||
import { SiGithubsponsors, SiOpencollective } from 'react-icons/si';
|
||||
import { BiDonateHeart } from 'react-icons/bi';
|
||||
|
||||
import { Tooltip } from 'components/Elements';
|
||||
import other_contributors from 'utils/data/other_contributors.json';
|
||||
|
||||
class About extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
contributors: [],
|
||||
sponsors: [],
|
||||
other_contributors: [],
|
||||
photographers: [],
|
||||
update: variables.getMessage('modals.main.settings.sections.about.version.checking_update'),
|
||||
loading: variables.getMessage('modals.main.loading'),
|
||||
image: document.body.classList.contains('dark')
|
||||
? 'src/assets/icons/mue_about.png'
|
||||
: 'src/assets/icons/mue_about.png',
|
||||
};
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
|
||||
async getGitHubData() {
|
||||
let contributors, sponsors, photographers, versionData;
|
||||
|
||||
try {
|
||||
versionData = await (
|
||||
await fetch(
|
||||
variables.constants.GITHUB_URL +
|
||||
'/repos/' +
|
||||
variables.constants.ORG_NAME +
|
||||
'/' +
|
||||
variables.constants.REPO_NAME +
|
||||
'/releases',
|
||||
{ signal: this.controller.signal },
|
||||
)
|
||||
).json();
|
||||
contributors = await (
|
||||
await fetch(
|
||||
variables.constants.GITHUB_URL +
|
||||
'/repos/' +
|
||||
variables.constants.ORG_NAME +
|
||||
'/' +
|
||||
variables.constants.REPO_NAME +
|
||||
'/contributors',
|
||||
{ signal: this.controller.signal },
|
||||
)
|
||||
).json();
|
||||
sponsors = (
|
||||
await (
|
||||
await fetch(variables.constants.SPONSORS_URL + '/list', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json()
|
||||
).sponsors;
|
||||
photographers = await (
|
||||
await fetch(variables.constants.API_URL + '/images/photographers', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
} catch (e) {
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.setState({
|
||||
update: variables.getMessage('modals.main.settings.sections.about.version.error.title'),
|
||||
loading: variables.getMessage(
|
||||
'modals.main.settings.sections.about.version.error.description',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (sponsors.length === 0) {
|
||||
sponsors = [{ handle: 'empty' }];
|
||||
}
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newVersion = versionData[0].tag_name;
|
||||
|
||||
let update = variables.getMessage('modals.main.settings.sections.about.version.no_update');
|
||||
if (
|
||||
Number(variables.constants.VERSION.replaceAll('.', '')) <
|
||||
Number(newVersion.replaceAll('.', ''))
|
||||
) {
|
||||
update = `${variables.getMessage(
|
||||
'modals.main.settings.sections.about.version.update_available',
|
||||
)}: ${newVersion}`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
// exclude bots
|
||||
contributors: contributors.filter((contributor) => !contributor.login.includes('bot')),
|
||||
sponsors,
|
||||
update,
|
||||
other_contributors,
|
||||
photographers,
|
||||
loading: null,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
this.setState({
|
||||
update: variables.getMessage('modals.main.settings.sections.about.version.checking_update'),
|
||||
loading: variables.getMessage('modals.main.marketplace.offline.description'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.getGitHubData();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// stop making requests
|
||||
this.controller.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div className="settingsRow" style={{ justifyContent: 'center' }}>
|
||||
<div style={{ display: 'flex', flexFlow: 'column', gap: '5px' }}>
|
||||
<img draggable={false} className="aboutLogo" src={this.state.image} alt="Logo" />
|
||||
<div className="aboutText">
|
||||
<span className="title">Mue, By Kaiso</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.about.version.title')}{' '}
|
||||
{variables.constants.VERSION}
|
||||
</span>
|
||||
<span className="subtitle">({this.state.update})</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
Copyright 2018-
|
||||
{new Date().getFullYear()}{' '}
|
||||
<a
|
||||
className="link"
|
||||
href={
|
||||
'https://github.com/' +
|
||||
variables.constants.ORG_NAME +
|
||||
'/' +
|
||||
variables.constants.REPO_NAME +
|
||||
'/graphs/contributors'
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
The Mue Authors
|
||||
</a>
|
||||
,{' '}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
Copyright 2023-2024{' '}
|
||||
<a
|
||||
className="link"
|
||||
href="https://kaiso.one"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{' '}
|
||||
Kaiso One Ltd
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<span className="subtitle">Licensed under the BSD-3-Clause License</span>
|
||||
<span className="subtitle">
|
||||
<a
|
||||
href={variables.constants.PRIVACY_URL}
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{variables.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.contact_us')}
|
||||
</span>
|
||||
<div className="aboutContact">
|
||||
<a
|
||||
className="donateButton"
|
||||
href="https://muetab.com/contact"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<MdContactPage />
|
||||
{variables.getMessage('modals.main.settings.sections.about.form_button')}
|
||||
</a>
|
||||
<Tooltip title={'Email'}>
|
||||
<a
|
||||
href={'mailto:' + variables.constants.EMAIL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<MdEmail />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Twitter'}>
|
||||
<a
|
||||
href={'https://twitter.com/' + variables.constants.TWITTER_HANDLE}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaTwitter />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Discord'}>
|
||||
<a
|
||||
href={'https://discord.gg/' + variables.constants.DISCORD_SERVER}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaDiscord />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.support_mue')}
|
||||
</span>
|
||||
<p>{variables.getMessage('modals.main.settings.sections.about.support_subtitle')}</p>
|
||||
<div className="aboutContact">
|
||||
<a
|
||||
className="donateButton"
|
||||
href={'https://opencollective.com/' + variables.constants.OPENCOLLECTIVE_USERNAME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BiDonateHeart />
|
||||
{variables.getMessage('modals.main.settings.sections.about.support_donate')}
|
||||
</a>
|
||||
<Tooltip title={'GitHub Sponsors'}>
|
||||
<a
|
||||
href={'https://github.com/sponsors/' + variables.constants.ORG_NAME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SiGithubsponsors />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Open Collective'}>
|
||||
<a
|
||||
href={'https://opencollective.com/' + variables.constants.OPENCOLLECTIVE_USERNAME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SiOpencollective />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="settingsRow"
|
||||
style={{ flexFlow: 'column', alignItems: 'flex-start', minHeight: '70px' }}
|
||||
>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.resources_used.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
<a
|
||||
href="https://www.pexels.com"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Pexels
|
||||
</a>
|
||||
,{' '}
|
||||
<a
|
||||
href="https://unsplash.com"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Unsplash
|
||||
</a>{' '}
|
||||
({variables.getMessage('modals.main.settings.sections.about.resources_used.bg_images')})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.contributors')}
|
||||
</span>
|
||||
<p>{this.state.loading}</p>
|
||||
<div className="contributorImages">
|
||||
{this.state.contributors.map(({ login, id }) => (
|
||||
<Tooltip title={login} key={login}>
|
||||
<a href={'https://github.com/' + login} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
draggable={false}
|
||||
src={'https://avatars.githubusercontent.com/u/' + id + '?s=128'}
|
||||
alt={login}
|
||||
></img>
|
||||
</a>
|
||||
</Tooltip>
|
||||
))}
|
||||
{
|
||||
// for those who contributed without opening a pull request
|
||||
this.state.other_contributors.map(({ login, avatar_url }) => (
|
||||
<Tooltip title={login} key={login}>
|
||||
<a href={'https://github.com/' + login} target="_blank" rel="noopener noreferrer">
|
||||
<img draggable={false} src={avatar_url + '&s=128'} alt={login}></img>
|
||||
</a>
|
||||
</Tooltip>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.supporters')}
|
||||
</span>
|
||||
<p>{this.state.loading}</p>
|
||||
<div className="contributorImages">
|
||||
{this.state.sponsors.map(({ handle, avatar }) => {
|
||||
if (handle === 'empty') {
|
||||
return (
|
||||
<p>{variables.getMessage('modals.main.settings.sections.about.no_supporters')}</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={handle} key={handle}>
|
||||
<a
|
||||
href={'https://github.com/' + handle}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img draggable={false} src={avatar.split('?')[0] + '?s=128'} alt={handle}></img>
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="settingsRow"
|
||||
style={{
|
||||
flexFlow: 'column',
|
||||
alignItems: 'flex-start',
|
||||
minHeight: '10px',
|
||||
borderBottom: '0',
|
||||
}}
|
||||
>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.photographers')}
|
||||
</span>
|
||||
{!!this.state.loading ? <p>{this.state.loading}</p> : <></>}
|
||||
<ul>
|
||||
{this.state.photographers.map(({ name, count }) => (
|
||||
<>
|
||||
<li className="subtitle-photographers">
|
||||
{name}
|
||||
<span> ({count} images)</span>
|
||||
</li>
|
||||
</>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { About as default, About };
|
||||
183
src/features/misc/sections/Advanced.jsx
Normal file
183
src/features/misc/sections/Advanced.jsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import variables from 'config/variables';
|
||||
import { useState } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import {
|
||||
MdUpload as ImportIcon,
|
||||
MdDownload as ExportIcon,
|
||||
MdRestartAlt as ResetIcon,
|
||||
MdDataUsage,
|
||||
} from 'react-icons/md';
|
||||
|
||||
import { exportSettings, importSettings } from 'utils/settings';
|
||||
|
||||
import { FileUpload, Text, Switch, Dropdown } from 'components/Form/Settings';
|
||||
import { ResetModal } from 'components/Elements';
|
||||
|
||||
import { Header, Section, Row, Content, Action } from 'components/Layout/Settings';
|
||||
|
||||
import time_zones from 'features/time/timezones.json';
|
||||
|
||||
function AdvancedOptions() {
|
||||
const [resetModal, setResetModal] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
const ADVANCED_SECTION = 'modals.main.settings.sections.advanced';
|
||||
|
||||
const Data = () => {
|
||||
return (
|
||||
<>
|
||||
{localStorage.getItem('welcomePreview') !== 'true' && (
|
||||
<div className="settingsRow">
|
||||
<div className="content">
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.data')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.data_subtitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="action activityButtons">
|
||||
<button onClick={() => setResetModal(true)}>
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
<ResetIcon />
|
||||
</button>
|
||||
<button onClick={() => exportSettings()}>
|
||||
{variables.getMessage('modals.main.settings.buttons.export')}
|
||||
<ExportIcon />
|
||||
</button>
|
||||
<button onClick={() => document.getElementById('file-input').click()}>
|
||||
{variables.getMessage('modals.main.settings.buttons.import')}
|
||||
<ImportIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
let header;
|
||||
if (data) {
|
||||
header = (
|
||||
<Header
|
||||
title={variables.getMessage(`${ADVANCED_SECTION}.title`)}
|
||||
secondaryTitle={variables.getMessage(`${ADVANCED_SECTION}.data`)}
|
||||
goBack={() => setData(false)}
|
||||
report={false}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
header = <Header title={variables.getMessage(`${ADVANCED_SECTION}.title`)} report={false} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{header}
|
||||
{data ? (
|
||||
<>
|
||||
<Data />
|
||||
<Modal
|
||||
closeTimeoutMS={100}
|
||||
onRequestClose={() => setResetModal(false)}
|
||||
isOpen={resetModal}
|
||||
className="Modal resetmodal mainModal"
|
||||
overlayClassName="Overlay resetoverlay"
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<ResetModal modalClose={() => setResetModal(false)} />
|
||||
</Modal>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Section
|
||||
title={variables.getMessage(`${ADVANCED_SECTION}.data`)}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.description',
|
||||
)}
|
||||
onClick={() => setData(true)}
|
||||
icon={<MdDataUsage />}
|
||||
/>
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.offline_mode')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.offline_subtitle',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Switch name="offlineMode" element=".other" />
|
||||
</Action>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.timezone.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.timezone.subtitle',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Dropdown
|
||||
name="timezone"
|
||||
category="timezone"
|
||||
items={[
|
||||
{
|
||||
value: 'auto',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.timezone.automatic',
|
||||
),
|
||||
},
|
||||
...time_zones.map((timezone) => ({ value: timezone, text: timezone })),
|
||||
]}
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.tab_name')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.tab_name_subtitle',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Text name="tabName" default={variables.getMessage('tabname')} category="other" />
|
||||
</Action>
|
||||
</Row>
|
||||
<FileUpload
|
||||
id="file-input"
|
||||
accept="application/json"
|
||||
type="settings"
|
||||
loadFunction={(e) => importSettings(e)}
|
||||
/>
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.custom_css')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.custom_css_subtitle',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Text name="customcss" textarea={true} category="other" customcss={true} />
|
||||
</Action>
|
||||
</Row>
|
||||
<Row final={true}>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.experimental.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.experimental_warning',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Switch
|
||||
name="experimental"
|
||||
text={variables.getMessage('modals.main.settings.enabled')}
|
||||
element=".other"
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { AdvancedOptions as default, AdvancedOptions };
|
||||
289
src/features/misc/sections/Appearance.jsx
Normal file
289
src/features/misc/sections/Appearance.jsx
Normal file
@@ -0,0 +1,289 @@
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
import variables from 'config/variables';
|
||||
|
||||
import { Checkbox, Dropdown, Radio, Slider, Text } from 'components/Form/Settings';
|
||||
import { Header, Section, Row, Content, Action } from 'components/Layout/Settings';
|
||||
|
||||
import { MdAccessibility } from 'react-icons/md';
|
||||
|
||||
import values from 'utils/data/slider_values.json';
|
||||
|
||||
function AppearanceOptions() {
|
||||
const [accessibility, setAccessibility] = useState(false);
|
||||
|
||||
const ThemeSelection = () => {
|
||||
return (
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.theme.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.theme.description',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Radio
|
||||
name="theme"
|
||||
options={[
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.theme.auto'),
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.theme.light'),
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.theme.dark'),
|
||||
value: 'dark',
|
||||
},
|
||||
]}
|
||||
category="other"
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const FontOptions = () => {
|
||||
const fontWeight = 'modals.main.settings.sections.appearance.font.weight';
|
||||
return (
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.font.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.description',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Checkbox
|
||||
name="fontGoogle"
|
||||
text={variables.getMessage('modals.main.settings.sections.appearance.font.google')}
|
||||
category="other"
|
||||
/>
|
||||
<Text
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.font.custom')}
|
||||
name="font"
|
||||
upperCaseFirst={true}
|
||||
category="other"
|
||||
/>
|
||||
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.weight.title',
|
||||
)}
|
||||
name="fontweight"
|
||||
category="other"
|
||||
items={[
|
||||
{
|
||||
value: '100',
|
||||
text: variables.getMessage(fontWeight + '.thin'),
|
||||
},
|
||||
{
|
||||
value: '200',
|
||||
text: variables.getMessage(fontWeight + '.extra_light'),
|
||||
},
|
||||
{
|
||||
value: '300',
|
||||
text: variables.getMessage(fontWeight + '.light'),
|
||||
},
|
||||
{
|
||||
value: '400',
|
||||
text: variables.getMessage(fontWeight + '.normal'),
|
||||
},
|
||||
{
|
||||
value: '500',
|
||||
text: variables.getMessage(fontWeight + '.medium'),
|
||||
},
|
||||
{
|
||||
value: '600',
|
||||
text: variables.getMessage(fontWeight + '.semi_bold'),
|
||||
},
|
||||
{
|
||||
value: '700',
|
||||
text: variables.getMessage(fontWeight + '.bold'),
|
||||
},
|
||||
{
|
||||
value: '800',
|
||||
text: variables.getMessage(fontWeight + '.extra_bold'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.style.title',
|
||||
)}
|
||||
name="fontstyle"
|
||||
category="other"
|
||||
items={[
|
||||
{
|
||||
value: 'normal',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.style.normal',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'italic',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.style.italic',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'oblique',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.style.oblique',
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const WidgetStyle = () => {
|
||||
return (
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.style.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.style.description',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Radio
|
||||
name="widgetStyle"
|
||||
element=".other"
|
||||
options={[
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.style.legacy'),
|
||||
value: 'legacy',
|
||||
},
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.style.new'),
|
||||
value: 'new',
|
||||
},
|
||||
]}
|
||||
category="widgets"
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const AccessibilityOptions = () => {
|
||||
return (
|
||||
<Row final={true}>
|
||||
<Content
|
||||
title={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.title',
|
||||
)}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.description',
|
||||
)}
|
||||
/>
|
||||
<Action>
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.title',
|
||||
)}
|
||||
name="textBorder"
|
||||
category="other"
|
||||
items={[
|
||||
{
|
||||
value: 'new',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.new',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'true',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.old',
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
text: variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.none',
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Checkbox
|
||||
text={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.animations',
|
||||
)}
|
||||
name="animations"
|
||||
category="other"
|
||||
/>
|
||||
<Slider
|
||||
title={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.toast_duration',
|
||||
)}
|
||||
name="toastDisplayTime"
|
||||
default="2500"
|
||||
step="100"
|
||||
min="500"
|
||||
max="5000"
|
||||
marks={values.toast}
|
||||
display={
|
||||
' ' +
|
||||
variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.milliseconds',
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Action>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
let header;
|
||||
if (accessibility) {
|
||||
header = (
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.title')}
|
||||
secondaryTitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.title',
|
||||
)}
|
||||
goBack={() => setAccessibility(false)}
|
||||
report={false}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
header = (
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.title')}
|
||||
report={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{header}
|
||||
{accessibility ? (
|
||||
<AccessibilityOptions />
|
||||
) : (
|
||||
<>
|
||||
<Section
|
||||
title={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.title',
|
||||
)}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.description',
|
||||
)}
|
||||
icon={<MdAccessibility />}
|
||||
onClick={() => setAccessibility(true)}
|
||||
/>
|
||||
<ThemeSelection />
|
||||
<FontOptions />
|
||||
<WidgetStyle />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const MemoizedAppearanceOptions = memo(AppearanceOptions);
|
||||
export { MemoizedAppearanceOptions as default, MemoizedAppearanceOptions as AppearanceOptions };
|
||||
162
src/features/misc/sections/Changelog.jsx
Normal file
162
src/features/misc/sections/Changelog.jsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import { MdOutlineWifiOff } from 'react-icons/md';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import Lightbox from '../../marketplace/components/Elements/Lightbox/Lightbox';
|
||||
|
||||
class Changelog extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
title: null,
|
||||
showLightbox: false,
|
||||
lightboxImg: null,
|
||||
};
|
||||
this.offlineMode = localStorage.getItem('offlineMode') === 'true';
|
||||
this.controller = new AbortController();
|
||||
this.changelog = createRef();
|
||||
}
|
||||
|
||||
async getUpdate() {
|
||||
const res = await fetch(variables.constants.BLOG_POST + '/index.json', {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
|
||||
if (res.status === 404) {
|
||||
this.setState({ error: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
let date = new Date(data.date.split(' ')[0]);
|
||||
date = date.toLocaleDateString(variables.languagecode.replace('_', '-'), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
||||
this.setState({
|
||||
title: data.title,
|
||||
date,
|
||||
image: data.featured_image || null,
|
||||
author: variables.getMessage('modals.main.settings.sections.changelog.by', {
|
||||
author: data.authors.join(', '),
|
||||
}),
|
||||
content: data.markdown,
|
||||
});
|
||||
|
||||
// lightbox etc
|
||||
const images = this.changelog.current.getElementsByTagName('img');
|
||||
const links = this.changelog.current.getElementsByTagName('a');
|
||||
|
||||
for (const img of images) {
|
||||
img.draggable = false;
|
||||
img.onclick = () => {
|
||||
this.setState({
|
||||
showLightbox: true,
|
||||
lightboxImg: img.src,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// open in new tab
|
||||
for (let link = 0; link < links.length; link++) {
|
||||
links[link].target = '_blank';
|
||||
links[link].rel = 'noopener noreferrer';
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || this.offlineMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getUpdate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// stop making requests
|
||||
this.controller.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
const errorMessage = (msg) => {
|
||||
return (
|
||||
<div className="emptyItems">
|
||||
<div className="emptyMessage">{msg}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (navigator.onLine === false || this.offlineMode) {
|
||||
return errorMessage(
|
||||
<>
|
||||
<MdOutlineWifiOff />
|
||||
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
|
||||
<p className="description">
|
||||
{variables.getMessage('modals.main.marketplace.offline.description')}
|
||||
</p>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.error === true) {
|
||||
return errorMessage(
|
||||
<>
|
||||
<MdOutlineWifiOff />
|
||||
<span className="title">{variables.getMessage('modals.main.error_boundary.title')}</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.error_boundary.message')}
|
||||
</span>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.title) {
|
||||
return errorMessage(
|
||||
<div className="loaderHolder">
|
||||
<div id="loader"></div>
|
||||
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="changelogtab" ref={this.changelog}>
|
||||
<h1>{this.state.title}</h1>
|
||||
<h5>
|
||||
{this.state.author} • {this.state.date}
|
||||
</h5>
|
||||
{this.state.image && (
|
||||
<img
|
||||
draggable={false}
|
||||
src={this.state.image}
|
||||
alt={this.state.title}
|
||||
className="updateImage"
|
||||
/>
|
||||
)}
|
||||
<div className="updateChangelog">{this.state.content}</div>
|
||||
<Modal
|
||||
closeTimeoutMS={100}
|
||||
onRequestClose={() => this.setState({ showLightbox: false })}
|
||||
isOpen={this.state.showLightbox}
|
||||
className="Modal lightBoxModal"
|
||||
overlayClassName="Overlay resetoverlay"
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<Lightbox
|
||||
modalClose={() => this.setState({ showLightbox: false })}
|
||||
img={this.state.lightboxImg}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Changelog as default, Changelog };
|
||||
82
src/features/misc/sections/Experimental.jsx
Normal file
82
src/features/misc/sections/Experimental.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import variables from 'config/variables';
|
||||
import { useState, memo } from 'react';
|
||||
import Checkbox from '../../../components/Form/Settings/Checkbox/Checkbox';
|
||||
import Slider from '../../../components/Form/Settings/Slider/Slider';
|
||||
import { TextField } from '@mui/material';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
import values from 'utils/data/slider_values.json';
|
||||
|
||||
import { Row, Content, Action } from '../../../components/Layout/Settings/Item/SettingsItem';
|
||||
|
||||
function ExperimentalOptions() {
|
||||
const [eventType, setEventType] = useState();
|
||||
const [eventName, setEventName] = useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.experimental.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.experimental.warning')}
|
||||
</span>
|
||||
<Row>
|
||||
<Content
|
||||
title={variables.getMessage('modals.main.settings.sections.experimental.developer')}
|
||||
/>
|
||||
<Action>
|
||||
<Checkbox name="debug" text="Debug hotkey (Ctrl + #)" element=".other" />
|
||||
<Slider
|
||||
title="Debug timeout"
|
||||
name="debugtimeout"
|
||||
min="0"
|
||||
max="5000"
|
||||
default="0"
|
||||
step="100"
|
||||
marks={values.experimental}
|
||||
element=".other"
|
||||
/>
|
||||
<p style={{ textAlign: 'left' }}>Send Event</p>
|
||||
<TextField
|
||||
label={'Type'}
|
||||
value={eventType}
|
||||
onChange={(e) => setEventType(e.target.value)}
|
||||
spellCheck={false}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
label={'Name'}
|
||||
value={eventName}
|
||||
onChange={(e) => setEventName(e.target.value)}
|
||||
spellCheck={false}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<button className="uploadbg" onClick={() => EventBus.emit(eventType, eventName)}>
|
||||
Send
|
||||
</button>
|
||||
</Action>
|
||||
</Row>
|
||||
<Row final={true}>
|
||||
<Content title="Data" />
|
||||
<Action>
|
||||
<button
|
||||
className="reset"
|
||||
style={{ marginLeft: '0px' }}
|
||||
onClick={() => localStorage.clear()}
|
||||
>
|
||||
Clear LocalStorage
|
||||
</button>
|
||||
</Action>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const MemoizedExperimentalOptions = memo(ExperimentalOptions);
|
||||
export {
|
||||
MemoizedExperimentalOptions as default,
|
||||
MemoizedExperimentalOptions as ExperimentalOptions,
|
||||
};
|
||||
113
src/features/misc/sections/Language.jsx
Normal file
113
src/features/misc/sections/Language.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { MdOutlineOpenInNew } from 'react-icons/md';
|
||||
|
||||
import { Radio } from 'components/Form/Settings';
|
||||
|
||||
import languages from '@/i18n/languages.json';
|
||||
|
||||
class LanguageOptions extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
quoteLanguages: [
|
||||
{
|
||||
name: variables.getMessage('modals.main.loading'),
|
||||
value: 'loading',
|
||||
},
|
||||
],
|
||||
};
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
|
||||
async getquoteLanguages() {
|
||||
const data = await (
|
||||
await fetch(variables.constants.API_URL + '/quotes/languages', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const quoteLanguages = data.map((language) => {
|
||||
return {
|
||||
name: languages.find((l) => l.value === language.name)
|
||||
? languages.find((l) => l.value === language.name).name
|
||||
: 'English',
|
||||
value: language,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({
|
||||
quoteLanguages,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return this.setState({
|
||||
quoteLanguages: [
|
||||
{
|
||||
name: variables.getMessage('modals.main.marketplace.offline.description'),
|
||||
value: 'loading',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
this.getquoteLanguages();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// stop making requests
|
||||
this.controller.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div className="modalHeader">
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.language.title')}
|
||||
</span>
|
||||
<div className="headerActions">
|
||||
{/*<a className="link" href="" target="_blank" rel="noopener noreferrer">
|
||||
Improve
|
||||
<MdOutlineOpenInNew />
|
||||
</a>*/}
|
||||
<a
|
||||
className="link"
|
||||
href="https://hosted.weblate.org/new-lang/mue/mue-tab/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Add translation
|
||||
<MdOutlineOpenInNew />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="languageSettings">
|
||||
<Radio name="language" options={languages} element=".other" />
|
||||
</div>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.language.quote')}
|
||||
</span>
|
||||
<div className="languageSettings">
|
||||
<Radio
|
||||
name="quoteLanguage"
|
||||
options={this.state.quoteLanguages.map((language) => {
|
||||
return { name: language.name, value: language.value.name };
|
||||
})}
|
||||
defaultValue={this.state.quoteLanguages[0].name}
|
||||
category="quote"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { LanguageOptions as default, LanguageOptions };
|
||||
197
src/features/misc/sections/Overview.jsx
Normal file
197
src/features/misc/sections/Overview.jsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { MdOutlineDragIndicator } from 'react-icons/md';
|
||||
import { sortableContainer, sortableElement } from '@muetab/react-sortable-hoc';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Greeting from './overview_skeletons/Greeting';
|
||||
import Clock from './overview_skeletons/Clock';
|
||||
import Quote from './overview_skeletons/Quote';
|
||||
import QuickLinks from './overview_skeletons/QuickLinks';
|
||||
import Date from './overview_skeletons/Date';
|
||||
import Message from './overview_skeletons/Message';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
|
||||
const widget_name = {
|
||||
greeting: variables.getMessage('modals.main.settings.sections.greeting.title'),
|
||||
time: variables.getMessage('modals.main.settings.sections.time.title'),
|
||||
quicklinks: variables.getMessage('modals.main.settings.sections.quicklinks.title'),
|
||||
quote: variables.getMessage('modals.main.settings.sections.quote.title'),
|
||||
date: variables.getMessage('modals.main.settings.sections.date.title'),
|
||||
message: variables.getMessage('modals.main.settings.sections.message.title'),
|
||||
};
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => (
|
||||
<li className="sortableItem">
|
||||
{widget_name[value]}
|
||||
<MdOutlineDragIndicator />
|
||||
</li>
|
||||
));
|
||||
|
||||
const SortableContainer = sortableContainer(({ children }) => (
|
||||
<ul className="sortablecontainer">{children}</ul>
|
||||
));
|
||||
|
||||
class Overview extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
items: JSON.parse(localStorage.getItem('order')),
|
||||
news: {
|
||||
title: '',
|
||||
date: '',
|
||||
description: '',
|
||||
link: '',
|
||||
linkText: '',
|
||||
},
|
||||
newsDone: false,
|
||||
};
|
||||
}
|
||||
|
||||
arrayMove(array, oldIndex, newIndex) {
|
||||
const result = Array.from(array);
|
||||
const [removed] = result.splice(oldIndex, 1);
|
||||
result.splice(newIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
this.setState({
|
||||
items: this.arrayMove(this.state.items, oldIndex, newIndex),
|
||||
});
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
localStorage.setItem(
|
||||
'order',
|
||||
JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']),
|
||||
);
|
||||
|
||||
this.setState({
|
||||
items: JSON.parse(localStorage.getItem('order')),
|
||||
});
|
||||
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
};
|
||||
|
||||
enabled = (setting) => {
|
||||
switch (setting) {
|
||||
case 'quicklinks':
|
||||
return localStorage.getItem('quicklinksenabled') === 'true';
|
||||
default:
|
||||
return localStorage.getItem(setting) === 'true';
|
||||
}
|
||||
};
|
||||
|
||||
getTab = (value) => {
|
||||
switch (value) {
|
||||
case 'greeting':
|
||||
return <Greeting />;
|
||||
case 'time':
|
||||
return <Clock />;
|
||||
case 'quicklinks':
|
||||
return <QuickLinks />;
|
||||
case 'quote':
|
||||
return <Quote />;
|
||||
case 'date':
|
||||
return <Date />;
|
||||
case 'message':
|
||||
return <Message />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
async getNews() {
|
||||
const data = await (await fetch(variables.constants.API_URL + '/news')).json();
|
||||
data.date = new window.Date(data.date).toLocaleDateString(
|
||||
variables.languagecode.replace('_', '-'),
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
},
|
||||
);
|
||||
|
||||
this.setState({
|
||||
news: data,
|
||||
newsDone: true,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('order', JSON.stringify(this.state.items));
|
||||
variables.stats.postEvent('setting', 'Widget order');
|
||||
EventBus.emit('refresh', 'widgets');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getNews();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.marketplace.product.overview')}
|
||||
</span>
|
||||
{/*<span className="title">{variables.getMessage('modals.main.marketplace.product.overview')}</span>
|
||||
<span className="link" onClick={this.reset}>
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>*/}
|
||||
<div className="overviewGrid">
|
||||
<div>
|
||||
<span className="title">{variables.getMessage('modals.welcome.buttons.preview')}</span>
|
||||
<div className="tabPreview">
|
||||
<div className="previewItem" style={{ maxWidth: '50%' }}>
|
||||
{this.state.items.map((value, index) => {
|
||||
if (!this.enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="previewItem" key={`item-${value}`} index={index}>
|
||||
{' '}
|
||||
{this.getTab(value)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="overviewNews">
|
||||
<span className="title">{this.state.news.title}</span>
|
||||
<span className="subtitle">{this.state.news.date}</span>
|
||||
<span className="content">{this.state.news.description}</span>
|
||||
<a className="link" href={this.state.news.link}>
|
||||
{this.state.news.linkText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.order.title')}
|
||||
</span>
|
||||
<SortableContainer
|
||||
onSortEnd={this.onSortEnd}
|
||||
lockAxis="y"
|
||||
lockToContainerEdges
|
||||
disableAutoscroll
|
||||
>
|
||||
{this.state.items.map((value, index) => {
|
||||
if (!this.enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SortableItem key={`item-${value}`} index={index} value={value} />;
|
||||
})}
|
||||
</SortableContainer>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Overview as default, Overview };
|
||||
228
src/features/misc/sections/Stats.jsx
Normal file
228
src/features/misc/sections/Stats.jsx
Normal file
@@ -0,0 +1,228 @@
|
||||
/* eslint-disable array-callback-return */
|
||||
import variables from 'config/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { MdShowChart, MdRestartAlt, MdDownload } from 'react-icons/md';
|
||||
import { FaTrophy } from 'react-icons/fa';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { Button } from 'components/Elements';
|
||||
import { Header, CustomActions } from 'components/Layout/Settings';
|
||||
|
||||
import { saveFile } from 'utils/saveFile';
|
||||
|
||||
import { translations, achievements } from 'utils/achievements';
|
||||
|
||||
class Stats extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
stats: JSON.parse(localStorage.getItem('statsData')) || {},
|
||||
achievements,
|
||||
};
|
||||
}
|
||||
|
||||
getAchievements() {
|
||||
const achievements = this.state.achievements;
|
||||
achievements.forEach((achievement) => {
|
||||
switch (achievement.condition.type) {
|
||||
case 'tabsOpened':
|
||||
if (this.state.stats['tabs-opened'] >= achievement.condition.amount) {
|
||||
achievement.achieved = true;
|
||||
}
|
||||
break;
|
||||
case 'addonInstall':
|
||||
if (this.state.stats.marketplace) {
|
||||
if (this.state.stats.marketplace['install'] >= achievement.condition.amount) {
|
||||
achievement.achieved = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
achievements,
|
||||
});
|
||||
}
|
||||
|
||||
getUnlockedCount() {
|
||||
let count = 0;
|
||||
this.state.achievements.forEach((achievement) => {
|
||||
if (achievement.achieved) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
resetStats() {
|
||||
localStorage.setItem('statsData', JSON.stringify({}));
|
||||
this.setState({
|
||||
stats: {},
|
||||
});
|
||||
toast(variables.getMessage('toasts.stats_reset'));
|
||||
this.getAchievements();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
downloadStats() {
|
||||
let date = new Date();
|
||||
// Format the date as YYYY-MM-DD_HH-MM-SS
|
||||
let formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}-${date.getSeconds().toString().padStart(2, '0')}`;
|
||||
let filename = `mue_stats_${formattedDate}.json`;
|
||||
saveFile(JSON.stringify(this.state.stats, null, 2), filename);
|
||||
}
|
||||
|
||||
getLocalisedAchievementData(id) {
|
||||
const localised = translations[variables.languagecode][id] ||
|
||||
translations.en_GB[id] || { name: id, description: '' };
|
||||
|
||||
return {
|
||||
name: localised.name,
|
||||
description: localised.description,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getAchievements();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
const achievementElement = (key, id, achieved) => {
|
||||
const { name, description } = this.getLocalisedAchievementData(id);
|
||||
|
||||
return (
|
||||
<div className="achievement" key={key}>
|
||||
<FaTrophy />
|
||||
<div className={'achievementContent' + (achieved ? ' achieved' : '')}>
|
||||
<span>{name}</span>
|
||||
<span className="subtitle">{description}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const STATS_SECTION = 'modals.main.settings.sections.stats';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title={variables.getMessage(`${STATS_SECTION}.title`)} report={false}>
|
||||
<CustomActions>
|
||||
<Button
|
||||
type="settings"
|
||||
onClick={() => this.downloadStats()}
|
||||
icon={<MdDownload />}
|
||||
label={variables.getMessage('widgets.background.download')}
|
||||
/>
|
||||
<Button
|
||||
type="settings"
|
||||
onClick={() => this.resetStats()}
|
||||
icon={<MdRestartAlt />}
|
||||
label={variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
/>
|
||||
</CustomActions>
|
||||
</Header>
|
||||
<div className="stats">
|
||||
<div className="statSection rightPanel">
|
||||
<div className="statIcon">
|
||||
<MdShowChart />
|
||||
</div>
|
||||
<div className="statGrid">
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(`${STATS_SECTION}.sections.tabs_opened`)}{' '}
|
||||
</span>
|
||||
<span>{this.state.stats['tabs-opened'] || 0}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.stats.sections.backgrounds_favourited',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>
|
||||
{this.state.stats.feature
|
||||
? this.state.stats.feature['background-favourite'] || 0
|
||||
: 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.stats.sections.backgrounds_downloaded',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>
|
||||
{this.state.stats.feature
|
||||
? this.state.stats.feature['background-download'] || 0
|
||||
: 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.stats.sections.quotes_favourited',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>
|
||||
{this.state.stats.feature ? this.state.stats.feature['quoted-favourite'] || 0 : 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.stats.sections.quicklinks_added',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>
|
||||
{this.state.stats.feature ? this.state.stats.feature['quicklink-add'] || 0 : 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.stats.sections.settings_changed',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>
|
||||
{this.state.stats.setting ? Object.keys(this.state.stats.setting).length : 0}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.stats.sections.addons_installed',
|
||||
)}{' '}
|
||||
</span>
|
||||
<span>
|
||||
{this.state.stats.marketplace ? this.state.stats.marketplace['install'] : 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="statSection leftPanel">
|
||||
<span className="title">{variables.getMessage(`${STATS_SECTION}.achievements`)}</span>
|
||||
<br />
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(`${STATS_SECTION}.unlocked`, {
|
||||
count: this.getUnlockedCount() + '/' + this.state.achievements.length,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="achievements">
|
||||
{this.state.achievements.map((achievement, index) => {
|
||||
if (achievement.achieved) {
|
||||
return achievementElement(index, achievement.id, achievement.achieved);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { Stats as default, Stats };
|
||||
8
src/features/misc/sections/index.jsx
Normal file
8
src/features/misc/sections/index.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './About';
|
||||
export * from './Advanced';
|
||||
export * from './Appearance';
|
||||
export * from './Changelog';
|
||||
export * from './Experimental';
|
||||
export * from './Language';
|
||||
export * from './Overview';
|
||||
export * from './Stats';
|
||||
31
src/features/misc/sections/overview_skeletons/Clock.jsx
Normal file
31
src/features/misc/sections/overview_skeletons/Clock.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Suspense, lazy, memo } from 'react';
|
||||
const Analog = lazy(() => import('react-clock'));
|
||||
|
||||
function ClockSkeleton() {
|
||||
if (localStorage.getItem('timeType') === 'analogue') {
|
||||
return (
|
||||
<Suspense fallback={<></>}>
|
||||
<div className="clockBackground">
|
||||
<Analog
|
||||
className="analogclock clock-container"
|
||||
value={'2022-10-07T17:00:00+00:00'}
|
||||
size={50}
|
||||
/>
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
} else if (localStorage.getItem('timeType') === 'percentageComplete') {
|
||||
return <span className="new-clock clock-container clockSkeleton">68%</span>;
|
||||
} else if (localStorage.getItem('timeType') === 'verticalClock') {
|
||||
return (
|
||||
<span className="new-clock clock-container" style={{ fontSize: '30px' }}>
|
||||
<div className="hour">10</div>
|
||||
<div className="minute">23</div>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return <span className="new-clock clock-container clockSkeleton">10:24</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export default memo(ClockSkeleton);
|
||||
7
src/features/misc/sections/overview_skeletons/Date.jsx
Normal file
7
src/features/misc/sections/overview_skeletons/Date.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
function DateSkeleton() {
|
||||
return <span className="date">Thursday January 1st</span>;
|
||||
}
|
||||
|
||||
export default memo(DateSkeleton);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
function GreetingSkeleton() {
|
||||
return <span className="greeting">Good Morning</span>;
|
||||
}
|
||||
|
||||
export default memo(GreetingSkeleton);
|
||||
14
src/features/misc/sections/overview_skeletons/Message.jsx
Normal file
14
src/features/misc/sections/overview_skeletons/Message.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
function MessageSkeleton() {
|
||||
return (
|
||||
<h2 className="message">
|
||||
<span>
|
||||
Message
|
||||
<br />
|
||||
</span>
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(MessageSkeleton);
|
||||
26
src/features/misc/sections/overview_skeletons/QuickLinks.jsx
Normal file
26
src/features/misc/sections/overview_skeletons/QuickLinks.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { memo } from 'react';
|
||||
import { FaDiscord, FaTwitter } from 'react-icons/fa';
|
||||
import { SiGithubsponsors, SiOpencollective } from 'react-icons/si';
|
||||
|
||||
function QuicklinksSkeleton() {
|
||||
return (
|
||||
<div className="quickLinksSkeleton">
|
||||
<div className="circles">
|
||||
<div>
|
||||
<FaDiscord />
|
||||
</div>
|
||||
<div>
|
||||
<FaTwitter />
|
||||
</div>
|
||||
<div>
|
||||
<SiGithubsponsors />
|
||||
</div>
|
||||
<div>
|
||||
<SiOpencollective />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(QuicklinksSkeleton);
|
||||
21
src/features/misc/sections/overview_skeletons/Quote.jsx
Normal file
21
src/features/misc/sections/overview_skeletons/Quote.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { memo } from 'react';
|
||||
import { MdPerson } from 'react-icons/md';
|
||||
|
||||
function QuoteSkeleton() {
|
||||
return (
|
||||
<div className="quoteSkeleton">
|
||||
<span className="subtitle">"Never gonna give you up"</span>
|
||||
<div className="skeletonAuthor">
|
||||
<div>
|
||||
<MdPerson />
|
||||
</div>
|
||||
<div className="text">
|
||||
<span className="title">Rick Astley</span>
|
||||
<span className="subtitle">English singer-songwriter</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(QuoteSkeleton);
|
||||
Reference in New Issue
Block a user