mirror of
https://github.com/mue/mue.git
synced 2026-07-21 16:04:22 +02:00
refactor: make modals like widgets
This commit is contained in:
43
src/components/modals/main/settings/Checkbox.jsx
Normal file
43
src/components/modals/main/settings/Checkbox.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import CheckboxUI from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
|
||||
export default class Checkbox extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
checked: (localStorage.getItem(this.props.name) === 'true')
|
||||
};
|
||||
}
|
||||
|
||||
handleChange(name) {
|
||||
SettingsFunctions.setItem(name);
|
||||
|
||||
this.setState({
|
||||
checked: (this.state.checked === true) ? false : true
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let text = this.props.text;
|
||||
|
||||
if (this.props.newFeature) {
|
||||
text = <span>{this.props.text} <span className='newFeature'> NEW</span></span>;
|
||||
} else if (this.props.betaFeature) {
|
||||
text = <span>{this.props.text} <span className='newFeature'> BETA</span></span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<FormControlLabel
|
||||
control={<CheckboxUI name='checkedB' color='primary' checked={this.state.checked} onChange={() => this.handleChange(this.props.name)} />}
|
||||
label={text}
|
||||
/>
|
||||
<br/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
31
src/components/modals/main/settings/Dropdown.jsx
Normal file
31
src/components/modals/main/settings/Dropdown.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class Dropdown extends React.PureComponent {
|
||||
getLabel() {
|
||||
return this.props.label ? <label htmlFor={this.props.name}>{this.props.label}</label> : null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.getElementById(this.props.name).value = localStorage.getItem(this.props.name);
|
||||
}
|
||||
|
||||
onChange = () => {
|
||||
localStorage.setItem(this.props.name, document.getElementById(this.props.name).value);
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.getLabel()}
|
||||
<div className='dropdown' style={{ display: 'inline' }}>
|
||||
<select name={this.props.name} id={this.props.name} onChange={this.onChange}>
|
||||
{this.props.children}
|
||||
</select>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
29
src/components/modals/main/settings/FileUpload.jsx
Normal file
29
src/components/modals/main/settings/FileUpload.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class FileUpload extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
document.getElementById(this.props.id).onchange = (e) => {
|
||||
const reader = new FileReader();
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (this.props.type === 'settings') {
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
} else {
|
||||
// background upload
|
||||
if (file.size > 2000000) {
|
||||
return toast('File is over 2MB');
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
reader.addEventListener('load', (e) => this.props.loadFunction(e));
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input id={this.props.id} type='file' className='hidden' accept={this.props.accept} />;
|
||||
}
|
||||
}
|
||||
14
src/components/modals/main/settings/ResetModal.jsx
Normal file
14
src/components/modals/main/settings/ResetModal.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function ResetModal(props) {
|
||||
return (
|
||||
<div className='welcomeContent'>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<div className='welcomeModalText'>
|
||||
reset text
|
||||
<button className='close' onClick={props.modalClose}>yes</button>
|
||||
<button className='close' onClick={props.modalClose}>no</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
83
src/components/modals/main/settings/sections/About.jsx
Normal file
83
src/components/modals/main/settings/sections/About.jsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
const other_contributors = require('../../../../../modules/other_contributors.json');
|
||||
const { version } = require('../../../../../../package.json');
|
||||
|
||||
export default class About extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
contributors: [],
|
||||
sponsors: [],
|
||||
other_contributors: [],
|
||||
update: window.language.modals.main.settings.sections.about.version.checking_update
|
||||
}
|
||||
this.language = window.language.modals.main.settings.sections.about;
|
||||
}
|
||||
|
||||
async getGitHubData() {
|
||||
const contributors = await (await fetch('https://api.github.com/repos/mue/mue/contributors')).json();
|
||||
const { sponsors } = await (await fetch(window.constants.SPONSORS_URL + '/list')).json();
|
||||
|
||||
const versionData = await (await fetch('https://api.github.com/repos/mue/mue/releases')).json();
|
||||
const newVersion = versionData[0].tag_name;
|
||||
|
||||
let updateMsg = this.language.version.no_update;
|
||||
if (version < newVersion) {
|
||||
updateMsg = `${this.language.version.update_available}: ${newVersion}`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
contributors: contributors.filter((contributor) => !contributor.login.includes('bot')),
|
||||
sponsors: sponsors,
|
||||
update: updateMsg,
|
||||
other_contributors: other_contributors
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('offlineMode') === 'true') {
|
||||
this.setState({
|
||||
update: this.language.version.offline_mode
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.getGitHubData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>{this.language.title}</h2>
|
||||
<img draggable='false' style={{'height': '100px', 'width': 'auto'}} src='https://raw.githubusercontent.com/mue/branding/master/logo/logo_horizontal.png' alt='Mue logo'></img>
|
||||
<p>{this.language.copyright} 2018-{new Date().getFullYear()} Mue Tab (BSD-3 License)</p>
|
||||
<p>{this.language.version.title} {version} ({this.state.update})</p>
|
||||
<h3>{this.language.resources_used.title}</h3>
|
||||
<p>Pexels ({this.language.resources_used.bg_images})</p>
|
||||
<p>Google ({this.language.resources_used.pin_icon})</p>
|
||||
<p>Undraw ({this.language.resources_used.welcome_img})</p>
|
||||
<h3>{this.language.contributors}</h3>
|
||||
{this.state.contributors.map((item) =>
|
||||
<Tooltip title={item.login} placement='top' key={item.login}>
|
||||
<a href={'https://github.com/' + item.login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar_url + '&size=256'} alt={item.login}></img></a>
|
||||
</Tooltip>
|
||||
)}
|
||||
{ // for those who contributed without opening a pull request
|
||||
this.state.other_contributors.map((item) =>
|
||||
<Tooltip title={item.login} placement='top' key={item.login}>
|
||||
<a href={'https://github.com/' + item.login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar_url + '&size=256'} alt={item.login}></img></a>
|
||||
</Tooltip>
|
||||
)}
|
||||
<h3>{this.language.supporters}</h3>
|
||||
{this.state.sponsors.map((item) =>
|
||||
<Tooltip title={item.handle} placement='top' key={item.handle}>
|
||||
<a href={item.profile} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar + '&size=256'} alt={item.handle}></img></a>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
80
src/components/modals/main/settings/sections/Advanced.jsx
Normal file
80
src/components/modals/main/settings/sections/Advanced.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import FileUpload from '../FileUpload';
|
||||
import ResetModal from '../ResetModal';
|
||||
|
||||
import SettingsFunctions from '../../../../../modules/helpers/settings';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
export default class AdvancedSettings extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
resetModal: false
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem(type) {
|
||||
document.getElementById(type).value = '';
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
settingsImport(e) {
|
||||
const content = JSON.parse(e.target.result);
|
||||
|
||||
for (const key of Object.keys(content)) {
|
||||
localStorage.setItem(key, content[key]);
|
||||
}
|
||||
|
||||
toast(this.language.toasts.imported);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.getElementById('customcss').value = localStorage.getItem('customcss');
|
||||
document.getElementById('customjs').value = localStorage.getItem('customjs');
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('customcss', document.getElementById('customcss').value);
|
||||
localStorage.setItem('customjs', document.getElementById('customjs').value);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { advanced } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{advanced.title}</h2>
|
||||
<Checkbox name='offlineMode' text={advanced.offline_mode} />
|
||||
|
||||
<h3>{advanced.data}</h3>
|
||||
<button className='reset' onClick={() => this.setState({ resetModal: true })}>{this.language.buttons.reset}</button>
|
||||
<button className='export' onClick={() => SettingsFunctions.exportSettings()}>{this.language.buttons.export}</button>
|
||||
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.language.buttons.import}</button>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.settingsImport(e)} />
|
||||
|
||||
<h3>{advanced.customisation}</h3>
|
||||
<ul>
|
||||
<p>{advanced.custom_css} <span className='modalLink' onClick={() => this.resetItem('customcss')}>{this.language.buttons.reset}</span></p>
|
||||
<textarea id='customcss'></textarea>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{advanced.custom_js} <span className='modalLink' onClick={() => this.resetItem('customjs')}>{this.language.buttons.reset}</span></p>
|
||||
<textarea id='customjs'></textarea>
|
||||
</ul>
|
||||
|
||||
<h3>{this.language.sections.experimental.title}</h3>
|
||||
<p>{advanced.experimental_warning}</p>
|
||||
<Checkbox name='experimental' text={this.language.enabled} />
|
||||
|
||||
<Modal onRequestClose={() => this.setState({ resetModal: false })} isOpen={this.state.resetModal} className={'modal'} overlayClassName={'Overlay'} ariaHideApp={false}>
|
||||
<ResetModal modalClose={() => this.setState({ resetModal: false })} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
115
src/components/modals/main/settings/sections/Appearance.jsx
Normal file
115
src/components/modals/main/settings/sections/Appearance.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class AppearanceSettings extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
zoom: localStorage.getItem('zoom'),
|
||||
toast_duration: localStorage.getItem('toastDisplayTime'),
|
||||
font: localStorage.getItem('font') || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem(key) {
|
||||
switch (key) {
|
||||
case 'zoom':
|
||||
localStorage.setItem('zoom', 100);
|
||||
this.setState({
|
||||
zoom: 100
|
||||
});
|
||||
break;
|
||||
|
||||
case 'toast_duration':
|
||||
localStorage.setItem('toastDisplayTime', 2500);
|
||||
this.setState({
|
||||
toast_duration: 2500
|
||||
});
|
||||
break;
|
||||
|
||||
case 'font':
|
||||
localStorage.setItem('font', '');
|
||||
this.setState({
|
||||
font: ''
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
toast('resetItem requires a key!');
|
||||
}
|
||||
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('zoom', this.state.zoom);
|
||||
localStorage.setItem('toastDisplayTime', this.state.toast_duration);
|
||||
localStorage.setItem('font', this.state.font.charAt(0).toUpperCase() + this.state.font.slice(1));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { appearance } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{appearance.title}</h2>
|
||||
<Checkbox name='darkTheme' text={appearance.dark_theme} />
|
||||
<Checkbox name='brightnessTime' text={appearance.night_mode} />
|
||||
|
||||
<h3>{appearance.navbar.title}</h3>
|
||||
<Checkbox name='notesEnabled' text={appearance.navbar.notes} />
|
||||
<Checkbox name='refresh' text={appearance.navbar.refresh} />
|
||||
|
||||
<h3>{appearance.font.title}</h3>
|
||||
<ul>
|
||||
<p>{appearance.font.custom} <span className='modalLink' onClick={() => this.resetItem('font')}>{this.language.buttons.reset}</span></p>
|
||||
<input type='text' value={this.state.font} onChange={(e) => this.setState({ font: e.target.value })}></input>
|
||||
</ul>
|
||||
<br />
|
||||
<Dropdown
|
||||
label='Font Weight'
|
||||
name='fontweight'
|
||||
id='fontWeight'
|
||||
onChange={() => localStorage.setItem('fontWeight', document.getElementById('fontWeight').value)}>
|
||||
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
||||
<option className='choices' value='100'>Thin</option>
|
||||
<option className='choices' value='200'>Extra-Light</option>
|
||||
<option className='choices' value='300'>Light</option>
|
||||
<option className='choices' value='400'>Normal</option>
|
||||
<option className='choices' value='500'>Medium</option>
|
||||
<option className='choices' value='600'>Semi-Bold</option>
|
||||
<option className='choices' value='700'>Bold</option>
|
||||
<option className='choices' value='800'>Extra-Bold</option>
|
||||
</Dropdown>
|
||||
<br /><br />
|
||||
<Dropdown
|
||||
label='Font Style'
|
||||
name='fontstyle'
|
||||
id='fontStyle'
|
||||
onChange={() => localStorage.setItem('fontStyle', document.getElementById('fontStyle').value)}>
|
||||
<option className='choices' value='normal'>Normal</option>
|
||||
<option className='choices' value='italic'>Italic</option>
|
||||
<option className='choices' value='oblique'>Oblique</option>
|
||||
</Dropdown>
|
||||
<br /><br />
|
||||
<Checkbox name='fontGoogle' text={appearance.font.google} />
|
||||
|
||||
<h3>{appearance.accessibility.title}</h3>
|
||||
<Checkbox name='animations' text={appearance.animations} betaFeature={true} />
|
||||
<ul>
|
||||
<p>{appearance.accessibility.zoom} ({this.state.zoom}%) <span className='modalLink' onClick={() => this.resetItem('zoom')}>{this.language.buttons.reset}</span></p>
|
||||
<input className='range' type='range' min='50' max='200' value={this.state.zoom} onChange={(event) => this.setState({ zoom: event.target.value })} />
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{appearance.accessibility.toast_duration} ({this.state.toast_duration} {appearance.accessibility.milliseconds}) <span className='modalLink' onClick={() => this.resetItem('toast_duration')}>{this.language.buttons.reset}</span></p>
|
||||
<input className='range' type='range' min='500' max='5000' value={this.state.toast_duration} onChange={(event) => this.setState({ toast_duration: event.target.value })} />
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
265
src/components/modals/main/settings/sections/Background.jsx
Normal file
265
src/components/modals/main/settings/sections/Background.jsx
Normal file
@@ -0,0 +1,265 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
import FileUpload from '../FileUpload';
|
||||
|
||||
import { ColorPicker } from 'react-color-gradient-picker';
|
||||
import hexToRgb from '../../../../../modules/helpers/background/hexToRgb';
|
||||
import rgbToHex from '../../../../../modules/helpers/background/rgbToHex';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import 'react-color-gradient-picker/dist/index.css';
|
||||
import '../../../../../scss/react-color-picker-gradient-picker-custom-styles.scss';
|
||||
|
||||
export default class BackgroundSettings extends React.PureComponent {
|
||||
DefaultGradientSettings = { 'angle': '180', 'gradient': [{ 'colour': window.language.modals.main.settings.sections.background.source.disabled, 'stop': 0 }], 'type': 'linear' };
|
||||
GradientPickerInitalState = undefined;
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
blur: localStorage.getItem('blur'),
|
||||
brightness: localStorage.getItem('brightness'),
|
||||
customBackground: localStorage.getItem('customBackground') || '',
|
||||
gradientSettings: this.DefaultGradientSettings
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem(key) {
|
||||
switch (key) {
|
||||
case 'customBackgroundColour':
|
||||
localStorage.setItem('customBackgroundColour', '');
|
||||
this.setState({
|
||||
gradientSettings: this.DefaultGradientSettings
|
||||
});
|
||||
break;
|
||||
|
||||
case 'customBackground':
|
||||
localStorage.setItem('customBackground', '');
|
||||
this.setState({
|
||||
customBackground: ''
|
||||
});
|
||||
break;
|
||||
|
||||
case 'blur':
|
||||
localStorage.setItem('blur', 0);
|
||||
this.setState({
|
||||
blur: 0
|
||||
});
|
||||
break;
|
||||
|
||||
case 'brightness':
|
||||
localStorage.setItem('brightness', 100);
|
||||
this.setState({
|
||||
brightness: 100
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
toast('resetItem requires a key!');
|
||||
}
|
||||
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
InitializeColorPickerState(gradientSettings) {
|
||||
this.GradientPickerInitalState = {
|
||||
points: gradientSettings.gradient.map((g) => {
|
||||
const rgb = hexToRgb(g.colour);
|
||||
return {
|
||||
left: +g.stop,
|
||||
red: rgb.red,
|
||||
green: rgb.green,
|
||||
blue: rgb.blue,
|
||||
alpha: 1
|
||||
};
|
||||
}),
|
||||
degree: + gradientSettings.angle,
|
||||
type: gradientSettings.type
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const hex = localStorage.getItem('customBackgroundColour');
|
||||
let gradientSettings = undefined;
|
||||
|
||||
if (hex !== '') {
|
||||
try {
|
||||
gradientSettings = JSON.parse(hex);
|
||||
} catch (e) {
|
||||
// Disregard exception.
|
||||
}
|
||||
}
|
||||
|
||||
if (gradientSettings === undefined) {
|
||||
gradientSettings = this.DefaultGradientSettings;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
gradientSettings
|
||||
});
|
||||
}
|
||||
|
||||
onGradientChange = (event, index) => {
|
||||
const newValue = event.target.value;
|
||||
const name = event.target.name;
|
||||
this.setState((s) => {
|
||||
const newState = {
|
||||
gradientSettings: {
|
||||
...s.gradientSettings,
|
||||
gradient: s.gradientSettings.gradient.map((g, i) => i === index ? { ...g, [name]: newValue } : g)
|
||||
}
|
||||
};
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
addColour = () => {
|
||||
this.setState((s) => {
|
||||
const [lastGradient, ...initGradients] = s.gradientSettings.gradient.reverse();
|
||||
const newState = {
|
||||
gradientSettings: {
|
||||
...s.gradientSettings,
|
||||
gradient: [...initGradients, lastGradient, { 'colour': localStorage.getItem('darkTheme') === 'true' ? '#000000' : '#ffffff', stop: 100 }].sort((a, b) => (a.stop > b.stop) ? 1 : -1)
|
||||
}
|
||||
};
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
currentGradientSettings = () => {
|
||||
if (typeof this.state.gradientSettings === 'object' && this.state.gradientSettings.gradient.every(g => g.colour !== this.language.sections.background.source.disabled)) {
|
||||
const clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
|
||||
return JSON.stringify({
|
||||
...this.state.gradientSettings,
|
||||
gradient: [...this.state.gradientSettings.gradient.map(g => { return { ...g, stop: clampNumber(+g.stop, 0, 100) } })].sort((a, b) => (a.stop > b.stop) ? 1 : -1)
|
||||
});
|
||||
}
|
||||
return this.language.sections.background.source.disabled;
|
||||
}
|
||||
|
||||
onColorPickerChange = (attrs, name) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(attrs, name);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
gradientSettings: {
|
||||
'angle': attrs.degree,
|
||||
'gradient': attrs.points.map((p) => {
|
||||
return {
|
||||
'colour': '#' + rgbToHex(p.red, p.green, p.blue),
|
||||
'stop': p.left
|
||||
}}),
|
||||
'type': attrs.type
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fileUpload(e) {
|
||||
localStorage.setItem('customBackground', e.target.result);
|
||||
this.setState({
|
||||
customBackground: e.target.result
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('blur', this.state.blur);
|
||||
localStorage.setItem('brightness', this.state.brightness);
|
||||
localStorage.setItem('customBackground', this.state.customBackground);
|
||||
|
||||
if (document.getElementById('customBackgroundHex').value !== this.language.sections.background.source.disabled) {
|
||||
localStorage.setItem('customBackgroundColour', document.getElementById('customBackgroundHex').value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { background } = this.language.sections;
|
||||
|
||||
let colourSettings = null;
|
||||
if (typeof this.state.gradientSettings === 'object') {
|
||||
const gradientHasMoreThanOneColour = this.state.gradientSettings.gradient.length > 1;
|
||||
|
||||
let gradientInputs;
|
||||
if (gradientHasMoreThanOneColour) {
|
||||
if (this.GradientPickerInitalState === undefined) {
|
||||
this.InitializeColorPickerState(this.state.gradientSettings);
|
||||
}
|
||||
|
||||
gradientInputs = (
|
||||
<ColorPicker
|
||||
onStartChange={(color) => this.onColorPickerChange(color, 'start')}
|
||||
onChange={(color) => this.onColorPickerChange(color, 'change')}
|
||||
onEndChange={(color) => this.onColorPickerChange(color, 'end')}
|
||||
gradient={this.GradientPickerInitalState}
|
||||
isGradient />
|
||||
);
|
||||
} else {
|
||||
gradientInputs = this.state.gradientSettings.gradient.map((g, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<input id={'colour_' + i} type='color' name='colour' className='colour' onChange={(event) => this.onGradientChange(event, i)} value={g.colour}></input>
|
||||
<label htmlFor={'colour_' + i} className='customBackgroundHex'>{g.colour}</label>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
colourSettings = (
|
||||
<div>
|
||||
{gradientInputs}
|
||||
{this.state.gradientSettings.gradient[0].colour !== background.source.disabled &&
|
||||
!gradientHasMoreThanOneColour ? (<button type='button' className='add' onClick={this.addColour}>{background.source.add_colour}</button>) : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{background.title}</h2>
|
||||
<Checkbox name='background' text='Enabled' />
|
||||
<h3>{background.buttons.title}</h3>
|
||||
<ul>
|
||||
<Checkbox name='view' text={background.buttons.view} />
|
||||
<Checkbox name='favouriteEnabled' text={background.buttons.favourite} />
|
||||
</ul>
|
||||
|
||||
<h3>{background.effects.title}</h3>
|
||||
<ul>
|
||||
<p>{background.effects.blur} ({this.state.blur}%) <span className='modalLink' onClick={() => this.resetItem('blur')}>{this.language.buttons.reset}</span></p>
|
||||
<input className='range' type='range' min='0' max='100' value={this.state.blur} onChange={(event) => this.setState({ blur: event.target.value })} />
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{background.effects.brightness} ({this.state.brightness}%) <span className='modalLink' onClick={() => this.resetItem('brightness')}>{this.language.buttons.reset}</span></p>
|
||||
<input className='range' type='range' min='0' max='100' value={this.state.brightness} onChange={(event) => this.setState({ brightness: event.target.value })} />
|
||||
</ul>
|
||||
|
||||
<h3>{background.source.title}</h3>
|
||||
<ul>
|
||||
<Dropdown label={background.source.api} name='backgroundAPI'>
|
||||
<option className='choices' value='mue'>Mue</option>
|
||||
<option className='choices' value='unsplash'>Unsplash</option>
|
||||
</Dropdown>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{background.source.custom_url} <span className='modalLink' onClick={() => this.resetItem('customBackground')}>{this.language.buttons.reset}</span></p>
|
||||
<input type='text' value={this.state.customBackground} onChange={(e) => this.setState({ customBackground: e.target.value })}></input>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{background.source.custom_background} <span className='modalLink' onClick={() => this.resetItem('customBackground')}>{this.language.buttons.reset}</span></p>
|
||||
<button className='uploadbg' onClick={() => document.getElementById('bg-input').click()}>{background.source.upload}</button>
|
||||
<FileUpload id='bg-input' accept='image/jpeg, image/png, image/webp, image/webm, image/gif' loadFunction={(e) => this.fileUpload(e)} />
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{background.source.custom_colour} <span className='modalLink' onClick={() => this.resetItem('customBackgroundColour')}>{this.language.buttons.reset}</span></p>
|
||||
<input id='customBackgroundHex' type='hidden' value={this.currentGradientSettings()} />
|
||||
{colourSettings}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
56
src/components/modals/main/settings/sections/Changelog.jsx
Normal file
56
src/components/modals/main/settings/sections/Changelog.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class Changelog extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
title: this.props.language.title,
|
||||
date: null,
|
||||
content: this.props.language.title,
|
||||
html: this.props.language.loading,
|
||||
image: null
|
||||
};
|
||||
}
|
||||
|
||||
async getUpdate() {
|
||||
const data = await (await fetch(window.constants.API_URL + '/getUpdate')).json();
|
||||
|
||||
if (data.statusCode === 500 || data.title === null) {
|
||||
const supportText = `<br/><p>${this.props.language.contact_support}: <a target='_blank' class='modalLink' href='https://muetab.com/contact'>https://muetab.com/contact</a></p>`;
|
||||
return this.setState({
|
||||
title: this.props.language.error.title,
|
||||
html: this.props.language.error.description + supportText
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
title: data.title,
|
||||
date: data.published,
|
||||
image: data.image || null,
|
||||
author: data.author,
|
||||
html: data.content
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('offlineMode') === 'true') {
|
||||
return this.setState({
|
||||
title: this.props.language.offline.title,
|
||||
html: this.props.language.offline.description
|
||||
});
|
||||
}
|
||||
|
||||
this.getUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1 style={{ 'marginBottom': '-10px' }}>{this.state.title}</h1>
|
||||
<h5 style={{ 'lineHeight': '0px' }}>{this.state.date}</h5>
|
||||
{this.state.image ? <img draggable='false' src={this.state.image} alt='Update'></img> : null}
|
||||
<p dangerouslySetInnerHTML={{ __html: this.state.html }}></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import Checkbox from '../Checkbox';
|
||||
|
||||
export default function ExperimentalSettings (props) {
|
||||
return (
|
||||
<div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
src/components/modals/main/settings/sections/Greeting.jsx
Normal file
66
src/components/modals/main/settings/sections/Greeting.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
|
||||
import DatePicker from 'react-date-picker';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class GreetingSettings extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
birthday: new Date(localStorage.getItem('birthday')) || new Date(),
|
||||
greetingName: localStorage.getItem('greetingName') || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem() {
|
||||
this.setState({
|
||||
greetingName: ''
|
||||
});
|
||||
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
changeDate(data) {
|
||||
//soon
|
||||
if (data === 'reset') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('birthday', data);
|
||||
|
||||
this.setState({
|
||||
birthday: data
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('greetingName', this.state.greetingName);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { greeting } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{greeting.title}</h2>
|
||||
<Checkbox name='greeting' text={this.language.enabled} />
|
||||
<Checkbox name='events' text={greeting.events} />
|
||||
<Checkbox name='defaultGreetingMessage' text={greeting.default} />
|
||||
<ul>
|
||||
<p>{greeting.name} <span className='modalLink' onClick={() => this.resetItem()}>{this.language.buttons.reset}</span></p>
|
||||
<input type='text' value={this.state.greetingName} onChange={(e) => this.setState({ greetingName: e.target.value })}></input>
|
||||
</ul>
|
||||
|
||||
<h3>{greeting.birthday}</h3>
|
||||
<Checkbox name='birthdayenabled' text={this.language.enabled} />
|
||||
<ul>
|
||||
<p>{greeting.birthday_date}</p>
|
||||
<DatePicker onChange={(data) => this.changeDate(data)} value={this.state.birthday}/>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/components/modals/main/settings/sections/Language.jsx
Normal file
25
src/components/modals/main/settings/sections/Language.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import Dropdown from '../Dropdown';
|
||||
|
||||
const languages = require('../../../../../modules/languages.json');
|
||||
|
||||
export default function LanguageSettings () {
|
||||
const language = window.language.modals.main.settings.sections.language;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{language.title}</h2>
|
||||
<Dropdown label={language.title} name='language' id='language' onChange={() => localStorage.setItem('language', document.getElementById('language').value)}>
|
||||
{languages.map(language =>
|
||||
<option className='choices' value={language.code} key={language.code}>{language.text}</option>
|
||||
)}
|
||||
</Dropdown>
|
||||
<br/>
|
||||
<Dropdown label={language.quote} name='quotelanguage' id='quotelanguage' onChange={() => localStorage.setItem('quotelanguage', document.getElementById('quotelanguage').value)}>
|
||||
<option className='choices' value='English'>English</option>
|
||||
<option className='choices' value='French'>Français</option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
69
src/components/modals/main/settings/sections/Quote.jsx
Normal file
69
src/components/modals/main/settings/sections/Quote.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class QuoteSettings extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
customQuote: localStorage.getItem('customQuote') || '',
|
||||
customQuoteAuthor: localStorage.getItem('customQuoteAuthor') || 'Unknown'
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
|
||||
resetItem(key) {
|
||||
switch (key) {
|
||||
case 'customQuote':
|
||||
localStorage.setItem('customQuote', '');
|
||||
this.setState({
|
||||
customQuote: ''
|
||||
});
|
||||
break;
|
||||
|
||||
case 'customQuoteAuthor':
|
||||
localStorage.setItem('customQuoteAuthor', '');
|
||||
this.setState({
|
||||
customQuoteAuthor: 'Unknown'
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
toast('resetItem requires a key!');
|
||||
}
|
||||
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('customQuote', this.state.customQuote);
|
||||
localStorage.setItem('customQuoteAuthor', this.state.customQuoteAuthor);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { quote } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{quote.title}</h2>
|
||||
<Checkbox name='quote' text={this.language.enabled}/>
|
||||
<Checkbox name='authorLink' text={quote.author_link}/>
|
||||
<ul>
|
||||
<p>{quote.custom} <span className='modalLink' onClick={() => this.resetItem('customQuote')}>{this.language.buttons.reset}</span></p>
|
||||
<input type='text' value={this.state.customQuote} onChange={(e) => this.setState({ customQuote: e.target.value })}></input>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{quote.custom_author} <span className='modalLink' onClick={() => this.resetItem('customQuoteAuthor')}>{this.language.buttons.reset}</span></p>
|
||||
<input type='text' value={this.state.customQuoteAuthor} onChange={(e) => this.setState({ customQuoteAuthor: e.target.value })}></input>
|
||||
</ul>
|
||||
<h3>{quote.buttons.title}</h3>
|
||||
<Checkbox name='copyButton' text={quote.buttons.copy}/>
|
||||
<Checkbox name='tweetButton' text={quote.buttons.tweet}/>
|
||||
<Checkbox name='favouriteQuoteEnabled' text={quote.buttons.favourite}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
84
src/components/modals/main/settings/sections/Search.jsx
Normal file
84
src/components/modals/main/settings/sections/Search.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Dropdown from '../Dropdown';
|
||||
import Checkbox from '../Checkbox';
|
||||
|
||||
const searchEngines = require('../../../../widgets/search/search_engines.json');
|
||||
|
||||
export default class SearchSettings extends React.PureComponent {
|
||||
resetSearch() {
|
||||
localStorage.removeItem('customSearchEngine');
|
||||
document.getElementById('customSearchEngine').value = '';
|
||||
|
||||
toast(this.props.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const searchEngine = localStorage.getItem('searchEngine');
|
||||
|
||||
if (searchEngine === 'custom') {
|
||||
const input = document.getElementById('searchEngineInput');
|
||||
|
||||
input.style.display = 'block';
|
||||
input.enabled = 'true';
|
||||
|
||||
document.getElementById('customSearchEngine').value = localStorage.getItem('customSearchEngine');
|
||||
} else {
|
||||
localStorage.removeItem('customSearchEngine');
|
||||
}
|
||||
|
||||
document.getElementById('searchEngine').value = searchEngine;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (document.getElementById('searchEngineInput').enabled === 'true') {
|
||||
const input = document.getElementById('customSearchEngine').value;
|
||||
if (input) {
|
||||
localStorage.setItem('searchEngine', 'custom');
|
||||
localStorage.setItem('customSearchEngine', input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSearchEngine(input) {
|
||||
const searchEngineInput = document.getElementById('searchEngineInput');
|
||||
if (input === 'custom') {
|
||||
searchEngineInput.enabled = 'true';
|
||||
searchEngineInput.style.display = 'block';
|
||||
} else {
|
||||
searchEngineInput.style.display = 'none';
|
||||
searchEngineInput.enabled = 'false';
|
||||
localStorage.setItem('searchEngine', input);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const language = window.language.modals.main.settings;
|
||||
const { search } = language.sections;
|
||||
|
||||
return (
|
||||
<div className='section'>
|
||||
<h2>{search.title}</h2>
|
||||
<Checkbox name='searchBar' text={language.enabled} />
|
||||
<Checkbox name='voiceSearch' text={search.voice_search} />
|
||||
<ul>
|
||||
<Dropdown label={search.search_engine}
|
||||
name='searchEngine'
|
||||
id='searchEngine'
|
||||
onChange={() => this.setSearchEngine(document.getElementById('searchEngine').value)} >
|
||||
{searchEngines.map((engine) =>
|
||||
<option key={engine.name} className='choices' value={engine.settingsName}>{engine.name}</option>
|
||||
)}
|
||||
<option className='choices' value='custom'>{search.custom.split(' ')[0]}</option>
|
||||
</Dropdown>
|
||||
</ul>
|
||||
<ul id='searchEngineInput' style={{ display: 'none' }}>
|
||||
<p style={{ 'marginTop': '0px' }}>{search.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{language.reset}</span></p>
|
||||
<input type='text' id='customSearchEngine'></input>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
89
src/components/modals/main/settings/sections/Time.jsx
Normal file
89
src/components/modals/main/settings/sections/Time.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
|
||||
export default class TimeSettings extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
timeType: localStorage.getItem('timeType') || 'digital'
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
changeType() {
|
||||
const value = document.getElementById('timeType').value;
|
||||
localStorage.setItem('timeType', value);
|
||||
this.setState({
|
||||
timeType: value
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { time } = this.language.sections;
|
||||
|
||||
let timeSettings;
|
||||
|
||||
const digitalSettings = (
|
||||
<React.Fragment>
|
||||
<h3>{time.digital.title}</h3>
|
||||
<Checkbox name='seconds' text={time.digital.seconds} />
|
||||
<Checkbox name='24hour' text={time.digital.twentyfourhour} />
|
||||
<Checkbox name='ampm' text={time.digital.ampm} />
|
||||
<Checkbox name='zero' text={time.digital.zero} />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const analogSettings = (
|
||||
<React.Fragment>
|
||||
<h3>{time.analogue.title}</h3>
|
||||
<Checkbox name='secondHand' text={time.analogue.second_hand} />
|
||||
<Checkbox name='minuteHand' text={time.analogue.minute_hand} />
|
||||
<Checkbox name='hourHand' text={time.analogue.hour_hand} />
|
||||
<Checkbox name='hourMarks' text={time.analogue.hour_marks} />
|
||||
<Checkbox name='minuteMarks' text={time.analogue.minute_marks} />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
switch (this.state.timeType) {
|
||||
case 'digital': timeSettings = digitalSettings; break;
|
||||
case 'analogue': timeSettings = analogSettings; break;
|
||||
default: timeSettings = null; break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{time.title}</h2>
|
||||
<Checkbox name='time' text={this.language.enabled} />
|
||||
<Dropdown label='Type' name='timeType' onChange={() => this.changeType()}>
|
||||
<option className='choices' value='digital'>{time.digital.title}</option>
|
||||
<option className='choices' value='analogue'>{time.analogue.title}</option>
|
||||
<option className='choices' value='percentageComplete'>{time.percentage_complete}</option>
|
||||
</Dropdown>
|
||||
|
||||
{timeSettings}
|
||||
|
||||
<h3>{time.date.title}</h3>
|
||||
<Checkbox name='date' text={this.language.enabled} />
|
||||
<Checkbox name='dayofweek' text={time.date.day_of_week} />
|
||||
<Checkbox name='weeknumber' text={time.date.week_number} />
|
||||
<Checkbox name='datenth' text={time.date.datenth} />
|
||||
<Checkbox name='short' text={time.date.short_date} betaFeature={true} />
|
||||
<Dropdown label={time.date.short_format} name='dateFormat'>
|
||||
<option className='choices' value='DMY'>DMY</option>
|
||||
<option className='choices' value='MDY'>MDY</option>
|
||||
<option className='choices' value='YMD'>YMD</option>
|
||||
</Dropdown>
|
||||
<br/>
|
||||
<br/>
|
||||
<Dropdown label={time.date.short_separator.title} name='shortFormat'>
|
||||
<option className='choices' value='dots'>{time.date.short_separator.dots}</option>
|
||||
<option className='choices' value='dash'>{time.date.short_separator.dash}</option>
|
||||
<option className='choices' value='gaps'>{time.date.short_separator.gaps}</option>
|
||||
<option className='choices' value='slashes'>{time.date.short_separator.slashes}</option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user