Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cdfb9db3e | ||
|
|
1ce6cb9419 | ||
|
|
141da4ba36 | ||
|
|
9db7b210bc | ||
|
|
090ffe05f4 | ||
|
|
66f6d0f7ed | ||
|
|
285145fdcb | ||
|
|
37df7fb08c | ||
|
|
5a91fa24e9 | ||
|
|
bdd277d876 | ||
|
|
d7be6e351d | ||
|
|
c9f2af6b96 | ||
|
|
b426041598 | ||
|
|
2d7e138b2f | ||
|
|
332e6c33ac | ||
|
|
99993720f2 | ||
|
|
59a888c861 | ||
|
|
6cccad0e17 | ||
|
|
8ffd0e04cb | ||
|
|
3d6884fe9a | ||
|
|
2a1e26d0c4 | ||
|
|
b842bd935e | ||
|
|
3a47089a00 | ||
|
|
e1f43dc343 | ||
|
|
75b3c23a38 | ||
|
|
7a271190d5 | ||
|
|
c725ad4cc3 | ||
|
|
c07d7ecbb0 | ||
|
|
99ffc82de0 | ||
|
|
7840b48727 | ||
|
|
401baa6653 | ||
|
|
70fc204e70 | ||
|
|
a01d778f65 | ||
|
|
ff3e2caf49 | ||
|
|
95614a383f | ||
|
|
44fc24951f | ||
|
|
19bc802f09 | ||
|
|
ba9f2e01c8 | ||
|
|
8ece0a7eb0 | ||
|
|
d91490874c | ||
|
|
6a3a367cda | ||
|
|
84e6532a80 | ||
|
|
cf36ced2a7 | ||
|
|
c6b65f943a | ||
|
|
6aa1c6b0ea | ||
|
|
2dcaa5270d | ||
|
|
1d44b2792e | ||
|
|
d0577ded59 | ||
|
|
ae5a07c756 | ||
|
|
5d3418a8af | ||
|
|
50353c9e49 | ||
|
|
ea850fae56 | ||
|
|
ba11ac171e | ||
|
|
faace9012a | ||
|
|
90ebfeedc5 | ||
|
|
cfc56c6abf | ||
|
|
d7784e7414 | ||
|
|
7990286e9a | ||
|
|
f7c39eeebb |
@@ -5,4 +5,4 @@ indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
insert_final_newline = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
extends: 'react-app',
|
||||
parser: '@babel/eslint-parser'
|
||||
extends: 'react-app',
|
||||
parser: '@babel/eslint-parser'
|
||||
};
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Request things to be added to mue
|
||||
title: "[Feature Request]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Description**
|
||||
A clear and concise description of what you want adding to Mue. If it's related to a problem, mention so.
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Design**
|
||||
Images or Figma prototypes of what your idea would be like.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the feature request here.
|
||||
1
.gitignore
vendored
@@ -9,3 +9,4 @@ yarn-error.log
|
||||
.eslintcache
|
||||
stats.json
|
||||
yarn.lock
|
||||
*.zip
|
||||
33
README.md
@@ -7,7 +7,7 @@
|
||||
<br>
|
||||
[](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja) [](https://addons.mozilla.org/firefox/addon/mue) [](https://chrome.google.com/webstore/detail/mue/bngmbednanpcfochchhgbkookpiaiaid)
|
||||
|
||||
Mue is a fast, open and free-to-use browser extension that gives a new, fresh and customisable tab page to modern browsers
|
||||
Mue is a fast, open and free-to-use browser extension that gives a new, fresh and customisable tab page to modern browsers.
|
||||
|
||||
<br>
|
||||
|
||||
@@ -19,7 +19,7 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
|
||||
* [Chrome](#chrome)
|
||||
* [Firefox](#firefox)
|
||||
* [Edge Chromium](#edge-chromium)
|
||||
* [Naver](#naver)
|
||||
* [Whale](#whale)
|
||||
* [Other](#other)
|
||||
* [Contributing](#development)
|
||||
* [Translations](#translations)
|
||||
@@ -36,17 +36,17 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
|
||||
* Fast and free
|
||||
* Supports multiple browsers
|
||||
* Actively developed and open source
|
||||
* Automatically updating API (no tracking) with new photos, quotes and offline mode
|
||||
* Automatically updating [API](https://github.com/mue/api) with new photos, quotes and offline mode
|
||||
* Widgets such as searchbar, weather, quick links, clock, date, quote, greeting
|
||||
* Settings - enable/disable various features and customise parts of Mue
|
||||
* Navbar with copy button, favourite background, notes feature etc
|
||||
* Marketplace - download custom photo packs made by the community
|
||||
* [Marketplace](https://github.com/mue/marketplace) - download custom photo packs, quote packs and preset settings made by the community
|
||||
|
||||
### Planned Features
|
||||
Please see our [roadmap](https://github.com/mue/mue/projects)
|
||||
|
||||
## Installation
|
||||
*A demo of the tab can be found [here](https://demo.muetab.com)*
|
||||
*A demo of the tab can be found [here](https://demo.muetab.com), and the latest GitHub commit build [here](https://mue.vercel.app)*
|
||||
### Chrome
|
||||
[](https://chrome.google.com/webstore/detail/mue/bngmbednanpcfochchhgbkookpiaiaid)
|
||||
<br>
|
||||
@@ -60,16 +60,14 @@ Please see our [roadmap](https://github.com/mue/mue/projects)
|
||||
### Edge (Chromium)
|
||||
[Microsoft Edge Addons](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja)
|
||||
|
||||
### Naver
|
||||
### Whale
|
||||
[Whale Store](https://store.whale.naver.com/detail/ecllekeilcmicbfkkiknfdddbogibbnc)
|
||||
|
||||
### Other
|
||||
Please note that we have dropped support for Opera as of Mue 5.0
|
||||
|
||||
[GitHub Releases](https://github.com/mue/mue/releases)
|
||||
|
||||
## Development
|
||||
This section has moved to the [documentation](https://docs.muetab.com/development#mue-tab).
|
||||
Please see the [documentation](https://docs.muetab.com/development#mue-tab).
|
||||
|
||||
### Translations
|
||||
Please see the [documentation](https://docs.muetab.com/translations).
|
||||
@@ -81,16 +79,17 @@ Please see the [documentation](https://docs.muetab.com/translations).
|
||||
[Isaac Saunders](https://github.com/eartharoid) - QA, development, photographer <br/>
|
||||
[Wessel Tip](https://github.com/Wessel) - Development <br/>
|
||||
### Translators
|
||||
[Wessel Tip](https://github.com/Wessel), [Heimen Stoffels](https://github.com/Vistaus) - Dutch<br/>
|
||||
[Alex Sparkes](https://github.com/alexsparkes), [Maxime](https://github.com/exiam) - French<br/>
|
||||
[Anders](https://github.com/FuryingFox) - Norwegian<br/>
|
||||
[Pronin Egor](https://github.com/MrZillaGold) - Russian<br/>
|
||||
[Vicente](https://github.com/Vicente015) - Spanish<br/>
|
||||
[Austin Huang](https://github.com/austinhuang0131) - Chinese (Simplified)<br/>
|
||||
[FreeFun](https://github.com/xXFreeFunXx) - German<br/>
|
||||
[Wessel Tip](https://github.com/Wessel), [Heimen Stoffels](https://github.com/Vistaus) - Dutch <br/>
|
||||
[Alex Sparkes](https://github.com/alexsparkes), [Maxime](https://github.com/exiam) - French <br/>
|
||||
[Anders](https://github.com/FuryingFox) - Norwegian <br/>
|
||||
[Pronin Egor](https://github.com/MrZillaGold) - Russian <br/>
|
||||
[Vicente](https://github.com/Vicente015) - Spanish <br/>
|
||||
[Austin Huang](https://github.com/austinhuang0131) - Chinese (Simplified) <br/>
|
||||
[FreeFun](https://github.com/xXFreeFunXx) - German <br/>
|
||||
### Contributors
|
||||
Many thanks to the photographers [here](https://api.muetab.com/images/photographers) for letting us use their wonderful photographs.
|
||||
|
||||
And finally, a big thank you to all the other [contributors](https://github.com/mue/mue/graphs/contributors)!
|
||||
### Resources
|
||||
[Pexels](https://pexels.com), [Unsplash](https://unsplash.com) - Stock photos used for offline mode
|
||||
[Pexels](https://pexels.com), [Unsplash](https://unsplash.com) - Stock photos used for offline mode <br/>
|
||||
[Undraw](https://undraw.co) - Welcome modal images
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env', ['@babel/preset-react', {
|
||||
'runtime': 'automatic'
|
||||
runtime: 'automatic'
|
||||
}]],
|
||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
|
||||
};
|
||||
|
||||
8
manifest/_locales/de/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/en/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/en_GB/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/en_US/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/es/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/fr/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/nl/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/no/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/ru/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
8
manifest/_locales/zh_CN/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Fast, open and free-to-use new tab page for modern browsers."
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"offline_enabled": true,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"version": "5.1.0",
|
||||
"default_locale": "en",
|
||||
"name": "__MSG_name__",
|
||||
"description": "__MSG_description__",
|
||||
"version": "5.2.0",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"version": "5.1.0",
|
||||
"version": "5.2.0",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
|
||||
46
package.json
@@ -9,18 +9,18 @@
|
||||
"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.1.0",
|
||||
"version": "5.2.0",
|
||||
"dependencies": {
|
||||
"@fontsource/lexend-deca": "^4.4.5",
|
||||
"@fontsource/montserrat": "^4.4.5",
|
||||
"@material-ui/core": "4.11.4",
|
||||
"@material-ui/icons": "4.11.2",
|
||||
"fetch-jsonp": "^1.1.3",
|
||||
"@emotion/react": "^11.4.0",
|
||||
"@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",
|
||||
"react": "17.0.2",
|
||||
"react-clock": "3.0.0",
|
||||
"react-color-gradient-picker": "0.1.2",
|
||||
"react-day-picker": "7.4.10",
|
||||
"react-device-detect": "1.17.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-modal": "3.14.3",
|
||||
"react-sortable-hoc": "2.0.0",
|
||||
@@ -28,33 +28,35 @@
|
||||
"weather-icons-react": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/eslint-parser": "^7.14.5",
|
||||
"@babel/core": "^7.14.8",
|
||||
"@babel/eslint-parser": "^7.14.9",
|
||||
"@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.5",
|
||||
"@babel/preset-env": "^7.14.9",
|
||||
"@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.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"eslint": "^7.28.0",
|
||||
"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.1",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"sass": "^1.35.1",
|
||||
"sass-loader": "^7.3.1",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"mini-css-extract-plugin": "^2.1.0",
|
||||
"sass": "^1.37.0",
|
||||
"sass-loader": "^12.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"webpack": "^5.39.1",
|
||||
"webpack": "^5.47.1",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
"webpack-dev-server": "^4.0.0-rc.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"build": "webpack --mode=production",
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json",
|
||||
"firefox": "cp manifest/firefox.json build/manifest.json"
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json && cp -r manifest/_locales build/_locales",
|
||||
"firefox": "rm -rf build/_locales && cp manifest/firefox.json build/manifest.json"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
1
public/icons/undraw_add_files_modified.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="783" height="702" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="m400.51 1.53-25.446 6.562L61.56 88.94 36.113 95.5a48.18 48.18 0 0 0-34.582 58.618l110.341 427.877a48.183 48.183 0 0 0 22.157 29.413 48.183 48.183 0 0 0 36.46 5.17l.066-.017 364.265-93.936.066-.017a48.18 48.18 0 0 0 34.583-58.618L459.128 36.113A48.18 48.18 0 0 0 400.51 1.53z" fill="#F2F2F2"/><path d="m403.969 14.945-30.139 7.773-304.119 78.426-30.139 7.772a34.312 34.312 0 0 0-24.627 41.743l110.341 427.878a34.307 34.307 0 0 0 41.743 24.627l.065-.017L531.36 509.21l.066-.017a34.308 34.308 0 0 0 24.627-41.743L445.712 39.573a34.31 34.31 0 0 0-41.743-24.628z" fill="#fff"/><path d="m381.212 153.503-184.273 47.521a8.016 8.016 0 0 1-9.761-5.759 8.014 8.014 0 0 1 5.759-9.762l184.273-47.52a8.012 8.012 0 0 1 9.754 5.761 8.01 8.01 0 0 1-5.752 9.759zM419.977 171.439l-216.284 55.775a8.01 8.01 0 0 1-9.761-5.759 8.011 8.011 0 0 1 5.759-9.761l216.284-55.775a8.011 8.011 0 0 1 9.761 5.759 8.012 8.012 0 0 1-5.759 9.761zM411.48 270.877l-184.273 47.52a8.014 8.014 0 0 1-4.003-15.52l184.273-47.521a8.014 8.014 0 0 1 4.003 15.521zM450.245 288.813l-216.284 55.775a8.014 8.014 0 0 1-4.003-15.521l216.284-55.775a8.015 8.015 0 0 1 4.003 15.521zM441.748 388.25l-184.273 47.521a8.017 8.017 0 0 1-9.756-5.761 8.011 8.011 0 0 1 5.753-9.76l184.273-47.52a8.014 8.014 0 0 1 4.003 15.52zM480.513 406.186l-216.284 55.775a8 8 0 0 1-6.077-.854 8.019 8.019 0 0 1-3.866-8.027 8.01 8.01 0 0 1 3.121-5.284 8.006 8.006 0 0 1 2.82-1.355l216.284-55.775a8.011 8.011 0 0 1 9.761 5.759 8.012 8.012 0 0 1-5.759 9.761z" fill="#F2F2F2"/><path d="m165.481 249.749-65.212 16.817a3.847 3.847 0 0 1-4.68-2.762l-14.97-58.048a3.845 3.845 0 0 1 2.761-4.681l65.213-16.817a3.85 3.85 0 0 1 4.68 2.762l14.97 58.048a3.846 3.846 0 0 1-2.762 4.681zM195.749 367.122l-65.212 16.817a3.845 3.845 0 0 1-4.681-2.761l-14.97-58.048a3.854 3.854 0 0 1 .413-2.912 3.853 3.853 0 0 1 2.349-1.769l65.212-16.817a3.849 3.849 0 0 1 4.681 2.761l14.969 58.049a3.847 3.847 0 0 1-2.761 4.68zM226.019 484.496l-65.213 16.817a3.846 3.846 0 0 1-4.681-2.762l-14.969-58.048a3.846 3.846 0 0 1 2.761-4.681l65.213-16.817a3.85 3.85 0 0 1 4.681 2.762l14.969 58.048a3.846 3.846 0 0 1-2.761 4.681zM654.658 109.992H278.34a48.179 48.179 0 0 0-48.125 48.125v441.876a48.176 48.176 0 0 0 48.125 48.125h376.318a48.184 48.184 0 0 0 48.125-48.125V158.117a48.179 48.179 0 0 0-48.125-48.125z" fill="#E6E6E6"/><path d="M654.658 123.846H278.339a34.309 34.309 0 0 0-34.271 34.271v441.876a34.312 34.312 0 0 0 34.271 34.27h376.319a34.309 34.309 0 0 0 34.271-34.27V158.117a34.309 34.309 0 0 0-34.271-34.271z" fill="#fff"/><path d="M694.194 701.88c48.519 0 87.85-39.332 87.85-87.85 0-48.519-39.331-87.851-87.85-87.851-48.518 0-87.85 39.332-87.85 87.851 0 48.518 39.332 87.85 87.85 87.85z" fill="#5352ED"/><path d="M598.022 366.656H407.72a8.018 8.018 0 0 1-8.023-8.015 8.021 8.021 0 0 1 2.351-5.67 8.005 8.005 0 0 1 5.672-2.344h190.302a8.016 8.016 0 0 1 0 16.029zM631.08 393.703H407.72a8.017 8.017 0 0 1-7.412-4.945 8.021 8.021 0 0 1 0-6.138 8.008 8.008 0 0 1 4.343-4.338 8.017 8.017 0 0 1 3.069-.607h223.36a8.013 8.013 0 1 1 0 16.028zM598.022 487.869H407.72a8.017 8.017 0 0 1-7.412-4.945 8.021 8.021 0 0 1 0-6.138 8.008 8.008 0 0 1 4.343-4.338 8.017 8.017 0 0 1 3.069-.607h190.302a8.013 8.013 0 1 1 0 16.028zM631.08 514.917H407.72a8.018 8.018 0 0 1-8.023-8.014 8.014 8.014 0 0 1 8.023-8.015h223.36a8.027 8.027 0 0 1 5.673 2.344 8.02 8.02 0 0 1 0 11.341 8.016 8.016 0 0 1-5.673 2.344zM365.093 405.982h-67.346a3.847 3.847 0 0 1-3.843-3.843v-59.947a3.847 3.847 0 0 1 3.843-3.843h67.346a3.847 3.847 0 0 1 3.843 3.843v59.947a3.85 3.85 0 0 1-3.843 3.843zM365.093 527.195h-67.346a3.847 3.847 0 0 1-3.843-3.843v-59.947a3.847 3.847 0 0 1 3.843-3.843h67.346a3.847 3.847 0 0 1 3.843 3.843v59.947a3.847 3.847 0 0 1-3.843 3.843z" fill="#E6E6E6"/><path d="M598.234 231.721H457.932a8.015 8.015 0 1 1 0-16.028h140.302a8.015 8.015 0 1 1 0 16.028zM631.292 258.769h-173.36a8.009 8.009 0 0 1-7.404-4.948 8.005 8.005 0 0 1 0-6.133 8.011 8.011 0 0 1 7.404-4.948h173.36a8.016 8.016 0 0 1 5.667 13.681 8.016 8.016 0 0 1-5.667 2.348z" fill="#CCC"/><path d="M426.882 291.547H297.536a3.845 3.845 0 0 1-3.843-3.843V186.757a3.847 3.847 0 0 1 3.843-3.843h129.346a3.847 3.847 0 0 1 3.843 3.843v100.947a3.843 3.843 0 0 1-1.127 2.716 3.843 3.843 0 0 1-2.716 1.127z" fill="#5352ED"/><path d="M700.5 648v-57a6.5 6.5 0 1 0-13 0v57a6.5 6.5 0 1 0 13 0z" fill="#fff" stroke="#fff" stroke-width="5"/><path d="m728.202 621.069.609-.571a6.5 6.5 0 0 0 .33-9.154l-30.362-32.861a6.5 6.5 0 0 0-9.548-.001l-30.371 32.862a6.5 6.5 0 0 0 .329 9.154l.61.571a6.498 6.498 0 0 0 9.219-.332l23.885-25.853a1.5 1.5 0 0 1 2.204 0l23.875 25.852a6.5 6.5 0 0 0 9.22.333z" fill="#fff" stroke="#fff" stroke-width="5"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h782.044v701.88H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
1
public/icons/undraw_around_the_world_modified.svg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
1
public/icons/undraw_by_the_road.svg
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
1
public/icons/undraw_dark_mode.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="769.001" height="771.636"><path d="M769.001 0H310.286a400.77 400.77 0 0 0-5.285 65c0 219.81 178.191 398 398 398a400.7 400.7 0 0 0 66-5.45z" fill="#2f2e41"/><path d="M574.85 248.809a57.4 57.4 0 0 1-20.163-3.64 3 3 0 1 1 2.106-5.617 52.393 52.393 0 0 0 51.263-8.531c-22.038.234-43.595-10.18-54.32-26.893-6.894-10.743-9.148-23.384-6.52-36.558a65.225 65.225 0 0 1 12.09-26.277 52.051 52.051 0 0 0-36.024 54.65 3 3 0 1 1-5.971.592 58.044 58.044 0 0 1 49.792-63.138 3 3 0 0 1 2.55 5.076C554.31 154.06 545 179.402 558.787 200.888c10.878 16.95 34.283 26.644 56.928 23.58a3 3 0 0 1 2.642 4.968 58.387 58.387 0 0 1-43.507 19.373z" fill="#6c63ff"/><path d="M527.001 250a22 22 0 1 1 22-22 22.025 22.025 0 0 1-22 22zm0-38a16 16 0 1 0 16 16 16.018 16.018 0 0 0-16-16z" fill="#6c63ff"/><path d="M8.69 567.078A10.276 10.276 0 0 1 .42 554.027l7.788-26.48 11.835 2.218.509 26.967a10.276 10.276 0 0 1-11.86 10.346z" fill="#9f616a"/><path fill="#2f2e41" d="m76.996 736.132-14.793-2.219 3.698-144.235-20.71 62.132-11.095 87.281-14.054-3.698-2.959-90.979 16.273-107.252 81.363 18.492-37.723 180.478z"/><path d="M70.205 771.636a10.873 10.873 0 0 1-10.873-10.873q0-.533.052-1.064l2.297-23.354a3.428 3.428 0 0 1 1.784-2.678c4.36-2.352 8.845-1.782 13.434 1.288a3.407 3.407 0 0 1 1.48 2.429l2.623 22.098a10.873 10.873 0 0 1-10.797 12.154zM9.82 769.898a9.794 9.794 0 0 1-4.754-14.662l15.78-23.457c5.689-4.082 9.12-2.094 10.454 5.534l3.19-7.993 2.404 2.622a7.647 7.647 0 0 1 .404 9.854l-16.368 24.91a9.794 9.794 0 0 1-11.11 3.192z" fill="#2f2e41"/><circle cx="109.542" cy="375.915" r="22.19" fill="#9f616a"/><path d="M123.595 416.596 85.872 409.2c4.86-10.364 8.75-13.87 4.438-25.15h30.327c-2.675 11.683-.09 22.648 2.958 32.546z" fill="#9f616a"/><path d="m119.157 566.009-88.76-25.149c17.78-39.13 21.503-82.09 16.232-127.504a8.217 8.217 0 0 1 7.207-9.116q.15-.017.3-.03c11.578-.953 23.648-2.177 36.174-3.886l14.054 9.615 16.273-3.698c5.221 2.53 10.48 4.636 15.227 6.734a19.038 19.038 0 0 1 10.35 23.414c-14.462 43.677-24.143 86.94-27.057 129.62z" fill="#e6e6e6"/><path d="m22.261 532.724-17.012-2.22L45.55 414.38c2.05-5.907 7.134-9.104 13.323-9.988l5.918.74-4.438 68.788zM155.525 510.524a13.484 13.484 0 0 1-13.487-8.271l-16.224-39.058 13.314-48.078 3.52 2.24q.509.323 1.004.663a31.653 31.653 0 0 1 13.002 31.471l-3.472 20.361 14.323 30.753a13.484 13.484 0 0 1-11.98 9.92z" fill="#e6e6e6"/><path d="M111.248 343.027a23.564 23.564 0 0 1 10.766 3.17c.755.439.406 2.022 1.103 2.539.878.65 2.785.255 3.563 1.017a23.615 23.615 0 0 1 7.022 19.026l-1.238 12.311-2.921-3.194a30.448 30.448 0 0 0-20.305-9.97q-.497-.033-.996-.052l2.247-3.933-3.905 3.906a38.03 38.03 0 0 0-5.321.433l2.988-5.23-5.734 5.734a15.331 15.331 0 0 0-10.713 8.738l-.637 1.411-.713-11.745a23.674 23.674 0 0 1 23.154-24.182q.82-.018 1.64.02z" fill="#2f2e41"/><ellipse cx="131.877" cy="380.205" rx="1.782" ry="4.233" fill="#9f616a"/><ellipse cx="87.319" cy="378.868" rx="1.782" ry="4.233" fill="#9f616a"/><path d="M174.034 435.42a10.276 10.276 0 0 1 7.134 13.706L171.16 474.85l-11.604-3.216 1.782-26.913a10.276 10.276 0 0 1 12.696-9.302z" fill="#9f616a"/><path fill="#e6e6e6" d="m160.579 509.055-18.492-11.835 15.533-31.806 19.231 7.397-9.615 28.107-6.657 8.137z"/><path d="M173.001 409.101V284h-2v125.101a5 5 0 1 0 2 0zm-1 7.899a3 3 0 1 1 3-3 3.003 3.003 0 0 1-3 3z" fill="#3f3d56"/><path d="M170.435 164.57a4.314 4.314 0 0 1-4.314-4.315v-12.941a4.314 4.314 0 0 1 8.627 0v12.941a4.314 4.314 0 0 1-4.313 4.314zM170.435 263.787a4.314 4.314 0 0 1-4.314-4.313v-12.942a4.314 4.314 0 1 1 8.627 0v12.942a4.314 4.314 0 0 1-4.313 4.313zM200.939 177.203a4.314 4.314 0 0 1-3.05-7.364l9.15-9.15a4.314 4.314 0 0 1 6.1 6.1l-9.15 9.15a4.3 4.3 0 0 1-3.05 1.264zM130.78 247.362a4.314 4.314 0 0 1-3.05-7.364l9.15-9.15a4.314 4.314 0 0 1 6.1 6.1l-9.15 9.151a4.3 4.3 0 0 1-3.05 1.263zM226.514 207.708h-12.941a4.314 4.314 0 0 1 0-8.628h12.941a4.314 4.314 0 1 1 0 8.628zM127.296 207.708h-12.941a4.314 4.314 0 1 1 0-8.628h12.941a4.314 4.314 0 0 1 0 8.628zM210.09 247.362a4.3 4.3 0 0 1-3.05-1.263l-9.152-9.15a4.314 4.314 0 1 1 6.101-6.101l9.15 9.15a4.314 4.314 0 0 1-3.05 7.365zM139.93 177.203a4.3 4.3 0 0 1-3.05-1.263l-9.15-9.15a4.314 4.314 0 0 1 6.1-6.102l9.15 9.151a4.314 4.314 0 0 1-3.05 7.364zM170.435 229.277a25.883 25.883 0 1 1 25.883-25.883 25.912 25.912 0 0 1-25.883 25.883zm0-43.139a17.255 17.255 0 1 0 17.255 17.256 17.275 17.275 0 0 0-17.255-17.256z" fill="#6c63ff"/></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
1
public/icons/undraw_on_the_way.svg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
1
public/icons/undraw_private_data_modified.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="400" height="100" viewBox="0 0 629 160" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M502.973 139.536h68.977L606.438 79.8 571.95 20.064h-68.977L468.484 79.8l34.489 59.736zM56.973 138.577h68.977l34.488-59.736-34.488-59.735H56.973L22.484 78.84l34.489 59.736zM279.973 139.616h68.977l34.488-59.736-34.488-59.735h-68.977L245.484 79.88l34.489 59.736z" fill="#F2F2F2"/><path d="M357.693 159.759h-86.464a5.019 5.019 0 0 1-4.33-2.5l-43.231-74.88a5.009 5.009 0 0 1 0-5L266.899 2.5a5.02 5.02 0 0 1 4.33-2.5h86.464a5.015 5.015 0 0 1 4.33 2.5l43.231 74.879a5.009 5.009 0 0 1 0 5l-43.231 74.88a5.018 5.018 0 0 1-4.33 2.5zM271.229 2a3.008 3.008 0 0 0-2.598 1.5L225.4 78.38a3.003 3.003 0 0 0 0 3l43.231 74.88a3.011 3.011 0 0 0 2.598 1.5h86.464a3.012 3.012 0 0 0 2.597-1.5l43.232-74.88a3.003 3.003 0 0 0 0-3L360.29 3.5a2.999 2.999 0 0 0-2.597-1.5h-86.464zM134.693 159.759H48.229a5.018 5.018 0 0 1-4.33-2.5L.668 82.379a5.015 5.015 0 0 1 0-5L43.899 2.5A5.014 5.014 0 0 1 48.23 0h86.464a5.015 5.015 0 0 1 4.33 2.5l43.231 74.879a5.009 5.009 0 0 1 0 5l-43.231 74.88a5.018 5.018 0 0 1-4.33 2.5zM48.229 2a3.008 3.008 0 0 0-2.598 1.5L2.4 78.379a3.009 3.009 0 0 0 0 3l43.231 74.88a3.011 3.011 0 0 0 2.598 1.5h86.464a3.012 3.012 0 0 0 2.597-1.5l43.232-74.88a3.003 3.003 0 0 0 0-3L137.29 3.5a2.999 2.999 0 0 0-2.597-1.5H48.229zM580.693 159.759H494.23a5.013 5.013 0 0 1-4.331-2.5l-43.231-74.88a5.009 5.009 0 0 1 0-5L489.899 2.5A5.016 5.016 0 0 1 494.23 0h86.463a5.015 5.015 0 0 1 4.33 2.5l43.231 74.879a5.009 5.009 0 0 1 0 5l-43.231 74.88a5.016 5.016 0 0 1-4.33 2.5zM494.23 2a3.01 3.01 0 0 0-2.599 1.5L448.4 78.379a3.003 3.003 0 0 0 0 3l43.231 74.88a3.011 3.011 0 0 0 2.599 1.5h86.463a3.012 3.012 0 0 0 2.598-1.5l43.231-74.88a3.003 3.003 0 0 0 0-3L583.291 3.5a3.009 3.009 0 0 0-2.598-1.5H494.23z" fill="#CCC"/><path d="M91.461 45.863a32.977 32.977 0 1 0 0 65.954 32.977 32.977 0 0 0 0-65.954zm0 9.893a9.893 9.893 0 1 1 0 19.786 9.893 9.893 0 0 1 0-19.786zm0 47.626a24.026 24.026 0 0 1-19.786-10.559c.159-6.595 13.19-10.226 19.786-10.226 6.595 0 19.628 3.631 19.786 10.226a24.062 24.062 0 0 1-19.786 10.559zM314.397 56.907a17.995 17.995 0 0 0-12.604-5.58 18.005 18.005 0 0 0-12.851 31.035l26.067 26.068 25.456-25.456a18.44 18.44 0 0 0-.306-25.761 18.44 18.44 0 0 0-25.762-.306zM558.697 59.821c0 11.728-21.235 37.474-21.235 37.474s-21.235-25.746-21.235-37.474a21.223 21.223 0 0 1 6.207-15.02 21.236 21.236 0 0 1 36.263 14.985v.035z" fill="#5352ED"/><path d="M537.462 68.163a8.342 8.342 0 1 0 0-16.684 8.342 8.342 0 0 0 0 16.684z" fill="#fff"/><path d="M536.702 121.029a8.342 8.342 0 1 0 0-16.685 8.342 8.342 0 0 0 0 16.685z" fill="#5352ED"/></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
1
public/icons/undraw_upgrade_modified.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1066" height="774" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path opacity=".1" d="M995.19 369.87c.59-6.77.9-13.65.9-20.6 0-108.83-73.84-197.06-164.92-197.06-29 0-56.34 9-80 24.75-64.47-50-148.64-80.21-240.72-80.21-149.83 0-278.71 80.07-335.89 194.9a138.6 138.6 0 0 0-9.6-.34C73.83 291.31 0 379.53 0 488.36c0 40 10 77.23 27.14 108.31 0 97.91 78.45 177.31 175.22 177.31h122l.54-.93c7.17-11.64 24.34-14.55 36.3-7.94 3.16 1.74 6.1 4 9.63 4.8 14.13 3 23.62-19.73 37.92-17.52 7.18 1.11 12.78 8.53 20 7.77 4.79-.51 8.42-4.52 12.92-6.22 7.21-2.74 14.52 1.06 19.57 7.23 2.93-8.43 2.91-18.42 7.65-26.15 7.17-11.64 24.34-14.55 36.3-7.94 3.16 1.74 6.1 4 9.63 4.8 14.13 3 23.62-19.73 37.92-17.52 7.18 1.11 12.78 8.53 20 7.77 4.79-.51 8.42-4.52 12.92-6.22 11.22-4.27 22.71 7.32 25.47 19a81.066 81.066 0 0 1 1.69 15.64c9.68-4.69 17.86-17.88 28.91-16.17 7.18 1.11 12.78 8.53 20 7.77 4.79-.51 8.42-4.52 12.92-6.22 11.22-4.27 22.71 7.32 25.47 19 1.47 6.2 1.57 12.64 1.82 19h202.15c76 0 137.58-62.32 137.58-139.2 15.42-30 24.33-65.28 24.33-103.06 0-67-28-126.2-70.81-161.8z" fill="#6C63FF"/><path d="M114 582.98v-24.3l11.2 11.2 2.8-2.9-16-16-16 16 2.8 2.8 11.2-11.1v24.3h4zM789 299a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM224 283a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM401 435a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM925 534a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM700 617a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM315 687a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM96 444a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM125 380a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM957 492a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM770 716a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM215 612a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM284 337a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM386.5 227.5H371V212h-2v15.5h-15.5v2H369V245h2v-15.5h15.5v-2zM764 513.89h-9.39v-9.39h-1.22v9.39H744v1.22h9.39v9.39h1.22v-9.39H764v-1.22zM707 688.89h-9.39v-9.39h-1.22v9.39H687v1.22h9.39v9.39h1.22v-9.39H707v-1.22zM240 666.89h-9.39v-9.39h-1.22v9.39H220v1.22h9.39v9.39h1.22v-9.39H240v-1.22zM174 490.89h-9.39v-9.39h-1.22v9.39H154v1.22h9.39v9.39h1.22v-9.39H174v-1.22zM940 667v-24.3l11.2 11.2 2.8-2.9-16-16-16 16 2.8 2.8 11.2-11.1V667h4zM747 385a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM816 618a6 6 0 1 0 0-12 6 6 0 0 0 0 12zM807 439a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM909.5 329.5H894V314h-2v15.5h-15.5v2H892V347h2v-15.5h15.5v-2zM697 592.89h-9.39v-9.39h-1.22v9.39H677v1.22h9.39v9.39h1.22v-9.39H697v-1.22z" fill="#fff"/><path d="m258.343 592.735 3.913-4.048-2.432-2.348-3.913 4.048-15.794 8.254 10.535 10.166 7.691-16.072z" fill="#5352ED"/><path d="m268.811 603.045 4.009-4.15-2.49-2.417-4.016 4.158-16.198 8.463 10.812 10.428 7.883-16.482z" fill="#5352ED"/><path d="m279.414 613.083 3.913-4.048-2.432-2.348-3.913 4.048-15.794 8.254 10.543 10.173 7.683-16.079zM354.436 685.727l4.016-4.158-2.497-2.409-4.016 4.157-16.191 8.456 10.805 10.435 7.883-16.481zM365.242 696.161l4.016-4.157-2.498-2.41-4.016 4.158-16.191 8.456 10.805 10.434 7.884-16.481zM376.047 706.596l4.016-4.158-2.498-2.409-4.016 4.157-16.191 8.456 10.805 10.435 7.884-16.481zM305.384 638.366l4.016-4.158-2.497-2.409-4.016 4.157-16.191 8.456 10.805 10.435 7.883-16.481z" fill="#5352ED"/><path d="m316.19 648.8 4.016-4.157-2.49-2.403-4.016 4.158-16.199 8.449 10.805 10.435 7.884-16.482zM326.995 659.235l4.016-4.158-2.49-2.402-4.016 4.157-16.199 8.45 10.812 10.441 7.877-16.488zM575.105 337.375c13.499-14.624 20.646-56.373 11.545-65.16-9.102-8.787-49.749.608-63.888 14.613l-.437-.411-277.74 287.63 52.795 50.972 277.733-287.623-.008-.021zM671.535 430.49c13.499-14.623 20.646-56.373 11.545-65.16-9.102-8.787-49.749.609-63.881 14.621l-.436-.412L341.022 667.17l52.795 50.971L671.55 430.518l-.015-.028z" fill="#5352ED"/><path d="M752.018 249.005c19.93-21.3 38.411-74.794 29.317-83.574-9.094-8.78-61.104 12.332-81.634 32.984l-.429-.405-407.254 421.748 52.781 50.972 407.26-421.755-.041.03z" fill="#363192"/><g opacity=".1" fill="#000"><path opacity=".1" d="M574.87 274.92c7.59-17.71 22.41-36.18 31.63-36.07 12 .14 33.62 31.79 36.07 52l.26-21.71c-.21-19.9-23.65-55.18-36.3-55.33-9.1-.11-23.64 17.88-31.32 35.38l-.34 25.73zM435.41 291.7h.6c.67-19.89 23.81-54.61 36.45-54.46 8.26.1 21.11 15.17 29.1 31.16l1.94-162.36h.59c.78-29.16 24.13-80.2 36.77-80 11.94.14 33.36 46.21 35.78 75.9l.23-20c-.11-29.17-23.36-80.77-36-80.92-12.64-.15-36 50.89-36.77 80.05h-.59l-1.94 162.36c-8-16-20.84-31.06-29.1-31.16-12.64-.15-35.78 34.57-36.45 54.46h-.6l-4.77 399.81h.3l4.46-374.84z"/></g><path d="M540.858 25.386h-.01a6.93 6.93 0 0 0-7.012 6.847l-.192 16.189a6.93 6.93 0 0 0 6.847 7.011h.01a6.93 6.93 0 0 0 7.012-6.847l.192-16.189a6.93 6.93 0 0 0-6.847-7.011z" fill="#ECECF3"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h1066v773.96H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -11,21 +11,17 @@
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lexend Deca';
|
||||
src: url('./static/media/lexend-deca-latin-400-normal.35a9aeba.woff2');
|
||||
}
|
||||
|
||||
*, a {
|
||||
font-family: 'Lexend Deca', sans-serif;
|
||||
text-align: center;
|
||||
color: black;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
*, a {
|
||||
color: white;
|
||||
background: #2f3640;
|
||||
background: #2f3640 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
public/welcome-images/example1.webp
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
public/welcome-images/example2.webp
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
public/welcome-images/example3.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/welcome-images/example4.webp
Normal file
|
After Width: | Height: | Size: 45 KiB |
12
scripts/updatetranslations.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const merge = require('@eartharoid/deep-merge');
|
||||
|
||||
fs.readdirSync('../src/translations').forEach((file) => {
|
||||
if (file === 'en_GB.json') {
|
||||
return;
|
||||
}
|
||||
|
||||
const newdata = merge(require('../src/translations/en_GB.json'), require('../src/translations/' + file));
|
||||
fs.writeFileSync('../src/translations/' + file, JSON.stringify(newdata, null, 2));
|
||||
fs.appendFileSync('../src/translations/' + file, '\n');
|
||||
});
|
||||
10
src/App.jsx
@@ -11,14 +11,10 @@ import { ToastContainer } from 'react-toastify';
|
||||
|
||||
export default class App extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
if (!localStorage.getItem('firstRun')) {
|
||||
SettingsFunctions.setDefaultSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// 4.0 -> 5.0 (the key below is only on 5.0)
|
||||
// now featuring 5.0 -> 5.1
|
||||
if (!localStorage.getItem('order') || !localStorage.getItem('autocompleteProvider')) {
|
||||
// the firstRun check was moved here because the old function was useless
|
||||
if (!localStorage.getItem('firstRun') || !localStorage.getItem('stats')) {
|
||||
SettingsFunctions.moveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
@@ -30,6 +26,8 @@ export default class App extends React.PureComponent {
|
||||
SettingsFunctions.loadSettings(true);
|
||||
}
|
||||
});
|
||||
|
||||
window.stats.tabLoad();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -7,14 +7,12 @@ export default class Autocomplete extends React.PureComponent {
|
||||
super(props);
|
||||
this.state = {
|
||||
filtered: [],
|
||||
showList: false,
|
||||
input: ''
|
||||
};
|
||||
this.enabled = (localStorage.getItem('autocomplete') === 'true');
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
if (this.enabled === false) {
|
||||
if (localStorage.getItem('autocomplete') !== 'true') {
|
||||
return this.setState({
|
||||
input: e.target.value
|
||||
});
|
||||
@@ -22,7 +20,6 @@ export default class Autocomplete extends React.PureComponent {
|
||||
|
||||
this.setState({
|
||||
filtered: this.props.suggestions.filter((suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1),
|
||||
showList: true,
|
||||
input: e.target.value
|
||||
});
|
||||
|
||||
@@ -32,7 +29,6 @@ export default class Autocomplete extends React.PureComponent {
|
||||
onClick = (e) => {
|
||||
this.setState({
|
||||
filtered: [],
|
||||
showList: false,
|
||||
input: e.target.innerText
|
||||
});
|
||||
|
||||
@@ -42,20 +38,19 @@ export default class Autocomplete extends React.PureComponent {
|
||||
render() {
|
||||
let autocomplete = null;
|
||||
|
||||
if (this.state.showList && this.state.input) {
|
||||
if (this.state.filtered.length && localStorage.getItem('autocomplete') === 'true') {
|
||||
autocomplete = (
|
||||
<ul className='suggestions'>
|
||||
{this.state.filtered.map((suggestion) => {
|
||||
return (
|
||||
<li key={suggestion} onClick={this.onClick}>
|
||||
{suggestion}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
// length will only be > 0 if enabled
|
||||
if (this.state.filtered.length > 0 && this.state.input.length > 0) {
|
||||
autocomplete = (
|
||||
<ul className='suggestions'>
|
||||
{this.state.filtered.map((suggestion) => {
|
||||
return (
|
||||
<li key={suggestion} onClick={this.onClick}>
|
||||
{suggestion}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -64,7 +59,6 @@ export default class Autocomplete extends React.PureComponent {
|
||||
type='text'
|
||||
onChange={this.onChange}
|
||||
value={this.state.input}
|
||||
name={this.props.name || 'name'}
|
||||
placeholder={this.props.placeholder || ''}
|
||||
autoComplete='off'
|
||||
id={this.props.id || ''} />
|
||||
|
||||
@@ -13,6 +13,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
console.log(error);
|
||||
window.stats.postEvent('modal', 'Error occurred');
|
||||
return {
|
||||
error: true
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../modules/helpers/eventbus';
|
||||
|
||||
import Main from './main/Main';
|
||||
import Navbar from '../widgets/navbar/Navbar';
|
||||
|
||||
@@ -27,6 +29,7 @@ export default class Modals extends React.PureComponent {
|
||||
this.setState({
|
||||
welcomeModal: true
|
||||
});
|
||||
window.stats.postEvent('modal', 'Opened welcome');
|
||||
}
|
||||
|
||||
// hide refresh reminder once the user has refreshed the page
|
||||
@@ -38,24 +41,36 @@ export default class Modals extends React.PureComponent {
|
||||
this.setState({
|
||||
welcomeModal: false
|
||||
});
|
||||
EventBus.dispatch('refresh', 'widgets');
|
||||
EventBus.dispatch('refresh', 'backgroundwelcome');
|
||||
}
|
||||
|
||||
toggleModal(type, action) {
|
||||
this.setState({
|
||||
[type]: action
|
||||
});
|
||||
|
||||
if (action !== false) {
|
||||
window.stats.postEvent('modal', `Opened ${type.replace('Modal', '')}`);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Navbar openModal={(modal) => this.setState({ [modal]: true })}/>
|
||||
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.setState({ mainModal: false })} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Main modalClose={() => this.setState({ mainModal: false })}/>
|
||||
<Navbar openModal={(modal) => this.toggleModal(modal, true)}/>
|
||||
<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()}>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<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.setState({ feedbackModal: false })} isOpen={this.state.feedbackModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Feedback modalClose={() => this.setState({ feedbackModal: false })}/>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const renderLoader = () => (
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label='' style={{ 'display': 'none' }}></div>
|
||||
<div label='' style={{ display: 'none' }}></div>
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
@@ -29,17 +29,17 @@ export default function MainModal(props) {
|
||||
<>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<Tabs navbar={true}>
|
||||
<div label={language.settings}>
|
||||
<div label={language.settings} name='settings'>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Settings/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
<div label={language.addons}>
|
||||
<div label={language.addons} name='addons'>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Addons/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
<div label={language.marketplace}>
|
||||
<div label={language.marketplace} name='marketplace'>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Marketplace/>
|
||||
</React.Suspense>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class Item extends React.PureComponent {
|
||||
}
|
||||
|
||||
// prevent console error
|
||||
let iconsrc = window.constants.DDG_PROXY + this.props.data.icon;
|
||||
let iconsrc = window.constants.DDG_IMAGE_PROXY + this.props.data.icon;
|
||||
if (!this.props.data.icon) {
|
||||
iconsrc = null;
|
||||
}
|
||||
@@ -45,27 +45,28 @@ export default class Item extends React.PureComponent {
|
||||
<ArrowBackIcon className='backArrow' onClick={this.props.toggleFunction}/>
|
||||
<br/>
|
||||
<h1>{this.props.data.display_name}</h1>
|
||||
<br/>
|
||||
{this.props.button}
|
||||
<br/>
|
||||
<br/><br/>
|
||||
{iconsrc ? <img alt='product' draggable='false' src={iconsrc} onClick={() => this.setState({ showLightbox: true })}/> : null}
|
||||
<div className='side'>
|
||||
<div className='productInformation'>
|
||||
<ul>
|
||||
<li className='header'>{language.version}</li>
|
||||
<li>{this.props.data.version}</li>
|
||||
<br/>
|
||||
<li className='header'>{language.author}</li>
|
||||
<li>{this.props.data.author}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/>
|
||||
{warningHTML}
|
||||
</div>
|
||||
<div className='sidebr'>
|
||||
<br/><br/>
|
||||
</div>
|
||||
<div className='informationContainer'>
|
||||
<h1 className='overview'>{language.overview}</h1>
|
||||
<p className='description' dangerouslySetInnerHTML={{ __html: this.props.data.description }}></p>
|
||||
<div className='productInformation'>
|
||||
<ul>
|
||||
{/* <li className='header'>{language.last_updated}</li>
|
||||
<li>{this.props.data.updated}</li>
|
||||
<br/>*/}
|
||||
<li className='header'>{language.version}</li>
|
||||
<li>{this.props.data.version}</li>
|
||||
<br/>
|
||||
<li className='header'>{language.author}</li>
|
||||
<li>{this.props.data.author}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/>
|
||||
{warningHTML}
|
||||
</div>
|
||||
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showLightbox: false })} isOpen={this.state.showLightbox} className='Modal lightboxmodal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
|
||||
<Lightbox modalClose={() => this.setState({ showLightbox: false })} img={iconsrc}/>
|
||||
|
||||
@@ -3,7 +3,7 @@ export default function Items(props) {
|
||||
<div className='items'>
|
||||
{props.items.map((item) => (
|
||||
<div className='item' onClick={() => props.toggleFunction(item.name)} key={item.name}>
|
||||
<img alt='icon' draggable='false' src={window.constants.DDG_PROXY + item.icon_url} />
|
||||
<img alt='icon' draggable='false' src={window.constants.DDG_IMAGE_PROXY + item.icon_url} />
|
||||
<div className='details'>
|
||||
<h4>{item.display_name || item.name}</h4>
|
||||
<p>{item.author}</p>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export default function Lightbox(props) {
|
||||
window.stats.postEvent('modal', 'Opened lightbox');
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
|
||||
@@ -42,6 +42,7 @@ export default class Added extends React.PureComponent {
|
||||
},
|
||||
button: this.buttons.uninstall
|
||||
});
|
||||
window.stats.postEvent('marketplace', 'Item viewed');
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
@@ -58,9 +59,11 @@ export default class Added extends React.PureComponent {
|
||||
button: '',
|
||||
installed: JSON.parse(localStorage.getItem('installed'))
|
||||
});
|
||||
|
||||
window.stats.postEvent('marketplace', 'Uninstall');
|
||||
}
|
||||
|
||||
sortAddons(value) {
|
||||
sortAddons(value, sendEvent) {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
switch (value) {
|
||||
case 'newest':
|
||||
@@ -82,10 +85,14 @@ export default class Added extends React.PureComponent {
|
||||
this.setState({
|
||||
installed: installed
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
window.stats.postEvent('marketplace', 'Sort');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.sortAddons(localStorage.getItem('sortAddons'));
|
||||
this.sortAddons(localStorage.getItem('sortAddons'), false);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -67,6 +67,8 @@ export default class Marketplace extends React.PureComponent {
|
||||
},
|
||||
button: button
|
||||
});
|
||||
|
||||
window.stats.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
@@ -75,7 +77,7 @@ export default class Marketplace extends React.PureComponent {
|
||||
}
|
||||
|
||||
async getItems() {
|
||||
const { data } = await (await fetch(window.constants.MARKETPLACE_URL + '/all', { signal: this.controller.signal })).json();
|
||||
const { data } = await (await fetch(window.constants.MARKETPLACE_URL + '/items/' + this.props.type, { signal: this.controller.signal })).json();
|
||||
const featured = await (await fetch(window.constants.MARKETPLACE_URL + '/featured', { signal: this.controller.signal })).json();
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
@@ -83,13 +85,13 @@ export default class Marketplace extends React.PureComponent {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
items: data[this.props.type],
|
||||
oldItems: data[this.props.type],
|
||||
items: data,
|
||||
oldItems: data,
|
||||
featured: featured.data,
|
||||
done: true
|
||||
});
|
||||
|
||||
this.sortMarketplace(localStorage.getItem('sortMarketplace'));
|
||||
this.sortMarketplace(localStorage.getItem('sortMarketplace'), false);
|
||||
}
|
||||
|
||||
manage(type) {
|
||||
@@ -103,9 +105,12 @@ export default class Marketplace extends React.PureComponent {
|
||||
this.setState({
|
||||
button: (type === 'install') ? this.buttons.uninstall : this.buttons.install
|
||||
});
|
||||
|
||||
window.stats.postEvent('marketplace-item', `${this.state.item.display_name} ${(type === 'install' ? 'installed': 'uninstalled')}`);
|
||||
window.stats.postEvent('marketplace', (type === 'install' ? 'Install': 'Uninstall'));
|
||||
}
|
||||
|
||||
sortMarketplace(value) {
|
||||
sortMarketplace(value, sendEvent) {
|
||||
let items = this.state.oldItems;
|
||||
switch (value) {
|
||||
case 'a-z':
|
||||
@@ -127,6 +132,10 @@ export default class Marketplace extends React.PureComponent {
|
||||
items: items,
|
||||
sortType: value
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
window.stats.postEvent('marketplace', 'Sort');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -154,11 +163,15 @@ 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 }}>
|
||||
<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={() => window.open(this.state.featured.buttonLink)}>{this.state.featured.buttonText}</button>
|
||||
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export default function Sideload() {
|
||||
const install = (input) => {
|
||||
MarketplaceFunctions.install(input.type, input);
|
||||
toast(window.language.toasts.installed);
|
||||
window.stats.postEvent('marketplace', 'Sideload');
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -348,6 +348,7 @@ li {
|
||||
min-height: 300px !important;
|
||||
max-width: 300px !important;
|
||||
margin: auto;
|
||||
font-size: 1rem;
|
||||
|
||||
h4 {
|
||||
cursor: initial;
|
||||
@@ -358,6 +359,9 @@ li {
|
||||
.resetfooter {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
width: 300px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
button.reset {
|
||||
margin-right: 43px;
|
||||
@@ -410,3 +414,7 @@ h3 {
|
||||
h5 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.checkbox svg {
|
||||
fill: var(--modal-text) !important;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
|
||||
border: 2px solid #ff4757;
|
||||
color: #ff4757;
|
||||
margin-top: 5px;
|
||||
float: right;
|
||||
margin-top: -10px;
|
||||
|
||||
&:hover {
|
||||
background: #ff4757;
|
||||
@@ -39,10 +40,23 @@
|
||||
|
||||
.addToMue {
|
||||
@extend %storebutton;
|
||||
margin-top: 12px;
|
||||
float: right;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.sideload {
|
||||
display: inline;
|
||||
margin-top: 0px;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
button.round {
|
||||
margin-left: 5px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 3px;
|
||||
vertical-align: middle;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1680px) {
|
||||
.side {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.sidebr {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
p.author {
|
||||
margin-top: -5px;
|
||||
}
|
||||
@@ -98,6 +108,15 @@ p.author {
|
||||
font-size: 40px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#item>h1,
|
||||
@@ -107,7 +126,7 @@ p.author {
|
||||
|
||||
p.description {
|
||||
margin-top: 0px;
|
||||
max-width: 400px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.emptyMessage {
|
||||
@@ -119,6 +138,10 @@ p.description {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 50px;
|
||||
margin-bottom: -20px;
|
||||
@@ -136,7 +159,8 @@ p.description {
|
||||
}
|
||||
|
||||
.informationContainer {
|
||||
margin-top: 20px;
|
||||
margin-top: 150px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.productInformation {
|
||||
@@ -192,6 +216,7 @@ p.description {
|
||||
|
||||
h1 {
|
||||
margin-top: -20px;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
.reset {
|
||||
@extend %settingsButton;
|
||||
|
||||
margin-left: 5px;
|
||||
background-color: map-get($button-colours, 'reset');
|
||||
border: 2px solid map-get($button-colours, 'reset');
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ select {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// safari dropdown
|
||||
@supports (-webkit-hyphens: none) {
|
||||
select {
|
||||
|
||||
@@ -47,6 +47,21 @@ input {
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&[type=date] {
|
||||
width: 200px;
|
||||
color: var(--modal-text);
|
||||
background: var(--sidebar);
|
||||
border: none;
|
||||
padding: 10px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
@@ -139,7 +154,7 @@ legend {
|
||||
border-radius: 0.7em;
|
||||
|
||||
h1 {
|
||||
font-size: 1em;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +198,7 @@ legend {
|
||||
.changelogtab {
|
||||
h1 {
|
||||
max-width: 85%;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -219,4 +235,4 @@ input[type=number] {
|
||||
vertical-align: sub;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import CheckboxUI from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
@@ -15,12 +14,15 @@ export default class Checkbox extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
SettingsFunctions.setItem(this.props.name);
|
||||
const value = (this.state.checked === true) ? false : true;
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
this.setState({
|
||||
checked: (this.state.checked === true) ? false : true
|
||||
checked: value
|
||||
});
|
||||
|
||||
window.stats.postEvent('setting', `${this.props.name} ${(this.state.checked === true) ? 'enabled' : 'disabled'}`);
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
@@ -43,7 +45,7 @@ export default class Checkbox extends React.PureComponent {
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={<CheckboxUI name={this.props.name} color='primary' checked={this.state.checked} onChange={this.handleChange} />}
|
||||
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} />}
|
||||
label={text}
|
||||
/>
|
||||
<br/>
|
||||
|
||||
@@ -22,6 +22,8 @@ export default class Dropdown extends React.PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
window.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
|
||||
|
||||
this.setState({
|
||||
value: value,
|
||||
title: e.target[e.target.selectedIndex].text
|
||||
@@ -55,7 +57,7 @@ export default class Dropdown extends React.PureComponent {
|
||||
return (
|
||||
<>
|
||||
{this.getLabel()}
|
||||
<select id={this.props.name} value={this.state.value} onChange={this.onChange} style={{width: `${(8*this.state.title.length) + 50}px`}}>
|
||||
<select id={this.props.name} value={this.state.value} onChange={this.onChange} style={{ width: `${(8*this.state.title.length) + 50}px` }}>
|
||||
{this.props.children}
|
||||
</select>
|
||||
</>
|
||||
|
||||
@@ -29,6 +29,8 @@ export default class Radio extends React.PureComponent {
|
||||
value: value
|
||||
});
|
||||
|
||||
window.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
|
||||
import SettingsFunctions 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');
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 style={{ 'textAlign': 'center' }}>{language.title}</h3>
|
||||
<h4>{language.question}</h4>
|
||||
<p>{language.information}</p>
|
||||
<h1 style={{ textAlign: 'center' }}>{language.title}</h1>
|
||||
<span>{language.question}</span>
|
||||
<br/><br/>
|
||||
<span>{language.information}</span>
|
||||
<div className='resetfooter'>
|
||||
<button className='reset' style={{ 'marginLeft': '0' }} onClick={() => reset()}>{window.language.modals.main.settings.buttons.reset}</button>
|
||||
<button className='import' style={{ 'marginLeft': '5px' }} onClick={props.modalClose}>{language.cancel}</button>
|
||||
<button className='round reset' style={{ marginLeft: 0 }} onClick={() => reset()}>
|
||||
<DeleteIcon/>
|
||||
</button>
|
||||
<button className='round import' style={{ marginLeft: '5px' }} onClick={props.modalClose}>
|
||||
<CloseIcon/>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import SwitchUI from '@material-ui/core/Switch';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
@@ -15,12 +14,15 @@ export default class Switch extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
SettingsFunctions.setItem(this.props.name);
|
||||
const value = (this.state.checked === true) ? false : true;
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
this.setState({
|
||||
checked: (this.state.checked === true) ? false : true
|
||||
checked: value
|
||||
});
|
||||
|
||||
window.stats.postEvent('setting', `${this.props.name} ${(this.state.checked === true) ? 'enabled' : 'disabled'}`);
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import Tooltip from '../../../../helpers/tooltip/Tooltip';
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
import TwitterIcon from '@material-ui/icons/Twitter';
|
||||
import ForumIcon from '@material-ui/icons/Forum';
|
||||
import ChatIcon from '@material-ui/icons/Chat';
|
||||
import InstagramIcon from '@material-ui/icons/Instagram';
|
||||
import FacebookIcon from '@material-ui/icons/Facebook';
|
||||
|
||||
@@ -28,9 +28,9 @@ export default class About extends React.PureComponent {
|
||||
let contributors, sponsors, photographers, versionData;
|
||||
|
||||
try {
|
||||
versionData = await (await fetch(window.constants.GITHUB_URL + '/repos/mue/mue/releases', { signal: this.controller.signal })).json();
|
||||
versionData = await (await fetch(window.constants.GITHUB_URL + '/repos/' + window.constants.ORG_NAME + '/' + window.constants.REPO_NAME + '/releases', { signal: this.controller.signal })).json();
|
||||
|
||||
contributors = await (await fetch(window.constants.GITHUB_URL + '/repos/mue/mue/contributors', { signal: this.controller.signal })).json();
|
||||
contributors = await (await fetch(window.constants.GITHUB_URL + '/repos/'+ window.constants.ORG_NAME + '/' + window.constants.REPO_NAME + '/contributors', { signal: this.controller.signal })).json();
|
||||
sponsors = (await (await fetch(window.constants.SPONSORS_URL + '/list', { signal: this.controller.signal })).json()).sponsors;
|
||||
|
||||
photographers = await (await fetch(window.constants.API_URL + '/images/photographers', { signal: this.controller.signal })).json();
|
||||
@@ -57,6 +57,7 @@ export default class About extends React.PureComponent {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
// exclude bots
|
||||
contributors: contributors.filter((contributor) => !contributor.login.includes('bot')),
|
||||
sponsors: sponsors,
|
||||
update: updateMsg,
|
||||
@@ -87,22 +88,23 @@ export default class About extends React.PureComponent {
|
||||
return (
|
||||
<>
|
||||
<h2>{this.language.title}</h2>
|
||||
<img draggable='false' className='aboutLogo' src='./././icons/logo_horizontal.png' alt='Mue logo'></img>
|
||||
<p>{this.language.copyright} 2018-{new Date().getFullYear()} <a href='https://github.com/mue/mue/graphs/contributors' className='aboutLink' target='_blank' rel='noopener noreferrer'>The Mue Authors</a> (BSD-3 License)</p>
|
||||
<img draggable='false' className='aboutLogo' src='./././icons/logo_horizontal.png' alt='Logo'></img>
|
||||
<p>{this.language.copyright} {window.constants.COPYRIGHT_YEAR}-{new Date().getFullYear()} <a href={'https://github.com/' + window.constants.ORG_NAME + '/' + window.constants.REPO_NAME + '/graphs/contributors'} className='aboutLink' target='_blank' rel='noopener noreferrer'>{window.constants.COPYRIGHT_NAME}</a> ({window.constants.COPYRIGHT_LICENSE})</p>
|
||||
<p>{this.language.version.title} {window.constants.VERSION} ({this.state.update})</p>
|
||||
<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:hello@muetab.com' className='aboutIcon' target='_blank' rel='noopener noreferrer'><EmailIcon/></a>
|
||||
<a href='https://twitter.com/getmue' className='aboutIcon' target='_blank' rel='noopener noreferrer'><TwitterIcon/></a>
|
||||
<a href='https://instagram.com/mue.tab' className='aboutIcon' target='_blank' rel='noopener noreferrer'><InstagramIcon/></a>
|
||||
<a href='https://facebook.com/muetab' className='aboutIcon' target='_blank' rel='noopener noreferrer'><FacebookIcon/></a>
|
||||
<a href='https://discord.gg/zv8C9F8' className='aboutIcon' target='_blank' rel='noopener noreferrer'><ForumIcon/></a>
|
||||
<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>
|
||||
|
||||
<h3>{this.language.support_mue}</h3>
|
||||
<p>
|
||||
<a href='https://github.com/sponsors/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>GitHub Sponsors</a>
|
||||
• <a href='https://ko-fi.com/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
|
||||
• <a href='https://patreon.com/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>Patreon</a>
|
||||
<a href={'https://github.com/sponsors/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>GitHub Sponsors</a>
|
||||
• <a href={'https://ko-fi.com/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
|
||||
• <a href={'https://patreon.com/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Patreon</a>
|
||||
</p>
|
||||
|
||||
<h3>{this.language.resources_used.title}</h3>
|
||||
@@ -131,7 +133,7 @@ export default class About extends React.PureComponent {
|
||||
<p>{this.state.loading}</p>
|
||||
{this.state.sponsors.map((item) => (
|
||||
<Tooltip title={item.handle} key={item.handle}>
|
||||
<a href={item.profile} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar + '&size=128'} alt={item.handle}></img></a>
|
||||
<a href={'https://github.com/' + item.handle} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar + '&size=128'} alt={item.handle}></img></a>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ import FileUpload from '../FileUpload';
|
||||
import Text from '../Text';
|
||||
import Switch from '../Switch';
|
||||
import ResetModal from '../ResetModal';
|
||||
import Dropdown from '../Dropdown';
|
||||
|
||||
import SettingsFunctions from '../../../../../modules/helpers/settings';
|
||||
import SettingsFunctions from '../../../../../modules/helpers/settings/modals';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
const time_zones = require('../../../../widgets/time/timezones.json');
|
||||
|
||||
export default class AdvancedSettings extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -20,16 +22,6 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
settingsImport(e) {
|
||||
const content = JSON.parse(e.target.result);
|
||||
|
||||
Object.keys(content).forEach((key) => {
|
||||
localStorage.setItem(key, content[key]);
|
||||
});
|
||||
|
||||
toast(window.language.toasts.imported);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { advanced } = this.language.sections;
|
||||
|
||||
@@ -37,12 +29,18 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
<>
|
||||
<h2>{advanced.title}</h2>
|
||||
<Checkbox name='offlineMode' text={advanced.offline_mode} element='.other' />
|
||||
<Dropdown name='timezone' label={advanced.timezone.title} category='timezone'>
|
||||
<option value='auto'>{advanced.timezone.automatic}</option>
|
||||
{time_zones.map((timezone) => (
|
||||
<option value={timezone} key={timezone}>{timezone}</option>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
<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='import' onClick={() => document.getElementById('file-input').click()}>{this.language.buttons.import}</button>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.settingsImport(e)}/>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => SettingsFunctions.importSettings(e)}/>
|
||||
|
||||
<h3>{advanced.customisation}</h3>
|
||||
<Text title={advanced.tab_name} name='tabName' default={window.language.tabname} category='other'/>
|
||||
@@ -50,7 +48,7 @@ export default class AdvancedSettings extends React.PureComponent {
|
||||
<Text title={advanced.custom_css} name='customcss' textarea={true} category='other'/>
|
||||
|
||||
<h3>{this.language.sections.experimental.title}</h3>
|
||||
<p style={{ 'maxWidth': '75%'}}>{advanced.experimental_warning}</p>
|
||||
<p style={{ maxWidth: '75%' }}>{advanced.experimental_warning}</p>
|
||||
<Switch name='experimental' text={this.language.enabled} element='.other'/>
|
||||
|
||||
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ resetModal: false })} isOpen={this.state.resetModal} className='Modal resetmodal mainModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { engineName } from 'react-device-detect';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
import Radio from '../Radio';
|
||||
@@ -30,8 +28,8 @@ export default function AppearanceSettings() {
|
||||
<Radio name='theme' title={appearance.theme.title} options={themeOptions} category='other' />
|
||||
|
||||
<h3>{appearance.navbar.title}</h3>
|
||||
<Checkbox name='notesEnabled' text={appearance.navbar.notes} element='.other' />
|
||||
<Checkbox name='refresh' text={appearance.navbar.refresh} element='.other' />
|
||||
<Checkbox name='notesEnabled' text={appearance.navbar.notes} category='navbar' />
|
||||
<Checkbox name='refresh' text={appearance.navbar.refresh} category='navbar' />
|
||||
|
||||
<h3>{appearance.font.title}</h3>
|
||||
<Text title={appearance.font.custom} name='font' upperCaseFirst={true} category='other' />
|
||||
@@ -56,7 +54,7 @@ export default function AppearanceSettings() {
|
||||
</Dropdown>
|
||||
|
||||
<h3>{appearance.accessibility.title}</h3>
|
||||
{(engineName === 'Blink') ?
|
||||
{(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}
|
||||
<Slider title={appearance.accessibility.toast_duration} name='toastDisplayTime' default='2500' step='100' min='500' max='5000' toast={true} display={' ' + appearance.accessibility.milliseconds} />
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class Changelog extends React.PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
let date = new Date(data.date);
|
||||
let date = new Date(data.date.split(' ')[0]);
|
||||
date = date.toLocaleDateString(window.languagecode.replace('_', '-'), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
@@ -101,8 +101,8 @@ export default class Changelog extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className='changelogtab'>
|
||||
<h1 style={{ 'marginBottom': '-10px' }}>{this.state.title}</h1>
|
||||
<h5 style={{ 'lineHeight': '0px' }}>{this.state.author} • {this.state.date}</h5>
|
||||
<h1 style={{ marginBottom: '-10px' }}>{this.state.title}</h1>
|
||||
<h5 style={{ lineHeight: '0px' }}>{this.state.author} • {this.state.date}</h5>
|
||||
{this.state.image ? <img draggable='false' src={this.state.image} alt={window.language.modals.update.title} className='updateimage'/> : null}
|
||||
<div className='updatechangelog' dangerouslySetInnerHTML={{ __html: this.state.html }}/>
|
||||
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showLightbox: false })} isOpen={this.state.showLightbox} className='Modal lightboxmodal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Checkbox from '../Checkbox';
|
||||
import Slider from '../Slider';
|
||||
import EventBus from '../../../../../modules/helpers/eventbus';
|
||||
|
||||
export default function ExperimentalSettings() {
|
||||
const { experimental } = window.language.modals.main.settings.sections;
|
||||
@@ -9,11 +10,21 @@ 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' />
|
||||
<br/>
|
||||
<p>Send Event</p>
|
||||
Type <input type='text' id='eventType'/>
|
||||
<br/><br/>
|
||||
<button className='reset' style={{'marginLeft': '0px'}} onClick={() => localStorage.clear()}>Clear LocalStorage</button>
|
||||
Name <input type='text' id='eventName'/>
|
||||
<br/><br/>
|
||||
<button className='uploadbg' onClick={() => EventBus.dispatch(document.getElementById('eventType').value, document.getElementById('eventName').value)}>Send</button>
|
||||
<br/><br/>
|
||||
<button className='reset' style={{ marginLeft: '0px' }} onClick={() => localStorage.clear()}>Clear LocalStorage</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@ import Switch from '../Switch';
|
||||
import Text from '../Text';
|
||||
import Slider from '../Slider';
|
||||
|
||||
import DayPickerInput from 'react-day-picker/DayPickerInput';
|
||||
import 'react-day-picker/lib/style.css';
|
||||
|
||||
export default class GreetingSettings extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -17,11 +14,11 @@ export default class GreetingSettings extends React.PureComponent {
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
changeDate = (data) => {
|
||||
localStorage.setItem('birthday', data);
|
||||
changeDate = (e) => {
|
||||
localStorage.setItem('birthday', e.target.value);
|
||||
|
||||
this.setState({
|
||||
birthday: data
|
||||
birthday: new Date(e.target.value)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,7 +38,7 @@ export default class GreetingSettings extends React.PureComponent {
|
||||
<Switch name='birthdayenabled' text={this.language.enabled} category='greeting' element='.greeting'/>
|
||||
<Checkbox name='birthdayage' text={greeting.birthday_age} category='greeting' element='.greeting'/>
|
||||
<p>{greeting.birthday_date}</p>
|
||||
<DayPickerInput onDayChange={this.changeDate} value={this.state.birthday}/>
|
||||
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const widget_name = {
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => (
|
||||
<li className='sortableitem' style={{ display: enabled(value) ? 'block' : 'none' }}>
|
||||
<DragHandleIcon style={{'verticalAlign': 'middle'}} />
|
||||
<DragHandleIcon style={{ verticalAlign: 'middle' }} />
|
||||
{widget_name[value]}
|
||||
</li>
|
||||
));
|
||||
@@ -52,7 +52,6 @@ export default class OrderSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
const newArray = [...array];
|
||||
|
||||
const target = newArray[oldIndex];
|
||||
const inc = newIndex < oldIndex ? -1 : 1;
|
||||
|
||||
@@ -61,7 +60,6 @@ export default class OrderSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
newArray[newIndex] = target;
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
@@ -83,6 +81,7 @@ export default class OrderSettings extends React.PureComponent {
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('order', JSON.stringify(this.state.items));
|
||||
window.stats.postEvent('setting', 'Widget order');
|
||||
EventBus.dispatch('refresh', 'widgets');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Switch from '../Switch';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Slider from '../Slider';
|
||||
|
||||
export default function QuickLinks() {
|
||||
const language = window.language.modals.main.settings.sections.quicklinks;
|
||||
@@ -8,9 +9,10 @@ export default function QuickLinks() {
|
||||
<>
|
||||
<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_proxy} element='.other' />
|
||||
<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' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,20 +21,32 @@ export default class QuoteSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { quote } = window.language.modals.main.settings.sections;
|
||||
const { quote, background } = window.language.modals.main.settings.sections;
|
||||
|
||||
let quoteSettings;
|
||||
|
||||
const customSettings = (
|
||||
<>
|
||||
<Text title={quote.custom} name='customQuote' category='quote' element='.quotediv' />
|
||||
<Text title={quote.custom_author} name='customQuoteAuthor' category='quote' element='.quotediv'/>
|
||||
</>
|
||||
);
|
||||
|
||||
switch (this.state.quoteType) {
|
||||
case 'custom': quoteSettings = customSettings; break;
|
||||
default: break;
|
||||
let customSettings;
|
||||
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'/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
// api
|
||||
customSettings = (
|
||||
<>
|
||||
<br/><br/>
|
||||
<Dropdown label={background.interval.title} name='quotechange'>
|
||||
<option value='refresh'>{window.language.tabname}</option>
|
||||
<option value='60000'>{background.interval.minute}</option>
|
||||
<option value='1800000'>{background.interval.half_hour}</option>
|
||||
<option value='3600000'>{background.interval.hour}</option>
|
||||
<option value='86400000'>{background.interval.day}</option>
|
||||
<option value='604800000'>{window.language.widgets.date.week}</option>
|
||||
<option value='2628000000'>{background.interval.month}</option>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -47,7 +59,7 @@ export default class QuoteSettings extends React.PureComponent {
|
||||
<option value='api'>{window.language.modals.main.settings.sections.background.type.api}</option>
|
||||
<option value='custom'>{quote.custom}</option>
|
||||
</Dropdown>
|
||||
{quoteSettings}
|
||||
{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' />
|
||||
|
||||
<h3>{quote.buttons.title}</h3>
|
||||
|
||||
@@ -7,11 +7,10 @@ import Radio from '../Radio';
|
||||
|
||||
import EventBus from '../../../../../modules/helpers/eventbus';
|
||||
|
||||
import { isChrome } from 'react-device-detect';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const searchEngines = require('../../../../widgets/search/search_engines.json');
|
||||
const autocompleteProviders = require('../../../../widgets/search/autocomplete_providers.json')
|
||||
const autocompleteProviders = require('../../../../widgets/search/autocomplete_providers.json');
|
||||
|
||||
export default class SearchSettings extends React.PureComponent {
|
||||
constructor() {
|
||||
@@ -21,7 +20,6 @@ export default class SearchSettings extends React.PureComponent {
|
||||
customDisplay: 'none',
|
||||
customValue: localStorage.getItem('customSearchEngine') || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
@@ -30,7 +28,7 @@ export default class SearchSettings extends React.PureComponent {
|
||||
customValue: ''
|
||||
});
|
||||
|
||||
toast(window.language.modals.main.settings.toasts.reset);
|
||||
toast(window.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -77,17 +75,18 @@ export default class SearchSettings extends React.PureComponent {
|
||||
<>
|
||||
<h2>{search.title}</h2>
|
||||
<Switch name='searchBar' text={language.enabled} category='widgets' />
|
||||
{isChrome ? <Checkbox name='voiceSearch' text={search.voice_search} category='search' element='.other' /> : null}
|
||||
{(navigator.userAgent.includes('Chrome') && typeof InstallTrigger === 'undefined') ?
|
||||
<Checkbox name='voiceSearch' text={search.voice_search} category='search'/>
|
||||
: null}
|
||||
<Dropdown label={search.search_engine} name='searchEngine' onChange={(value) => this.setSearchEngine(value)}>
|
||||
{searchEngines.map((engine) => (
|
||||
<option key={engine.name} value={engine.settingsName}>{engine.name}</option>
|
||||
))}
|
||||
<option value='custom'>{search.custom.split(' ')[0]}</option>
|
||||
</Dropdown>
|
||||
|
||||
<ul style={{ display: this.state.customDisplay }}>
|
||||
<br/>
|
||||
<p style={{ 'marginTop': '0px' }}>{search.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{language.buttons.reset}</span></p>
|
||||
<p style={{ marginTop: '0px' }}>{search.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{language.buttons.reset}</span></p>
|
||||
<input type='text' value={this.state.customValue} onInput={(e) => this.setState({ customValue: e.target.value })}></input>
|
||||
</ul>
|
||||
<br/>
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class TimeSettings extends React.PureComponent {
|
||||
|
||||
getAuto() {
|
||||
navigator.geolocation.getCurrentPosition(async (position) => {
|
||||
const data = await (await fetch(`${window.constants.WEATHER_URL}/location?getAuto=true&lat=${position.coords.latitude}&lon=${position.coords.longitude}`)).json();
|
||||
const data = await (await fetch(`${window.constants.PROXY_URL}/weather/autolocation?lat=${position.coords.latitude}&lon=${position.coords.longitude}`)).json();
|
||||
this.setState({
|
||||
location: data[0].name
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
const APISettings = (
|
||||
<>
|
||||
<br/>
|
||||
<Radio title={background.source.api} options={apiOptions} name='backgroundAPI' category='background'/>
|
||||
<Radio title={background.source.api} options={apiOptions} name='backgroundAPI' category='background' element='#backgroundImage'/>
|
||||
<br/>
|
||||
<Dropdown label={background.category} name='apiCategory'>
|
||||
{this.state.backgroundCategories.map((category) => (
|
||||
@@ -128,6 +128,16 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
<option value='normal'>{background.source.quality.normal}</option>
|
||||
<option value='datasaver'>{background.source.quality.datasaver}</option>
|
||||
</Dropdown>
|
||||
<br/><br/>
|
||||
<Dropdown label={background.interval.title} name='backgroundchange'>
|
||||
<option value='refresh'>{window.language.tabname}</option>
|
||||
<option value='60000'>{background.interval.minute}</option>
|
||||
<option value='1800000'>{background.interval.half_hour}</option>
|
||||
<option value='3600000'>{background.interval.hour}</option>
|
||||
<option value='86400000'>{background.interval.day}</option>
|
||||
<option value='604800000'>{window.language.widgets.date.week}</option>
|
||||
<option value='2628000000'>{background.interval.month}</option>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -148,12 +158,16 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
case 'colour': backgroundSettings = <ColourSettings/>; break;
|
||||
default: backgroundSettings = APISettings; break;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('photo_packs') && this.state.backgroundType !== 'custom' && this.state.backgroundType !== 'colour' && this.state.backgroundType !== 'api') {
|
||||
backgroundSettings = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{background.title}</h2>
|
||||
<Switch name='background' text={this.language.enabled} category='background' />
|
||||
<Checkbox name='ddgProxy' text={background.ddg_proxy} />
|
||||
<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' />
|
||||
|
||||
@@ -169,22 +183,22 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
{backgroundSettings}
|
||||
|
||||
<h3>{background.buttons.title}</h3>
|
||||
<Checkbox name='view' text={background.buttons.view} element='.other' />
|
||||
<Checkbox name='favouriteEnabled' text={background.buttons.favourite} element='.other' />
|
||||
<Checkbox name='view' text={background.buttons.view} category='navbar' />
|
||||
<Checkbox name='favouriteEnabled' text={background.buttons.favourite} category='navbar' />
|
||||
<Checkbox name='downloadbtn' text={background.buttons.download} element='.other' />
|
||||
|
||||
<h3>{background.effects.title}</h3>
|
||||
<Slider title={background.effects.blur} name='blur' min='0' max='100' default='0' display='%' category='background' />
|
||||
<Slider title={background.effects.brightness} name='brightness' min='0' max='100' default='90' display='%' category='background' />
|
||||
<Slider title={background.effects.blur} name='blur' min='0' max='100' default='0' display='%' category='background' element='#backgroundImage' />
|
||||
<Slider title={background.effects.brightness} name='brightness' min='0' max='100' default='90' display='%' category='background' element='#backgroundImage' />
|
||||
<br/><br/>
|
||||
<Dropdown label={background.effects.filters.title} name='backgroundFilter' category='background'>
|
||||
<Dropdown label={background.effects.filters.title} name='backgroundFilter' category='background' element='#backgroundImage'>
|
||||
<option value='grayscale'>{background.effects.filters.grayscale}</option>
|
||||
<option value='sepia'>{background.effects.filters.sepia}</option>
|
||||
<option value='invert'>{background.effects.filters.invert}</option>
|
||||
<option value='saturate'>{background.effects.filters.saturate}</option>
|
||||
<option value='contrast'>{background.effects.filters.contrast}</option>
|
||||
</Dropdown>
|
||||
<Slider title={background.effects.filters.amount} name='backgroundFilterAmount' min='0' max='100' default='0' display='%' category='background' />
|
||||
<Slider title={background.effects.filters.amount} name='backgroundFilterAmount' min='0' max='100' default='0' display='%' category='background' element='#backgroundImage' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export default class ColourSettings extends React.PureComponent {
|
||||
try {
|
||||
gradientSettings = JSON.parse(hex);
|
||||
} catch (e) {
|
||||
// Disregard exception.
|
||||
// Disregard exception
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ export default class ColourSettings extends React.PureComponent {
|
||||
};
|
||||
return newState;
|
||||
});
|
||||
|
||||
window.stats.postEvent('setting', 'Changed backgroundtype from colour to gradient');
|
||||
}
|
||||
|
||||
currentGradientSettings = () => {
|
||||
|
||||
@@ -8,8 +8,8 @@ export default function Addons() {
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<div label={addons.added}><Added/></div>
|
||||
<div label={addons.sideload}><Sideload/></div>
|
||||
<div label={addons.added} name='added'><Added/></div>
|
||||
<div label={addons.sideload} name='sideload'><Sideload/></div>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ export default function Marketplace() {
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<div label={marketplace.photo_packs}><MarketplaceTab type='photo_packs'/></div>
|
||||
<div label={marketplace.quote_packs}><MarketplaceTab type='quote_packs'/></div>
|
||||
<div label={marketplace.preset_settings}><MarketplaceTab type='preset_settings'/></div>
|
||||
<div label={marketplace.photo_packs} name='photo_packs'><MarketplaceTab type='photo_packs'/></div>
|
||||
<div label={marketplace.quote_packs} name='quote_packs'><MarketplaceTab type='quote_packs'/></div>
|
||||
<div label={marketplace.preset_settings} name='preset_settings'><MarketplaceTab type='preset_settings'/></div>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,22 +26,22 @@ export default function Settings() {
|
||||
return (
|
||||
<>
|
||||
<Tabs>
|
||||
<div label={sections.time.title}><Time/></div>
|
||||
<div label={sections.quote.title}><Quote/></div>
|
||||
<div label={sections.greeting.title}><Greeting/></div>
|
||||
<div label={sections.background.title}><Background/></div>
|
||||
<div label={sections.search.title}><Search/></div>
|
||||
<div label={sections.quicklinks.title}><QuickLinks/></div>
|
||||
<div label={sections.weather.title}><Weather/></div>
|
||||
<div label={sections.appearance.title}><Appearance/></div>
|
||||
<div label={sections.order.title}><Order/></div>
|
||||
<div label={sections.language.title}><Language/></div>
|
||||
<div label={sections.advanced.title}><Advanced/></div>
|
||||
<div label={sections.experimental.title}><Experimental/></div>
|
||||
<div label={sections.changelog}><Changelog/></div>
|
||||
<div label={sections.about.title}><About/></div>
|
||||
<div label={sections.time.title} name='time'><Time/></div>
|
||||
<div label={sections.quote.title} name='quote'><Quote/></div>
|
||||
<div label={sections.greeting.title} name='greeting'><Greeting/></div>
|
||||
<div label={sections.background.title} name='background'><Background/></div>
|
||||
<div label={sections.search.title} name='search'><Search/></div>
|
||||
<div label={sections.quicklinks.title} name='quicklinks'><QuickLinks/></div>
|
||||
<div label={sections.weather.title} name='weather'><Weather/></div>
|
||||
<div label={sections.appearance.title} name='appearance'><Appearance/></div>
|
||||
<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.experimental.title} name='experimental'><Experimental/></div>
|
||||
<div label={sections.changelog} name='changelog'><Changelog/></div>
|
||||
<div label={sections.about.title} name='about'><About/></div>
|
||||
</Tabs>
|
||||
<div className='reminder-info' style={{ 'display': display }}>
|
||||
<div className='reminder-info' style={{ display: display }}>
|
||||
<h1>{reminder.title}</h1>
|
||||
<p>{reminder.message}</p>
|
||||
<button className='pinNote' onClick={() => window.location.reload()}>{window.language.modals.main.error_boundary.refresh}</button>
|
||||
|
||||
@@ -64,12 +64,14 @@ function Tab(props) {
|
||||
case settings.changelog: icon = <Changelog/>; break;
|
||||
case settings.about.title: icon = <About/>; break;
|
||||
|
||||
// Store
|
||||
// Addons
|
||||
case addons.added: icon = <Added/>; break;
|
||||
case addons.sideload: icon = <Sideload/>; break;
|
||||
|
||||
// Marketplace
|
||||
case marketplace.photo_packs: icon = <Background/>; break;
|
||||
case marketplace.quote_packs: icon = <Quote/>; break;
|
||||
case marketplace.preset_settings: icon = <Advanced/>; break;
|
||||
case addons.added: icon = <Added/>; break;
|
||||
case addons.sideload: icon = <Sideload/>; break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
@@ -90,4 +92,4 @@ function Tab(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Tab);
|
||||
export default React.memo(Tab);
|
||||
|
||||
@@ -8,13 +8,19 @@ export default class Tabs extends React.PureComponent {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
currentTab: this.props.children[0].props.label
|
||||
currentTab: this.props.children[0].props.label,
|
||||
currentName: this.props.children[0].props.name
|
||||
};
|
||||
}
|
||||
|
||||
onClick = (tab) => {
|
||||
onClick = (tab, name) => {
|
||||
if (name !== this.state.currentName) {
|
||||
window.stats.postEvent('tab', `Changed ${this.state.currentName} to ${name}`);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentTab: tab
|
||||
currentTab: tab,
|
||||
currentName: name
|
||||
});
|
||||
};
|
||||
|
||||
@@ -28,7 +34,7 @@ export default class Tabs extends React.PureComponent {
|
||||
tabClass = '';
|
||||
optionsText = '';
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul className={className}>
|
||||
@@ -36,9 +42,9 @@ export default class Tabs extends React.PureComponent {
|
||||
{this.props.children.map((tab, index) => (
|
||||
<Tab
|
||||
currentTab={this.state.currentTab}
|
||||
key={tab.props.label || index}
|
||||
key={index}
|
||||
label={tab.props.label}
|
||||
onClick={this.onClick}
|
||||
onClick={(nextTab) => this.onClick(nextTab, tab.props.name)}
|
||||
navbar={this.props.navbar || false}
|
||||
/>
|
||||
))}
|
||||
|
||||
16
src/components/modals/welcome/ProgressBar.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export default function ProgressBar(props) {
|
||||
return (
|
||||
<div className='progressbar'>
|
||||
{props.count.map((num) => {
|
||||
let className = 'step';
|
||||
|
||||
const index = props.count.indexOf(num);
|
||||
if (index === props.currentTab) {
|
||||
className = 'step active';
|
||||
}
|
||||
|
||||
return <div className={className} key={index} onClick={() => props.switchTab(index)}></div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +1,103 @@
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
import TwitterIcon from '@material-ui/icons/Twitter';
|
||||
import ForumIcon from '@material-ui/icons/Forum';
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
|
||||
import WelcomeSections from './WelcomeSections';
|
||||
import ProgressBar from './ProgressBar';
|
||||
|
||||
import './welcome.scss';
|
||||
|
||||
export default function WelcomeModal(props) {
|
||||
const language = window.language.modals.welcome;
|
||||
export default class WelcomeModal extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
image: './././icons/undraw_celebration.svg',
|
||||
currentTab: 0,
|
||||
finalTab: 4,
|
||||
buttonText: window.language.modals.welcome.buttons.next
|
||||
};
|
||||
this.language = window.language.modals.welcome;
|
||||
this.images = [
|
||||
'./././icons/undraw_celebration.svg',
|
||||
'./././icons/undraw_around_the_world_modified.svg',
|
||||
'./././icons/undraw_add_files_modified.svg',
|
||||
'./././icons/undraw_dark_mode.svg',
|
||||
'./././icons/undraw_private_data_modified.svg',
|
||||
'./././icons/undraw_upgrade_modified.svg'
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='welcomeContent'>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<div className='welcomeModalText'>
|
||||
<h2 className='subtitle'>{language.title}</h2>
|
||||
<h1 className='welcometitle'>Mue Tab</h1>
|
||||
<img alt='celebration' style={{ 'height': '200px', 'width': 'auto' }} draggable={false} src='./././icons/undraw_celebration.svg' />
|
||||
<h2 className='subtitle'>{language.information}</h2>
|
||||
<p>{language.thankyoumessage1}<br/> {language.thankyoumessage2}</p>
|
||||
<h2 className='subtitle'>{language.support}</h2>
|
||||
<a href='mailto:hello@muetab.com' className='welcomeLink' target='_blank' rel='noopener noreferrer'><EmailIcon/></a>
|
||||
<a href='https://twitter.com/getmue' className='welcomeLink' target='_blank' rel='noopener noreferrer'><TwitterIcon/></a>
|
||||
<a href='https://discord.gg/zv8C9F8' className='welcomeLink' target='_blank' rel='noopener noreferrer'><ForumIcon/></a>
|
||||
<br/>
|
||||
<button className='close' onClick={props.modalClose}>{language.close}</button>
|
||||
changeTab(minus) {
|
||||
localStorage.setItem('bgtransition', true);
|
||||
localStorage.removeItem('welcomeTab');
|
||||
|
||||
if (minus) {
|
||||
return this.setState({
|
||||
currentTab: this.state.currentTab - 1,
|
||||
image: this.images[this.state.currentTab - 1],
|
||||
buttonText: this.language.buttons.next
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.buttonText === this.language.buttons.close) {
|
||||
return this.props.modalClose();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentTab: this.state.currentTab + 1,
|
||||
image: this.images[this.state.currentTab + 1],
|
||||
buttonText: (this.state.currentTab !== this.state.finalTab) ? this.language.buttons.next : this.language.buttons.close
|
||||
});
|
||||
}
|
||||
|
||||
// specific
|
||||
switchTab(tab) {
|
||||
this.setState({
|
||||
currentTab: tab,
|
||||
image: this.images[tab],
|
||||
buttonText: (tab !== this.state.finalTab + 1) ? this.language.buttons.next : this.language.buttons.close
|
||||
});
|
||||
|
||||
localStorage.setItem('bgtransition', true);
|
||||
localStorage.removeItem('welcomeTab');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const welcomeTab = localStorage.getItem('welcomeTab');
|
||||
if (welcomeTab) {
|
||||
this.setState({
|
||||
currentTab: Number(welcomeTab),
|
||||
image: this.images[Number(welcomeTab)],
|
||||
buttonText: (Number(welcomeTab) !== this.state.finalTab + 1) ? this.language.buttons.next : this.language.buttons.close
|
||||
});
|
||||
}
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'welcomeLanguage') {
|
||||
localStorage.setItem('welcomeTab', this.state.currentTab);
|
||||
localStorage.setItem('bgtransition', false);
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='welcomeContent'>
|
||||
<section>
|
||||
<img className='showcaseimg' alt='celebration' draggable={false} src={this.state.image} />
|
||||
<ProgressBar count={this.images} currentTab={this.state.currentTab} switchTab={(tab) => this.switchTab(tab)}/>
|
||||
</section>
|
||||
<section>
|
||||
<div className='content'>
|
||||
<WelcomeSections currentTab={this.state.currentTab} switchTab={(tab) => this.switchTab(tab)}/>
|
||||
</div>
|
||||
<div className='buttons'>
|
||||
{(this.state.currentTab !== 0) ? <button className='close' style={{ marginRight: '20px' }} onClick={() => this.changeTab(true)}>{this.language.buttons.previous}</button> : null}
|
||||
<button className='close' onClick={() => this.changeTab()}>{this.state.buttonText}</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
217
src/components/modals/welcome/WelcomeSections.jsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React from 'react';
|
||||
|
||||
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';
|
||||
|
||||
const languages = require('../../../modules/languages.json');
|
||||
const default_settings = require('../../../modules/default_settings.json');
|
||||
|
||||
export default class WelcomeSections extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
// themes
|
||||
autoClass: 'toggle auto active',
|
||||
lightClass: 'toggle lightTheme',
|
||||
darkClass: 'toggle darkTheme',
|
||||
// welcome
|
||||
welcomeImage: 0,
|
||||
// final
|
||||
importedSettings: []
|
||||
};
|
||||
this.changeWelcomeImg = this.changeWelcomeImg.bind(this);
|
||||
this.welcomeImages = ['./welcome-images/example1.webp', './welcome-images/example2.webp', './welcome-images/example3.webp', './welcome-images/example4.webp'];
|
||||
}
|
||||
|
||||
changeTheme(type) {
|
||||
this.setState({
|
||||
autoClass: (type === 'auto') ? 'toggle auto active' : 'toggle auto',
|
||||
lightClass: (type === 'light') ? 'toggle lightTheme active' : 'toggle lightTheme',
|
||||
darkClass: (type === 'dark') ? 'toggle darkTheme active': 'toggle darkTheme'
|
||||
});
|
||||
|
||||
localStorage.setItem('theme', type);
|
||||
SettingsFunctions.loadSettings(true);
|
||||
}
|
||||
|
||||
getSetting(name) {
|
||||
const value = localStorage.getItem(name).replace('false', 'Off').replace('true', 'On');
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
|
||||
importSettings(e) {
|
||||
SettingsFunctionsModal.importSettings(e);
|
||||
|
||||
let settings = [];
|
||||
const data = JSON.parse(e.target.result);
|
||||
Object.keys(data).forEach((setting) => {
|
||||
if (setting === 'language' || setting === 'theme'|| setting === 'firstRun' || setting === 'showWelcome' || setting === 'showReminder') {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultSetting = default_settings.find((i) => i.name === setting);
|
||||
if (defaultSetting !== undefined) {
|
||||
if (data[setting] === String(defaultSetting.value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
settings.push({
|
||||
name: setting,
|
||||
value: data[setting]
|
||||
});
|
||||
});
|
||||
|
||||
this.setState({
|
||||
importedSettings: settings
|
||||
});
|
||||
|
||||
this.props.switchTab(5);
|
||||
}
|
||||
|
||||
changeWelcomeImg() {
|
||||
let welcomeImage = this.state.welcomeImage;
|
||||
|
||||
this.setState({
|
||||
welcomeImage: ++welcomeImage % this.welcomeImages.length
|
||||
});
|
||||
|
||||
this.timeout = setTimeout(this.changeWelcomeImg, 3 * 1000);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.timeout = setTimeout(this.changeWelcomeImg, 3 * 1000);
|
||||
}
|
||||
|
||||
// cancel welcome image timer if not on welcome tab
|
||||
componentDidUpdate() {
|
||||
if (this.props.currentTab !== 0) {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
} else {
|
||||
if (!this.timeout) {
|
||||
this.timeout = setTimeout(this.changeWelcomeImg, 3 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const language = window.language.modals.welcome;
|
||||
let tabContent;
|
||||
|
||||
const intro = (
|
||||
<>
|
||||
<h1>{language.sections.intro.title}</h1>
|
||||
<p>{language.sections.intro.description}</p>
|
||||
<h3 className='quicktip'>#shareyourmue</h3>
|
||||
<div className='examples'>
|
||||
<img src={this.welcomeImages[this.state.welcomeImage]} alt='Example Mue setup' draggable={false}/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const chooseLanguage = (
|
||||
<>
|
||||
<h1>{language.sections.language.title}</h1>
|
||||
<p>{language.sections.language.description}</p>
|
||||
<Radio name='language' options={languages} category='welcomeLanguage'/>
|
||||
</>
|
||||
);
|
||||
|
||||
const { appearance, advanced, background, quicklinks } = window.language.modals.main.settings.sections;
|
||||
const languageSettings = window.language.modals.main.settings.sections.language;
|
||||
|
||||
const theme = (
|
||||
<>
|
||||
<h1>{language.sections.theme.title}</h1>
|
||||
<p>{language.sections.theme.description}</p>
|
||||
<div className='themesToggleArea'>
|
||||
<div className={this.state.autoClass} onClick={() => this.changeTheme('auto')}>
|
||||
<AutoIcon/>
|
||||
<span>{appearance.theme.auto}</span>
|
||||
</div>
|
||||
<div className='options'>
|
||||
<div className={this.state.lightClass} onClick={() => this.changeTheme('light')}>
|
||||
<LightModeIcon/>
|
||||
<span>{appearance.theme.light}</span>
|
||||
</div>
|
||||
<div className={this.state.darkClass} onClick={() => this.changeTheme('dark')}>
|
||||
<DarkModeIcon/>
|
||||
<span>{appearance.theme.dark}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className='quicktip'>{language.tip}</h3>
|
||||
<p>{language.sections.theme.tip}</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const settings = (
|
||||
<>
|
||||
<h1>{language.sections.settings.title}</h1>
|
||||
<p>{language.sections.settings.description}</p>
|
||||
<button className='upload' onClick={() => document.getElementById('file-input').click()}>
|
||||
<UploadIcon/>
|
||||
<br/>
|
||||
<span>{window.language.modals.main.settings.buttons.import}</span>
|
||||
</button>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.importSettings(e)}/>
|
||||
<h3 className='quicktip'>{language.tip}</h3>
|
||||
<p>{language.sections.settings.tip}</p>
|
||||
</>
|
||||
);
|
||||
|
||||
const privacy = (
|
||||
<>
|
||||
<h1>{language.sections.privacy.title}</h1>
|
||||
<p>{language.sections.privacy.description}</p>
|
||||
<Checkbox name='offlineMode' text={advanced.offline_mode} element='.other' />
|
||||
<p>{language.sections.privacy.offline_mode_description}</p>
|
||||
<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>
|
||||
<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/>
|
||||
<a className='privacy' href={'https://github.com/' + window.constants.ORG_NAME} target='_blank' rel='noopener noreferrer'>{language.sections.privacy.links.source_code}</a>
|
||||
</>
|
||||
);
|
||||
|
||||
const final = (
|
||||
<>
|
||||
<h1>{language.sections.final.title}</h1>
|
||||
<p>{language.sections.final.description}</p>
|
||||
<h3 className='quicktip'>{language.sections.final.changes}</h3>
|
||||
<p>{language.sections.final.changes_description}</p>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='toggle' onClick={() => this.props.switchTab(1)}><span>{languageSettings.title}: {languages.find((i) => i.value === localStorage.getItem('language')).name}</span></div>
|
||||
<div className='toggle' onClick={() => this.props.switchTab(3)}><span>{appearance.theme.title}: {this.getSetting('theme')}</span></div>
|
||||
{(this.state.importedSettings.length !== 0) ? <div className='toggle' onClick={() => this.props.switchTab(2)}>{language.sections.final.imported} {this.state.importedSettings.length} {language.sections.final.settings}</div> : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
switch (this.props.currentTab) {
|
||||
case 1: tabContent = chooseLanguage; break;
|
||||
case 2: tabContent = settings; break;
|
||||
case 3: tabContent = theme; break;
|
||||
case 4: tabContent = privacy; break;
|
||||
case 5: tabContent = final; break;
|
||||
// 0
|
||||
default: tabContent = intro;
|
||||
}
|
||||
|
||||
return tabContent;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,176 @@
|
||||
@import '../main/scss/index.scss';
|
||||
|
||||
.welcomemodal {
|
||||
margin-top: 40px;
|
||||
}
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 80%;
|
||||
width: 60%;
|
||||
padding: 0;
|
||||
|
||||
.welcomeModalText {
|
||||
line-height: 2px;
|
||||
|
||||
h2.subtitle {
|
||||
font-size: 24px;
|
||||
color: var(--modal-text);
|
||||
text-transform: uppercase;
|
||||
section {
|
||||
width: 50%;
|
||||
display: inline;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
h1.welcometitle {
|
||||
font-size: 50px;
|
||||
section:nth-child(1) {
|
||||
float: left;
|
||||
background: var(--sidebar);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
section:nth-child(2) {
|
||||
float: right;
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
h3.quicktip {
|
||||
text-transform: uppercase;
|
||||
color: #5352ed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeContent {
|
||||
margin-top: 2em;
|
||||
.welcomeoverlay {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.progressbar {
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
text-align: center;
|
||||
padding: 25px;
|
||||
display: inline;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
line-height: 20px !important;
|
||||
color: var(--modal-link);
|
||||
.step {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
background: #8395a7;
|
||||
height: 4px;
|
||||
margin: 10px;
|
||||
transition: .2s ease;
|
||||
cursor: pointer;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
img.icon,
|
||||
svg {
|
||||
margin-top: -12px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
transition: ease 0.2s;
|
||||
.active {
|
||||
background: #5352ed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.themesToggleArea {
|
||||
.active {
|
||||
background: var(--tab-active) !important;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
background: var(--sidebar);
|
||||
text-align: center;
|
||||
border-radius: 40px;
|
||||
padding: 20px;
|
||||
margin: 10px;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
background: var(--tab-active);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0.7rem;
|
||||
line-height: 1em;
|
||||
.auto {
|
||||
svg {
|
||||
font-size: 12px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
.options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.lightTheme,
|
||||
.darkTheme {
|
||||
width: 40%;
|
||||
padding: 50px;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeLink {
|
||||
color: var(--modal-text) !important;
|
||||
a.privacy {
|
||||
text-decoration: none;
|
||||
color: var(--modal-text);
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover {
|
||||
color: #5352ed;
|
||||
}
|
||||
}
|
||||
|
||||
.examples {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 30rem !important;
|
||||
height: auto !important;
|
||||
display: block;
|
||||
margin: 30px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.showcaseimg {
|
||||
width: 350px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1440px) {
|
||||
.buttons {
|
||||
position: relative !important;
|
||||
bottom: 0rem !important;
|
||||
}
|
||||
|
||||
.examples img {
|
||||
width: 15rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1600px) {
|
||||
.examples img {
|
||||
width: 20rem !important;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
position: relative !important;
|
||||
bottom: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ export default class Widgets extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
// don't show when welcome is there
|
||||
if (localStorage.getItem('showWelcome') !== 'false') {
|
||||
return <div id='widgets'></div>;
|
||||
}
|
||||
|
||||
// allow for re-ordering widgets
|
||||
let elements = [];
|
||||
|
||||
@@ -52,7 +57,7 @@ export default class Widgets extends React.PureComponent {
|
||||
});
|
||||
} else {
|
||||
// prevent error
|
||||
elements = ['greeting', 'time', 'quicklinks', 'quote', 'date'];
|
||||
elements = [<Greeting/>, <Clock/>, <QuickLinks/>, <Quote/>, <Date/>];
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -60,7 +65,7 @@ export default class Widgets extends React.PureComponent {
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
{this.enabled('searchBar') ? <Search/> : null}
|
||||
{elements}
|
||||
{this.enabled('weatherEnabled') && !localStorage.getItem('offlineMode') ? <Weather/> : null}
|
||||
{this.enabled('weatherEnabled') && (localStorage.getItem('offlineMode') === 'false') ? <Weather/> : null}
|
||||
</React.Suspense>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// warning: the code here is fairly messy and probably needs a rewrite
|
||||
// todo: rewrite this mess
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
import Interval from '../../../modules/helpers/interval';
|
||||
|
||||
import PhotoInformation from './PhotoInformation';
|
||||
|
||||
@@ -55,20 +56,23 @@ export default class Background extends React.PureComponent {
|
||||
Math.floor(Math.random() * offlineImages[photographer].photo.length)
|
||||
];
|
||||
|
||||
this.setState({
|
||||
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');
|
||||
|
||||
if (this.state.url !== '') {
|
||||
const url = (localStorage.getItem('ddgProxy') === 'true' && this.state.photoInfo.offline !== true) ? window.constants.DDG_PROXY + this.state.url : this.state.url;
|
||||
const url = (localStorage.getItem('ddgProxy') === 'true' && this.state.photoInfo.offline !== true && !this.state.url.startsWith('data:')) ? window.constants.DDG_IMAGE_PROXY + this.state.url : this.state.url;
|
||||
const photoInformation = document.querySelector('.photoInformation');
|
||||
|
||||
// just set the background
|
||||
@@ -98,7 +102,6 @@ export default class Background extends React.PureComponent {
|
||||
backgroundImage.classList.remove('backgroundPreload');
|
||||
backgroundImage.classList.add('fade-in');
|
||||
|
||||
// this doesn't make it fetch again which is nice
|
||||
backgroundImage.style.background = `url(${url})`;
|
||||
// remove the preloader element we created earlier
|
||||
preloader.remove();
|
||||
@@ -116,7 +119,10 @@ export default class Background extends React.PureComponent {
|
||||
|
||||
// Main background getting function
|
||||
async getBackground() {
|
||||
const offline = (localStorage.getItem('offlineMode') === 'true');
|
||||
let offline = (localStorage.getItem('offlineMode') === 'true');
|
||||
if (localStorage.getItem('showWelcome') === 'true') {
|
||||
offline = true;
|
||||
}
|
||||
|
||||
switch (localStorage.getItem('backgroundType')) {
|
||||
case 'api':
|
||||
@@ -132,8 +138,7 @@ export default class Background extends React.PureComponent {
|
||||
photoInfo: {
|
||||
credit: favourited.credit,
|
||||
location: favourited.location,
|
||||
camera: favourited.camera,
|
||||
resolution: favourited.resolution
|
||||
camera: favourited.camera
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -146,11 +151,10 @@ export default class Background extends React.PureComponent {
|
||||
let requestURL, data;
|
||||
switch (backgroundAPI) {
|
||||
case 'unsplash':
|
||||
//requestURL = `${window.constants.UNSPLASH_URL}/getImage?category=${apiCategory}`;
|
||||
requestURL = `${window.constants.UNSPLASH_URL}/images/random?quality=${apiQuality}`;
|
||||
requestURL = `${window.constants.PROXY_URL}/images/unsplash?quality=${apiQuality}`;
|
||||
break;
|
||||
case 'pexels':
|
||||
requestURL = `${window.constants.PEXELS_URL}/images/random?quality=${apiQuality}`;
|
||||
requestURL = `${window.constants.PROXY_URL}/images/pexels?quality=${apiQuality}`;
|
||||
break;
|
||||
// Defaults to Mue
|
||||
default:
|
||||
@@ -166,8 +170,7 @@ export default class Background extends React.PureComponent {
|
||||
}
|
||||
|
||||
let credit = data.photographer;
|
||||
let photoURL = '';
|
||||
let photographerURL = '';
|
||||
let photoURL, photographerURL;
|
||||
|
||||
if (backgroundAPI === 'unsplash') {
|
||||
credit = data.photographer + ` ${this.language.unsplash}`;
|
||||
@@ -179,7 +182,7 @@ export default class Background extends React.PureComponent {
|
||||
photographerURL = data.photographer_page;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
const object = {
|
||||
url: data.file,
|
||||
type: 'api',
|
||||
currentAPI: backgroundAPI,
|
||||
@@ -188,12 +191,14 @@ export default class Background extends React.PureComponent {
|
||||
credit: credit,
|
||||
location: data.location,
|
||||
camera: data.camera,
|
||||
resolution: data.resolution,
|
||||
url: data.file,
|
||||
photographerURL: photographerURL,
|
||||
photoURL: photoURL
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setState(object);
|
||||
|
||||
localStorage.setItem('currentBackground', JSON.stringify(object));
|
||||
break;
|
||||
|
||||
case 'colour':
|
||||
@@ -279,6 +284,10 @@ export default class Background extends React.PureComponent {
|
||||
};
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'welcomeLanguage') {
|
||||
localStorage.setItem('welcomeImage', JSON.stringify(this.state));
|
||||
}
|
||||
|
||||
if (data === 'background') {
|
||||
if (localStorage.getItem('background') === 'false') {
|
||||
// user is using custom colour or image
|
||||
@@ -299,10 +308,11 @@ export default class Background extends React.PureComponent {
|
||||
document.getElementById('backgroundVideo').style.display = 'block';
|
||||
} else {
|
||||
if (this.state.photoInfo.hidden === false) {
|
||||
// fix bug
|
||||
try {
|
||||
document.querySelector('.photoInformation').style.display = 'block';
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// Disregard exception
|
||||
}
|
||||
}
|
||||
|
||||
element.style.display = 'block';
|
||||
@@ -315,6 +325,10 @@ export default class Background extends React.PureComponent {
|
||||
if (backgroundType !== this.state.type || (this.state.type === 'api' && localStorage.getItem('backgroundAPI') !== this.state.currentAPI) || (this.state.type === 'custom' && localStorage.getItem('customBackground') !== this.state.url)) {
|
||||
return refresh();
|
||||
}
|
||||
} else {
|
||||
if (backgroundType !== this.state.type) {
|
||||
return refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// background effects so we don't get another image again
|
||||
@@ -328,12 +342,48 @@ export default class Background extends React.PureComponent {
|
||||
}
|
||||
|
||||
// uninstall photo pack reverts your background to what you had previously
|
||||
if (data === 'marketplacebackgrounduninstall') {
|
||||
if (data === 'marketplacebackgrounduninstall' || data === 'backgroundwelcome') {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.getBackground();
|
||||
if (localStorage.getItem('welcomeTab')) {
|
||||
return this.setState(JSON.parse(localStorage.getItem('welcomeImage')));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
this.setState(current);
|
||||
}
|
||||
} catch (e) {
|
||||
this.setBackground();
|
||||
}
|
||||
} else {
|
||||
this.getBackground();
|
||||
}
|
||||
}
|
||||
|
||||
// only set once we've got the info
|
||||
@@ -352,7 +402,7 @@ export default class Background extends React.PureComponent {
|
||||
};
|
||||
|
||||
return (
|
||||
<video autoPlay muted={enabled('backgroundVideoMute')} loop={enabled('backgroundVideoLoop')} style={{ 'WebkitFilter': `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%)` }} id='backgroundVideo'>
|
||||
<video autoPlay muted={enabled('backgroundVideoMute')} loop={enabled('backgroundVideoLoop')} style={{ WebkitFilter: `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%)` }} id='backgroundVideo'>
|
||||
<source src={this.state.url}/>
|
||||
</video>
|
||||
);
|
||||
@@ -362,9 +412,9 @@ 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'/>
|
||||
<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}/>
|
||||
<PhotoInformation className={this.props.photoInformationClass} info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,19 +6,25 @@ import StarIcon from '@material-ui/icons/Star';
|
||||
import StarIcon2 from '@material-ui/icons/StarBorder';
|
||||
|
||||
export default class Favourite extends React.PureComponent {
|
||||
buttons = {
|
||||
favourited: <StarIcon onClick={() => this.favourite()} className='topicons' />,
|
||||
unfavourited: <StarIcon2 onClick={() => this.favourite()} className='topicons' />
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
favourited: (localStorage.getItem('favourite')) ? <StarIcon onClick={this.favourite} className='topicons' /> : <StarIcon2 onClick={this.favourite} className='topicons' />
|
||||
favourited: (localStorage.getItem('favourite')) ? this.buttons.favourited : this.buttons.unfavourited
|
||||
};
|
||||
}
|
||||
|
||||
favourite = () => {
|
||||
favourite() {
|
||||
if (localStorage.getItem('favourite')) {
|
||||
localStorage.removeItem('favourite');
|
||||
this.setState({
|
||||
favourited: <StarIcon2 onClick={this.favourite} className='topicons' />
|
||||
favourited: this.buttons.unfavourited
|
||||
});
|
||||
window.stats.postEvent('feature', 'Background favourite');
|
||||
} else {
|
||||
const url = document.getElementById('backgroundImage').style.backgroundImage.replace('url("', '').replace('")', '');
|
||||
|
||||
@@ -30,13 +36,13 @@ export default class Favourite extends React.PureComponent {
|
||||
url: url,
|
||||
credit: document.getElementById('credit').textContent,
|
||||
location: document.getElementById('infoLocation').textContent,
|
||||
camera: document.getElementById('infoCamera').textContent,
|
||||
resolution: document.getElementById('infoResolution').textContent
|
||||
camera: document.getElementById('infoCamera').textContent
|
||||
}));
|
||||
|
||||
this.setState({
|
||||
favourited: <StarIcon onClick={this.favourite} className='topicons' />
|
||||
favourited: this.buttons.favourited
|
||||
});
|
||||
window.stats.postEvent('feature', 'Background unfavourite');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ export default class Maximise extends React.PureComponent {
|
||||
}
|
||||
|
||||
setAttribute(blur, brightness, filter) {
|
||||
// don't attempt to modify the background if it isn't an image
|
||||
const backgroundType = localStorage.getItem('backgroundType');
|
||||
if (backgroundType === 'colour') {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById('backgroundImage');
|
||||
|
||||
let backgroundFilter;
|
||||
@@ -27,16 +33,9 @@ export default class Maximise extends React.PureComponent {
|
||||
}
|
||||
|
||||
maximise = () => {
|
||||
// elements to hide
|
||||
const elements = ['.searchBar', '.clock', '.greeting', '.quotediv', 'time', '.quicklinks-container', '.weather', '.date'];
|
||||
|
||||
elements.forEach((element) => {
|
||||
try {
|
||||
(this.state.hidden === false) ? document.querySelector(element).style.display = 'none' : document.querySelector(element).style.display = 'block';
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
// hide widgets
|
||||
const widgets = document.getElementById('widgets');
|
||||
(this.state.hidden === false) ? widgets.style.display = 'none' : widgets.style.display = 'block';
|
||||
|
||||
if (this.state.hidden === false) {
|
||||
this.setState({
|
||||
@@ -44,12 +43,14 @@ export default class Maximise extends React.PureComponent {
|
||||
});
|
||||
|
||||
this.setAttribute(0, 100);
|
||||
window.stats.postEvent('feature', 'Background maximise');
|
||||
} else {
|
||||
this.setState({
|
||||
hidden: false
|
||||
});
|
||||
|
||||
this.setAttribute(localStorage.getItem('blur'), localStorage.getItem('brightness'), true);
|
||||
window.stats.postEvent('feature', 'Background unmaximise');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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';
|
||||
@@ -18,9 +20,13 @@ const downloadImage = async (info) => {
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.stats.postEvent('feature', 'Background download');
|
||||
};
|
||||
|
||||
export default function PhotoInformation(props) {
|
||||
const [width, setWidth] = React.useState(0);
|
||||
const [height, setHeight] = React.useState(0);
|
||||
|
||||
const language = window.language.widgets.background;
|
||||
|
||||
if (props.info.hidden === true || !props.info.credit) {
|
||||
@@ -44,10 +50,20 @@ export default function PhotoInformation(props) {
|
||||
}
|
||||
}
|
||||
|
||||
// get resolution
|
||||
const img = new Image();
|
||||
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;
|
||||
|
||||
return (
|
||||
<div className='photoInformation'>
|
||||
<h1>{photo} <span id='credit'>{credit}</span></h1>
|
||||
{localStorage.getItem('photoInformation') !== 'false' ? <><Info className='photoInformationHover'/>
|
||||
{localStorage.getItem('photoInformation') !== 'false' ?
|
||||
<>
|
||||
<Info className='photoInformationHover'/>
|
||||
<div className={props.className || 'infoCard'}>
|
||||
<Info className='infoIcon'/>
|
||||
<h1>{language.information}</h1>
|
||||
@@ -57,7 +73,7 @@ export default function PhotoInformation(props) {
|
||||
<Camera/>
|
||||
<span id='infoCamera'>{props.info.camera || 'N/A'}</span>
|
||||
<Resolution/>
|
||||
<span id='infoResolution'>{props.info.resolution || 'N/A'}</span>
|
||||
<span id='infoResolution'>{width}x{height}</span>
|
||||
<Photographer/>
|
||||
<span>{photographer}</span>
|
||||
{(localStorage.getItem('downloadbtn') === 'true') && !props.info.offline && !props.info.photographerURL ?
|
||||
@@ -66,7 +82,7 @@ export default function PhotoInformation(props) {
|
||||
<span className='download' onClick={() => downloadImage(props.info)}>{language.download}</span>
|
||||
</> : null}
|
||||
</div>
|
||||
</>: null}
|
||||
</> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
left: 0.7em;
|
||||
padding: 1rem;
|
||||
border-radius: 24px 24px 24px 0;
|
||||
max-width: 500px;
|
||||
width: 300px !important;
|
||||
text-align: left;
|
||||
text-shadow: none;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
import { utcToZonedTime } from 'date-fns-tz';
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
|
||||
import dtf from '../../../modules/helpers/date';
|
||||
|
||||
import './greeting.scss';
|
||||
@@ -46,7 +48,12 @@ export default class Greeting extends React.PureComponent {
|
||||
|
||||
getGreeting(time = (60000 - Date.now() % 60000)) {
|
||||
this.timer = setTimeout(() => {
|
||||
const now = new Date();
|
||||
let now = new Date();
|
||||
const timezone = localStorage.getItem('timezone');
|
||||
if (timezone) {
|
||||
now = utcToZonedTime(now, timezone);
|
||||
}
|
||||
|
||||
const hour = now.getHours();
|
||||
|
||||
// Set the default greeting string to "Good evening"
|
||||
@@ -103,7 +110,7 @@ export default class Greeting extends React.PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'greeting') {
|
||||
if (data === 'greeting' || data === 'timezone') {
|
||||
const element = document.querySelector('.greeting');
|
||||
|
||||
if (localStorage.getItem('greeting') === 'false') {
|
||||
@@ -114,7 +121,7 @@ export default class Greeting extends React.PureComponent {
|
||||
this.getGreeting(0);
|
||||
|
||||
element.style.display = 'block';
|
||||
element.style.fontSize = `${1.6 * Number(localStorage.getItem('zoomGreeting') / 100)}em`;
|
||||
element.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -125,6 +132,10 @@ export default class Greeting extends React.PureComponent {
|
||||
this.getGreeting(0);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.remove('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
return <h1 className='greeting'>
|
||||
{this.state.greeting}
|
||||
|
||||
@@ -10,38 +10,54 @@ import Maximise from '../background/Maximise';
|
||||
import Favourite from '../background/Favourite';
|
||||
import Tooltip from '../../helpers/tooltip/Tooltip';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
|
||||
import './scss/index.scss';
|
||||
|
||||
export default function Navbar(props) {
|
||||
const backgroundEnabled = (localStorage.getItem('background') === 'true');
|
||||
export default class Navbar extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'navbar') {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navbar-container'>
|
||||
{(localStorage.getItem('view') === 'true' && backgroundEnabled) ? <Maximise/> : null}
|
||||
{(localStorage.getItem('favouriteEnabled') === 'true' && backgroundEnabled) ? <Favourite/> : null}
|
||||
render() {
|
||||
if (localStorage.getItem('showWelcome') !== 'false') {
|
||||
return null;
|
||||
}
|
||||
|
||||
{(localStorage.getItem('notesEnabled') === 'true') ?
|
||||
<div className='notes'>
|
||||
<NotesIcon className='topicons'/>
|
||||
<Notes/>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{(window.constants.BETA_VERSION === true) ?
|
||||
<Tooltip title={window.language.widgets.navbar.tooltips.feedback}>
|
||||
<Report className='topicons' onClick={() => props.openModal('feedbackModal')}/>
|
||||
const backgroundEnabled = (localStorage.getItem('background') === 'true');
|
||||
|
||||
return (
|
||||
<div className='navbar-container'>
|
||||
{(localStorage.getItem('view') === 'true' && backgroundEnabled) ? <Maximise/> : null}
|
||||
{(localStorage.getItem('favouriteEnabled') === 'true' && backgroundEnabled) ? <Favourite/> : null}
|
||||
|
||||
{(localStorage.getItem('notesEnabled') === 'true') ?
|
||||
<div className='notes'>
|
||||
<NotesIcon className='topicons'/>
|
||||
<Notes/>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{(window.constants.BETA_VERSION === true) ?
|
||||
<Tooltip title={window.language.widgets.navbar.tooltips.feedback}>
|
||||
<Report className='topicons' onClick={() => this.props.openModal('feedbackModal')}/>
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
{(localStorage.getItem('refresh') === 'true') ?
|
||||
<Tooltip title={window.language.widgets.navbar.tooltips.refresh}>
|
||||
<RefreshIcon 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')}/>
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
{(localStorage.getItem('refresh') === 'true') ?
|
||||
<Tooltip title={window.language.widgets.navbar.tooltips.refresh}>
|
||||
<RefreshIcon className='refreshicon topicons' onClick={() => window.location.reload()}/>
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
<Tooltip title={window.language.modals.main.navbar.settings}>
|
||||
<Gear className='settings-icon topicons' onClick={() => props.openModal('mainModal')}/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import TextareaAutosize from '@material-ui/core/TextareaAutosize';
|
||||
|
||||
import CopyIcon from '@material-ui/icons/FileCopyRounded';
|
||||
import NotesIcon from '@material-ui/icons/AssignmentRounded';
|
||||
import Pin from './Pin';
|
||||
import Pin from '@material-ui/icons/PushPin';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -12,7 +12,8 @@ export default class Notes extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
notes: localStorage.getItem('notes') || ''
|
||||
notes: localStorage.getItem('notes') || '',
|
||||
visibility: (localStorage.getItem('notesPinned') === 'true') ? 'visible' : 'hidden'
|
||||
};
|
||||
this.language = window.language.widgets.navbar.notes;
|
||||
}
|
||||
@@ -25,43 +26,43 @@ export default class Notes extends React.PureComponent {
|
||||
};
|
||||
|
||||
pin() {
|
||||
document.getElementById('noteContainer').classList.toggle('visibilityshow');
|
||||
window.stats.postEvent('feature', 'Notes pin');
|
||||
|
||||
if (localStorage.getItem('notesPinned') === 'true') {
|
||||
localStorage.setItem('notesPinned', false);
|
||||
this.setState({
|
||||
visibility: 'hidden'
|
||||
});
|
||||
} else {
|
||||
localStorage.setItem('notesPinned', true);
|
||||
this.setState({
|
||||
visibility: 'visible'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
copy() {
|
||||
// this.state.notes doesnt work for some reason
|
||||
navigator.clipboard.writeText(localStorage.getItem('notes'));
|
||||
window.stats.postEvent('feature', 'Notes copied');
|
||||
navigator.clipboard.writeText(this.state.notes);
|
||||
toast(window.language.toasts.notes);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const noteContainer = document.getElementById('noteContainer');
|
||||
|
||||
if (localStorage.getItem('notesPinned') === 'true') {
|
||||
noteContainer.classList.toggle('visibilityshow');
|
||||
}
|
||||
|
||||
if (localStorage.getItem('refresh') === 'false') {
|
||||
noteContainer.style.marginLeft = '-200px';
|
||||
document.getElementById('noteContainer').style.marginLeft = '-200px';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span id='noteContainer' className='notescontainer'>
|
||||
<span id='noteContainer' className='notescontainer' style={{ visibility: this.state.visibility }}>
|
||||
<div className='topbarnotes'>
|
||||
<NotesIcon/>
|
||||
<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>
|
||||
<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>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// License for original pin SVG below
|
||||
/*
|
||||
Copyright 2020 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
export default function Pin() {
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' enableBackground='new 0 0 24 24' viewBox='0 0 24 24' fill='black' width='18px' height='18px'>
|
||||
<g><rect fill='none' height='24' width='24'/></g>
|
||||
<g><path d='M16,9V4l1,0c0.55,0,1-0.45,1-1v0c0-0.55-0.45-1-1-1H7C6.45,2,6,2.45,6,3v0 c0,0.55,0.45,1,1,1l1,0v5c0,1.66-1.34,3-3,3h0v2h5.97v7l1,1l1-1v-7H19v-2h0C17.34,12,16,10.66,16,9z' fillRule='evenodd'/></g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
&:hover .notescontainer {
|
||||
visibility: visible;
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.visibilityshow {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ export default class QuickLinks extends React.PureComponent {
|
||||
this.setState({
|
||||
items: data
|
||||
});
|
||||
|
||||
window.stats.postEvent('feature', 'Quicklink delete');
|
||||
}
|
||||
|
||||
addLink = () => {
|
||||
@@ -73,7 +75,16 @@ export default class QuickLinks extends React.PureComponent {
|
||||
url: ''
|
||||
});
|
||||
|
||||
window.stats.postEvent('feature', 'Quicklink add');
|
||||
|
||||
this.toggleAdd();
|
||||
|
||||
// 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`;
|
||||
};
|
||||
}
|
||||
|
||||
toggleAdd = () => {
|
||||
@@ -88,7 +99,20 @@ export default class QuickLinks extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
@@ -98,6 +122,8 @@ export default class QuickLinks extends React.PureComponent {
|
||||
}
|
||||
|
||||
element.style.display = 'block';
|
||||
this.setZoom(element);
|
||||
|
||||
this.setState({
|
||||
items: JSON.parse(localStorage.getItem('quicklinks'))
|
||||
});
|
||||
@@ -113,9 +139,19 @@ export default class QuickLinks extends React.PureComponent {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
this.setZoom(document.querySelector('.quicklinks-container'));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.remove('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (localStorage.getItem('offlineMode') === 'true') {
|
||||
return null;
|
||||
}
|
||||
|
||||
let target, rel = null;
|
||||
if (localStorage.getItem('quicklinksnewtab') === 'true') {
|
||||
target = '_blank';
|
||||
@@ -147,12 +183,12 @@ export default class QuickLinks extends React.PureComponent {
|
||||
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 }}>
|
||||
<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 })} />
|
||||
<TextareaAutosize rowsmax={1} placeholder={this.language.name} value={this.state.name} onChange={(e) => this.setState({ name: e.target.value })} />
|
||||
<p>{this.state.nameError}</p>
|
||||
<TextareaAutosize rowsMax={10} placeholder={this.language.url} value={this.state.url} onChange={(e) => this.setState({ url: e.target.value })} />
|
||||
<TextareaAutosize rowsmax={10} placeholder={this.language.url} value={this.state.url} onChange={(e) => this.setState({ url: e.target.value })} />
|
||||
<p>{this.state.urlError}</p>
|
||||
<button className='pinNote' onClick={this.addLink}>{this.language.add}</button>
|
||||
</div>
|
||||
@@ -160,4 +196,4 @@ export default class QuickLinks extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
position: absolute;
|
||||
margin-left: -140px;
|
||||
margin-top: 50px;
|
||||
z-index: 1;
|
||||
|
||||
svg {
|
||||
float: left;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -12,23 +13,35 @@ import { toast } from 'react-toastify';
|
||||
import './quote.scss';
|
||||
|
||||
export default class Quote extends React.PureComponent {
|
||||
buttons = {
|
||||
tweet: <TwitterIcon 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()} />
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
quote: '',
|
||||
author: '',
|
||||
favourited: <StarIcon2 className='copyButton' onClick={this.favourite} />,
|
||||
tweet: '',
|
||||
copy: '',
|
||||
quoteLanguage: ''
|
||||
};
|
||||
this.buttons = {
|
||||
tweet: <TwitterIcon className='copyButton' onClick={this.tweetQuote} />,
|
||||
copy: <FileCopy className='copyButton' onClick={this.copyQuote} />
|
||||
favourited: this.useFavourite(),
|
||||
tweet: (localStorage.getItem('tweetButton') === 'false') ? null : this.buttons.tweet,
|
||||
copy: (localStorage.getItem('copyButton') === 'false') ? null : this.buttons.copy,
|
||||
quoteLanguage: '',
|
||||
type: localStorage.getItem('quoteType') || 'api'
|
||||
};
|
||||
this.language = window.language.widgets.quote;
|
||||
}
|
||||
|
||||
useFavourite() {
|
||||
let favouriteQuote = null;
|
||||
if (localStorage.getItem('favouriteQuoteEnabled') === 'true') {
|
||||
favouriteQuote = localStorage.getItem('favouriteQuote') ? this.buttons.favourited : this.buttons.unfavourited;
|
||||
}
|
||||
return favouriteQuote;
|
||||
}
|
||||
|
||||
doOffline() {
|
||||
const quotes = require('./offline_quotes.json');
|
||||
|
||||
@@ -63,7 +76,7 @@ export default class Quote extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
switch (localStorage.getItem('quoteType') || 'api') {
|
||||
switch (this.state.type) {
|
||||
case 'custom':
|
||||
const customQuote = localStorage.getItem('customQuote');
|
||||
const customQuoteAuthor = localStorage.getItem('customQuoteAuthor');
|
||||
@@ -72,8 +85,7 @@ export default class Quote extends React.PureComponent {
|
||||
return this.setState({
|
||||
quote: '"' + customQuote + '"',
|
||||
author: customQuoteAuthor,
|
||||
authorlink: this.getAuthorLink(customQuote),
|
||||
type: 'custom'
|
||||
authorlink: this.getAuthorLink(customQuote)
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -91,8 +103,7 @@ export default class Quote extends React.PureComponent {
|
||||
return this.setState({
|
||||
quote: '"' + data[quotePackAPI.quote] + '"',
|
||||
author: author,
|
||||
authorlink: this.getAuthorLink(author),
|
||||
type: 'quote_pack'
|
||||
authorlink: this.getAuthorLink(author)
|
||||
});
|
||||
} catch (e) {
|
||||
return this.doOffline();
|
||||
@@ -109,8 +120,7 @@ export default class Quote extends React.PureComponent {
|
||||
return this.setState({
|
||||
quote: '"' + data.quote + '"',
|
||||
author: data.author,
|
||||
authorlink: this.getAuthorLink(data.author),
|
||||
type: 'quote_pack'
|
||||
authorlink: this.getAuthorLink(data.author)
|
||||
});
|
||||
} else {
|
||||
return this.doOffline();
|
||||
@@ -132,13 +142,15 @@ export default class Quote extends React.PureComponent {
|
||||
return this.doOffline();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
const object = {
|
||||
quote: '"' + data.quote + '"',
|
||||
author: data.author,
|
||||
authorlink: this.getAuthorLink(data.author),
|
||||
quoteLanguage: quotelanguage,
|
||||
type: 'api'
|
||||
});
|
||||
quoteLanguage: quotelanguage
|
||||
}
|
||||
|
||||
this.setState(object);
|
||||
localStorage.setItem('currentQuote', JSON.stringify(object));
|
||||
} catch (e) {
|
||||
// ..and if that fails we load one locally
|
||||
this.doOffline();
|
||||
@@ -149,40 +161,35 @@ export default class Quote extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
copyQuote = () => {
|
||||
copyQuote() {
|
||||
window.stats.postEvent('feature', 'Quote copied');
|
||||
navigator.clipboard.writeText(`${this.state.quote} - ${this.state.author}`);
|
||||
toast(window.language.toasts.quote);
|
||||
}
|
||||
|
||||
tweetQuote = () => {
|
||||
tweetQuote() {
|
||||
window.stats.postEvent('feature', 'Quote tweet');
|
||||
window.open(`https://twitter.com/intent/tweet?text=${this.state.quote} - ${this.state.author} on @getmue`, '_blank').focus();
|
||||
}
|
||||
|
||||
favourite = () => {
|
||||
favourite() {
|
||||
if (localStorage.getItem('favouriteQuote')) {
|
||||
localStorage.removeItem('favouriteQuote');
|
||||
this.setState({
|
||||
favourited: <StarIcon2 className='copyButton' onClick={this.favourite} />
|
||||
favourited: this.buttons.unfavourited
|
||||
});
|
||||
} else {
|
||||
localStorage.setItem('favouriteQuote', this.state.quote + ' - ' + this.state.author);
|
||||
this.setState({
|
||||
favourited: <StarIcon className='copyButton' onClick={this.favourite} />
|
||||
favourited: this.buttons.favourited
|
||||
});
|
||||
}
|
||||
|
||||
window.stats.postEvent('feature', 'Quote favourite');
|
||||
}
|
||||
|
||||
init() {
|
||||
let favouriteQuote = '';
|
||||
if (localStorage.getItem('favouriteQuoteEnabled') === 'true') {
|
||||
favouriteQuote = localStorage.getItem('favouriteQuote') ? <StarIcon className='copyButton' onClick={this.favourite} /> : <StarIcon2 className='copyButton' onClick={this.favourite} />;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
favourited: favouriteQuote,
|
||||
copy: (localStorage.getItem('copyButton') === 'false') ? null : this.buttons.copy,
|
||||
tweet: (localStorage.getItem('tweetButton') === 'false') ? null : this.buttons.tweet
|
||||
});
|
||||
this.setZoom();
|
||||
|
||||
const quoteType = localStorage.getItem('quoteType');
|
||||
|
||||
@@ -192,6 +199,11 @@ 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`;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'quote') {
|
||||
@@ -202,8 +214,6 @@ export default class Quote extends React.PureComponent {
|
||||
}
|
||||
|
||||
element.style.display = 'block';
|
||||
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`;
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -212,17 +222,35 @@ export default class Quote extends React.PureComponent {
|
||||
this.init();
|
||||
}
|
||||
});
|
||||
|
||||
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`;
|
||||
|
||||
this.init();
|
||||
const interval = localStorage.getItem('quotechange');
|
||||
if (interval && interval !== 'refresh' && localStorage.getItem('quoteType') === 'api') {
|
||||
Interval(() => {
|
||||
this.setZoom();
|
||||
this.getQuote();
|
||||
}, Number(interval), 'quote');
|
||||
|
||||
try {
|
||||
this.setState(JSON.parse(localStorage.getItem('currentQuote')));
|
||||
} catch (e) {
|
||||
this.setZoom();
|
||||
this.getQuote();
|
||||
}
|
||||
} else {
|
||||
// don't bother with the checks if we're loading for the first time
|
||||
this.setZoom();
|
||||
this.getQuote();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.remove('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='quotediv'>
|
||||
<h1 className='quote'>{`${this.state.quote}`}</h1>
|
||||
<h1 className='quote'>{this.state.quote}</h1>
|
||||
<h1 className='quoteauthor'>
|
||||
<a href={this.state.authorlink} className='quoteauthorlink' target='_blank' rel='noopener noreferrer'>{this.state.author}</a>
|
||||
<br/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
import fetchJSONP from 'fetch-jsonp';
|
||||
|
||||
import AutocompleteInput from '../../helpers/autocomplete/Autocomplete';
|
||||
|
||||
@@ -44,6 +43,7 @@ export default class Search extends React.PureComponent {
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
window.stats.postEvent('feature', 'Voice search');
|
||||
window.location.href = this.state.url + `?${this.state.query}=` + searchText.value;
|
||||
}, 1000);
|
||||
};
|
||||
@@ -58,23 +58,33 @@ export default class Search extends React.PureComponent {
|
||||
value = document.getElementById('searchtext').value || 'mue fast';
|
||||
}
|
||||
|
||||
window.stats.postEvent('feature', 'Search');
|
||||
window.location.href = this.state.url + `?${this.state.query}=` + value;
|
||||
}
|
||||
|
||||
async getSuggestions(input) {
|
||||
const data = await (await fetchJSONP(this.state.autocompleteURL + this.state.autocompleteQuery + input, {
|
||||
jsonpCallback: this.state.autocompleteCallback
|
||||
})).json();
|
||||
window.setResults = (results) => {
|
||||
window.searchResults = results;
|
||||
};
|
||||
|
||||
this.setState({
|
||||
suggestions: data[1].splice(0, 3)
|
||||
});
|
||||
const script = document.createElement('script');
|
||||
script.src = `${this.state.autocompleteURL + this.state.autocompleteQuery + input}&${this.state.autocompleteCallback}=window.setResults`;
|
||||
document.head.appendChild(script);
|
||||
|
||||
try {
|
||||
this.setState({
|
||||
suggestions: window.searchResults[1].splice(0, 3)
|
||||
});
|
||||
} catch (e) {
|
||||
// ignore error if empty
|
||||
}
|
||||
|
||||
document.head.removeChild(script);
|
||||
}
|
||||
|
||||
init() {
|
||||
let url;
|
||||
let url, microphone;
|
||||
let query = 'q';
|
||||
let microphone = null;
|
||||
|
||||
const setting = localStorage.getItem('searchEngine');
|
||||
const info = searchEngines.find((i) => i.settingsName === setting);
|
||||
@@ -94,9 +104,7 @@ export default class Search extends React.PureComponent {
|
||||
microphone = <MicIcon className='micIcon' onClick={this.startSpeechRecognition}/>;
|
||||
}
|
||||
|
||||
let autocompleteURL;
|
||||
let autocompleteQuery;
|
||||
let autocompleteCallback;
|
||||
let autocompleteURL, autocompleteQuery, autocompleteCallback;
|
||||
|
||||
if (localStorage.getItem('autocomplete') === 'true') {
|
||||
const info = autocompleteProviders.find((i) => i.value === localStorage.getItem('autocompleteProvider'));
|
||||
@@ -134,7 +142,7 @@ export default class Search extends React.PureComponent {
|
||||
<form action={this.state.url} className='searchBar'>
|
||||
{this.state.microphone}
|
||||
<SearchIcon onClick={this.searchButton}/>
|
||||
<AutocompleteInput placeholder={this.language} name={this.state.query} id='searchtext' suggestions={this.state.suggestions} onChange={(e) => this.getSuggestions(e)} onClick={this.searchButton}/>
|
||||
<AutocompleteInput placeholder={this.language} id='searchtext' suggestions={this.state.suggestions} onChange={(e) => this.getSuggestions(e)} onClick={this.searchButton}/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { utcToZonedTime } from 'date-fns-tz';
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
|
||||
import './clock.scss';
|
||||
@@ -20,11 +21,13 @@ export default class Clock extends React.PureComponent {
|
||||
|
||||
startTime(time = localStorage.getItem('seconds') === 'true' || localStorage.getItem('timeType') === 'analogue' ? (1000 - Date.now() % 1000) : (60000 - Date.now() % 60000)) {
|
||||
this.timer = setTimeout(() => {
|
||||
const now = new Date();
|
||||
let now = new Date();
|
||||
const timezone = localStorage.getItem('timezone');
|
||||
if (timezone) {
|
||||
now = utcToZonedTime(now, timezone);
|
||||
}
|
||||
|
||||
const timeType = localStorage.getItem('timeType');
|
||||
|
||||
switch (timeType) {
|
||||
switch (localStorage.getItem('timeType')) {
|
||||
case 'percentageComplete':
|
||||
this.setState({
|
||||
time: (now.getHours() / 24).toFixed(2).replace('0.', '') + '%'
|
||||
@@ -44,11 +47,7 @@ export default class Clock extends React.PureComponent {
|
||||
const zero = localStorage.getItem('zero');
|
||||
|
||||
if (localStorage.getItem('seconds') === 'true') {
|
||||
if (zero === 'false') {
|
||||
sec = ':' + now.getSeconds();
|
||||
} else {
|
||||
sec = `:${('00' + now.getSeconds()).slice(-2)}`;
|
||||
}
|
||||
sec = `:${('00' + now.getSeconds()).slice(-2)}`;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('timeformat') === 'twentyfourhour') {
|
||||
@@ -70,7 +69,7 @@ export default class Clock extends React.PureComponent {
|
||||
}
|
||||
|
||||
if (zero === 'false') {
|
||||
time = `${hours}:${now.getMinutes()}${sec}`;
|
||||
time = `${hours}:${('00' + now.getMinutes()).slice(-2)}${sec}`;
|
||||
} else {
|
||||
time = `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`;
|
||||
}
|
||||
@@ -89,7 +88,7 @@ export default class Clock extends React.PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'clock') {
|
||||
if (data === 'clock' || data === 'timezone') {
|
||||
const element = document.querySelector('.clock-container');
|
||||
|
||||
if (localStorage.getItem('time') === 'false') {
|
||||
@@ -111,6 +110,10 @@ export default class Clock extends React.PureComponent {
|
||||
this.startTime(0);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.remove('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
let clockHTML = <h1 className='clock clock-container'>{this.state.time}<span className='ampm'>{this.state.ampm}</span></h1>;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { utcToZonedTime } from 'date-fns-tz';
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
|
||||
import dtf from '../../../modules/helpers/date';
|
||||
@@ -11,7 +12,7 @@ export default class DateWidget extends React.PureComponent {
|
||||
super();
|
||||
this.state = {
|
||||
date: '',
|
||||
weekNumber: ''
|
||||
weekNumber: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,10 +34,18 @@ export default class DateWidget extends React.PureComponent {
|
||||
}
|
||||
|
||||
getDate() {
|
||||
const date = new Date();
|
||||
let date = new Date();
|
||||
const timezone = localStorage.getItem('timezone');
|
||||
if (timezone) {
|
||||
date = utcToZonedTime(date, timezone);
|
||||
}
|
||||
|
||||
if (localStorage.getItem('weeknumber') === 'true') {
|
||||
this.getWeekNumber(date);
|
||||
} else if (this.state.weekNumber !== null) {
|
||||
this.setState({
|
||||
weekNumber: null
|
||||
});
|
||||
}
|
||||
|
||||
if (localStorage.getItem('dateType') === 'short') {
|
||||
@@ -60,7 +69,8 @@ export default class DateWidget extends React.PureComponent {
|
||||
year = dateDay;
|
||||
break;
|
||||
// DMY
|
||||
default: break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
let format;
|
||||
@@ -77,7 +87,8 @@ export default class DateWidget extends React.PureComponent {
|
||||
case 'slashes':
|
||||
format = `${day}/${month}/${year}`;
|
||||
break;
|
||||
default: break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -100,7 +111,7 @@ export default class DateWidget extends React.PureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'date') {
|
||||
if (data === 'date' || data === 'timezone') {
|
||||
const element = document.querySelector('.date');
|
||||
|
||||
if (localStorage.getItem('date') === 'false') {
|
||||
@@ -118,6 +129,10 @@ export default class DateWidget extends React.PureComponent {
|
||||
this.getDate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.remove('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
return <span className='date'>{this.state.date} <br/> {this.state.weekNumber}</span>;
|
||||
}
|
||||
|
||||
350
src/components/widgets/time/timezones.json
Normal file
@@ -0,0 +1,350 @@
|
||||
[
|
||||
"Africa/Abidjan",
|
||||
"Africa/Accra",
|
||||
"Africa/Algiers",
|
||||
"Africa/Bissau",
|
||||
"Africa/Cairo",
|
||||
"Africa/Casablanca",
|
||||
"Africa/Ceuta",
|
||||
"Africa/El_Aaiun",
|
||||
"Africa/Johannesburg",
|
||||
"Africa/Juba",
|
||||
"Africa/Khartoum",
|
||||
"Africa/Lagos",
|
||||
"Africa/Maputo",
|
||||
"Africa/Monrovia",
|
||||
"Africa/Nairobi",
|
||||
"Africa/Ndjamena",
|
||||
"Africa/Sao_Tome",
|
||||
"Africa/Tripoli",
|
||||
"Africa/Tunis",
|
||||
"Africa/Windhoek",
|
||||
"America/Adak",
|
||||
"America/Anchorage",
|
||||
"America/Araguaina",
|
||||
"America/Argentina/Buenos_Aires",
|
||||
"America/Argentina/Catamarca",
|
||||
"America/Argentina/Cordoba",
|
||||
"America/Argentina/Jujuy",
|
||||
"America/Argentina/La_Rioja",
|
||||
"America/Argentina/Mendoza",
|
||||
"America/Argentina/Rio_Gallegos",
|
||||
"America/Argentina/Salta",
|
||||
"America/Argentina/San_Juan",
|
||||
"America/Argentina/San_Luis",
|
||||
"America/Argentina/Tucuman",
|
||||
"America/Argentina/Ushuaia",
|
||||
"America/Asuncion",
|
||||
"America/Atikokan",
|
||||
"America/Bahia",
|
||||
"America/Bahia_Banderas",
|
||||
"America/Barbados",
|
||||
"America/Belem",
|
||||
"America/Belize",
|
||||
"America/Blanc-Sablon",
|
||||
"America/Boa_Vista",
|
||||
"America/Bogota",
|
||||
"America/Boise",
|
||||
"America/Cambridge_Bay",
|
||||
"America/Campo_Grande",
|
||||
"America/Cancun",
|
||||
"America/Caracas",
|
||||
"America/Cayenne",
|
||||
"America/Chicago",
|
||||
"America/Chihuahua",
|
||||
"America/Costa_Rica",
|
||||
"America/Creston",
|
||||
"America/Cuiaba",
|
||||
"America/Curacao",
|
||||
"America/Danmarkshavn",
|
||||
"America/Dawson",
|
||||
"America/Dawson_Creek",
|
||||
"America/Denver",
|
||||
"America/Detroit",
|
||||
"America/Edmonton",
|
||||
"America/Eirunepe",
|
||||
"America/El_Salvador",
|
||||
"America/Fort_Nelson",
|
||||
"America/Fortaleza",
|
||||
"America/Glace_Bay",
|
||||
"America/Godthab",
|
||||
"America/Goose_Bay",
|
||||
"America/Grand_Turk",
|
||||
"America/Guatemala",
|
||||
"America/Guayaquil",
|
||||
"America/Guyana",
|
||||
"America/Halifax",
|
||||
"America/Havana",
|
||||
"America/Hermosillo",
|
||||
"America/Indiana/Indianapolis",
|
||||
"America/Indiana/Knox",
|
||||
"America/Indiana/Marengo",
|
||||
"America/Indiana/Petersburg",
|
||||
"America/Indiana/Tell_City",
|
||||
"America/Indiana/Vevay",
|
||||
"America/Indiana/Vincennes",
|
||||
"America/Indiana/Winamac",
|
||||
"America/Inuvik",
|
||||
"America/Iqaluit",
|
||||
"America/Jamaica",
|
||||
"America/Juneau",
|
||||
"America/Kentucky/Louisville",
|
||||
"America/Kentucky/Monticello",
|
||||
"America/La_Paz",
|
||||
"America/Lima",
|
||||
"America/Los_Angeles",
|
||||
"America/Maceio",
|
||||
"America/Managua",
|
||||
"America/Manaus",
|
||||
"America/Martinique",
|
||||
"America/Matamoros",
|
||||
"America/Mazatlan",
|
||||
"America/Menominee",
|
||||
"America/Merida",
|
||||
"America/Metlakatla",
|
||||
"America/Mexico_City",
|
||||
"America/Miquelon",
|
||||
"America/Moncton",
|
||||
"America/Monterrey",
|
||||
"America/Montevideo",
|
||||
"America/Nassau",
|
||||
"America/New_York",
|
||||
"America/Nipigon",
|
||||
"America/Nome",
|
||||
"America/Noronha",
|
||||
"America/North_Dakota/Beulah",
|
||||
"America/North_Dakota/Center",
|
||||
"America/North_Dakota/New_Salem",
|
||||
"America/Ojinaga",
|
||||
"America/Panama",
|
||||
"America/Pangnirtung",
|
||||
"America/Paramaribo",
|
||||
"America/Phoenix",
|
||||
"America/Port-au-Prince",
|
||||
"America/Port_of_Spain",
|
||||
"America/Porto_Velho",
|
||||
"America/Puerto_Rico",
|
||||
"America/Punta_Arenas",
|
||||
"America/Rainy_River",
|
||||
"America/Rankin_Inlet",
|
||||
"America/Recife",
|
||||
"America/Regina",
|
||||
"America/Resolute",
|
||||
"America/Rio_Branco",
|
||||
"America/Santarem",
|
||||
"America/Santiago",
|
||||
"America/Santo_Domingo",
|
||||
"America/Sao_Paulo",
|
||||
"America/Scoresbysund",
|
||||
"America/Sitka",
|
||||
"America/St_Johns",
|
||||
"America/Swift_Current",
|
||||
"America/Tegucigalpa",
|
||||
"America/Thule",
|
||||
"America/Thunder_Bay",
|
||||
"America/Tijuana",
|
||||
"America/Toronto",
|
||||
"America/Vancouver",
|
||||
"America/Whitehorse",
|
||||
"America/Winnipeg",
|
||||
"America/Yakutat",
|
||||
"America/Yellowknife",
|
||||
"Antarctica/Casey",
|
||||
"Antarctica/Davis",
|
||||
"Antarctica/DumontDUrville",
|
||||
"Antarctica/Macquarie",
|
||||
"Antarctica/Mawson",
|
||||
"Antarctica/Palmer",
|
||||
"Antarctica/Rothera",
|
||||
"Antarctica/Syowa",
|
||||
"Antarctica/Troll",
|
||||
"Antarctica/Vostok",
|
||||
"Asia/Almaty",
|
||||
"Asia/Amman",
|
||||
"Asia/Anadyr",
|
||||
"Asia/Aqtau",
|
||||
"Asia/Aqtobe",
|
||||
"Asia/Ashgabat",
|
||||
"Asia/Atyrau",
|
||||
"Asia/Baghdad",
|
||||
"Asia/Baku",
|
||||
"Asia/Bangkok",
|
||||
"Asia/Barnaul",
|
||||
"Asia/Beirut",
|
||||
"Asia/Bishkek",
|
||||
"Asia/Brunei",
|
||||
"Asia/Chita",
|
||||
"Asia/Choibalsan",
|
||||
"Asia/Colombo",
|
||||
"Asia/Damascus",
|
||||
"Asia/Dhaka",
|
||||
"Asia/Dili",
|
||||
"Asia/Dubai",
|
||||
"Asia/Dushanbe",
|
||||
"Asia/Famagusta",
|
||||
"Asia/Gaza",
|
||||
"Asia/Hebron",
|
||||
"Asia/Ho_Chi_Minh",
|
||||
"Asia/Hong_Kong",
|
||||
"Asia/Hovd",
|
||||
"Asia/Irkutsk",
|
||||
"Asia/Jakarta",
|
||||
"Asia/Jayapura",
|
||||
"Asia/Jerusalem",
|
||||
"Asia/Kabul",
|
||||
"Asia/Kamchatka",
|
||||
"Asia/Karachi",
|
||||
"Asia/Kathmandu",
|
||||
"Asia/Khandyga",
|
||||
"Asia/Kolkata",
|
||||
"Asia/Krasnoyarsk",
|
||||
"Asia/Kuala_Lumpur",
|
||||
"Asia/Kuching",
|
||||
"Asia/Macau",
|
||||
"Asia/Magadan",
|
||||
"Asia/Makassar",
|
||||
"Asia/Manila",
|
||||
"Asia/Nicosia",
|
||||
"Asia/Novokuznetsk",
|
||||
"Asia/Novosibirsk",
|
||||
"Asia/Omsk",
|
||||
"Asia/Oral",
|
||||
"Asia/Pontianak",
|
||||
"Asia/Pyongyang",
|
||||
"Asia/Qatar",
|
||||
"Asia/Qostanay",
|
||||
"Asia/Qyzylorda",
|
||||
"Asia/Riyadh",
|
||||
"Asia/Sakhalin",
|
||||
"Asia/Samarkand",
|
||||
"Asia/Seoul",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Singapore",
|
||||
"Asia/Srednekolymsk",
|
||||
"Asia/Taipei",
|
||||
"Asia/Tashkent",
|
||||
"Asia/Tbilisi",
|
||||
"Asia/Tehran",
|
||||
"Asia/Thimphu",
|
||||
"Asia/Tokyo",
|
||||
"Asia/Tomsk",
|
||||
"Asia/Ulaanbaatar",
|
||||
"Asia/Urumqi",
|
||||
"Asia/Ust-Nera",
|
||||
"Asia/Vladivostok",
|
||||
"Asia/Yakutsk",
|
||||
"Asia/Yangon",
|
||||
"Asia/Yekaterinburg",
|
||||
"Asia/Yerevan",
|
||||
"Atlantic/Azores",
|
||||
"Atlantic/Bermuda",
|
||||
"Atlantic/Canary",
|
||||
"Atlantic/Cape_Verde",
|
||||
"Atlantic/Faroe",
|
||||
"Atlantic/Madeira",
|
||||
"Atlantic/Reykjavik",
|
||||
"Atlantic/South_Georgia",
|
||||
"Atlantic/Stanley",
|
||||
"Australia/Adelaide",
|
||||
"Australia/Brisbane",
|
||||
"Australia/Broken_Hill",
|
||||
"Australia/Currie",
|
||||
"Australia/Darwin",
|
||||
"Australia/Eucla",
|
||||
"Australia/Hobart",
|
||||
"Australia/Lindeman",
|
||||
"Australia/Lord_Howe",
|
||||
"Australia/Melbourne",
|
||||
"Australia/Perth",
|
||||
"Australia/Sydney",
|
||||
"Europe/Amsterdam",
|
||||
"Europe/Andorra",
|
||||
"Europe/Astrakhan",
|
||||
"Europe/Athens",
|
||||
"Europe/Belgrade",
|
||||
"Europe/Berlin",
|
||||
"Europe/Brussels",
|
||||
"Europe/Bucharest",
|
||||
"Europe/Budapest",
|
||||
"Europe/Chisinau",
|
||||
"Europe/Copenhagen",
|
||||
"Europe/Dublin",
|
||||
"Europe/Gibraltar",
|
||||
"Europe/Helsinki",
|
||||
"Europe/Istanbul",
|
||||
"Europe/Kaliningrad",
|
||||
"Europe/Kiev",
|
||||
"Europe/Kirov",
|
||||
"Europe/Lisbon",
|
||||
"Europe/London",
|
||||
"Europe/Luxembourg",
|
||||
"Europe/Madrid",
|
||||
"Europe/Malta",
|
||||
"Europe/Minsk",
|
||||
"Europe/Monaco",
|
||||
"Europe/Moscow",
|
||||
"Europe/Oslo",
|
||||
"Europe/Paris",
|
||||
"Europe/Prague",
|
||||
"Europe/Riga",
|
||||
"Europe/Rome",
|
||||
"Europe/Samara",
|
||||
"Europe/Saratov",
|
||||
"Europe/Simferopol",
|
||||
"Europe/Sofia",
|
||||
"Europe/Stockholm",
|
||||
"Europe/Tallinn",
|
||||
"Europe/Tirane",
|
||||
"Europe/Ulyanovsk",
|
||||
"Europe/Uzhgorod",
|
||||
"Europe/Vienna",
|
||||
"Europe/Vilnius",
|
||||
"Europe/Volgograd",
|
||||
"Europe/Warsaw",
|
||||
"Europe/Zaporozhye",
|
||||
"Europe/Zurich",
|
||||
"Indian/Chagos",
|
||||
"Indian/Christmas",
|
||||
"Indian/Cocos",
|
||||
"Indian/Kerguelen",
|
||||
"Indian/Mahe",
|
||||
"Indian/Maldives",
|
||||
"Indian/Mauritius",
|
||||
"Indian/Reunion",
|
||||
"Pacific/Apia",
|
||||
"Pacific/Auckland",
|
||||
"Pacific/Bougainville",
|
||||
"Pacific/Chatham",
|
||||
"Pacific/Chuuk",
|
||||
"Pacific/Easter",
|
||||
"Pacific/Efate",
|
||||
"Pacific/Enderbury",
|
||||
"Pacific/Fakaofo",
|
||||
"Pacific/Fiji",
|
||||
"Pacific/Funafuti",
|
||||
"Pacific/Galapagos",
|
||||
"Pacific/Gambier",
|
||||
"Pacific/Guadalcanal",
|
||||
"Pacific/Guam",
|
||||
"Pacific/Honolulu",
|
||||
"Pacific/Kiritimati",
|
||||
"Pacific/Kosrae",
|
||||
"Pacific/Kwajalein",
|
||||
"Pacific/Majuro",
|
||||
"Pacific/Marquesas",
|
||||
"Pacific/Nauru",
|
||||
"Pacific/Niue",
|
||||
"Pacific/Norfolk",
|
||||
"Pacific/Noumea",
|
||||
"Pacific/Pago_Pago",
|
||||
"Pacific/Palau",
|
||||
"Pacific/Pitcairn",
|
||||
"Pacific/Pohnpei",
|
||||
"Pacific/Port_Moresby",
|
||||
"Pacific/Rarotonga",
|
||||
"Pacific/Tahiti",
|
||||
"Pacific/Tarawa",
|
||||
"Pacific/Tongatapu",
|
||||
"Pacific/Wake",
|
||||
"Pacific/Wallis"
|
||||
]
|
||||
@@ -35,6 +35,8 @@ export default class Weather extends React.PureComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
document.querySelector('.weather').style.fontSize = `${Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
|
||||
|
||||
let data = {
|
||||
weather: [
|
||||
{
|
||||
@@ -60,7 +62,7 @@ export default class Weather extends React.PureComponent {
|
||||
};
|
||||
|
||||
if (!this.state.weather.temp) {
|
||||
data = await (await fetch (window.constants.WEATHER_URL + `/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=${localStorage.getItem('language')}`)).json();
|
||||
}
|
||||
|
||||
if (data.cod === '404') {
|
||||
@@ -88,7 +90,8 @@ export default class Weather extends React.PureComponent {
|
||||
temp_text = '°F';
|
||||
break;
|
||||
// kelvin
|
||||
default: break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -118,12 +121,8 @@ export default class Weather extends React.PureComponent {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'weather') {
|
||||
this.getWeather();
|
||||
document.querySelector('.weather').style.fontSize = `${Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
|
||||
document.querySelector('.weather svg').style.fontSize = `${0.95 * Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('.weather').style.fontSize = `${Number((localStorage.getItem('zoomWeather') || 100) / 100)}em`;
|
||||
this.getWeather();
|
||||
}
|
||||
|
||||
@@ -165,7 +164,7 @@ export default class Weather extends React.PureComponent {
|
||||
<div className='weather'>
|
||||
<WeatherIcon name={this.state.icon}/>
|
||||
<span>{this.state.weather.temp + this.state.temp_text}</span>
|
||||
{enabled('weatherdescription') ? <span className='loc' style={{ 'textTransform': 'capitalize' }}><br/>{this.state.weather.description}</span> : null}
|
||||
{enabled('weatherdescription') ? <span className='loc'><br/>{this.state.weather.description}</span> : null}
|
||||
<span className='minmax'>{minmax()}</span>
|
||||
{enabled('humidity') ? <span className='loc'><br/><WiHumidity/>{this.state.weather.humidity}%</span> : null}
|
||||
{enabled('windspeed') ? <span className='loc'><br/><WiWindy/>{this.state.weather.wind_speed}<span className='minmax'> m/s</span> {enabled('windDirection') ? <WindDirectionIcon degrees={this.state.weather.wind_degrees}/> : null}</span> : null}
|
||||
@@ -177,4 +176,4 @@ export default class Weather extends React.PureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ import { WiDirectionDownLeft, WiDirectionDownRight, WiDirectionDown, WiDirection
|
||||
|
||||
export default function WindDirectionIcon(props) {
|
||||
let icon;
|
||||
// fix potential bug, idk what causes it but now it is fixed
|
||||
let degrees = props.degrees;
|
||||
|
||||
// convert the number openweathermap gives us to closest direction or something
|
||||
const directions = ['North', 'North-West', 'West', 'South-West', 'South', 'South-East', 'East', 'North-East'];
|
||||
const direction = directions[Math.round(((props.degrees %= 360) < 0 ? props.degrees + 360 : props.degrees) / 45) % 8];
|
||||
const direction = directions[Math.round(((degrees %= 360) < 0 ? degrees + 360 : degrees) / 45) % 8];
|
||||
|
||||
switch (direction) {
|
||||
case 'North': icon = <WiDirectionUp/>; break;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
font-size: 0.7em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.minmax {
|
||||
|
||||
17
src/index.js
@@ -8,7 +8,8 @@ import './scss/index.scss';
|
||||
// the toast css is based on default so we need to import it
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
|
||||
import '@fontsource/lexend-deca/400.css';
|
||||
// this is opt-in btw, allows you to see your stats etc
|
||||
import Stats from './modules/helpers/stats';
|
||||
|
||||
// language
|
||||
import merge from '@material-ui/utils/esm/deepmerge';
|
||||
@@ -22,11 +23,6 @@ if (languagecode === 'en') {
|
||||
window.languagecode = 'en_GB';
|
||||
}
|
||||
|
||||
// only load font if needed
|
||||
if (languagecode === 'ru') {
|
||||
require('@fontsource/montserrat/cyrillic-500.css');
|
||||
}
|
||||
|
||||
// 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`));
|
||||
|
||||
@@ -36,6 +32,15 @@ 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);
|
||||
} else {
|
||||
window.stats = {
|
||||
tabLoad: () => '',
|
||||
postEvent: () => ''
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<App/>,
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
// API URLs
|
||||
export const API_URL = 'https://api.muetab.com';
|
||||
export const UNSPLASH_URL = 'https://unsplash.muetab.com';
|
||||
export const PEXELS_URL = 'https://pexels.muetab.com';
|
||||
export const PROXY_URL = 'https://proxy.muetab.com';
|
||||
export const MARKETPLACE_URL = 'https://marketplace.muetab.com';
|
||||
export const WEATHER_URL = 'https://weather.muetab.com';
|
||||
export const WEBSITE_URL = 'https://muetab.com';
|
||||
export const SPONSORS_URL = 'https://sponsors.muetab.com';
|
||||
export const GITHUB_URL = 'https://api.github.com';
|
||||
export const BLOG_POST = 'https://blog.muetab.com/posts/version-5-1';
|
||||
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 FEEDBACK_FORM = 'https://api.formcake.com/api/form/349b56cb-7e2b-4004-b32b-e8964d217dd1/submission';
|
||||
export const DDG_PROXY = 'https://external-content.duckduckgo.com/iu/?u=';
|
||||
|
||||
// Mue Info
|
||||
export const ORG_NAME = 'mue';
|
||||
export const REPO_NAME = 'mue';
|
||||
export const EMAIL = 'hello@muetab.com';
|
||||
export const TWITTER_HANDLE = 'getmue';
|
||||
export const INSTAGRAM_HANDLE = 'mue.tab';
|
||||
export const FACEBOOK_HANDLE = 'muetab';
|
||||
export const DISCORD_SERVER = 'zv8C9F8';
|
||||
export const COPYRIGHT_NAME = 'The Mue Authors';
|
||||
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.1.0';
|
||||
export const VERSION = '5.2.0';
|
||||
|
||||