Compare commits

..

44 Commits
5.2.0 ... 5.3.2

Author SHA1 Message Date
David Ralph
30768053eb chore: remove umami, release 5.3.2 2021-08-23 15:33:09 +01:00
David Ralph
1d99622123 chore: release 5.3.1 2021-08-23 12:01:25 +01:00
David Ralph
66ac192c09 chore(translations): add umami warning to stats tab when turned off as well 2021-08-22 22:21:40 +01:00
David Ralph
37464acf85 chore: use constants for other welcome link 2021-08-22 22:15:13 +01:00
David Ralph
c687b2fb67 fix: improve welcome modal text, cleanup settings helpers slightly 2021-08-22 22:12:47 +01:00
David Ralph
6bf6cca8c6 chore: release 5.3 2021-08-22 16:12:13 +01:00
David Ralph
096df4f073 fix: tab name language, favourite marketplace backgrounds, birthday greeting message etc 2021-08-22 15:00:05 +01:00
David Ralph
490f42a9ad style: codacy fixes 2021-08-21 11:11:00 +01:00
David Ralph
ef2f666ccf perf: add eventbus unmount in more places and optimise stats tab 2021-08-20 22:06:40 +01:00
David Ralph
afcead634b fix: text shadow and chrome manifest 2021-08-20 19:20:59 +01:00
David Ralph
98c857e7ad fix: photoinformation console errors 2021-08-20 11:52:05 +01:00
David Ralph
34cf696bb4 fix: quicklinks zoom 2021-08-19 22:57:16 +01:00
David Ralph
2f3252d15e refactor: cleanup photoinformation slightly 2021-08-19 22:24:03 +01:00
David Ralph
7ce2afb773 refactor: cleanup background widget slightly 2021-08-19 16:32:10 +01:00
David Ralph
203ff27ee1 fix: responsive modal 2021-08-19 15:35:51 +01:00
David Ralph
50b1d16171 fix: responsive marketplace items (WIP) 2021-08-18 23:13:26 +01:00
David Ralph
13825c5525 fix: responsive sidebar on smaller resolutions 2021-08-18 15:26:03 +01:00
David Ralph
a91d5843b9 fix: clock hot reload 2021-08-18 14:48:52 +01:00
David Ralph
fb15a1037b fix: close #176 2021-08-18 11:28:01 +01:00
David Ralph
c66add33d2 fix: modal transition and about tooltips 2021-08-17 22:20:27 +01:00
David Ralph
6ec57c75d4 fix: hot reload bugs 2021-08-17 17:09:54 +01:00
David Ralph
54e7a3a33f fix: widget order ui, tab name language etc 2021-08-17 16:31:42 +01:00
David Ralph
7e2432b9b4 feat: navbar zoom 2021-08-16 14:36:34 +01:00
David Ralph
640b83e1c3 fix: search on enter 2021-08-16 14:06:57 +01:00
David Ralph
4d3c7cbbe6 fix: favourite button, optimise widgets and remove dependency 2021-08-16 12:21:37 +01:00
dependabot[bot]
23251d248b chore(deps): bump @material-ui/icons from 5.0.0-beta.1 to 5.0.0-beta.4 (#175)
Bumps [@material-ui/icons](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui-icons) from 5.0.0-beta.1 to 5.0.0-beta.4.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.4/packages/material-ui-icons)

---
updated-dependencies:
- dependency-name: "@material-ui/icons"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-16 10:42:11 +01:00
dependabot[bot]
ba31ea5841 chore(deps): bump @material-ui/core from 5.0.0-beta.3 to 5.0.0-beta.4 (#174)
Bumps [@material-ui/core](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui) from 5.0.0-beta.3 to 5.0.0-beta.4.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.4/packages/material-ui)

---
updated-dependencies:
- dependency-name: "@material-ui/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-16 10:39:23 +01:00
David Ralph
4f5233fab9 refactor: cleanup 2021-08-15 22:28:37 +01:00
David Ralph
5e4a14ba2c fix: weather typo 2021-08-15 20:33:58 +01:00
David Ralph
e82ac7da9e perf: reduce localstorage calls etc 2021-08-15 20:33:06 +01:00
David Ralph
16485c3d2c fix: tooltip css, create tab translation, other small css bugs 2021-08-15 16:08:16 +01:00
Vicente
9a8a360e21 feat(translations): updated spanish translation (#173)
* feat(translations): updated spanish translation

* fix(translations): improved some translations
2021-08-15 16:07:17 +01:00
David Ralph
cd5364dd36 style: better create tab settings ui 2021-08-15 15:12:03 +01:00
David Ralph
70273798b6 feat(translations): add WIP support for create, fix stats issue 2021-08-15 11:59:30 +01:00
David Ralph
baaa780ade feat: improved create ui, prepare for photo and quote packs etc 2021-08-15 11:28:21 +01:00
David Ralph
39751736ca refactor: imports etc 2021-08-14 20:10:48 +01:00
David Ralph
1a8bb69288 feat: create addon tab, translation support for stats tab, fixes etc 2021-08-14 17:23:54 +01:00
David Ralph
b8c793741f build: fix firefox, add 2 new things to stats tab 2021-08-14 15:03:01 +01:00
David Ralph
0f20983c37 feat: open new tab on install and add uninstall page 2021-08-14 15:01:45 +01:00
David Ralph
9c3b8c7f59 feat: stats tab (WIP) 2021-08-11 18:15:54 +01:00
David Ralph
943d817e71 perf: optimise greeting 2021-08-11 16:21:35 +01:00
Wessel Tip
564b82c427 refractor: Update gradientStyleBuilder 2021-08-11 14:06:02 +02:00
dependabot[bot]
26df915801 chore(deps): bump @material-ui/core from 5.0.0-beta.2 to 5.0.0-beta.3 (#172)
Bumps [@material-ui/core](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui) from 5.0.0-beta.2 to 5.0.0-beta.3.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.3/packages/material-ui)

---
updated-dependencies:
- dependency-name: "@material-ui/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-09 10:55:36 +01:00
David Ralph
eefc594ec1 fix: improvements to feedback modal 2021-08-05 17:25:46 +01:00
94 changed files with 1679 additions and 1092 deletions

View File

@@ -2,5 +2,5 @@ module.exports = {
presets: ['@babel/preset-env', ['@babel/preset-react', {
runtime: 'automatic'
}]],
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', '@babel/plugin-transform-react-inline-elements', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
};

View File

@@ -0,0 +1,10 @@
/* eslint-disable no-undef */
chrome.runtime.setUninstallURL('https://muetab.com/uninstall');
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
chrome.tabs.create({
url: chrome.runtime.getURL('index.html')
});
}
});

View File

@@ -0,0 +1,10 @@
/* eslint-disable no-undef */
browser.runtime.setUninstallURL('https://muetab.com/uninstall');
browser.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
browser.tabs.create({
url: browser.runtime.getURL('index.html')
});
}
});

View File

@@ -4,7 +4,7 @@
"default_locale": "en",
"name": "__MSG_name__",
"description": "__MSG_description__",
"version": "5.2.0",
"version": "5.3.2",
"homepage_url": "https://muetab.com",
"browser_action": {
"default_icon": "icons/128x128.png"
@@ -17,5 +17,9 @@
"48": "icons/48x48.png",
"128": "icons/128x128.png"
},
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'"
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'",
"background": {
"persistent": false,
"scripts": [ "background-chrome.js" ]
}
}

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Mue",
"description": "Fast, open and free-to-use new tab page for modern browsers.",
"version": "5.2.0",
"version": "5.3.2",
"homepage_url": "https://muetab.com",
"browser_action": {
"default_icon": "icons/128x128.png"

View File

@@ -9,15 +9,14 @@
"homepage": "https://muetab.com",
"bugs": "https://github.com/mue/mue/issues/new?assignees=&labels=bug&template=bug-report.md&title=%5BBUG%5D",
"license": "BSD-3-Clause",
"version": "5.2.0",
"version": "5.3.2",
"dependencies": {
"@emotion/react": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@fontsource/lexend-deca": "4.4.5",
"@fontsource/montserrat": "4.4.5",
"@material-ui/core": "5.0.0-beta.2",
"@material-ui/icons": "5.0.0-beta.1",
"date-fns-tz": "^1.1.6",
"@material-ui/core": "5.0.0-beta.4",
"@material-ui/icons": "5.0.0-beta.4",
"react": "17.0.2",
"react-clock": "3.0.0",
"react-color-gradient-picker": "0.1.2",
@@ -28,34 +27,34 @@
"weather-icons-react": "1.2.0"
},
"devDependencies": {
"@babel/core": "^7.14.8",
"@babel/eslint-parser": "^7.14.9",
"@babel/core": "^7.15.0",
"@babel/eslint-parser": "^7.15.0",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-transform-react-constant-elements": "^7.13.15",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.9",
"@babel/plugin-transform-react-constant-elements": "^7.14.5",
"@babel/plugin-transform-react-inline-elements": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@eartharoid/deep-merge": "^0.0.1",
"babel-loader": "^8.2.2",
"babel-plugin-transform-react-class-to-function": "^1.2.2",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.2.0",
"date-fns": "^2.23.0",
"eslint": "^7.32.0",
"eslint-config-react-app": "^6.0.0",
"html-webpack-plugin": "^5.3.2",
"mini-css-extract-plugin": "^2.1.0",
"sass": "^1.37.0",
"mini-css-extract-plugin": "^2.2.0",
"sass": "^1.38.0",
"sass-loader": "^12.1.0",
"source-map-loader": "^3.0.0",
"webpack": "^5.47.1",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.0.0-rc.0"
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0"
},
"scripts": {
"start": "webpack serve",
"build": "webpack --mode=production",
"chrome": "cp manifest/chrome.json build/manifest.json && cp -r manifest/_locales build/_locales",
"chrome": "cp manifest/chrome.json build/manifest.json && cp -r manifest/_locales build/_locales && cp manifest/background-chrome.js build/background-chrome.js",
"firefox": "rm -rf build/_locales && cp manifest/firefox.json build/manifest.json"
},
"browserslist": {

View File

@@ -1,29 +1,29 @@
import React from 'react';
import { PureComponent } from 'react';
import { ToastContainer } from 'react-toastify';
import Background from './components/widgets/background/Background';
import Widgets from './components/widgets/Widgets';
import Modals from './components/modals/Modals';
import { loadSettings, moveSettings } from './modules/helpers/settings';
import EventBus from './modules/helpers/eventbus';
import SettingsFunctions from './modules/helpers/settings';
import { ToastContainer } from 'react-toastify';
export default class App extends React.PureComponent {
export default class App extends PureComponent {
componentDidMount() {
// 4.0 -> 5.0 (the key below is only on 5.0)
// now featuring 5.0 -> 5.1
// the firstRun check was moved here because the old function was useless
if (!localStorage.getItem('firstRun') || !localStorage.getItem('stats')) {
SettingsFunctions.moveSettings();
moveSettings();
window.location.reload();
}
SettingsFunctions.loadSettings();
loadSettings();
EventBus.on('refresh', (data) => {
if (data === 'other') {
SettingsFunctions.loadSettings(true);
loadSettings(true);
}
});

View File

@@ -1,18 +1,21 @@
import React from 'react';
import { PureComponent } from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import './autocomplete.scss';
export default class Autocomplete extends React.PureComponent {
export default class Autocomplete extends PureComponent {
constructor(props) {
super(props);
this.state = {
filtered: [],
input: ''
input: '',
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
};
}
onChange = (e) => {
if (localStorage.getItem('autocomplete') !== 'true') {
if (this.state.autocompleteDisabled) {
return this.setState({
input: e.target.value
});
@@ -35,6 +38,20 @@ export default class Autocomplete extends React.PureComponent {
this.props.onClick(e);
};
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'search') {
this.setState({
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
});
}
});
}
componentWillUnount() {
EventBus.off('refresh');
}
render() {
let autocomplete = null;

View File

@@ -47,4 +47,4 @@
background-color: rgba(0, 0, 0, 0.5);
}
}
}
}

View File

@@ -4,7 +4,7 @@
display: inline-block;
.tooltipTitle {
width: 60px;
min-width: 60px;
background-color: rgba(255, 255, 255, 0.89);
color: #000000;
text-align: center;
@@ -20,7 +20,7 @@
cursor: initial;
user-select: none;
opacity: 0;
transition: opacity 0.8s;
transition: 0.2s;
}
&:hover {

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { PureComponent } from 'react';
import { ErrorOutline } from '@material-ui/icons';
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
export default class ErrorBoundary extends React.PureComponent {
export default class ErrorBoundary extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -24,7 +23,7 @@ export default class ErrorBoundary extends React.PureComponent {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<ErrorOutlineIcon/>
<ErrorOutline/>
<h1>{this.language.title}</h1>
<p>{this.language.message}</p>
<button className='refresh' onClick={() => window.location.reload()}>{this.language.refresh}</button>

View File

@@ -1,19 +1,18 @@
import React from 'react';
import { PureComponent, Suspense, lazy } from 'react';
import Modal from 'react-modal';
import Main from './main/Main';
import Feedback from './feedback/Feedback';
import Navbar from '../widgets/navbar/Navbar';
import EventBus from '../../modules/helpers/eventbus';
import Main from './main/Main';
import Navbar from '../widgets/navbar/Navbar';
import Modal from 'react-modal';
// These modals are lazy loaded as the user won't use them every time they open a tab
// We used to lazy load the main modal, but doing so broke the main modal open animation on first click
const Welcome = React.lazy(() => import('./welcome/Welcome'));
const Feedback = React.lazy(() => import('./feedback/Feedback'));
// Welcome modal is lazy loaded as the user won't use it every time they open a tab
// We used to lazy load the main and feedback modals, but doing so broke the modal open animation on first click
const Welcome = lazy(() => import('./welcome/Welcome'));
const renderLoader = () => <></>;
export default class Modals extends React.PureComponent {
export default class Modals extends PureComponent {
constructor() {
super();
this.state = {
@@ -58,18 +57,18 @@ export default class Modals extends React.PureComponent {
render() {
return (
<>
<Navbar openModal={(modal) => this.toggleModal(modal, true)}/>
{this.state.welcomeModal === false ? <Navbar openModal={(modal) => this.toggleModal(modal, true)}/> : null}
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.toggleModal('mainModal', false)} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
<Main modalClose={() => this.toggleModal('mainModal', false)}/>
</Modal>
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay welcomeoverlay' shouldCloseOnOverlayClick={false} ariaHideApp={false}>
<Welcome modalClose={() => this.closeWelcome()}/>
</Modal>
<Modal closeTimeoutMS={300} onRequestClose={() => this.toggleModal('feedbackModal', false)} isOpen={this.state.feedbackModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
<Feedback modalClose={() => this.toggleModal('feedbackModal', false)}/>
</Modal>
</React.Suspense>
</Suspense>
</>
);
}

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { PureComponent } from 'react';
import './feedback.scss';
export default class FeedbackModal extends React.PureComponent {
export default class FeedbackModal extends PureComponent {
constructor() {
super();
this.state = {
@@ -38,7 +38,7 @@ export default class FeedbackModal extends React.PureComponent {
});
await fetch(window.constants.FEEDBACK_FORM, {
'method': 'POST'
method: 'POST'
});
this.setState({
@@ -47,7 +47,7 @@ export default class FeedbackModal extends React.PureComponent {
setTimeout(() => {
this.props.modalClose();
}, 3000);
}, 5000);
}
}
@@ -84,7 +84,7 @@ export default class FeedbackModal extends React.PureComponent {
<textarea name='question4' id='questionfour'/>
<p className='feedbackerror'>{this.state.questionfourerror}</p>
</>
{this.state.formsubmit}
<p>{this.state.formsubmit}</p>
<button onClick={() => this.submitForm()}>{this.language.submit}</button>
</>
</div>

View File

@@ -31,10 +31,14 @@
border: none;
padding: 15px 20px;
font-size: 1.5em;
background: linear-gradient(90deg, #ffb032 0%, #dd3b67 100%);
background: #5352ed;
color: #fff;
text-transform: uppercase;
cursor: pointer;
&:hover {
background: rgba(83, 82, 237, 0.8);
}
}
input[type=text] {
@@ -54,7 +58,8 @@
textarea {
width: 80%;
background-color: var(--sidebar);
padding: 10px;
background-color: var(--sidebar) !important;
}
.feedbackerror {

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { Suspense, lazy } from 'react';
import Tabs from './tabs/backend/Tabs';
import './scss/index.scss';
// Lazy load all the tabs instead of the modal itself
const Settings = React.lazy(() => import('./tabs/Settings'));
const Addons = React.lazy(() => import('./tabs/Addons'));
const Marketplace = React.lazy(() => import('./tabs/Marketplace'));
const Settings = lazy(() => import('./tabs/Settings'));
const Addons = lazy(() => import('./tabs/Addons'));
const Marketplace = lazy(() => import('./tabs/Marketplace'));
const renderLoader = () => (
<Tabs>
@@ -30,19 +30,19 @@ export default function MainModal(props) {
<span className='closeModal' onClick={props.modalClose}>&times;</span>
<Tabs navbar={true}>
<div label={language.settings} name='settings'>
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
<Settings/>
</React.Suspense>
</Suspense>
</div>
<div label={language.addons} name='addons'>
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
<Addons/>
</React.Suspense>
</Suspense>
</div>
<div label={language.marketplace} name='marketplace'>
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
<Marketplace/>
</React.Suspense>
</Suspense>
</div>
</Tabs>
</>

View File

@@ -1,12 +1,10 @@
import React from 'react';
import { PureComponent } from 'react';
import { ArrowBack } from '@material-ui/icons';
import Modal from 'react-modal';
import Lightbox from './Lightbox';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
export default class Item extends React.PureComponent {
export default class Item extends PureComponent {
constructor() {
super();
this.state = {
@@ -42,7 +40,7 @@ export default class Item extends React.PureComponent {
return (
<div id='item'>
<br/>
<ArrowBackIcon className='backArrow' onClick={this.props.toggleFunction}/>
<ArrowBack className='backArrow' onClick={this.props.toggleFunction}/>
<br/>
<h1>{this.props.data.display_name}</h1>
{this.props.button}

View File

@@ -1,15 +1,14 @@
import React from 'react';
import { PureComponent } from 'react';
import { LocalMall } from '@material-ui/icons';
import { toast } from 'react-toastify';
import LocalMallIcon from '@material-ui/icons/LocalMall';
import Item from '../Item';
import Items from '../Items';
import Dropdown from '../../settings/Dropdown';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { uninstall, urlParser } from '../../../../../modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Added extends React.PureComponent {
export default class Added extends PureComponent {
constructor() {
super();
this.state = {
@@ -34,7 +33,7 @@ export default class Added extends React.PureComponent {
name: data,
display_name: info.name,
author: info.author,
description: MarketplaceFunctions.urlParser(info.description.replace(/\n/g, '<br>')),
description: urlParser(info.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.version,
icon: info.screenshot_url,
@@ -51,7 +50,7 @@ export default class Added extends React.PureComponent {
}
uninstall() {
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
uninstall(this.state.item.type, this.state.item.display_name);
toast(window.language.toasts.uninstalled);
@@ -100,7 +99,7 @@ export default class Added extends React.PureComponent {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<LocalMallIcon/>
<LocalMall/>
<h1>{this.language.empty.title}</h1>
<p className='description'>{this.language.empty.description}</p>
</div>

View File

@@ -0,0 +1,220 @@
/* eslint-disable no-unused-vars */
import { PureComponent } from 'react';
import {
SettingsRounded as Settings,
PhotoOutlined as Photos,
FormatQuoteOutlined as Quotes
} from '@material-ui/icons';
import { toast } from 'react-toastify';
import { saveFile } from '../../../../../modules/helpers/settings/modals';
import FileUpload from '../../settings/FileUpload';
import '../../../welcome/welcome.scss';
export default class Create extends PureComponent {
constructor() {
super();
this.state = {
currentTab: 1,
addonMetadata: {
name: '',
description: '',
type: '',
version: '',
author: '',
icon_url: '',
screenshot_url: ''
},
addonData: '',
settingsClasses: {
current: 'toggle lightTheme',
json: 'toggle lightTheme'
}
};
}
changeTab(tab, type) {
if (type) {
return this.setState({
currentTab: tab,
addonMetadata: {
type: type
}
});
}
this.setState({
currentTab: tab
});
}
importSettings(input) {
const data = input || localStorage;
let settings = {};
Object.keys(data).forEach((key) => {
if (key === 'statsData' || key === 'firstRun' || key === 'showWelcome' || key === 'language' || key === 'installed' || key === 'stats') {
return;
}
settings[key] = localStorage.getItem(key);
});
this.setState({
addonData: settings,
settingsClasses: {
current: input ? 'toggle lightTheme' : 'toggle lightTheme active',
json: input ? 'toggle lightTheme active' : 'toggle lightTheme'
}
});
toast('Imported settings!');
}
downloadAddon() {
saveFile({
name: this.state.addonMetadata.name,
description: this.state.addonMetadata.description,
type: this.state.addonMetadata.type,
version: this.state.addonMetadata.version,
author: this.state.addonMetadata.author,
icon_url: this.state.addonMetadata.icon_url,
screenshot_url: this.state.addonMetadata.screenshot_url,
[this.state.addonMetadata.type]: this.state.addonData
}, this.state.addonMetadata.name + '.json');
}
render() {
let tabContent;
const { time } = window.language.modals.main.settings.sections;
const { marketplace, addons } = window.language.modals.main;
const { welcome } = window.language.modals;
const chooseType = (
<>
<h3>{time.type}</h3>
<div className='themesToggleArea'>
<div className='options'>
{/* <div className='toggle lightTheme' onClick={() => this.changeTab(2, 'photos')}>
<Photos/>
<span>{marketplace.photo_packs}</span>
</div>
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'quotes')}>
<Quotes/>
<span>{marketplace.quote_packs}</span>
</div>
*/}
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'settings')}>
<Settings/>
<span>{marketplace.preset_settings}</span>
</div>
</div>
</div>
</>
);
// todo: find a better way to do all this
const nextDescriptionDisabled = (this.state.addonMetadata.name !== undefined &&
this.state.addonMetadata.description !== undefined &&
this.state.addonMetadata.version !== undefined && this.state.addonMetadata.author !== undefined &&
this.state.addonMetadata.icon_url !== undefined && this.state.addonMetadata.screenshot_url !== undefined)
? false : true;
const setMetadata = (data, type) => {
this.setState({
addonMetadata: {
name: (type === 'name') ? data : this.state.addonMetadata.name,
description: (type === 'description') ? data : this.state.addonMetadata.description,
version: (type === 'version') ? data : this.state.addonMetadata.version,
author: (type === 'author') ? data : this.state.addonMetadata.author,
icon_url: (type === 'icon_url') ? data : this.state.addonMetadata.icon_url,
screenshot_url: (type === 'screenshot_url') ? data : this.state.addonMetadata.screenshot_url,
type: this.state.addonMetadata.type
}
});
}
const writeDescription = (
<>
<h3>{marketplace.product.information}</h3>
<p>{addons.create.metadata.name}</p>
<input type='text' value={this.state.addonMetadata.name} onInput={(e) => setMetadata(e.target.value, 'name')}/>
<p>{marketplace.product.version}</p>
<input type='text' value={this.state.addonMetadata.version} onInput={(e) => setMetadata(e.target.value, 'version')}/>
<p>{marketplace.product.author}</p>
<input type='text' value={this.state.addonMetadata.author} onInput={(e) => setMetadata(e.target.value, 'author')}/>
<p>{addons.create.metadata.icon_url}</p>
<input type='text' value={this.state.addonMetadata.icon_url} onInput={(e) => setMetadata(e.target.value, 'icon_url')}/>
<p>{addons.create.metadata.screenshot_url}</p>
<input type='text' value={this.state.addonMetadata.screenshot_url} onInput={(e) => setMetadata(e.target.value, 'screenshot_url')}/>
<p>{addons.create.metadata.description}</p>
<textarea className='settingsTextarea' value={this.state.addonMetadata.description} onInput={(e) => setMetadata(e.target.value, 'description')}></textarea>
<br/>
<button onClick={() => this.changeTab(1)} className='uploadbg' style={{ marginRight: '10px' }}>{welcome.buttons.previous}</button>
<button onClick={() => this.changeTab(this.state.addonMetadata.type)} className='uploadbg' disabled={nextDescriptionDisabled}>{welcome.buttons.next}</button>
</>
);
// settings
const nextSettingsDisabled = (this.state.addonData === '') ? true : false;
const importSettings = (
<>
<h3>{welcome.sections.settings.title}</h3>
<div className='themesToggleArea'>
<div className='options'>
<div className={this.state.settingsClasses.current} onClick={() => this.importSettings()}>
<span>{addons.create.settings.current}</span>
</div>
<div className={this.state.settingsClasses.json} onClick={() => document.getElementById('file-input').click()}>
<span>{addons.create.settings.json}</span>
</div>
</div>
</div>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.importSettings(JSON.parse(e.target.result))} />
<br/><br/>
<button onClick={() => this.changeTab(2)} className='uploadbg' style={{ marginRight: '10px' }}>{welcome.buttons.previous}</button>
<button onClick={() => this.changeTab(3)} className='uploadbg' disabled={nextSettingsDisabled}>{welcome.buttons.next}</button>
</>
);
// quotes
const addQuotes = (
<>
<h3>{addons.create.quotes.title}</h3>
</>
);
// photos
const addPhotos = (
<>
<h3>{addons.create.photos.title}</h3>
</>
);
const downloadAddon = (
<>
<h3>{addons.create.finish.title}</h3>
<button onClick={() => this.downloadAddon()} className='upload'>{addons.create.finish.download}</button>
<br/><br/>
<button onClick={() => this.changeTab(this.state.addonMetadata.type)} className='uploadbg' style={{ marginRight: '10px' }}>{welcome.buttons.previous}</button>
</>
);
switch (this.state.currentTab) {
case 2: tabContent = writeDescription; break;
case 'settings': tabContent = importSettings; break;
case 'quotes': tabContent = addQuotes; break;
case 'photos': tabContent = addPhotos; break;
case 3: tabContent = downloadAddon; break;
default: tabContent = chooseType;
}
return (
<>
<h2>{addons.create.other_title}</h2>
{tabContent}
</>
);
}
}

View File

@@ -1,17 +1,14 @@
import React from 'react';
import WifiOffIcon from '@material-ui/icons/WifiOff';
import LocalMallIcon from '@material-ui/icons/LocalMall';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { WifiOff, LocalMall } from '@material-ui/icons';
import Item from '../Item';
import Items from '../Items';
import Dropdown from '../../settings/Dropdown';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { install, urlParser, uninstall } from '../../../../../modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Marketplace extends React.PureComponent {
export default class Marketplace extends PureComponent {
constructor() {
super();
this.state = {
@@ -59,7 +56,7 @@ export default class Marketplace extends React.PureComponent {
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
description: urlParser(info.data.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.data.version,
icon: info.data.screenshot_url,
@@ -96,9 +93,9 @@ export default class Marketplace extends React.PureComponent {
manage(type) {
if (type === 'install') {
MarketplaceFunctions.install(this.state.item.type, this.state.item.data);
install(this.state.item.type, this.state.item.data);
} else {
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
uninstall(this.state.item.type, this.state.item.display_name);
}
toast(window.language.toasts[type + 'ed']);
@@ -162,23 +159,9 @@ export default class Marketplace extends React.PureComponent {
);
};
const featured = () => {
const openFeatured = () => {
window.stats.postEvent('marketplace', 'Featured clicked');
window.open(this.state.featured.buttonLink);
}
return (
<div className='featured' style={{ backgroundColor: this.state.featured.colour }}>
<p>{this.state.featured.title}</p>
<h1>{this.state.featured.name}</h1>
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
</div>
);
}
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return errorMessage(<>
<WifiOffIcon/>
<WifiOff/>
<h1>{this.language.offline.title}</h1>
<p className='description'>{this.language.offline.description}</p>
</>);
@@ -188,17 +171,32 @@ export default class Marketplace extends React.PureComponent {
return errorMessage(<h1>{window.language.modals.main.loading}</h1>);
}
const featured = () => {
const openFeatured = () => {
window.stats.postEvent('marketplace', 'Featured clicked');
window.open(this.state.featured.buttonLink);
}
return (
<div className='featured' style={{ backgroundColor: this.state.featured.colour }}>
<p>{this.state.featured.title}</p>
<h1>{this.state.featured.name}</h1>
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
</div>
);
}
if (this.state.items.length === 0) {
return (
<>
{featured()}
{errorMessage(<>
<LocalMallIcon/>
<LocalMall/>
<h1>{window.language.modals.main.addons.empty.title}</h1>
<p className='description'>{this.language.no_items}</p>
</>)}
</>
)
);
}
if (this.state.item.display_name) {

View File

@@ -1,14 +1,13 @@
import LocalMallIcon from '@material-ui/icons/LocalMall';
import { LocalMall } from '@material-ui/icons';
import { toast } from 'react-toastify';
import FileUpload from '../../settings/FileUpload';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { toast } from 'react-toastify';
import { install } from '../../../../../modules/helpers/marketplace';
export default function Sideload() {
const install = (input) => {
MarketplaceFunctions.install(input.type, input);
const installAddon = (input) => {
install(input.type, input);
toast(window.language.toasts.installed);
window.stats.postEvent('marketplace', 'Sideload');
};
@@ -16,8 +15,8 @@ export default function Sideload() {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => install(JSON.parse(e.target.result))} />
<LocalMallIcon/>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => installAddon(JSON.parse(e.target.result))} />
<LocalMall/>
<h1>{window.language.modals.main.addons.sideload}</h1>
<button className='addToMue sideload' onClick={() => document.getElementById('file-input').click()}>{window.language.modals.main.settings.sections.background.source.upload}</button>
</div>

View File

@@ -89,14 +89,11 @@
}
.ReactModal__Content {
//min-height: calc(100vh - 30vh);
//max-height: calc(100vh - 10vh);
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
overflow-y: auto;
position: relative;
// animation
opacity: 0;
transform: scale(0);
transition: all 300ms cubic-bezier(0.47, 1.64, 0.41, 0.8);
}
@@ -149,6 +146,24 @@ ul.sidebar {
}
}
@media (max-height: 999px) and (min-height: 920px) {
ul.sidebar {
min-height: 160vh;
}
}
@media (max-height: 919px) and (min-height: 700px) {
ul.sidebar {
min-height: 200vh;
}
}
@media (max-height: 699px) and (min-height: 400px) {
ul.sidebar {
min-height: 260vh;
}
}
li {
list-style: none;
font-size: 24px;
@@ -165,17 +180,12 @@ li {
bottom: 0;
left: 0;
height: 80%;
width: 60%;
}
@media only screen and (max-width: 1300px) {
@media (max-width: 1700px) {
#modal {
width: 90% !important;
}
}
@media only screen and (min-width: 1310px) {
#modal {
width: 60%;
width: 80% !important;
}
}
@@ -252,7 +262,7 @@ li {
display: inline-flex;
&:hover {
color: rgb(165, 165, 165);
color: grey;
background: none;
}
@@ -291,7 +301,7 @@ li {
}
}
@media only screen and (max-width: 1650px) {
@media only screen and (max-width: 800px) {
li.navbar-item {
span {
display: none;

View File

@@ -16,7 +16,7 @@
.items {
display: inline-grid;
grid-template-columns: repeat(6, 1fr);
grid-template-columns: repeat(4, 1fr);
margin-top: 15px;
.item {
@@ -71,31 +71,21 @@
}
}
@media only screen and (max-width: 2100px) {
@media (max-width: 1920px) {
.items {
grid-template-columns: repeat(3, 1fr);
}
}
@media only screen and (max-width: 1870px) {
@media (max-width: 1680px) and (min-width: 1500px) {
.items {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (min-width: 1079px), (max-width: 1869px) {
@media (max-width: 1440px) {
.items {
grid-template-columns: repeat(3, 1fr);
}
}
@media only screen and (max-width: 1680px) {
.side {
float: none !important;
}
.sidebr {
display: none;
grid-template-columns: repeat(1, 1fr);
}
}

View File

@@ -34,4 +34,4 @@ select {
.dark select {
background: url("data:image/svg+xml;utf8,<svg fill='white' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>") right center no-repeat, var(--sidebar) !important;
}
}
}

View File

@@ -17,11 +17,11 @@ input {
-webkit-appearance: none;
vertical-align: middle;
background: none;
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border: none;
border-radius: 100%;
@@ -37,11 +37,11 @@ input {
-moz-appearance: none;
vertical-align: middle;
background: none;
&::-moz-color-swatch-wrapper {
padding: 0;
}
&::-moz-color-swatch {
border: none;
border-radius: 100%;
@@ -106,11 +106,6 @@ ul {
}
}
.newFeature {
color: #ff4757;
font-size: 12px;
}
.settingsTextarea {
font-family: Consolas !important;
padding: 15px;
@@ -181,7 +176,7 @@ legend {
li {
cursor: initial;
font-size: 1rem;
list-style-type:disc;
list-style-type: disc;
padding: 0;
margin-left: 20px;
}
@@ -228,9 +223,13 @@ input[type=number] {
h2 {
font-size: 2rem !important;
}
h2, span, svg {
h2,
span,
svg {
display: inline;
}
svg {
vertical-align: sub;
font-size: 1.4rem;

View File

@@ -1,11 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { Checkbox as CheckboxUI, FormControlLabel } from '@material-ui/core';
import EventBus from '../../../../modules/helpers/eventbus';
import CheckboxUI from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
export default class Checkbox extends React.PureComponent {
export default class Checkbox extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -34,19 +32,11 @@ export default class Checkbox extends React.PureComponent {
}
render() {
let text = this.props.text;
if (this.props.newFeature) {
text = <>{this.props.text} <span className='newFeature'> NEW</span></>;
} else if (this.props.betaFeature) {
text = <>{this.props.text} <span className='newFeature'> BETA</span></>;
}
return (
<>
<FormControlLabel
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} />}
label={text}
label={this.props.text}
/>
<br/>
</>

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { PureComponent } from 'react';
import EventBus from '../../../../modules/helpers/eventbus';
export default class Dropdown extends React.PureComponent {
export default class Dropdown extends PureComponent {
constructor(props) {
super(props);
this.state = {

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
export default class FileUpload extends React.PureComponent {
export default class FileUpload extends PureComponent {
componentDidMount() {
document.getElementById(this.props.id).onchange = (e) => {
const reader = new FileReader();

View File

@@ -1,14 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { Radio as RadioUI, RadioGroup, FormControlLabel, FormControl, FormLabel } from '@material-ui/core';
import EventBus from '../../../../modules/helpers/eventbus';
import RadioUI from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
export default class Radio extends React.PureComponent {
export default class Radio extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -23,6 +18,13 @@ export default class Radio extends React.PureComponent {
return;
}
if (this.props.name === 'language') {
// old tab name
if (localStorage.getItem('tabName') === window.language.tabname) {
localStorage.setItem('tabName', require(`../../../../translations/${value.replace('-', '_')}.json`).tabname);
}
}
localStorage.setItem(this.props.name, value);
this.setState({

View File

@@ -1,14 +1,12 @@
import CloseIcon from '@material-ui/icons/Close';
import DeleteIcon from '@material-ui/icons/Delete';
import SettingsFunctions from '../../../../modules/helpers/settings';
import { Close, Delete } from '@material-ui/icons';
import { setDefaultSettings } from '../../../../modules/helpers/settings';
export default function ResetModal(props) {
const language = window.language.modals.main.settings.sections.advanced.reset_modal;
const reset = () => {
window.stats.postEvent('setting', 'Reset');
SettingsFunctions.setDefaultSettings('reset');
setDefaultSettings('reset');
window.location.reload();
};
@@ -20,10 +18,10 @@ export default function ResetModal(props) {
<span>{language.information}</span>
<div className='resetfooter'>
<button className='round reset' style={{ marginLeft: 0 }} onClick={() => reset()}>
<DeleteIcon/>
<Delete/>
</button>
<button className='round import' style={{ marginLeft: '5px' }} onClick={props.modalClose}>
<CloseIcon/>
<Close/>
</button>
</div>
</>

View File

@@ -1,11 +1,10 @@
// todo: find a better method to do width of number input
import React from 'react';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import EventBus from '../../../../modules/helpers/eventbus';
import { toast } from 'react-toastify';
export default class Slider extends React.PureComponent {
export default class Slider extends PureComponent {
constructor(props) {
super(props);
this.state = {

View File

@@ -1,11 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { Switch as SwitchUI, FormControlLabel } from '@material-ui/core';
import EventBus from '../../../../modules/helpers/eventbus';
import SwitchUI from '@material-ui/core/Switch';
import FormControlLabel from '@material-ui/core/FormControlLabel';
export default class Switch extends React.PureComponent {
export default class Switch extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -34,19 +32,11 @@ export default class Switch extends React.PureComponent {
}
render() {
let text = this.props.text;
if (this.props.newFeature) {
text = <>{this.props.text} <span className='newFeature'> NEW</span></>;
} else if (this.props.betaFeature) {
text = <>{this.props.text} <span className='newFeature'> BETA</span></>;
}
return (
<>
<FormControlLabel
control={<SwitchUI name={this.props.name} color='primary' checked={this.state.checked} onChange={this.handleChange} />}
label={text}
label={this.props.text}
labelPlacement='start'
/>
<br/>

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import EventBus from '../../../../modules/helpers/eventbus';
import { toast } from 'react-toastify';
export default class Text extends React.PureComponent {
export default class Text extends PureComponent {
constructor(props) {
super(props);
this.state = {

View File

@@ -1,15 +1,11 @@
import React from 'react';
import { PureComponent } from 'react';
import { Email, Twitter, Chat, Instagram, Facebook } from '@material-ui/icons';
import Tooltip from '../../../../helpers/tooltip/Tooltip';
import EmailIcon from '@material-ui/icons/Email';
import TwitterIcon from '@material-ui/icons/Twitter';
import ChatIcon from '@material-ui/icons/Chat';
import InstagramIcon from '@material-ui/icons/Instagram';
import FacebookIcon from '@material-ui/icons/Facebook';
const other_contributors = require('../../../../../modules/other_contributors.json');
export default class About extends React.PureComponent {
export default class About extends PureComponent {
constructor() {
super();
this.state = {
@@ -94,11 +90,11 @@ export default class About extends React.PureComponent {
<a href={window.constants.PRIVACY_URL} className='aboutLink' target='_blank' rel='noopener noreferrer' style={{ fontSize: '1rem' }}>{window.language.modals.welcome.sections.privacy.links.privacy_policy}</a>
<h3>{this.language.contact_us}</h3>
<a href={'mailto:' + window.constants.EMAIL} className='aboutIcon' target='_blank' rel='noopener noreferrer'><EmailIcon/></a>
<a href={'https://twitter.com/' + window.constants.TWITTER_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><TwitterIcon/></a>
<a href={'https://instagram.com/' + window.constants.INSTAGRAM_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><InstagramIcon/></a>
<a href={'https://facebook.com/' + window.constants.FACEBOOK_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><FacebookIcon/></a>
<a href={'https://discord.gg/' + window.constants.DISCORD_SERVER} className='aboutIcon' target='_blank' rel='noopener noreferrer'><ChatIcon/></a>
<a href={'mailto:' + window.constants.EMAIL} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Email/></a>
<a href={'https://twitter.com/' + window.constants.TWITTER_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Twitter/></a>
<a href={'https://instagram.com/' + window.constants.INSTAGRAM_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Instagram/></a>
<a href={'https://facebook.com/' + window.constants.FACEBOOK_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Facebook/></a>
<a href={'https://discord.gg/' + window.constants.DISCORD_SERVER} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Chat/></a>
<h3>{this.language.support_mue}</h3>
<p>

View File

@@ -1,4 +1,7 @@
import React from 'react';
import { PureComponent } from 'react';
import Modal from 'react-modal';
import { exportSettings, importSettings } from '../../../../../modules/helpers/settings/modals';
import Checkbox from '../Checkbox';
import FileUpload from '../FileUpload';
@@ -7,13 +10,9 @@ import Switch from '../Switch';
import ResetModal from '../ResetModal';
import Dropdown from '../Dropdown';
import SettingsFunctions from '../../../../../modules/helpers/settings/modals';
import Modal from 'react-modal';
const time_zones = require('../../../../widgets/time/timezones.json');
export default class AdvancedSettings extends React.PureComponent {
export default class AdvancedSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -38,9 +37,9 @@ export default class AdvancedSettings extends React.PureComponent {
<h3>{advanced.data}</h3>
<button className='reset' onClick={() => this.setState({ resetModal: true })}>{this.language.buttons.reset}</button>
<button className='export' onClick={() => SettingsFunctions.exportSettings()}>{this.language.buttons.export}</button>
<button className='export' onClick={() => exportSettings()}>{this.language.buttons.export}</button>
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.language.buttons.import}</button>
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => SettingsFunctions.importSettings(e)}/>
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => importSettings(e)}/>
<h3>{advanced.customisation}</h3>
<Text title={advanced.tab_name} name='tabName' default={window.language.tabname} category='other'/>

View File

@@ -30,6 +30,7 @@ export default function AppearanceSettings() {
<h3>{appearance.navbar.title}</h3>
<Checkbox name='notesEnabled' text={appearance.navbar.notes} category='navbar' />
<Checkbox name='refresh' text={appearance.navbar.refresh} category='navbar' />
<Slider title={appearance.accessibility.widget_zoom} name='zoomNavbar' min='10' max='400' default='100' display='%' category='navbar' />
<h3>{appearance.font.title}</h3>
<Text title={appearance.font.custom} name='font' upperCaseFirst={true} category='other' />
@@ -54,6 +55,7 @@ export default function AppearanceSettings() {
</Dropdown>
<h3>{appearance.accessibility.title}</h3>
{/* not supported on firefox */}
{(navigator.userAgent.includes('Chrome') && typeof InstallTrigger === 'undefined') ?
<Slider title={appearance.accessibility.widget_zoom} name='widgetzoom' default='100' step='10' min='50' max='200' display='%' category='other'/>
: null}

View File

@@ -1,12 +1,10 @@
import React from 'react';
import { PureComponent } from 'react';
import { WifiOff } from '@material-ui/icons';
import Modal from 'react-modal';
import Lightbox from '../../marketplace/Lightbox';
import WifiOffIcon from '@material-ui/icons/WifiOff';
export default class Changelog extends React.PureComponent {
export default class Changelog extends PureComponent {
constructor() {
super();
this.state = {
@@ -89,7 +87,7 @@ export default class Changelog extends React.PureComponent {
const language = window.language.modals.main.marketplace;
return errorMessage(<>
<WifiOffIcon/>
<WifiOff/>
<h1>{language.offline.title}</h1>
<p className='description'>{language.offline.description}</p>
</>);

View File

@@ -1,5 +1,6 @@
import Checkbox from '../Checkbox';
import Slider from '../Slider';
import EventBus from '../../../../../modules/helpers/eventbus';
export default function ExperimentalSettings() {
@@ -10,9 +11,6 @@ export default function ExperimentalSettings() {
<h2>{experimental.title}</h2>
<p>{experimental.warning}</p>
<Checkbox name='animations' text={window.language.modals.main.settings.sections.appearance.animations} element='.other'/>
<h3>Usage Stats</h3>
<p>Allows you to see stats such as how many tabs you have opened, quotes favourited etc. It also sends this data anonymously to our<a className='modalLink' href='https://github.com/mue/umami'>umami</a> instance.</p>
<Checkbox name='stats' text='Enable Usage Stats' element='.other'/>
<h3>{experimental.developer}</h3>
<Checkbox name='debug' text='Debug hotkey (Ctrl + #)' element='.other'/>
<Slider title='Debug timeout' name='debugtimeout' min='0' max='5000' default='0' step='100' display=' miliseconds' element='.other' />

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { PureComponent } from 'react';
import Checkbox from '../Checkbox';
import Switch from '../Switch';
import Text from '../Text';
import Slider from '../Slider';
export default class GreetingSettings extends React.PureComponent {
export default class GreetingSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -29,14 +29,14 @@ export default class GreetingSettings extends React.PureComponent {
<>
<h2>{greeting.title}</h2>
<Switch name='greeting' text={this.language.enabled} category='greeting' element='.greeting'/>
<Checkbox name='events' text={greeting.events} category='greeting' element='.greeting'/>
<Checkbox name='defaultGreetingMessage' text={greeting.default} category='greeting' element='.greeting'/>
<Text title={greeting.name} name='greetingName' category='greeting' element='.greeting'/>
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomGreeting' min='10' max='400' default='100' display='%' category='greeting' element='.greeting' />
<Checkbox name='events' text={greeting.events} category='greeting'/>
<Checkbox name='defaultGreetingMessage' text={greeting.default} category='greeting'/>
<Text title={greeting.name} name='greetingName' category='greeting'/>
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomGreeting' min='10' max='400' default='100' display='%' category='greeting' />
<h3>{greeting.birthday}</h3>
<Switch name='birthdayenabled' text={this.language.enabled} category='greeting' element='.greeting'/>
<Checkbox name='birthdayage' text={greeting.birthday_age} category='greeting' element='.greeting'/>
<Switch name='birthdayenabled' text={this.language.enabled} category='greeting'/>
<Checkbox name='birthdayage' text={greeting.birthday_age} category='greeting'/>
<p>{greeting.birthday_date}</p>
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)}/>
</>

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { PureComponent } from 'react';
import Radio from '../Radio';
const languages = require('../../../../../modules/languages.json');
export default class BackgroundSettings extends React.PureComponent {
export default class BackgroundSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -60,7 +60,7 @@ export default class BackgroundSettings extends React.PureComponent {
return (
<>
<h2>{language.title}</h2>
<Radio name='language' options={languages} element='.language' />
<Radio name='language' options={languages} element='.other' />
<h3>{language.quote}</h3>
<Radio name='quotelanguage' options={this.state.quoteLanguages} category='quote' />
</>

View File

@@ -1,20 +1,9 @@
import React from 'react';
import EventBus from '../../../../../modules/helpers/eventbus';
import DragHandleIcon from '@material-ui/icons/DragIndicator';
import { PureComponent } from 'react';
import { DragIndicator } from '@material-ui/icons';
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
import { toast } from 'react-toastify';
const enabled = (setting) => {
switch (setting) {
case 'quicklinks':
return (localStorage.getItem('quicklinksenabled') === 'true');
default:
return (localStorage.getItem(setting) === 'true');
}
};
import EventBus from '../../../../../modules/helpers/eventbus';
const settings = window.language.modals.main.settings.sections;
const widget_name = {
@@ -26,8 +15,8 @@ const widget_name = {
};
const SortableItem = sortableElement(({ value }) => (
<li className='sortableitem' style={{ display: enabled(value) ? 'block' : 'none' }}>
<DragHandleIcon style={{ verticalAlign: 'middle' }} />
<li className='sortableitem'>
<DragIndicator style={{ verticalAlign: 'middle' }} />
{widget_name[value]}
</li>
));
@@ -36,7 +25,7 @@ const SortableContainer = sortableContainer(({ children }) => {
return <ul className='sortablecontainer'>{children}</ul>;
});
export default class OrderSettings extends React.PureComponent {
export default class OrderSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -79,6 +68,15 @@ export default class OrderSettings extends React.PureComponent {
toast(window.language.toasts.reset);
}
enabled = (setting) => {
switch (setting) {
case 'quicklinks':
return (localStorage.getItem('quicklinksenabled') === 'true');
default:
return (localStorage.getItem(setting) === 'true');
}
}
componentDidUpdate() {
localStorage.setItem('order', JSON.stringify(this.state.items));
window.stats.postEvent('setting', 'Widget order');
@@ -91,9 +89,15 @@ export default class OrderSettings extends React.PureComponent {
<h2>{this.language.sections.order.title}</h2>
<span className='modalLink' onClick={this.reset}>{this.language.buttons.reset}</span>
<SortableContainer onSortEnd={this.onSortEnd} lockAxis='y' lockToContainerEdges disableAutoscroll>
{this.state.items.map((value, index) => (
<SortableItem key={`item-${value}`} index={index} value={value} />
))}
{this.state.items.map((value, index) => {
if (!this.enabled(value)) {
return null;
}
return (
<SortableItem key={`item-${value}`} index={index} value={value} />
);
})}
</SortableContainer>
</>
);

View File

@@ -8,11 +8,11 @@ export default function QuickLinks() {
return (
<>
<h2>{language.title}</h2>
<Switch name='quicklinksenabled' text={window.language.modals.main.settings.enabled} category='quicklinks' element='.quicklinks-container' />
<Checkbox name='quicklinksddgProxy' text={window.language.modals.main.settings.sections.background.ddg_image_proxy} element='.other' />
<Checkbox name='quicklinksnewtab' text={language.open_new} category='quicklinks' />
<Checkbox name='quicklinkstooltip' text={language.tooltip} category='quicklinks' />
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuicklinks' min='10' max='400' default='100' display='%' category='quicklinks' element='.quicklinks-container' />
<Switch name='quicklinksenabled' text={window.language.modals.main.settings.enabled} category='quicklinks' element='.quicklinks-container'/>
<Checkbox name='quicklinksddgProxy' text={window.language.modals.main.settings.sections.background.ddg_image_proxy} category='quicklinks'/>
<Checkbox name='quicklinksnewtab' text={language.open_new} category='quicklinks'/>
<Checkbox name='quicklinkstooltip' text={language.tooltip} category='quicklinks'/>
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuicklinks' min='10' max='400' default='100' display='%' category='quicklinks'/>
</>
);
}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import { PureComponent } from 'react';
import Checkbox from '../Checkbox';
import Text from '../Text';
@@ -6,7 +6,7 @@ import Switch from '../Switch';
import Slider from '../Slider';
import Dropdown from '../Dropdown';
export default class QuoteSettings extends React.PureComponent {
export default class QuoteSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -27,8 +27,8 @@ export default class QuoteSettings extends React.PureComponent {
if (this.state.quoteType === 'custom') {
customSettings = (
<>
<Text title={quote.custom} name='customQuote' category='quote' element='.quotediv' />
<Text title={quote.custom_author} name='customQuoteAuthor' category='quote' element='.quotediv'/>
<Text title={quote.custom} name='customQuote' category='quote' />
<Text title={quote.custom_author} name='customQuoteAuthor' category='quote'/>
</>
);
} else {
@@ -60,7 +60,7 @@ export default class QuoteSettings extends React.PureComponent {
<option value='custom'>{quote.custom}</option>
</Dropdown>
{customSettings}
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuote' min='10' max='400' default='100' display='%' category='quote' element='.quotediv' />
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuote' min='10' max='400' default='100' display='%' category='quote' />
<h3>{quote.buttons.title}</h3>
<Checkbox name='copyButton' text={quote.buttons.copy} category='quote'/>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import Dropdown from '../Dropdown';
import Checkbox from '../Checkbox';
@@ -7,12 +8,10 @@ import Radio from '../Radio';
import EventBus from '../../../../../modules/helpers/eventbus';
import { toast } from 'react-toastify';
const searchEngines = require('../../../../widgets/search/search_engines.json');
const autocompleteProviders = require('../../../../widgets/search/autocomplete_providers.json');
export default class SearchSettings extends React.PureComponent {
export default class SearchSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -90,7 +89,7 @@ export default class SearchSettings extends React.PureComponent {
<input type='text' value={this.state.customValue} onInput={(e) => this.setState({ customValue: e.target.value })}></input>
</ul>
<br/>
<Checkbox name='autocomplete' text={search.autocomplete} category='search' element='.other'/>
<Checkbox name='autocomplete' text={search.autocomplete} category='search' />
<Radio title={search.autocomplete_provider} options={autocompleteProviders} name='autocompleteProvider' category='search'/>
</>
);

View File

@@ -0,0 +1,59 @@
import { PureComponent } from 'react';
import Switch from '../Switch';
import EventBus from '../../../../../modules/helpers/eventbus';
export default class Stats extends PureComponent {
constructor() {
super();
this.state = {
stats: JSON.parse(localStorage.getItem('statsData')) || {}
};
this.language = window.language.modals.main.settings.sections.stats;
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'stats') {
if (localStorage.getItem('stats') === 'false') {
localStorage.setItem('statsData', JSON.stringify({}));
return this.setState({
stats: {}
});
}
this.forceUpdate();
}
});
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
if (localStorage.getItem('stats') === 'false') {
return (
<>
<h2>{window.language.modals.main.settings.reminder.title}</h2>
<p>{this.language.warning}</p>
<Switch name='stats' text={this.language.usage} category='stats'/>
</>
);
}
return (
<>
<h2>{this.language.title}</h2>
<p>{this.language.sections.tabs_opened}: {this.state.stats['tabs-opened'] || 0}</p>
<p>{this.language.sections.backgrounds_favourited}: {this.state.stats.feature ? this.state.stats.feature['background-favourite'] || 0 : 0}</p>
<p>{this.language.sections.backgrounds_downloaded}: {this.state.stats.feature ? this.state.stats.feature['background-download'] || 0 : 0}</p>
<p>{this.language.sections.quotes_favourited}: {this.state.stats.feature ? this.state.stats.feature['quoted-favourite'] || 0 : 0}</p>
<p>{this.language.sections.quicklinks_added}: {this.state.stats.feature ? this.state.stats.feature['quicklink-add'] || 0 : 0}</p>
<p>{this.language.sections.settings_changed}: {this.state.stats.setting ? Object.keys(this.state.stats.setting).length : 0}</p>
<p>{this.language.sections.addons_installed}: {this.state.stats.marketplace ? this.state.stats.marketplace['install'] : 0}</p>
<Switch name='stats' text={this.language.usage} category='stats'/>
</>
);
}
}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import { PureComponent } from 'react';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
@@ -6,7 +6,7 @@ import Switch from '../Switch';
import Radio from '../Radio';
import Slider from '../Slider';
export default class TimeSettings extends React.PureComponent {
export default class TimeSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -35,21 +35,21 @@ export default class TimeSettings extends React.PureComponent {
const digitalSettings = (
<>
<h3>{time.digital.title}</h3>
<Radio title={time.format} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' element='.clock-container' />
<Radio title={time.format} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' />
<br/>
<Checkbox name='seconds' text={time.digital.seconds} category='clock' element='.clock-container' />
<Checkbox name='zero' text={time.digital.zero} category='clock' element='.clock-container' />
<Checkbox name='seconds' text={time.digital.seconds} category='clock' />
<Checkbox name='zero' text={time.digital.zero} category='clock' />
</>
);
const analogSettings = (
<>
<h3>{time.analogue.title}</h3>
<Checkbox name='secondHand' text={time.analogue.second_hand} category='clock' element='.clock-container' />
<Checkbox name='minuteHand' text={time.analogue.minute_hand} category='clock' element='.clock-container' />
<Checkbox name='hourHand' text={time.analogue.hour_hand} category='clock' element='.clock-container' />
<Checkbox name='hourMarks' text={time.analogue.hour_marks} category='clock' element='.clock-container' />
<Checkbox name='minuteMarks' text={time.analogue.minute_marks} category='clock' element='.clock-container' />
<Checkbox name='secondHand' text={time.analogue.second_hand} category='clock' />
<Checkbox name='minuteHand' text={time.analogue.minute_hand} category='clock' />
<Checkbox name='hourHand' text={time.analogue.hour_hand} category='clock' />
<Checkbox name='hourMarks' text={time.analogue.hour_marks} category='clock' />
<Checkbox name='minuteMarks' text={time.analogue.minute_marks} category='clock' />
</>
);
@@ -63,23 +63,23 @@ export default class TimeSettings extends React.PureComponent {
const longSettings = (
<>
<Checkbox name='dayofweek' text={time.date.day_of_week} category='date' element='.date' />
<Checkbox name='datenth' text={time.date.datenth} category='date' element='.date' />
<Checkbox name='dayofweek' text={time.date.day_of_week} category='date' />
<Checkbox name='datenth' text={time.date.datenth} category='date' />
</>
);
const shortSettings = (
<>
<br/>
<Dropdown label={time.date.short_format} name='dateFormat' category='date' element='.date'>
<Dropdown label={time.date.short_format} name='dateFormat' category='date'>
<option value='DMY'>DMY</option>
<option value='MDY'>MDY</option>
<option value='YMD'>YMD</option>
</Dropdown>
<br/><br/>
<Dropdown label={time.date.short_separator.title} name='shortFormat' category='date' element='.date'>
<option value='dots'>{time.date.short_separator.dots}</option>
<Dropdown label={time.date.short_separator.title} name='shortFormat' category='date'>
<option value='dash'>{time.date.short_separator.dash}</option>
<option value='dots'>{time.date.short_separator.dots}</option>
<option value='gaps'>{time.date.short_separator.gaps}</option>
<option value='slashes'>{time.date.short_separator.slashes}</option>
</Dropdown>
@@ -96,27 +96,27 @@ export default class TimeSettings extends React.PureComponent {
<>
<h2>{time.title}</h2>
<Switch name='time' text={this.language.enabled} category='clock' element='.clock-container' />
<Dropdown label={time.type} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock' element='.clock-container'>
<Dropdown label={time.type} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock'>
<option value='digital'>{time.digital.title}</option>
<option value='analogue'>{time.analogue.title}</option>
<option value='percentageComplete'>{time.percentage_complete}</option>
</Dropdown>
{timeSettings}
{this.state.timeType !== 'analogue' ?
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomClock' min='10' max='400' default='100' display='%' category='clock' element='.clock-container' />
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomClock' min='10' max='400' default='100' display='%' category='clock'/>
: null}
<h3>{time.date.title}</h3>
<Switch name='date' text={this.language.enabled} category='date' element='.date'/>
<Dropdown label={time.type} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date' element='.date'>
<Dropdown label={time.type} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date'>
<option value='long'>{time.date.type.long}</option>
<option value='short'>{time.date.type.short}</option>
</Dropdown>
<br/>
<Checkbox name='datezero' text={time.digital.zero} category='date' element='.date' />
<Checkbox name='weeknumber' text={time.date.week_number} category='date' element='.date'/>
<Checkbox name='datezero' text={time.digital.zero} category='date'/>
<Checkbox name='weeknumber' text={time.date.week_number} category='date'/>
{dateSettings}
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomDate' min='10' max='400' default='100' display='%' category='date' element='.date' />
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomDate' min='10' max='400' default='100' display='%' category='date'/>
</>
);
}

View File

@@ -1,11 +1,11 @@
import React from 'react';
import { PureComponent } from 'react';
import Switch from '../Switch';
import Radio from '../Radio';
import Checkbox from '../Checkbox';
import Slider from '../Slider';
export default class TimeSettings extends React.PureComponent {
export default class TimeSettings extends PureComponent {
constructor() {
super();
this.state = {

View File

@@ -1,6 +1,5 @@
import React from 'react';
import EventBus from '../../../../../../modules/helpers/eventbus';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import Checkbox from '../../Checkbox';
import Dropdown from '../../Dropdown';
@@ -11,9 +10,9 @@ import Radio from '../../Radio';
import ColourSettings from './Colour';
import { toast } from 'react-toastify';
import EventBus from '../../../../../../modules/helpers/eventbus';
export default class BackgroundSettings extends React.PureComponent {
export default class BackgroundSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -122,7 +121,7 @@ export default class BackgroundSettings extends React.PureComponent {
))}
</Dropdown>
<br/><br/>
<Dropdown label={background.source.quality.title} name='apiQuality' category='background' element='.other'>
<Dropdown label={background.source.quality.title} name='apiQuality' element='.other'>
<option value='original'>{background.source.quality.original}</option>
<option value='high'>{background.source.quality.high}</option>
<option value='normal'>{background.source.quality.normal}</option>
@@ -167,9 +166,9 @@ export default class BackgroundSettings extends React.PureComponent {
<>
<h2>{background.title}</h2>
<Switch name='background' text={this.language.enabled} category='background' element='#backgroundImage' />
<Checkbox name='ddgProxy' text={background.ddg_image_proxy} />
<Checkbox name='bgtransition' text={background.transition} />
<Checkbox name='photoInformation' text={background.photo_information} category='background' element='.other' />
<Checkbox name='ddgProxy' text={background.ddg_image_proxy} element='.other' />
<Checkbox name='bgtransition' text={background.transition} element='.other' />
<Checkbox name='photoInformation' text={background.photo_information} element='.other' />
<h3>{background.source.title}</h3>
<Dropdown label={background.type.title} name='backgroundType' onChange={(value) => this.setState({ backgroundType: value })} category='background'>

View File

@@ -1,16 +1,14 @@
import React from 'react';
import { PureComponent, Fragment } from 'react';
import { ColorPicker } from 'react-color-gradient-picker';
import { toast } from 'react-toastify';
import hexToRgb from '../../../../../../modules/helpers/background/hexToRgb';
import rgbToHex from '../../../../../../modules/helpers/background/rgbToHex';
import { toast } from 'react-toastify';
import 'react-color-gradient-picker/dist/index.css';
import '../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss';
export default class ColourSettings extends React.PureComponent {
export default class ColourSettings extends PureComponent {
DefaultGradientSettings = { angle: '180', gradient: [{ colour: '#ffb032', stop: 0 }], type: 'linear' };
GradientPickerInitalState = undefined;
@@ -166,10 +164,10 @@ export default class ColourSettings extends React.PureComponent {
} else {
gradientInputs = this.state.gradientSettings.gradient.map((g, i) => {
return (
<React.Fragment key={i}>
<Fragment key={i}>
<input id={'colour_' + i} type='color' name='colour' className='colour' onChange={(event) => this.onGradientChange(event, i)} value={g.colour}></input>
<label htmlFor={'colour_' + i} className='customBackgroundHex'>{g.colour}</label>
</React.Fragment>
</Fragment>
);
});
}

View File

@@ -1,7 +1,8 @@
import Tabs from './backend/Tabs';
import Added from '../marketplace/sections/Added';
import Sideload from '../marketplace/sections/Sideload';
import Tabs from './backend/Tabs';
import Create from '../marketplace/sections/Create';
export default function Addons() {
const addons = window.language.modals.main.addons;
@@ -10,6 +11,7 @@ export default function Addons() {
<Tabs>
<div label={addons.added} name='added'><Added/></div>
<div label={addons.sideload} name='sideload'><Sideload/></div>
<div label={addons.create.title} name='create'><Create/></div>
</Tabs>
);
}

View File

@@ -1,7 +1,7 @@
import MarketplaceTab from '../marketplace/sections/Marketplace';
import Tabs from './backend/Tabs';
import MarketplaceTab from '../marketplace/sections/Marketplace';
export default function Marketplace() {
const marketplace = window.language.modals.main.marketplace;

View File

@@ -1,3 +1,5 @@
import Tabs from './backend/Tabs';
import About from '../settings/sections/About';
import Language from '../settings/sections/Language';
import Search from '../settings/sections/Search';
@@ -12,16 +14,11 @@ import Order from '../settings/sections/Order';
import Experimental from '../settings/sections/Experimental';
import QuickLinks from '../settings/sections/QuickLinks';
import Weather from '../settings/sections/Weather';
import Tabs from './backend/Tabs';
import Stats from '../settings/sections/Stats';
export default function Settings() {
const { reminder, sections } = window.language.modals.main.settings;
let display = 'none';
if (localStorage.getItem('showReminder') === 'true') {
display = 'block';
}
const display = (localStorage.getItem('showReminder') === 'true') ? 'block' : 'none';
return (
<>
@@ -37,6 +34,7 @@ export default function Settings() {
<div label={sections.order.title} name='order'><Order/></div>
<div label={sections.language.title} name='language'><Language/></div>
<div label={sections.advanced.title} name='advanced'><Advanced/></div>
<div label={sections.stats.title} name='stats'><Stats/></div>
<div label={sections.experimental.title} name='experimental'><Experimental/></div>
<div label={sections.changelog} name='changelog'><Changelog/></div>
<div label={sections.about.title} name='about'><About/></div>

View File

@@ -1,29 +1,27 @@
import React from 'react';
// Navbar
import Settings from '@material-ui/icons/SettingsRounded';
import Addons from '@material-ui/icons/Widgets';
import Marketplace from '@material-ui/icons/ShoppingBasket';
// Settings
import Time from '@material-ui/icons/AccessAlarm';
import Greeting from '@material-ui/icons/EmojiPeopleOutlined';
import Quote from '@material-ui/icons/FormatQuoteOutlined';
import Background from '@material-ui/icons/PhotoOutlined';
import Search from '@material-ui/icons/Search';
import Appearance from '@material-ui/icons/FormatPaintOutlined';
import Language from '@material-ui/icons/Translate';
import Changelog from '@material-ui/icons/NewReleasesOutlined';
import About from '@material-ui/icons/InfoOutlined';
import Experimental from '@material-ui/icons/BugReportOutlined';
import Order from '@material-ui/icons/List';
import Weather from '@material-ui/icons/CloudOutlined';
import Advanced from '@material-ui/icons/SettingsOutlined';
import QuickLinks from '@material-ui/icons/Link';
// Addons
import Sideload from '@material-ui/icons/Code';
import Added from '@material-ui/icons/AddCircleOutline';
import { memo } from 'react';
import {
SettingsRounded as Settings,
Widgets as Addons,
ShoppingBasket as Marketplace,
AccessAlarm as Time,
EmojiPeopleOutlined as Greeting,
FormatQuoteOutlined as Quote,
PhotoOutlined as Background,
Search,
FormatPaintOutlined as Appearance,
Translate as Language,
NewReleasesOutlined as Changelog,
InfoOutlined as About,
BugReportOutlined as Experimental,
List as Order,
CloudOutlined as Weather,
SettingsOutlined as Advanced,
Link as QuickLinks,
AssessmentOutlined as Stats,
Code as Sideload,
AddCircleOutline as Added,
CreateNewFolderOutlined as Create
} from '@material-ui/icons';
function Tab(props) {
let className = 'tab-list-item';
@@ -60,6 +58,7 @@ function Tab(props) {
case settings.order.title: icon = <Order/>; break;
case settings.language.title: icon = <Language/>; divider = true; break;
case settings.advanced.title: icon = <Advanced/>; break;
case settings.stats.title: icon = <Stats/>; break;
case settings.experimental.title: icon = <Experimental/>; divider = true; break;
case settings.changelog: icon = <Changelog/>; break;
case settings.about.title: icon = <About/>; break;
@@ -67,6 +66,7 @@ function Tab(props) {
// Addons
case addons.added: icon = <Added/>; break;
case addons.sideload: icon = <Sideload/>; break;
case addons.create.title: icon = <Create/>; break;
// Marketplace
case marketplace.photo_packs: icon = <Background/>; break;
@@ -92,4 +92,4 @@ function Tab(props) {
);
}
export default React.memo(Tab);
export default memo(Tab);

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import Tab from './Tab';
import ErrorBoundary from '../../../ErrorBoundary';
export default class Tabs extends React.PureComponent {
export default class Tabs extends PureComponent {
constructor(props) {
super(props);
@@ -15,7 +15,7 @@ export default class Tabs extends React.PureComponent {
onClick = (tab, name) => {
if (name !== this.state.currentName) {
window.stats.postEvent('tab', `Changed ${this.state.currentName} to ${name}`);
window.stats.postEvent('tab', `Opened ${name}`);
}
this.setState({

View File

@@ -1,4 +1,4 @@
import React from 'react';
import { PureComponent } from 'react';
import EventBus from '../../../modules/helpers/eventbus';
@@ -7,7 +7,7 @@ import ProgressBar from './ProgressBar';
import './welcome.scss';
export default class WelcomeModal extends React.PureComponent {
export default class WelcomeModal extends PureComponent {
constructor() {
super();
this.state = {
@@ -81,6 +81,10 @@ export default class WelcomeModal extends React.PureComponent {
});
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
return (
<div className='welcomeContent'>

View File

@@ -1,21 +1,17 @@
import React from 'react';
import { PureComponent } from 'react';
import { CloudUpload, AutoAwesome, LightMode, DarkMode } from '@material-ui/icons';
import Radio from '../main/settings/Radio';
import Checkbox from '../main/settings/Checkbox';
import FileUpload from '../main/settings/FileUpload';
import UploadIcon from '@material-ui/icons/CloudUpload';
import AutoIcon from '@material-ui/icons/AutoAwesome';
import LightModeIcon from '@material-ui/icons/LightMode';
import DarkModeIcon from '@material-ui/icons/DarkMode';
import SettingsFunctions from '../../../modules/helpers/settings';
import SettingsFunctionsModal from '../../../modules/helpers/settings/modals';
import { loadSettings } from '../../../modules/helpers/settings';
import { importSettings } from '../../../modules/helpers/settings/modals';
const languages = require('../../../modules/languages.json');
const default_settings = require('../../../modules/default_settings.json');
export default class WelcomeSections extends React.PureComponent {
export default class WelcomeSections extends PureComponent {
constructor() {
super();
this.state = {
@@ -40,7 +36,7 @@ export default class WelcomeSections extends React.PureComponent {
});
localStorage.setItem('theme', type);
SettingsFunctions.loadSettings(true);
loadSettings(true);
}
getSetting(name) {
@@ -49,11 +45,12 @@ export default class WelcomeSections extends React.PureComponent {
}
importSettings(e) {
SettingsFunctionsModal.importSettings(e);
importSettings(e);
let settings = [];
const data = JSON.parse(e.target.result);
Object.keys(data).forEach((setting) => {
// language and theme already shown, the others are only used internally
if (setting === 'language' || setting === 'theme'|| setting === 'firstRun' || setting === 'showWelcome' || setting === 'showReminder') {
return;
}
@@ -124,12 +121,12 @@ export default class WelcomeSections extends React.PureComponent {
const chooseLanguage = (
<>
<h1>{language.sections.language.title}</h1>
<p>{language.sections.language.description}</p>
<p>{language.sections.language.description} <a href={window.constants.TRANSLATIONS_URL} className='resetLink' target='_blank' rel='noopener noreferrer'>GitHub</a>!</p>
<Radio name='language' options={languages} category='welcomeLanguage'/>
</>
);
const { appearance, advanced, background, quicklinks } = window.language.modals.main.settings.sections;
const { appearance, advanced, background, quicklinks, stats } = window.language.modals.main.settings.sections;
const languageSettings = window.language.modals.main.settings.sections.language;
const theme = (
@@ -138,16 +135,16 @@ export default class WelcomeSections extends React.PureComponent {
<p>{language.sections.theme.description}</p>
<div className='themesToggleArea'>
<div className={this.state.autoClass} onClick={() => this.changeTheme('auto')}>
<AutoIcon/>
<AutoAwesome/>
<span>{appearance.theme.auto}</span>
</div>
<div className='options'>
<div className={this.state.lightClass} onClick={() => this.changeTheme('light')}>
<LightModeIcon/>
<LightMode/>
<span>{appearance.theme.light}</span>
</div>
<div className={this.state.darkClass} onClick={() => this.changeTheme('dark')}>
<DarkModeIcon/>
<DarkMode/>
<span>{appearance.theme.dark}</span>
</div>
</div>
@@ -162,7 +159,7 @@ export default class WelcomeSections extends React.PureComponent {
<h1>{language.sections.settings.title}</h1>
<p>{language.sections.settings.description}</p>
<button className='upload' onClick={() => document.getElementById('file-input').click()}>
<UploadIcon/>
<CloudUpload/>
<br/>
<span>{window.language.modals.main.settings.buttons.import}</span>
</button>
@@ -181,6 +178,8 @@ export default class WelcomeSections extends React.PureComponent {
<Checkbox name='quicklinksddgProxy' text={background.ddg_image_proxy + ' (' + quicklinks.title + ')'}/>
<Checkbox name='ddgProxy' text={background.ddg_image_proxy + ' (' + background.title + ')'}/>
<p>{language.sections.privacy.ddg_proxy_description}</p>
<Checkbox name='stats' text={stats.usage}/>
<p>{language.sections.privacy.stats_description}</p>
<h3 className='quicktip'>{language.sections.privacy.links.title}</h3>
<a className='privacy' href={window.constants.PRIVACY_URL} target='_blank' rel='noopener noreferrer'>{language.sections.privacy.links.privacy_policy}</a>
<br/><br/>

View File

@@ -1,6 +1,4 @@
import React from 'react';
import EventBus from '../../modules/helpers/eventbus';
import { PureComponent, Fragment, Suspense, lazy } from 'react';
import Clock from './time/Clock';
import Greeting from './greeting/Greeting';
@@ -9,10 +7,13 @@ import Search from './search/Search';
import QuickLinks from './quicklinks/QuickLinks';
import Date from './time/Date';
const Weather = React.lazy(() => import('./weather/Weather'));
import EventBus from '../../modules/helpers/eventbus';
const Weather = lazy(() => import('./weather/Weather'));
const renderLoader = () => <></>;
export default class Widgets extends React.PureComponent {
export default class Widgets extends PureComponent {
online = (localStorage.getItem('offlineMode') === 'false');
constructor() {
super();
this.state = {
@@ -24,7 +25,7 @@ export default class Widgets extends React.PureComponent {
greeting: this.enabled('greeting') ? <Greeting/> : null,
quote: this.enabled('quote') ? <Quote/> : null,
date: this.enabled('date') ? <Date/> : null,
quicklinks: this.enabled('quicklinksenabled') ? <QuickLinks/> : null
quicklinks: this.enabled('quicklinksenabled') && this.online ? <QuickLinks/> : null
};
}
@@ -53,7 +54,7 @@ export default class Widgets extends React.PureComponent {
if (this.state.order) {
this.state.order.forEach((element) => {
elements.push(<React.Fragment key={element}>{this.widgets[element]}</React.Fragment>);
elements.push(<Fragment key={element}>{this.widgets[element]}</Fragment>);
});
} else {
// prevent error
@@ -62,11 +63,11 @@ export default class Widgets extends React.PureComponent {
return (
<div id='widgets'>
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
{this.enabled('searchBar') ? <Search/> : null}
{elements}
{this.enabled('weatherEnabled') && (localStorage.getItem('offlineMode') === 'false') ? <Weather/> : null}
</React.Suspense>
{this.enabled('weatherEnabled') && this.online ? <Weather/> : null}
</Suspense>
</div>
);
}

View File

@@ -1,14 +1,15 @@
// todo: rewrite this mess
import React from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import Interval from '../../../modules/helpers/interval';
import { PureComponent } from 'react';
import PhotoInformation from './PhotoInformation';
import EventBus from '../../../modules/helpers/eventbus';
import Interval from '../../../modules/helpers/interval';
import { videoCheck, offlineBackground, gradientStyleBuilder } from '../../../modules/helpers/background/widget';
import './scss/index.scss';
export default class Background extends React.PureComponent {
export default class Background extends PureComponent {
constructor() {
super();
this.state = {
@@ -25,49 +26,6 @@ export default class Background extends React.PureComponent {
this.language = window.language.widgets.background;
}
gradientStyleBuilder(gradientSettings) {
const { type, angle, gradient } = gradientSettings;
let style = `background: ${gradient[0].colour};`;
if (gradient.length > 1) {
// Note: Append the gradient for additional browser support.
const stepStyles = gradient.map((g) => ` ${g.colour} ${g.stop}%`).join();
style += ` background: ${type}-gradient(${(type === 'linear' ? (`${angle}deg,`) : '')}${stepStyles})`;
}
this.setState({
type: 'colour',
style: style
});
}
videoCheck(url) {
return url.startsWith('data:video/') || url.endsWith('.mp4') || url.endsWith('.webm') || url.endsWith('.ogg');
}
offlineBackground() {
const offlineImages = require('./offline_images.json');
// Get all photographers from the keys in offlineImages.json
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`,
photoInfo: {
offline: true,
credit: photographer
}
}
this.setState(object);
localStorage.setItem('currentBackground', JSON.stringify(object));
}
setBackground() {
const backgroundImage = document.getElementById('backgroundImage');
@@ -81,7 +39,7 @@ export default class Background extends React.PureComponent {
photoInformation.style.display = 'block';
}
backgroundImage.style.background = null;
return backgroundImage.style.background = `url(${url})`;
return backgroundImage.style.background = `url(${url})`;
}
// firstly we set the background as hidden and make sure there is no background set currently
@@ -124,23 +82,26 @@ export default class Background extends React.PureComponent {
offline = true;
}
const setFavourited = (favourited) => {
this.setState({
url: favourited.url,
photoInfo: {
credit: favourited.credit,
location: favourited.location,
camera: favourited.camera
}
});
}
switch (localStorage.getItem('backgroundType')) {
case 'api':
if (offline) {
return this.offlineBackground();
return this.setState(offlineBackground());
}
// favourite button
const favourited = JSON.parse(localStorage.getItem('favourite'));
if (favourited) {
return this.setState({
url: favourited.url,
photoInfo: {
credit: favourited.credit,
location: favourited.location,
camera: favourited.camera
}
});
return setFavourited(favourited);
}
// API background
@@ -166,8 +127,8 @@ export default class Background extends React.PureComponent {
data = await (await fetch(requestURL)).json();
} catch (e) {
// if requesting to the API fails, we get an offline image
return this.offlineBackground();
}
return this.setState(offlineBackground());
}
let credit = data.photographer;
let photoURL, photographerURL;
@@ -195,7 +156,8 @@ export default class Background extends React.PureComponent {
photographerURL: photographerURL,
photoURL: photoURL
}
}
};
this.setState(object);
localStorage.setItem('currentBackground', JSON.stringify(object));
@@ -217,7 +179,7 @@ export default class Background extends React.PureComponent {
}
if (typeof gradientSettings === 'object' && gradientSettings !== null) {
return this.gradientStyleBuilder(gradientSettings);
return this.setState(gradientStyleBuilder(gradientSettings));
}
break;
@@ -226,14 +188,14 @@ export default class Background extends React.PureComponent {
// allow users to use offline images
if (offline && !customBackground.startsWith('data:')) {
return this.offlineBackground();
return this.setState(offlineBackground());
}
if (customBackground !== '' && customBackground !== 'undefined') {
this.setState({
url: customBackground,
type: 'custom',
video: this.videoCheck(customBackground),
video: videoCheck(customBackground),
photoInfo: {
hidden: true
}
@@ -243,7 +205,12 @@ export default class Background extends React.PureComponent {
case 'photo_pack':
if (offline) {
return this.offlineBackground();
return this.setState(offlineBackground());
}
const photofavourited = JSON.parse(localStorage.getItem('favourite'));
if (photofavourited) {
return setFavourited(photofavourited);
}
const photoPack = JSON.parse(localStorage.getItem('photo_packs'));
@@ -260,7 +227,7 @@ export default class Background extends React.PureComponent {
});
}
break;
default:
default:
break;
}
}
@@ -302,7 +269,7 @@ export default class Background extends React.PureComponent {
return element.style.display = 'none';
}
}
// video backgrounds
if (this.state.video === true) {
document.getElementById('backgroundVideo').style.display = 'block';
@@ -352,34 +319,36 @@ export default class Background extends React.PureComponent {
}
const interval = localStorage.getItem('backgroundchange');
if (interval && interval !== 'refresh' && localStorage.getItem('backgroundType') === 'api') {
Interval(() => {
try {
document.getElementById('backgroundImage').classList.remove('fade-in');
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
} catch (e) {
// Disregard exception
}
this.getBackground();
}, Number(interval), 'background');
try {
// todo: refactor this mess
const current = JSON.parse(localStorage.getItem('currentBackground'));
const offline = localStorage.getItem('offlineMode')
if (current.url.startsWith('http') && offline === 'false') {
this.setState(current);
} else if (current.url.startsWith('http')) {
this.offlineBackground();
} else {
if (offline === 'false') {
localStorage.removeItem('currentBackground');
return this.getBackground();
if (interval && interval !== 'refresh') {
if (localStorage.getItem('backgroundType') === 'api') {
Interval(() => {
try {
document.getElementById('backgroundImage').classList.remove('fade-in');
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
} catch (e) {
// Disregard exception
}
this.setState(current);
this.getBackground();
}, Number(interval), 'background');
try {
// todo: refactor this mess
const current = JSON.parse(localStorage.getItem('currentBackground'));
const offline = localStorage.getItem('offlineMode');
if (current.url.startsWith('http') && offline === 'false') {
this.setState(current);
} else if (current.url.startsWith('http')) {
this.setState(offlineBackground());
} else {
if (offline === 'false') {
localStorage.removeItem('currentBackground');
return this.getBackground();
}
this.setState(current);
}
} catch (e) {
this.setBackground();
}
} catch (e) {
this.setBackground();
}
} else {
this.getBackground();
@@ -395,6 +364,10 @@ export default class Background extends React.PureComponent {
this.setBackground();
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
if (this.state.video === true) {
const enabled = (setting) => {
@@ -413,8 +386,8 @@ export default class Background extends React.PureComponent {
return (
<>
<div style={{ WebkitFilter: `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}` }} id='backgroundImage'/>
{(this.state.photoInfo.credit !== '') ?
<PhotoInformation className={this.props.photoInformationClass} info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
{(this.state.photoInfo.credit !== '') ?
<PhotoInformation info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
: null}
</>
);

View File

@@ -1,14 +1,12 @@
import React from 'react';
import { PureComponent } from 'react';
import { Star, StarBorder } from '@material-ui/icons';
import Tooltip from '../../helpers/tooltip/Tooltip';
import StarIcon from '@material-ui/icons/Star';
import StarIcon2 from '@material-ui/icons/StarBorder';
export default class Favourite extends React.PureComponent {
export default class Favourite extends PureComponent {
buttons = {
favourited: <StarIcon onClick={() => this.favourite()} className='topicons' />,
unfavourited: <StarIcon2 onClick={() => this.favourite()} className='topicons' />
favourited: <Star onClick={() => this.favourite()} className='topicons' />,
unfavourited: <StarBorder onClick={() => this.favourite()} className='topicons' />
}
constructor() {
@@ -31,12 +29,17 @@ export default class Favourite extends React.PureComponent {
if (!url) {
return;
}
// photo information now hides information if it isn't sent, unless if photoinformation hover is hidden
const location = document.getElementById('infoLocation');
const camera = document.getElementById('infoCamera');
localStorage.setItem('favourite', JSON.stringify({
url: url,
credit: document.getElementById('credit').textContent,
location: document.getElementById('infoLocation').textContent,
camera: document.getElementById('infoCamera').textContent
location: location ? location.innerText : 'N/A',
camera: camera ? camera.innerText : 'N/A',
resolution: document.getElementById('infoResolution').textContent
}));
this.setState({

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { Fullscreen } from '@material-ui/icons';
import Tooltip from '../../helpers/tooltip/Tooltip';
import FullscreenIcon from '@material-ui/icons/Fullscreen';
export default class Maximise extends React.PureComponent {
export default class Maximise extends PureComponent {
constructor() {
super();
this.state = {
@@ -57,7 +56,7 @@ export default class Maximise extends React.PureComponent {
render() {
return (
<Tooltip title={window.language.modals.main.settings.sections.background.buttons.view}>
<FullscreenIcon onClick={this.maximise} className='topicons' />
<Fullscreen onClick={this.maximise} className='topicons' />
</Tooltip>
);
}

View File

@@ -1,22 +1,19 @@
import React from 'react';
import Info from '@material-ui/icons/Info';
import Location from '@material-ui/icons/LocationOn';
import Camera from '@material-ui/icons/PhotoCamera';
import Resolution from '@material-ui/icons/Crop';
import Photographer from '@material-ui/icons/Person';
import Download from '@material-ui/icons/GetApp';
import { useState, Fragment } from 'react';
import { Info, LocationOn, PhotoCamera, Crop as Resolution, Person as Photographer, GetApp as Download } from '@material-ui/icons';
const toDataURL = async (url) => {
const res = await fetch(url);
return URL.createObjectURL(await res.blob());
};
const formatText = (text) => {
return text.toLowerCase().replaceAll(',', '').replaceAll(' ', '-');
};
const downloadImage = async (info) => {
const link = document.createElement('a');
link.href = await toDataURL(info.url);
// todo: make this a bit cleaner
link.download = `mue-${info.credit.toLowerCase().replaceAll(' ', '-')}-${info.location.toLowerCase().replaceAll(',', '').replaceAll(' ', '-')}.jpg`;
link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
@@ -24,8 +21,8 @@ const downloadImage = async (info) => {
};
export default function PhotoInformation(props) {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const language = window.language.widgets.background;
@@ -55,34 +52,51 @@ export default function PhotoInformation(props) {
img.onload = (event) => {
setWidth(event.target.width);
setHeight(event.target.height);
}
};
img.src = (localStorage.getItem('ddgProxy') === 'true' && !props.info.offline && !props.url.startsWith('data:')) ? window.constants.DDG_IMAGE_PROXY + props.url : props.url;
// info is still there because we want the favourite button to work
if (localStorage.getItem('photoInformation') === 'false') {
return (
<div className='photoInformation'>
<h1>{photo} <span id='credit'>{credit}</span></h1>
<div style={{ display: 'none' }}>
<span id='infoLocation'>{props.info.location || 'N/A'}</span>
<span id='infoCamera'>{props.info.camera || 'N/A'}</span>
<span id='infoResolution'>{width}x{height}</span>
</div>
</div>
);
}
return (
<div className='photoInformation'>
<h1>{photo} <span id='credit'>{credit}</span></h1>
{localStorage.getItem('photoInformation') !== 'false' ?
<>
<Info className='photoInformationHover'/>
<div className={props.className || 'infoCard'}>
<Info className='infoIcon'/>
<h1>{language.information}</h1>
<hr/>
<Location/>
<span id='infoLocation'>{props.info.location || 'N/A'}</span>
<Camera/>
<span id='infoCamera'>{props.info.camera || 'N/A'}</span>
<Resolution/>
<span id='infoResolution'>{width}x{height}</span>
<Photographer/>
<span>{photographer}</span>
{(localStorage.getItem('downloadbtn') === 'true') && !props.info.offline && !props.info.photographerURL ?
<>
<Download/>
<span className='download' onClick={() => downloadImage(props.info)}>{language.download}</span>
</> : null}
</div>
</> : null}
<Info className='photoInformationHover'/>
<div className='infoCard'>
<Info className='infoIcon'/>
<h1>{language.information}</h1>
<hr/>
{/* fix console error by using fragment and key */}
{props.info.location && props.info.location !== 'N/A' ? <Fragment key='location'>
<LocationOn/>
<span id='infoLocation'>{props.info.location}</span>
</Fragment> : null}
{props.info.camera && props.info.camera !== 'N/A' ? <Fragment key='camera'>
<PhotoCamera/>
<span id='infoCamera'>{props.info.camera}</span>
</Fragment> : null}
<Resolution/>
<span id='infoResolution'>{width}x{height}</span>
<Photographer/>
<span>{photographer}</span>
{(localStorage.getItem('downloadbtn') === 'true') && !props.info.offline && !props.info.photographerURL ?
<>
<Download/>
<span className='download' onClick={() => downloadImage(props.info)}>{language.download}</span>
</>
: null}
</div>
</div>
);
}

View File

@@ -1,13 +1,11 @@
import React from 'react';
import { PureComponent } from 'react';
import { utcToZonedTime } from 'date-fns-tz';
import { nth, convertTimezone } from '../../../modules/helpers/date';
import EventBus from '../../../modules/helpers/eventbus';
import dtf from '../../../modules/helpers/date';
import './greeting.scss';
export default class Greeting extends React.PureComponent {
export default class Greeting extends PureComponent {
constructor() {
super();
this.state = {
@@ -50,8 +48,8 @@ export default class Greeting extends React.PureComponent {
this.timer = setTimeout(() => {
let now = new Date();
const timezone = localStorage.getItem('timezone');
if (timezone) {
now = utcToZonedTime(now, timezone);
if (timezone && timezone !== 'auto') {
now = convertTimezone(now, timezone);
}
const hour = now.getHours();
@@ -66,12 +64,12 @@ export default class Greeting extends React.PureComponent {
message = this.language.afternoon;
}
// Events
message = this.doEvents(now, message);
// Events and custom
const custom = localStorage.getItem('defaultGreetingMessage');
if (custom === 'false') {
message = '';
} else {
message = this.doEvents(now, message);
}
// Name
@@ -84,18 +82,23 @@ export default class Greeting extends React.PureComponent {
}
}
if (custom === 'false') {
const birthday = localStorage.getItem('birthdayenabled');
if (custom === 'false' && birthday !== 'true') {
name = name.replace(',', '');
}
// Birthday
const birth = new Date(localStorage.getItem('birthday'));
if (localStorage.getItem('birthdayenabled') === 'true' && birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true') {
const text = this.language.birthday.split(' ');
message = `${text[0]} ${dtf.nth(this.calculateAge(birth))} ${text[1]}`;
} else {
message = this.language.birthday;
if (birthday === 'true') {
const birth = new Date(localStorage.getItem('birthday'));
if (birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true') {
const text = this.language.birthday.split(' ');
message = `${text[0]} ${nth(this.calculateAge(birth))} ${text[1]}`;
} else {
message = this.language.birthday;
}
}
}
@@ -133,7 +136,7 @@ export default class Greeting extends React.PureComponent {
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {

View File

@@ -1,9 +1,5 @@
import React from 'react';
import RefreshIcon from '@material-ui/icons/RefreshRounded';
import Gear from '@material-ui/icons/SettingsRounded';
import NotesIcon from '@material-ui/icons/AssignmentRounded';
import Report from '@material-ui/icons/SmsFailed';
import { PureComponent } from 'react';
import { RefreshRounded, SettingsRounded, AssignmentRounded as NotesRounded, SmsFailed as Report } from '@material-ui/icons';
import Notes from './Notes';
import Maximise from '../background/Maximise';
@@ -14,20 +10,27 @@ import EventBus from '../../../modules/helpers/eventbus';
import './scss/index.scss';
export default class Navbar extends React.PureComponent {
export default class Navbar extends PureComponent {
setZoom() {
const zoomNavbar = Number((localStorage.getItem('zoomNavbar') || 100) / 100);
const navbarIcons = document.querySelectorAll('.navbar-container');
for (let i = 0; i < navbarIcons.length; i++) {
navbarIcons[i].style.fontSize = `${zoomNavbar}em`;
}
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'navbar') {
if (data === 'navbar' || data === 'background') {
this.forceUpdate();
this.setZoom();
}
});
this.setZoom();
}
render() {
if (localStorage.getItem('showWelcome') !== 'false') {
return null;
}
const backgroundEnabled = (localStorage.getItem('background') === 'true');
return (
@@ -37,7 +40,7 @@ export default class Navbar extends React.PureComponent {
{(localStorage.getItem('notesEnabled') === 'true') ?
<div className='notes'>
<NotesIcon className='topicons'/>
<NotesRounded className='topicons'/>
<Notes/>
</div>
: null}
@@ -50,12 +53,12 @@ export default class Navbar extends React.PureComponent {
{(localStorage.getItem('refresh') === 'true') ?
<Tooltip title={window.language.widgets.navbar.tooltips.refresh}>
<RefreshIcon className='refreshicon topicons' onClick={() => window.location.reload()}/>
<RefreshRounded className='refreshicon topicons' onClick={() => window.location.reload()}/>
</Tooltip>
: null}
<Tooltip title={window.language.modals.main.navbar.settings}>
<Gear className='settings-icon topicons' onClick={() => this.props.openModal('mainModal')}/>
<SettingsRounded className='settings-icon topicons' onClick={() => this.props.openModal('mainModal')}/>
</Tooltip>
</div>
);

View File

@@ -1,14 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { FileCopyRounded, AssignmentRounded as NotesRounded, PushPin }from '@material-ui/icons';
import TextareaAutosize from '@material-ui/core/TextareaAutosize';
import CopyIcon from '@material-ui/icons/FileCopyRounded';
import NotesIcon from '@material-ui/icons/AssignmentRounded';
import Pin from '@material-ui/icons/PushPin';
import { toast } from 'react-toastify';
export default class Notes extends React.PureComponent {
export default class Notes extends PureComponent {
constructor() {
super();
this.state = {
@@ -57,12 +52,12 @@ export default class Notes extends React.PureComponent {
return (
<span id='noteContainer' className='notescontainer' style={{ visibility: this.state.visibility }}>
<div className='topbarnotes'>
<NotesIcon/>
<NotesRounded/>
<h3>{this.language.title}</h3>
</div>
<TextareaAutosize rowsmax={50} placeholder={this.language.placeholder} value={this.state.notes} onChange={this.setNotes}/>
<button onClick={() => this.pin()} className='pinNote'><Pin/></button>
<button onClick={() => this.copy()} className='copyNote'><CopyIcon/></button>
<button onClick={() => this.pin()} className='pinNote'><PushPin/></button>
<button onClick={() => this.copy()} className='copyNote'><FileCopyRounded/></button>
</span>
);
}

View File

@@ -26,6 +26,7 @@
svg {
float: left;
font-size: 1em !important;
}
::placeholder {
@@ -45,7 +46,7 @@ textarea {
.topbarnotes {
svg {
font-size: 46px;
font-size: 46px !important;
padding: 9px;
}

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { PureComponent } from 'react';
import { TextareaAutosize } from '@material-ui/core';
import Tooltip from '../../helpers/tooltip/Tooltip';
import EventBus from '../../../modules/helpers/eventbus';
import Tooltip from '../../helpers/tooltip/Tooltip';
import TextareaAutosize from '@material-ui/core/TextareaAutosize';
import './quicklinks.scss';
export default class QuickLinks extends React.PureComponent {
export default class QuickLinks extends PureComponent {
constructor() {
super();
this.state = {
@@ -24,8 +24,8 @@ export default class QuickLinks extends React.PureComponent {
deleteLink(key, event) {
event.preventDefault();
let data = JSON.parse(localStorage.getItem('quicklinks'));
data = data.filter((i) => i.key !== key);
// remove link from array
const data = JSON.parse(localStorage.getItem('quicklinks')).filter((i) => i.key !== key);
localStorage.setItem('quicklinks', JSON.stringify(data));
this.setState({
@@ -36,7 +36,7 @@ export default class QuickLinks extends React.PureComponent {
}
addLink = () => {
let data = JSON.parse(localStorage.getItem('quicklinks'));
const data = JSON.parse(localStorage.getItem('quicklinks'));
let url = this.state.url;
let nameError, urlError;
@@ -81,38 +81,25 @@ export default class QuickLinks extends React.PureComponent {
// make sure image is correct size
const element = document.querySelector('.quicklinks-container');
const images = element.getElementsByTagName('img');
for (const img of images) {
img.style.height = `${0.87 * Number((localStorage.getItem('zoomQuicklinks') || 100) / 100)}em`;
};
this.setZoom(element);
}
toggleAdd = () => {
if (this.state.showAddLink === 'hidden') {
this.setState({
showAddLink: 'visible'
});
} else {
this.setState({
showAddLink: 'hidden'
});
}
this.setState({
showAddLink: (this.state.showAddLink === 'hidden') ? 'visible' : 'hidden'
});
}
// widget zoom
setZoom(element) {
const images = element.getElementsByTagName('img');
for (const img of images) {
img.style.height = `${0.87 * Number((localStorage.getItem('zoomQuicklinks') || 100) / 100)}em`;
};
element.querySelector('button').style.fontSize = `${1.15 * Number((localStorage.getItem('zoomQuicklinks') || 100) / 100)}em`;
}
componentDidMount() {
if (localStorage.getItem('offlineMode') === 'true') {
return;
}
EventBus.on('refresh', (data) => {
if (data === 'quicklinks') {
const element = document.querySelector('.quicklinks-container');
@@ -144,14 +131,10 @@ export default class QuickLinks extends React.PureComponent {
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {
if (localStorage.getItem('offlineMode') === 'true') {
return null;
}
let target, rel = null;
if (localStorage.getItem('quicklinksnewtab') === 'true') {
target = '_blank';
@@ -159,9 +142,9 @@ export default class QuickLinks extends React.PureComponent {
}
const tooltipEnabled = localStorage.getItem('quicklinkstooltip');
const useProxy = (localStorage.getItem('quicklinksddgProxy') !== 'false');
const quickLink = (item) => {
const useProxy = (localStorage.getItem('quicklinksddgProxy') !== 'false');
const url = useProxy ? 'https://icons.duckduckgo.com/ip2/' : 'https://www.google.com/s2/favicons?sz=32&domain=';
const link = (
@@ -171,19 +154,21 @@ export default class QuickLinks extends React.PureComponent {
);
if (tooltipEnabled === 'true') {
return <Tooltip title={item.name} key={item.name} draggable={false}>{link}</Tooltip>;
return <Tooltip title={item.name}>{link}</Tooltip>;
} else {
return link;
}
};
const marginTop = (this.state.items.length > 0) ? '9vh' : '4vh';
return (
<div className='quicklinks-container'>
{this.state.items.map((item) => (
quickLink(item)
))}
<button className='quicklinks' onClick={this.toggleAdd}>+</button>
<span className='quicklinkscontainer' style={{ visibility: this.state.showAddLink }}>
<span className='quicklinkscontainer' style={{ visibility: this.state.showAddLink, marginTop: marginTop }}>
<div className='topbarquicklinks'>
<h4>{this.language.new}</h4>
<TextareaAutosize rowsmax={1} placeholder={this.language.name} value={this.state.name} onChange={(e) => this.setState({ name: e.target.value })} />

View File

@@ -53,6 +53,7 @@ textarea {
margin: 0;
cursor: initial;
user-select: none;
text-shadow: none;
}
p {

View File

@@ -1,23 +1,18 @@
import React from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import Interval from '../../../modules/helpers/interval';
import FileCopy from '@material-ui/icons/FilterNone';
import TwitterIcon from '@material-ui/icons/Twitter';
import StarIcon from '@material-ui/icons/Star';
import StarIcon2 from '@material-ui/icons/StarBorder';
import { PureComponent } from 'react';
import { FilterNone as FileCopy, Twitter, Star, StarBorder } from '@material-ui/icons';
import { toast } from 'react-toastify';
import Interval from '../../../modules/helpers/interval';
import EventBus from '../../../modules/helpers/eventbus';
import './quote.scss';
export default class Quote extends React.PureComponent {
export default class Quote extends PureComponent {
buttons = {
tweet: <TwitterIcon className='copyButton' onClick={() => this.tweetQuote()} />,
tweet: <Twitter className='copyButton' onClick={() => this.tweetQuote()} />,
copy: <FileCopy className='copyButton' onClick={() => this.copyQuote()} />,
unfavourited: <StarIcon2 className='copyButton' onClick={() => this.favourite()} />,
favourited: <StarIcon className='copyButton' onClick={() => this.favourite()} />
unfavourited: <StarBorder className='copyButton' onClick={() => this.favourite()} />,
favourited: <Star className='copyButton' onClick={() => this.favourite()} />
}
constructor() {
@@ -147,7 +142,7 @@ export default class Quote extends React.PureComponent {
author: data.author,
authorlink: this.getAuthorLink(data.author),
quoteLanguage: quotelanguage
}
};
this.setState(object);
localStorage.setItem('currentQuote', JSON.stringify(object));
@@ -200,8 +195,9 @@ export default class Quote extends React.PureComponent {
}
setZoom() {
document.querySelector('.quote').style.fontSize = `${0.8 * Number((localStorage.getItem('zoomQuote') || 100) / 100)}em`;
document.querySelector('.quoteauthor').style.fontSize = `${0.9 * Number((localStorage.getItem('zoomQuote') || 100) / 100)}em`;
const zoomQuote = Number((localStorage.getItem('zoomQuote') || 100) / 100);
document.querySelector('.quote').style.fontSize = `${0.8 * zoomQuote}em`;
document.querySelector('.quoteauthor').style.fontSize = `${0.9 * zoomQuote}em`;
}
componentDidMount() {
@@ -215,6 +211,13 @@ export default class Quote extends React.PureComponent {
element.style.display = 'block';
this.init();
// buttons hot reload
this.setState({
favourited: this.useFavourite(),
tweet: (localStorage.getItem('tweetButton') === 'false') ? null : this.buttons.tweet,
copy: (localStorage.getItem('copyButton') === 'false') ? null : this.buttons.copy
});
}
// uninstall quote pack reverts the quote to what you had previously
@@ -244,7 +247,7 @@ export default class Quote extends React.PureComponent {
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {

View File

@@ -1,18 +1,16 @@
import React from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import { PureComponent } from 'react';
import { Search as SearchIcon, Mic } from '@material-ui/icons';
import AutocompleteInput from '../../helpers/autocomplete/Autocomplete';
import SearchIcon from '@material-ui/icons/Search';
import MicIcon from '@material-ui/icons/Mic';
import EventBus from '../../../modules/helpers/eventbus';
import './search.scss';
const searchEngines = require('./search_engines.json');
const autocompleteProviders = require('./autocomplete_providers.json');
export default class Search extends React.PureComponent {
export default class Search extends PureComponent {
constructor() {
super();
this.state = {
@@ -50,14 +48,8 @@ export default class Search extends React.PureComponent {
}
searchButton = (e) => {
let value;
if (e.target.innerText !== undefined) {
value = e.target.innerText;
} else {
value = document.getElementById('searchtext').value || 'mue fast';
}
e.preventDefault();
const value = e.target.value || document.getElementById('searchtext').value || 'mue fast';
window.stats.postEvent('feature', 'Search');
window.location.href = this.state.url + `?${this.state.query}=` + value;
}
@@ -101,7 +93,7 @@ export default class Search extends React.PureComponent {
}
if (localStorage.getItem('voiceSearch') === 'true') {
microphone = <MicIcon className='micIcon' onClick={this.startSpeechRecognition}/>;
microphone = <Mic className='micIcon' onClick={this.startSpeechRecognition}/>;
}
let autocompleteURL, autocompleteQuery, autocompleteCallback;
@@ -134,12 +126,12 @@ export default class Search extends React.PureComponent {
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {
return (
<form action={this.state.url} className='searchBar'>
<form onSubmit={this.searchButton} className='searchBar'>
{this.state.microphone}
<SearchIcon onClick={this.searchButton}/>
<AutocompleteInput placeholder={this.language} id='searchtext' suggestions={this.state.suggestions} onChange={(e) => this.getSuggestions(e)} onClick={this.searchButton}/>

View File

@@ -71,4 +71,4 @@
color: white;
}
}
}
}

View File

@@ -1,14 +1,14 @@
import React from 'react';
import { PureComponent, Suspense, lazy } from 'react';
import { utcToZonedTime } from 'date-fns-tz';
import { convertTimezone } from '../../../modules/helpers/date';
import EventBus from '../../../modules/helpers/eventbus';
import './clock.scss';
const Analog = React.lazy(() => import('react-clock'));
const Analog = lazy(() => import('react-clock'));
const renderLoader = () => <></>;
export default class Clock extends React.PureComponent {
export default class Clock extends PureComponent {
constructor() {
super();
@@ -23,8 +23,8 @@ export default class Clock extends React.PureComponent {
this.timer = setTimeout(() => {
let now = new Date();
const timezone = localStorage.getItem('timezone');
if (timezone) {
now = utcToZonedTime(now, timezone);
if (timezone && timezone !== 'auto') {
now = convertTimezone(now, timezone);
}
switch (localStorage.getItem('timeType')) {
@@ -58,7 +58,8 @@ export default class Clock extends React.PureComponent {
}
this.setState({
time: time
time: time,
ampm: ''
});
} else {
// 12 hour
@@ -66,6 +67,8 @@ export default class Clock extends React.PureComponent {
if (hours > 12) {
hours -= 12;
} else if (hours === 0) {
hours = 12;
}
if (zero === 'false') {
@@ -111,7 +114,7 @@ export default class Clock extends React.PureComponent {
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {
@@ -123,7 +126,7 @@ export default class Clock extends React.PureComponent {
if (localStorage.getItem('timeType') === 'analogue') {
clockHTML = (
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
<Analog
className='analogclock clock-container'
value={this.state.time}
@@ -133,7 +136,7 @@ export default class Clock extends React.PureComponent {
renderMinuteHand={enabled('minuteHand')}
renderHourHand={enabled('hourHand')}
/>
</React.Suspense>
</Suspense>
);
}

View File

@@ -1,13 +1,11 @@
import React from 'react';
import { PureComponent } from 'react';
import { utcToZonedTime } from 'date-fns-tz';
import { nth, convertTimezone } from '../../../modules/helpers/date';
import EventBus from '../../../modules/helpers/eventbus';
import dtf from '../../../modules/helpers/date';
import './date.scss';
export default class DateWidget extends React.PureComponent {
export default class DateWidget extends PureComponent {
constructor() {
super();
this.state = {
@@ -36,8 +34,8 @@ export default class DateWidget extends React.PureComponent {
getDate() {
let date = new Date();
const timezone = localStorage.getItem('timezone');
if (timezone) {
date = utcToZonedTime(date, timezone);
if (timezone && timezone !== 'auto') {
date = convertTimezone(date, timezone);
}
if (localStorage.getItem('weeknumber') === 'true') {
@@ -96,15 +94,15 @@ export default class DateWidget extends React.PureComponent {
});
} else {
// Long date
const lang = localStorage.getItem('language').split('_')[0];
const lang = window.languagecode.split('_')[0];
const nth = (localStorage.getItem('datenth') === 'true') ? dtf.nth(date.getDate()) : date.getDate();
const datenth = (localStorage.getItem('datenth') === 'true') ? nth(date.getDate()) : date.getDate();
const day = (localStorage.getItem('dayofweek') === 'true') ? date.toLocaleDateString(lang, { weekday: 'long' }) : '';
const month = date.toLocaleDateString(lang, { month: 'long' });
this.setState({
date: `${day} ${nth} ${month} ${date.getFullYear()}`
date: `${day} ${datenth} ${month} ${date.getFullYear()}`
});
}
}
@@ -130,7 +128,7 @@ export default class DateWidget extends React.PureComponent {
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {

View File

@@ -1,14 +1,14 @@
import React from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import { PureComponent } from 'react';
import { WiHumidity, WiWindy, WiBarometer, WiCloud } from 'weather-icons-react';
import WeatherIcon from './WeatherIcon';
import WindDirectionIcon from './WindDirectionIcon';
import { WiHumidity, WiWindy, WiBarometer, WiCloud } from 'weather-icons-react';
import EventBus from '../../../modules/helpers/eventbus';
import './weather.scss';
export default class Weather extends React.PureComponent {
export default class Weather extends PureComponent {
constructor() {
super();
this.state = {
@@ -31,11 +31,8 @@ export default class Weather extends React.PureComponent {
}
async getWeather() {
if (localStorage.getItem('offlineMode') === 'true') {
return null;
}
document.querySelector('.weather').style.fontSize = `${Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
const zoomWeather = `${Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
document.querySelector('.weather').style.fontSize = zoomWeather;
let data = {
weather: [
@@ -62,7 +59,7 @@ export default class Weather extends React.PureComponent {
};
if (!this.state.weather.temp) {
data = await (await fetch(window.constants.PROXY_URL + `/weather/current?city=${this.state.location}&lang=${localStorage.getItem('language')}`)).json();
data = await (await fetch(window.constants.PROXY_URL + `/weather/current?city=${this.state.location}&lang=${window.languagecode}`)).json();
}
if (data.cod === '404') {
@@ -114,7 +111,7 @@ export default class Weather extends React.PureComponent {
}
});
document.querySelector('.weather svg').style.fontSize = `${0.95 * Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
document.querySelector('.weather svg').style.fontSize = zoomWeather;
}
componentDidMount() {
@@ -123,11 +120,12 @@ export default class Weather extends React.PureComponent {
this.getWeather();
}
});
this.getWeather();
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {
@@ -135,10 +133,6 @@ export default class Weather extends React.PureComponent {
return (localStorage.getItem(setting) === 'true');
};
if (enabled('offlineMode')) {
return null;
}
if (this.state.location === window.language.widgets.weather.not_found) {
return (<div className='weather'>
<span className='loc'>{this.state.location}</span>

View File

@@ -44,4 +44,4 @@
vertical-align: middle;
font-size: 35px;
}
}
}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { render } from 'react-dom';
import App from './App';
import * as Constants from './modules/constants';
@@ -8,12 +7,10 @@ import './scss/index.scss';
// the toast css is based on default so we need to import it
import 'react-toastify/dist/ReactToastify.min.css';
// this is opt-in btw, allows you to see your stats etc
// local stats
import Stats from './modules/helpers/stats';
// language
import merge from '@material-ui/utils/esm/deepmerge';
const languagecode = localStorage.getItem('language') || 'en_GB';
// we set things to window. so we avoid passing the translation strings as props to each component
@@ -23,8 +20,7 @@ if (languagecode === 'en') {
window.languagecode = 'en_GB';
}
// these are merged so if a string is untranslated it doesn't break mue
window.language = merge(require('./translations/en_GB.json'), require(`./translations/${window.languagecode}.json`));
window.language = require(`./translations/${window.languagecode}.json`);
// set html language tag
if (window.languagecode !== 'en_GB' || window.languagecode !== 'en_US') {
@@ -32,17 +28,16 @@ if (window.languagecode !== 'en_GB' || window.languagecode !== 'en_US') {
}
window.constants = Constants;
// doesn't send to umami when offline mode is on
if (localStorage.getItem('stats') === 'true') {
window.stats = new Stats(window.constants.UMAMI_ID);
window.stats = Stats;
} else {
window.stats = {
tabLoad: () => '',
postEvent: () => ''
}
};
}
ReactDOM.render(
render(
<App/>,
document.getElementById('root')
);

View File

@@ -9,8 +9,9 @@ export const DDG_IMAGE_PROXY = 'https://external-content.duckduckgo.com/iu/?u=';
// Mue URLs
export const WEBSITE_URL = 'https://muetab.com';
export const PRIVACY_URL = 'https://muetab.com/privacy';
export const BLOG_POST = 'https://blog.muetab.com/posts/version-5-2';
export const BLOG_POST = 'https://blog.muetab.com/posts/version-5-3';
export const FEEDBACK_FORM = 'https://api.formcake.com/api/form/349b56cb-7e2b-4004-b32b-e8964d217dd1/submission';
export const TRANSLATIONS_URL = 'https://docs.muetab.com/translations/';
// Mue Info
export const ORG_NAME = 'mue';
@@ -25,13 +26,9 @@ export const COPYRIGHT_YEAR = '2018';
export const COPYRIGHT_LICENSE = 'BSD-3 License';
export const DONATE_USERNAME = 'davidjcralph'; // this only works if you use the same username for Patreon, GitHub and Ko-Fi
// umami
export const UMAMI_DOMAIN = 'https://umami.muetab.com';
export const UMAMI_ID = '1b97e723-199c-48d8-8992-17c4e22d4f3c';
// Offline images
export const OFFLINE_IMAGES = 20;
// Version
export const BETA_VERSION = false;
export const VERSION = '5.2.0';
export const VERSION = '5.3.2';

View File

@@ -0,0 +1,39 @@
// since there is so much code in the component, we have moved it to a separate file
export function videoCheck(url) {
return url.startsWith('data:video/') || url.endsWith('.mp4') || url.endsWith('.webm') || url.endsWith('.ogg');
}
export function offlineBackground() {
const offlineImages = require('./offlineImages.json');
// Get all photographers from the keys in offlineImages.json
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`,
photoInfo: {
offline: true,
credit: photographer
}
};
localStorage.setItem('currentBackground', JSON.stringify(object));
return object;
};
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}`
}
};

View File

@@ -1,18 +1,20 @@
export default class Date {
static nth(d) {
if (d > 3 && d < 21) {
return d + 'th';
}
export function nth(d) {
if (d > 3 && d < 21) {
return d + 'th';
}
switch (d % 10) {
case 1:
return d + 'st';
case 2:
return d + 'nd';
case 3:
return d + 'rd';
default:
return d + 'th';
}
switch (d % 10) {
case 1:
return d + 'st';
case 2:
return d + 'nd';
case 3:
return d + 'rd';
default:
return d + 'th';
}
}
export function convertTimezone(date, tz) {
return new Date((typeof date === 'string' ? new Date(date) : date).toLocaleString('en-US', { timeZone: tz }));
};

View File

@@ -11,7 +11,7 @@ export default class EventBus {
}));
}
static remove(event, callback) {
static off(event, callback) {
document.removeEventListener(event, callback);
}
}

View File

@@ -1,5 +1,5 @@
// based on https://stackoverflow.com/a/47009962
export default function Interval(callback, interval, name) {
export default function interval(callback, interval, name) {
const key = name + 'interval';
const timeInMs = localStorage.getItem(key);
@@ -8,7 +8,7 @@ export default function Interval(callback, interval, name) {
const executeCallback = () => {
localStorage.setItem(key, Date.now());
callback();
}
};
if (timeInMs) {
const delta = now - parseInt(timeInMs);

View File

@@ -1,101 +1,99 @@
import EventBus from './eventbus';
export default class MarketplaceFunctions {
// based on https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links
static urlParser(input) {
const urlPattern = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_+.~#?&//=]*)/;
return input.replace(urlPattern, '<a href="$&" target="_blank">$&</a>');
}
// 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,6}\b([-a-zA-Z0-9()!@:%_+.~#?&//=]*)/;
return input.replace(urlPattern, '<a href="$&" target="_blank">$&</a>');
};
static uninstall(type, name) {
switch (type) {
case 'settings':
const oldSettings = JSON.parse(localStorage.getItem('backup_settings'));
localStorage.clear();
oldSettings.forEach((item) => {
localStorage.setItem(item.name, item.value);
});
break;
case 'quotes':
localStorage.removeItem('quote_packs');
localStorage.removeItem('quoteAPI');
localStorage.setItem('quoteType', localStorage.getItem('oldQuoteType'));
localStorage.removeItem('oldQuoteType');
EventBus.dispatch('refresh', 'marketplacequoteuninstall');
break;
case 'photos':
localStorage.removeItem('photo_packs');
localStorage.setItem('backgroundType', localStorage.getItem('oldBackgroundType'));
localStorage.removeItem('oldBackgroundType');
EventBus.dispatch('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));
}
static 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]);
});
break;
case 'photos':
localStorage.setItem('photo_packs', JSON.stringify(input.photos));
localStorage.setItem('oldBackgroundType', localStorage.getItem('backgroundType'));
localStorage.setItem('backgroundType', 'photo_pack');
EventBus.dispatch('refresh', 'background');
break;
case 'quotes':
if (input.quote_api) {
localStorage.setItem('quoteAPI', JSON.stringify(input.quote_api));
}
localStorage.setItem('quote_packs', JSON.stringify(input.quotes));
localStorage.setItem('oldQuoteType', localStorage.getItem('quoteType'));
localStorage.setItem('quoteType', 'quote_pack');
EventBus.dispatch('refresh', 'quote');
break;
default:
break;
}
const installed = JSON.parse(localStorage.getItem('installed'));
if (sideload) {
installed.push({
content: {
updated: 'Unpublished',
data: input
}
export function uninstall(type, name) {
switch (type) {
case 'settings':
const oldSettings = JSON.parse(localStorage.getItem('backup_settings'));
localStorage.clear();
oldSettings.forEach((item) => {
localStorage.setItem(item.name, item.value);
});
} else {
installed.push(input);
}
localStorage.setItem('installed', JSON.stringify(installed));
break;
case 'quotes':
localStorage.removeItem('quote_packs');
localStorage.removeItem('quoteAPI');
localStorage.setItem('quoteType', localStorage.getItem('oldQuoteType'));
localStorage.removeItem('oldQuoteType');
EventBus.dispatch('refresh', 'marketplacequoteuninstall');
break;
case 'photos':
localStorage.removeItem('photo_packs');
localStorage.setItem('backgroundType', localStorage.getItem('oldBackgroundType'));
localStorage.removeItem('oldBackgroundType');
EventBus.dispatch('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));
};
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]);
});
break;
case 'photos':
localStorage.setItem('photo_packs', JSON.stringify(input.photos));
localStorage.setItem('oldBackgroundType', localStorage.getItem('backgroundType'));
localStorage.setItem('backgroundType', 'photo_pack');
EventBus.dispatch('refresh', 'background');
break;
case 'quotes':
if (input.quote_api) {
localStorage.setItem('quoteAPI', JSON.stringify(input.quote_api));
}
localStorage.setItem('quote_packs', JSON.stringify(input.quotes));
localStorage.setItem('oldQuoteType', localStorage.getItem('quoteType'));
localStorage.setItem('quoteType', 'quote_pack');
EventBus.dispatch('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));
};

View File

@@ -3,144 +3,142 @@ import experimentalInit from '../experimental';
const defaultSettings = require('../../default_settings.json');
const languages = require('../../languages.json');
export default class SettingsFunctions {
static setDefaultSettings(reset) {
localStorage.clear();
defaultSettings.forEach((element) => localStorage.setItem(element.name, element.value));
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('-', '_');
// 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', window.language.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);
if (languageCodes.includes(browserLanguage)) {
localStorage.setItem('language', browserLanguage);
} else {
localStorage.setItem('language', 'en_GB');
}
static loadSettings(hotreload) {
document.getElementById('widgets').style.zoom = localStorage.getItem('widgetzoom') + '%';
localStorage.setItem('tabName', window.language.tabname);
const theme = localStorage.getItem('theme');
switch (theme) {
case 'dark':
document.body.classList.add('dark');
break;
case 'auto':
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
break;
default:
document.body.classList.remove('dark');
}
const tabName = localStorage.getItem('tabName') || window.language.tabname;
document.title = tabName;
if (hotreload === true) {
const custom = ['customcss', 'customjs', 'customfont'];
custom.forEach((element) => {
try {
document.head.removeChild(document.getElementById(element));
} catch (e) {
// Disregard exception
}
});
}
const css = localStorage.getItem('customcss');
if (css) {
document.head.insertAdjacentHTML('beforeend', '<style id="customcss">' + css + '</style>');
}
const font = localStorage.getItem('font');
if (font) {
let url = '';
if (localStorage.getItem('fontGoogle') === 'true') {
url = `@import url('https://fonts.googleapis.com/css2?family=${font}&display=swap');`;
}
document.head.insertAdjacentHTML('beforeend', `
<style id='customfont'>
${url}
* {
font-family: '${font}', 'Lexend Deca', 'Montserrat', sans-serif !important;
font-weight: ${localStorage.getItem('fontweight')};
font-style: ${localStorage.getItem('fontstyle')};
}
</style>
`);
}
// everything below this either doesn't support hot reload (custom js) or shouldn't run on a hot reload event
if (hotreload === true) {
return;
}
const js = localStorage.getItem('customjs');
if (js) {
try {
// eslint-disable-next-line no-eval
eval(js);
} catch (e) {
console.error('Failed to run custom JS: ', e);
}
}
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 ██
█████████████████████████████████████████████████████████████
`);
if (reset) {
localStorage.setItem('showWelcome', false);
}
// in a nutshell, this function 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
static moveSettings() {
if (Object.keys(localStorage).length === 0) {
return this.setDefaultSettings();
}
let settings = {};
Object.keys(localStorage).forEach((key) => {
settings[key] = localStorage.getItem(key);
});
localStorage.clear();
this.setDefaultSettings();
Object.keys(settings).forEach((key) => {
localStorage.setItem(key, settings[key]);
});
}
// Finally we set this to true so it doesn't run the function on every load
localStorage.setItem('firstRun', true);
}
export function loadSettings(hotreload) {
document.getElementById('widgets').style.zoom = localStorage.getItem('widgetzoom') + '%';
switch (localStorage.getItem('theme')) {
case 'dark':
document.body.classList.add('dark');
break;
case 'auto':
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
break;
default:
document.body.classList.remove('dark');
}
document.title = localStorage.getItem('tabName') || window.language.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
}
});
}
const css = localStorage.getItem('customcss');
if (css) {
document.head.insertAdjacentHTML('beforeend', '<style id="customcss">' + css + '</style>');
}
const font = localStorage.getItem('font');
if (font) {
let url = '';
if (localStorage.getItem('fontGoogle') === 'true') {
url = `@import url('https://fonts.googleapis.com/css2?family=${font}&display=swap');`;
}
document.head.insertAdjacentHTML('beforeend', `
<style id='customfont'>
${url}
* {
font-family: '${font}', 'Lexend Deca', 'Montserrat', sans-serif !important;
font-weight: ${localStorage.getItem('fontweight')};
font-style: ${localStorage.getItem('fontstyle')};
}
</style>
`);
}
// everything below this either doesn't support hot reload (custom js) or shouldn't run on a hot reload event
if (hotreload === true) {
return;
}
const js = localStorage.getItem('customjs');
if (js) {
try {
// eslint-disable-next-line no-eval
eval(js);
} catch (e) {
console.error('Failed to run custom JS: ', e);
}
}
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 ██
█████████████████████████████████████████████████████████████
`);
};
// in a nutshell, this function 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
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]);
});
};

View File

@@ -1,40 +1,39 @@
import { toast } from 'react-toastify';
const saveFile = (data, filename = 'file') => {
export function saveFile(data, filename = 'file') {
if (typeof data === 'object') {
data = JSON.stringify(data, undefined, 4);
}
const blob = new Blob([data], { type: 'text/json' });
let e = document.createEvent('MouseEvents');
let a = document.createElement('a');
const event = document.createEvent('MouseEvents');
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = filename;
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(e);
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(event);
}
export function exportSettings() {
const settings = {};
Object.keys(localStorage).forEach((key) => {
settings[key] = localStorage.getItem(key);
});
saveFile(settings, 'mue-settings.json');
window.stats.postEvent('tab', 'Settings exported');
};
export default class ModalSettingsFunctions {
static exportSettings() {
let settings = {};
Object.keys(localStorage).forEach((key) => {
settings[key] = localStorage.getItem(key);
});
saveFile(settings, 'mue-settings.json');
window.stats.postEvent('tab', 'Settings exported');
}
export function importSettings(e) {
const content = JSON.parse(e.target.result);
static importSettings(e) {
const content = JSON.parse(e.target.result);
Object.keys(content).forEach((key) => {
localStorage.setItem(key, content[key]);
});
Object.keys(content).forEach((key) => {
localStorage.setItem(key, content[key]);
});
toast(window.language.toasts.imported);
window.stats.postEvent('tab', 'Settings imported');
}
}
toast(window.language.toasts.imported);
window.stats.postEvent('tab', 'Settings imported');
};

View File

@@ -1,34 +1,8 @@
export default class Stats {
constructor(id) {
this.id = id;
this.url = window.constants.UMAMI_DOMAIN + '/api/collect';
this.online = (localStorage.getItem('offlineMode') === 'false');
}
async postEvent(type, name) {
static async postEvent(type, name) {
const value = name.toLowerCase().replaceAll(' ', '-');
if (this.online) {
// umami
await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'event',
payload: {
website: this.id,
url: '/',
event_type: type,
event_value: value
}
})
});
}
// local
let data = JSON.parse(localStorage.getItem('statsData'));
const data = JSON.parse(localStorage.getItem('statsData'));
// tl;dr this creates the objects if they don't exist
// this really needs a cleanup at some point
if (!data[type] || !data[type][value]) {
@@ -45,29 +19,9 @@ export default class Stats {
localStorage.setItem('statsData', JSON.stringify(data));
}
async tabLoad() {
if (this.online) {
// umami
await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'pageview',
payload: {
website: this.id,
url: '/',
language: localStorage.getItem('language').replace('_', '-'),
screen: `${window.screen.width}x${window.screen.height}`
}
})
});
}
// local
let data = JSON.parse(localStorage.getItem('statsData'));
static async tabLoad() {
const data = JSON.parse(localStorage.getItem('statsData'));
data['tabs-opened'] = data['tabs-opened'] + 1 || 1;
localStorage.setItem('statsData', JSON.stringify(data));
}
}
}

View File

@@ -12,6 +12,13 @@
outline: none;
background: none;
}
&:disabled {
cursor: not-allowed;
color: grey !important;
background: none;
border: 2px solid grey !important;
}
}
.dark %settingsButton {
@@ -69,7 +76,7 @@
cursor: pointer;
&:hover {
background: var(--tab-active);
background: var(--tab-active);
}
svg {
@@ -79,4 +86,4 @@
span {
font-size: 2em;
}
}
}

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Bitte beachten Sie, dass das Mue Team keinen Support leisten kann, wenn Sie den experimentellen Modus aktiviert haben. Bitte deaktivieren Sie ihn zuerst und schauen Sie, ob das Problem weiterhin auftritt, bevor Sie den Support kontaktieren."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Experimentell",
"warning": "Diese Einstellungen sind nicht vollständig getestet/implementiert und funktionieren möglicherweise nicht korrekt!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -396,7 +434,7 @@
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Please note that the Mue team cannot provide support if you have experimental mode on. Please disable it first and see if the issue continues to occur before contacting support."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Experimental",
"warning": "These settings have not been fully tested/implemented and may not work correctly!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -392,11 +430,11 @@
"sections": {
"intro": {
"title": "Welcome to Mue Tab",
"description": "Thank you for installing, we hope you enjoy your time with our extension."
"description": "Thank you for installing Mue, we hope you enjoy your time with our extension."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Please note that the Mue team cannot provide support if you have experimental mode on. Please disable it first and see if the issue continues to occur before contacting support."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Experimental",
"warning": "These settings have not been fully tested/implemented and may not work correctly!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -392,11 +430,11 @@
"sections": {
"intro": {
"title": "Welcome to Mue Tab",
"description": "Thank you for installing, we hope you enjoy your time with our extension."
"description": "Thank you for installing Mue, we hope you enjoy your time with our extension."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -179,12 +179,12 @@
}
},
"interval": {
"title": "Change every",
"minute": "Minute",
"half_hour": "Half hour",
"hour": "Hour",
"day": "Day",
"month": "Month"
"title": "Cambiar cada",
"minute": "Minuto",
"half_hour": "Media hora",
"hour": "Hora",
"day": "Día",
"month": "Mes"
}
},
"search": {
@@ -285,11 +285,25 @@
"custom_js": "JS personalizado",
"tab_name": "Nombre de la pestaña",
"timezone": {
"title": "Time Zone",
"automatic": "Automatic"
"title": "Zona horaria",
"automatic": "Automático"
},
"experimental_warning": "Por favor, ten en cuenta que el equipo de Mue no puede dar soporte si tienes el modo experimental activado. Por favor, desactívalo primero y comprueba si el problema sigue ocurriendo antes de contactar con el equipo de soporte."
},
"stats": {
"title": "Estadísticas",
"warning": "Tienes que activar las estadísticas de uso para poder utilizar esta función. This data is only stored locally.",
"sections": {
"tabs_opened": "Pestañas abiertas",
"backgrounds_favourited": "Fondos favoritos",
"backgrounds_downloaded": "Fondos descargados",
"quotes_favourited": "Citas favoritas",
"quicklinks_added": "Enlaces rápidos añadidos",
"settings_changed": "Ajustes cambiados",
"addons_installed": "Complementos instalados"
},
"usage": "Estadísticas de uso"
},
"experimental": {
"title": "Experimental",
"warning": "Estos ajustes no han sido totalmente probados/implementados y pueden no funcionar correctamente.",
@@ -371,6 +385,30 @@
"oldest": "Instalado (Antiguos)",
"a_z": "Alfabético (A-Z)",
"z_a": "Alfabético (Z-A)"
},
"create": {
"title": "Crear",
"other_title": "Crear complemento",
"metadata": {
"name": "Nombre",
"icon_url": "URL del icono",
"screenshot_url": "URL de la captura de pantalla",
"description": "Descripción"
},
"finish": {
"title": "Acabar",
"download": "Descargar complemento"
},
"settings": {
"current": "Importar la configuración actual",
"json": "Subir JSON"
},
"photos": {
"title": "Añadir fotos"
},
"quotes": {
"title": "Añadir citas"
}
}
}
},
@@ -388,49 +426,49 @@
"contact_support": "Contáctanos aquí"
},
"welcome": {
"tip": "Quick Tip",
"tip": "Truco",
"sections": {
"intro": {
"title": "Bienvenido a Mue Tab",
"description": "Gracias por instalar, esperamos que disfrute de su tiempo con nuestra extensión."
"description": "Gracias por instalar Mue, esperamos que disfrute de su tiempo con nuestra extensión."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"title": "Elige el idioma",
"description": "Mue puede mostrarse en los idiomas que se indican debajo. ¡También puedes contribuir con traducciones en nuestro"
},
"theme": {
"title": "Select a theme",
"description": "Mue is available in both light and dark theme, or this can be automatically set depending on your system theme.",
"tip": "Using the Auto settings will use the theme on your computer. This setting will impact the modals and some of the widgets displayed on the screen, such as weather and notes."
"title": "Selecciona un tema",
"description": "Mue está disponible tanto en el tema claro como en el oscuro, también se puede configurar automáticamente en función del tema de su sistema.",
"tip": "Si utiliza la configuración automática, utilizará el tema de su ordenador. Esta configuración afectará a los modales y a algunos de los widgets que aparecen en la pantalla, como el tiempo y las notas."
},
"settings": {
"title": "Import Settings",
"description": "Installing Mue on a new device? Feel free to import your old settings!",
"tip": "You can export your old settings by navigating to the Advanced tab in your old Mue setup. Then you need to click the export button which will download the JSON file. You can upload this file here to carry across your settings and preferences from your previous Mue installation."
"title": "Importa los ajustes",
"description": "¿Instalando Mue en un nuevo dispositivo? No dudes en importar tu antigua configuración.",
"tip": "Puedes exportar tu antigua configuración navegando a la pestaña Avanzado en tu antigua configuración de Mue. Luego debes hacer clic en el botón de exportación que descargará el archivo JSON. Puedes subir este archivo aquí para mantener tus ajustes y preferencias de tu anterior instalación de Mue."
},
"privacy": {
"title": "Privacy Options",
"description": "Enable settings to further protect your privacy with Mue.",
"offline_mode_description": "Enabling offline mode will disable all requests to any service. This will result in online backgrounds, online quotes, marketplace, weather, quick links, change log and some about tab information to be disabled.",
"ddg_proxy_description": "You can make image requests go through DuckDuckGo if you wish. By default, API requests go through our open source servers and image requests go through the original server. Turning this off for quick links will get the icons from Google instead of DuckDuckGo. DuckDuckGo proxy is always enabled for the Marketplace.",
"title": "Opciones de privacidad",
"description": "Activa estos ajustes para proteger aún más tu privacidad con Mue.",
"offline_mode_description": "Al activar el modo offline se deshabilitarán todas las peticiones a cualquier servicio. Esto hará que se desactiven los fondos en línea, las citas en línea, la tienda, el tiempo, los enlaces rápidos, el registro de cambios y alguna información de la pestaña Acerca de.",
"ddg_proxy_description": "Puedes hacer que las solicitudes de imágenes pasen por DuckDuckGo si lo deseas. Por defecto, las solicitudes a la API van a tráves de nuestros servidores de código abierto y las solicitudes de imágenes van a través del servidor original. Si desactivas esta opción para los enlaces rápidos y los iconos de obtendrán de Google en lugar de DuckDuckGo. El proxy de DuckDuckGo está siempre activado para la tienda.",
"links": {
"title": "Links",
"privacy_policy": "Privacy Policy",
"source_code": "Source Code"
"title": "Enlaces",
"privacy_policy": "Política de privacidad",
"source_code": "Código fuente"
}
},
"final": {
"title": "Final step",
"description": "Your Mue Tab experience is about to begin.",
"changes": "Changes",
"changes_description": "To change settings later click on the settings icon in the top right corner of your tab.",
"imported": "Imported",
"settings": "settings"
"title": "Último paso",
"description": "Tu experiencia con Mue Tab está a punto de comenzar",
"changes": "Cambios",
"changes_description": "Para cambiar la configuración más tarde, haga clic en el icono de configuración en la esquina superior derecha de su pestaña.",
"imported": "Importados",
"settings": "ajustes"
}
},
"buttons": {
"next": "Next",
"previous": "Previous",
"next": "Siguiente",
"previous": "Anterior",
"close": "Cerrar"
}
},

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Veuillez noter que l'équipe Mue ne peut pas fournir d'assistance si vous avez activé le mode expérimental. Veuillez d'abord le désactiver et voir si le problème persiste avant de contacter le support."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Expérimental",
"warning": "These settings have not been fully tested/implemented and may not work correctly!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -392,11 +430,11 @@
"sections": {
"intro": {
"title": "Bienvenue en Mue Tab",
"description": "Merci d'avoir installé, nous espérons que vous apprécierez votre temps avec notre extension."
"description": "Merci d'avoir installé Mue, nous espérons que vous apprécierez votre temps avec notre extension."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Please note that the Mue team cannot provide support if you have experimental mode on. Please disable it first and see if the issue continues to occur before contacting support."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Experimenteel",
"warning": "These settings have not been fully tested/implemented and may not work correctly!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -392,11 +430,11 @@
"sections": {
"intro": {
"title": "Welcome to Mue Tab",
"description": "Thank you for installing, we hope you enjoy your time with our extension."
"description": "Thank you for installing Mue, we hope you enjoy your time with our extension."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Please note that the Mue team cannot provide support if you have experimental mode on. Please disable it first and see if the issue continues to occur before contacting support."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Eksperimental",
"warning": "These settings have not been fully tested/implemented and may not work correctly!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -392,11 +430,11 @@
"sections": {
"intro": {
"title": "Welcome to Mue Tab",
"description": "Thank you for installing, we hope you enjoy your time with our extension."
"description": "Thank you for installing Mue, we hope you enjoy your time with our extension."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "Please note that the Mue team cannot provide support if you have experimental mode on. Please disable it first and see if the issue continues to occur before contacting support."
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "Экспериментальные настройки",
"warning": "These settings have not been fully tested/implemented and may not work correctly!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -393,11 +431,11 @@
"sections": {
"intro": {
"title": "Welcome to Mue Tab",
"description": "Thank you for installing, we hope you enjoy your time with our extension."
"description": "Thank you for installing Mue, we hope you enjoy your time with our extension."
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",

View File

@@ -290,6 +290,20 @@
},
"experimental_warning": "请注意 Mue 团队无法提供实验性功能的支持。若您遇到问题,请先关闭实验性功能,再寻求帮助。"
},
"stats": {
"title": "Stats",
"warning": "You need to enable usage data in order to use this feature. This data is only stored locally.",
"sections": {
"tabs_opened": "Tabs opened",
"backgrounds_favourited": "Backgrounds favourited",
"backgrounds_downloaded": "Backgrounds downloaded",
"quotes_favourited": "Quotes favourited",
"quicklinks_added": "Quicklinks added",
"settings_changed": "Settings changed",
"addons_installed": "Add-ons installed"
},
"usage": "Usage Stats"
},
"experimental": {
"title": "实验性功能",
"warning": "以下设置仍未完成编写或测试,可能无法正常运作!",
@@ -371,6 +385,30 @@
"oldest": "Installed (Oldest)",
"a_z": "Alphabetical (A-Z)",
"z_a": "Alphabetical (Z-A)"
},
"create": {
"title": "Create",
"other_title": "Create Add-on",
"metadata": {
"name": "Name",
"icon_url": "Icon URL",
"screenshot_url": "Screenshot URL",
"description": "Description"
},
"finish": {
"title": "Finish",
"download": "Download Add-on"
},
"settings": {
"current": "Import current setup",
"json": "Upload JSON"
},
"photos": {
"title": "Add Photos"
},
"quotes": {
"title": "Add Quotes"
}
}
}
},
@@ -396,7 +434,7 @@
},
"language": {
"title": "Choose your language",
"description": "Mue can be displayed the languages listed below. You can also add new translations on our GitHub!"
"description": "Mue can be displayed the languages listed below. You can also add new translations on our"
},
"theme": {
"title": "Select a theme",