mirror of
https://github.com/mue/mue.git
synced 2026-06-08 22:18:40 +02:00
refactor: language cleanup and css fix
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
This commit is contained in:
20
src/App.jsx
20
src/App.jsx
@@ -8,7 +8,6 @@ import Navbar from './components/widgets/navbar/Navbar';
|
||||
import SettingsFunctions from './modules/helpers/settings';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
import merge from './modules/helpers/merge';
|
||||
|
||||
// Modals are lazy loaded as the user won't use them every time they open a tab
|
||||
const Main = React.lazy(() => import('./components/modals/Main'));
|
||||
@@ -80,30 +79,25 @@ export default class App extends React.PureComponent {
|
||||
}
|
||||
|
||||
const overlayClassList = (localStorage.getItem('animations') === 'true') ? 'Overlay modal-animation' : 'Overlay';
|
||||
|
||||
/// language
|
||||
const languagecode = localStorage.getItem('language') || 'en-GB';
|
||||
const language = merge(require('./translations/en-GB.json'), require(`./translations/${languagecode}.json`));
|
||||
|
||||
const toastDisplayTime = localStorage.getItem('toastDisplayTime') || 2500;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Background/>
|
||||
<ToastContainer position='bottom-right' autoClose={toastDisplayTime} newestOnTop={true} closeOnClick pauseOnFocusLoss />
|
||||
<ToastContainer position='bottom-right' autoClose={toastDisplayTime} newestOnTop={true} closeOnClick pauseOnFocusLoss/>
|
||||
<div id='center'>
|
||||
<Navbar openModal={(modal) => this.setState({ [modal]: true })} language={language} />
|
||||
<Widgets language={language} languagecode={languagecode} />
|
||||
<PhotoInformation language={language.widgets.background} className={tooltipClassList} />
|
||||
<Navbar openModal={(modal) => this.setState({ [modal]: true })}/>
|
||||
<Widgets/>
|
||||
<PhotoInformation className={tooltipClassList}/>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Modal closeTimeoutMS={300} id={'modal'} onRequestClose={() => this.setState({ mainModal: false })} isOpen={this.state.mainModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
|
||||
<Main language={language} modalClose={() => this.setState({ mainModal: false })} />
|
||||
<Main modalClose={() => this.setState({ mainModal: false })} />
|
||||
</Modal>
|
||||
<Modal onRequestClose={() => this.setState({ updateModal: false })} isOpen={this.state.updateModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
|
||||
<Update language={language.update} modalClose={() => this.setState({ updateModal: false })} />
|
||||
<Update modalClose={() => this.setState({ updateModal: false })} />
|
||||
</Modal>
|
||||
<Modal onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
|
||||
<Welcome modalClose={() => this.closeWelcome()} language={language.modals.welcome} />
|
||||
<Welcome modalClose={() => this.closeWelcome()} />
|
||||
</Modal>
|
||||
{/* <Modal onRequestClose={() => this.setState({ feedbackModal: false })} isOpen={this.state.feedbackModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
|
||||
<Feedback modalClose={() => this.setState({ feedbackModal: false })} />
|
||||
|
||||
@@ -7,18 +7,20 @@ import Marketplace from './tabs/Marketplace';
|
||||
import Navigation from './tabs/backend/Tabs';
|
||||
|
||||
export default function MainModal(props) {
|
||||
const language = window.language;
|
||||
|
||||
return (
|
||||
<div className='modal'>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<div>
|
||||
<Navigation navbar={true}>
|
||||
<div label={props.language.modals.main.navbar.settings}>
|
||||
<Settings language={props.language.modals.main.settings} toastLanguage={props.language.toasts} updateLanguage={props.language.modals.update} />
|
||||
<div label={language.modals.main.navbar.settings}>
|
||||
<Settings/>
|
||||
</div>
|
||||
<div label={props.language.modals.main.navbar.addons}>
|
||||
<div label={language.modals.main.navbar.addons}>
|
||||
<Addons/>
|
||||
</div>
|
||||
<div label={props.language.modals.main.navbar.marketplace}>
|
||||
<div label={language.modals.main.navbar.marketplace}>
|
||||
<Marketplace/>
|
||||
</div>
|
||||
</Navigation>
|
||||
|
||||
@@ -5,21 +5,23 @@ import TwitterIcon from '@material-ui/icons/Twitter';
|
||||
import ForumIcon from '@material-ui/icons/Forum';
|
||||
|
||||
export default function WelcomeModal(props) {
|
||||
const language = window.language.modals.welcome;
|
||||
|
||||
return (
|
||||
<div className='welcomeContent'>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<div className='welcomeModalText'>
|
||||
<h2 className='subtitle'>{props.language.title}</h2>
|
||||
<h2 className='subtitle'>{language.title}</h2>
|
||||
<h1 className='welcometitle'>Mue Tab</h1>
|
||||
<img alt='celebration' style={{ 'height': '200px', 'width': 'auto' }} draggable={false} src='./././icons/undraw_celebration.svg' />
|
||||
<h2 className='subtitle'>{props.language.information}</h2>
|
||||
<p>{props.language.thankyoumessage1},<br/> {props.language.thankyoumessage2}</p>
|
||||
<h2 className='subtitle'>{props.language.support}</h2>
|
||||
<h2 className='subtitle'>{language.information}</h2>
|
||||
<p>{language.thankyoumessage1},<br/> {language.thankyoumessage2}</p>
|
||||
<h2 className='subtitle'>{language.support}</h2>
|
||||
<a href='mailto:hello@muetab.com' className='welcomeLink' target='_blank' rel='noopener noreferrer'><EmailIcon/></a>
|
||||
<a href='https://twitter.com/getmue' className='welcomeLink' target='_blank' rel='noopener noreferrer'><TwitterIcon/></a>
|
||||
<a href='https://discord.gg/zv8C9F8' className='welcomeLink' target='_blank' rel='noopener noreferrer'><ForumIcon/></a>
|
||||
<br/>
|
||||
<button className='close' onClick={props.modalClose}>{props.language.close}</button>
|
||||
<button className='close' onClick={props.modalClose}>{language.close}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,8 +14,9 @@ export default class About extends React.PureComponent {
|
||||
contributors: [],
|
||||
sponsors: [],
|
||||
other_contributors: [],
|
||||
update: this.props.language.version.checking_update
|
||||
update: window.language.modals.main.settings.sections.about.version.checking_update
|
||||
}
|
||||
this.language = window.language.modals.main.settings.sections.about;
|
||||
}
|
||||
|
||||
async getGitHubData() {
|
||||
@@ -25,9 +26,9 @@ export default class About extends React.PureComponent {
|
||||
const versionData = await (await fetch('https://api.github.com/repos/mue/mue/releases')).json();
|
||||
const newVersion = versionData[0].tag_name;
|
||||
|
||||
let updateMsg = this.props.language.version.no_update;
|
||||
let updateMsg = this.language.version.no_update;
|
||||
if (version < newVersion) {
|
||||
updateMsg = `${this.props.language.version.update_available}: ${newVersion}`;
|
||||
updateMsg = `${this.language.version.update_available}: ${newVersion}`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -41,7 +42,7 @@ export default class About extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('offlineMode') === 'true') {
|
||||
this.setState({
|
||||
update: this.props.language.version.offline_mode
|
||||
update: this.language.version.offline_mode
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -52,15 +53,15 @@ export default class About extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>{this.props.language.title}</h2>
|
||||
<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.props.language.copyright} 2018-{new Date().getFullYear()} Mue Tab (BSD-3 License)</p>
|
||||
<p>{this.props.language.version.title} {version} ({this.state.update})</p>
|
||||
<h3>{this.props.language.resources_used.title}</h3>
|
||||
<p>Pexels ({this.props.language.resources_used.bg_images})</p>
|
||||
<p>Google ({this.props.language.resources_used.pin_icon})</p>
|
||||
<p>Undraw ({this.props.language.resources_used.welcome_img})</p>
|
||||
<h3>{this.props.language.contributors}</h3>
|
||||
<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>
|
||||
@@ -72,7 +73,7 @@ export default class About extends React.PureComponent {
|
||||
<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.props.language.supporters}</h3>
|
||||
<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>
|
||||
|
||||
@@ -15,11 +15,12 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
this.state = {
|
||||
resetModal: false
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem(type) {
|
||||
document.getElementById(type).value = '';
|
||||
toast(this.props.language.toasts.reset);
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
settingsImport(e) {
|
||||
@@ -29,7 +30,7 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
localStorage.setItem(key, content[key]);
|
||||
}
|
||||
|
||||
toast(this.props.language.toasts.imported);
|
||||
toast(this.language.toasts.imported);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -43,7 +44,7 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { advanced } = this.props.language.sections;
|
||||
const { advanced } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -51,24 +52,24 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
<Checkbox name='offlineMode' text={advanced.offline_mode} />
|
||||
|
||||
<h3>{advanced.data}</h3>
|
||||
<button className='reset' onClick={() => this.setState({ resetModal: true })}>{this.props.language.buttons.reset}</button>
|
||||
<button className='export' onClick={() => SettingsFunctions.exportSettings()}>{this.props.language.buttons.export}</button>
|
||||
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.props.language.buttons.import}</button>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<p>{advanced.custom_js} <span className='modalLink' onClick={() => this.resetItem('customjs')}>{this.language.buttons.reset}</span></p>
|
||||
<textarea id='customjs'></textarea>
|
||||
</ul>
|
||||
|
||||
<h3>{this.props.language.sections.experimental.title}</h3>
|
||||
<h3>{this.language.sections.experimental.title}</h3>
|
||||
<p>{advanced.experimental_warning}</p>
|
||||
<Checkbox name='experimental' text={this.props.language.enabled} />
|
||||
<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 })} />
|
||||
|
||||
@@ -13,6 +13,7 @@ export default class AppearanceSettings extends React.PureComponent {
|
||||
toast_duration: localStorage.getItem('toastDisplayTime'),
|
||||
font: localStorage.getItem('font') || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem(key) {
|
||||
@@ -42,7 +43,7 @@ export default class AppearanceSettings extends React.PureComponent {
|
||||
toast('resetItem requires a key!');
|
||||
}
|
||||
|
||||
toast(this.props.language.toasts.reset);
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -52,7 +53,7 @@ export default class AppearanceSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { appearance } = this.props.language.sections;
|
||||
const { appearance } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -66,9 +67,10 @@ export default class AppearanceSettings extends React.PureComponent {
|
||||
|
||||
<h3>{appearance.font.title}</h3>
|
||||
<ul>
|
||||
<p>{appearance.font.custom} <span className='modalLink' onClick={() => this.resetItem('font')}>{this.props.language.buttons.reset}</span></p>
|
||||
<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'
|
||||
@@ -84,6 +86,7 @@ export default class AppearanceSettings extends React.PureComponent {
|
||||
<option className='choices' value='700'>Bold</option>
|
||||
<option className='choices' value='800'>Extra-Bold</option>
|
||||
</Dropdown>
|
||||
<br /><br />
|
||||
<Dropdown
|
||||
label='Font Style'
|
||||
name='fontstyle'
|
||||
@@ -93,16 +96,17 @@ export default class AppearanceSettings extends React.PureComponent {
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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>
|
||||
|
||||
@@ -14,7 +14,7 @@ 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': this.props.language.sections.background.source.disabled, 'stop': 0 }], 'type': 'linear' };
|
||||
DefaultGradientSettings = { 'angle': '180', 'gradient': [{ 'colour': window.language.modals.main.settings.sections.background.source.disabled, 'stop': 0 }], 'type': 'linear' };
|
||||
GradientPickerInitalState = undefined;
|
||||
|
||||
constructor(...args) {
|
||||
@@ -25,6 +25,7 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
customBackground: localStorage.getItem('customBackground') || '',
|
||||
gradientSettings: this.DefaultGradientSettings
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem(key) {
|
||||
@@ -61,7 +62,7 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
toast('resetItem requires a key!');
|
||||
}
|
||||
|
||||
toast(this.props.language.toasts.reset);
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
InitializeColorPickerState(gradientSettings) {
|
||||
@@ -130,14 +131,14 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
currentGradientSettings = () => {
|
||||
if (typeof this.state.gradientSettings === 'object' && this.state.gradientSettings.gradient.every(g => g.colour !== this.props.language.sections.background.source.disabled)) {
|
||||
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.props.language.sections.background.source.disabled;
|
||||
return this.language.sections.background.source.disabled;
|
||||
}
|
||||
|
||||
onColorPickerChange = (attrs, name) => {
|
||||
@@ -170,13 +171,13 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
localStorage.setItem('brightness', this.state.brightness);
|
||||
localStorage.setItem('customBackground', this.state.customBackground);
|
||||
|
||||
if (document.getElementById('customBackgroundHex').value !== this.props.language.sections.background.source.disabled) {
|
||||
if (document.getElementById('customBackgroundHex').value !== this.language.sections.background.source.disabled) {
|
||||
localStorage.setItem('customBackgroundColour', document.getElementById('customBackgroundHex').value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { background } = this.props.language.sections;
|
||||
const { background } = this.language.sections;
|
||||
|
||||
let colourSettings = null;
|
||||
if (typeof this.state.gradientSettings === 'object') {
|
||||
@@ -229,11 +230,11 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
|
||||
<h3>{background.effects.title}</h3>
|
||||
<ul>
|
||||
<p>{background.effects.blur} ({this.state.blur}%) <span className='modalLink' onClick={() => this.resetItem('blur')}>{this.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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>
|
||||
|
||||
@@ -245,16 +246,16 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
</Dropdown>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{background.source.custom_url} <span className='modalLink' onClick={() => this.resetItem('customBackground')}>{this.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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>
|
||||
|
||||
@@ -12,6 +12,7 @@ export default class GreetingSettings extends React.PureComponent {
|
||||
birthday: new Date(localStorage.getItem('birthday')) || new Date(),
|
||||
greetingName: localStorage.getItem('greetingName') || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetItem() {
|
||||
@@ -19,7 +20,7 @@ export default class GreetingSettings extends React.PureComponent {
|
||||
greetingName: ''
|
||||
});
|
||||
|
||||
toast(this.props.language.toasts.reset);
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
changeDate(data) {
|
||||
@@ -40,21 +41,21 @@ export default class GreetingSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { greeting } = this.props.language.sections;
|
||||
const { greeting } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{greeting.title}</h2>
|
||||
<Checkbox name='greeting' text={this.props.language.enabled} />
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.enabled} />
|
||||
<Checkbox name='birthdayenabled' text={this.language.enabled} />
|
||||
<ul>
|
||||
<p>{greeting.birthday_date}</p>
|
||||
<DatePicker onChange={(data) => this.changeDate(data)} value={this.state.birthday}/>
|
||||
|
||||
@@ -4,17 +4,19 @@ import Dropdown from '../Dropdown';
|
||||
|
||||
const languages = require('../../../../modules/languages.json');
|
||||
|
||||
export default function LanguageSettings (props) {
|
||||
export default function LanguageSettings () {
|
||||
const language = window.language.modals.main.settings.sections.language;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{props.language.title}</h2>
|
||||
<Dropdown label={props.language.title} name='language' id='language' onChange={() => localStorage.setItem('language', document.getElementById('language').value)}>
|
||||
<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={props.language.quote} name='quotelanguage' id='quotelanguage' onChange={() => localStorage.setItem('quotelanguage', document.getElementById('quotelanguage').value)}>
|
||||
<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>
|
||||
|
||||
@@ -11,6 +11,7 @@ export default class QuoteSettings extends React.PureComponent {
|
||||
customQuote: localStorage.getItem('customQuote') || '',
|
||||
customQuoteAuthor: localStorage.getItem('customQuoteAuthor') || 'Unknown'
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +35,7 @@ export default class QuoteSettings extends React.PureComponent {
|
||||
toast('resetItem requires a key!');
|
||||
}
|
||||
|
||||
toast(this.props.language.toasts.reset);
|
||||
toast(this.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -43,19 +44,19 @@ export default class QuoteSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { quote } = this.props.language.sections;
|
||||
const { quote } = this.language.sections;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{quote.title}</h2>
|
||||
<Checkbox name='quote' text={this.props.language.enabled}/>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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.props.language.buttons.reset}</span></p>
|
||||
<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>
|
||||
|
||||
@@ -55,12 +55,13 @@ export default class SearchSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { search } = this.props.language.sections;
|
||||
const language = window.language.modals.main.settings;
|
||||
const { search } = language.sections;
|
||||
|
||||
return (
|
||||
<div className='section'>
|
||||
<h2>{search.title}</h2>
|
||||
<Checkbox name='searchBar' text={this.props.language.enabled} />
|
||||
<Checkbox name='searchBar' text={language.enabled} />
|
||||
<Checkbox name='voiceSearch' text={search.voice_search} />
|
||||
<ul>
|
||||
<Dropdown label={search.search_engine}
|
||||
@@ -74,7 +75,7 @@ export default class SearchSettings extends React.PureComponent {
|
||||
</Dropdown>
|
||||
</ul>
|
||||
<ul id='searchEngineInput' style={{ display: 'none' }}>
|
||||
<p style={{ 'marginTop': '0px' }}>{search.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{this.props.language.reset}</span></p>
|
||||
<p style={{ 'marginTop': '0px' }}>{search.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{language.reset}</span></p>
|
||||
<input type='text' id='customSearchEngine'></input>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ export default class TimeSettings extends React.PureComponent {
|
||||
this.state = {
|
||||
timeType: localStorage.getItem('timeType') || 'digital'
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
changeType() {
|
||||
@@ -20,7 +21,7 @@ export default class TimeSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { time } = this.props.language.sections;
|
||||
const { time } = this.language.sections;
|
||||
|
||||
let timeSettings;
|
||||
|
||||
@@ -54,7 +55,7 @@ export default class TimeSettings extends React.PureComponent {
|
||||
return (
|
||||
<div>
|
||||
<h2>{time.title}</h2>
|
||||
<Checkbox name='time' text={this.props.language.enabled} />
|
||||
<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>
|
||||
@@ -64,7 +65,7 @@ export default class TimeSettings extends React.PureComponent {
|
||||
{timeSettings}
|
||||
|
||||
<h3>{time.date.title}</h3>
|
||||
<Checkbox name='date' text={this.props.language.enabled} />
|
||||
<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} />
|
||||
|
||||
@@ -13,43 +13,23 @@ import Changelog from '../settings/sections/Changelog';
|
||||
|
||||
import SettingsTabs from './backend/Tabs';
|
||||
|
||||
export default function Settings (props) {
|
||||
export default function Settings () {
|
||||
const language = window.language.modals.main.settings.sections;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SettingsTabs>
|
||||
<div label={props.language.sections.time.title}>
|
||||
<Time language={props.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.quote.title}>
|
||||
<Quote language={props.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.greeting.title}>
|
||||
<Greeting language={props.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.background.title}>
|
||||
<Background language={props.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.search.title}>
|
||||
<Search language={props.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.appearance.title}>
|
||||
<Appearance language={props.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.language.title}>
|
||||
<Language language={props.language.sections.language}/>
|
||||
</div>
|
||||
<div label={props.language.sections.advanced.title}>
|
||||
<Advanced language={props.language}/>
|
||||
</div>
|
||||
<div label='Experimental'>
|
||||
|
||||
</div>
|
||||
<div label={props.language.sections.changelog}>
|
||||
<Changelog language={props.updateLanguage} />
|
||||
</div>
|
||||
<div label={props.language.sections.about.title}>
|
||||
<About language={props.language.sections.about}/>
|
||||
</div>
|
||||
<div label={language.time.title}><Time/></div>
|
||||
<div label={language.quote.title}><Quote/></div>
|
||||
<div label={language.greeting.title}><Greeting/></div>
|
||||
<div label={language.background.title}><Background/></div>
|
||||
<div label={language.search.title}><Search/></div>
|
||||
<div label={language.appearance.title}><Appearance/></div>
|
||||
<div label={language.language.title}><Language/></div>
|
||||
<div label={language.advanced.title}><Advanced/></div>
|
||||
<div label='Experimental'></div>
|
||||
<div label={language.changelog}><Changelog/></div>
|
||||
<div label={language.about.title}><About/></div>
|
||||
</SettingsTabs>
|
||||
<div className='reminder-info'>
|
||||
<h1>IMPORTANT INFO</h1>
|
||||
|
||||
@@ -49,15 +49,9 @@ export default function Tab(props) {
|
||||
case 'Background': icon = <Background/>; break;
|
||||
case 'Search': icon = <Search/>; break;
|
||||
case 'Appearance': icon = <Appearance/>; break;
|
||||
case 'Language':
|
||||
icon = <Language/>;
|
||||
divider = <div><hr/></div>;
|
||||
break;
|
||||
case 'Language': icon = <Language/>; divider = true; break;
|
||||
case 'Advanced': icon = <Settings/>; break;
|
||||
case 'Experimental':
|
||||
icon = <Experimental/>;
|
||||
divider = <div><hr/></div>;
|
||||
break;
|
||||
case 'Experimental': icon = <Experimental/>; divider = true; break;
|
||||
case 'Change Log': icon = <Changelog/>; break;
|
||||
case 'About': icon = <About/>; break;
|
||||
|
||||
@@ -65,10 +59,7 @@ export default function Tab(props) {
|
||||
case 'Themes': icon = <Colors/>; break;
|
||||
case 'Photo Packs': icon = <Background/>; break;
|
||||
case 'Quote Packs': icon = <Quote/>; break;
|
||||
case 'Plugins':
|
||||
icon = <Plugins/>;
|
||||
divider = <div><hr/></div>;
|
||||
break;
|
||||
case 'Plugins': icon = <Plugins/>; divider = true; break;
|
||||
case 'Added': icon = <Added/>; break;
|
||||
|
||||
default: break;
|
||||
@@ -79,7 +70,7 @@ export default function Tab(props) {
|
||||
<li className={className} onClick={() => props.onClick(props.label)}>
|
||||
{icon} <span>{props.label}</span>
|
||||
</li>
|
||||
{divider}
|
||||
{(divider === true) ? <div><hr/></div> : null}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,16 +27,15 @@ export default class Widgets extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { language, languagecode } = this.props;
|
||||
const enabled = this.enabled;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{enabled('searchBar') ? <Search language={language.widgets.search} /> : null}
|
||||
{enabled('greeting') ? <Greeting language={language.widgets.greeting} /> : null}
|
||||
{enabled('searchBar') ? <Search/> : null}
|
||||
{enabled('greeting') ? <Greeting/> : null}
|
||||
{enabled('time') ? <Clock/> : null}
|
||||
{enabled('date') ? <Date/> : null}
|
||||
{enabled('quote') ? <Quote language={language.toasts} languagecode={languagecode} /> : null}
|
||||
{enabled('quote') ? <Quote/> : null}
|
||||
{enabled('view') ? <Maximise/> : null}
|
||||
{enabled('favouriteEnabled') ? <Favourite/> : null}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -6,13 +6,15 @@ import Resolution from '@material-ui/icons/Crop';
|
||||
import Photographer from '@material-ui/icons/Person';
|
||||
|
||||
export default function PhotoInformation(props) {
|
||||
const language = window.language.widgets.background;
|
||||
|
||||
return (
|
||||
<div className='photoInformation'>
|
||||
<h1 id='photographer'>{props.language.credit}</h1>
|
||||
<h1 id='photographer'>{language.credit}</h1>
|
||||
<Info className='photoInformationHover'/>
|
||||
<div className={props.className || 'infoCard'}>
|
||||
<Info className='infoIcon'/>
|
||||
<h1>{props.language.information}</h1>
|
||||
<h1>{language.information}</h1>
|
||||
<hr/>
|
||||
<Location/>
|
||||
<span id='location'/>
|
||||
|
||||
@@ -8,6 +8,7 @@ export default class Greeting extends React.PureComponent {
|
||||
this.state = {
|
||||
greeting: ''
|
||||
};
|
||||
this.language = window.language.widgets.greeting;
|
||||
}
|
||||
|
||||
doEvents(time, message) {
|
||||
@@ -21,13 +22,13 @@ export default class Greeting extends React.PureComponent {
|
||||
|
||||
// If it's December 25th, set the greeting string to "Merry Christmas"
|
||||
if (month === 11 && date === 25) {
|
||||
message = this.props.language.christmas;
|
||||
message = this.language.christmas;
|
||||
// If the date is January 1st, set the greeting string to "Happy new year"
|
||||
} else if (month === 0 && date === 1) {
|
||||
message = this.props.language.newyear;
|
||||
message = this.language.newyear;
|
||||
// If it's October 31st, set the greeting string to "Happy Halloween"
|
||||
} else if (month === 9 && date === 31) {
|
||||
message = this.props.language.halloween;
|
||||
message = this.language.halloween;
|
||||
}
|
||||
|
||||
return message;
|
||||
@@ -38,13 +39,13 @@ export default class Greeting extends React.PureComponent {
|
||||
const hour = now.getHours();
|
||||
|
||||
// Set the default greeting string to "Good evening"
|
||||
let message = this.props.language.evening;
|
||||
let message = this.language.evening;
|
||||
// If it's before 12am, set the greeting string to "Good morning"
|
||||
if (hour < 12) {
|
||||
message = this.props.language.morning;
|
||||
message = this.language.morning;
|
||||
// If it's before 6pm, set the greeting string to "Good afternoon"
|
||||
} else if (hour < 18) {
|
||||
message = this.props.language.afternoon;
|
||||
message = this.language.afternoon;
|
||||
}
|
||||
|
||||
// Events
|
||||
@@ -72,7 +73,7 @@ export default class Greeting extends React.PureComponent {
|
||||
// Birthday
|
||||
const birth = new Date(localStorage.getItem('birthday'));
|
||||
if (localStorage.getItem('birthdayenabled') === 'true' && birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
|
||||
message = this.props.language.birthday;
|
||||
message = this.language.birthday;
|
||||
}
|
||||
|
||||
// Set the state to the greeting string
|
||||
|
||||
@@ -15,13 +15,15 @@ const Notes = React.lazy(() => import('./Notes'));
|
||||
const renderLoader = () => <div></div>;
|
||||
|
||||
export default function Navbar(props) {
|
||||
const language = window.language;
|
||||
|
||||
return (
|
||||
<div className='navbar-container'>
|
||||
{(localStorage.getItem('notesEnabled') === 'true') ?
|
||||
<div className='notes'>
|
||||
<NotesIcon className='topicons'/>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Notes language={props.language.widgets.navbar.notes}/>
|
||||
<Notes/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
:null}
|
||||
@@ -33,12 +35,12 @@ export default function Navbar(props) {
|
||||
:null}
|
||||
|
||||
{(localStorage.getItem('refresh') === 'true') ?
|
||||
<Tooltip title={props.language.widgets.navbar.tooltips.refresh}>
|
||||
<Tooltip title={language.widgets.navbar.tooltips.refresh}>
|
||||
<RefreshIcon className='refreshicon topicons' onClick={() => window.location.reload()}/>
|
||||
</Tooltip>
|
||||
:null}
|
||||
|
||||
<Tooltip title={props.language.modals.main.navbar.settings} placement='top'>
|
||||
<Tooltip title={language.modals.main.navbar.settings} placement='top'>
|
||||
<Gear className='settings-icon topicons' onClick={() => props.openModal('mainModal')}/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ export default class Notes extends React.PureComponent {
|
||||
this.state = {
|
||||
notes: localStorage.getItem('notes') || ''
|
||||
};
|
||||
this.language = window.language.widgets.navbar.notes
|
||||
}
|
||||
|
||||
setNotes = (e) => {
|
||||
@@ -47,9 +48,9 @@ export default class Notes extends React.PureComponent {
|
||||
<span id='noteContainer' className={classList}>
|
||||
<div className='topbarnotes'>
|
||||
<NotesIcon/>
|
||||
<h3>{this.props.language.title}</h3>
|
||||
<h3>{this.language.title}</h3>
|
||||
</div>
|
||||
<TextareaAutosize rowsMax={50} placeholder={this.props.language.placeholder} value={this.state.notes} onChange={this.setNotes}/>
|
||||
<TextareaAutosize rowsMax={50} placeholder={this.language.placeholder} value={this.state.notes} onChange={this.setNotes}/>
|
||||
<button onClick={this.pin} className='pinNote'><Pin/></button>
|
||||
<button onClick={() => navigator.clipboard.writeText(this.state.notes)} className='saveNote'><CopyIcon/></button>
|
||||
</span>
|
||||
|
||||
@@ -22,6 +22,9 @@ export default class Quote extends React.PureComponent {
|
||||
tweet: <TwitterIcon className='copyButton' onClick={() => this.tweetQuote()} />,
|
||||
copy: <FileCopy className='copyButton' onClick={() => this.copyQuote()} />
|
||||
};
|
||||
|
||||
this.language = window.language.widgets.quote;
|
||||
this.languagecode = window.languagecode;
|
||||
}
|
||||
|
||||
doOffline() {
|
||||
@@ -98,7 +101,7 @@ export default class Quote extends React.PureComponent {
|
||||
return this.doOffline();
|
||||
}
|
||||
|
||||
let authorlink = `https://${this.props.languagecode.split('-')[0]}.wikipedia.org/wiki/${data.author.split(' ').join('_')}`;
|
||||
let authorlink = `https://${this.languagecode.split('-')[0]}.wikipedia.org/wiki/${data.author.split(' ').join('_')}`;
|
||||
if (localStorage.getItem('authorLink') === 'false') {
|
||||
authorLink = null;
|
||||
}
|
||||
@@ -116,7 +119,7 @@ export default class Quote extends React.PureComponent {
|
||||
|
||||
copyQuote() {
|
||||
navigator.clipboard.writeText(`${this.state.quote} - ${this.state.author}`);
|
||||
toast(this.props.language.quote);
|
||||
toast(this.language.quote);
|
||||
}
|
||||
|
||||
tweetQuote() {
|
||||
|
||||
@@ -15,6 +15,7 @@ export default class Search extends React.PureComponent {
|
||||
query: '',
|
||||
microphone: null
|
||||
};
|
||||
this.language = window.language.widgets.search;
|
||||
}
|
||||
|
||||
startSpeechRecognition() {
|
||||
@@ -73,7 +74,7 @@ export default class Search extends React.PureComponent {
|
||||
<form action={this.state.url}>
|
||||
{this.state.microphone}
|
||||
<SearchIcon onClick={() => this.searchButton()} id='searchButton'/>
|
||||
<input type='text' placeholder={this.props.language} name={this.state.query} id='searchtext'/>
|
||||
<input type='text' placeholder={this.language} name={this.state.query} id='searchtext'/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,13 @@ import 'react-toastify/dist/ReactToastify.css';
|
||||
import '@fontsource/lexend-deca/latin-400.css';
|
||||
import '@fontsource/roboto/cyrillic-400.css';
|
||||
|
||||
// language
|
||||
import merge from './modules/helpers/merge';
|
||||
const languagecode = localStorage.getItem('language') || 'en-GB';
|
||||
// we set things to window. so they're global and we avoid passing the translation strings as props to each component
|
||||
window.languagecode = languagecode;
|
||||
window.language = merge(require('./translations/en-GB.json'), require(`./translations/${languagecode}.json`));
|
||||
|
||||
ReactDOM.render(
|
||||
<App/>,
|
||||
document.getElementById('root')
|
||||
|
||||
@@ -209,7 +209,6 @@ li {
|
||||
.tab-content {
|
||||
position: absolute;
|
||||
left: 25%;
|
||||
top: 7%;
|
||||
|
||||
h3 {
|
||||
text-transform: uppercase;
|
||||
@@ -220,6 +219,15 @@ li {
|
||||
.tab-content {
|
||||
position: absolute;
|
||||
left: 30%;
|
||||
top: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1920px) {
|
||||
.tab-content {
|
||||
position: absolute;
|
||||
left: 30%;
|
||||
top: 7%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user