Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daee291aa9 | ||
|
|
30768053eb | ||
|
|
1d99622123 | ||
|
|
66ac192c09 | ||
|
|
37464acf85 | ||
|
|
c687b2fb67 | ||
|
|
6bf6cca8c6 | ||
|
|
096df4f073 | ||
|
|
490f42a9ad | ||
|
|
ef2f666ccf | ||
|
|
afcead634b | ||
|
|
98c857e7ad | ||
|
|
34cf696bb4 | ||
|
|
2f3252d15e | ||
|
|
7ce2afb773 | ||
|
|
203ff27ee1 | ||
|
|
50b1d16171 | ||
|
|
13825c5525 | ||
|
|
a91d5843b9 | ||
|
|
fb15a1037b | ||
|
|
c66add33d2 | ||
|
|
6ec57c75d4 | ||
|
|
54e7a3a33f | ||
|
|
7e2432b9b4 | ||
|
|
640b83e1c3 | ||
|
|
4d3c7cbbe6 | ||
|
|
23251d248b | ||
|
|
ba31ea5841 | ||
|
|
4f5233fab9 | ||
|
|
5e4a14ba2c | ||
|
|
e82ac7da9e | ||
|
|
16485c3d2c | ||
|
|
9a8a360e21 | ||
|
|
cd5364dd36 | ||
|
|
70273798b6 | ||
|
|
baaa780ade | ||
|
|
39751736ca | ||
|
|
1a8bb69288 | ||
|
|
b8c793741f | ||
|
|
0f20983c37 | ||
|
|
9c3b8c7f59 | ||
|
|
943d817e71 | ||
|
|
564b82c427 | ||
|
|
26df915801 | ||
|
|
eefc594ec1 | ||
|
|
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']
|
||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', '@babel/plugin-transform-react-inline-elements', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
|
||||
};
|
||||
|
||||
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."
|
||||
}
|
||||
}
|
||||
10
manifest/background-chrome.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable no-undef */
|
||||
chrome.runtime.setUninstallURL('https://muetab.com/uninstall');
|
||||
|
||||
chrome.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === 'install') {
|
||||
chrome.tabs.create({
|
||||
url: chrome.runtime.getURL('index.html')
|
||||
});
|
||||
}
|
||||
});
|
||||
10
manifest/background-firefox.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable no-undef */
|
||||
browser.runtime.setUninstallURL('https://muetab.com/uninstall');
|
||||
|
||||
browser.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === 'install') {
|
||||
browser.tabs.create({
|
||||
url: browser.runtime.getURL('index.html')
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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.3.3",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
@@ -16,5 +17,9 @@
|
||||
"48": "icons/48x48.png",
|
||||
"128": "icons/128x128.png"
|
||||
},
|
||||
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'"
|
||||
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'",
|
||||
"background": {
|
||||
"persistent": false,
|
||||
"scripts": [ "background-chrome.js" ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.3.3",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
|
||||
51
package.json
@@ -9,18 +9,17 @@
|
||||
"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.3.3",
|
||||
"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.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@fontsource/lexend-deca": "4.4.5",
|
||||
"@fontsource/montserrat": "4.4.5",
|
||||
"@material-ui/core": "5.0.0-beta.4",
|
||||
"@material-ui/icons": "5.0.0-beta.4",
|
||||
"react": "17.0.2",
|
||||
"react-clock": "3.0.0",
|
||||
"react-color-gradient-picker": "0.1.2",
|
||||
"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 +27,35 @@
|
||||
"weather-icons-react": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/eslint-parser": "^7.14.5",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/eslint-parser": "^7.15.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.13.15",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.5",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.14.5",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@eartharoid/deep-merge": "^0.0.1",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-transform-react-class-to-function": "^1.2.2",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"eslint": "^7.28.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"css-loader": "^6.2.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.2.0",
|
||||
"sass": "^1.38.0",
|
||||
"sass-loader": "^12.1.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"webpack": "^5.39.1",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
"webpack": "^5.51.1",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"build": "webpack --mode=production",
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json",
|
||||
"firefox": "cp manifest/firefox.json build/manifest.json"
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json && cp -r manifest/_locales build/_locales && cp manifest/background-chrome.js build/background-chrome.js",
|
||||
"firefox": "rm -rf build/_locales && cp manifest/firefox.json build/manifest.json"
|
||||
},
|
||||
"browserslist": {
|
||||
"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');
|
||||
});
|
||||
26
src/App.jsx
@@ -1,35 +1,33 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import Background from './components/widgets/background/Background';
|
||||
import Widgets from './components/widgets/Widgets';
|
||||
import Modals from './components/modals/Modals';
|
||||
|
||||
import { loadSettings, moveSettings } from './modules/helpers/settings';
|
||||
|
||||
import EventBus from './modules/helpers/eventbus';
|
||||
import SettingsFunctions from './modules/helpers/settings';
|
||||
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
export default class App extends React.PureComponent {
|
||||
export default class App extends PureComponent {
|
||||
componentDidMount() {
|
||||
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')) {
|
||||
SettingsFunctions.moveSettings();
|
||||
// the firstRun check was moved here because the old function was useless
|
||||
if (!localStorage.getItem('firstRun') || !localStorage.getItem('stats')) {
|
||||
moveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
SettingsFunctions.loadSettings();
|
||||
loadSettings();
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'other') {
|
||||
SettingsFunctions.loadSettings(true);
|
||||
loadSettings(true);
|
||||
}
|
||||
});
|
||||
|
||||
window.stats.tabLoad();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
|
||||
import './autocomplete.scss';
|
||||
|
||||
export default class Autocomplete extends React.PureComponent {
|
||||
export default class Autocomplete extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filtered: [],
|
||||
showList: false,
|
||||
input: ''
|
||||
input: '',
|
||||
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
|
||||
};
|
||||
this.enabled = (localStorage.getItem('autocomplete') === 'true');
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
if (this.enabled === false) {
|
||||
if (this.state.autocompleteDisabled) {
|
||||
return this.setState({
|
||||
input: e.target.value
|
||||
});
|
||||
@@ -22,7 +23,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,30 +32,42 @@ export default class Autocomplete extends React.PureComponent {
|
||||
onClick = (e) => {
|
||||
this.setState({
|
||||
filtered: [],
|
||||
showList: false,
|
||||
input: e.target.innerText
|
||||
});
|
||||
|
||||
this.props.onClick(e);
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'search') {
|
||||
this.setState({
|
||||
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
let autocomplete = null;
|
||||
|
||||
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 +76,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 || ''} />
|
||||
|
||||
@@ -47,4 +47,4 @@
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
display: inline-block;
|
||||
|
||||
.tooltipTitle {
|
||||
width: 60px;
|
||||
min-width: 60px;
|
||||
background-color: rgba(255, 255, 255, 0.89);
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
@@ -20,7 +20,7 @@
|
||||
cursor: initial;
|
||||
user-select: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { ErrorOutline } from '@material-ui/icons';
|
||||
|
||||
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
|
||||
|
||||
export default class ErrorBoundary extends React.PureComponent {
|
||||
export default class ErrorBoundary extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -13,6 +12,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
console.log(error);
|
||||
window.stats.postEvent('modal', 'Error occurred');
|
||||
return {
|
||||
error: true
|
||||
};
|
||||
@@ -23,7 +23,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<ErrorOutlineIcon/>
|
||||
<ErrorOutline/>
|
||||
<h1>{this.language.title}</h1>
|
||||
<p>{this.language.message}</p>
|
||||
<button className='refresh' onClick={() => window.location.reload()}>{this.language.refresh}</button>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import Main from './main/Main';
|
||||
import Navbar from '../widgets/navbar/Navbar';
|
||||
|
||||
import { PureComponent, Suspense, lazy } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
// These modals are lazy loaded as the user won't use them every time they open a tab
|
||||
// We used to lazy load the main modal, but doing so broke the main modal open animation on first click
|
||||
const Welcome = React.lazy(() => import('./welcome/Welcome'));
|
||||
const Feedback = React.lazy(() => import('./feedback/Feedback'));
|
||||
import Main from './main/Main';
|
||||
import Feedback from './feedback/Feedback';
|
||||
import Navbar from '../widgets/navbar/Navbar';
|
||||
|
||||
import EventBus from '../../modules/helpers/eventbus';
|
||||
|
||||
// Welcome modal is lazy loaded as the user won't use it every time they open a tab
|
||||
// We used to lazy load the main and feedback modals, but doing so broke the modal open animation on first click
|
||||
const Welcome = lazy(() => import('./welcome/Welcome'));
|
||||
const renderLoader = () => <></>;
|
||||
|
||||
export default class Modals extends React.PureComponent {
|
||||
export default class Modals extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -27,6 +28,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 +40,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 })}/>
|
||||
{this.state.welcomeModal === false ? <Navbar openModal={(modal) => this.toggleModal(modal, true)}/> : null}
|
||||
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.toggleModal('mainModal', false)} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Main modalClose={() => this.toggleModal('mainModal', false)}/>
|
||||
</Modal>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay welcomeoverlay' shouldCloseOnOverlayClick={false} ariaHideApp={false}>
|
||||
<Welcome modalClose={() => this.closeWelcome()}/>
|
||||
</Modal>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.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>
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import './feedback.scss';
|
||||
|
||||
export default class FeedbackModal extends React.PureComponent {
|
||||
export default class FeedbackModal extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -38,7 +38,7 @@ export default class FeedbackModal extends React.PureComponent {
|
||||
});
|
||||
|
||||
await fetch(window.constants.FEEDBACK_FORM, {
|
||||
'method': 'POST'
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
this.setState({
|
||||
@@ -47,7 +47,7 @@ export default class FeedbackModal extends React.PureComponent {
|
||||
|
||||
setTimeout(() => {
|
||||
this.props.modalClose();
|
||||
}, 3000);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export default class FeedbackModal extends React.PureComponent {
|
||||
<textarea name='question4' id='questionfour'/>
|
||||
<p className='feedbackerror'>{this.state.questionfourerror}</p>
|
||||
</>
|
||||
{this.state.formsubmit}
|
||||
<p>{this.state.formsubmit}</p>
|
||||
<button onClick={() => this.submitForm()}>{this.language.submit}</button>
|
||||
</>
|
||||
</div>
|
||||
|
||||
@@ -31,10 +31,14 @@
|
||||
border: none;
|
||||
padding: 15px 20px;
|
||||
font-size: 1.5em;
|
||||
background: linear-gradient(90deg, #ffb032 0%, #dd3b67 100%);
|
||||
background: #5352ed;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(83, 82, 237, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
@@ -54,7 +58,8 @@
|
||||
|
||||
textarea {
|
||||
width: 80%;
|
||||
background-color: var(--sidebar);
|
||||
padding: 10px;
|
||||
background-color: var(--sidebar) !important;
|
||||
}
|
||||
|
||||
.feedbackerror {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Suspense, lazy } from 'react';
|
||||
|
||||
import Tabs from './tabs/backend/Tabs';
|
||||
|
||||
import './scss/index.scss';
|
||||
|
||||
// Lazy load all the tabs instead of the modal itself
|
||||
const Settings = React.lazy(() => import('./tabs/Settings'));
|
||||
const Addons = React.lazy(() => import('./tabs/Addons'));
|
||||
const Marketplace = React.lazy(() => import('./tabs/Marketplace'));
|
||||
const Settings = lazy(() => import('./tabs/Settings'));
|
||||
const Addons = lazy(() => import('./tabs/Addons'));
|
||||
const Marketplace = lazy(() => import('./tabs/Marketplace'));
|
||||
|
||||
const renderLoader = () => (
|
||||
<Tabs>
|
||||
@@ -18,7 +18,7 @@ const renderLoader = () => (
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label='' style={{ 'display': 'none' }}></div>
|
||||
<div label='' style={{ display: 'none' }}></div>
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
@@ -29,20 +29,20 @@ export default function MainModal(props) {
|
||||
<>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<Tabs navbar={true}>
|
||||
<div label={language.settings}>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<div label={language.settings} name='settings'>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Settings/>
|
||||
</React.Suspense>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div label={language.addons}>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<div label={language.addons} name='addons'>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Addons/>
|
||||
</React.Suspense>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div label={language.marketplace}>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<div label={language.marketplace} name='marketplace'>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Marketplace/>
|
||||
</React.Suspense>
|
||||
</Suspense>
|
||||
</div>
|
||||
</Tabs>
|
||||
</>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import { ArrowBack } from '@material-ui/icons';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import Lightbox from './Lightbox';
|
||||
|
||||
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
|
||||
|
||||
export default class Item extends React.PureComponent {
|
||||
export default class Item extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -34,7 +32,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;
|
||||
}
|
||||
@@ -42,30 +40,31 @@ export default class Item extends React.PureComponent {
|
||||
return (
|
||||
<div id='item'>
|
||||
<br/>
|
||||
<ArrowBackIcon className='backArrow' onClick={this.props.toggleFunction}/>
|
||||
<ArrowBack className='backArrow' onClick={this.props.toggleFunction}/>
|
||||
<br/>
|
||||
<h1>{this.props.data.display_name}</h1>
|
||||
<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>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { LocalMall } from '@material-ui/icons';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
import Item from '../Item';
|
||||
import Items from '../Items';
|
||||
import Dropdown from '../../settings/Dropdown';
|
||||
|
||||
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
|
||||
import { uninstall, urlParser } from '../../../../../modules/helpers/marketplace';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Added extends React.PureComponent {
|
||||
export default class Added extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -34,7 +33,7 @@ export default class Added extends React.PureComponent {
|
||||
name: data,
|
||||
display_name: info.name,
|
||||
author: info.author,
|
||||
description: MarketplaceFunctions.urlParser(info.description.replace(/\n/g, '<br>')),
|
||||
description: urlParser(info.description.replace(/\n/g, '<br>')),
|
||||
//updated: info.updated,
|
||||
version: info.version,
|
||||
icon: info.screenshot_url,
|
||||
@@ -42,6 +41,7 @@ export default class Added extends React.PureComponent {
|
||||
},
|
||||
button: this.buttons.uninstall
|
||||
});
|
||||
window.stats.postEvent('marketplace', 'Item viewed');
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
@@ -50,7 +50,7 @@ export default class Added extends React.PureComponent {
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
|
||||
uninstall(this.state.item.type, this.state.item.display_name);
|
||||
|
||||
toast(window.language.toasts.uninstalled);
|
||||
|
||||
@@ -58,9 +58,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 +84,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() {
|
||||
@@ -93,7 +99,7 @@ export default class Added extends React.PureComponent {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<LocalMallIcon/>
|
||||
<LocalMall/>
|
||||
<h1>{this.language.empty.title}</h1>
|
||||
<p className='description'>{this.language.empty.description}</p>
|
||||
</div>
|
||||
|
||||
220
src/components/modals/main/marketplace/sections/Create.jsx
Normal file
@@ -0,0 +1,220 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { PureComponent } from 'react';
|
||||
import {
|
||||
SettingsRounded as Settings,
|
||||
PhotoOutlined as Photos,
|
||||
FormatQuoteOutlined as Quotes
|
||||
} from '@material-ui/icons';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { saveFile } from '../../../../../modules/helpers/settings/modals';
|
||||
|
||||
import FileUpload from '../../settings/FileUpload';
|
||||
|
||||
import '../../../welcome/welcome.scss';
|
||||
|
||||
export default class Create extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
currentTab: 1,
|
||||
addonMetadata: {
|
||||
name: '',
|
||||
description: '',
|
||||
type: '',
|
||||
version: '',
|
||||
author: '',
|
||||
icon_url: '',
|
||||
screenshot_url: ''
|
||||
},
|
||||
addonData: '',
|
||||
settingsClasses: {
|
||||
current: 'toggle lightTheme',
|
||||
json: 'toggle lightTheme'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
changeTab(tab, type) {
|
||||
if (type) {
|
||||
return this.setState({
|
||||
currentTab: tab,
|
||||
addonMetadata: {
|
||||
type: type
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentTab: tab
|
||||
});
|
||||
}
|
||||
|
||||
importSettings(input) {
|
||||
const data = input || localStorage;
|
||||
let settings = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (key === 'statsData' || key === 'firstRun' || key === 'showWelcome' || key === 'language' || key === 'installed' || key === 'stats') {
|
||||
return;
|
||||
}
|
||||
settings[key] = localStorage.getItem(key);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
addonData: settings,
|
||||
settingsClasses: {
|
||||
current: input ? 'toggle lightTheme' : 'toggle lightTheme active',
|
||||
json: input ? 'toggle lightTheme active' : 'toggle lightTheme'
|
||||
}
|
||||
});
|
||||
|
||||
toast('Imported settings!');
|
||||
}
|
||||
|
||||
downloadAddon() {
|
||||
saveFile({
|
||||
name: this.state.addonMetadata.name,
|
||||
description: this.state.addonMetadata.description,
|
||||
type: this.state.addonMetadata.type,
|
||||
version: this.state.addonMetadata.version,
|
||||
author: this.state.addonMetadata.author,
|
||||
icon_url: this.state.addonMetadata.icon_url,
|
||||
screenshot_url: this.state.addonMetadata.screenshot_url,
|
||||
[this.state.addonMetadata.type]: this.state.addonData
|
||||
}, this.state.addonMetadata.name + '.json');
|
||||
}
|
||||
|
||||
render() {
|
||||
let tabContent;
|
||||
|
||||
const { time } = window.language.modals.main.settings.sections;
|
||||
const { marketplace, addons } = window.language.modals.main;
|
||||
const { welcome } = window.language.modals;
|
||||
|
||||
const chooseType = (
|
||||
<>
|
||||
<h3>{time.type}</h3>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='options'>
|
||||
{/* <div className='toggle lightTheme' onClick={() => this.changeTab(2, 'photos')}>
|
||||
<Photos/>
|
||||
<span>{marketplace.photo_packs}</span>
|
||||
</div>
|
||||
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'quotes')}>
|
||||
<Quotes/>
|
||||
<span>{marketplace.quote_packs}</span>
|
||||
</div>
|
||||
*/}
|
||||
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'settings')}>
|
||||
<Settings/>
|
||||
<span>{marketplace.preset_settings}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// todo: find a better way to do all this
|
||||
const nextDescriptionDisabled = (this.state.addonMetadata.name !== undefined &&
|
||||
this.state.addonMetadata.description !== undefined &&
|
||||
this.state.addonMetadata.version !== undefined && this.state.addonMetadata.author !== undefined &&
|
||||
this.state.addonMetadata.icon_url !== undefined && this.state.addonMetadata.screenshot_url !== undefined)
|
||||
? false : true;
|
||||
|
||||
const setMetadata = (data, type) => {
|
||||
this.setState({
|
||||
addonMetadata: {
|
||||
name: (type === 'name') ? data : this.state.addonMetadata.name,
|
||||
description: (type === 'description') ? data : this.state.addonMetadata.description,
|
||||
version: (type === 'version') ? data : this.state.addonMetadata.version,
|
||||
author: (type === 'author') ? data : this.state.addonMetadata.author,
|
||||
icon_url: (type === 'icon_url') ? data : this.state.addonMetadata.icon_url,
|
||||
screenshot_url: (type === 'screenshot_url') ? data : this.state.addonMetadata.screenshot_url,
|
||||
type: this.state.addonMetadata.type
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const writeDescription = (
|
||||
<>
|
||||
<h3>{marketplace.product.information}</h3>
|
||||
<p>{addons.create.metadata.name}</p>
|
||||
<input type='text' value={this.state.addonMetadata.name} onInput={(e) => setMetadata(e.target.value, 'name')}/>
|
||||
<p>{marketplace.product.version}</p>
|
||||
<input type='text' value={this.state.addonMetadata.version} onInput={(e) => setMetadata(e.target.value, 'version')}/>
|
||||
<p>{marketplace.product.author}</p>
|
||||
<input type='text' value={this.state.addonMetadata.author} onInput={(e) => setMetadata(e.target.value, 'author')}/>
|
||||
<p>{addons.create.metadata.icon_url}</p>
|
||||
<input type='text' value={this.state.addonMetadata.icon_url} onInput={(e) => setMetadata(e.target.value, 'icon_url')}/>
|
||||
<p>{addons.create.metadata.screenshot_url}</p>
|
||||
<input type='text' value={this.state.addonMetadata.screenshot_url} onInput={(e) => setMetadata(e.target.value, 'screenshot_url')}/>
|
||||
<p>{addons.create.metadata.description}</p>
|
||||
<textarea className='settingsTextarea' value={this.state.addonMetadata.description} onInput={(e) => setMetadata(e.target.value, 'description')}></textarea>
|
||||
<br/>
|
||||
<button onClick={() => this.changeTab(1)} className='uploadbg' style={{ marginRight: '10px' }}>{welcome.buttons.previous}</button>
|
||||
<button onClick={() => this.changeTab(this.state.addonMetadata.type)} className='uploadbg' disabled={nextDescriptionDisabled}>{welcome.buttons.next}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
// settings
|
||||
const nextSettingsDisabled = (this.state.addonData === '') ? true : false;
|
||||
const importSettings = (
|
||||
<>
|
||||
<h3>{welcome.sections.settings.title}</h3>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='options'>
|
||||
<div className={this.state.settingsClasses.current} onClick={() => this.importSettings()}>
|
||||
<span>{addons.create.settings.current}</span>
|
||||
</div>
|
||||
<div className={this.state.settingsClasses.json} onClick={() => document.getElementById('file-input').click()}>
|
||||
<span>{addons.create.settings.json}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.importSettings(JSON.parse(e.target.result))} />
|
||||
<br/><br/>
|
||||
<button onClick={() => this.changeTab(2)} className='uploadbg' style={{ marginRight: '10px' }}>{welcome.buttons.previous}</button>
|
||||
<button onClick={() => this.changeTab(3)} className='uploadbg' disabled={nextSettingsDisabled}>{welcome.buttons.next}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
// quotes
|
||||
const addQuotes = (
|
||||
<>
|
||||
<h3>{addons.create.quotes.title}</h3>
|
||||
</>
|
||||
);
|
||||
|
||||
// photos
|
||||
const addPhotos = (
|
||||
<>
|
||||
<h3>{addons.create.photos.title}</h3>
|
||||
</>
|
||||
);
|
||||
|
||||
const downloadAddon = (
|
||||
<>
|
||||
<h3>{addons.create.finish.title}</h3>
|
||||
<button onClick={() => this.downloadAddon()} className='upload'>{addons.create.finish.download}</button>
|
||||
<br/><br/>
|
||||
<button onClick={() => this.changeTab(this.state.addonMetadata.type)} className='uploadbg' style={{ marginRight: '10px' }}>{welcome.buttons.previous}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
switch (this.state.currentTab) {
|
||||
case 2: tabContent = writeDescription; break;
|
||||
case 'settings': tabContent = importSettings; break;
|
||||
case 'quotes': tabContent = addQuotes; break;
|
||||
case 'photos': tabContent = addPhotos; break;
|
||||
case 3: tabContent = downloadAddon; break;
|
||||
default: tabContent = chooseType;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{addons.create.other_title}</h2>
|
||||
{tabContent}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import WifiOffIcon from '@material-ui/icons/WifiOff';
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { WifiOff, LocalMall } from '@material-ui/icons';
|
||||
|
||||
import Item from '../Item';
|
||||
import Items from '../Items';
|
||||
import Dropdown from '../../settings/Dropdown';
|
||||
|
||||
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
|
||||
import { install, urlParser, uninstall } from '../../../../../modules/helpers/marketplace';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Marketplace extends React.PureComponent {
|
||||
export default class Marketplace extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -59,7 +56,7 @@ export default class Marketplace extends React.PureComponent {
|
||||
type: info.data.type,
|
||||
display_name: info.data.name,
|
||||
author: info.data.author,
|
||||
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
|
||||
description: urlParser(info.data.description.replace(/\n/g, '<br>')),
|
||||
//updated: info.updated,
|
||||
version: info.data.version,
|
||||
icon: info.data.screenshot_url,
|
||||
@@ -67,6 +64,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 +74,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,29 +82,32 @@ 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) {
|
||||
if (type === 'install') {
|
||||
MarketplaceFunctions.install(this.state.item.type, this.state.item.data);
|
||||
install(this.state.item.type, this.state.item.data);
|
||||
} else {
|
||||
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
|
||||
uninstall(this.state.item.type, this.state.item.display_name);
|
||||
}
|
||||
|
||||
toast(window.language.toasts[type + 'ed']);
|
||||
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 +129,10 @@ export default class Marketplace extends React.PureComponent {
|
||||
items: items,
|
||||
sortType: value
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
window.stats.postEvent('marketplace', 'Sort');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -153,19 +159,9 @@ export default class Marketplace extends React.PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
const featured = () => {
|
||||
return (
|
||||
<div className='featured' style={{ 'backgroundColor': this.state.featured.colour }}>
|
||||
<p>{this.state.featured.title}</p>
|
||||
<h1>{this.state.featured.name}</h1>
|
||||
<button className='addToMue' onClick={() => window.open(this.state.featured.buttonLink)}>{this.state.featured.buttonText}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return errorMessage(<>
|
||||
<WifiOffIcon/>
|
||||
<WifiOff/>
|
||||
<h1>{this.language.offline.title}</h1>
|
||||
<p className='description'>{this.language.offline.description}</p>
|
||||
</>);
|
||||
@@ -175,17 +171,32 @@ export default class Marketplace extends React.PureComponent {
|
||||
return errorMessage(<h1>{window.language.modals.main.loading}</h1>);
|
||||
}
|
||||
|
||||
const featured = () => {
|
||||
const openFeatured = () => {
|
||||
window.stats.postEvent('marketplace', 'Featured clicked');
|
||||
window.open(this.state.featured.buttonLink);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='featured' style={{ backgroundColor: this.state.featured.colour }}>
|
||||
<p>{this.state.featured.title}</p>
|
||||
<h1>{this.state.featured.name}</h1>
|
||||
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.items.length === 0) {
|
||||
return (
|
||||
<>
|
||||
{featured()}
|
||||
{errorMessage(<>
|
||||
<LocalMallIcon/>
|
||||
<LocalMall/>
|
||||
<h1>{window.language.modals.main.addons.empty.title}</h1>
|
||||
<p className='description'>{this.language.no_items}</p>
|
||||
</>)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.item.display_name) {
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
import { LocalMall } from '@material-ui/icons';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import FileUpload from '../../settings/FileUpload';
|
||||
|
||||
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import { install } from '../../../../../modules/helpers/marketplace';
|
||||
|
||||
export default function Sideload() {
|
||||
const install = (input) => {
|
||||
MarketplaceFunctions.install(input.type, input);
|
||||
const installAddon = (input) => {
|
||||
install(input.type, input);
|
||||
toast(window.language.toasts.installed);
|
||||
window.stats.postEvent('marketplace', 'Sideload');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => install(JSON.parse(e.target.result))} />
|
||||
<LocalMallIcon/>
|
||||
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => installAddon(JSON.parse(e.target.result))} />
|
||||
<LocalMall/>
|
||||
<h1>{window.language.modals.main.addons.sideload}</h1>
|
||||
<button className='addToMue sideload' onClick={() => document.getElementById('file-input').click()}>{window.language.modals.main.settings.sections.background.source.upload}</button>
|
||||
</div>
|
||||
|
||||
@@ -89,14 +89,11 @@
|
||||
}
|
||||
|
||||
.ReactModal__Content {
|
||||
//min-height: calc(100vh - 30vh);
|
||||
//max-height: calc(100vh - 10vh);
|
||||
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
|
||||
// animation
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: all 300ms cubic-bezier(0.47, 1.64, 0.41, 0.8);
|
||||
}
|
||||
@@ -149,6 +146,24 @@ ul.sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 999px) and (min-height: 920px) {
|
||||
ul.sidebar {
|
||||
min-height: 160vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 919px) and (min-height: 700px) {
|
||||
ul.sidebar {
|
||||
min-height: 200vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 699px) and (min-height: 400px) {
|
||||
ul.sidebar {
|
||||
min-height: 260vh;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
font-size: 24px;
|
||||
@@ -165,17 +180,12 @@ li {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 80%;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1300px) {
|
||||
@media (max-width: 1700px) {
|
||||
#modal {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1310px) {
|
||||
#modal {
|
||||
width: 60%;
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +262,7 @@ li {
|
||||
display: inline-flex;
|
||||
|
||||
&:hover {
|
||||
color: rgb(165, 165, 165);
|
||||
color: grey;
|
||||
background: none;
|
||||
}
|
||||
|
||||
@@ -291,7 +301,7 @@ li {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1650px) {
|
||||
@media only screen and (max-width: 800px) {
|
||||
li.navbar-item {
|
||||
span {
|
||||
display: none;
|
||||
@@ -348,6 +358,7 @@ li {
|
||||
min-height: 300px !important;
|
||||
max-width: 300px !important;
|
||||
margin: auto;
|
||||
font-size: 1rem;
|
||||
|
||||
h4 {
|
||||
cursor: initial;
|
||||
@@ -358,6 +369,9 @@ li {
|
||||
.resetfooter {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
width: 300px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
button.reset {
|
||||
margin-right: 43px;
|
||||
@@ -410,3 +424,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;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
.items {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
margin-top: 15px;
|
||||
|
||||
.item {
|
||||
@@ -71,21 +71,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 2100px) {
|
||||
@media (max-width: 1920px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1870px) {
|
||||
@media (max-width: 1680px) and (min-width: 1500px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1079px), (max-width: 1869px) {
|
||||
@media (max-width: 1440px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,15 @@ p.author {
|
||||
font-size: 40px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#item>h1,
|
||||
@@ -107,7 +116,7 @@ p.author {
|
||||
|
||||
p.description {
|
||||
margin-top: 0px;
|
||||
max-width: 400px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.emptyMessage {
|
||||
@@ -119,6 +128,10 @@ p.description {
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 50px;
|
||||
margin-bottom: -20px;
|
||||
@@ -136,7 +149,8 @@ p.description {
|
||||
}
|
||||
|
||||
.informationContainer {
|
||||
margin-top: 20px;
|
||||
margin-top: 150px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.productInformation {
|
||||
@@ -192,6 +206,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 {
|
||||
@@ -35,4 +34,4 @@ select {
|
||||
.dark select {
|
||||
background: url("data:image/svg+xml;utf8,<svg fill='white' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>") right center no-repeat, var(--sidebar) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ input {
|
||||
-webkit-appearance: none;
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
|
||||
|
||||
&::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
&::-webkit-color-swatch {
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
@@ -37,16 +37,31 @@ input {
|
||||
-moz-appearance: none;
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
|
||||
|
||||
&::-moz-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
&::-moz-color-swatch {
|
||||
border: none;
|
||||
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 {
|
||||
@@ -91,11 +106,6 @@ ul {
|
||||
}
|
||||
}
|
||||
|
||||
.newFeature {
|
||||
color: #ff4757;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settingsTextarea {
|
||||
font-family: Consolas !important;
|
||||
padding: 15px;
|
||||
@@ -139,7 +149,7 @@ legend {
|
||||
border-radius: 0.7em;
|
||||
|
||||
h1 {
|
||||
font-size: 1em;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +176,7 @@ legend {
|
||||
li {
|
||||
cursor: initial;
|
||||
font-size: 1rem;
|
||||
list-style-type:disc;
|
||||
list-style-type: disc;
|
||||
padding: 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
@@ -183,6 +193,7 @@ legend {
|
||||
.changelogtab {
|
||||
h1 {
|
||||
max-width: 85%;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -212,11 +223,15 @@ input[type=number] {
|
||||
h2 {
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
h2, span, svg {
|
||||
|
||||
h2,
|
||||
span,
|
||||
svg {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: sub;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { Checkbox as CheckboxUI, FormControlLabel } from '@material-ui/core';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import CheckboxUI from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
|
||||
export default class Checkbox extends React.PureComponent {
|
||||
export default class Checkbox extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -15,12 +12,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';
|
||||
@@ -32,19 +32,11 @@ export default class Checkbox extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
let text = this.props.text;
|
||||
|
||||
if (this.props.newFeature) {
|
||||
text = <>{this.props.text} <span className='newFeature'> NEW</span></>;
|
||||
} else if (this.props.betaFeature) {
|
||||
text = <>{this.props.text} <span className='newFeature'> BETA</span></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={<CheckboxUI name={this.props.name} color='primary' checked={this.state.checked} onChange={this.handleChange} />}
|
||||
label={text}
|
||||
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} />}
|
||||
label={this.props.text}
|
||||
/>
|
||||
<br/>
|
||||
</>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
export default class Dropdown extends React.PureComponent {
|
||||
export default class Dropdown extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class FileUpload extends React.PureComponent {
|
||||
export default class FileUpload extends PureComponent {
|
||||
componentDidMount() {
|
||||
document.getElementById(this.props.id).onchange = (e) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { Radio as RadioUI, RadioGroup, FormControlLabel, FormControl, FormLabel } from '@material-ui/core';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
import RadioUI from '@material-ui/core/Radio';
|
||||
import RadioGroup from '@material-ui/core/RadioGroup';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormLabel from '@material-ui/core/FormLabel';
|
||||
|
||||
export default class Radio extends React.PureComponent {
|
||||
export default class Radio extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -23,12 +18,21 @@ export default class Radio extends React.PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.name === 'language') {
|
||||
// old tab name
|
||||
if (localStorage.getItem('tabName') === window.language.tabname) {
|
||||
localStorage.setItem('tabName', require(`../../../../translations/${value.replace('-', '_')}.json`).tabname);
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
this.setState({
|
||||
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,28 @@
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
import { Close, Delete } from '@material-ui/icons';
|
||||
import { setDefaultSettings } from '../../../../modules/helpers/settings';
|
||||
|
||||
export default function ResetModal(props) {
|
||||
const language = window.language.modals.main.settings.sections.advanced.reset_modal;
|
||||
|
||||
const reset = () => {
|
||||
SettingsFunctions.setDefaultSettings('reset');
|
||||
window.stats.postEvent('setting', 'Reset');
|
||||
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()}>
|
||||
<Delete/>
|
||||
</button>
|
||||
<button className='round import' style={{ marginLeft: '5px' }} onClick={props.modalClose}>
|
||||
<Close/>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// todo: find a better method to do width of number input
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Slider extends React.PureComponent {
|
||||
export default class Slider extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { Switch as SwitchUI, FormControlLabel } from '@material-ui/core';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import SwitchUI from '@material-ui/core/Switch';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
|
||||
export default class Switch extends React.PureComponent {
|
||||
export default class Switch extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -15,12 +12,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';
|
||||
@@ -32,19 +32,11 @@ export default class Switch extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
let text = this.props.text;
|
||||
|
||||
if (this.props.newFeature) {
|
||||
text = <>{this.props.text} <span className='newFeature'> NEW</span></>;
|
||||
} else if (this.props.betaFeature) {
|
||||
text = <>{this.props.text} <span className='newFeature'> BETA</span></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={<SwitchUI name={this.props.name} color='primary' checked={this.state.checked} onChange={this.handleChange} />}
|
||||
label={text}
|
||||
label={this.props.text}
|
||||
labelPlacement='start'
|
||||
/>
|
||||
<br/>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Text extends React.PureComponent {
|
||||
export default class Text extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { Email, Twitter, Chat, Instagram, Facebook } from '@material-ui/icons';
|
||||
|
||||
import Tooltip from '../../../../helpers/tooltip/Tooltip';
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
import TwitterIcon from '@material-ui/icons/Twitter';
|
||||
import ForumIcon from '@material-ui/icons/Forum';
|
||||
import InstagramIcon from '@material-ui/icons/Instagram';
|
||||
import FacebookIcon from '@material-ui/icons/Facebook';
|
||||
|
||||
const other_contributors = require('../../../../../modules/other_contributors.json');
|
||||
|
||||
export default class About extends React.PureComponent {
|
||||
export default class About extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -28,9 +24,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 +53,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 +84,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'><Email/></a>
|
||||
<a href={'https://twitter.com/' + window.constants.TWITTER_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Twitter/></a>
|
||||
<a href={'https://instagram.com/' + window.constants.INSTAGRAM_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Instagram/></a>
|
||||
<a href={'https://facebook.com/' + window.constants.FACEBOOK_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Facebook/></a>
|
||||
<a href={'https://discord.gg/' + window.constants.DISCORD_SERVER} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Chat/></a>
|
||||
|
||||
<h3>{this.language.support_mue}</h3>
|
||||
<p>
|
||||
<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 +129,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>
|
||||
))}
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import { exportSettings, importSettings } from '../../../../../modules/helpers/settings/modals';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import FileUpload from '../FileUpload';
|
||||
import Text from '../Text';
|
||||
import Switch from '../Switch';
|
||||
import ResetModal from '../ResetModal';
|
||||
import Dropdown from '../Dropdown';
|
||||
|
||||
import SettingsFunctions from '../../../../../modules/helpers/settings';
|
||||
const time_zones = require('../../../../widgets/time/timezones.json');
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
export default class AdvancedSettings extends React.PureComponent {
|
||||
export default class AdvancedSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -20,16 +21,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 +28,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='export' onClick={() => exportSettings()}>{this.language.buttons.export}</button>
|
||||
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.language.buttons.import}</button>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.settingsImport(e)}/>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => importSettings(e)}/>
|
||||
|
||||
<h3>{advanced.customisation}</h3>
|
||||
<Text title={advanced.tab_name} name='tabName' default={window.language.tabname} category='other'/>
|
||||
@@ -50,7 +47,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,9 @@ 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' />
|
||||
<Slider title={appearance.accessibility.widget_zoom} name='zoomNavbar' min='10' max='400' default='100' display='%' category='navbar' />
|
||||
|
||||
<h3>{appearance.font.title}</h3>
|
||||
<Text title={appearance.font.custom} name='font' upperCaseFirst={true} category='other' />
|
||||
@@ -56,9 +55,6 @@ export default function AppearanceSettings() {
|
||||
</Dropdown>
|
||||
|
||||
<h3>{appearance.accessibility.title}</h3>
|
||||
{(engineName === 'Blink') ?
|
||||
<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} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import { WifiOff } from '@material-ui/icons';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import Lightbox from '../../marketplace/Lightbox';
|
||||
|
||||
import WifiOffIcon from '@material-ui/icons/WifiOff';
|
||||
|
||||
export default class Changelog extends React.PureComponent {
|
||||
export default class Changelog extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -25,7 +23,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',
|
||||
@@ -89,7 +87,7 @@ export default class Changelog extends React.PureComponent {
|
||||
const language = window.language.modals.main.marketplace;
|
||||
|
||||
return errorMessage(<>
|
||||
<WifiOffIcon/>
|
||||
<WifiOff/>
|
||||
<h1>{language.offline.title}</h1>
|
||||
<p className='description'>{language.offline.description}</p>
|
||||
</>);
|
||||
@@ -101,8 +99,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,6 +1,8 @@
|
||||
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;
|
||||
|
||||
@@ -12,8 +14,15 @@ export default function ExperimentalSettings() {
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Switch from '../Switch';
|
||||
import Text from '../Text';
|
||||
import Slider from '../Slider';
|
||||
|
||||
import DayPickerInput from 'react-day-picker/DayPickerInput';
|
||||
import 'react-day-picker/lib/style.css';
|
||||
|
||||
export default class GreetingSettings extends React.PureComponent {
|
||||
export default class GreetingSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,16 +29,16 @@ export default class GreetingSettings extends React.PureComponent {
|
||||
<>
|
||||
<h2>{greeting.title}</h2>
|
||||
<Switch name='greeting' text={this.language.enabled} category='greeting' element='.greeting'/>
|
||||
<Checkbox name='events' text={greeting.events} category='greeting' element='.greeting'/>
|
||||
<Checkbox name='defaultGreetingMessage' text={greeting.default} category='greeting' element='.greeting'/>
|
||||
<Text title={greeting.name} name='greetingName' category='greeting' element='.greeting'/>
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomGreeting' min='10' max='400' default='100' display='%' category='greeting' element='.greeting' />
|
||||
<Checkbox name='events' text={greeting.events} category='greeting'/>
|
||||
<Checkbox name='defaultGreetingMessage' text={greeting.default} category='greeting'/>
|
||||
<Text title={greeting.name} name='greetingName' category='greeting'/>
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomGreeting' min='10' max='400' default='100' display='%' category='greeting' />
|
||||
|
||||
<h3>{greeting.birthday}</h3>
|
||||
<Switch name='birthdayenabled' text={this.language.enabled} category='greeting' element='.greeting'/>
|
||||
<Checkbox name='birthdayage' text={greeting.birthday_age} category='greeting' element='.greeting'/>
|
||||
<Switch name='birthdayenabled' text={this.language.enabled} category='greeting'/>
|
||||
<Checkbox name='birthdayage' text={greeting.birthday_age} category='greeting'/>
|
||||
<p>{greeting.birthday_date}</p>
|
||||
<DayPickerInput onDayChange={this.changeDate} value={this.state.birthday}/>
|
||||
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Radio from '../Radio';
|
||||
|
||||
const languages = require('../../../../../modules/languages.json');
|
||||
|
||||
export default class BackgroundSettings extends React.PureComponent {
|
||||
export default class BackgroundSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -60,7 +60,7 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
return (
|
||||
<>
|
||||
<h2>{language.title}</h2>
|
||||
<Radio name='language' options={languages} element='.language' />
|
||||
<Radio name='language' options={languages} element='.other' />
|
||||
<h3>{language.quote}</h3>
|
||||
<Radio name='quotelanguage' options={this.state.quoteLanguages} category='quote' />
|
||||
</>
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../../modules/helpers/eventbus';
|
||||
|
||||
import DragHandleIcon from '@material-ui/icons/DragIndicator';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import { DragIndicator } from '@material-ui/icons';
|
||||
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const enabled = (setting) => {
|
||||
switch (setting) {
|
||||
case 'quicklinks':
|
||||
return (localStorage.getItem('quicklinksenabled') === 'true');
|
||||
default:
|
||||
return (localStorage.getItem(setting) === 'true');
|
||||
}
|
||||
};
|
||||
import EventBus from '../../../../../modules/helpers/eventbus';
|
||||
|
||||
const settings = window.language.modals.main.settings.sections;
|
||||
const widget_name = {
|
||||
@@ -26,8 +15,8 @@ const widget_name = {
|
||||
};
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => (
|
||||
<li className='sortableitem' style={{ display: enabled(value) ? 'block' : 'none' }}>
|
||||
<DragHandleIcon style={{'verticalAlign': 'middle'}} />
|
||||
<li className='sortableitem'>
|
||||
<DragIndicator style={{ verticalAlign: 'middle' }} />
|
||||
{widget_name[value]}
|
||||
</li>
|
||||
));
|
||||
@@ -36,7 +25,7 @@ const SortableContainer = sortableContainer(({ children }) => {
|
||||
return <ul className='sortablecontainer'>{children}</ul>;
|
||||
});
|
||||
|
||||
export default class OrderSettings extends React.PureComponent {
|
||||
export default class OrderSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -52,7 +41,6 @@ export default class OrderSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
const newArray = [...array];
|
||||
|
||||
const target = newArray[oldIndex];
|
||||
const inc = newIndex < oldIndex ? -1 : 1;
|
||||
|
||||
@@ -61,7 +49,6 @@ export default class OrderSettings extends React.PureComponent {
|
||||
}
|
||||
|
||||
newArray[newIndex] = target;
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
@@ -81,8 +68,18 @@ export default class OrderSettings extends React.PureComponent {
|
||||
toast(window.language.toasts.reset);
|
||||
}
|
||||
|
||||
enabled = (setting) => {
|
||||
switch (setting) {
|
||||
case 'quicklinks':
|
||||
return (localStorage.getItem('quicklinksenabled') === 'true');
|
||||
default:
|
||||
return (localStorage.getItem(setting) === 'true');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('order', JSON.stringify(this.state.items));
|
||||
window.stats.postEvent('setting', 'Widget order');
|
||||
EventBus.dispatch('refresh', 'widgets');
|
||||
}
|
||||
|
||||
@@ -92,9 +89,15 @@ export default class OrderSettings extends React.PureComponent {
|
||||
<h2>{this.language.sections.order.title}</h2>
|
||||
<span className='modalLink' onClick={this.reset}>{this.language.buttons.reset}</span>
|
||||
<SortableContainer onSortEnd={this.onSortEnd} lockAxis='y' lockToContainerEdges disableAutoscroll>
|
||||
{this.state.items.map((value, index) => (
|
||||
<SortableItem key={`item-${value}`} index={index} value={value} />
|
||||
))}
|
||||
{this.state.items.map((value, index) => {
|
||||
if (!this.enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem key={`item-${value}`} index={index} value={value} />
|
||||
);
|
||||
})}
|
||||
</SortableContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -7,10 +8,11 @@ export default function QuickLinks() {
|
||||
return (
|
||||
<>
|
||||
<h2>{language.title}</h2>
|
||||
<Switch name='quicklinksenabled' text={window.language.modals.main.settings.enabled} category='quicklinks' element='.quicklinks-container' />
|
||||
<Checkbox name='quicklinksddgProxy' text={window.language.modals.main.settings.sections.background.ddg_proxy} element='.other' />
|
||||
<Checkbox name='quicklinksnewtab' text={language.open_new} category='quicklinks' />
|
||||
<Checkbox name='quicklinkstooltip' text={language.tooltip} category='quicklinks' />
|
||||
<Switch name='quicklinksenabled' text={window.language.modals.main.settings.enabled} category='quicklinks' element='.quicklinks-container'/>
|
||||
<Checkbox name='quicklinksddgProxy' text={window.language.modals.main.settings.sections.background.ddg_image_proxy} category='quicklinks'/>
|
||||
<Checkbox name='quicklinksnewtab' text={language.open_new} category='quicklinks'/>
|
||||
<Checkbox name='quicklinkstooltip' text={language.tooltip} category='quicklinks'/>
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuicklinks' min='10' max='400' default='100' display='%' category='quicklinks'/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Text from '../Text';
|
||||
@@ -6,7 +6,7 @@ import Switch from '../Switch';
|
||||
import Slider from '../Slider';
|
||||
import Dropdown from '../Dropdown';
|
||||
|
||||
export default class QuoteSettings extends React.PureComponent {
|
||||
export default class QuoteSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -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' />
|
||||
<Text title={quote.custom_author} name='customQuoteAuthor' category='quote'/>
|
||||
</>
|
||||
);
|
||||
} 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,8 +59,8 @@ 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}
|
||||
<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' />
|
||||
{customSettings}
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuote' min='10' max='400' default='100' display='%' category='quote' />
|
||||
|
||||
<h3>{quote.buttons.title}</h3>
|
||||
<Checkbox name='copyButton' text={quote.buttons.copy} category='quote'/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Dropdown from '../Dropdown';
|
||||
import Checkbox from '../Checkbox';
|
||||
@@ -7,13 +8,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 {
|
||||
export default class SearchSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -21,7 +19,6 @@ export default class SearchSettings extends React.PureComponent {
|
||||
customDisplay: 'none',
|
||||
customValue: localStorage.getItem('customSearchEngine') || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
@@ -30,7 +27,7 @@ export default class SearchSettings extends React.PureComponent {
|
||||
customValue: ''
|
||||
});
|
||||
|
||||
toast(window.language.modals.main.settings.toasts.reset);
|
||||
toast(window.language.toasts.reset);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -77,21 +74,23 @@ 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}
|
||||
{/* not supported on firefox */}
|
||||
{(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/>
|
||||
<Checkbox name='autocomplete' text={search.autocomplete} category='search' element='.other'/>
|
||||
<Checkbox name='autocomplete' text={search.autocomplete} category='search' />
|
||||
<Radio title={search.autocomplete_provider} options={autocompleteProviders} name='autocompleteProvider' category='search'/>
|
||||
</>
|
||||
);
|
||||
|
||||
59
src/components/modals/main/settings/sections/Stats.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Switch from '../Switch';
|
||||
|
||||
import EventBus from '../../../../../modules/helpers/eventbus';
|
||||
|
||||
export default class Stats extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
stats: JSON.parse(localStorage.getItem('statsData')) || {}
|
||||
};
|
||||
this.language = window.language.modals.main.settings.sections.stats;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'stats') {
|
||||
if (localStorage.getItem('stats') === 'false') {
|
||||
localStorage.setItem('statsData', JSON.stringify({}));
|
||||
return this.setState({
|
||||
stats: {}
|
||||
});
|
||||
}
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (localStorage.getItem('stats') === 'false') {
|
||||
return (
|
||||
<>
|
||||
<h2>{window.language.modals.main.settings.reminder.title}</h2>
|
||||
<p>{this.language.warning}</p>
|
||||
<Switch name='stats' text={this.language.usage} category='stats'/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{this.language.title}</h2>
|
||||
<p>{this.language.sections.tabs_opened}: {this.state.stats['tabs-opened'] || 0}</p>
|
||||
<p>{this.language.sections.backgrounds_favourited}: {this.state.stats.feature ? this.state.stats.feature['background-favourite'] || 0 : 0}</p>
|
||||
<p>{this.language.sections.backgrounds_downloaded}: {this.state.stats.feature ? this.state.stats.feature['background-download'] || 0 : 0}</p>
|
||||
<p>{this.language.sections.quotes_favourited}: {this.state.stats.feature ? this.state.stats.feature['quoted-favourite'] || 0 : 0}</p>
|
||||
<p>{this.language.sections.quicklinks_added}: {this.state.stats.feature ? this.state.stats.feature['quicklink-add'] || 0 : 0}</p>
|
||||
<p>{this.language.sections.settings_changed}: {this.state.stats.setting ? Object.keys(this.state.stats.setting).length : 0}</p>
|
||||
<p>{this.language.sections.addons_installed}: {this.state.stats.marketplace ? this.state.stats.marketplace['install'] : 0}</p>
|
||||
<Switch name='stats' text={this.language.usage} category='stats'/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
@@ -6,7 +6,7 @@ import Switch from '../Switch';
|
||||
import Radio from '../Radio';
|
||||
import Slider from '../Slider';
|
||||
|
||||
export default class TimeSettings extends React.PureComponent {
|
||||
export default class TimeSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -35,21 +35,21 @@ export default class TimeSettings extends React.PureComponent {
|
||||
const digitalSettings = (
|
||||
<>
|
||||
<h3>{time.digital.title}</h3>
|
||||
<Radio title={time.format} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' element='.clock-container' />
|
||||
<Radio title={time.format} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' />
|
||||
<br/>
|
||||
<Checkbox name='seconds' text={time.digital.seconds} category='clock' element='.clock-container' />
|
||||
<Checkbox name='zero' text={time.digital.zero} category='clock' element='.clock-container' />
|
||||
<Checkbox name='seconds' text={time.digital.seconds} category='clock' />
|
||||
<Checkbox name='zero' text={time.digital.zero} category='clock' />
|
||||
</>
|
||||
);
|
||||
|
||||
const analogSettings = (
|
||||
<>
|
||||
<h3>{time.analogue.title}</h3>
|
||||
<Checkbox name='secondHand' text={time.analogue.second_hand} category='clock' element='.clock-container' />
|
||||
<Checkbox name='minuteHand' text={time.analogue.minute_hand} category='clock' element='.clock-container' />
|
||||
<Checkbox name='hourHand' text={time.analogue.hour_hand} category='clock' element='.clock-container' />
|
||||
<Checkbox name='hourMarks' text={time.analogue.hour_marks} category='clock' element='.clock-container' />
|
||||
<Checkbox name='minuteMarks' text={time.analogue.minute_marks} category='clock' element='.clock-container' />
|
||||
<Checkbox name='secondHand' text={time.analogue.second_hand} category='clock' />
|
||||
<Checkbox name='minuteHand' text={time.analogue.minute_hand} category='clock' />
|
||||
<Checkbox name='hourHand' text={time.analogue.hour_hand} category='clock' />
|
||||
<Checkbox name='hourMarks' text={time.analogue.hour_marks} category='clock' />
|
||||
<Checkbox name='minuteMarks' text={time.analogue.minute_marks} category='clock' />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -63,23 +63,23 @@ export default class TimeSettings extends React.PureComponent {
|
||||
|
||||
const longSettings = (
|
||||
<>
|
||||
<Checkbox name='dayofweek' text={time.date.day_of_week} category='date' element='.date' />
|
||||
<Checkbox name='datenth' text={time.date.datenth} category='date' element='.date' />
|
||||
<Checkbox name='dayofweek' text={time.date.day_of_week} category='date' />
|
||||
<Checkbox name='datenth' text={time.date.datenth} category='date' />
|
||||
</>
|
||||
);
|
||||
|
||||
const shortSettings = (
|
||||
<>
|
||||
<br/>
|
||||
<Dropdown label={time.date.short_format} name='dateFormat' category='date' element='.date'>
|
||||
<Dropdown label={time.date.short_format} name='dateFormat' category='date'>
|
||||
<option value='DMY'>DMY</option>
|
||||
<option value='MDY'>MDY</option>
|
||||
<option value='YMD'>YMD</option>
|
||||
</Dropdown>
|
||||
<br/><br/>
|
||||
<Dropdown label={time.date.short_separator.title} name='shortFormat' category='date' element='.date'>
|
||||
<option value='dots'>{time.date.short_separator.dots}</option>
|
||||
<Dropdown label={time.date.short_separator.title} name='shortFormat' category='date'>
|
||||
<option value='dash'>{time.date.short_separator.dash}</option>
|
||||
<option value='dots'>{time.date.short_separator.dots}</option>
|
||||
<option value='gaps'>{time.date.short_separator.gaps}</option>
|
||||
<option value='slashes'>{time.date.short_separator.slashes}</option>
|
||||
</Dropdown>
|
||||
@@ -96,27 +96,27 @@ export default class TimeSettings extends React.PureComponent {
|
||||
<>
|
||||
<h2>{time.title}</h2>
|
||||
<Switch name='time' text={this.language.enabled} category='clock' element='.clock-container' />
|
||||
<Dropdown label={time.type} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock' element='.clock-container'>
|
||||
<Dropdown label={time.type} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock'>
|
||||
<option value='digital'>{time.digital.title}</option>
|
||||
<option value='analogue'>{time.analogue.title}</option>
|
||||
<option value='percentageComplete'>{time.percentage_complete}</option>
|
||||
</Dropdown>
|
||||
{timeSettings}
|
||||
{this.state.timeType !== 'analogue' ?
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomClock' min='10' max='400' default='100' display='%' category='clock' element='.clock-container' />
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomClock' min='10' max='400' default='100' display='%' category='clock'/>
|
||||
: null}
|
||||
|
||||
<h3>{time.date.title}</h3>
|
||||
<Switch name='date' text={this.language.enabled} category='date' element='.date'/>
|
||||
<Dropdown label={time.type} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date' element='.date'>
|
||||
<Dropdown label={time.type} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date'>
|
||||
<option value='long'>{time.date.type.long}</option>
|
||||
<option value='short'>{time.date.type.short}</option>
|
||||
</Dropdown>
|
||||
<br/>
|
||||
<Checkbox name='datezero' text={time.digital.zero} category='date' element='.date' />
|
||||
<Checkbox name='weeknumber' text={time.date.week_number} category='date' element='.date'/>
|
||||
<Checkbox name='datezero' text={time.digital.zero} category='date'/>
|
||||
<Checkbox name='weeknumber' text={time.date.week_number} category='date'/>
|
||||
{dateSettings}
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomDate' min='10' max='400' default='100' display='%' category='date' element='.date' />
|
||||
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomDate' min='10' max='400' default='100' display='%' category='date'/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Switch from '../Switch';
|
||||
import Radio from '../Radio';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Slider from '../Slider';
|
||||
|
||||
export default class TimeSettings extends React.PureComponent {
|
||||
export default class TimeSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../../../modules/helpers/eventbus';
|
||||
import { PureComponent } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '../../Checkbox';
|
||||
import Dropdown from '../../Dropdown';
|
||||
@@ -11,9 +10,9 @@ import Radio from '../../Radio';
|
||||
|
||||
import ColourSettings from './Colour';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import EventBus from '../../../../../../modules/helpers/eventbus';
|
||||
|
||||
export default class BackgroundSettings extends React.PureComponent {
|
||||
export default class BackgroundSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -114,7 +113,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) => (
|
||||
@@ -122,12 +121,22 @@ export default class BackgroundSettings extends React.PureComponent {
|
||||
))}
|
||||
</Dropdown>
|
||||
<br/><br/>
|
||||
<Dropdown label={background.source.quality.title} name='apiQuality' category='background' element='.other'>
|
||||
<Dropdown label={background.source.quality.title} name='apiQuality' element='.other'>
|
||||
<option value='original'>{background.source.quality.original}</option>
|
||||
<option value='high'>{background.source.quality.high}</option>
|
||||
<option value='normal'>{background.source.quality.normal}</option>
|
||||
<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,14 +157,18 @@ 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} />
|
||||
<Checkbox name='bgtransition' text={background.transition} />
|
||||
<Checkbox name='photoInformation' text={background.photo_information} category='background' element='.other' />
|
||||
<Switch name='background' text={this.language.enabled} category='background' element='#backgroundImage' />
|
||||
<Checkbox name='ddgProxy' text={background.ddg_image_proxy} element='.other' />
|
||||
<Checkbox name='bgtransition' text={background.transition} element='.other' />
|
||||
<Checkbox name='photoInformation' text={background.photo_information} element='.other' />
|
||||
|
||||
<h3>{background.source.title}</h3>
|
||||
<Dropdown label={background.type.title} name='backgroundType' onChange={(value) => this.setState({ backgroundType: value })} category='background'>
|
||||
@@ -169,22 +182,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' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PureComponent, Fragment } from 'react';
|
||||
import { ColorPicker } from 'react-color-gradient-picker';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import hexToRgb from '../../../../../../modules/helpers/background/hexToRgb';
|
||||
import rgbToHex from '../../../../../../modules/helpers/background/rgbToHex';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import 'react-color-gradient-picker/dist/index.css';
|
||||
import '../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss';
|
||||
|
||||
export default class ColourSettings extends React.PureComponent {
|
||||
export default class ColourSettings extends PureComponent {
|
||||
DefaultGradientSettings = { angle: '180', gradient: [{ colour: '#ffb032', stop: 0 }], type: 'linear' };
|
||||
GradientPickerInitalState = undefined;
|
||||
|
||||
@@ -55,7 +53,7 @@ export default class ColourSettings extends React.PureComponent {
|
||||
try {
|
||||
gradientSettings = JSON.parse(hex);
|
||||
} catch (e) {
|
||||
// Disregard exception.
|
||||
// Disregard exception
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +101,8 @@ export default class ColourSettings extends React.PureComponent {
|
||||
};
|
||||
return newState;
|
||||
});
|
||||
|
||||
window.stats.postEvent('setting', 'Changed backgroundtype from colour to gradient');
|
||||
}
|
||||
|
||||
currentGradientSettings = () => {
|
||||
@@ -164,10 +164,10 @@ export default class ColourSettings extends React.PureComponent {
|
||||
} else {
|
||||
gradientInputs = this.state.gradientSettings.gradient.map((g, i) => {
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<Fragment key={i}>
|
||||
<input id={'colour_' + i} type='color' name='colour' className='colour' onChange={(event) => this.onGradientChange(event, i)} value={g.colour}></input>
|
||||
<label htmlFor={'colour_' + i} className='customBackgroundHex'>{g.colour}</label>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import Tabs from './backend/Tabs';
|
||||
|
||||
import Added from '../marketplace/sections/Added';
|
||||
import Sideload from '../marketplace/sections/Sideload';
|
||||
|
||||
import Tabs from './backend/Tabs';
|
||||
import Create from '../marketplace/sections/Create';
|
||||
|
||||
export default function Addons() {
|
||||
const addons = window.language.modals.main.addons;
|
||||
|
||||
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>
|
||||
<div label={addons.create.title} name='create'><Create/></div>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import MarketplaceTab from '../marketplace/sections/Marketplace';
|
||||
|
||||
import Tabs from './backend/Tabs';
|
||||
|
||||
import MarketplaceTab from '../marketplace/sections/Marketplace';
|
||||
|
||||
export default function Marketplace() {
|
||||
const marketplace = window.language.modals.main.marketplace;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Tabs from './backend/Tabs';
|
||||
|
||||
import About from '../settings/sections/About';
|
||||
import Language from '../settings/sections/Language';
|
||||
import Search from '../settings/sections/Search';
|
||||
@@ -12,36 +14,32 @@ import Order from '../settings/sections/Order';
|
||||
import Experimental from '../settings/sections/Experimental';
|
||||
import QuickLinks from '../settings/sections/QuickLinks';
|
||||
import Weather from '../settings/sections/Weather';
|
||||
|
||||
import Tabs from './backend/Tabs';
|
||||
import Stats from '../settings/sections/Stats';
|
||||
|
||||
export default function Settings() {
|
||||
const { reminder, sections } = window.language.modals.main.settings;
|
||||
|
||||
let display = 'none';
|
||||
if (localStorage.getItem('showReminder') === 'true') {
|
||||
display = 'block';
|
||||
}
|
||||
const display = (localStorage.getItem('showReminder') === 'true') ? 'block' : 'none';
|
||||
|
||||
return (
|
||||
<>
|
||||
<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.stats.title} name='stats'><Stats/></div>
|
||||
<div label={sections.experimental.title} name='experimental'><Experimental/></div>
|
||||
<div label={sections.changelog} name='changelog'><Changelog/></div>
|
||||
<div label={sections.about.title} name='about'><About/></div>
|
||||
</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>
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
import React from 'react';
|
||||
|
||||
// Navbar
|
||||
import Settings from '@material-ui/icons/SettingsRounded';
|
||||
import Addons from '@material-ui/icons/Widgets';
|
||||
import Marketplace from '@material-ui/icons/ShoppingBasket';
|
||||
|
||||
// Settings
|
||||
import Time from '@material-ui/icons/AccessAlarm';
|
||||
import Greeting from '@material-ui/icons/EmojiPeopleOutlined';
|
||||
import Quote from '@material-ui/icons/FormatQuoteOutlined';
|
||||
import Background from '@material-ui/icons/PhotoOutlined';
|
||||
import Search from '@material-ui/icons/Search';
|
||||
import Appearance from '@material-ui/icons/FormatPaintOutlined';
|
||||
import Language from '@material-ui/icons/Translate';
|
||||
import Changelog from '@material-ui/icons/NewReleasesOutlined';
|
||||
import About from '@material-ui/icons/InfoOutlined';
|
||||
import Experimental from '@material-ui/icons/BugReportOutlined';
|
||||
import Order from '@material-ui/icons/List';
|
||||
import Weather from '@material-ui/icons/CloudOutlined';
|
||||
import Advanced from '@material-ui/icons/SettingsOutlined';
|
||||
import QuickLinks from '@material-ui/icons/Link';
|
||||
|
||||
// Addons
|
||||
import Sideload from '@material-ui/icons/Code';
|
||||
import Added from '@material-ui/icons/AddCircleOutline';
|
||||
import { memo } from 'react';
|
||||
import {
|
||||
SettingsRounded as Settings,
|
||||
Widgets as Addons,
|
||||
ShoppingBasket as Marketplace,
|
||||
AccessAlarm as Time,
|
||||
EmojiPeopleOutlined as Greeting,
|
||||
FormatQuoteOutlined as Quote,
|
||||
PhotoOutlined as Background,
|
||||
Search,
|
||||
FormatPaintOutlined as Appearance,
|
||||
Translate as Language,
|
||||
NewReleasesOutlined as Changelog,
|
||||
InfoOutlined as About,
|
||||
BugReportOutlined as Experimental,
|
||||
List as Order,
|
||||
CloudOutlined as Weather,
|
||||
SettingsOutlined as Advanced,
|
||||
Link as QuickLinks,
|
||||
AssessmentOutlined as Stats,
|
||||
Code as Sideload,
|
||||
AddCircleOutline as Added,
|
||||
CreateNewFolderOutlined as Create
|
||||
} from '@material-ui/icons';
|
||||
|
||||
function Tab(props) {
|
||||
let className = 'tab-list-item';
|
||||
@@ -60,16 +58,20 @@ function Tab(props) {
|
||||
case settings.order.title: icon = <Order/>; break;
|
||||
case settings.language.title: icon = <Language/>; divider = true; break;
|
||||
case settings.advanced.title: icon = <Advanced/>; break;
|
||||
case settings.stats.title: icon = <Stats/>; break;
|
||||
case settings.experimental.title: icon = <Experimental/>; divider = true; break;
|
||||
case settings.changelog: icon = <Changelog/>; break;
|
||||
case settings.about.title: icon = <About/>; break;
|
||||
|
||||
// Store
|
||||
// Addons
|
||||
case addons.added: icon = <Added/>; break;
|
||||
case addons.sideload: icon = <Sideload/>; break;
|
||||
case addons.create.title: icon = <Create/>; break;
|
||||
|
||||
// Marketplace
|
||||
case marketplace.photo_packs: icon = <Background/>; break;
|
||||
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 memo(Tab);
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Tab from './Tab';
|
||||
import ErrorBoundary from '../../../ErrorBoundary';
|
||||
|
||||
export default class Tabs extends React.PureComponent {
|
||||
export default class Tabs extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
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', `Opened ${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,107 @@
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
import TwitterIcon from '@material-ui/icons/Twitter';
|
||||
import ForumIcon from '@material-ui/icons/Forum';
|
||||
import { PureComponent } 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 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
214
src/components/modals/welcome/WelcomeSections.jsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { CloudUpload, AutoAwesome, LightMode, DarkMode } from '@material-ui/icons';
|
||||
|
||||
import Radio from '../main/settings/Radio';
|
||||
import Checkbox from '../main/settings/Checkbox';
|
||||
import FileUpload from '../main/settings/FileUpload';
|
||||
|
||||
import { loadSettings } from '../../../modules/helpers/settings';
|
||||
import { importSettings } from '../../../modules/helpers/settings/modals';
|
||||
|
||||
const languages = require('../../../modules/languages.json');
|
||||
const default_settings = require('../../../modules/default_settings.json');
|
||||
|
||||
export default class WelcomeSections extends 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);
|
||||
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) {
|
||||
importSettings(e);
|
||||
|
||||
let settings = [];
|
||||
const data = JSON.parse(e.target.result);
|
||||
Object.keys(data).forEach((setting) => {
|
||||
// language and theme already shown, the others are only used internally
|
||||
if (setting === 'language' || setting === 'theme'|| setting === 'firstRun' || setting === 'showWelcome' || setting === 'showReminder') {
|
||||
return;
|
||||
}
|
||||
|
||||
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} <a href={window.constants.TRANSLATIONS_URL} className='resetLink' target='_blank' rel='noopener noreferrer'>GitHub</a>!</p>
|
||||
<Radio name='language' options={languages} category='welcomeLanguage'/>
|
||||
</>
|
||||
);
|
||||
|
||||
const { appearance, advanced, background, quicklinks } = window.language.modals.main.settings.sections;
|
||||
const 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')}>
|
||||
<AutoAwesome/>
|
||||
<span>{appearance.theme.auto}</span>
|
||||
</div>
|
||||
<div className='options'>
|
||||
<div className={this.state.lightClass} onClick={() => this.changeTheme('light')}>
|
||||
<LightMode/>
|
||||
<span>{appearance.theme.light}</span>
|
||||
</div>
|
||||
<div className={this.state.darkClass} onClick={() => this.changeTheme('dark')}>
|
||||
<DarkMode/>
|
||||
<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()}>
|
||||
<CloudUpload/>
|
||||
<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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../modules/helpers/eventbus';
|
||||
import { PureComponent, Fragment, Suspense, lazy } from 'react';
|
||||
|
||||
import Clock from './time/Clock';
|
||||
import Greeting from './greeting/Greeting';
|
||||
@@ -9,10 +7,13 @@ import Search from './search/Search';
|
||||
import QuickLinks from './quicklinks/QuickLinks';
|
||||
import Date from './time/Date';
|
||||
|
||||
const Weather = React.lazy(() => import('./weather/Weather'));
|
||||
import EventBus from '../../modules/helpers/eventbus';
|
||||
|
||||
const Weather = lazy(() => import('./weather/Weather'));
|
||||
const renderLoader = () => <></>;
|
||||
|
||||
export default class Widgets extends React.PureComponent {
|
||||
export default class Widgets extends PureComponent {
|
||||
online = (localStorage.getItem('offlineMode') === 'false');
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -24,7 +25,7 @@ export default class Widgets extends React.PureComponent {
|
||||
greeting: this.enabled('greeting') ? <Greeting/> : null,
|
||||
quote: this.enabled('quote') ? <Quote/> : null,
|
||||
date: this.enabled('date') ? <Date/> : null,
|
||||
quicklinks: this.enabled('quicklinksenabled') ? <QuickLinks/> : null
|
||||
quicklinks: this.enabled('quicklinksenabled') && this.online ? <QuickLinks/> : null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,25 +44,30 @@ 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 = [];
|
||||
|
||||
if (this.state.order) {
|
||||
this.state.order.forEach((element) => {
|
||||
elements.push(<React.Fragment key={element}>{this.widgets[element]}</React.Fragment>);
|
||||
elements.push(<Fragment key={element}>{this.widgets[element]}</Fragment>);
|
||||
});
|
||||
} else {
|
||||
// prevent error
|
||||
elements = ['greeting', 'time', 'quicklinks', 'quote', 'date'];
|
||||
elements = [<Greeting/>, <Clock/>, <QuickLinks/>, <Quote/>, <Date/>];
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='widgets'>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
{this.enabled('searchBar') ? <Search/> : null}
|
||||
{elements}
|
||||
{this.enabled('weatherEnabled') && !localStorage.getItem('offlineMode') ? <Weather/> : null}
|
||||
</React.Suspense>
|
||||
{this.enabled('weatherEnabled') && this.online ? <Weather/> : null}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// warning: the code here is fairly messy and probably needs a rewrite
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
// todo: rewrite this mess
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import PhotoInformation from './PhotoInformation';
|
||||
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
import Interval from '../../../modules/helpers/interval';
|
||||
import { videoCheck, offlineBackground, gradientStyleBuilder } from '../../../modules/helpers/background/widget';
|
||||
|
||||
import './scss/index.scss';
|
||||
|
||||
export default class Background extends React.PureComponent {
|
||||
export default class Background extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -24,51 +26,11 @@ export default class Background extends React.PureComponent {
|
||||
this.language = window.language.widgets.background;
|
||||
}
|
||||
|
||||
gradientStyleBuilder(gradientSettings) {
|
||||
const { type, angle, gradient } = gradientSettings;
|
||||
let style = `background: ${gradient[0].colour};`;
|
||||
|
||||
if (gradient.length > 1) {
|
||||
// Note: Append the gradient for additional browser support.
|
||||
const stepStyles = gradient.map((g) => ` ${g.colour} ${g.stop}%`).join();
|
||||
style += ` background: ${type}-gradient(${(type === 'linear' ? (`${angle}deg,`) : '')}${stepStyles})`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
type: 'colour',
|
||||
style: style
|
||||
});
|
||||
}
|
||||
|
||||
videoCheck(url) {
|
||||
return url.startsWith('data:video/') || url.endsWith('.mp4') || url.endsWith('.webm') || url.endsWith('.ogg');
|
||||
}
|
||||
|
||||
offlineBackground() {
|
||||
const offlineImages = require('./offline_images.json');
|
||||
|
||||
// Get all photographers from the keys in offlineImages.json
|
||||
const photographers = Object.keys(offlineImages);
|
||||
const photographer = photographers[Math.floor(Math.random() * photographers.length)];
|
||||
|
||||
const randomImage = offlineImages[photographer].photo[
|
||||
Math.floor(Math.random() * offlineImages[photographer].photo.length)
|
||||
];
|
||||
|
||||
this.setState({
|
||||
url: `./offline-images/${randomImage}.webp`,
|
||||
photoInfo: {
|
||||
offline: true,
|
||||
credit: photographer
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
@@ -77,7 +39,7 @@ export default class Background extends React.PureComponent {
|
||||
photoInformation.style.display = 'block';
|
||||
}
|
||||
backgroundImage.style.background = null;
|
||||
return backgroundImage.style.background = `url(${url})`;
|
||||
return backgroundImage.style.background = `url(${url})`;
|
||||
}
|
||||
|
||||
// firstly we set the background as hidden and make sure there is no background set currently
|
||||
@@ -98,7 +60,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,26 +77,31 @@ 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;
|
||||
}
|
||||
|
||||
const setFavourited = (favourited) => {
|
||||
this.setState({
|
||||
url: favourited.url,
|
||||
photoInfo: {
|
||||
credit: favourited.credit,
|
||||
location: favourited.location,
|
||||
camera: favourited.camera
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switch (localStorage.getItem('backgroundType')) {
|
||||
case 'api':
|
||||
if (offline) {
|
||||
return this.offlineBackground();
|
||||
return this.setState(offlineBackground());
|
||||
}
|
||||
|
||||
// favourite button
|
||||
const favourited = JSON.parse(localStorage.getItem('favourite'));
|
||||
if (favourited) {
|
||||
return this.setState({
|
||||
url: favourited.url,
|
||||
photoInfo: {
|
||||
credit: favourited.credit,
|
||||
location: favourited.location,
|
||||
camera: favourited.camera,
|
||||
resolution: favourited.resolution
|
||||
}
|
||||
});
|
||||
return setFavourited(favourited);
|
||||
}
|
||||
|
||||
// API background
|
||||
@@ -146,11 +112,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:
|
||||
@@ -162,12 +127,11 @@ export default class Background extends React.PureComponent {
|
||||
data = await (await fetch(requestURL)).json();
|
||||
} catch (e) {
|
||||
// if requesting to the API fails, we get an offline image
|
||||
return this.offlineBackground();
|
||||
}
|
||||
return this.setState(offlineBackground());
|
||||
}
|
||||
|
||||
let credit = data.photographer;
|
||||
let photoURL = '';
|
||||
let photographerURL = '';
|
||||
let photoURL, photographerURL;
|
||||
|
||||
if (backgroundAPI === 'unsplash') {
|
||||
credit = data.photographer + ` ${this.language.unsplash}`;
|
||||
@@ -179,7 +143,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 +152,15 @@ 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':
|
||||
@@ -212,7 +179,7 @@ export default class Background extends React.PureComponent {
|
||||
}
|
||||
|
||||
if (typeof gradientSettings === 'object' && gradientSettings !== null) {
|
||||
return this.gradientStyleBuilder(gradientSettings);
|
||||
return this.setState(gradientStyleBuilder(gradientSettings));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -221,14 +188,14 @@ export default class Background extends React.PureComponent {
|
||||
|
||||
// allow users to use offline images
|
||||
if (offline && !customBackground.startsWith('data:')) {
|
||||
return this.offlineBackground();
|
||||
return this.setState(offlineBackground());
|
||||
}
|
||||
|
||||
if (customBackground !== '' && customBackground !== 'undefined') {
|
||||
this.setState({
|
||||
url: customBackground,
|
||||
type: 'custom',
|
||||
video: this.videoCheck(customBackground),
|
||||
video: videoCheck(customBackground),
|
||||
photoInfo: {
|
||||
hidden: true
|
||||
}
|
||||
@@ -238,7 +205,12 @@ export default class Background extends React.PureComponent {
|
||||
|
||||
case 'photo_pack':
|
||||
if (offline) {
|
||||
return this.offlineBackground();
|
||||
return this.setState(offlineBackground());
|
||||
}
|
||||
|
||||
const photofavourited = JSON.parse(localStorage.getItem('favourite'));
|
||||
if (photofavourited) {
|
||||
return setFavourited(photofavourited);
|
||||
}
|
||||
|
||||
const photoPack = JSON.parse(localStorage.getItem('photo_packs'));
|
||||
@@ -255,7 +227,7 @@ export default class Background extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -279,6 +251,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
|
||||
@@ -293,16 +269,17 @@ export default class Background extends React.PureComponent {
|
||||
return element.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// video backgrounds
|
||||
if (this.state.video === true) {
|
||||
document.getElementById('backgroundVideo').style.display = 'block';
|
||||
} 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 +292,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 +309,50 @@ 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') {
|
||||
if (localStorage.getItem('backgroundType') === 'api') {
|
||||
Interval(() => {
|
||||
try {
|
||||
document.getElementById('backgroundImage').classList.remove('fade-in');
|
||||
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
|
||||
} catch (e) {
|
||||
// Disregard exception
|
||||
}
|
||||
this.getBackground();
|
||||
}, Number(interval), 'background');
|
||||
|
||||
try {
|
||||
// todo: refactor this mess
|
||||
const current = JSON.parse(localStorage.getItem('currentBackground'));
|
||||
const offline = localStorage.getItem('offlineMode');
|
||||
if (current.url.startsWith('http') && offline === 'false') {
|
||||
this.setState(current);
|
||||
} else if (current.url.startsWith('http')) {
|
||||
this.setState(offlineBackground());
|
||||
} else {
|
||||
if (offline === 'false') {
|
||||
localStorage.removeItem('currentBackground');
|
||||
return this.getBackground();
|
||||
}
|
||||
this.setState(current);
|
||||
}
|
||||
} catch (e) {
|
||||
this.setBackground();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.getBackground();
|
||||
}
|
||||
}
|
||||
|
||||
// only set once we've got the info
|
||||
@@ -345,6 +364,10 @@ export default class Background extends React.PureComponent {
|
||||
this.setBackground();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.video === true) {
|
||||
const enabled = (setting) => {
|
||||
@@ -352,7 +375,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 +385,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'/>
|
||||
{(this.state.photoInfo.credit !== '') ?
|
||||
<PhotoInformation className={this.props.photoInformationClass} info={this.state.photoInfo} api={this.state.currentAPI}/>
|
||||
<div style={{ WebkitFilter: `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}` }} id='backgroundImage'/>
|
||||
{(this.state.photoInfo.credit !== '') ?
|
||||
<PhotoInformation info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,42 +1,51 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { Star, StarBorder } from '@material-ui/icons';
|
||||
|
||||
import Tooltip from '../../helpers/tooltip/Tooltip';
|
||||
|
||||
import StarIcon from '@material-ui/icons/Star';
|
||||
import StarIcon2 from '@material-ui/icons/StarBorder';
|
||||
export default class Favourite extends PureComponent {
|
||||
buttons = {
|
||||
favourited: <Star onClick={() => this.favourite()} className='topicons' />,
|
||||
unfavourited: <StarBorder onClick={() => this.favourite()} className='topicons' />
|
||||
}
|
||||
|
||||
export default class Favourite extends React.PureComponent {
|
||||
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('")', '');
|
||||
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// photo information now hides information if it isn't sent, unless if photoinformation hover is hidden
|
||||
const location = document.getElementById('infoLocation');
|
||||
const camera = document.getElementById('infoCamera');
|
||||
|
||||
localStorage.setItem('favourite', JSON.stringify({
|
||||
url: url,
|
||||
credit: document.getElementById('credit').textContent,
|
||||
location: document.getElementById('infoLocation').textContent,
|
||||
camera: document.getElementById('infoCamera').textContent,
|
||||
location: location ? location.innerText : 'N/A',
|
||||
camera: camera ? camera.innerText : 'N/A',
|
||||
resolution: document.getElementById('infoResolution').textContent
|
||||
}));
|
||||
|
||||
this.setState({
|
||||
favourited: <StarIcon onClick={this.favourite} className='topicons' />
|
||||
favourited: this.buttons.favourited
|
||||
});
|
||||
window.stats.postEvent('feature', 'Background unfavourite');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import { Fullscreen } from '@material-ui/icons';
|
||||
|
||||
import Tooltip from '../../helpers/tooltip/Tooltip';
|
||||
|
||||
import FullscreenIcon from '@material-ui/icons/Fullscreen';
|
||||
|
||||
export default class Maximise extends React.PureComponent {
|
||||
export default class Maximise extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -13,6 +12,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 +32,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,19 +42,21 @@ 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');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Tooltip title={window.language.modals.main.settings.sections.background.buttons.view}>
|
||||
<FullscreenIcon onClick={this.maximise} className='topicons' />
|
||||
<Fullscreen onClick={this.maximise} className='topicons' />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import Info from '@material-ui/icons/Info';
|
||||
import Location from '@material-ui/icons/LocationOn';
|
||||
import Camera from '@material-ui/icons/PhotoCamera';
|
||||
import Resolution from '@material-ui/icons/Crop';
|
||||
import Photographer from '@material-ui/icons/Person';
|
||||
import Download from '@material-ui/icons/GetApp';
|
||||
import { useState, Fragment } from 'react';
|
||||
import { Info, LocationOn, PhotoCamera, Crop as Resolution, Person as Photographer, GetApp as Download } from '@material-ui/icons';
|
||||
|
||||
const toDataURL = async (url) => {
|
||||
const res = await fetch(url);
|
||||
return URL.createObjectURL(await res.blob());
|
||||
};
|
||||
|
||||
const formatText = (text) => {
|
||||
return text.toLowerCase().replaceAll(',', '').replaceAll(' ', '-');
|
||||
};
|
||||
|
||||
const downloadImage = async (info) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = await toDataURL(info.url);
|
||||
// todo: make this a bit cleaner
|
||||
link.download = `mue-${info.credit.toLowerCase().replaceAll(' ', '-')}-${info.location.toLowerCase().replaceAll(',', '').replaceAll(' ', '-')}.jpg`;
|
||||
link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.stats.postEvent('feature', 'Background download');
|
||||
};
|
||||
|
||||
export default function PhotoInformation(props) {
|
||||
const [width, setWidth] = useState(0);
|
||||
const [height, setHeight] = useState(0);
|
||||
|
||||
const language = window.language.widgets.background;
|
||||
|
||||
if (props.info.hidden === true || !props.info.credit) {
|
||||
@@ -44,29 +47,56 @@ 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;
|
||||
|
||||
// info is still there because we want the favourite button to work
|
||||
if (localStorage.getItem('photoInformation') === 'false') {
|
||||
return (
|
||||
<div className='photoInformation'>
|
||||
<h1>{photo} <span id='credit'>{credit}</span></h1>
|
||||
<div style={{ display: 'none' }}>
|
||||
<span id='infoLocation'>{props.info.location || 'N/A'}</span>
|
||||
<span id='infoCamera'>{props.info.camera || 'N/A'}</span>
|
||||
<span id='infoResolution'>{width}x{height}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='photoInformation'>
|
||||
<h1>{photo} <span id='credit'>{credit}</span></h1>
|
||||
{localStorage.getItem('photoInformation') !== 'false' ? <><Info className='photoInformationHover'/>
|
||||
<div className={props.className || 'infoCard'}>
|
||||
<Info className='infoIcon'/>
|
||||
<h1>{language.information}</h1>
|
||||
<hr/>
|
||||
<Location/>
|
||||
<span id='infoLocation'>{props.info.location || 'N/A'}</span>
|
||||
<Camera/>
|
||||
<span id='infoCamera'>{props.info.camera || 'N/A'}</span>
|
||||
<Resolution/>
|
||||
<span id='infoResolution'>{props.info.resolution || 'N/A'}</span>
|
||||
<Photographer/>
|
||||
<span>{photographer}</span>
|
||||
{(localStorage.getItem('downloadbtn') === 'true') && !props.info.offline && !props.info.photographerURL ?
|
||||
<>
|
||||
<Download/>
|
||||
<span className='download' onClick={() => downloadImage(props.info)}>{language.download}</span>
|
||||
</> : null}
|
||||
</div>
|
||||
</>: null}
|
||||
<Info className='photoInformationHover'/>
|
||||
<div className='infoCard'>
|
||||
<Info className='infoIcon'/>
|
||||
<h1>{language.information}</h1>
|
||||
<hr/>
|
||||
{/* fix console error by using fragment and key */}
|
||||
{props.info.location && props.info.location !== 'N/A' ? <Fragment key='location'>
|
||||
<LocationOn/>
|
||||
<span id='infoLocation'>{props.info.location}</span>
|
||||
</Fragment> : null}
|
||||
{props.info.camera && props.info.camera !== 'N/A' ? <Fragment key='camera'>
|
||||
<PhotoCamera/>
|
||||
<span id='infoCamera'>{props.info.camera}</span>
|
||||
</Fragment> : null}
|
||||
<Resolution/>
|
||||
<span id='infoResolution'>{width}x{height}</span>
|
||||
<Photographer/>
|
||||
<span>{photographer}</span>
|
||||
{(localStorage.getItem('downloadbtn') === 'true') && !props.info.offline && !props.info.photographerURL ?
|
||||
<>
|
||||
<Download/>
|
||||
<span className='download' onClick={() => downloadImage(props.info)}>{language.download}</span>
|
||||
</>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { nth, convertTimezone } from '../../../modules/helpers/date';
|
||||
import EventBus from '../../../modules/helpers/eventbus';
|
||||
import dtf from '../../../modules/helpers/date';
|
||||
|
||||
import './greeting.scss';
|
||||
|
||||
export default class Greeting extends React.PureComponent {
|
||||
export default class Greeting extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -46,7 +46,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 && timezone !== 'auto') {
|
||||
now = convertTimezone(now, timezone);
|
||||
}
|
||||
|
||||
const hour = now.getHours();
|
||||
|
||||
// Set the default greeting string to "Good evening"
|
||||
@@ -59,12 +64,12 @@ export default class Greeting extends React.PureComponent {
|
||||
message = this.language.afternoon;
|
||||
}
|
||||
|
||||
// Events
|
||||
message = this.doEvents(now, message);
|
||||
|
||||
// Events and custom
|
||||
const custom = localStorage.getItem('defaultGreetingMessage');
|
||||
if (custom === 'false') {
|
||||
message = '';
|
||||
} else {
|
||||
message = this.doEvents(now, message);
|
||||
}
|
||||
|
||||
// Name
|
||||
@@ -77,18 +82,23 @@ export default class Greeting extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
if (custom === 'false') {
|
||||
const birthday = localStorage.getItem('birthdayenabled');
|
||||
|
||||
if (custom === 'false' && birthday !== 'true') {
|
||||
name = name.replace(',', '');
|
||||
}
|
||||
|
||||
// Birthday
|
||||
const birth = new Date(localStorage.getItem('birthday'));
|
||||
if (localStorage.getItem('birthdayenabled') === 'true' && birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
|
||||
if (localStorage.getItem('birthdayage') === 'true') {
|
||||
const text = this.language.birthday.split(' ');
|
||||
message = `${text[0]} ${dtf.nth(this.calculateAge(birth))} ${text[1]}`;
|
||||
} else {
|
||||
message = this.language.birthday;
|
||||
if (birthday === 'true') {
|
||||
const birth = new Date(localStorage.getItem('birthday'));
|
||||
|
||||
if (birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
|
||||
if (localStorage.getItem('birthdayage') === 'true') {
|
||||
const text = this.language.birthday.split(' ');
|
||||
message = `${text[0]} ${nth(this.calculateAge(birth))} ${text[1]}`;
|
||||
} else {
|
||||
message = this.language.birthday;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +113,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 +124,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 +135,10 @@ export default class Greeting extends React.PureComponent {
|
||||
this.getGreeting(0);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
return <h1 className='greeting'>
|
||||
{this.state.greeting}
|
||||
|
||||
@@ -1,47 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
import RefreshIcon from '@material-ui/icons/RefreshRounded';
|
||||
import Gear from '@material-ui/icons/SettingsRounded';
|
||||
import NotesIcon from '@material-ui/icons/AssignmentRounded';
|
||||
import Report from '@material-ui/icons/SmsFailed';
|
||||
import { PureComponent } from 'react';
|
||||
import { RefreshRounded, SettingsRounded, AssignmentRounded as NotesRounded, SmsFailed as Report } from '@material-ui/icons';
|
||||
|
||||
import Notes from './Notes';
|
||||
import Maximise from '../background/Maximise';
|
||||
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 PureComponent {
|
||||
setZoom() {
|
||||
const zoomNavbar = Number((localStorage.getItem('zoomNavbar') || 100) / 100);
|
||||
const navbarIcons = document.querySelectorAll('.navbar-container');
|
||||
for (let i = 0; i < navbarIcons.length; i++) {
|
||||
navbarIcons[i].style.fontSize = `${zoomNavbar}em`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navbar-container'>
|
||||
{(localStorage.getItem('view') === 'true' && backgroundEnabled) ? <Maximise/> : null}
|
||||
{(localStorage.getItem('favouriteEnabled') === 'true' && backgroundEnabled) ? <Favourite/> : null}
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'navbar' || data === 'background') {
|
||||
this.forceUpdate();
|
||||
this.setZoom();
|
||||
}
|
||||
});
|
||||
|
||||
this.setZoom();
|
||||
}
|
||||
|
||||
render() {
|
||||
const backgroundEnabled = (localStorage.getItem('background') === 'true');
|
||||
|
||||
{(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')}/>
|
||||
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'>
|
||||
<NotesRounded 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}>
|
||||
<RefreshRounded className='refreshicon topicons' onClick={() => window.location.reload()}/>
|
||||
</Tooltip>
|
||||
: null}
|
||||
|
||||
<Tooltip title={window.language.modals.main.navbar.settings}>
|
||||
<SettingsRounded 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import { FileCopyRounded, AssignmentRounded as NotesRounded, PushPin }from '@material-ui/icons';
|
||||
import TextareaAutosize from '@material-ui/core/TextareaAutosize';
|
||||
|
||||
import CopyIcon from '@material-ui/icons/FileCopyRounded';
|
||||
import NotesIcon from '@material-ui/icons/AssignmentRounded';
|
||||
import Pin from './Pin';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Notes extends React.PureComponent {
|
||||
export default class Notes extends 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 +21,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/>
|
||||
<NotesRounded/>
|
||||
<h3>{this.language.title}</h3>
|
||||
</div>
|
||||
<TextareaAutosize rowsMax={50} placeholder={this.language.placeholder} value={this.state.notes} onChange={this.setNotes}/>
|
||||
<button onClick={this.pin} className='pinNote'><Pin/></button>
|
||||
<button onClick={this.copy} className='copyNote'><CopyIcon/></button>
|
||||
<TextareaAutosize rowsmax={50} placeholder={this.language.placeholder} value={this.state.notes} onChange={this.setNotes}/>
|
||||
<button onClick={() => this.pin()} className='pinNote'><PushPin/></button>
|
||||
<button onClick={() => this.copy()} className='copyNote'><FileCopyRounded/></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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
svg {
|
||||
float: left;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
@@ -45,7 +46,7 @@ textarea {
|
||||
|
||||
.topbarnotes {
|
||||
svg {
|
||||
font-size: 46px;
|
||||
font-size: 46px !important;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
|
||||