mirror of
https://github.com/mue/mue.git
synced 2026-07-14 04:24:01 +02:00
refactor(modules): experimental changes to module structure
This commit is contained in:
52
src/utils/data/achievements.json
Normal file
52
src/utils/data/achievements.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"achievements": [
|
||||
{
|
||||
"name": "10/10 IGN",
|
||||
"description": "Opened 10 tabs",
|
||||
"condition": {
|
||||
"type": "tabsOpened",
|
||||
"amount": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Thank you",
|
||||
"description": "Opened 39 tabs",
|
||||
"condition": {
|
||||
"type": "tabsOpened",
|
||||
"amount": 39
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Seasoning",
|
||||
"description": "Opened 100 tabs",
|
||||
"condition": {
|
||||
"type": "tabsOpened",
|
||||
"amount": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mr Worldwide",
|
||||
"description": "Opened 305 tabs",
|
||||
"condition": {
|
||||
"type": "tabsOpened",
|
||||
"amount": 305
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Average Linux user",
|
||||
"description": "Installed an add-on",
|
||||
"condition": {
|
||||
"type": "addonInstall",
|
||||
"amount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Fully riced",
|
||||
"description": "Installed 5 add-ons",
|
||||
"condition": {
|
||||
"type": "addonInstall",
|
||||
"amount": 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
274
src/utils/data/default_settings.json
Normal file
274
src/utils/data/default_settings.json
Normal file
@@ -0,0 +1,274 @@
|
||||
[
|
||||
{
|
||||
"name": "time",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "greeting",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "background",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "quote",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "searchBar",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "blur",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "brightness",
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"name": "zero",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "events",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "customBackgroundColour",
|
||||
"value": "{\"angle\":\"180\",\"gradient\":[{\"colour\":\"#ffb032\",\"stop\":0}],\"type\":\"linear\"}"
|
||||
},
|
||||
{
|
||||
"name": "customBackground",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "greetingName",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "defaultGreetingMessage",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "backgroundAPI",
|
||||
"value": "mue"
|
||||
},
|
||||
{
|
||||
"name": "copyButton",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "installed",
|
||||
"value": "[]"
|
||||
},
|
||||
{
|
||||
"name": "searchEngine",
|
||||
"value": "duckduckgo"
|
||||
},
|
||||
{
|
||||
"name": "refresh",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "view",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "favouriteEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "quoteShareButton",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "favouriteQuoteEnabled",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "showWelcome",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "quoteLanguage",
|
||||
"value": "en"
|
||||
},
|
||||
{
|
||||
"name": "date",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"name": "timeType",
|
||||
"value": "digital"
|
||||
},
|
||||
{
|
||||
"name": "dateFormat",
|
||||
"value": "DMY"
|
||||
},
|
||||
{
|
||||
"name": "shortFormat",
|
||||
"value": "dots"
|
||||
},
|
||||
{
|
||||
"name": "toastDisplayTime",
|
||||
"value": 2500
|
||||
},
|
||||
{
|
||||
"name": "fontstyle",
|
||||
"value": "normal"
|
||||
},
|
||||
{
|
||||
"name": "fontweight",
|
||||
"value": 400
|
||||
},
|
||||
{
|
||||
"name": "order",
|
||||
"value": "[\"greeting\", \"time\", \"quicklinks\", \"quote\", \"date\", \"message\"]"
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
"value": "auto"
|
||||
},
|
||||
{
|
||||
"name": "backgroundType",
|
||||
"value": "api"
|
||||
},
|
||||
{
|
||||
"name": "timeformat",
|
||||
"value": "twentyfourhour"
|
||||
},
|
||||
{
|
||||
"name": "experimental",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "debugtimeout",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "quicklinks",
|
||||
"value": "[]"
|
||||
},
|
||||
{
|
||||
"name": "quicklinksenabled",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "weatherEnabled",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "tempformat",
|
||||
"value": "celsius"
|
||||
},
|
||||
{
|
||||
"name": "authorLink",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "bgtransition",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "backgroundVideoLoop",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "backgroundVideoMute",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "quicklinkstooltip",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "notesEnabled",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "showlocation",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "minuteHand",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "hourHand",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "datezero",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "autocompleteProvider",
|
||||
"value": "google"
|
||||
},
|
||||
{
|
||||
"name": "quoteType",
|
||||
"value": "api"
|
||||
},
|
||||
{
|
||||
"name": "backgroundFilter",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"name": "apiQuality",
|
||||
"value": "high"
|
||||
},
|
||||
{
|
||||
"name": "photoInformation",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "quicklinksddgProxy",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "backgroundFilterAmount",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "stats",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "statsData",
|
||||
"value": "{}"
|
||||
},
|
||||
{
|
||||
"name": "offlineMode",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "animations",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "textBorder",
|
||||
"value": "new"
|
||||
},
|
||||
{
|
||||
"name": "widgetStyle",
|
||||
"value": "new"
|
||||
},
|
||||
{
|
||||
"name": "backgroundExclude",
|
||||
"value": "[]"
|
||||
},
|
||||
{
|
||||
"name": "photoMap",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"name": "appsEnabled",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"name": "applinks",
|
||||
"value": "[]"
|
||||
}
|
||||
]
|
||||
32
src/utils/data/offline_images.json
Normal file
32
src/utils/data/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]
|
||||
}
|
||||
}
|
||||
6
src/utils/data/other_contributors.json
Normal file
6
src/utils/data/other_contributors.json
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"login": "FuryingFox",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28787893?v=4"
|
||||
}
|
||||
]
|
||||
15
src/utils/helpers/background/avif.js
Normal file
15
src/utils/helpers/background/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);
|
||||
});
|
||||
};
|
||||
39
src/utils/helpers/background/hexToRgb.js
Normal file
39
src/utils/helpers/background/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;
|
||||
}
|
||||
0
src/utils/helpers/background/index.js
Normal file
0
src/utils/helpers/background/index.js
Normal file
27
src/utils/helpers/background/rgbToHex.js
Normal file
27
src/utils/helpers/background/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/utils/helpers/background/rgbToHsv.js
Normal file
48
src/utils/helpers/background/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/utils/helpers/background/setRgba.js
Normal file
35
src/utils/helpers/background/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.
|
||||
*/
|
||||
const 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 default 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;
|
||||
}
|
||||
}
|
||||
132
src/utils/helpers/background/widget.js
Normal file
132
src/utils/helpers/background/widget.js
Normal file
@@ -0,0 +1,132 @@
|
||||
// since there is so much code in the component, we have moved it to a separate file
|
||||
import offlineImages from 'utils/data/offline_images.json';
|
||||
|
||||
/**
|
||||
* 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 function videoCheck(url) {
|
||||
return (
|
||||
url.startsWith('data:video/') ||
|
||||
url.endsWith('.mp4') ||
|
||||
url.endsWith('.webm') ||
|
||||
url.endsWith('.ogg')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 offlineBackground(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: `./offline-images/${randomImage}.webp`,
|
||||
type,
|
||||
photoInfo: {
|
||||
offline: true,
|
||||
credit: photographer,
|
||||
},
|
||||
};
|
||||
|
||||
localStorage.setItem('currentBackground', JSON.stringify(object));
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* It takes a gradient object and returns a style object
|
||||
* @returns An object with two properties: type and style.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
36
src/utils/helpers/date.js
Normal file
36
src/utils/helpers/date.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* If the number is between 3 and 20, return the number with the suffix "th". Otherwise, return the
|
||||
* number with the suffix "st", "nd", "rd", or "th" depending on the last digit of the number
|
||||
* @param d - The day of the month.
|
||||
* @returns the day of the month with the appropriate suffix.
|
||||
*/
|
||||
export function nth(d) {
|
||||
if (d > 3 && d < 21) {
|
||||
return d + 'th';
|
||||
}
|
||||
|
||||
switch (d % 10) {
|
||||
case 1:
|
||||
return d + 'st';
|
||||
case 2:
|
||||
return d + 'nd';
|
||||
case 3:
|
||||
return d + 'rd';
|
||||
default:
|
||||
return d + 'th';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It takes a date and a timezone and returns a new date object with the timezone applied.
|
||||
* @param date - The date you want to convert.
|
||||
* @param tz - The timezone you want to convert to.
|
||||
* @returns A new Date object with the timezone set to the timezone passed in.
|
||||
*/
|
||||
export function convertTimezone(date, tz) {
|
||||
return new Date(
|
||||
(typeof date === 'string' ? new Date(date) : date).toLocaleString('en-US', {
|
||||
timeZone: tz,
|
||||
}),
|
||||
);
|
||||
}
|
||||
136
src/utils/helpers/eventbus.js
Normal file
136
src/utils/helpers/eventbus.js
Normal file
@@ -0,0 +1,136 @@
|
||||
export default class EventBus {
|
||||
static listeners = {};
|
||||
|
||||
/**
|
||||
* The on function adds an event listener to the document, and then pushes the callback function into
|
||||
* the listeners array.
|
||||
* @param event - The event name
|
||||
* @param callback - The function to be called when the event is triggered.
|
||||
*/
|
||||
static on(event, callback) {
|
||||
document.addEventListener(event, (e) => {
|
||||
callback(e.detail);
|
||||
});
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* It creates a new custom event, and dispatches it
|
||||
* @param event - The name of the event you want to emit.
|
||||
* @param data - The data you want to pass to the event.
|
||||
*/
|
||||
static emit(event, data) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(event, {
|
||||
detail: data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* It removes an event listener from the document and removes the callback from the listeners array
|
||||
* @param event - The event to listen for.
|
||||
* @param callback - The function to be called when the event is triggered.
|
||||
*/
|
||||
static off(event, callback) {
|
||||
document.removeEventListener(event, callback);
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event] = this.listeners[event].filter((listener) => listener !== callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The once function takes an event and a callback, and then adds a listener to the event that calls
|
||||
* the callback with the event's detail, and then removes the listener.
|
||||
* @param event - The event name
|
||||
* @param callback - The function to be called when the event is triggered.
|
||||
*/
|
||||
static once(event, callback) {
|
||||
const listener = (e) => {
|
||||
callback(e.detail);
|
||||
this.off(event, listener);
|
||||
};
|
||||
document.addEventListener(event, listener);
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event].push({ callback, listener });
|
||||
}
|
||||
|
||||
/**
|
||||
* It adds a listener to the beginning of the event listener queue.
|
||||
* @param event - The name of the event to listen for.
|
||||
* @param callback - The function to be called when the event is triggered.
|
||||
*/
|
||||
static prepend(event, callback) {
|
||||
const listener = (e) => {
|
||||
callback(e.detail);
|
||||
};
|
||||
document.addEventListener(
|
||||
event,
|
||||
listener,
|
||||
true, // set `useCapture` to `true` to insert the listener at the beginning
|
||||
);
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event].unshift({ callback, listener });
|
||||
}
|
||||
|
||||
/**
|
||||
* It adds an event listener to the document that will be called before any other event listeners for
|
||||
* the same event
|
||||
* @param event - The name of the event to listen for.
|
||||
* @param callback - The function to be called when the event is triggered.
|
||||
*/
|
||||
static prependOnce(event, callback) {
|
||||
const listener = (e) => {
|
||||
callback(e.detail);
|
||||
this.off(event, listener);
|
||||
};
|
||||
document.addEventListener(
|
||||
event,
|
||||
listener,
|
||||
true, // set `useCapture` to `true` to insert the listener at the beginning
|
||||
);
|
||||
if (!this.listeners[event]) {
|
||||
this.listeners[event] = [];
|
||||
}
|
||||
this.listeners[event].unshift({ callback, listener });
|
||||
}
|
||||
|
||||
/**
|
||||
* If the event exists, return the listeners for that event, otherwise return an empty array.
|
||||
* @param event - The event name.
|
||||
* @returns An array of listeners for the event.
|
||||
*/
|
||||
static getListeners(event) {
|
||||
if (this.listeners[event]) {
|
||||
return this.listeners[event];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* It returns an array of all the event names that have listeners attached to them.
|
||||
* @returns An array of the keys of the listeners object.
|
||||
*/
|
||||
static eventNames() {
|
||||
return Object.keys(this.listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* It removes all event listeners from the document.
|
||||
*/
|
||||
static removeAllListeners() {
|
||||
Object.keys(this.listeners).forEach((event) => {
|
||||
this.listeners[event].forEach(({ listener }) => {
|
||||
document.removeEventListener(event, listener);
|
||||
});
|
||||
});
|
||||
this.listeners = {};
|
||||
}
|
||||
}
|
||||
29
src/utils/helpers/experimental.js
Normal file
29
src/utils/helpers/experimental.js
Normal file
@@ -0,0 +1,29 @@
|
||||
export default function ExperimentalInit() {
|
||||
if (localStorage.getItem('debug') === 'true') {
|
||||
document.onkeydown = (e) => {
|
||||
e = e || window.event;
|
||||
|
||||
if (!e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const code = e.which || e.keyCode;
|
||||
|
||||
switch (code) {
|
||||
case 222:
|
||||
const timeout = localStorage.getItem('debugtimeout');
|
||||
|
||||
if (timeout !== '0') {
|
||||
setTimeout(() => {
|
||||
debugger;
|
||||
}, timeout);
|
||||
} else {
|
||||
debugger;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
168
src/utils/helpers/marketplace.js
Normal file
168
src/utils/helpers/marketplace.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import EventBus from './eventbus';
|
||||
|
||||
function showReminder() {
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// based on https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links
|
||||
export function urlParser(input) {
|
||||
const urlPattern =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,63}\b([-a-zA-Z0-9()!@:%_+.~#?&//=]*)/g;
|
||||
const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g;
|
||||
|
||||
const replaceUrl = (url) => `<br/><a class="link" href="${url}" target="_blank">${url}</a>`;
|
||||
const replaceEmail = (email) => `<a class="link" href="mailto:${email}">${email}</a>`;
|
||||
|
||||
const replacedUrls = input.replace(urlPattern, replaceUrl);
|
||||
const replacedEmails = replacedUrls.replace(emailPattern, replaceEmail);
|
||||
return replacedEmails;
|
||||
}
|
||||
|
||||
export function install(type, input, sideload) {
|
||||
switch (type) {
|
||||
case 'settings':
|
||||
localStorage.removeItem('backup_settings');
|
||||
|
||||
let oldSettings = [];
|
||||
Object.keys(localStorage).forEach((key) => {
|
||||
oldSettings.push({
|
||||
name: key,
|
||||
value: localStorage.getItem(key),
|
||||
});
|
||||
});
|
||||
|
||||
localStorage.setItem('backup_settings', JSON.stringify(oldSettings));
|
||||
Object.keys(input.settings).forEach((key) => {
|
||||
localStorage.setItem(key, input.settings[key]);
|
||||
});
|
||||
showReminder();
|
||||
break;
|
||||
|
||||
case 'photos':
|
||||
const currentPhotos = JSON.parse(localStorage.getItem('photo_packs')) || [];
|
||||
input.photos.forEach((photo) => {
|
||||
currentPhotos.push(photo);
|
||||
});
|
||||
localStorage.setItem('photo_packs', JSON.stringify(currentPhotos));
|
||||
|
||||
if (localStorage.getItem('backgroundType') !== 'photo_pack') {
|
||||
localStorage.setItem('oldBackgroundType', localStorage.getItem('backgroundType'));
|
||||
}
|
||||
localStorage.setItem('backgroundType', 'photo_pack');
|
||||
localStorage.removeItem('backgroundchange');
|
||||
EventBus.emit('refresh', 'background');
|
||||
// TODO: make this legitimately good and work without a reload - currently we just refresh
|
||||
sleep(4000);
|
||||
window.location.reload();
|
||||
break;
|
||||
|
||||
case 'quotes':
|
||||
const currentQuotes = JSON.parse(localStorage.getItem('quote_packs')) || [];
|
||||
input.quotes.forEach((quote) => {
|
||||
currentQuotes.push(quote);
|
||||
});
|
||||
localStorage.setItem('quote_packs', JSON.stringify(currentQuotes));
|
||||
|
||||
if (localStorage.getItem('quoteType') !== 'quote_pack') {
|
||||
localStorage.setItem('oldQuoteType', localStorage.getItem('quoteType'));
|
||||
}
|
||||
localStorage.setItem('quoteType', 'quote_pack');
|
||||
localStorage.removeItem('quotechange');
|
||||
EventBus.emit('refresh', 'quote');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
|
||||
if (sideload) {
|
||||
installed.push({
|
||||
content: {
|
||||
updated: 'Unpublished',
|
||||
data: input,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
installed.push(input);
|
||||
}
|
||||
|
||||
localStorage.setItem('installed', JSON.stringify(installed));
|
||||
}
|
||||
|
||||
export function uninstall(type, name) {
|
||||
let installedContents, packContents;
|
||||
switch (type) {
|
||||
case 'settings':
|
||||
const oldSettings = JSON.parse(localStorage.getItem('backup_settings'));
|
||||
localStorage.clear();
|
||||
oldSettings.forEach((item) => {
|
||||
localStorage.setItem(item.name, item.value);
|
||||
});
|
||||
showReminder();
|
||||
break;
|
||||
|
||||
case 'quotes':
|
||||
installedContents = JSON.parse(localStorage.getItem('quote_packs'));
|
||||
packContents = JSON.parse(localStorage.getItem('installed')).find(
|
||||
(content) => content.name === name,
|
||||
);
|
||||
installedContents.forEach((item, index) => {
|
||||
const exists = packContents.quotes.find(
|
||||
(content) => content.quote === item.quote || content.author === item.author,
|
||||
);
|
||||
if (exists !== undefined) {
|
||||
installedContents.splice(index, 1);
|
||||
}
|
||||
});
|
||||
localStorage.setItem('quote_packs', JSON.stringify(installedContents));
|
||||
if (installedContents.length === 0) {
|
||||
localStorage.setItem('quoteType', localStorage.getItem('oldQuoteType') || 'api');
|
||||
localStorage.removeItem('oldQuoteType');
|
||||
localStorage.removeItem('quote_packs');
|
||||
}
|
||||
localStorage.removeItem('quotechange');
|
||||
EventBus.emit('refresh', 'marketplacequoteuninstall');
|
||||
break;
|
||||
|
||||
case 'photos':
|
||||
installedContents = JSON.parse(localStorage.getItem('photo_packs'));
|
||||
packContents = JSON.parse(localStorage.getItem('installed')).find(
|
||||
(content) => content.name === name,
|
||||
);
|
||||
installedContents.forEach((item, index) => {
|
||||
const exists = packContents.photos.find((content) => content.photo === item.photo);
|
||||
if (exists !== undefined) {
|
||||
installedContents.splice(index, 1);
|
||||
}
|
||||
});
|
||||
localStorage.setItem('photo_packs', JSON.stringify(installedContents));
|
||||
if (installedContents.length === 0) {
|
||||
localStorage.setItem('backgroundType', localStorage.getItem('oldBackgroundType') || 'api');
|
||||
localStorage.removeItem('oldBackgroundType');
|
||||
localStorage.removeItem('photo_packs');
|
||||
}
|
||||
localStorage.removeItem('backgroundchange');
|
||||
EventBus.emit('refresh', 'marketplacebackgrounduninstall');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
for (let i = 0; i < installed.length; i++) {
|
||||
if (installed[i].name === name) {
|
||||
installed.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem('installed', JSON.stringify(installed));
|
||||
}
|
||||
199
src/utils/helpers/settings/index.js
Normal file
199
src/utils/helpers/settings/index.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import variables from 'config/variables';
|
||||
import experimentalInit from '../experimental';
|
||||
|
||||
import defaultSettings from 'utils/data/default_settings.json';
|
||||
import languages from '@/i18n/languages.json';
|
||||
|
||||
/**
|
||||
* It sets the default settings for the extension
|
||||
* @param reset - boolean
|
||||
*/
|
||||
export function setDefaultSettings(reset) {
|
||||
localStorage.clear();
|
||||
defaultSettings.forEach((element) => localStorage.setItem(element.name, element.value));
|
||||
|
||||
// Languages
|
||||
const languageCodes = languages.map(({ value }) => value);
|
||||
const browserLanguage =
|
||||
(navigator.languages &&
|
||||
navigator.languages.find((lang) => lang.replace('-', '_') && languageCodes.includes(lang))) ||
|
||||
navigator.language.replace('-', '_');
|
||||
|
||||
if (languageCodes.includes(browserLanguage)) {
|
||||
localStorage.setItem('language', browserLanguage);
|
||||
} else {
|
||||
localStorage.setItem('language', 'en_GB');
|
||||
}
|
||||
|
||||
localStorage.setItem('tabName', variables.getMessage('tabname'));
|
||||
|
||||
if (reset) {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
}
|
||||
|
||||
// finally we set this to true so it doesn't run the function on every load
|
||||
localStorage.setItem('firstRun', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* It loads the settings from localStorage and applies them to the page.
|
||||
* @param hotreload - boolean
|
||||
*/
|
||||
export function loadSettings(hotreload) {
|
||||
switch (localStorage.getItem('theme')) {
|
||||
case 'dark':
|
||||
document.body.classList.add('dark');
|
||||
document.body.classList.remove('light');
|
||||
break;
|
||||
case 'auto':
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.body.classList.add('dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
document.body.classList.add('light');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
document.body.classList.add('light');
|
||||
document.body.classList.remove('dark');
|
||||
}
|
||||
|
||||
document.title = localStorage.getItem('tabName') || variables.getMessage('tabname');
|
||||
|
||||
if (hotreload === true) {
|
||||
// remove old custom stuff and add new
|
||||
const custom = ['customcss', 'customfont'];
|
||||
custom.forEach((element) => {
|
||||
try {
|
||||
document.head.removeChild(document.getElementById(element));
|
||||
} catch (e) {
|
||||
// Disregard exception if custom stuff doesn't exist
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (localStorage.getItem('animations') === 'false') {
|
||||
document.body.classList.add('no-animations');
|
||||
} else {
|
||||
document.body.classList.remove('no-animations');
|
||||
}
|
||||
|
||||
// technically, this is text SHADOW, and not BORDER
|
||||
// however it's a mess and we'll just leave it at this for now
|
||||
const textBorder = localStorage.getItem('textBorder');
|
||||
// enable/disable old text border from before redesign
|
||||
if (textBorder === 'true') {
|
||||
const elements = ['greeting', 'clock', 'quote', 'quoteauthor', 'date'];
|
||||
elements.forEach((element) => {
|
||||
try {
|
||||
document.querySelector('.' + element).classList.add('textBorder');
|
||||
} catch (e) {
|
||||
// Disregard exception
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const elements = ['greeting', 'clock', 'quote', 'quoteauthor', 'date'];
|
||||
elements.forEach((element) => {
|
||||
try {
|
||||
document.querySelector('.' + element).classList.remove('textBorder');
|
||||
} catch (e) {
|
||||
// Disregard exception
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// remove actual default shadow
|
||||
if (textBorder === 'none') {
|
||||
document.getElementById('center').classList.add('no-textBorder');
|
||||
} else {
|
||||
document.getElementById('center').classList.remove('no-textBorder');
|
||||
}
|
||||
|
||||
const css = localStorage.getItem('customcss');
|
||||
if (css) {
|
||||
document.head.insertAdjacentHTML('beforeend', '<style id="customcss">' + css + '</style>');
|
||||
}
|
||||
|
||||
const font = localStorage.getItem('font');
|
||||
if (font) {
|
||||
let url = '';
|
||||
if (localStorage.getItem('fontGoogle') === 'true') {
|
||||
url = `@import url('https://fonts.googleapis.com/css2?family=${font}&display=swap');`;
|
||||
}
|
||||
|
||||
document.head.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`
|
||||
<style id='customfont'>
|
||||
${url}
|
||||
* {
|
||||
font-family: '${font}', 'Lexend Deca', 'Montserrat', sans-serif !important;
|
||||
font-weight: ${localStorage.getItem('fontweight')};
|
||||
font-style: ${localStorage.getItem('fontstyle')};
|
||||
}
|
||||
</style>
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
// If the extension got updated and the new app links default settings
|
||||
// were not set, set them
|
||||
if (localStorage.getItem('applinks') === null) {
|
||||
localStorage.setItem('applinks', JSON.stringify([]));
|
||||
}
|
||||
if (localStorage.getItem('appsEnabled') === null) {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
}
|
||||
|
||||
// everything below this shouldn't run on a hot reload event
|
||||
if (hotreload === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('experimental') === 'true') {
|
||||
experimentalInit();
|
||||
}
|
||||
|
||||
// easter egg
|
||||
console.log(`
|
||||
█████████████████████████████████████████████████████████████
|
||||
██ ██
|
||||
██ ███ ███ ██ ██ ███████ ██
|
||||
██ ████ ████ ██ ██ ██ ██
|
||||
██ ██ ████ ██ ██ ██ █████ ██
|
||||
██ ██ ██ ██ ██ ██ ██ ██
|
||||
██ ██ ██ ██████ ███████ ██
|
||||
██ ██
|
||||
██ ██
|
||||
██ Copyright 2018-${new Date().getFullYear()} The Mue Authors ██
|
||||
██ GitHub: https://github.com/mue/mue ██
|
||||
██ ██
|
||||
██ Thank you for using Mue! ██
|
||||
██ Feedback: hello@muetab.com ██
|
||||
█████████████████████████████████████████████████████████████
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all of the current settings, resets them, sets the defaults and then overrides
|
||||
* the new settings with the old saved messages where they exist.
|
||||
* @returns the result of the setDefaultSettings() function.
|
||||
*/
|
||||
export function moveSettings() {
|
||||
const currentSettings = Object.keys(localStorage);
|
||||
if (currentSettings.length === 0) {
|
||||
return this.setDefaultSettings();
|
||||
}
|
||||
|
||||
const settings = {};
|
||||
currentSettings.forEach((key) => {
|
||||
settings[key] = localStorage.getItem(key);
|
||||
});
|
||||
|
||||
localStorage.clear();
|
||||
setDefaultSettings();
|
||||
|
||||
Object.keys(settings).forEach((key) => {
|
||||
localStorage.setItem(key, settings[key]);
|
||||
});
|
||||
}
|
||||
150
src/utils/helpers/settings/modals.js
Normal file
150
src/utils/helpers/settings/modals.js
Normal file
@@ -0,0 +1,150 @@
|
||||
import variables from 'config/variables';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
/**
|
||||
* It creates a link to a file, and then clicks it
|
||||
* @param data - the data you want to save
|
||||
* @param [filename=file] - the name of the file to be saved
|
||||
* @param [type=text/json] - the type of file you want to save.
|
||||
*/
|
||||
export function saveFile(data, filename = 'file', type = 'text/json') {
|
||||
if (typeof data === 'object') {
|
||||
data = JSON.stringify(data, undefined, 4);
|
||||
}
|
||||
|
||||
const blob = new Blob([data], { type });
|
||||
|
||||
const event = document.createEvent('MouseEvents');
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.download = filename;
|
||||
a.dataset.downloadurl = [type, a.download, a.href].join(':');
|
||||
|
||||
// i need to see what all this actually does, i think wessel wrote this function
|
||||
event.initMouseEvent(
|
||||
'click',
|
||||
true,
|
||||
false,
|
||||
window,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
null,
|
||||
);
|
||||
a.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* It takes all the settings from localStorage and saves them to a file
|
||||
*/
|
||||
export function exportSettings() {
|
||||
const settings = {};
|
||||
|
||||
Object.keys(localStorage).forEach((key) => {
|
||||
settings[key] = localStorage.getItem(key);
|
||||
});
|
||||
|
||||
let date = new Date();
|
||||
// Format the date as YYYY-MM-DD_HH-MM-SS
|
||||
let formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}-${date.getSeconds().toString().padStart(2, '0')}`;
|
||||
let filename = `mue_settings_backup_${formattedDate}.json`;
|
||||
saveFile(settings, filename);
|
||||
variables.stats.postEvent('tab', 'Settings exported');
|
||||
}
|
||||
|
||||
/**
|
||||
* It takes a JSON file of Mue settings, parses it, and then sets the localStorage values to the values in the
|
||||
* file.
|
||||
* @param e - The JSON settings string to import
|
||||
*/
|
||||
export function importSettings(e) {
|
||||
const content = JSON.parse(e);
|
||||
|
||||
Object.keys(content).forEach((key) => {
|
||||
localStorage.setItem(key, content[key]);
|
||||
});
|
||||
|
||||
toast(variables.getMessage('toasts.imported'));
|
||||
variables.stats.postEvent('tab', 'Settings imported');
|
||||
}
|
||||
|
||||
/**
|
||||
* It returns an array of objects with a value and label property for the Mue sliders.
|
||||
* @param type - The type of slider you want to use.
|
||||
* @returns An object with keys of either zoom, toast, background or experimental.
|
||||
*/
|
||||
export function values(type) {
|
||||
const marks = {
|
||||
zoom: [
|
||||
{ value: 10, label: '0.1x' },
|
||||
{ value: 100, label: '1x' },
|
||||
{ value: 200, label: '2x' },
|
||||
{ value: 400, label: '4x' },
|
||||
],
|
||||
toast: [
|
||||
{ value: 500, label: '0.5s' },
|
||||
{ value: 1000, label: '1s' },
|
||||
{ value: 1500, label: '1.5s' },
|
||||
{ value: 2000, label: '2s' },
|
||||
{ value: 2500, label: '2.5s' },
|
||||
{ value: 3000, label: '3s' },
|
||||
{ value: 4000, label: '4s' },
|
||||
{ value: 5000, label: '5s' },
|
||||
],
|
||||
background: [
|
||||
{ value: 0, label: '0%' },
|
||||
{ value: 25, label: '25%' },
|
||||
{ value: 50, label: '50%' },
|
||||
{ value: 75, label: '75%' },
|
||||
{ value: 100, label: '100%' },
|
||||
],
|
||||
experimental: [
|
||||
{ value: 0, label: '0s' },
|
||||
{ value: 500, label: '0.5s' },
|
||||
{ value: 1000, label: '1s' },
|
||||
{ value: 1500, label: '1.5s' },
|
||||
{ value: 2000, label: '2s' },
|
||||
{ value: 2500, label: '2.5s' },
|
||||
{ value: 3000, label: '3s' },
|
||||
{ value: 4000, label: '4s' },
|
||||
{ value: 5000, label: '5s' },
|
||||
],
|
||||
};
|
||||
|
||||
return marks[type] || [];
|
||||
}
|
||||
|
||||
export async function getTitleFromUrl(url) {
|
||||
let title;
|
||||
try {
|
||||
let response = await fetch(url);
|
||||
if (response.redirected) {
|
||||
response = await fetch(response.url);
|
||||
}
|
||||
const html = await response.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
title = doc.title;
|
||||
} catch (e) {
|
||||
title = url;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
export function isValidUrl(url) {
|
||||
// regex: https://ihateregex.io/expr/url/
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,63}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/;
|
||||
|
||||
return urlRegex.test(url);
|
||||
}
|
||||
37
src/utils/helpers/stats.js
Normal file
37
src/utils/helpers/stats.js
Normal file
@@ -0,0 +1,37 @@
|
||||
export default class Stats {
|
||||
/**
|
||||
* It takes two arguments, a type and a name, and then it increments the value of the name in the type
|
||||
* object in localStorage
|
||||
* @param type - The type of event you want to track. This can be anything you want, but I recommend
|
||||
* using something like "click" or "hover"
|
||||
* @param name - The name of the event.
|
||||
*/
|
||||
static async postEvent(type, name) {
|
||||
const value = name.toLowerCase().replaceAll(' ', '-');
|
||||
|
||||
const data = JSON.parse(localStorage.getItem('statsData'));
|
||||
// tl;dr this creates the objects if they don't exist
|
||||
// this really needs a cleanup at some point
|
||||
if (!data[type] || !data[type][value]) {
|
||||
if (!data[type]) {
|
||||
data[type] = {};
|
||||
}
|
||||
|
||||
if (!data[type][value]) {
|
||||
data[type][value] = 1;
|
||||
}
|
||||
} else {
|
||||
data[type][value] = data[type][value] + 1;
|
||||
}
|
||||
localStorage.setItem('statsData', JSON.stringify(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* It increments the value of the key 'tabs-opened' in the object stored in localStorage by 1.
|
||||
*/
|
||||
static async tabLoad() {
|
||||
const data = JSON.parse(localStorage.getItem('statsData'));
|
||||
data['tabs-opened'] = data['tabs-opened'] + 1 || 1;
|
||||
localStorage.setItem('statsData', JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
60
src/utils/translations.js
Normal file
60
src/utils/translations.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import I18n from '@eartharoid/i18n';
|
||||
|
||||
import * as de_DE from 'translations/de_DE.json';
|
||||
import * as en_GB from 'translations/en_GB.json';
|
||||
import * as en_US from 'translations/en_US.json';
|
||||
import * as es from 'translations/es.json';
|
||||
import * as es_419 from 'translations/es_419.json';
|
||||
import * as fr from 'translations/fr.json';
|
||||
import * as nl from 'translations/nl.json';
|
||||
import * as no from 'translations/no.json';
|
||||
import * as ru from 'translations/ru.json';
|
||||
import * as zh_CN from 'translations/zh_CN.json';
|
||||
import * as id_ID from 'translations/id_ID.json';
|
||||
import * as tr_TR from 'translations/tr_TR.json';
|
||||
import * as pt_BR from 'translations/pt_BR.json';
|
||||
import * as bn from 'translations/bn.json';
|
||||
|
||||
/**
|
||||
* Initialise the i18n object.
|
||||
* The i18n object is then returned.
|
||||
* @param locale - The locale to use.
|
||||
* @returns The i18n object.
|
||||
*/
|
||||
export function initTranslations(locale) {
|
||||
const i18n = new I18n(locale, {
|
||||
de_DE,
|
||||
en_GB,
|
||||
en_US,
|
||||
es,
|
||||
es_419,
|
||||
fr,
|
||||
nl,
|
||||
no,
|
||||
ru,
|
||||
zh_CN,
|
||||
id_ID,
|
||||
tr_TR,
|
||||
pt_BR,
|
||||
bn,
|
||||
});
|
||||
|
||||
return i18n;
|
||||
}
|
||||
|
||||
export const translations = {
|
||||
de_DE,
|
||||
en_GB,
|
||||
en_US,
|
||||
es,
|
||||
es_419,
|
||||
fr,
|
||||
nl,
|
||||
no,
|
||||
ru,
|
||||
zh_CN,
|
||||
id_ID,
|
||||
tr_TR,
|
||||
pt_BR,
|
||||
bn,
|
||||
};
|
||||
Reference in New Issue
Block a user