refactor: new background component

This commit is contained in:
David Ralph
2021-03-20 12:05:14 +00:00
parent 70756befa2
commit 0c71f0ebef
4 changed files with 177 additions and 165 deletions

View File

@@ -2,7 +2,6 @@ import React from 'react';
import Background from './components/widgets/background/Background';
import Widgets from './components/widgets/Widgets';
import PhotoInformation from './components/widgets/background/PhotoInformation';
import Navbar from './components/widgets/navbar/Navbar';
import SettingsFunctions from './modules/helpers/settings';
@@ -23,7 +22,9 @@ export default class App extends React.PureComponent {
mainModal: false,
updateModal: false,
welcomeModal: false,
feedbackModal: false
feedbackModal: false,
toastDisplayTime: localStorage.getItem('toastDisplayTime') || 2500,
overlayClassList: (localStorage.getItem('animations') === 'true') ? 'Overlay modal-animation' : 'Overlay'
};
}
@@ -40,6 +41,20 @@ export default class App extends React.PureComponent {
SettingsFunctions.loadSettings();
// dark theme support for modals and info card
let modalClassList = 'Modal';
let tooltipClassList = 'infoCard';
if ((localStorage.getItem('brightnessTime') && new Date().getHours() > 18) || localStorage.getItem('darkTheme') === 'true') {
modalClassList += ' dark';
tooltipClassList += ' dark';
}
this.setState({
modalClassList: modalClassList,
tooltipClassList: tooltipClassList
});
// These lines of code prevent double clicking the page or pressing CTRL + A from highlighting the page
document.addEventListener('mousedown', (event) => {
if (event.detail > 1) {
@@ -69,34 +84,21 @@ export default class App extends React.PureComponent {
}
render() {
// dark theme support for modals and info card
let modalClassList = 'Modal';
let tooltipClassList = 'infoCard';
if ((localStorage.getItem('brightnessTime') && new Date().getHours() > 18) || localStorage.getItem('darkTheme') === 'true') {
modalClassList += ' dark';
tooltipClassList += ' dark';
}
const overlayClassList = (localStorage.getItem('animations') === 'true') ? 'Overlay modal-animation' : 'Overlay';
const toastDisplayTime = localStorage.getItem('toastDisplayTime') || 2500;
return (
<React.Fragment>
<Background/>
<ToastContainer position='bottom-right' autoClose={toastDisplayTime} newestOnTop={true} closeOnClick pauseOnFocusLoss/>
<Background photoInformationClass={this.state.tooltipClassList}/>
<ToastContainer position='bottom-right' autoClose={this.state.toastDisplayTime} newestOnTop={true} closeOnClick pauseOnFocusLoss/>
<div id='center'>
<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}>
<Modal closeTimeoutMS={300} id={'modal'} onRequestClose={() => this.setState({ mainModal: false })} isOpen={this.state.mainModal} className={this.state.modalClassList} overlayClassName={this.state.overlayClassList} ariaHideApp={false}>
<Main modalClose={() => this.setState({ mainModal: false })} />
</Modal>
<Modal onRequestClose={() => this.setState({ updateModal: false })} isOpen={this.state.updateModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
<Modal onRequestClose={() => this.setState({ updateModal: false })} isOpen={this.state.updateModal} className={this.state.modalClassList} overlayClassName={this.state.overlayClassList} ariaHideApp={false}>
<Update modalClose={() => this.setState({ updateModal: false })} />
</Modal>
<Modal onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
<Modal onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className={this.state.modalClassList} overlayClassName={this.state.overlayClassList} ariaHideApp={false}>
<Welcome modalClose={() => this.closeWelcome()} />
</Modal>
{/* <Modal onRequestClose={() => this.setState({ feedbackModal: false })} isOpen={this.state.feedbackModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>

View File

@@ -1,10 +1,29 @@
import React from 'react';
import PhotoInformation from './PhotoInformation';
import * as Constants from '../../../modules/constants';
import './scss/index.scss';
export default class Background extends React.PureComponent {
constructor(...args) {
super(...args);
this.state = {
style: '',
url: '',
video: false,
photoInfo: {
hidden: false,
credit: '',
location: 'N/A',
camera: 'N/A',
resolution: 'N/A'
}
};
this.language = window.language.widgets.background;
}
gradientStyleBuilder(gradientSettings) {
const { type, angle, gradient } = gradientSettings;
let style = `background: ${gradient[0].colour};`;
@@ -13,55 +32,32 @@ export default class Background extends React.PureComponent {
const stepStyles = gradient.map(g => ` ${g.colour} ${g.stop}%`).join();
style += ` background: ${type}-gradient(${(type === 'linear' ? (`${angle}deg,`) : '')}${stepStyles})`;
}
return style;
this.setState({
style: style
});
}
// Sets the attributes of the background image
setBackground(url, colour, credit) {
let gradientSettings = '';
try {
gradientSettings = JSON.parse(colour);
} catch (e) {
const hexColorRegex = /#[0-9a-fA-F]{6}/s;
if (hexColorRegex.exec(colour)) {
// Colour use to be simply a hex colour or a NULL value before it was a JSON object. This automatically upgrades the hex colour value to the new standard. (NULL would not trigger an exception)
gradientSettings = { "type": "linear", "angle": "180", "gradient": [{ "colour": colour, "stop": 0 }] };
localStorage.setItem('customBackgroundColour', JSON.stringify(gradientSettings));
}
}
const background = typeof gradientSettings === 'object' && gradientSettings !== null ? this.gradientStyleBuilder(gradientSettings) : `background-image: url(${url})`;
setBackground() {
// Brightness
let brightness = localStorage.getItem('brightness');
if (localStorage.getItem('brightnessTime') && new Date().getHours() > 18) {
brightness = 75;
}
document.querySelector('#backgroundImage').setAttribute(
'style',
`${background}; -webkit-filter: blur(${localStorage.getItem('blur')}px) brightness(${brightness}%);`
);
// Hide the credit
if (credit === 'false' && document.querySelector('#credits')) {
document.querySelector('#credits').style.display = 'none';
if (this.state.url !== '') {
document.querySelector('#backgroundImage').setAttribute(
'style',
`background-image: url(${this.state.url}); -webkit-filter: blur(${localStorage.getItem('blur')}px) brightness(${brightness}%);`
);
} else {
document.querySelector('#backgroundImage').setAttribute(
'style',
`${this.state.style}; -webkit-filter: blur(${localStorage.getItem('blur')}px) brightness(${brightness}%);`
);
}
}
setCredit(photographer, unsplash, url) {
let credit = photographer;
if (unsplash){
credit = `<a href='${url}' class='creditlink'>${photographer}</a> on <a href='https://unsplash.com/?utm_source=mue&utm_medium=referral' class='creditlink'>Unsplash</a>`;
}
document.querySelector('#photographer').insertAdjacentHTML('beforeend', ` ${credit}`); // Append credit
document.getElementById('credit').textContent = credit;
document.getElementById('photographerCard').textContent = credit;
}
// Handles setting the background if the user is offline
doOffline() {
offlineBackground() {
const offlineImages = require('./offline_images.json');
// Get all photographers from the keys in offlineImages.json
@@ -74,126 +70,139 @@ export default class Background extends React.PureComponent {
Math.floor(Math.random() * offlineImages[photographer].photo.length)
];
const url = `./offline-images/${randomImage}.jpg`;
this.setBackground(url);
this.setCredit(photographer);
// Hide the location icon
//document.querySelector('#backgroundCredits').style.display = 'none';
this.setState({
url: `./offline-images/${randomImage}.jpg`,
photoInfo: {
hidden: true,
credit: photographer
}
});
}
async determineMode() {
const offlineMode = localStorage.getItem('offlineMode');
const photoPack = JSON.parse(localStorage.getItem('photo_packs'));
const customBackgroundColour = localStorage.getItem('customBackgroundColour');
const customBackground = localStorage.getItem('customBackground');
// Main background getting function
async getBackground() {
if (localStorage.getItem('offlineMode') === 'true') {
return this.offlineBackground();
}
// favourite button
const favourited = JSON.parse(localStorage.getItem('favourite'));
if (favourited) {
if (offlineMode === 'true') {
return this.doOffline();
}
return this.setState({
url: favourited.url,
photoInfo: {
credit: favourited.credit,
location: favourited.location
}
});
}
this.setBackground(favourited.url, null, 'true');
this.setCredit(favourited.credit);
document.getElementById('location').textContent = favourited.location;
} else if (photoPack) {
if (offlineMode === 'true') {
return this.doOffline();
}
const randomPhoto = photoPack[Math.floor(Math.random() * photoPack.length)];
this.setBackground(randomPhoto.url.default, null, randomPhoto.photographer);
this.setCredit(randomPhoto.photographer);
document.getElementById('location').textContent = randomPhoto.location;
} else if (customBackgroundColour !== 'Disabled' && customBackgroundColour !== '') {
this.setBackground(null, customBackgroundColour, 'false');
} else if (customBackground !== '') {
if (customBackground.includes('.mp4') || customBackground.includes('.webm') || customBackground.includes('.ogg')) {
document.getElementById('backgroundImage').innerHTML = `
<video autoplay muted loop id='backgroundVideo'>
<source src='${customBackground}'/>
</video>`;
} else {
// Local
this.setBackground(customBackground, null, 'false');
}
// Online
} else {
if (offlineMode === 'true') {
return this.doOffline();
}
// First we try and get an image from the API...
// background colour
const customBackgroundColour = localStorage.getItem('customBackgroundColour');
if (customBackgroundColour !== 'Disabled' && customBackgroundColour !== '') {
let gradientSettings = '';
try {
let requestURL;
switch (localStorage.getItem('backgroundAPI')) {
case 'unsplash':
requestURL = `${Constants.UNSPLASH_URL}/getImage`;
break;
// Defaults to Mue
default:
requestURL = `${Constants.API_URL}/getImage?category=Outdoors`;
break;
}
// Fetch JSON data from requestURL
const data = await (await fetch(requestURL)).json();
// If we hit the rate limit, fallback to local images
if (data.statusCode === 429) {
this.doOffline();
// Otherwise, set the background and credit from remote data
} else {
this.setBackground(data.file);
if (localStorage.getItem('backgroundAPI') === 'unsplash') {
return this.setCredit(data.photographer, 'unsplash', data.photographer_page);
}
this.setCredit(data.photographer);
document.getElementById('camera').textContent = data.camera || 'N/A';
document.getElementById('resolution').textContent = data.resolution || 'N/A';
}
if (data.location.replace(/[null]+/g, '') === ' ') {
return document.querySelector('#backgroundCredits').style.display = 'none';
}
// Set the location tooltip
document.getElementById('location').innerText = `${data.location.replace('null', '')}`;
// ..and if that fails we load one locally
gradientSettings = JSON.parse(colour);
} catch (e) {
this.doOffline();
const hexColorRegex = /#[0-9a-fA-F]{6}/s;
if (hexColorRegex.exec(colour)) {
// Colour use to be simply a hex colour or a NULL value before it was a JSON object. This automatically upgrades the hex colour value to the new standard. (NULL would not trigger an exception)
gradientSettings = { "type": "linear", "angle": "180", "gradient": [{ "colour": colour, "stop": 0 }] };
localStorage.setItem('customBackgroundColour', JSON.stringify(gradientSettings));
}
}
if (typeof gradientSettings === 'object' && gradientSettings !== null) {
return this.gradientStyleBuilder(gradientSettings);
}
}
// custom user background
const customBackground = localStorage.getItem('customBackground');
if (customBackground !== '') {
// video background
if (customBackground.includes('.mp4') || customBackground.includes('.webm') || customBackground.includes('.ogg')) {
return this.setState({
url: customBackground,
video: true,
photoInfo: {
hidden: true
}
});
// normal background
} else {
return this.setState({
url: customBackground,
photoInfo: {
hidden: true
}
});
}
}
// photo pack
const photoPack = JSON.parse(localStorage.getItem('photo_packs'));
if (photoPack) {
const randomPhoto = photoPack[Math.floor(Math.random() * photoPack.length)];
return this.setState({
url: randomPhoto.url.default,
photoInfo: {
credit: randomPhoto.photographer
}
});
}
// API background
const backgroundAPI = localStorage.getItem('backgroundAPI');
let requestURL, data;
switch (backgroundAPI) {
case 'unsplash':
requestURL = `${Constants.UNSPLASH_URL}/getImage`;
break;
// Defaults to Mue
default:
requestURL = `${Constants.API_URL}/getImage?category=Outdoors`;
break;
}
try {
data = await (await fetch(requestURL)).json();
} catch (e) {
// if requesting to the API fails, we get an offline image
return this.offlineBackground();
}
this.setState({
url: data.file,
photoInfo: {
credit: (backgroundAPI !== 'unsplash') ? data.photographer : data.photographer + ' on Unsplash',
location: (data.location.replace(/[null]+/g, '') !== ' ') ? data.location : 'N/A',
camera: data.camera || 'N/A',
resolution: data.resolution || 'N/A'
}
});
}
componentDidMount() {
// Hide the credit
if (localStorage.getItem('background') === 'false') {
return document.querySelector('.photoInformation').style.display = 'none';
return;
}
const backgroundColour = localStorage.getItem('customBackgroundColour');
if (backgroundColour !== 'Disabled' && backgroundColour !== '') {
document.querySelector('.photoInformation').style.display = 'none';
}
this.getBackground();
}
if (localStorage.getItem('animations') === 'true') {
document.querySelector('#backgroundImage').classList.add('fade-in');
}
this.determineMode();
// only set once we've got the info
componentDidUpdate() {
this.setBackground();
}
render() {
return <div id='backgroundImage'/>;
return (
<React.Fragment>
<div id='backgroundImage'/>
<PhotoInformation className={this.props.photoInformationClass} info={this.state.photoInfo}/>
</React.Fragment>
);
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import Info from '@material-ui/icons/Info';
import Location from '@material-ui/icons/LocationOn';
import Camera from '@material-ui/icons/PhotoCamera';
@@ -10,22 +11,21 @@ export default function PhotoInformation(props) {
return (
<div className='photoInformation'>
<h1 id='photographer'>{language.credit}</h1>
<h1>{language.credit} {props.info.credit}</h1>
<Info className='photoInformationHover'/>
<div className={props.className || 'infoCard'}>
<Info className='infoIcon'/>
<h1>{language.information}</h1>
<hr/>
<Location/>
<span id='location'/>
<span>{props.info.location}</span>
<Camera/>
<span id='camera'/>
<span>{props.info.camera}</span>
<Resolution/>
<span id='resolution'/>
<span>{props.info.resolution}</span>
<Photographer/>
<span id='photographerCard'/>
<span>{props.info.credit.split(' on Unsplash')[0]}</span>
</div>
<span id='credit' style={{ 'display': 'none' }}></span>
</div>
);
}

View File

@@ -4,6 +4,7 @@
left: 1rem;
font-size: calc(10px + 0.1vmin) !important;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 99;
svg {
float: left;