From 294b3830bfbfe3b93ceb2ba6a4a5d66c7be4fe55 Mon Sep 17 00:00:00 2001 From: David Ralph Date: Mon, 19 Feb 2024 09:42:59 +0000 Subject: [PATCH] refactor(modules): second part of moving files around, changing layout etc --- src/App.jsx | 4 +- .../Form/Settings/Checkbox/Checkbox.jsx | 2 +- .../Form/Settings/Dropdown/Dropdown.jsx | 2 +- .../Form/Settings/FileUpload/FileUpload.jsx | 2 +- src/components/Form/Settings/Radio/Radio.jsx | 4 +- .../Form/Settings/Slider/Slider.jsx | 2 +- .../Form/Settings/Switch/Switch.jsx | 2 +- src/components/Form/Settings/Text/Text.jsx | 2 +- .../Layout/Settings/Header/Header.jsx | 2 +- .../PreferencesWrapper/PreferencesWrapper.jsx | 6 +- src/config/variables.js | 2 +- .../helpers/autocomplete/Autocomplete.jsx | 2 +- src/features/modals/Modals.jsx | 2 +- src/features/modals/main/marketplace/Item.jsx | 2 +- .../main/marketplace/sections/Added.jsx | 2 +- .../main/marketplace/sections/Marketplace.jsx | 2 +- .../modals/main/settings/ResetModal.jsx | 2 +- .../main/settings/sections/Advanced.jsx | 2 +- .../main/settings/sections/Appearance.jsx | 4 +- .../main/settings/sections/Experimental.jsx | 6 +- .../modals/main/settings/sections/Message.jsx | 2 +- .../modals/main/settings/sections/Navbar.jsx | 2 +- .../main/settings/sections/Overview.jsx | 2 +- .../main/settings/sections/QuickLinks.jsx | 4 +- .../modals/main/settings/sections/Quote.jsx | 2 +- .../modals/main/settings/sections/Search.jsx | 2 +- .../modals/main/settings/sections/Stats.jsx | 2 +- .../sections/background/Background.jsx | 9 +- .../settings/sections/background/Colour.jsx | 3 +- .../settings/sections/background/Custom.jsx | 4 +- .../welcome/Sections/ImportSettings.jsx | 2 +- .../welcome/Sections/ThemeSelection.jsx | 2 +- src/features/modals/welcome/Welcome.jsx | 2 +- .../modals/welcome/WelcomeSections.jsx | 3 +- src/features/widgets/Widgets.jsx | 2 +- .../widgets/background/Background.jsx | 9 +- .../widgets/background/ExcludeModal.jsx | 2 +- src/features/widgets/greeting/Greeting.jsx | 4 +- src/features/widgets/message/Message.jsx | 2 +- src/features/widgets/navbar/Apps.jsx | 2 +- src/features/widgets/navbar/Navbar.jsx | 2 +- src/features/widgets/navbar/Notes.jsx | 4 +- src/features/widgets/navbar/Todo.jsx | 2 +- .../widgets/quicklinks/QuickLinks.jsx | 2 +- src/features/widgets/quote/Quote.jsx | 2 +- src/features/widgets/search/Search.jsx | 2 +- src/features/widgets/time/Clock.jsx | 4 +- src/features/widgets/time/Date.jsx | 4 +- src/features/widgets/weather/Weather.jsx | 2 +- src/index.jsx | 2 +- src/{utils => lib}/translations.js | 0 src/utils/{helpers => }/background/avif.js | 0 src/utils/background/getOfflineImage.js | 35 +++ src/utils/background/gradient/getGradient.js | 46 ++++ .../gradient}/hexToRgb.js | 4 +- src/utils/background/gradient/index.js | 17 ++ .../gradient}/rgbToHex.js | 0 .../gradient}/rgbToHsv.js | 0 .../gradient}/setRgba.js | 4 +- src/utils/background/index.js | 6 + src/utils/background/randomColour.js | 34 +++ src/utils/background/videoCheck.js | 13 ++ src/utils/data/slider_values.json | 36 ++++ src/utils/{helpers/date.js => date/index.js} | 1 + src/utils/{helpers => }/eventbus.js | 0 src/utils/{helpers => }/experimental.js | 0 src/utils/helpers/background/index.js | 0 src/utils/helpers/background/widget.js | 132 ------------ src/utils/helpers/marketplace.js | 168 --------------- src/utils/helpers/settings/index.js | 199 ------------------ src/utils/helpers/settings/modals.js | 150 ------------- src/utils/links/getTitleFromUrl.js | 17 ++ src/utils/links/index.js | 4 + src/utils/links/isValidUrl.js | 8 + src/utils/marketplace/index.js | 5 + src/utils/marketplace/install.js | 84 ++++++++ src/utils/marketplace/uninstall.js | 78 +++++++ src/utils/marketplace/urlParser.js | 13 ++ src/utils/saveFile.js | 40 ++++ src/utils/settings/default.js | 34 +++ src/utils/settings/export.js | 20 ++ src/utils/settings/import.js | 18 ++ src/utils/settings/index.js | 7 + src/utils/settings/load.js | 141 +++++++++++++ src/utils/settings/move.js | 25 +++ src/utils/{helpers => }/stats.js | 0 vite.config.mjs | 7 +- 87 files changed, 760 insertions(+), 728 deletions(-) rename src/{utils => lib}/translations.js (100%) rename src/utils/{helpers => }/background/avif.js (100%) create mode 100644 src/utils/background/getOfflineImage.js create mode 100644 src/utils/background/gradient/getGradient.js rename src/utils/{helpers/background => background/gradient}/hexToRgb.js (91%) create mode 100644 src/utils/background/gradient/index.js rename src/utils/{helpers/background => background/gradient}/rgbToHex.js (100%) rename src/utils/{helpers/background => background/gradient}/rgbToHsv.js (100%) rename src/utils/{helpers/background => background/gradient}/setRgba.js (91%) create mode 100644 src/utils/background/index.js create mode 100644 src/utils/background/randomColour.js create mode 100644 src/utils/background/videoCheck.js create mode 100644 src/utils/data/slider_values.json rename src/utils/{helpers/date.js => date/index.js} (97%) rename src/utils/{helpers => }/eventbus.js (100%) rename src/utils/{helpers => }/experimental.js (100%) delete mode 100644 src/utils/helpers/background/index.js delete mode 100644 src/utils/helpers/background/widget.js delete mode 100644 src/utils/helpers/marketplace.js delete mode 100644 src/utils/helpers/settings/index.js delete mode 100644 src/utils/helpers/settings/modals.js create mode 100644 src/utils/links/getTitleFromUrl.js create mode 100644 src/utils/links/index.js create mode 100644 src/utils/links/isValidUrl.js create mode 100644 src/utils/marketplace/index.js create mode 100644 src/utils/marketplace/install.js create mode 100644 src/utils/marketplace/uninstall.js create mode 100644 src/utils/marketplace/urlParser.js create mode 100644 src/utils/saveFile.js create mode 100644 src/utils/settings/default.js create mode 100644 src/utils/settings/export.js create mode 100644 src/utils/settings/import.js create mode 100644 src/utils/settings/index.js create mode 100644 src/utils/settings/load.js create mode 100644 src/utils/settings/move.js rename src/utils/{helpers => }/stats.js (100%) 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'), }, }, };