diff --git a/src/App.jsx b/src/App.jsx
index 9c491771..ed5b84f2 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -6,9 +6,9 @@ import Background from 'features/widgets/background/Background';
import Widgets from 'features/widgets/Widgets';
import Modals from 'features/modals/Modals';
-import { loadSettings, moveSettings } from 'utils/helpers/settings';
+import { loadSettings, moveSettings } from 'utils/settings';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
export default class App extends PureComponent {
componentDidMount() {
diff --git a/src/components/Form/Settings/Checkbox/Checkbox.jsx b/src/components/Form/Settings/Checkbox/Checkbox.jsx
index e57eb017..952f5b0d 100644
--- a/src/components/Form/Settings/Checkbox/Checkbox.jsx
+++ b/src/components/Form/Settings/Checkbox/Checkbox.jsx
@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { PureComponent } from 'react';
import { Checkbox as CheckboxUI, FormControlLabel } from '@mui/material';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
class Checkbox extends PureComponent {
constructor(props) {
diff --git a/src/components/Form/Settings/Dropdown/Dropdown.jsx b/src/components/Form/Settings/Dropdown/Dropdown.jsx
index 9196e030..8c03533d 100644
--- a/src/components/Form/Settings/Dropdown/Dropdown.jsx
+++ b/src/components/Form/Settings/Dropdown/Dropdown.jsx
@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
import { InputLabel, MenuItem, FormControl, Select } from '@mui/material';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
class Dropdown extends PureComponent {
constructor(props) {
diff --git a/src/components/Form/Settings/FileUpload/FileUpload.jsx b/src/components/Form/Settings/FileUpload/FileUpload.jsx
index 07aebd47..8c74eb49 100644
--- a/src/components/Form/Settings/FileUpload/FileUpload.jsx
+++ b/src/components/Form/Settings/FileUpload/FileUpload.jsx
@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { compressAccurately, filetoDataURL } from 'image-conversion';
-import { videoCheck } from 'utils/helpers/background/widget';
+import { videoCheck } from 'utils/background';
class FileUpload extends PureComponent {
componentDidMount() {
diff --git a/src/components/Form/Settings/Radio/Radio.jsx b/src/components/Form/Settings/Radio/Radio.jsx
index 94376daa..791e193e 100644
--- a/src/components/Form/Settings/Radio/Radio.jsx
+++ b/src/components/Form/Settings/Radio/Radio.jsx
@@ -8,8 +8,8 @@ import {
FormLabel,
} from '@mui/material';
-import EventBus from 'utils/helpers/eventbus';
-import { translations } from 'utils/translations';
+import EventBus from 'utils/eventbus';
+import { translations } from 'lib/translations';
class Radio extends PureComponent {
constructor(props) {
diff --git a/src/components/Form/Settings/Slider/Slider.jsx b/src/components/Form/Settings/Slider/Slider.jsx
index 746da717..764f6679 100644
--- a/src/components/Form/Settings/Slider/Slider.jsx
+++ b/src/components/Form/Settings/Slider/Slider.jsx
@@ -4,7 +4,7 @@ import { toast } from 'react-toastify';
import { Slider } from '@mui/material';
import { MdRefresh } from 'react-icons/md';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
class SliderComponent extends PureComponent {
constructor(props) {
diff --git a/src/components/Form/Settings/Switch/Switch.jsx b/src/components/Form/Settings/Switch/Switch.jsx
index d51e3e5e..29a1a25f 100644
--- a/src/components/Form/Settings/Switch/Switch.jsx
+++ b/src/components/Form/Settings/Switch/Switch.jsx
@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { PureComponent } from 'react';
import { Switch as SwitchUI, FormControlLabel } from '@mui/material';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
class Switch extends PureComponent {
constructor(props) {
diff --git a/src/components/Form/Settings/Text/Text.jsx b/src/components/Form/Settings/Text/Text.jsx
index a46f69c3..38ba1226 100644
--- a/src/components/Form/Settings/Text/Text.jsx
+++ b/src/components/Form/Settings/Text/Text.jsx
@@ -4,7 +4,7 @@ import { toast } from 'react-toastify';
import { TextField } from '@mui/material';
import { MdRefresh } from 'react-icons/md';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
class Text extends PureComponent {
constructor(props) {
diff --git a/src/components/Layout/Settings/Header/Header.jsx b/src/components/Layout/Settings/Header/Header.jsx
index f2ffe80b..f19aca3e 100644
--- a/src/components/Layout/Settings/Header/Header.jsx
+++ b/src/components/Layout/Settings/Header/Header.jsx
@@ -6,7 +6,7 @@ import {
MdOutlineVisibility,
MdOutlineKeyboardArrowRight,
} from 'react-icons/md';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import { Button } from 'components/Elements';
export const CustomActions = ({ children }) => {
diff --git a/src/components/Layout/Settings/PreferencesWrapper/PreferencesWrapper.jsx b/src/components/Layout/Settings/PreferencesWrapper/PreferencesWrapper.jsx
index 93626393..179d9a00 100644
--- a/src/components/Layout/Settings/PreferencesWrapper/PreferencesWrapper.jsx
+++ b/src/components/Layout/Settings/PreferencesWrapper/PreferencesWrapper.jsx
@@ -3,8 +3,8 @@ import { Row, Content, Action } from '../Item/SettingsItem';
import variables from 'config/variables';
import Slider from '../../../Form/Settings/Slider/Slider';
-import { values } from 'utils/helpers/settings/modals';
-import EventBus from 'utils/helpers/eventbus';
+import values from 'utils/data/slider_values.json';
+import EventBus from 'utils/eventbus';
const PreferencesWrapper = ({ children, ...props }) => {
const [shown, setShown] = useState(localStorage.getItem(props.setting) === 'true');
@@ -32,7 +32,7 @@ const PreferencesWrapper = ({ children, ...props }) => {
max="400"
default="100"
display="%"
- marks={values('zoom')}
+ marks={values.zoom}
category={props.zoomCategory || props.category}
/>
diff --git a/src/config/variables.js b/src/config/variables.js
index 3df75543..e8cceeeb 100644
--- a/src/config/variables.js
+++ b/src/config/variables.js
@@ -1,5 +1,5 @@
import * as constants from 'config/constants';
-import Stats from 'utils/helpers/stats';
+import Stats from 'utils/stats';
const variables = {
language: {},
diff --git a/src/features/helpers/autocomplete/Autocomplete.jsx b/src/features/helpers/autocomplete/Autocomplete.jsx
index 3ec3518f..54adbff3 100644
--- a/src/features/helpers/autocomplete/Autocomplete.jsx
+++ b/src/features/helpers/autocomplete/Autocomplete.jsx
@@ -1,6 +1,6 @@
import { PureComponent } from 'react';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './autocomplete.scss';
diff --git a/src/features/modals/Modals.jsx b/src/features/modals/Modals.jsx
index f4a76fc7..9f5f423e 100644
--- a/src/features/modals/Modals.jsx
+++ b/src/features/modals/Modals.jsx
@@ -6,7 +6,7 @@ import Main from './main/Main';
import Navbar from '../widgets/navbar/Navbar';
import Preview from '../helpers/preview/Preview';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import Welcome from './welcome/Welcome';
diff --git a/src/features/modals/main/marketplace/Item.jsx b/src/features/modals/main/marketplace/Item.jsx
index deec2e52..ef1ea9d0 100644
--- a/src/features/modals/main/marketplace/Item.jsx
+++ b/src/features/modals/main/marketplace/Item.jsx
@@ -20,7 +20,7 @@ import Modal from 'react-modal';
import { Header } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
-import { install, uninstall } from 'utils/helpers/marketplace';
+import { install, uninstall } from 'utils/marketplace';
import { ShareModal } from 'components/Elements';
diff --git a/src/features/modals/main/marketplace/sections/Added.jsx b/src/features/modals/main/marketplace/sections/Added.jsx
index 02c96804..c111de34 100644
--- a/src/features/modals/main/marketplace/sections/Added.jsx
+++ b/src/features/modals/main/marketplace/sections/Added.jsx
@@ -12,7 +12,7 @@ import Dropdown from '../../../../../components/Form/Settings/Dropdown/Dropdown'
import { Header, CustomActions } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
-import { install, uninstall, urlParser } from 'utils/helpers/marketplace';
+import { install, uninstall, urlParser } from 'utils/marketplace';
export default class Added extends PureComponent {
constructor() {
diff --git a/src/features/modals/main/marketplace/sections/Marketplace.jsx b/src/features/modals/main/marketplace/sections/Marketplace.jsx
index e416b6df..8ab60438 100644
--- a/src/features/modals/main/marketplace/sections/Marketplace.jsx
+++ b/src/features/modals/main/marketplace/sections/Marketplace.jsx
@@ -16,7 +16,7 @@ import Dropdown from '../../../../../components/Form/Settings/Dropdown/Dropdown'
import { Header } from 'components/Layout/Settings';
import { Button } from 'components/Elements';
-import { install, urlParser, uninstall } from 'utils/helpers/marketplace';
+import { install, urlParser, uninstall } from 'utils/marketplace';
class Marketplace extends PureComponent {
constructor() {
diff --git a/src/features/modals/main/settings/ResetModal.jsx b/src/features/modals/main/settings/ResetModal.jsx
index 470c99e5..3d78f9cd 100644
--- a/src/features/modals/main/settings/ResetModal.jsx
+++ b/src/features/modals/main/settings/ResetModal.jsx
@@ -1,7 +1,7 @@
import { memo } from 'react';
import variables from 'config/variables';
import { MdClose, MdRestartAlt } from 'react-icons/md';
-import { setDefaultSettings } from 'utils/helpers/settings';
+import { setDefaultSettings } from 'utils/settings';
import { Tooltip } from 'components/Elements';
function ResetModal({ modalClose }) {
const reset = () => {
diff --git a/src/features/modals/main/settings/sections/Advanced.jsx b/src/features/modals/main/settings/sections/Advanced.jsx
index 9d2ec216..9561aab4 100644
--- a/src/features/modals/main/settings/sections/Advanced.jsx
+++ b/src/features/modals/main/settings/sections/Advanced.jsx
@@ -8,7 +8,7 @@ import {
MdDataUsage,
} from 'react-icons/md';
-import { exportSettings, importSettings } from 'utils/helpers/settings/modals';
+import { exportSettings, importSettings } from 'utils/settings';
import { FileUpload, Text, Switch, Dropdown } from 'components/Form/Settings';
import ResetModal from '../ResetModal';
diff --git a/src/features/modals/main/settings/sections/Appearance.jsx b/src/features/modals/main/settings/sections/Appearance.jsx
index d95c0743..041fd7a9 100644
--- a/src/features/modals/main/settings/sections/Appearance.jsx
+++ b/src/features/modals/main/settings/sections/Appearance.jsx
@@ -7,7 +7,7 @@ import { Header, Section, Row, Content, Action } from 'components/Layout/Setting
import { MdAccessibility } from 'react-icons/md';
-import { values } from 'utils/helpers/settings/modals';
+import values from 'utils/data/slider_values.json';
function AppearanceSettings() {
const [accessibility, setAccessibility] = useState(false);
@@ -226,7 +226,7 @@ function AppearanceSettings() {
step="100"
min="500"
max="5000"
- marks={values('toast')}
+ marks={values.toast}
display={
' ' +
variables.getMessage(
diff --git a/src/features/modals/main/settings/sections/Experimental.jsx b/src/features/modals/main/settings/sections/Experimental.jsx
index 29727042..19b1a4e9 100644
--- a/src/features/modals/main/settings/sections/Experimental.jsx
+++ b/src/features/modals/main/settings/sections/Experimental.jsx
@@ -4,8 +4,8 @@ import Checkbox from '../../../../../components/Form/Settings/Checkbox/Checkbox'
import Slider from '../../../../../components/Form/Settings/Slider/Slider';
import { TextField } from '@mui/material';
-import EventBus from 'utils/helpers/eventbus';
-import { values } from 'utils/helpers/settings/modals';
+import EventBus from 'utils/eventbus';
+import values from 'utils/data/slider_values.json';
import { Row, Content, Action } from '../../../../../components/Layout/Settings/Item/SettingsItem';
@@ -34,7 +34,7 @@ function ExperimentalSettings() {
max="5000"
default="0"
step="100"
- marks={values('experimental')}
+ marks={values.experimental}
element=".other"
/>
Send Event
diff --git a/src/features/modals/main/settings/sections/Message.jsx b/src/features/modals/main/settings/sections/Message.jsx
index 4e516e26..fba6c5b7 100644
--- a/src/features/modals/main/settings/sections/Message.jsx
+++ b/src/features/modals/main/settings/sections/Message.jsx
@@ -5,7 +5,7 @@ import { toast } from 'react-toastify';
import { TextareaAutosize } from '@mui/material';
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
export default class Message extends PureComponent {
constructor() {
diff --git a/src/features/modals/main/settings/sections/Navbar.jsx b/src/features/modals/main/settings/sections/Navbar.jsx
index a55495d2..42affcbe 100644
--- a/src/features/modals/main/settings/sections/Navbar.jsx
+++ b/src/features/modals/main/settings/sections/Navbar.jsx
@@ -13,7 +13,7 @@ import { Button } from 'components/Elements';
import { Row, Content, Action } from '../../../../../components/Layout/Settings/Item/SettingsItem';
import { Header } from 'components/Layout/Settings';
-import { getTitleFromUrl, isValidUrl } from 'utils/helpers/settings/modals';
+import { getTitleFromUrl, isValidUrl } from 'utils/links';
import QuickLink from './quicklinks/QuickLink';
function Navbar() {
diff --git a/src/features/modals/main/settings/sections/Overview.jsx b/src/features/modals/main/settings/sections/Overview.jsx
index 9b81b11b..857b4d1b 100644
--- a/src/features/modals/main/settings/sections/Overview.jsx
+++ b/src/features/modals/main/settings/sections/Overview.jsx
@@ -11,7 +11,7 @@ import QuickLinks from './overview_skeletons/QuickLinks';
import Date from './overview_skeletons/Date';
import Message from './overview_skeletons/Message';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
const widget_name = {
greeting: variables.getMessage('modals.main.settings.sections.greeting.title'),
diff --git a/src/features/modals/main/settings/sections/QuickLinks.jsx b/src/features/modals/main/settings/sections/QuickLinks.jsx
index 71463b2e..44f330a8 100644
--- a/src/features/modals/main/settings/sections/QuickLinks.jsx
+++ b/src/features/modals/main/settings/sections/QuickLinks.jsx
@@ -8,9 +8,9 @@ import Modal from 'react-modal';
import AddModal from './quicklinks/AddModal';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import QuickLink from './quicklinks/QuickLink';
-import { getTitleFromUrl, isValidUrl } from 'utils/helpers/settings/modals';
+import { getTitleFromUrl, isValidUrl } from 'utils/links';
export default class QuickLinks extends PureComponent {
constructor() {
diff --git a/src/features/modals/main/settings/sections/Quote.jsx b/src/features/modals/main/settings/sections/Quote.jsx
index 725ad371..750e68a5 100644
--- a/src/features/modals/main/settings/sections/Quote.jsx
+++ b/src/features/modals/main/settings/sections/Quote.jsx
@@ -14,7 +14,7 @@ import {
import { Checkbox, Dropdown } from 'components/Form/Settings';
import { toast } from 'react-toastify';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
export default class QuoteSettings extends PureComponent {
constructor() {
diff --git a/src/features/modals/main/settings/sections/Search.jsx b/src/features/modals/main/settings/sections/Search.jsx
index 654c6d4b..f64f39c1 100644
--- a/src/features/modals/main/settings/sections/Search.jsx
+++ b/src/features/modals/main/settings/sections/Search.jsx
@@ -6,7 +6,7 @@ import { MenuItem, TextField } from '@mui/material';
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
import { Dropdown, Checkbox } from 'components/Form/Settings';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import searchEngines from 'features/widgets/search/search_engines.json';
diff --git a/src/features/modals/main/settings/sections/Stats.jsx b/src/features/modals/main/settings/sections/Stats.jsx
index 1d8bbf16..fffaac60 100644
--- a/src/features/modals/main/settings/sections/Stats.jsx
+++ b/src/features/modals/main/settings/sections/Stats.jsx
@@ -8,7 +8,7 @@ import { toast } from 'react-toastify';
import { Button } from 'components/Elements';
import { Header, CustomActions } from 'components/Layout/Settings';
-import { saveFile } from 'utils/helpers/settings/modals';
+import { saveFile } from 'utils/saveFile';
import achievementsData from 'utils/data/achievements.json';
import translations from 'i18n/locales/achievements/index';
diff --git a/src/features/modals/main/settings/sections/background/Background.jsx b/src/features/modals/main/settings/sections/background/Background.jsx
index e6070a6e..57308d03 100644
--- a/src/features/modals/main/settings/sections/background/Background.jsx
+++ b/src/features/modals/main/settings/sections/background/Background.jsx
@@ -1,6 +1,5 @@
import variables from 'config/variables';
import { PureComponent } from 'react';
-import { MenuItem } from '@mui/material';
import { MdSource, MdOutlineKeyboardArrowRight, MdOutlineAutoAwesome } from 'react-icons/md';
import { Header } from 'components/Layout/Settings';
@@ -19,7 +18,7 @@ import Text from '../../../../../../components/Form/Settings/Text/Text';
import ColourSettings from './Colour';
import CustomSettings from './Custom';
-import { values } from 'utils/helpers/settings/modals';
+import values from 'utils/data/slider_values.json';
export default class BackgroundSettings extends PureComponent {
constructor() {
@@ -542,7 +541,7 @@ export default class BackgroundSettings extends PureComponent {
max="100"
default="0"
display="%"
- marks={values('background')}
+ marks={values.background}
category="background"
element="#backgroundImage"
/>
@@ -555,7 +554,7 @@ export default class BackgroundSettings extends PureComponent {
max="100"
default="90"
display="%"
- marks={values('background')}
+ marks={values.background}
category="background"
element="#backgroundImage"
/>
@@ -616,7 +615,7 @@ export default class BackgroundSettings extends PureComponent {
max="100"
default="0"
display="%"
- marks={values('background')}
+ marks={values.background}
category="background"
element="#backgroundImage"
/>
diff --git a/src/features/modals/main/settings/sections/background/Colour.jsx b/src/features/modals/main/settings/sections/background/Colour.jsx
index bfbf4a30..5ee9df6b 100644
--- a/src/features/modals/main/settings/sections/background/Colour.jsx
+++ b/src/features/modals/main/settings/sections/background/Colour.jsx
@@ -8,8 +8,7 @@ import {
Action,
} from '../../../../../../components/Layout/Settings/Item/SettingsItem';
-import hexToRgb from 'utils/helpers/background/hexToRgb';
-import rgbToHex from 'utils/helpers/background/rgbToHex';
+import { hexToRgb, rgbToHex } from 'utils/background/gradient';
//import '@muetab/react-color-gradient-picker/dist/index.css';
import '../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss';
diff --git a/src/features/modals/main/settings/sections/background/Custom.jsx b/src/features/modals/main/settings/sections/background/Custom.jsx
index 332924e0..ce8ee4e6 100644
--- a/src/features/modals/main/settings/sections/background/Custom.jsx
+++ b/src/features/modals/main/settings/sections/background/Custom.jsx
@@ -9,9 +9,9 @@ import {
MdOutlineFileUpload,
MdFolder,
} from 'react-icons/md';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import { compressAccurately, filetoDataURL } from 'image-conversion';
-import { videoCheck } from 'utils/helpers/background/widget';
+import { videoCheck } from 'utils/background';
import Checkbox from '../../../../../../components/Form/Settings/Checkbox/Checkbox';
import FileUpload from '../../../../../../components/Form/Settings/FileUpload/FileUpload';
diff --git a/src/features/modals/welcome/Sections/ImportSettings.jsx b/src/features/modals/welcome/Sections/ImportSettings.jsx
index 1b371e21..1f3bdabb 100644
--- a/src/features/modals/welcome/Sections/ImportSettings.jsx
+++ b/src/features/modals/welcome/Sections/ImportSettings.jsx
@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { useState } from 'react';
import { FileUpload } from 'components/Form/Settings';
import { MdCloudUpload } from 'react-icons/md';
-import { importSettings as importSettingsFunction } from 'utils/helpers/settings/modals';
+import { importSettings as importSettingsFunction } from 'utils/settings';
import { Header } from '../components/Layout';
import default_settings from 'utils/data/default_settings.json';
diff --git a/src/features/modals/welcome/Sections/ThemeSelection.jsx b/src/features/modals/welcome/Sections/ThemeSelection.jsx
index b649de8b..2cae0508 100644
--- a/src/features/modals/welcome/Sections/ThemeSelection.jsx
+++ b/src/features/modals/welcome/Sections/ThemeSelection.jsx
@@ -1,7 +1,7 @@
import variables from 'config/variables';
import { useState } from 'react';
import { MdAutoAwesome, MdLightMode, MdDarkMode } from 'react-icons/md';
-import { loadSettings } from 'utils/helpers/settings';
+import { loadSettings } from 'utils/settings';
import { Header } from '../components/Layout';
function ThemeSelection() {
diff --git a/src/features/modals/welcome/Welcome.jsx b/src/features/modals/welcome/Welcome.jsx
index e086844c..457bd90b 100644
--- a/src/features/modals/welcome/Welcome.jsx
+++ b/src/features/modals/welcome/Welcome.jsx
@@ -2,7 +2,7 @@ import variables from 'config/variables';
import { PureComponent } from 'react';
import { MdArrowBackIosNew, MdArrowForwardIos, MdOutlinePreview } from 'react-icons/md';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import { ProgressBar } from './components/Elements';
import { Button } from 'components/Elements';
diff --git a/src/features/modals/welcome/WelcomeSections.jsx b/src/features/modals/welcome/WelcomeSections.jsx
index ba770883..fd0aba65 100644
--- a/src/features/modals/welcome/WelcomeSections.jsx
+++ b/src/features/modals/welcome/WelcomeSections.jsx
@@ -15,8 +15,7 @@ import {
import { FaDiscord, FaGithub } from 'react-icons/fa';
import { Radio, Checkbox, FileUpload } from 'components/Form/Settings';
-import { loadSettings } from 'utils/helpers/settings';
-import { importSettings } from 'utils/helpers/settings/modals';
+import { loadSettings, importSettings } from 'utils/settings';
import default_settings from 'utils/data/default_settings.json';
import languages from '@/i18n/languages.json';
diff --git a/src/features/widgets/Widgets.jsx b/src/features/widgets/Widgets.jsx
index e83a3ea3..e61499a7 100644
--- a/src/features/widgets/Widgets.jsx
+++ b/src/features/widgets/Widgets.jsx
@@ -9,7 +9,7 @@ import Date from './time/Date';
import Message from './message/Message';
import { WidgetsLayout } from 'components/Layout';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
// weather is lazy loaded due to the size of the weather icons module
// since we're using react-icons this might not be accurate,
diff --git a/src/features/widgets/background/Background.jsx b/src/features/widgets/background/Background.jsx
index 317824d7..b877c6ae 100644
--- a/src/features/widgets/background/Background.jsx
+++ b/src/features/widgets/background/Background.jsx
@@ -5,17 +5,18 @@ import { PureComponent } from 'react';
import PhotoInformation from './PhotoInformation';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import {
videoCheck,
offlineBackground,
- getGradient,
randomColourStyleBuilder,
-} from 'utils/helpers/background/widget';
+ supportsAVIF
+} from 'utils/background';
+
+import { getGradient } from 'utils/background/gradient';
import './scss/index.scss';
import { decodeBlurHash } from 'fast-blurhash';
-import { supportsAVIF } from 'utils/helpers/background/avif';
export default class Background extends PureComponent {
constructor() {
diff --git a/src/features/widgets/background/ExcludeModal.jsx b/src/features/widgets/background/ExcludeModal.jsx
index 79384ec3..381a75d4 100644
--- a/src/features/widgets/background/ExcludeModal.jsx
+++ b/src/features/widgets/background/ExcludeModal.jsx
@@ -1,6 +1,6 @@
import variables from 'config/variables';
import { memo } from 'react';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import { Tooltip } from 'components/Elements';
import { MdClose, MdDone } from 'react-icons/md';
diff --git a/src/features/widgets/greeting/Greeting.jsx b/src/features/widgets/greeting/Greeting.jsx
index e6f9bc26..316799e1 100644
--- a/src/features/widgets/greeting/Greeting.jsx
+++ b/src/features/widgets/greeting/Greeting.jsx
@@ -1,8 +1,8 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
-import { nth, convertTimezone } from 'utils/helpers/date';
-import EventBus from 'utils/helpers/eventbus';
+import { nth, convertTimezone } from 'utils/date';
+import EventBus from 'utils/eventbus';
import './greeting.scss';
diff --git a/src/features/widgets/message/Message.jsx b/src/features/widgets/message/Message.jsx
index 71614f6d..adbcae16 100644
--- a/src/features/widgets/message/Message.jsx
+++ b/src/features/widgets/message/Message.jsx
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './message.scss';
const Message = () => {
diff --git a/src/features/widgets/navbar/Apps.jsx b/src/features/widgets/navbar/Apps.jsx
index b1b7acc5..a61fc7f2 100644
--- a/src/features/widgets/navbar/Apps.jsx
+++ b/src/features/widgets/navbar/Apps.jsx
@@ -6,7 +6,7 @@ import { MdPlaylistRemove, MdOutlineApps } from 'react-icons/md';
import { Tooltip } from 'components/Elements';
import { shift, useFloating } from '@floating-ui/react-dom';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
class Apps extends PureComponent {
constructor() {
diff --git a/src/features/widgets/navbar/Navbar.jsx b/src/features/widgets/navbar/Navbar.jsx
index 395c4fb0..60d0ae10 100644
--- a/src/features/widgets/navbar/Navbar.jsx
+++ b/src/features/widgets/navbar/Navbar.jsx
@@ -8,7 +8,7 @@ import Todo from './Todo';
import Apps from './Apps';
import Maximise from '../background/Maximise';
import { Tooltip } from 'components/Elements';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './scss/index.scss';
diff --git a/src/features/widgets/navbar/Notes.jsx b/src/features/widgets/navbar/Notes.jsx
index 20f8d7d2..de609ca3 100644
--- a/src/features/widgets/navbar/Notes.jsx
+++ b/src/features/widgets/navbar/Notes.jsx
@@ -7,8 +7,8 @@ import TextareaAutosize from '@mui/material/TextareaAutosize';
import { toast } from 'react-toastify';
import { Tooltip } from 'components/Elements';
-import { saveFile } from 'utils/helpers/settings/modals';
-import EventBus from 'utils/helpers/eventbus';
+import { saveFile } from 'utils/saveFile';
+import EventBus from 'utils/eventbus';
class Notes extends PureComponent {
constructor() {
diff --git a/src/features/widgets/navbar/Todo.jsx b/src/features/widgets/navbar/Todo.jsx
index 25130cf4..8fc90253 100644
--- a/src/features/widgets/navbar/Todo.jsx
+++ b/src/features/widgets/navbar/Todo.jsx
@@ -15,7 +15,7 @@ import { Tooltip } from 'components/Elements';
import Checkbox from '@mui/material/Checkbox';
import { shift, useFloating } from '@floating-ui/react-dom';
import { sortableContainer, sortableElement, sortableHandle } from '@muetab/react-sortable-hoc';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
const SortableItem = sortableElement(({ value }) => {value}
);
const SortableContainer = sortableContainer(({ children }) => {children}
);
diff --git a/src/features/widgets/quicklinks/QuickLinks.jsx b/src/features/widgets/quicklinks/QuickLinks.jsx
index 438bbb95..9f75c8be 100644
--- a/src/features/widgets/quicklinks/QuickLinks.jsx
+++ b/src/features/widgets/quicklinks/QuickLinks.jsx
@@ -1,7 +1,7 @@
import { PureComponent, createRef } from 'react';
import { Tooltip } from 'components/Elements';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './quicklinks.scss';
diff --git a/src/features/widgets/quote/Quote.jsx b/src/features/widgets/quote/Quote.jsx
index a15f79f3..7a1fcc40 100644
--- a/src/features/widgets/quote/Quote.jsx
+++ b/src/features/widgets/quote/Quote.jsx
@@ -18,7 +18,7 @@ import { ShareModal } from 'components/Elements';
import offline_quotes from './offline_quotes.json';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './quote.scss';
diff --git a/src/features/widgets/search/Search.jsx b/src/features/widgets/search/Search.jsx
index 1350748f..6738cdfb 100644
--- a/src/features/widgets/search/Search.jsx
+++ b/src/features/widgets/search/Search.jsx
@@ -7,7 +7,7 @@ import { FaYandex } from 'react-icons/fa';
import { Tooltip } from 'components/Elements';
import AutocompleteInput from 'features/helpers/autocomplete/Autocomplete';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './search.scss';
diff --git a/src/features/widgets/time/Clock.jsx b/src/features/widgets/time/Clock.jsx
index 069b8408..cb686d43 100644
--- a/src/features/widgets/time/Clock.jsx
+++ b/src/features/widgets/time/Clock.jsx
@@ -1,7 +1,7 @@
import { PureComponent, Suspense, lazy } from 'react';
-import { convertTimezone } from 'utils/helpers/date';
-import EventBus from 'utils/helpers/eventbus';
+import { convertTimezone } from 'utils/date';
+import EventBus from 'utils/eventbus';
import './clock.scss';
diff --git a/src/features/widgets/time/Date.jsx b/src/features/widgets/time/Date.jsx
index 968c980c..42514110 100644
--- a/src/features/widgets/time/Date.jsx
+++ b/src/features/widgets/time/Date.jsx
@@ -1,8 +1,8 @@
import variables from 'config/variables';
import { PureComponent, createRef } from 'react';
-import { nth, convertTimezone } from 'utils/helpers/date';
-import EventBus from 'utils/helpers/eventbus';
+import { nth, convertTimezone } from 'utils/date';
+import EventBus from 'utils/eventbus';
import './date.scss';
diff --git a/src/features/widgets/weather/Weather.jsx b/src/features/widgets/weather/Weather.jsx
index c976a834..975efc68 100644
--- a/src/features/widgets/weather/Weather.jsx
+++ b/src/features/widgets/weather/Weather.jsx
@@ -4,7 +4,7 @@ import { PureComponent } from 'react';
import WeatherIcon from './WeatherIcon';
import Expanded from './Expanded';
-import EventBus from 'utils/helpers/eventbus';
+import EventBus from 'utils/eventbus';
import './weather.scss';
diff --git a/src/index.jsx b/src/index.jsx
index bde8ee74..e4d8e2f4 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -10,7 +10,7 @@ import './scss/index.scss';
// the toast css is based on default so we need to import it
import 'react-toastify/dist/ReactToastify.min.css';
-import { initTranslations } from 'utils/translations';
+import { initTranslations } from 'lib/translations';
const languagecode = localStorage.getItem('language') || 'en_GB';
variables.language = initTranslations(languagecode);
diff --git a/src/utils/translations.js b/src/lib/translations.js
similarity index 100%
rename from src/utils/translations.js
rename to src/lib/translations.js
diff --git a/src/utils/helpers/background/avif.js b/src/utils/background/avif.js
similarity index 100%
rename from src/utils/helpers/background/avif.js
rename to src/utils/background/avif.js
diff --git a/src/utils/background/getOfflineImage.js b/src/utils/background/getOfflineImage.js
new file mode 100644
index 00000000..b4b545b3
--- /dev/null
+++ b/src/utils/background/getOfflineImage.js
@@ -0,0 +1,35 @@
+import offlineImages from 'utils/data/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.
+ *
+ * @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;
+}
diff --git a/src/utils/background/gradient/getGradient.js b/src/utils/background/gradient/getGradient.js
new file mode 100644
index 00000000..dd89dc7a
--- /dev/null
+++ b/src/utils/background/gradient/getGradient.js
@@ -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);
+ }
+}
diff --git a/src/utils/helpers/background/hexToRgb.js b/src/utils/background/gradient/hexToRgb.js
similarity index 91%
rename from src/utils/helpers/background/hexToRgb.js
rename to src/utils/background/gradient/hexToRgb.js
index ad2960f3..42bb2a26 100644
--- a/src/utils/helpers/background/hexToRgb.js
+++ b/src/utils/background/gradient/hexToRgb.js
@@ -1,5 +1,5 @@
import rgbToHsv from './rgbToHsv';
-import setRgba from './setRgba';
+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;
@@ -26,7 +26,7 @@ export default function hexToRgb(value) {
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 color = setRGBA(red, green, blue, alpha);
const hsv = rgbToHsv({ ...color });
return {
diff --git a/src/utils/background/gradient/index.js b/src/utils/background/gradient/index.js
new file mode 100644
index 00000000..5d4bd674
--- /dev/null
+++ b/src/utils/background/gradient/index.js
@@ -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,
+};
diff --git a/src/utils/helpers/background/rgbToHex.js b/src/utils/background/gradient/rgbToHex.js
similarity index 100%
rename from src/utils/helpers/background/rgbToHex.js
rename to src/utils/background/gradient/rgbToHex.js
diff --git a/src/utils/helpers/background/rgbToHsv.js b/src/utils/background/gradient/rgbToHsv.js
similarity index 100%
rename from src/utils/helpers/background/rgbToHsv.js
rename to src/utils/background/gradient/rgbToHsv.js
diff --git a/src/utils/helpers/background/setRgba.js b/src/utils/background/gradient/setRgba.js
similarity index 91%
rename from src/utils/helpers/background/setRgba.js
rename to src/utils/background/gradient/setRgba.js
index a4a0ec64..38f1899b 100644
--- a/src/utils/helpers/background/setRgba.js
+++ b/src/utils/background/gradient/setRgba.js
@@ -3,7 +3,7 @@
* @param value - The value to check.
* @returns A function that takes a value and returns a boolean.
*/
-const isValidRGBValue = (value) => {
+export function isValidRGBValue(value) {
return typeof value === 'number' && Number.isNaN(value) === false && value >= 0 && value <= 255;
};
@@ -18,7 +18,7 @@ const isValidRGBValue = (value) => {
* @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) {
+export function setRGBA(red, green, blue, alpha) {
if (isValidRGBValue(red) && isValidRGBValue(green) && isValidRGBValue(blue)) {
const color = {
red: red | 0,
diff --git a/src/utils/background/index.js b/src/utils/background/index.js
new file mode 100644
index 00000000..59d24da0
--- /dev/null
+++ b/src/utils/background/index.js
@@ -0,0 +1,6 @@
+import { supportsAVIF } from './avif';
+import { offlineBackground } from './getOfflineImage';
+import { randomColourStyleBuilder } from './randomColour';
+import videoCheck from './videoCheck';
+
+export { supportsAVIF, offlineBackground, randomColourStyleBuilder, videoCheck };
diff --git a/src/utils/background/randomColour.js b/src/utils/background/randomColour.js
new file mode 100644
index 00000000..6d919fa4
--- /dev/null
+++ b/src/utils/background/randomColour.js
@@ -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,
+ };
+}
diff --git a/src/utils/background/videoCheck.js b/src/utils/background/videoCheck.js
new file mode 100644
index 00000000..8b6d44fa
--- /dev/null
+++ b/src/utils/background/videoCheck.js
@@ -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')
+ );
+}
diff --git a/src/utils/data/slider_values.json b/src/utils/data/slider_values.json
new file mode 100644
index 00000000..97d78ef8
--- /dev/null
+++ b/src/utils/data/slider_values.json
@@ -0,0 +1,36 @@
+{
+ "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" }
+ ]
+}
diff --git a/src/utils/helpers/date.js b/src/utils/date/index.js
similarity index 97%
rename from src/utils/helpers/date.js
rename to src/utils/date/index.js
index 35a5ace9..9dd48fc2 100644
--- a/src/utils/helpers/date.js
+++ b/src/utils/date/index.js
@@ -1,3 +1,4 @@
+// todo: maybe move stuff
/**
* 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
diff --git a/src/utils/helpers/eventbus.js b/src/utils/eventbus.js
similarity index 100%
rename from src/utils/helpers/eventbus.js
rename to src/utils/eventbus.js
diff --git a/src/utils/helpers/experimental.js b/src/utils/experimental.js
similarity index 100%
rename from src/utils/helpers/experimental.js
rename to src/utils/experimental.js
diff --git a/src/utils/helpers/background/index.js b/src/utils/helpers/background/index.js
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/utils/helpers/background/widget.js b/src/utils/helpers/background/widget.js
deleted file mode 100644
index 812a8060..00000000
--- a/src/utils/helpers/background/widget.js
+++ /dev/null
@@ -1,132 +0,0 @@
-// 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.
- *
- * @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,
- };
-}
diff --git a/src/utils/helpers/marketplace.js b/src/utils/helpers/marketplace.js
deleted file mode 100644
index 95c8a1ac..00000000
--- a/src/utils/helpers/marketplace.js
+++ /dev/null
@@ -1,168 +0,0 @@
-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) => `
${url}`;
- const replaceEmail = (email) => `${email}`;
-
- 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));
-}
diff --git a/src/utils/helpers/settings/index.js b/src/utils/helpers/settings/index.js
deleted file mode 100644
index da9233ac..00000000
--- a/src/utils/helpers/settings/index.js
+++ /dev/null
@@ -1,199 +0,0 @@
-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', '');
- }
-
- 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',
- `
-
- `,
- );
- }
-
- // 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]);
- });
-}
diff --git a/src/utils/helpers/settings/modals.js b/src/utils/helpers/settings/modals.js
deleted file mode 100644
index 847bbabc..00000000
--- a/src/utils/helpers/settings/modals.js
+++ /dev/null
@@ -1,150 +0,0 @@
-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);
-}
diff --git a/src/utils/links/getTitleFromUrl.js b/src/utils/links/getTitleFromUrl.js
new file mode 100644
index 00000000..da1cfae6
--- /dev/null
+++ b/src/utils/links/getTitleFromUrl.js
@@ -0,0 +1,17 @@
+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;
+}
diff --git a/src/utils/links/index.js b/src/utils/links/index.js
new file mode 100644
index 00000000..ce99246d
--- /dev/null
+++ b/src/utils/links/index.js
@@ -0,0 +1,4 @@
+import { getTitleFromUrl } from "./getTitleFromUrl";
+import { isValidUrl } from "./isValidUrl";
+
+export { getTitleFromUrl, isValidUrl };
\ No newline at end of file
diff --git a/src/utils/links/isValidUrl.js b/src/utils/links/isValidUrl.js
new file mode 100644
index 00000000..f8fab6e6
--- /dev/null
+++ b/src/utils/links/isValidUrl.js
@@ -0,0 +1,8 @@
+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);
+}
diff --git a/src/utils/marketplace/index.js b/src/utils/marketplace/index.js
new file mode 100644
index 00000000..c5cbfc23
--- /dev/null
+++ b/src/utils/marketplace/index.js
@@ -0,0 +1,5 @@
+import { install } from './install';
+import { uninstall } from './uninstall';
+import { urlParser } from './urlParser';
+
+export { install, uninstall, urlParser };
diff --git a/src/utils/marketplace/install.js b/src/utils/marketplace/install.js
new file mode 100644
index 00000000..84c899c5
--- /dev/null
+++ b/src/utils/marketplace/install.js
@@ -0,0 +1,84 @@
+import EventBus from 'utils/eventbus';
+
+// todo: relocate these 2 functions
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+function showReminder() {
+ document.querySelector('.reminder-info').style.display = 'flex';
+ localStorage.setItem('showReminder', true);
+}
+
+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));
+}
diff --git a/src/utils/marketplace/uninstall.js b/src/utils/marketplace/uninstall.js
new file mode 100644
index 00000000..e85e1223
--- /dev/null
+++ b/src/utils/marketplace/uninstall.js
@@ -0,0 +1,78 @@
+import EventBus from 'utils/eventbus';
+
+// todo: relocate this function
+function showReminder() {
+ document.querySelector('.reminder-info').style.display = 'flex';
+ localStorage.setItem('showReminder', true);
+}
+
+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));
+}
diff --git a/src/utils/marketplace/urlParser.js b/src/utils/marketplace/urlParser.js
new file mode 100644
index 00000000..1f35438d
--- /dev/null
+++ b/src/utils/marketplace/urlParser.js
@@ -0,0 +1,13 @@
+// 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) => `
${url}`;
+ const replaceEmail = (email) => `${email}`;
+
+ const replacedUrls = input.replace(urlPattern, replaceUrl);
+ const replacedEmails = replacedUrls.replace(emailPattern, replaceEmail);
+ return replacedEmails;
+}
diff --git a/src/utils/saveFile.js b/src/utils/saveFile.js
new file mode 100644
index 00000000..69d3274f
--- /dev/null
+++ b/src/utils/saveFile.js
@@ -0,0 +1,40 @@
+/**
+ * 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);
+}
diff --git a/src/utils/settings/default.js b/src/utils/settings/default.js
new file mode 100644
index 00000000..43d8acd0
--- /dev/null
+++ b/src/utils/settings/default.js
@@ -0,0 +1,34 @@
+import defaultSettings from 'utils/data/default_settings.json';
+import languages from 'i18n/languages.json';
+import variables from 'config/variables';
+
+/**
+ * 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);
+}
diff --git a/src/utils/settings/export.js b/src/utils/settings/export.js
new file mode 100644
index 00000000..a1dcd61b
--- /dev/null
+++ b/src/utils/settings/export.js
@@ -0,0 +1,20 @@
+import { saveFile } from 'utils/saveFile';
+import variables from 'config/variables';
+
+/**
+ * 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');
+}
diff --git a/src/utils/settings/import.js b/src/utils/settings/import.js
new file mode 100644
index 00000000..ea2673d8
--- /dev/null
+++ b/src/utils/settings/import.js
@@ -0,0 +1,18 @@
+import { toast } from 'react-toastify';
+import variables from 'config/variables';
+
+/**
+ * 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');
+}
diff --git a/src/utils/settings/index.js b/src/utils/settings/index.js
new file mode 100644
index 00000000..1ab6b591
--- /dev/null
+++ b/src/utils/settings/index.js
@@ -0,0 +1,7 @@
+import { setDefaultSettings } from './default';
+import { exportSettings } from './export';
+import { importSettings } from './import';
+import { loadSettings } from './load';
+import { moveSettings } from './move';
+
+export { setDefaultSettings, exportSettings, importSettings, loadSettings, moveSettings };
diff --git a/src/utils/settings/load.js b/src/utils/settings/load.js
new file mode 100644
index 00000000..3374ef13
--- /dev/null
+++ b/src/utils/settings/load.js
@@ -0,0 +1,141 @@
+import variables from "config/variables";
+import ExperimentalInit from "utils/experimental";
+
+/**
+ * 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', '');
+ }
+
+ 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',
+ `
+
+ `,
+ );
+ }
+
+ // 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 ██
+ █████████████████████████████████████████████████████████████
+ `);
+}
diff --git a/src/utils/settings/move.js b/src/utils/settings/move.js
new file mode 100644
index 00000000..17bcf947
--- /dev/null
+++ b/src/utils/settings/move.js
@@ -0,0 +1,25 @@
+import { setDefaultSettings } from "./default";
+
+/**
+ * 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]);
+ });
+}
diff --git a/src/utils/helpers/stats.js b/src/utils/stats.js
similarity index 100%
rename from src/utils/helpers/stats.js
rename to src/utils/stats.js
diff --git a/vite.config.mjs b/vite.config.mjs
index c4dc61d8..7bfa75fd 100644
--- a/vite.config.mjs
+++ b/vite.config.mjs
@@ -99,12 +99,13 @@ export default defineConfig(({ command, mode }) => {
alias: {
'@': path.resolve(__dirname, './src'),
i18n: path.resolve(__dirname, './src/i18n'),
- features: path.resolve(__dirname, './src/features'),
components: path.resolve(__dirname, './src/components'),
- utils: path.resolve(__dirname, './src/utils'),
- translations: path.resolve(__dirname, './src/i18n/locales'),
config: path.resolve(__dirname, './src/config'),
+ features: path.resolve(__dirname, './src/features'),
+ lib: path.resolve(__dirname, './src/lib'),
scss: path.resolve(__dirname, './src/scss'),
+ translations: path.resolve(__dirname, './src/i18n/locales'),
+ utils: path.resolve(__dirname, './src/utils'),
},
},
};