From 21ae1ff461cff5fdfdd27a045081cf0aadfd35d6 Mon Sep 17 00:00:00 2001 From: David Ralph Date: Mon, 10 Aug 2020 19:49:46 +0100 Subject: [PATCH] Massive Update Co-authored-by: Alex Sparkes Co-authored-by: Wessel Tip Co-authored-by: Isaac Saunders --- README.md | 2 +- package.json | 2 + src/App.jsx | 69 +++--- src/components/Background.jsx | 68 +++--- src/components/Clock.jsx | 67 ++++-- src/components/Credit.jsx | 9 +- src/components/Greeting.jsx | 14 +- src/components/Navbar.jsx | 13 +- src/components/Quote.jsx | 32 ++- src/components/Search.jsx | 15 +- src/components/Settings.jsx | 320 +++++++++++++++------------ src/components/Toast.jsx | 15 -- src/components/Update.jsx | 35 +-- src/components/settings/Checkbox.jsx | 13 ++ src/components/settings/Slider.jsx | 15 ++ src/index.js | 2 - src/modules/constants.js | 2 + src/modules/defaultSettings.json | 62 ++++++ src/modules/settingsFunctions.js | 60 +++++ src/scss/index.scss | 41 ++++ src/scss/modules/_clock.scss | 29 +++ src/scss/modules/_credit.scss | 17 +- src/scss/modules/_modal.scss | 95 +++++++- src/scss/modules/_navbar.scss | 19 +- src/scss/modules/_quote.scss | 2 +- src/scss/modules/_search.scss | 42 ++-- src/scss/modules/_settings.scss | 84 ++++++- src/scss/modules/_toast.scss | 105 ++------- src/translations/en.json | 74 +++++++ src/translations/fr.json | 74 +++++++ src/translations/nl.json | 74 +++++++ 31 files changed, 1017 insertions(+), 454 deletions(-) delete mode 100644 src/components/Toast.jsx create mode 100644 src/components/settings/Checkbox.jsx create mode 100644 src/components/settings/Slider.jsx create mode 100644 src/modules/constants.js create mode 100644 src/modules/defaultSettings.json create mode 100644 src/modules/settingsFunctions.js create mode 100644 src/translations/en.json create mode 100644 src/translations/fr.json create mode 100644 src/translations/nl.json diff --git a/README.md b/README.md index 025f05da..7ecf87e0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an * [Opera/Other](#operaother) * [Contributing](#development) * [Requirements](#requirements) - * [Starting](#starting) + * [Starting](#starting) * [Building](#building) * [Credits](#credits) * [Maintainers](#maintainers) diff --git a/package.json b/package.json index 7f366323..6f13a392 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ "@muetab/quotes": "^1.0.0", "copy-text-to-clipboard": "^2.2.0", "react": "^16.13.1", + "react-clock": "^2.4.0", "react-dom": "^16.13.1", "react-modal": "^3.11.2", "react-scripts": "3.4.1", + "react-toastify": "^6.0.8", "supports-webp": "^2.0.1" }, "devDependencies": { diff --git a/src/App.jsx b/src/App.jsx index 7239c088..d1d67eb9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,3 @@ -//* Imports import React from 'react'; import Background from './components/Background'; import Clock from './components/Clock'; @@ -7,50 +6,34 @@ import Quote from './components/Quote'; import Search from './components/Search'; import Credit from './components/Credit'; import Navbar from './components/Navbar'; -import Toast from './components/Toast'; +import { ToastContainer } from 'react-toastify'; import Modal from 'react-modal'; -import './scss/index.scss'; +import './scss/index.scss'; +import 'react-toastify/dist/ReactToastify.css'; + +const defaultSettings = require('./modules/defaultSettings.json'); const Settings = React.lazy(() => import('./components/Settings')); const Update = React.lazy(() => import('./components/Update')); const renderLoader = () =>
; -//* App -export default class App extends React.Component { - // Modal stuff +export default class App extends React.PureComponent { constructor(props) { super(props); - this.state = { - settingsModal: false, + this.state = { + settingsModal: false, updateModal: false }; } setDefaultSettings() { localStorage.clear(); - - localStorage.setItem('time', true); - localStorage.setItem('greeting', true); - localStorage.setItem('background', true); - localStorage.setItem('quote', true); - localStorage.setItem('searchBar', true); - localStorage.setItem('blur', 0); - localStorage.setItem('copyButton', false); - localStorage.setItem('seconds', false); - localStorage.setItem('24hour', false); - localStorage.setItem('offlineMode', false); - localStorage.setItem('webp', false); - localStorage.setItem('events', true); - localStorage.setItem('customBackgroundColour', ''); - localStorage.setItem('customBackground', ''); - localStorage.setItem('greetingName', ''); - localStorage.setItem('defaultGreetingMessage', true); + defaultSettings.forEach(element => localStorage.setItem(element.name, element.value)); // Set theme depending on user preferred - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) localStorage.setItem('darkTheme', true); - else localStorage.setItem('darkTheme', false); - localStorage.setItem('darkTheme', false); + // if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) localStorage.setItem('darkTheme', true); + //else localStorage.setItem('darkTheme', false); // Finally we set this to true so it doesn't run the function on every load localStorage.setItem('firstRun', true); @@ -62,27 +45,31 @@ export default class App extends React.Component { if (!localStorage.getItem('firstRun')) this.setDefaultSettings(); let modalClassList = 'Modal'; - const darkTheme = localStorage.getItem('darkTheme'); - if (darkTheme === 'true') modalClassList = 'Modal dark'; - + if (localStorage.getItem('darkTheme') === 'true') modalClassList = 'Modal dark'; + + let overlayClassList = 'Overlay'; + if (localStorage.getItem('animations') === 'true') overlayClassList = 'Overlay modal-animation'; + + let language = require(`./translations/${localStorage.getItem('language')}.json`); + return (
+
- + this.setState({ settingsModal: true })} updateModalOpen={() => this.setState({ updateModal: true })} /> - - - - - + + + + - - this.setState({ settingsModal: false })} setDefaultSettings={() => this.setDefaultSettings()} /> + this.setState({ settingsModal: false })} isOpen={this.state.settingsModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}> + this.setState({ settingsModal: false })} setDefaultSettings={() => this.setDefaultSettings()} /> - - this.setState({ updateModal: false })} /> + this.setState({ updateModal: false })} isOpen={this.state.updateModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}> + this.setState({ updateModal: false })} />
diff --git a/src/components/Background.jsx b/src/components/Background.jsx index 0d8fef47..987fd883 100644 --- a/src/components/Background.jsx +++ b/src/components/Background.jsx @@ -1,15 +1,14 @@ -//* Imports import React from 'react'; import supportsWebP from 'supports-webp'; +import * as Constants from '../modules/constants'; -export default class Background extends React.Component { +export default class Background extends React.PureComponent { doOffline() { - const photo = Math.floor(Math.random() * (20 - 1 + 1)) + 1; // There are 20 images in the offline-images folder + const photo = Math.floor(Math.random() * (Constants.OFFLINE_IMAGES - 1 + 1)) + 1; // There are 20 images in the offline-images folder document.getElementById('backgroundCredits').style.display = 'none'; // Hide the location icon let photographer; // Photographer credit - const pixabayNumbers = [2, 3, 9, 11, 13, 14, 15]; // As there are a lot of Pixabay photos, we shorten the code a bit here - if (pixabayNumbers.includes(photo)) photographer = 'Pixabay'; + if ([2, 3, 9, 11, 13, 14, 15].includes(photo)) photographer = 'Pixabay'; // As there are a lot of Pixabay photos, we shorten the code a bit here else switch (photo) { case 1: photographer = 'Tirachard Kumtanom'; break; case 4: photographer = 'Sohail Na'; break; @@ -20,47 +19,62 @@ export default class Background extends React.Component { } document.getElementById('backgroundImage').setAttribute('style', `-webkit-filter:blur(${localStorage.getItem('blur')}px); background-image: url(../offline-images/${photo}.jpeg)`); // Set background and blur etc - document.getElementById('photographer').innerText = `Photo by ${photographer} (Pexels)`; // Set the credit + let credit = document.getElementById('photographer'); + credit.innerText = `${credit.innerText} ${photographer} (Pexels)`; // Set the credit } async setBackground() { - const enabled = localStorage.getItem('offlineMode'); - if (enabled === 'true') return this.doOffline(); + if (localStorage.getItem('offlineMode')=== 'true') return this.doOffline(); const colour = localStorage.getItem('customBackgroundColour'); if (colour) { document.getElementById('backgroundCredits').style.display = 'none'; // Hide the location icon + document.getElementById('photographer').style.display = 'none'; return document.getElementById('backgroundImage').setAttribute('style', `-webkit-filter:blur(${localStorage.getItem('blur')}px); background-color: ${colour}`); // Set background and blur etc } const custom = localStorage.getItem('customBackground'); - if (custom) { + if (custom !== '') { document.getElementById('backgroundCredits').style.display = 'none'; // Hide the location icon + document.getElementById('photographer').style.display = 'none'; return document.getElementById('backgroundImage').setAttribute('style', `-webkit-filter:blur(${localStorage.getItem('blur')}px); background-image: url(${custom})`); // Set background and blur etc - } + } else { + try { // First we try and get an image from the API... + let requestURL; + const enabled = localStorage.getItem('webp'); + const backgroundAPI = localStorage.getItem('backgroundAPI'); + let data; - try { // First we try and get an image from the API... - let requestURL; - const enabled = localStorage.getItem('webp'); - if (await supportsWebP && enabled === 'true') requestURL = 'https://api.muetab.xyz/getImage?webp=true'; - else requestURL = 'https://api.muetab.xyz/getImage?category=Outdoors'; - let data = await fetch(requestURL); - data = await data.json(); + switch (backgroundAPI) { + case 'mue': + if (await supportsWebP && enabled === 'true') requestURL = Constants.API_URL + '/getImage?webp=true'; + else requestURL = Constants.API_URL + '/getImage?category=Outdoors'; + break; + case 'unsplash': + requestURL = 'https://unsplash.muetab.xyz/getImage'; + break; + default: + if (await supportsWebP && enabled === 'true') requestURL = Constants.API_URL +'/getImage?webp=true'; + else requestURL = Constants.API_URL + '/getImage?category=Outdoors'; + break; + } - document.getElementById('backgroundImage').setAttribute('style', `-webkit-filter:blur(${localStorage.getItem('blur')}px); background-image: url(${data.file})`); // Set background and blur etc - document.getElementById('photographer').innerText = `Photo by ${data.photographer}`; // Set the credit - document.getElementById('location').innerText = `${data.location}`; // Set the location tooltip - } catch (e) { // ..and if that fails we load one locally - this.doOffline(); + data = await fetch(requestURL); + data = await data.json(); + + document.getElementById('backgroundImage').setAttribute('style', `-webkit-filter:blur(${localStorage.getItem('blur')}px); background-image: url(${data.file})`); // Set background and blur etc + let credit = document.getElementById('photographer'); + credit.innerText = `${credit.innerText} ${data.photographer}`; // Set the credit + document.getElementById('location').innerText = `${data.location}`; // Set the location tooltip + } catch (e) { // ..and if that fails we load one locally + this.doOffline(); + } } } componentDidMount() { - const enabled = localStorage.getItem('background'); - if (enabled === 'false') { - document.getElementById('backgroundCredits').style.display = 'none'; - return; - } + if (localStorage.getItem('background') === 'false') return document.getElementById('backgroundCredits').style.display = 'none'; + if (localStorage.getItem('animations') === 'true') document.getElementById('backgroundImage').classList.add('fade-in'); this.setBackground(); } diff --git a/src/components/Clock.jsx b/src/components/Clock.jsx index 05a4a1fe..27664dd3 100644 --- a/src/components/Clock.jsx +++ b/src/components/Clock.jsx @@ -1,36 +1,61 @@ import React from 'react'; +import Analog from 'react-clock'; -export default class Clock extends React.Component { +export default class Clock extends React.PureComponent { constructor(...args) { super(...args); this.timer = undefined; this.state = { - date: '', + time: '', ampm: '' }; } - startTime(time = localStorage.getItem('seconds') === 'true' ? (1000 - Date.now() % 1000) : (60000 - Date.now() % 60000)) { + startTime(time = localStorage.getItem('seconds') === 'true' || localStorage.getItem('analog') === 'true' ? (1000 - Date.now() % 1000) : (60000 - Date.now() % 60000)) { this.timer = setTimeout(() => { const now = new Date(); - let sec = ''; - if (localStorage.getItem('seconds') === 'true') sec = `:${('00' + now.getSeconds()).slice(-2)}`; - - if (localStorage.getItem('24hour') === 'true') { + // Analog clock + if (localStorage.getItem('analog') === 'true') { this.setState({ - date: `${('00' + now.getHours()).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}` + time: now }); } else { - // 12 hour support - let hours = now.getHours(); - if (hours > 12) hours -= 12; + let sec = ''; - this.setState({ - date: `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`, - ampm: now.getHours() > 11 ? 'PM' : 'AM' - }); + // Extra 0 + const zero = localStorage.getItem('zero'); + + if (localStorage.getItem('seconds') === 'true') { + if (zero === 'false') sec = `:${now.getSeconds()}`; + else sec = `:${('00' + now.getSeconds()).slice(-2)}`; + } + + if (localStorage.getItem('24hour') === 'true') { + let time = ''; + if (zero === 'false') time = `${now.getHours()}:${now.getMinutes()}${sec}`; + else time = `${('00' + now.getHours()).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`; + this.setState({ + time: time + }); + } else { + // 12 hour support + let hours = now.getHours(); + if (hours > 12) hours -= 12; + + // Toggle AM/PM + let ampm = now.getHours() > 11 ? 'PM' : 'AM'; + if (localStorage.getItem('ampm') === 'false') ampm = ''; + + let time = ''; + if (zero === 'false') time = `${hours}:${now.getMinutes()}${sec}`; + else time = `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`; + this.setState({ + time: time, + ampm: ampm + }); + } } this.startTime(); @@ -38,17 +63,13 @@ export default class Clock extends React.Component { } componentDidMount() { - const enabled = localStorage.getItem('time'); - if (enabled === 'false') return; + if (localStorage.getItem('time') === 'false') return; this.startTime(0); } render() { - return

- {this.state.date} - - {this.state.ampm} - -

; + let clockHTML =

{this.state.time}{this.state.ampm}

; + if (localStorage.getItem('analog') === 'true') clockHTML = ; + return clockHTML; } } diff --git a/src/components/Credit.jsx b/src/components/Credit.jsx index a44781b1..b6c7bae2 100644 --- a/src/components/Credit.jsx +++ b/src/components/Credit.jsx @@ -1,19 +1,18 @@ -/* eslint-disable */ -//* Imports +/* eslint-disable jsx-a11y/heading-has-content */ import RoomIcon from '@material-ui/icons/Room'; import React from 'react'; -export default class Credit extends React.Component { +export default class Credit extends React.PureComponent { render() { return (
{/*

*/} -

+

{this.props.language}

-
+ ); } } \ No newline at end of file diff --git a/src/components/Greeting.jsx b/src/components/Greeting.jsx index a389a8f2..71a79827 100644 --- a/src/components/Greeting.jsx +++ b/src/components/Greeting.jsx @@ -1,6 +1,6 @@ import React from 'react'; -export default class Greeting extends React.Component { +export default class Greeting extends React.PureComponent { constructor(...args) { super(...args); this.state = { @@ -9,8 +9,7 @@ export default class Greeting extends React.Component { } doEvents(time, message) { - const enabled = localStorage.getItem('events'); - if (enabled === 'false') return message; + if (localStorage.getItem('events') === 'false') return message; // Get current month & day const m = time.getMonth(); @@ -27,9 +26,9 @@ export default class Greeting extends React.Component { const now = new Date(); const hour = now.getHours(); - let message = 'Good evening'; // Set the default greeting string to "Good evening" - if (hour < 12) message = 'Good morning'; // If it's before 12am, set the greeting string to "Good morning" - else if (hour < 18) message = 'Good afternoon'; // If it's before 6pm, set the greeting string to "Good afternoon" + let message = this.props.language.evening; // Set the default greeting string to "Good evening" + if (hour < 12) message = this.props.language.morning; // If it's before 12am, set the greeting string to "Good morning" + else if (hour < 18) message = this.props.language.afternoon; // If it's before 6pm, set the greeting string to "Good afternoon" // Events message = this.doEvents(now, message); @@ -53,8 +52,7 @@ export default class Greeting extends React.Component { } componentDidMount() { - const enabled = localStorage.getItem('greeting'); - if (enabled === 'false') return; + if (localStorage.getItem('greeting') === 'false') return; this.getGreeting(); } diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 2e6aac09..3a72b330 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,20 +1,21 @@ -//* Imports import RefreshIcon from '@material-ui/icons/Refresh'; import Gear from '@material-ui/icons/Settings'; import NewReleases from '@material-ui/icons/NewReleases'; import React from 'react'; -export default class Navbar extends React.Component { +export default class Navbar extends React.PureComponent { render() { + let refreshHTML =
window.location.reload()} />
+ const refresh = localStorage.getItem('refresh'); + if (refresh === 'false') refreshHTML = ''; + return (
-
- window.location.reload()} /> -
-
+ {refreshHTML} +
diff --git a/src/components/Quote.jsx b/src/components/Quote.jsx index c44ecc6c..3b3b0b93 100644 --- a/src/components/Quote.jsx +++ b/src/components/Quote.jsx @@ -1,10 +1,11 @@ -//* Imports import React from 'react'; import Quotes from '@muetab/quotes'; import copy from 'copy-text-to-clipboard'; -import FileCopy from '@material-ui/icons/AttachFile'; +import FileCopy from '@material-ui/icons/FilterNone'; +import { toast } from 'react-toastify'; +import * as Constants from '../modules/constants'; -export default class Quote extends React.Component { +export default class Quote extends React.PureComponent { constructor(...args) { super(...args); this.state = { @@ -22,11 +23,10 @@ export default class Quote extends React.Component { } async getQuote() { - const enabled = localStorage.getItem('offlineMode'); - if (enabled === 'true') return this.doOffline(); + if (localStorage.getItem('offlineMode') === 'true') return this.doOffline(); try { // First we try and get a quote from the API... - let data = await fetch('https://api.muetab.xyz/getQuote'); + let data = await fetch(Constants.API_URL +'/getQuote'); data = await data.json(); if (data.statusCode === 429) this.doOffline(); // If we hit the ratelimit, we fallback to local quotes this.setState({ @@ -40,25 +40,23 @@ export default class Quote extends React.Component { copyQuote() { copy(`${this.state.quote} - ${this.state.author}`); - const toast = document.getElementById('toast'); - toast.className = 'show'; - setTimeout(() => { toast.className = toast.className.replace('show', ''); }, 3000); + toast('Quote copied!'); } componentDidMount() { - const enabled = localStorage.getItem('quote'); - if (enabled === 'false') return; + if (localStorage.getItem('quote') === 'false') return; this.getQuote(); } render() { let copy = this.copyQuote() }>; - const enabled = localStorage.getItem('copyButton'); - if (enabled === 'false') copy = ''; + if (localStorage.getItem('copyButton') === 'false') copy = ''; - return [ -

{`${this.state.quote}`}

, -

{this.state.author} {copy}

, - ]; + return ( +
+

{`${this.state.quote}`}

+

{this.state.author} {copy}

+
+ ) } } diff --git a/src/components/Search.jsx b/src/components/Search.jsx index 7f1ea2af..597c1d25 100644 --- a/src/components/Search.jsx +++ b/src/components/Search.jsx @@ -1,28 +1,23 @@ -//* Imports import React from 'react'; -export default class Search extends React.Component { +export default class Search extends React.PureComponent { render() { - const enabled = localStorage.getItem('searchBar'); - if (enabled === 'false') return (
); + if (localStorage.getItem('searchBar') === 'false') return
; - const searchEngine = localStorage.getItem('searchEngine'); let url; - switch (searchEngine) { + switch (localStorage.getItem('searchEngine')) { case 'duckduckgo': url = 'https://duckduckgo.com'; break; case 'google': url = 'https://google.com/search'; break; case 'bing': url = 'https://bing.com/search'; break; default: url = 'https://duckduckgo.com'; break; } - return ( -