mirror of
https://github.com/mue/mue.git
synced 2026-07-16 13:34:03 +02:00
refactor(background): Converted to functional component + fixed background transition quirk
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import variables from 'config/variables';
|
||||
import PhotoInformation from './components/PhotoInformation';
|
||||
import EventBus from 'utils/eventbus';
|
||||
@@ -13,85 +13,26 @@ import BackgroundImage from './components/BackgroundImage';
|
||||
import BackgroundVideo from './components/BackgroundVideo';
|
||||
import './scss/index.scss';
|
||||
|
||||
export default class Background extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
blob: null,
|
||||
style: '',
|
||||
url: '',
|
||||
currentAPI: '',
|
||||
firstTime: false,
|
||||
photoInfo: {
|
||||
hidden: false,
|
||||
offline: false,
|
||||
photographerURL: '',
|
||||
photoURL: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
const Background = () => {
|
||||
const [state, setState] = useState({
|
||||
blob: null,
|
||||
style: '',
|
||||
url: '',
|
||||
currentAPI: '',
|
||||
firstTime: false,
|
||||
photoInfo: {
|
||||
hidden: false,
|
||||
offline: false,
|
||||
photographerURL: '',
|
||||
photoURL: '',
|
||||
},
|
||||
type: '',
|
||||
video: false,
|
||||
});
|
||||
|
||||
async componentDidMount() {
|
||||
const element = document.getElementById('backgroundImage');
|
||||
EventBus.on('refresh', (data) => this.handleRefreshEvent(data));
|
||||
EventBus.on('backgroundeffect', () => this.handleBackgroundEffectEvent());
|
||||
if (localStorage.getItem('welcomeTab')) {
|
||||
this.setState(JSON.parse(localStorage.getItem('welcomeImage')));
|
||||
return;
|
||||
}
|
||||
this.getBackground();
|
||||
}
|
||||
const blobRef = useRef(null);
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.video !== true) {
|
||||
this.setBackground();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
EventBus.off('backgroundeffect');
|
||||
}
|
||||
|
||||
async setBackground() {
|
||||
if (this.blob) {
|
||||
URL.revokeObjectURL(this.blob);
|
||||
}
|
||||
const backgroundImage = document.getElementById('backgroundImage');
|
||||
if (this.state.url !== '') {
|
||||
let url = this.state.url;
|
||||
const photoInformation = document.querySelector('.photoInformation');
|
||||
if (localStorage.getItem('bgtransition') === 'false') {
|
||||
if (photoInformation) {
|
||||
photoInformation.style.display = 'flex';
|
||||
}
|
||||
backgroundImage.style.background = `url(${url})`;
|
||||
return;
|
||||
}
|
||||
backgroundImage.style.background = null;
|
||||
if (this.state.photoInfo.blur_hash) {
|
||||
backgroundImage.style.backgroundColor = this.state.photoInfo.colour;
|
||||
backgroundImage.classList.add('fade-in');
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imageData = ctx.createImageData(32, 32);
|
||||
imageData.data.set(decodeBlurHash(this.state.photoInfo.blur_hash, 32, 32));
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
backgroundImage.style.backgroundImage = `url(${canvas.toDataURL()})`;
|
||||
}
|
||||
this.blob = URL.createObjectURL(await (await fetch(url)).blob());
|
||||
backgroundImage.classList.add('backgroundTransform');
|
||||
backgroundImage.style.backgroundImage = `url(${this.blob})`;
|
||||
Stats.postEvent('feature', 'background-image', 'shown');
|
||||
} else {
|
||||
backgroundImage.setAttribute('style', this.state.style);
|
||||
Stats.postEvent('background', 'colour', 'set');
|
||||
}
|
||||
}
|
||||
|
||||
async getAPIImageData(currentPun) {
|
||||
const getAPIImageData = useCallback(async (currentPun) => {
|
||||
let apiCategories;
|
||||
try {
|
||||
apiCategories = JSON.parse(localStorage.getItem('apiCategories'));
|
||||
@@ -107,7 +48,8 @@ export default class Background extends PureComponent {
|
||||
if (currentPun) {
|
||||
backgroundExclude.push(currentPun);
|
||||
}
|
||||
let requestURL, data;
|
||||
|
||||
let requestURL;
|
||||
switch (backgroundAPI) {
|
||||
case 'unsplash':
|
||||
case 'pexels':
|
||||
@@ -121,58 +63,152 @@ export default class Background extends PureComponent {
|
||||
requestURL = `${variables.constants.API_URL}/images/random?categories=${apiCategories || ''}&quality=${apiQuality}&excludes=${backgroundExclude}`;
|
||||
break;
|
||||
}
|
||||
|
||||
const accept = `application/json, ${supportsAVIF() ? 'image/avif' : 'image/webp'}`;
|
||||
try {
|
||||
data = await (await fetch(requestURL, { headers: { accept } })).json();
|
||||
const response = await fetch(requestURL, { headers: { accept } });
|
||||
const data = await response.json();
|
||||
let photoURL, photographerURL;
|
||||
if (backgroundAPI === 'unsplash') {
|
||||
photoURL = data.photo_page;
|
||||
photographerURL = data.photographer_page;
|
||||
}
|
||||
return {
|
||||
url: data.file,
|
||||
type: 'api',
|
||||
currentAPI: backgroundAPI,
|
||||
photoInfo: {
|
||||
hidden: false,
|
||||
category: data.category,
|
||||
credit: data.photographer,
|
||||
location: data.location.name,
|
||||
camera: data.camera,
|
||||
url: data.file,
|
||||
photographerURL,
|
||||
photoURL,
|
||||
latitude: data.location.latitude || null,
|
||||
longitude: data.location.longitude || null,
|
||||
views: data.views || null,
|
||||
downloads: data.downloads || null,
|
||||
likes: data.likes || null,
|
||||
description: data.description || null,
|
||||
colour: data.colour,
|
||||
blur_hash: data.blur_hash,
|
||||
pun: data.pun || null,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
this.setState(getOfflineImage('api'));
|
||||
setState(getOfflineImage('api'));
|
||||
Stats.postEvent('background', 'image', 'offline');
|
||||
return null;
|
||||
}
|
||||
let photoURL, photographerURL;
|
||||
if (backgroundAPI === 'unsplash') {
|
||||
photoURL = data.photo_page;
|
||||
photographerURL = data.photographer_page;
|
||||
}
|
||||
return {
|
||||
url: data.file,
|
||||
type: 'api',
|
||||
currentAPI: backgroundAPI,
|
||||
photoInfo: {
|
||||
hidden: false,
|
||||
category: data.category,
|
||||
credit: data.photographer,
|
||||
location: data.location.name,
|
||||
camera: data.camera,
|
||||
url: data.file,
|
||||
photographerURL,
|
||||
photoURL,
|
||||
latitude: data.location.latitude || null,
|
||||
longitude: data.location.longitude || null,
|
||||
views: data.views || null,
|
||||
downloads: data.downloads || null,
|
||||
likes: data.likes || null,
|
||||
description: data.description || null,
|
||||
colour: data.colour,
|
||||
blur_hash: data.blur_hash,
|
||||
pun: data.pun || null,
|
||||
},
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
async getBackground() {
|
||||
const setBackground = useCallback(async () => {
|
||||
// Clean up previous blob URL
|
||||
if (blobRef.current) {
|
||||
URL.revokeObjectURL(blobRef.current);
|
||||
blobRef.current = null;
|
||||
}
|
||||
|
||||
const backgroundImage = document.getElementById('backgroundImage');
|
||||
const blurhashOverlay = document.getElementById('blurhashOverlay');
|
||||
const backgroundImageActual = document.getElementById('backgroundImageActual');
|
||||
const photoInformation = document.querySelector('.photoInformation');
|
||||
|
||||
// Reset elements to default state
|
||||
backgroundImageActual.style.opacity = 0;
|
||||
blurhashOverlay.style.backgroundImage = '';
|
||||
blurhashOverlay.style.backgroundColor = '';
|
||||
|
||||
// Handle different background types
|
||||
try {
|
||||
switch (state.type) {
|
||||
case 'api':
|
||||
// Handle API image with blurhash
|
||||
if (state.url) {
|
||||
if (localStorage.getItem('bgtransition') === 'false') {
|
||||
if (photoInformation) {
|
||||
photoInformation.style.display = 'flex';
|
||||
}
|
||||
backgroundImage.style.background = `url(${state.url})`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set blurhash overlay if available
|
||||
if (state.photoInfo.blur_hash) {
|
||||
blurhashOverlay.style.backgroundColor = state.photoInfo.colour;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imageData = ctx.createImageData(32, 32);
|
||||
imageData.data.set(decodeBlurHash(state.photoInfo.blur_hash, 32, 32));
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
blurhashOverlay.style.backgroundImage = `url(${canvas.toDataURL()})`;
|
||||
}
|
||||
|
||||
// Load actual image
|
||||
const newBlob = URL.createObjectURL(await (await fetch(state.url)).blob());
|
||||
blobRef.current = newBlob;
|
||||
backgroundImageActual.src = newBlob;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'colour':
|
||||
case 'random_colour':
|
||||
case 'random_gradient':
|
||||
backgroundImage.style.background = state.style;
|
||||
backgroundImageActual.src = '';
|
||||
Stats.postEvent('background', 'colour', 'set');
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
case 'photo_pack':
|
||||
if (state.url) {
|
||||
backgroundImage.style.background = `url(${state.url})`;
|
||||
if (photoInformation && !state.photoInfo.hidden) {
|
||||
photoInformation.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
Stats.postEvent('background', state.type, 'set');
|
||||
break;
|
||||
|
||||
default:
|
||||
// Fallback to solid color
|
||||
backgroundImage.style.background = state.style || 'rgb(0,0,0)';
|
||||
Stats.postEvent('background', 'colour', 'set');
|
||||
}
|
||||
|
||||
// Set up image load handler for all image types
|
||||
if (backgroundImageActual.src) {
|
||||
backgroundImageActual.onload = () => {
|
||||
backgroundImageActual.style.opacity = 1;
|
||||
blurhashOverlay.style.opacity = 0;
|
||||
Stats.postEvent('feature', 'background-image', 'shown');
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error setting background:', error);
|
||||
// Fallback to solid black background
|
||||
backgroundImage.style.background = 'rgb(0,0,0)';
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
const getBackground = useCallback(async () => {
|
||||
let offline = localStorage.getItem('offlineMode') === 'true';
|
||||
if (localStorage.getItem('showWelcome') !== 'false') {
|
||||
offline = true;
|
||||
}
|
||||
|
||||
const setFavourited = ({ type, url, credit, location, camera, pun, offline }) => {
|
||||
if (type === 'random_colour' || type === 'random_gradient') {
|
||||
this.setState({
|
||||
setState({
|
||||
type: 'colour',
|
||||
style: `background:${url}`,
|
||||
style: url,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
setState({
|
||||
url,
|
||||
photoInfo: {
|
||||
credit,
|
||||
@@ -186,27 +222,29 @@ export default class Background extends PureComponent {
|
||||
}
|
||||
Stats.postEvent('background', 'favourite', 'set');
|
||||
};
|
||||
|
||||
const favourited = JSON.parse(localStorage.getItem('favourite'));
|
||||
if (favourited) {
|
||||
setFavourited(favourited);
|
||||
return;
|
||||
}
|
||||
|
||||
const type = localStorage.getItem('backgroundType') || defaults.backgroundType;
|
||||
switch (type) {
|
||||
case 'api':
|
||||
if (offline) {
|
||||
this.setState(getOfflineImage('api'));
|
||||
setState(getOfflineImage('api'));
|
||||
Stats.postEvent('background', 'image', 'offline');
|
||||
return;
|
||||
}
|
||||
let data = JSON.parse(localStorage.getItem('nextImage')) || (await this.getAPIImageData());
|
||||
let data = JSON.parse(localStorage.getItem('nextImage')) || (await getAPIImageData());
|
||||
localStorage.setItem('nextImage', null);
|
||||
if (data) {
|
||||
this.setState(data);
|
||||
setState(data);
|
||||
localStorage.setItem('currentBackground', JSON.stringify(data));
|
||||
localStorage.setItem(
|
||||
'nextImage',
|
||||
JSON.stringify(await this.getAPIImageData(data.photoInfo.pun)),
|
||||
JSON.stringify(await getAPIImageData(data.photoInfo.pun)),
|
||||
);
|
||||
Stats.postEvent('background', 'image', 'api');
|
||||
}
|
||||
@@ -222,15 +260,19 @@ export default class Background extends PureComponent {
|
||||
customBackgroundColour = 'rgb(0,0,0)';
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
setState({
|
||||
type: 'colour',
|
||||
style: `background: ${customBackgroundColour || 'rgb(0,0,0)'}`,
|
||||
style: customBackgroundColour || 'rgb(0,0,0)',
|
||||
});
|
||||
Stats.postEvent('background', 'colour', 'custom');
|
||||
break;
|
||||
case 'random_colour':
|
||||
case 'random_gradient':
|
||||
this.setState(randomColourStyleBuilder(type));
|
||||
const randomStyle = randomColourStyleBuilder(type);
|
||||
setState({
|
||||
type: 'colour',
|
||||
style: randomStyle,
|
||||
});
|
||||
Stats.postEvent('background', 'colour', 'random');
|
||||
break;
|
||||
case 'custom':
|
||||
@@ -246,7 +288,7 @@ export default class Background extends PureComponent {
|
||||
}
|
||||
customBackground = customBackground[Math.floor(Math.random() * customBackground.length)];
|
||||
if (offline && !customBackground.startsWith('data:')) {
|
||||
this.setState(getOfflineImage('custom'));
|
||||
setState(getOfflineImage('custom'));
|
||||
Stats.postEvent('background', 'image', 'offline');
|
||||
return;
|
||||
}
|
||||
@@ -263,14 +305,14 @@ export default class Background extends PureComponent {
|
||||
hidden: true,
|
||||
},
|
||||
};
|
||||
this.setState(object);
|
||||
setState(object);
|
||||
localStorage.setItem('currentBackground', JSON.stringify(object));
|
||||
Stats.postEvent('background', 'image', 'custom');
|
||||
}
|
||||
break;
|
||||
case 'photo_pack':
|
||||
if (offline) {
|
||||
this.setState(getOfflineImage('photo'));
|
||||
setState(getOfflineImage('photo'));
|
||||
Stats.postEvent('background', 'image', 'offline');
|
||||
return;
|
||||
}
|
||||
@@ -283,12 +325,12 @@ export default class Background extends PureComponent {
|
||||
}
|
||||
});
|
||||
if (photoPack.length === 0) {
|
||||
this.setState(getOfflineImage('photo'));
|
||||
setState(getOfflineImage('photo'));
|
||||
Stats.postEvent('background', 'image', 'offline');
|
||||
return;
|
||||
}
|
||||
const photo = photoPack[Math.floor(Math.random() * photoPack.length)];
|
||||
this.setState({
|
||||
setState({
|
||||
url: photo.url.default,
|
||||
type: 'photo_pack',
|
||||
video: videoCheck(photo.url.default),
|
||||
@@ -301,111 +343,132 @@ export default class Background extends PureComponent {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [getAPIImageData]);
|
||||
|
||||
handleRefreshEvent(data) {
|
||||
const element = document.getElementById('backgroundImage');
|
||||
const refresh = () => {
|
||||
element.classList.remove('fade-in');
|
||||
this.setState({
|
||||
url: '',
|
||||
style: '',
|
||||
type: '',
|
||||
video: false,
|
||||
photoInfo: {
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
this.getBackground();
|
||||
};
|
||||
if (data === 'welcomeLanguage') {
|
||||
localStorage.setItem('welcomeImage', JSON.stringify(this.state));
|
||||
}
|
||||
if (data === 'background') {
|
||||
if (localStorage.getItem('background') === 'false') {
|
||||
if (this.state.photoInfo.hidden === false) {
|
||||
document.querySelector('.photoInformation').style.display = 'none';
|
||||
}
|
||||
if (this.state.video === true) {
|
||||
document.getElementById('backgroundVideo').style.display = 'none';
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
return;
|
||||
const handleRefreshEvent = useCallback(
|
||||
(data) => {
|
||||
const element = document.getElementById('backgroundImage');
|
||||
const refresh = () => {
|
||||
element.classList.remove('fade-in');
|
||||
setState({
|
||||
url: '',
|
||||
style: '',
|
||||
type: '',
|
||||
video: false,
|
||||
photoInfo: {
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
getBackground();
|
||||
};
|
||||
|
||||
if (data === 'welcomeLanguage') {
|
||||
localStorage.setItem('welcomeImage', JSON.stringify(state));
|
||||
}
|
||||
if (this.state.video === true) {
|
||||
document.getElementById('backgroundVideo').style.display = 'block';
|
||||
} else {
|
||||
if (this.state.photoInfo.hidden === false) {
|
||||
try {
|
||||
document.querySelector('.photoInformation').style.display = 'flex';
|
||||
} catch (e) {
|
||||
// Disregard exception
|
||||
if (data === 'background') {
|
||||
if (localStorage.getItem('background') === 'false') {
|
||||
if (state.photoInfo.hidden === false) {
|
||||
document.querySelector('.photoInformation').style.display = 'none';
|
||||
}
|
||||
if (state.video === true) {
|
||||
document.getElementById('backgroundVideo').style.display = 'none';
|
||||
} else {
|
||||
element.style.display = 'none';
|
||||
}
|
||||
return;
|
||||
}
|
||||
element.style.display = 'block';
|
||||
}
|
||||
const backgroundType = localStorage.getItem('backgroundType') || defaults.backgroundType;
|
||||
if (this.state.photoInfo.offline !== true) {
|
||||
if (
|
||||
backgroundType !== this.state.type ||
|
||||
(this.state.type === 'api' &&
|
||||
localStorage.getItem('backgroundAPI') !== this.state.currentAPI) ||
|
||||
(this.state.type === 'custom' &&
|
||||
localStorage.getItem('customBackground') !== this.state.url) ||
|
||||
JSON.parse(localStorage.getItem('backgroundExclude')).includes(this.state.photoInfo.pun)
|
||||
) {
|
||||
if (state.video === true) {
|
||||
document.getElementById('backgroundVideo').style.display = 'block';
|
||||
} else {
|
||||
if (state.photoInfo.hidden === false) {
|
||||
try {
|
||||
document.querySelector('.photoInformation').style.display = 'flex';
|
||||
} catch (e) {
|
||||
// Disregard exception
|
||||
}
|
||||
}
|
||||
element.style.display = 'block';
|
||||
}
|
||||
const backgroundType = localStorage.getItem('backgroundType') || defaults.backgroundType;
|
||||
if (state.photoInfo.offline !== true) {
|
||||
if (
|
||||
backgroundType !== state.type ||
|
||||
(state.type === 'api' && localStorage.getItem('backgroundAPI') !== state.currentAPI) ||
|
||||
(state.type === 'custom' && localStorage.getItem('customBackground') !== state.url) ||
|
||||
JSON.parse(localStorage.getItem('backgroundExclude')).includes(state.photoInfo.pun)
|
||||
) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
} else if (backgroundType !== state.type) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
} else if (backgroundType !== this.state.type) {
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (
|
||||
data === 'marketplacebackgrounduninstall' ||
|
||||
data === 'backgroundwelcome' ||
|
||||
data === 'backgroundrefresh'
|
||||
) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
if (
|
||||
data === 'marketplacebackgrounduninstall' ||
|
||||
data === 'backgroundwelcome' ||
|
||||
data === 'backgroundrefresh'
|
||||
) {
|
||||
refresh();
|
||||
}
|
||||
},
|
||||
[state, getBackground],
|
||||
);
|
||||
|
||||
handleBackgroundEffectEvent() {
|
||||
const handleBackgroundEffectEvent = useCallback(() => {
|
||||
const element = document.getElementById('backgroundImage');
|
||||
const backgroundFilterSetting =
|
||||
localStorage.getItem('backgroundFilter') || defaults.backgroundFilter;
|
||||
const backgroundFilter = backgroundFilterSetting && backgroundFilterSetting !== 'none';
|
||||
const filterValue = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem(
|
||||
'brightness',
|
||||
)}%) ${
|
||||
const filterValue = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${
|
||||
backgroundFilter
|
||||
? backgroundFilterSetting + '(' + localStorage.getItem('backgroundFilterAmount') + '%)'
|
||||
: ''
|
||||
}`;
|
||||
if (this.state.video === true) {
|
||||
if (state.video === true) {
|
||||
document.getElementById('backgroundVideo').style.filter = filterValue;
|
||||
} else {
|
||||
element.style.filter = filterValue;
|
||||
}
|
||||
}, [state.video]);
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem('welcomeTab')) {
|
||||
setState(JSON.parse(localStorage.getItem('welcomeImage')));
|
||||
return;
|
||||
}
|
||||
getBackground();
|
||||
}, [getBackground]);
|
||||
|
||||
useEffect(() => {
|
||||
EventBus.on('refresh', handleRefreshEvent);
|
||||
EventBus.on('backgroundeffect', handleBackgroundEffectEvent);
|
||||
|
||||
return () => {
|
||||
EventBus.off('refresh', handleRefreshEvent);
|
||||
EventBus.off('backgroundeffect', handleBackgroundEffectEvent);
|
||||
};
|
||||
}, [handleRefreshEvent, handleBackgroundEffectEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.video !== true) {
|
||||
setBackground();
|
||||
}
|
||||
}, [state, setBackground]);
|
||||
|
||||
if (state.video === true) {
|
||||
return <BackgroundVideo url={state.url} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.video === true) {
|
||||
return <BackgroundVideo url={this.state.url} />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<BackgroundImage />
|
||||
{this.state.photoInfo.credit !== '' && (
|
||||
<PhotoInformation
|
||||
info={this.state.photoInfo}
|
||||
api={this.state.currentAPI}
|
||||
url={this.state.url}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<BackgroundImage />
|
||||
{state.photoInfo && state.photoInfo.credit && (
|
||||
<PhotoInformation info={state.photoInfo} api={state.currentAPI} url={state.url} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Background;
|
||||
|
||||
@@ -1,34 +1,89 @@
|
||||
/**
|
||||
* It returns a random colour or random gradient as a style object
|
||||
* @param type - The type of the style. This is used to determine which style builder to use.
|
||||
* @returns An object with two properties: type and style.
|
||||
* Converts HSL values to Hex color code
|
||||
* @param {number} h - Hue (0-360)
|
||||
* @param {number} s - Saturation (0-100)
|
||||
* @param {number} l - Lightness (0-100)
|
||||
* @returns {string} Hex color code
|
||||
*/
|
||||
const hslToHex = (h, s, l) => {
|
||||
l /= 100;
|
||||
const a = (s * Math.min(l, 1 - l)) / 100;
|
||||
const f = (n) => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color)
|
||||
.toString(16)
|
||||
.padStart(2, '0');
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a random number within a range
|
||||
* @param {number} min - Minimum value
|
||||
* @param {number} max - Maximum value
|
||||
* @returns {number}
|
||||
*/
|
||||
const randomRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
/**
|
||||
* Aesthetic color palettes
|
||||
*/
|
||||
const PALETTES = {
|
||||
nature: [
|
||||
{ h: [85, 150], s: [40, 70], l: [35, 60] }, // Forest greens
|
||||
{ h: [200, 240], s: [40, 70], l: [40, 65] }, // Ocean blues
|
||||
{ h: [35, 45], s: [40, 80], l: [35, 60] }, // Earth tones
|
||||
{ h: [270, 310], s: [30, 60], l: [40, 65] }, // Lavender
|
||||
],
|
||||
sunset: [
|
||||
{ h: [0, 40], s: [60, 90], l: [45, 65] }, // Warm oranges
|
||||
{ h: [280, 320], s: [30, 60], l: [40, 65] }, // Soft purples
|
||||
{ h: [20, 40], s: [70, 90], l: [50, 70] }, // Golden hour
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates an aesthetically pleasing random color
|
||||
* @returns {string} Hex color code
|
||||
*/
|
||||
const generateRandomColor = () => {
|
||||
const palette = PALETTES.nature.concat(PALETTES.sunset);
|
||||
const colors = palette[Math.floor(Math.random() * palette.length)];
|
||||
|
||||
const h = randomRange(colors.h[0], colors.h[1]);
|
||||
const s = randomRange(colors.s[0], colors.s[1]);
|
||||
const l = randomRange(colors.l[0], colors.l[1]);
|
||||
|
||||
return hslToHex(h, s, l);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a complementary color based on the input color
|
||||
* @param {string} baseColor - Base color in hex
|
||||
* @returns {string} Complementary color in hex
|
||||
*/
|
||||
const getComplementaryColor = (baseColor) => {
|
||||
// Convert hex to HSL, shift hue by 180 degrees
|
||||
const h = (parseInt(baseColor.slice(1), 16) + 180) % 360;
|
||||
const s = randomRange(40, 70);
|
||||
const l = randomRange(40, 65);
|
||||
return hslToHex(h, s, l);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates random background style based on type
|
||||
* @param {string} type - Either 'random_colour' or 'random_gradient'
|
||||
* @returns {string} CSS background style string
|
||||
*/
|
||||
export function randomColourStyleBuilder(type) {
|
||||
// randomColour based on https://stackoverflow.com/a/5092872
|
||||
const randomColour = () =>
|
||||
'#000000'.replace(/0/g, () => {
|
||||
return (~~(Math.random() * 16)).toString(16);
|
||||
});
|
||||
let style = `background:${randomColour()};`;
|
||||
|
||||
if (type === 'random_gradient') {
|
||||
const directions = [
|
||||
'to right',
|
||||
'to left',
|
||||
'to bottom',
|
||||
'to top',
|
||||
'to bottom right',
|
||||
'to bottom left',
|
||||
'to top right',
|
||||
'to top left',
|
||||
];
|
||||
style = `background:linear-gradient(${
|
||||
directions[Math.floor(Math.random() * directions.length)]
|
||||
}, ${randomColour()}, ${randomColour()});`;
|
||||
const directions = ['to right', 'to bottom right', 'to bottom', 'to bottom left', 'to left'];
|
||||
const direction = directions[Math.floor(Math.random() * directions.length)];
|
||||
const color1 = generateRandomColor();
|
||||
const color2 = getComplementaryColor(color1);
|
||||
return `linear-gradient(${direction}, ${color1}, ${color2})`;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'colour',
|
||||
style,
|
||||
};
|
||||
return generateRandomColor();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ const BackgroundImage = () => {
|
||||
}`,
|
||||
}}
|
||||
id="backgroundImage"
|
||||
/>
|
||||
className="backgroundImage"
|
||||
>
|
||||
<div id="blurhashOverlay" className="blurhashOverlay" />
|
||||
<img id="backgroundImageActual" className="backgroundImageActual" alt="background" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
background-attachment: fixed !important;
|
||||
zoom: 100% !important;
|
||||
transform: scale(1) !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.backgroundTransform {
|
||||
@include animation('fadein2 2s');
|
||||
|
||||
transition: background 2s;
|
||||
transition: opacity 2s;
|
||||
}
|
||||
|
||||
@include keyframes(fadein2) {
|
||||
@@ -26,6 +26,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.blurhashOverlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: opacity 2s;
|
||||
}
|
||||
|
||||
.backgroundImageActual {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 0;
|
||||
transition: opacity 2s;
|
||||
}
|
||||
|
||||
.backgroundPreload {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user