Compare commits

..

104 Commits
5.1.0 ... 5.3.3

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

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

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

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

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

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

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

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-09 10:55:36 +01:00
David Ralph
eefc594ec1 fix: improvements to feedback modal 2021-08-05 17:25:46 +01:00
David Ralph
6cdfb9db3e chore: release 5.2 2021-08-02 13:47:06 +01:00
David Ralph
1ce6cb9419 fix: background offline interval 2021-08-01 18:20:38 +01:00
David Ralph
141da4ba36 fix: background proxy and offline mode hot reload bugs 2021-08-01 15:02:18 +01:00
David Ralph
9db7b210bc Merge branch 'main' of https://github.com/mue/mue 2021-07-29 14:18:34 +01:00
David Ralph
090ffe05f4 fix: about copyright link 2021-07-29 14:18:21 +01:00
David Ralph
66f6d0f7ed chore(translations): add messages.json files 2021-07-29 14:18:03 +01:00
dependabot[bot]
285145fdcb chore(deps): bump @material-ui/core from 5.0.0-beta.1 to 5.0.0-beta.2 (#171)
Bumps [@material-ui/core](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui) from 5.0.0-beta.1 to 5.0.0-beta.2.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.2/packages/material-ui)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 10:15:12 +01:00
David Ralph
37df7fb08c fix: revert using api to get temperature format 2021-07-23 10:55:20 +01:00
David Ralph
5a91fa24e9 chore: update api urls 2021-07-22 21:47:08 +01:00
David Ralph
bdd277d876 feat(translations): add support for description and chrome store page 2021-07-19 20:28:40 +01:00
David Ralph
d7be6e351d fix: firefox changelog date, cleanup background a bit 2021-07-19 10:20:00 +01:00
David Ralph
c9f2af6b96 fix: font size on builds, typo on welcome modal 2021-07-18 21:53:35 +01:00
David Ralph
b426041598 fix: marketplace background settings, reset button, intervals 2021-07-18 15:54:45 +01:00
David Ralph
2d7e138b2f fix: welcome background refresh, add more privacy tab content 2021-07-18 12:20:35 +01:00
David Ralph
332e6c33ac feat: welcome modal language hot reload 2021-07-18 10:59:38 +01:00
David Ralph
99993720f2 fix: welcome modal is now responsive, fix close button with other languages, codacy fixes etc 2021-07-18 10:43:25 +01:00
David Ralph
59a888c861 feat(translations): add support for background/quote interval 2021-07-17 19:45:05 +01:00
David Ralph
6cccad0e17 feat: change background/quote interval, use api to get temperature format 2021-07-17 19:38:00 +01:00
David Ralph
8ffd0e04cb build: fix vercel 2021-07-17 15:56:49 +01:00
David Ralph
3d6884fe9a feat: timezone setting, translation file updater 2021-07-17 15:51:57 +01:00
David Ralph
2a1e26d0c4 fix: add link, date picker, photo information, uninstall button 2021-07-17 13:43:18 +01:00
David Ralph
b842bd935e fix: discord link in about now works again 2021-07-15 17:41:51 +01:00
David Ralph
3a47089a00 fix: welcome start, marketplace item page is now responsive, update text etc 2021-07-15 17:39:38 +01:00
dependabot[bot]
e1f43dc343 chore(deps): bump @material-ui/icons from 5.0.0-beta.0 to 5.0.0-beta.1 (#168)
Bumps [@material-ui/icons](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui-icons) from 5.0.0-beta.0 to 5.0.0-beta.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.1/packages/material-ui-icons)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Ralph <ohlookitsderpy@protonmail.com>
2021-07-15 15:09:31 +01:00
dependabot[bot]
75b3c23a38 chore(deps-dev): bump css-loader from 5.2.7 to 6.0.0 (#169)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 5.2.7 to 6.0.0.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v5.2.7...v6.0.0)

---
updated-dependencies:
- dependency-name: css-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-15 15:09:03 +01:00
dependabot[bot]
7a271190d5 chore(deps): bump @material-ui/core from 5.0.0-beta.0 to 5.0.0-beta.1 (#167)
Bumps [@material-ui/core](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui) from 5.0.0-beta.0 to 5.0.0-beta.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.1/packages/material-ui)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-15 15:09:00 +01:00
David Ralph
c725ad4cc3 style: improve item page, update photo width/height code 2021-07-15 15:08:37 +01:00
David Ralph
c07d7ecbb0 feat: background resolution is now automatic and more accurate 2021-07-12 21:48:08 +01:00
David Ralph
99ffc82de0 refactor: update widgets and fix font change 2021-07-12 11:34:05 +01:00
David Ralph
7840b48727 fix: missing unicode-range 2021-07-11 15:11:12 +01:00
David Ralph
401baa6653 fix: noscript message css, remove woff from build 2021-07-11 15:08:35 +01:00
David Ralph
70fc204e70 perf: optimise search and autocomplete 2021-07-11 14:29:32 +01:00
David Ralph
a01d778f65 perf: new date picker and add purecomponent to more things 2021-07-10 21:59:53 +01:00
David Ralph
ff3e2caf49 build: fix vercel dev builds and mention vercel in readme 2021-07-06 19:42:58 +01:00
David Ralph
95614a383f fix: welcome svg, privacy welcome tab, marketplace optimisations, various widget fixes etc
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-07-06 19:38:20 +01:00
David Ralph
44fc24951f fix: import settings on welcome, progress bar, custom js etc 2021-07-04 12:28:28 +01:00
David Ralph
19bc802f09 feat: navbar hot reload, quick links zoom, add example images to welcome modal, fixes 2021-07-03 14:48:49 +01:00
David Ralph
ba9f2e01c8 perf: optimise svg 2021-07-02 22:04:11 +01:00
David Ralph
8ece0a7eb0 feat(translations): add translation support to welcome modal, fix clock 2021-07-02 21:59:58 +01:00
David Ralph
d91490874c fix: add that fix for minutes, not just seconds 2021-07-02 21:42:10 +01:00
David Ralph
6a3a367cda fix: close #164 2021-07-02 21:39:25 +01:00
dependabot[bot]
84e6532a80 chore(deps-dev): bump mini-css-extract-plugin from 1.6.2 to 2.0.0 (#163)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 1.6.2 to 2.0.0.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v1.6.2...v2.0.0)

---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-02 21:32:00 +01:00
David Ralph
cf36ced2a7 feat: new welcome modal, refactor many things, various bug fixes, use material-ui v5 etc
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-07-02 21:29:33 +01:00
David Ralph
c6b65f943a fix: various settings bug fixes and style changes 2021-06-30 15:25:34 +01:00
David Ralph
6aa1c6b0ea fix: weather with offline mode, maximise fixes, voice search hot reload 2021-06-30 14:11:45 +01:00
David Ralph
2dcaa5270d feat: locally store stats so users can see them (no ui yet) 2021-06-30 11:27:54 +01:00
David Ralph
1d44b2792e chore: update readme 2021-06-23 12:19:12 +01:00
David Ralph
d0577ded59 chore: revert other changes 2021-06-23 12:00:12 +01:00
David Ralph
ae5a07c756 build: revert changes 2021-06-23 11:57:36 +01:00
David Ralph
5d3418a8af build: optimise production builds, automate creating zip files 2021-06-22 21:07:56 +01:00
David Ralph
50353c9e49 build: cleanup webpack config 2021-06-22 19:45:13 +01:00
David Ralph
ea850fae56 perf: replace react-device-detect with custom code and allow edge to use voice search 2021-06-22 12:41:25 +01:00
dependabot[bot]
ba11ac171e chore(deps-dev): bump sass-loader from 7.3.1 to 12.1.0 (#162)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 7.3.1 to 12.1.0.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v7.3.1...v12.1.0)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-22 11:47:24 +01:00
David Ralph
faace9012a feat: add experimental send event feature 2021-06-22 11:46:08 +01:00
David Ralph
90ebfeedc5 chore: release 5.1.1 2021-06-22 10:19:37 +01:00
David Ralph
cfc56c6abf fix: urgent fix for background blur 2021-06-22 10:17:51 +01:00
David Ralph
d7784e7414 chore: update events 2021-06-21 23:03:47 +01:00
David Ralph
7990286e9a style: cleanup constants 2021-06-21 17:45:06 +01:00
David Ralph
f7c39eeebb feat: add opt-in umami analytics (WIP) 2021-06-21 17:42:14 +01:00
139 changed files with 3879 additions and 1439 deletions

View File

@@ -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

View File

@@ -1,4 +1,4 @@
module.exports = {
extends: 'react-app',
parser: '@babel/eslint-parser'
extends: 'react-app',
parser: '@babel/eslint-parser'
};

View 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
View File

@@ -9,3 +9,4 @@ yarn-error.log
.eslintcache
stats.json
yarn.lock
*.zip

View File

@@ -7,7 +7,7 @@
<br>
[![Microsoft Edge](https://img.shields.io/badge/dynamic/json?style=flat-square&label=microsoft%20edge&query=%24.version&url=https%3A%2F%2Fmicrosoftedge.microsoft.com%2Faddons%2Fgetproductdetailsbycrxid%2Faepnglgjfokepefimhbnibfjekidhmja)](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja) [![Firefox](https://img.shields.io/amo/v/mue?label=firefox&style=flat-square)](https://addons.mozilla.org/firefox/addon/mue) [![Chrome](https://img.shields.io/chrome-web-store/v/bngmbednanpcfochchhgbkookpiaiaid?label=chrome&style=flat-square)](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
[![Chrome Web Store Logo](assets/chrome.png)](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

View File

@@ -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']
};

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

@@ -0,0 +1,8 @@
{
"name": {
"message": "Mue"
},
"description": {
"message": "Fast, open and free-to-use new tab page for modern browsers."
}
}

View File

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

View File

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

View File

@@ -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" ]
}
}

View File

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

View File

@@ -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": [

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

View 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

View 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

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View 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');
});

View File

@@ -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() {

View File

@@ -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 || ''} />

View File

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

View File

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

View File

@@ -1,8 +1,7 @@
import React from 'react';
import { PureComponent } from 'react';
import { ErrorOutline } from '@material-ui/icons';
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
export default class ErrorBoundary extends React.PureComponent {
export default class ErrorBoundary extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -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>

View File

@@ -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>
</>
);
}
}
}

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
import React from 'react';
import { Suspense, lazy } from 'react';
import Tabs from './tabs/backend/Tabs';
import './scss/index.scss';
// Lazy load all the tabs instead of the modal itself
const Settings = React.lazy(() => import('./tabs/Settings'));
const Addons = React.lazy(() => import('./tabs/Addons'));
const Marketplace = React.lazy(() => import('./tabs/Marketplace'));
const Settings = lazy(() => import('./tabs/Settings'));
const Addons = lazy(() => import('./tabs/Addons'));
const Marketplace = lazy(() => import('./tabs/Marketplace'));
const renderLoader = () => (
<Tabs>
@@ -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}>&times;</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>
</>

View File

@@ -1,12 +1,10 @@
import React from 'react';
import { PureComponent } from 'react';
import { ArrowBack } from '@material-ui/icons';
import Modal from 'react-modal';
import Lightbox from './Lightbox';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
export default class Item extends React.PureComponent {
export default class Item extends PureComponent {
constructor() {
super();
this.state = {
@@ -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}/>

View File

@@ -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>

View File

@@ -1,4 +1,6 @@
export default function Lightbox(props) {
window.stats.postEvent('modal', 'Opened lightbox');
return (
<>
<span className='closeModal' onClick={props.modalClose}>&times;</span>

View File

@@ -1,15 +1,14 @@
import React from 'react';
import { PureComponent } from 'react';
import { LocalMall } from '@material-ui/icons';
import { toast } from 'react-toastify';
import LocalMallIcon from '@material-ui/icons/LocalMall';
import Item from '../Item';
import Items from '../Items';
import Dropdown from '../../settings/Dropdown';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { uninstall, urlParser } from '../../../../../modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Added extends React.PureComponent {
export default class Added extends PureComponent {
constructor() {
super();
this.state = {
@@ -34,7 +33,7 @@ export default class Added extends React.PureComponent {
name: data,
display_name: info.name,
author: info.author,
description: MarketplaceFunctions.urlParser(info.description.replace(/\n/g, '<br>')),
description: urlParser(info.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.version,
icon: info.screenshot_url,
@@ -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>

View File

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

View File

@@ -1,17 +1,14 @@
import React from 'react';
import WifiOffIcon from '@material-ui/icons/WifiOff';
import LocalMallIcon from '@material-ui/icons/LocalMall';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { WifiOff, LocalMall } from '@material-ui/icons';
import Item from '../Item';
import Items from '../Items';
import Dropdown from '../../settings/Dropdown';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { install, urlParser, uninstall } from '../../../../../modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Marketplace extends React.PureComponent {
export default class Marketplace extends PureComponent {
constructor() {
super();
this.state = {
@@ -59,7 +56,7 @@ export default class Marketplace extends React.PureComponent {
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
description: urlParser(info.data.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.data.version,
icon: info.data.screenshot_url,
@@ -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) {

View File

@@ -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>

View File

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

View File

@@ -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;
}

View File

@@ -16,7 +16,7 @@
.items {
display: inline-grid;
grid-template-columns: repeat(6, 1fr);
grid-template-columns: repeat(4, 1fr);
margin-top: 15px;
.item {
@@ -71,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;
}
}

View File

@@ -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');

View File

@@ -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;
}
}
}

View File

@@ -17,11 +17,11 @@ input {
-webkit-appearance: none;
vertical-align: middle;
background: none;
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border: none;
border-radius: 100%;
@@ -37,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;
}
}
}

View File

@@ -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/>
</>

View File

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

View File

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

View File

@@ -1,14 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { Radio as RadioUI, RadioGroup, FormControlLabel, FormControl, FormLabel } from '@material-ui/core';
import EventBus from '../../../../modules/helpers/eventbus';
import RadioUI from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
export default class Radio extends React.PureComponent {
export default class Radio extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -23,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';

View File

@@ -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>
</>
);

View File

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

View File

@@ -1,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/>

View File

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

View File

@@ -1,15 +1,11 @@
import React from 'react';
import { PureComponent } from 'react';
import { Email, Twitter, Chat, Instagram, Facebook } from '@material-ui/icons';
import Tooltip from '../../../../helpers/tooltip/Tooltip';
import EmailIcon from '@material-ui/icons/Email';
import TwitterIcon from '@material-ui/icons/Twitter';
import 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>
&nbsp; &nbsp;<a href='https://ko-fi.com/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
&nbsp; &nbsp;<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>
&nbsp; &nbsp;<a href={'https://ko-fi.com/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
&nbsp; &nbsp;<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>
))}

View File

@@ -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}>

View File

@@ -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} />
</>
);

View File

@@ -1,12 +1,10 @@
import React from 'react';
import { PureComponent } from 'react';
import { WifiOff } from '@material-ui/icons';
import Modal from 'react-modal';
import Lightbox from '../../marketplace/Lightbox';
import WifiOffIcon from '@material-ui/icons/WifiOff';
export default class Changelog extends React.PureComponent {
export default class Changelog extends PureComponent {
constructor() {
super();
this.state = {
@@ -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}>

View File

@@ -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>
</>
);
}

View File

@@ -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)}/>
</>
);
}

View File

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

View File

@@ -1,20 +1,9 @@
import React from 'react';
import EventBus from '../../../../../modules/helpers/eventbus';
import DragHandleIcon from '@material-ui/icons/DragIndicator';
import { PureComponent } from 'react';
import { DragIndicator } from '@material-ui/icons';
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
import { toast } from 'react-toastify';
const enabled = (setting) => {
switch (setting) {
case 'quicklinks':
return (localStorage.getItem('quicklinksenabled') === 'true');
default:
return (localStorage.getItem(setting) === 'true');
}
};
import EventBus from '../../../../../modules/helpers/eventbus';
const settings = window.language.modals.main.settings.sections;
const widget_name = {
@@ -26,8 +15,8 @@ const widget_name = {
};
const SortableItem = sortableElement(({ value }) => (
<li className='sortableitem' style={{ display: enabled(value) ? 'block' : 'none' }}>
<DragHandleIcon style={{'verticalAlign': 'middle'}} />
<li className='sortableitem'>
<DragIndicator style={{ verticalAlign: 'middle' }} />
{widget_name[value]}
</li>
));
@@ -36,7 +25,7 @@ const SortableContainer = sortableContainer(({ children }) => {
return <ul className='sortablecontainer'>{children}</ul>;
});
export default class OrderSettings extends React.PureComponent {
export default class OrderSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -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>
</>
);

View File

@@ -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'/>
</>
);
}

View File

@@ -1,4 +1,4 @@
import React from 'react';
import { PureComponent } from 'react';
import Checkbox from '../Checkbox';
import Text from '../Text';
@@ -6,7 +6,7 @@ import Switch from '../Switch';
import Slider from '../Slider';
import Dropdown from '../Dropdown';
export default class QuoteSettings extends React.PureComponent {
export default class QuoteSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -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'/>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import Dropdown from '../Dropdown';
import Checkbox from '../Checkbox';
@@ -7,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'/>
</>
);

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
import React from 'react';
import EventBus from '../../../../../../modules/helpers/eventbus';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import Checkbox from '../../Checkbox';
import Dropdown from '../../Dropdown';
@@ -11,9 +10,9 @@ import Radio from '../../Radio';
import ColourSettings from './Colour';
import { toast } from 'react-toastify';
import EventBus from '../../../../../../modules/helpers/eventbus';
export default class BackgroundSettings extends React.PureComponent {
export default class BackgroundSettings extends PureComponent {
constructor() {
super();
this.state = {
@@ -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' />
</>
);
}

View File

@@ -1,16 +1,14 @@
import React from 'react';
import { PureComponent, Fragment } from 'react';
import { ColorPicker } from 'react-color-gradient-picker';
import { toast } from 'react-toastify';
import hexToRgb from '../../../../../../modules/helpers/background/hexToRgb';
import rgbToHex from '../../../../../../modules/helpers/background/rgbToHex';
import { toast } from 'react-toastify';
import 'react-color-gradient-picker/dist/index.css';
import '../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss';
export default class ColourSettings extends React.PureComponent {
export default class ColourSettings extends PureComponent {
DefaultGradientSettings = { angle: '180', gradient: [{ colour: '#ffb032', stop: 0 }], type: 'linear' };
GradientPickerInitalState = undefined;
@@ -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>
);
});
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -1,3 +1,5 @@
import Tabs from './backend/Tabs';
import About from '../settings/sections/About';
import Language from '../settings/sections/Language';
import Search from '../settings/sections/Search';
@@ -12,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>

View File

@@ -1,29 +1,27 @@
import React from 'react';
// Navbar
import Settings from '@material-ui/icons/SettingsRounded';
import Addons from '@material-ui/icons/Widgets';
import Marketplace from '@material-ui/icons/ShoppingBasket';
// Settings
import Time from '@material-ui/icons/AccessAlarm';
import Greeting from '@material-ui/icons/EmojiPeopleOutlined';
import Quote from '@material-ui/icons/FormatQuoteOutlined';
import Background from '@material-ui/icons/PhotoOutlined';
import Search from '@material-ui/icons/Search';
import Appearance from '@material-ui/icons/FormatPaintOutlined';
import Language from '@material-ui/icons/Translate';
import Changelog from '@material-ui/icons/NewReleasesOutlined';
import About from '@material-ui/icons/InfoOutlined';
import Experimental from '@material-ui/icons/BugReportOutlined';
import Order from '@material-ui/icons/List';
import Weather from '@material-ui/icons/CloudOutlined';
import Advanced from '@material-ui/icons/SettingsOutlined';
import QuickLinks from '@material-ui/icons/Link';
// Addons
import Sideload from '@material-ui/icons/Code';
import Added from '@material-ui/icons/AddCircleOutline';
import { memo } from 'react';
import {
SettingsRounded as Settings,
Widgets as Addons,
ShoppingBasket as Marketplace,
AccessAlarm as Time,
EmojiPeopleOutlined as Greeting,
FormatQuoteOutlined as Quote,
PhotoOutlined as Background,
Search,
FormatPaintOutlined as Appearance,
Translate as Language,
NewReleasesOutlined as Changelog,
InfoOutlined as About,
BugReportOutlined as Experimental,
List as Order,
CloudOutlined as Weather,
SettingsOutlined as Advanced,
Link as QuickLinks,
AssessmentOutlined as Stats,
Code as Sideload,
AddCircleOutline as Added,
CreateNewFolderOutlined as Create
} from '@material-ui/icons';
function Tab(props) {
let className = 'tab-list-item';
@@ -60,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);

View File

@@ -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}
/>
))}

View 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>
);
}

View File

@@ -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}>&times;</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>
);
);
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -1,6 +1,4 @@
import React from 'react';
import EventBus from '../../modules/helpers/eventbus';
import { PureComponent, Fragment, Suspense, lazy } from 'react';
import Clock from './time/Clock';
import Greeting from './greeting/Greeting';
@@ -9,10 +7,13 @@ import Search from './search/Search';
import QuickLinks from './quicklinks/QuickLinks';
import Date from './time/Date';
const Weather = React.lazy(() => import('./weather/Weather'));
import EventBus from '../../modules/helpers/eventbus';
const Weather = lazy(() => import('./weather/Weather'));
const renderLoader = () => <></>;
export default class Widgets extends React.PureComponent {
export default class Widgets extends PureComponent {
online = (localStorage.getItem('offlineMode') === 'false');
constructor() {
super();
this.state = {
@@ -24,7 +25,7 @@ export default class Widgets extends React.PureComponent {
greeting: this.enabled('greeting') ? <Greeting/> : null,
quote: this.enabled('quote') ? <Quote/> : null,
date: this.enabled('date') ? <Date/> : null,
quicklinks: this.enabled('quicklinksenabled') ? <QuickLinks/> : null
quicklinks: this.enabled('quicklinksenabled') && this.online ? <QuickLinks/> : null
};
}
@@ -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>
);
}

View File

@@ -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}
</>
);

View File

@@ -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');
}
}

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { PureComponent } from 'react';
import { Fullscreen } from '@material-ui/icons';
import Tooltip from '../../helpers/tooltip/Tooltip';
import FullscreenIcon from '@material-ui/icons/Fullscreen';
export default class Maximise extends React.PureComponent {
export default class Maximise extends PureComponent {
constructor() {
super();
this.state = {
@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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;

View File

@@ -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}

View File

@@ -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>
);
}
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More