mirror of
https://github.com/mue/mue.git
synced 2026-07-13 20:13:47 +02:00
refactor: once again move background utils, this time to api
This commit is contained in:
@@ -6,14 +6,12 @@ import { PureComponent } from 'react';
|
||||
import PhotoInformation from './components/PhotoInformation';
|
||||
|
||||
import EventBus from 'utils/eventbus';
|
||||
import {
|
||||
videoCheck,
|
||||
offlineBackground,
|
||||
randomColourStyleBuilder,
|
||||
supportsAVIF,
|
||||
} from 'utils/background';
|
||||
|
||||
import { getGradient } from 'utils/background/gradient';
|
||||
import { supportsAVIF } from './api/avif';
|
||||
import { getOfflineImage } from './api/getOfflineImage';
|
||||
import videoCheck from './api/videoCheck';
|
||||
import { getGradient } from './api/gradient';
|
||||
import { randomColourStyleBuilder } from './api/randomColour';
|
||||
|
||||
import './scss/index.scss';
|
||||
import { decodeBlurHash } from 'fast-blurhash';
|
||||
@@ -128,7 +126,7 @@ export default class Background extends PureComponent {
|
||||
data = await (await fetch(requestURL, { headers: { accept } })).json();
|
||||
} catch (e) {
|
||||
// if requesting to the API fails, we get an offline image
|
||||
this.setState(offlineBackground('api'));
|
||||
this.setState(getOfflineImage('api'));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -201,7 +199,7 @@ export default class Background extends PureComponent {
|
||||
switch (type) {
|
||||
case 'api':
|
||||
if (offline) {
|
||||
return this.setState(offlineBackground('api'));
|
||||
return this.setState(getOfflineImage('api'));
|
||||
}
|
||||
|
||||
// API background
|
||||
@@ -246,7 +244,7 @@ export default class Background extends PureComponent {
|
||||
|
||||
// allow users to use offline images
|
||||
if (offline && !customBackground.startsWith('data:')) {
|
||||
return this.setState(offlineBackground('custom'));
|
||||
return this.setState(getOfflineImage('custom'));
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -271,7 +269,7 @@ export default class Background extends PureComponent {
|
||||
|
||||
case 'photo_pack':
|
||||
if (offline) {
|
||||
return this.setState(offlineBackground('photo_pack'));
|
||||
return this.setState(getOfflineImage('photo_pack'));
|
||||
}
|
||||
|
||||
const photofavourited = JSON.parse(localStorage.getItem('favourite'));
|
||||
@@ -503,7 +501,7 @@ export default class Background extends PureComponent {
|
||||
this.setState(current);
|
||||
}
|
||||
} else if (current.url.startsWith('http')) {
|
||||
this.setState(offlineBackground());
|
||||
this.setState(getOfflineImage());
|
||||
}
|
||||
if (this.state.firstTime !== true) {
|
||||
this.setState(current);
|
||||
|
||||
15
src/features/background/api/avif.js
Normal file
15
src/features/background/api/avif.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const testImage =
|
||||
'AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAAFCbWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAFBETmF2aWYAAAAADnBpdG0AAAAAAAEAAAAsaWxvYwAAAABEAAACAAEAAAABAAACRgAAABgAAgAAAAEAAAFqAAAA3AAAAEFpaW5mAAAAAAACAAAAGmluZmUCAAAAAAEAAGF2MDFDb2xvcgAAAAAZaW5mZQIAAAEAAgAARXhpZkV4aWYAAAAAGmlyZWYAAAAAAAAADmNkc2MAAgABAAEAAAB5aXBycAAAAFlpcGNvAAAAFGlzcGUAAAAAAAAAAQAAAAEAAAAQcGFzcAAAAAEAAAABAAAADGF2MUOBABwAAAAADnBpeGkAAAAAAQgAAAATY29scm5jbHgAAQANAAGAAAAAGGlwbWEAAAAAAAAAAQABBQECg4SFAAAA/G1kYXQAAAAASUkqAAgAAAAGABIBAwABAAAAAQAAABoBBQABAAAAVgAAABsBBQABAAAAXgAAACgBAwABAAAAAgAAADEBAgARAAAAZgAAAGmHBAABAAAAeAAAAAAAAAABAAAAAQAAAAEAAAABAAAAcGFpbnQubmV0IDQuMy4xMgAABQAAkAcABAAAADAyMzABoAMAAQAAAAEAAAACoAQAAQAAAAEAAAADoAQAAQAAAAEAAAAFoAQAAQAAALoAAAAAAAAAAgABAAIABAAAAFI5OAACAAcABAAAADAxMDAAAAAAEgAKBxgABpgIaA0yCxJABBEAEADG1FkX';
|
||||
|
||||
/**
|
||||
* It creates a new image element, sets the source to a base64 encoded AVIF image, and then resolves
|
||||
* true if the image loads successfully, or false if it doesn't
|
||||
*/
|
||||
export const supportsAVIF = () => {
|
||||
new Promise((resolve) => {
|
||||
const image = new Image();
|
||||
image.src = `data:image/avif;base64,${testImage}`;
|
||||
image.onload = () => resolve(true);
|
||||
image.onerror = () => resolve(false);
|
||||
});
|
||||
};
|
||||
35
src/features/background/api/getOfflineImage.js
Normal file
35
src/features/background/api/getOfflineImage.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import offlineImages from '../offline_images.json';
|
||||
|
||||
/**
|
||||
* It gets a random photographer from the offlineImages.json file, then gets a random image from that
|
||||
* photographer, and returns an object with the image's URL, type, and photoInfo.
|
||||
* </code>
|
||||
* @param type - 'background' or 'thumbnail'
|
||||
* @returns An object with the following properties:
|
||||
* url: A string that is the path to the image.
|
||||
* type: A string that is the type of image.
|
||||
* photoInfo: An object with the following properties:
|
||||
* offline: A boolean that is true.
|
||||
* credit: A string that is the name of the photographer.
|
||||
*/
|
||||
export function getOfflineImage(type) {
|
||||
const photographers = Object.keys(offlineImages);
|
||||
const photographer = photographers[Math.floor(Math.random() * photographers.length)];
|
||||
|
||||
const randomImage =
|
||||
offlineImages[photographer].photo[
|
||||
Math.floor(Math.random() * offlineImages[photographer].photo.length)
|
||||
];
|
||||
|
||||
const object = {
|
||||
url: `src/assets/offline-images/${randomImage}.webp`,
|
||||
type,
|
||||
photoInfo: {
|
||||
offline: true,
|
||||
credit: photographer,
|
||||
},
|
||||
};
|
||||
|
||||
localStorage.setItem('currentBackground', JSON.stringify(object));
|
||||
return object;
|
||||
}
|
||||
46
src/features/background/api/gradient/getGradient.js
Normal file
46
src/features/background/api/gradient/getGradient.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* It takes a gradient object and returns a style object
|
||||
* @returns An object with two properties: type and style.
|
||||
*/
|
||||
export function gradientStyleBuilder({ type, angle, gradient }) {
|
||||
// Note: Append the gradient for additional browser support.
|
||||
const steps = gradient?.map((v) => `${v.colour} ${v.stop}%`);
|
||||
const grad = `background: ${type}-gradient(${type === 'linear' ? `${angle}deg,` : ''}${steps})`;
|
||||
|
||||
return {
|
||||
type: 'colour',
|
||||
style: `background:${gradient[0]?.colour};${grad}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* It gets the gradient settings from localStorage, parses it, and returns the gradient style.
|
||||
* @returns A string.
|
||||
*/
|
||||
export function getGradient() {
|
||||
const customBackgroundColour = localStorage.getItem('customBackgroundColour') || {
|
||||
angle: '180',
|
||||
gradient: [{ colour: '#ffb032', stop: 0 }],
|
||||
type: 'linear',
|
||||
};
|
||||
|
||||
let gradientSettings = '';
|
||||
try {
|
||||
gradientSettings = JSON.parse(customBackgroundColour);
|
||||
} catch (e) {
|
||||
const hexColorRegex = /#[0-9a-fA-F]{6}/s;
|
||||
if (hexColorRegex.exec(customBackgroundColour)) {
|
||||
// Colour used 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: customBackgroundColour, stop: 0 }],
|
||||
};
|
||||
localStorage.setItem('customBackgroundColour', JSON.stringify(gradientSettings));
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof gradientSettings === 'object' && gradientSettings !== null) {
|
||||
return gradientStyleBuilder(gradientSettings);
|
||||
}
|
||||
}
|
||||
39
src/features/background/api/gradient/hexToRgb.js
Normal file
39
src/features/background/api/gradient/hexToRgb.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import rgbToHsv from './rgbToHsv';
|
||||
import { setRGBA } from './setRgba';
|
||||
|
||||
const hexRegexp = /(^#{0,1}[0-9A-F]{6}$)|(^#{0,1}[0-9A-F]{3}$)|(^#{0,1}[0-9A-F]{8}$)/i;
|
||||
const regexp = /([0-9A-F])([0-9A-F])([0-9A-F])/i;
|
||||
|
||||
/**
|
||||
* It takes a hex color code and returns an object with the color's RGB and HSV values
|
||||
* @param value - The hex value to convert to RGB.
|
||||
* @returns An object with the properties red, green, blue, alpha, hue, saturation, and value.
|
||||
*/
|
||||
export default function hexToRgb(value) {
|
||||
const valid = hexRegexp.test(value);
|
||||
|
||||
if (valid) {
|
||||
if (value[0] === '#') {
|
||||
value = value.slice(1, value.length);
|
||||
}
|
||||
|
||||
if (value.length === 3) {
|
||||
value = value.replace(regexp, '$1$1$2$2$3$3');
|
||||
}
|
||||
|
||||
const red = parseInt(value.substr(0, 2), 16);
|
||||
const green = parseInt(value.substr(2, 2), 16);
|
||||
const blue = parseInt(value.substr(4, 2), 16);
|
||||
const alpha = parseInt(value.substr(6, 2), 16) / 255;
|
||||
|
||||
const color = setRGBA(red, green, blue, alpha);
|
||||
const hsv = rgbToHsv({ ...color });
|
||||
|
||||
return {
|
||||
...color,
|
||||
...hsv,
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
17
src/features/background/api/gradient/index.js
Normal file
17
src/features/background/api/gradient/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { getGradient, gradientStyleBuilder } from './getGradient';
|
||||
|
||||
import hexToRgb from './hexToRgb';
|
||||
import rgbToHex from './rgbToHex';
|
||||
import rgbToHsv from './rgbToHsv';
|
||||
|
||||
import { setRGBA, isValidRGBValue } from './setRgba';
|
||||
|
||||
export {
|
||||
getGradient,
|
||||
gradientStyleBuilder,
|
||||
hexToRgb,
|
||||
rgbToHex,
|
||||
rgbToHsv,
|
||||
setRGBA,
|
||||
isValidRGBValue,
|
||||
};
|
||||
27
src/features/background/api/gradient/rgbToHex.js
Normal file
27
src/features/background/api/gradient/rgbToHex.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* It takes three numbers, converts them to hexadecimal, and returns a string of the three hexadecimal
|
||||
* numbers concatenated together
|
||||
* @param red - The red value of the color (0-255)
|
||||
* @param green - 0
|
||||
* @param blue - 0
|
||||
* @returns a string of the hexadecimal value of the rgb values passed in.
|
||||
*/
|
||||
export default function rgbToHex(red, green, blue) {
|
||||
let r16 = red.toString(16);
|
||||
let g16 = green.toString(16);
|
||||
let b16 = blue.toString(16);
|
||||
|
||||
if (red < 16) {
|
||||
r16 = `0${r16}`;
|
||||
}
|
||||
|
||||
if (green < 16) {
|
||||
g16 = `0${g16}`;
|
||||
}
|
||||
|
||||
if (blue < 16) {
|
||||
b16 = `0${b16}`;
|
||||
}
|
||||
|
||||
return r16 + g16 + b16;
|
||||
}
|
||||
48
src/features/background/api/gradient/rgbToHsv.js
Normal file
48
src/features/background/api/gradient/rgbToHsv.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* It converts RGB values to HSV values
|
||||
* @returns An object with the hue, saturation, and value of the color.
|
||||
* @param red - The red value of the color.
|
||||
* @param green - 0-255
|
||||
* @param blue - The blue value of the color.
|
||||
*/
|
||||
export default function rgbToHSv({ red, green, blue }) {
|
||||
let rr, gg, bb, h, s;
|
||||
|
||||
const rabs = red / 255;
|
||||
const gabs = green / 255;
|
||||
const babs = blue / 255;
|
||||
|
||||
const v = Math.max(rabs, gabs, babs);
|
||||
const diff = v - Math.min(rabs, gabs, babs);
|
||||
const diffc = (c) => (v - c) / 6 / diff + 1 / 2;
|
||||
|
||||
if (diff === 0) {
|
||||
h = 0;
|
||||
s = 0;
|
||||
} else {
|
||||
s = diff / v;
|
||||
rr = diffc(rabs);
|
||||
gg = diffc(gabs);
|
||||
bb = diffc(babs);
|
||||
|
||||
if (rabs === v) {
|
||||
h = bb - gg;
|
||||
} else if (gabs === v) {
|
||||
h = 1 / 3 + rr - bb;
|
||||
} else if (babs === v) {
|
||||
h = 2 / 3 + gg - rr;
|
||||
}
|
||||
|
||||
if (h < 0) {
|
||||
h += 1;
|
||||
} else if (h > 1) {
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hue: Math.round(h * 360),
|
||||
saturation: Math.round(s * 100),
|
||||
value: Math.round(v * 100),
|
||||
};
|
||||
}
|
||||
35
src/features/background/api/gradient/setRgba.js
Normal file
35
src/features/background/api/gradient/setRgba.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* It returns true if the value is a number, not NaN, and between 0 and 255.
|
||||
* @param value - The value to check.
|
||||
* @returns A function that takes a value and returns a boolean.
|
||||
*/
|
||||
export function isValidRGBValue(value) {
|
||||
return typeof value === 'number' && Number.isNaN(value) === false && value >= 0 && value <= 255;
|
||||
};
|
||||
|
||||
/**
|
||||
* "If the red, green, and blue values are valid, return an object with the red, green, and blue
|
||||
* values, and if the alpha value is valid, add it to the object."
|
||||
*
|
||||
* The function is a bit more complicated than that, but that's the gist of it
|
||||
* @param red - The red value of the color.
|
||||
* @param green - 0-255
|
||||
* @param blue - The blue value of the color.
|
||||
* @param alpha - The alpha value of the color.
|
||||
* @returns An object with the properties red, green, blue, and alpha.
|
||||
*/
|
||||
export function setRGBA(red, green, blue, alpha) {
|
||||
if (isValidRGBValue(red) && isValidRGBValue(green) && isValidRGBValue(blue)) {
|
||||
const color = {
|
||||
red: red | 0,
|
||||
green: green | 0,
|
||||
blue: blue | 0,
|
||||
};
|
||||
|
||||
if (isValidRGBValue(alpha) === true) {
|
||||
color.alpha = alpha | 0;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
||||
34
src/features/background/api/randomColour.js
Normal file
34
src/features/background/api/randomColour.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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()});`;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'colour',
|
||||
style,
|
||||
};
|
||||
}
|
||||
13
src/features/background/api/videoCheck.js
Normal file
13
src/features/background/api/videoCheck.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* If the URL starts with `data:video/` or ends with `.mp4`, `.webm`, or `.ogg`, then it's a video.
|
||||
* @param url - The URL of the file to be checked.
|
||||
* @returns A function that takes a url and returns a boolean.
|
||||
*/
|
||||
export default function videoCheck(url) {
|
||||
return (
|
||||
url.startsWith('data:video/') ||
|
||||
url.endsWith('.mp4') ||
|
||||
url.endsWith('.webm') ||
|
||||
url.endsWith('.ogg')
|
||||
);
|
||||
}
|
||||
32
src/features/background/offline_images.json
Normal file
32
src/features/background/offline_images.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"Ryan Hutton": {
|
||||
"photo": [2]
|
||||
},
|
||||
"Francesco De Tommaso": {
|
||||
"photo": [3]
|
||||
},
|
||||
"Andy Vu": {
|
||||
"photo": [4]
|
||||
},
|
||||
"Kevin Kurek": {
|
||||
"photo": [5]
|
||||
},
|
||||
"Frank Mckenna": {
|
||||
"photo": [6]
|
||||
},
|
||||
"Yuriy Bogdanov": {
|
||||
"photo": [7]
|
||||
},
|
||||
"Sergi Ferrete": {
|
||||
"photo": [9]
|
||||
},
|
||||
"Marcin Czerniawski": {
|
||||
"photo": [10]
|
||||
},
|
||||
"Thomas Lipke": {
|
||||
"photo": [11]
|
||||
},
|
||||
"Pixabay": {
|
||||
"photo": [1, 8, 12]
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { ColorPicker } from '@muetab/react-color-gradient-picker';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Row, Content, Action } from '../../../components/Layout/Settings/Item/SettingsItem';
|
||||
|
||||
import { hexToRgb, rgbToHex } from 'utils/background/gradient';
|
||||
import { hexToRgb, rgbToHex } from '../api/gradient';
|
||||
|
||||
//import '@muetab/react-color-gradient-picker/dist/index.css';
|
||||
import '../scss/react-color-picker-gradient-picker-custom-styles.scss';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from 'react-icons/md';
|
||||
import EventBus from 'utils/eventbus';
|
||||
import { compressAccurately, filetoDataURL } from 'image-conversion';
|
||||
import { videoCheck } from 'utils/background';
|
||||
import videoCheck from '../api/videoCheck';
|
||||
|
||||
import Checkbox from '../../../components/Form/Settings/Checkbox/Checkbox';
|
||||
import FileUpload from '../../../components/Form/Settings/FileUpload/FileUpload';
|
||||
|
||||
@@ -2,6 +2,7 @@ import variables from 'config/variables';
|
||||
import { useState, memo } from 'react';
|
||||
import { MdClose, MdOutlineAddLink } from 'react-icons/md';
|
||||
import { Tooltip } from 'components/Elements';
|
||||
|
||||
function CustomURLModal({ modalClose, urlError, modalCloseOnly }) {
|
||||
const [url, setURL] = useState();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user