Compare commits
433 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ebfeedc5 | ||
|
|
cfc56c6abf | ||
|
|
d7784e7414 | ||
|
|
7990286e9a | ||
|
|
f7c39eeebb | ||
|
|
4449957fe6 | ||
|
|
5b00b3d859 | ||
|
|
aad568bd28 | ||
|
|
4e1347ad4e | ||
|
|
8073d8325a | ||
|
|
dc1f1fab45 | ||
|
|
dc442ef917 | ||
|
|
796896571d | ||
|
|
cad3f53140 | ||
|
|
b0da5cfa75 | ||
|
|
c2ca979971 | ||
|
|
1e5f288d42 | ||
|
|
b2c94924a2 | ||
|
|
de1797c662 | ||
|
|
db3081efc5 | ||
|
|
dcddd78164 | ||
|
|
335a6864b1 | ||
|
|
1f6dc34ee6 | ||
|
|
fbc9189cba | ||
|
|
586b6f8700 | ||
|
|
1d6013f738 | ||
|
|
c1ef3e3528 | ||
|
|
4b57229396 | ||
|
|
e721babcf7 | ||
|
|
8a816516c9 | ||
|
|
bd09542b34 | ||
|
|
84dad33548 | ||
|
|
588021cf92 | ||
|
|
6a9eccaed0 | ||
|
|
299547842a | ||
|
|
ba2dc61a5d | ||
|
|
2fd2e06bac | ||
|
|
08bfa2772d | ||
|
|
9b9c2e74f6 | ||
|
|
63e49b79cd | ||
|
|
da6ebb8c60 | ||
|
|
b1fbaa7601 | ||
|
|
7327382497 | ||
|
|
cb129238cb | ||
|
|
2ac08a7cf6 | ||
|
|
eeb0870feb | ||
|
|
260d1928d8 | ||
|
|
fa19570c36 | ||
|
|
ed76fcccc8 | ||
|
|
6b4922b825 | ||
|
|
1dc5e7375b | ||
|
|
7628e769be | ||
|
|
7470ca9e3a | ||
|
|
f49cf1f65b | ||
|
|
f21ee5c5ba | ||
|
|
5ea158c21d | ||
|
|
3aa17f0c30 | ||
|
|
e8698f2141 | ||
|
|
9b5946038a | ||
|
|
31a666fe22 | ||
|
|
b9663831fd | ||
|
|
48268111d0 | ||
|
|
af335adc23 | ||
|
|
e88cd0b765 | ||
|
|
a1b6832747 | ||
|
|
7942c367a7 | ||
|
|
f2f683201d | ||
|
|
84dbe5cb69 | ||
|
|
90927c9e8f | ||
|
|
1af688c489 | ||
|
|
1715e7d089 | ||
|
|
4b18ea74a7 | ||
|
|
6c7ab96350 | ||
|
|
b26a261644 | ||
|
|
7ce71497bb | ||
|
|
696b58f9bc | ||
|
|
e45600b4db | ||
|
|
7126a2e295 | ||
|
|
e323d4c692 | ||
|
|
b795ceb33d | ||
|
|
b26265eceb | ||
|
|
2afc9159cf | ||
|
|
8f629b1ef9 | ||
|
|
79a0ec8df9 | ||
|
|
133db009aa | ||
|
|
8f017cda32 | ||
|
|
38546ef074 | ||
|
|
57cc5de60a | ||
|
|
84571682b0 | ||
|
|
d97a3236cf | ||
|
|
1bc1729bdd | ||
|
|
ff94d66163 | ||
|
|
48979d4a75 | ||
|
|
1c4a0fa9c1 | ||
|
|
cf176bccda | ||
|
|
f3cf6bd0b3 | ||
|
|
7a03a00013 | ||
|
|
6382202dbf | ||
|
|
942644dc40 | ||
|
|
eb5bc8a843 | ||
|
|
6a147ff890 | ||
|
|
03670e8773 | ||
|
|
6a64f31940 | ||
|
|
11129f2d70 | ||
|
|
4eb76b6ed5 | ||
|
|
de5449d8b7 | ||
|
|
51e6582d23 | ||
|
|
9726ea1c89 | ||
|
|
28dec3c1f9 | ||
|
|
b28614340c | ||
|
|
4128fbae46 | ||
|
|
51d0715f0f | ||
|
|
9b43063935 | ||
|
|
98d6ef115c | ||
|
|
9113193d0f | ||
|
|
34aa72191b | ||
|
|
cb6e9ddf9c | ||
|
|
6c4cc5c373 | ||
|
|
44125d7471 | ||
|
|
b429ae2158 | ||
|
|
4a71cef3fc | ||
|
|
214d3dfe80 | ||
|
|
b1b8a5f6dc | ||
|
|
6573ba9553 | ||
|
|
de417f5fd1 | ||
|
|
bc065be590 | ||
|
|
05163532fe | ||
|
|
37e2985c46 | ||
|
|
8f21c0c7bf | ||
|
|
6e5b5dd77b | ||
|
|
81ae913be1 | ||
|
|
6c3b15a8f9 | ||
|
|
cae2c5ef71 | ||
|
|
1ac6f418bf | ||
|
|
fe4d17eff3 | ||
|
|
ea1a1fba2c | ||
|
|
b2bd871a7c | ||
|
|
6662514e5f | ||
|
|
f874f2a54c | ||
|
|
502f1683e2 | ||
|
|
4feb4f79a9 | ||
|
|
ca2ce43f2f | ||
|
|
6137139586 | ||
|
|
6ca99789c7 | ||
|
|
d596670fdb | ||
|
|
7fbadad649 | ||
|
|
0edaeb1732 | ||
|
|
3cc32e2738 | ||
|
|
baefd24c38 | ||
|
|
aff4158901 | ||
|
|
18c6514666 | ||
|
|
81787211b8 | ||
|
|
1fb0af10a8 | ||
|
|
a5b5ee1912 | ||
|
|
5c638ec355 | ||
|
|
f5b8346a26 | ||
|
|
90a4696114 | ||
|
|
b370de9ea3 | ||
|
|
fbe6e040ea | ||
|
|
cc341300b4 | ||
|
|
9d09ece8b7 | ||
|
|
061c9ef6db | ||
|
|
fbc656f978 | ||
|
|
465596d22d | ||
|
|
149bcdbe05 | ||
|
|
f4de44bbbb | ||
|
|
dad43e969f | ||
|
|
e3a482614c | ||
|
|
7c8c61472e | ||
|
|
d94813ef78 | ||
|
|
dece385ce2 | ||
|
|
9d3a72bc26 | ||
|
|
2f21b5b5c2 | ||
|
|
75fea391f0 | ||
|
|
eaa992ce16 | ||
|
|
2670c917b7 | ||
|
|
5cf9bd74f4 | ||
|
|
3d945656d6 | ||
|
|
ee9e5d2a90 | ||
|
|
670f897a80 | ||
|
|
d02da12d3f | ||
|
|
e2a18316f6 | ||
|
|
ea0c36474e | ||
|
|
8ce7be5173 | ||
|
|
426278d483 | ||
|
|
ad176a690f | ||
|
|
281713c679 | ||
|
|
5c8c6b8d07 | ||
|
|
d0934d8e6d | ||
|
|
5947aa25fd | ||
|
|
436dd884f0 | ||
|
|
6c7ca7a0a9 | ||
|
|
ceb68012d5 | ||
|
|
0575d5d565 | ||
|
|
0994bd08d8 | ||
|
|
7cf65d07c8 | ||
|
|
a5b2dd39cb | ||
|
|
4ad3c3142a | ||
|
|
880778dd7a | ||
|
|
5f9bc4a94b | ||
|
|
353fa85cba | ||
|
|
9eef6c9497 | ||
|
|
d1850f5e96 | ||
|
|
66d5f7e5b8 | ||
|
|
ea24e2653a | ||
|
|
456350c669 | ||
|
|
46142091d1 | ||
|
|
43741f4d58 | ||
|
|
98082158b7 | ||
|
|
ff69a0f487 | ||
|
|
507e7e507f | ||
|
|
3388701603 | ||
|
|
17bb8407b8 | ||
|
|
2956dabc8b | ||
|
|
214d06927e | ||
|
|
a3cf09287f | ||
|
|
af5354afb0 | ||
|
|
cf8546ebc5 | ||
|
|
f6cb133c4d | ||
|
|
b773f256a4 | ||
|
|
b4e1d00633 | ||
|
|
f3eb2c4cdb | ||
|
|
33a002003e | ||
|
|
a33151da91 | ||
|
|
5dd11aca94 | ||
|
|
29171bce5d | ||
|
|
177e4fdcdc | ||
|
|
1b40f112af | ||
|
|
2bf8e0cfbc | ||
|
|
f89a2f880d | ||
|
|
c6dd27180a | ||
|
|
d99cc7869a | ||
|
|
f6564fa758 | ||
|
|
e4395497ed | ||
|
|
b7656fa951 | ||
|
|
e092c805e8 | ||
|
|
ab7681f3d0 | ||
|
|
0c71f0ebef | ||
|
|
70756befa2 | ||
|
|
025303a01a | ||
|
|
8f74095a85 | ||
|
|
9ea6c18cd2 | ||
|
|
77a0bbe7ee | ||
|
|
66b11134c0 | ||
|
|
47817e06ac | ||
|
|
418d658658 | ||
|
|
b24739cabb | ||
|
|
4adf45094a | ||
|
|
51eaf3a90e | ||
|
|
b76cbc3bc6 | ||
|
|
f54fd5adb7 | ||
|
|
07c9e62205 | ||
|
|
b4a1a4bb19 | ||
|
|
04fb389d59 | ||
|
|
2454f1bbab | ||
|
|
8f1ebb309c | ||
|
|
67562c7297 | ||
|
|
895973e95b | ||
|
|
a6f10032c5 | ||
|
|
83b9b779b4 | ||
|
|
0f3b0ec144 | ||
|
|
9981a28638 | ||
|
|
ee0100895b | ||
|
|
aec47d9d0b | ||
|
|
4a427e23d8 | ||
|
|
7105360b31 | ||
|
|
30a3d87431 | ||
|
|
9d45d3e2b9 | ||
|
|
7ff14f5d96 | ||
|
|
5836849ab9 | ||
|
|
d42f69ae95 | ||
|
|
78350663e1 | ||
|
|
afcb7908d5 | ||
|
|
caf4a07473 | ||
|
|
195b7839d0 | ||
|
|
d79baacc1a | ||
|
|
412aa339d9 | ||
|
|
2664fcab19 | ||
|
|
e3fb5140be | ||
|
|
700fe25046 | ||
|
|
c1f34ae946 | ||
|
|
182da7cd5d | ||
|
|
ed5e8cfe76 | ||
|
|
3aafc445c2 | ||
|
|
3a39de75db | ||
|
|
e0e02544d7 | ||
|
|
8f0223dd07 | ||
|
|
c27c7074f6 | ||
|
|
049cfc35f8 | ||
|
|
801eb5f1aa | ||
|
|
98a065b934 | ||
|
|
06cc5705b5 | ||
|
|
7785706737 | ||
|
|
c83b65f27b | ||
|
|
f7946c4e19 | ||
|
|
c68228381b | ||
|
|
a9c06fd935 | ||
|
|
3ec5a2c199 | ||
|
|
0a735384df | ||
|
|
5c579ee0a2 | ||
|
|
c42e41ca15 | ||
|
|
d3f000fd69 | ||
|
|
61bc581557 | ||
|
|
bea887fff9 | ||
|
|
eef61ef9e2 | ||
|
|
17e1c43ad5 | ||
|
|
3fec08a492 | ||
|
|
e25f48cb14 | ||
|
|
e2d7a9aebd | ||
|
|
11b82fe944 | ||
|
|
88e54ad26a | ||
|
|
408f8c4502 | ||
|
|
0ba55b64bc | ||
|
|
3a5af3fe0d | ||
|
|
822f3bf260 | ||
|
|
7147dbef30 | ||
|
|
ba843b44c4 | ||
|
|
73d006d8da | ||
|
|
57cf5cb89e | ||
|
|
fc570089c2 | ||
|
|
89b4d154ad | ||
|
|
f60aae6b24 | ||
|
|
12e7ec8995 | ||
|
|
d80e9d09c8 | ||
|
|
7862c43752 | ||
|
|
7bfa7ed0a8 | ||
|
|
86b6ad6542 | ||
|
|
727e21480d | ||
|
|
82e1d7684a | ||
|
|
87dd07c45c | ||
|
|
4486050d06 | ||
|
|
a3ba90d1da | ||
|
|
0e10a4cf45 | ||
|
|
bb0db0f2de | ||
|
|
2215dd7dcd | ||
|
|
888dd7fb5f | ||
|
|
636c1892ec | ||
|
|
d954ae78f6 | ||
|
|
13757243a4 | ||
|
|
40d3281fa1 | ||
|
|
bbb49f536d | ||
|
|
6b9a0028c6 | ||
|
|
d7e4bfafda | ||
|
|
e1510e8277 | ||
|
|
f6d8ccf6e7 | ||
|
|
3b52010213 | ||
|
|
c0cced4f5d | ||
|
|
8642757bd8 | ||
|
|
be1bccd2ac | ||
|
|
5d2dc65e08 | ||
|
|
aa010be11d | ||
|
|
492b3b6715 | ||
|
|
1245d58b7e | ||
|
|
b3195d0819 | ||
|
|
aacc779162 | ||
|
|
96377c72ed | ||
|
|
3c27a5baf4 | ||
|
|
2d12875476 | ||
|
|
acc583f995 | ||
|
|
ac60d54950 | ||
|
|
7490b5926f | ||
|
|
8b8353297d | ||
|
|
d455e4b63b | ||
|
|
cc5fa542e1 | ||
|
|
4cb5d1eaae | ||
|
|
0f279b8087 | ||
|
|
1647d3c520 | ||
|
|
3da376e68a | ||
|
|
1f0bc275a0 | ||
|
|
60bf6315a4 | ||
|
|
4b25a1c955 | ||
|
|
16419ca775 | ||
|
|
f390c6ca92 | ||
|
|
d85fc79734 | ||
|
|
b45129f3d0 | ||
|
|
392f14c89d | ||
|
|
1c49306a46 | ||
|
|
30c50968a2 | ||
|
|
3b5ac499cc | ||
|
|
754fdbe284 | ||
|
|
1ce238722a | ||
|
|
0b4383d263 | ||
|
|
3b957142bf | ||
|
|
74b02f6bcf | ||
|
|
2f8d5eca21 | ||
|
|
78d3384d7e | ||
|
|
be74c98963 | ||
|
|
c84e727b22 | ||
|
|
38c84d1e82 | ||
|
|
a0d3b084a3 | ||
|
|
086d463277 | ||
|
|
610bbda43d | ||
|
|
5dc8bbf963 | ||
|
|
93edb489f7 | ||
|
|
c12091c5f0 | ||
|
|
eeaf678485 | ||
|
|
5e71a477de | ||
|
|
fef78cfeed | ||
|
|
df2a873ac6 | ||
|
|
4dd3e550a8 | ||
|
|
a865cb87ef | ||
|
|
52cc9cb579 | ||
|
|
aa16b7cffa | ||
|
|
917f99ff13 | ||
|
|
b70bd85370 | ||
|
|
edd8872403 | ||
|
|
d0a2d6c24c | ||
|
|
0a7ee1c52e | ||
|
|
205a56f086 | ||
|
|
26f5e0b0e5 | ||
|
|
ba66ed6279 | ||
|
|
d9021b0f57 | ||
|
|
44281c2449 | ||
|
|
38590f8278 | ||
|
|
201e3382f4 | ||
|
|
840917d123 | ||
|
|
7d59b18058 | ||
|
|
443c53e294 | ||
|
|
32fe3a664c | ||
|
|
3c5c7e71f1 | ||
|
|
8aaf3bb6e2 | ||
|
|
bf1ded0bd5 | ||
|
|
ca84948751 | ||
|
|
70a03b49da | ||
|
|
c9a89e5dc3 | ||
|
|
693aa1aa93 | ||
|
|
2bcc0beec7 | ||
|
|
caaed9603d | ||
|
|
d2ce84cac4 | ||
|
|
1cdb469d08 | ||
|
|
cdddffbe79 | ||
|
|
4ba7022b54 | ||
|
|
4b27532ac6 |
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
4
.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: 'react-app',
|
||||
parser: '@babel/eslint-parser'
|
||||
};
|
||||
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
||||
github: ohlookitsderpy
|
||||
patreon: ohlookitsderpy
|
||||
ko_fi: ohlookitsderpy
|
||||
14
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for Mue
|
||||
title: "[Feature Request]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Description**
|
||||
A clear and concise description of what you want in this new feature.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
17
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
- dependency-name: fontsource-lexend-deca
|
||||
versions:
|
||||
- ">= 4.a, < 5"
|
||||
- dependency-name: fontsource-roboto
|
||||
versions:
|
||||
- ">= 4.a, < 5"
|
||||
- dependency-name: react-modal
|
||||
versions:
|
||||
- 3.13.1
|
||||
4
.gitignore
vendored
@@ -6,4 +6,6 @@ build/
|
||||
# Files
|
||||
package-lock.json
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
.eslintcache
|
||||
stats.json
|
||||
yarn.lock
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at github@muetab.xyz. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018-2020 Mue Tab
|
||||
Copyright (c) 2018-2021 The Mue Authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
156
README.md
@@ -1,53 +1,52 @@
|
||||
<img src="https://raw.githubusercontent.com/mue/branding/master/logo/logo_round.png" align="left" width="180px" height="180px"/>
|
||||
<img src="https://raw.githubusercontent.com/mue/branding/main/logo/logo_round.png" align="left" width="180px" height="180px"/>
|
||||
<img align="left" width="0" height="192px" hspace="10"/>
|
||||
|
||||
> <a href="https://muetab.xyz/">Mue</a>
|
||||
> <a href="https://muetab.com/">Mue</a>
|
||||
|
||||
[](/LICENSE) [](https://discord.gg/zv8C9F8) []()
|
||||
[](/LICENSE) [](https://discord.gg/zv8C9F8) []()
|
||||
<br>
|
||||
[](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja) [](https://addons.mozilla.org/firefox/addon/mue) [](https://chrome.google.com/webstore/detail/mue/bngmbednanpcfochchhgbkookpiaiaid)
|
||||
|
||||
Mue is a fast, open and free-to-use browser extension that gives a new, fresh and customizable tab page to most 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>
|
||||
|
||||
## Table of contents
|
||||
* [Screenshot](#screenshot)
|
||||
* [Features](#features)
|
||||
* [Planned Features](#planned-features)
|
||||
* [Planned Features](#planned-features)
|
||||
* [Installation](#installation)
|
||||
* [Chrome](#chrome)
|
||||
* [Firefox](#firefox)
|
||||
* [Chromium](#edge-chromium)
|
||||
* [Opera/Other](#operaother)
|
||||
* [Contributing](#development)
|
||||
* [Requirements](#requirements)
|
||||
* [Starting](#starting)
|
||||
* [Building](#building)
|
||||
* [Credits](#credits)
|
||||
* [Maintainers](#maintainers)
|
||||
* [Contributors](#contributors)
|
||||
* [Translators](#translators)
|
||||
* [Edge Chromium](#edge-chromium)
|
||||
* [Naver](#naver)
|
||||
* [Other](#other)
|
||||
|
||||
## Screenshot
|
||||

|
||||
* [Contributing](#development)
|
||||
* [Translations](#translations)
|
||||
* [Credits](#credits)
|
||||
* [Developers](#developers)
|
||||
* [Translators](#translators)
|
||||
* [Contributors](#contributors)
|
||||
* [Resources](#resources)
|
||||
## Screenshots
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
* Fast and free
|
||||
* Supports multiple browsers
|
||||
* Actively developed and open source
|
||||
* Automatically updating API (no tracking) with new photos, quotes and offline mode
|
||||
* Search bar
|
||||
* Widgets such as searchbar, weather, quick links, clock, date, quote, greeting
|
||||
* Settings - enable/disable various features and customise parts of Mue
|
||||
* Update modal, copy button and more!
|
||||
* Marketplace - download custom photo packs, quote packs, preset settings and themes made by the community!
|
||||
* Navbar with copy button, favourite background, notes feature etc
|
||||
* Marketplace - download custom photo packs made by the community
|
||||
|
||||
### Planned Features
|
||||
Please see our [roadmap](https://github.com/mue/mue/projects/2)
|
||||
Please see our [roadmap](https://github.com/mue/mue/projects)
|
||||
|
||||
## Installation
|
||||
*A demo of the tab can be found [here](https://demo.muetab.xyz)*
|
||||
*A demo of the tab can be found [here](https://demo.muetab.com)*
|
||||
### Chrome
|
||||
[](https://chrome.google.com/webstore/detail/mue/bngmbednanpcfochchhgbkookpiaiaid)
|
||||
<br>
|
||||
@@ -61,90 +60,37 @@ Please see our [roadmap](https://github.com/mue/mue/projects/2)
|
||||
### Edge (Chromium)
|
||||
[Microsoft Edge Addons](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja)
|
||||
|
||||
### Opera/Other
|
||||
[GitHub Releases](https://github.com/mue/mue/releases)
|
||||
|
||||
### Development
|
||||
#### Requirements
|
||||
<ol>
|
||||
<li><a href='https://git-scm.com'>Git</a></li>
|
||||
<li><a href='https://nodejs.org'>Node.JS</a></li>
|
||||
<li>A suitable browser</li>
|
||||
</ol>
|
||||
<h5>Starting</h5>
|
||||
<ol>
|
||||
<li> Clone the repository using <code>git clone https://github.com/mue/mue.git</code>
|
||||
<li> Run <code>yarn</code> or <code>npm i</code> to install all needed dependencies
|
||||
<li> Run <code>yarn start</code> or <code>npm start</code> to start testing
|
||||
<li> Code your heart out! (See the sections below for how to build the extension)
|
||||
</ol>
|
||||
<h2>Building</h2>
|
||||
<details>
|
||||
<summary><b>Chrome/Edge (Chromium)</b> (Click to expand)</summary>
|
||||
<ol>
|
||||
<li> <code>yarn run build</code> or <code>npm run build</code>
|
||||
<li> <code>yarn run chrome</code> or <code>npm run chrome</code>
|
||||
<li> Visit <code>chrome://extensions</code> in Chrome
|
||||
<li> Click <b>Load unpacked</b> (Make sure <b>Developer Mode</b> is on)
|
||||
<li> Go to the directory containing the built copy of Mue and click <b>ok</b>
|
||||
<li> Enjoy your new tab!
|
||||
</details>
|
||||
<details>
|
||||
<summary><b>Opera</b> (Click to expand)</summary>
|
||||
<ol>
|
||||
<li> <code>yarn run build</code> or <code>npm run build</code>
|
||||
<li> <code>yarn run opera</code> or <code>npm run opera</code>
|
||||
<li> Visit <code>about://extensions</code> in Opera
|
||||
<li> Click <b>Load unpacked extension...</b> (Make sure <b>Developer Mode</b> is on)
|
||||
<li> Go to the directory containing Mue and click <b>ok</b>
|
||||
<li> Enjoy your new tab!
|
||||
</details>
|
||||
<details>
|
||||
<summary><b>Firefox</b> (Click to expand)</summary>
|
||||
<ol>
|
||||
<li> <code>yarn run build</code> or <code>npm run build</code>
|
||||
<li> <code>yarn run firefox</code> or <code>npm run firefox</code>
|
||||
<li> Visit <code>about:debugging#addons</code> in Firefox
|
||||
<li> Click <b>Load Temporary Add-on</b>
|
||||
<li> Go to the directory containing Mue and click on the <b>manifest.json</b>
|
||||
<li> Enjoy your new tab!
|
||||
</ol>
|
||||
</details>
|
||||
<details>
|
||||
<summary><b>Other</b> (Click to expand)</summary>
|
||||
<i>Note: To get the full new tab experience, set your browser to open the <code>index.html</code> on startup and tab open!</i>
|
||||
<ol>
|
||||
<li> <code>yarn run build</code> or <code>npm run build</code>
|
||||
<li> Open the <code>index.html</code> in your browser
|
||||
<li> Enjoy your new tab!
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
## Credits
|
||||
### Maintainers
|
||||
[David Ralph (ohlookitsderpy)](https://github.com/ohlookitsderpy) - Founder, Lead development, Photographer <br>
|
||||
[Alex Sparkes](https://github.com/alexsparkes) - Name, Lead design, Photographer <br>
|
||||
|
||||
### Contributors
|
||||
[Wessel Tip](https://github.com/Wessel) - Development <br>
|
||||
[Isaac (Eartharoid)](https://github.com/eartharoid) - QA, Development, Photographer <br>
|
||||
|
||||
### Translators
|
||||
English - [David Ralph (ohlookitsderpy)](https://github.com/ohlookitsderpy) & [Alex Sparkes](https://github.com/alexsparkes)
|
||||
|
||||
Dutch - [Wessel Tip](https://github.com/Wessel)
|
||||
|
||||
French - [Alex Sparkes](https://github.com/alexsparkes)
|
||||
|
||||
Norwegian - [Anders](https://github.com/FuryingFox)
|
||||
|
||||
Russian - [MrZillaGold](https://github.com/MrZillaGold)
|
||||
### Naver
|
||||
[Whale Store](https://store.whale.naver.com/detail/ecllekeilcmicbfkkiknfdddbogibbnc)
|
||||
|
||||
### Other
|
||||
[Pexels](https://pexels.com) - Stock photos used for offline mode
|
||||
Please note that we have dropped support for Opera as of Mue 5.0
|
||||
|
||||
[Opera Forum](https://forums.opera.com/topic/25046/how-to-disable-completely-the-speed-dial/14) - Portions of code to add Opera support
|
||||
[GitHub Releases](https://github.com/mue/mue/releases)
|
||||
|
||||
[Google Fonts](https://fonts.google.com) - Lexend Deca and Roboto fonts
|
||||
## Development
|
||||
This section has moved to the [documentation](https://docs.muetab.com/development#mue-tab).
|
||||
|
||||
And many thanks to [Highholding](https://discord.bio/p/highholding), [Noa Shapira](#), [Roee Lupo](https://github.com/RoeeLupo), [Jeroen](#), [Glasvegas](https://twitter.com/_glasvegas), [Anders](https://github.com/FuryingFox), [Oded Shapira](https://twitter.com/dondishdev), Jacob Tyrrell and [Nikka Lai](#) for letting us use their wonderful photographs
|
||||
### Translations
|
||||
Please see the [documentation](https://docs.muetab.com/translations).
|
||||
|
||||
## Credits
|
||||
### Developers
|
||||
[David Ralph](https://github.com/davidjcralph) - Lead development, photographer <br/>
|
||||
[Alex Sparkes](https://github.com/alexsparkes) - Name, lead design, photographer <br/>
|
||||
[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/>
|
||||
### 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
|
||||
|
||||
|
Before Width: | Height: | Size: 262 KiB |
BIN
assets/screenshot.webp
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
assets/screenshot2.webp
Normal file
|
After Width: | Height: | Size: 68 KiB |
6
babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env', ['@babel/preset-react', {
|
||||
'runtime': 'automatic'
|
||||
}]],
|
||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
/* eslint-disable no-undef */
|
||||
// Original code sourced from https://forums.opera.com/topic/25046/how-to-disable-completely-the-speed-dial/14
|
||||
|
||||
chrome.tabs.onCreated.addListener((tab) => {
|
||||
if (tab.status === 'complete' && tab.url === 'chrome://startpage/') chrome.tabs.update(tab.id, {
|
||||
url: chrome.extension.getURL('index.html')
|
||||
});
|
||||
});
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === 'complete' && tab.url === 'chrome://startpage/') chrome.tabs.update(tabId, {
|
||||
url: chrome.extension.getURL('index.html')
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,20 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"offline_enabled": true,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for most modern browsers.",
|
||||
"version": "4.1.0",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/16x16.png",
|
||||
"48": "icons/48x48.png",
|
||||
"128": "icons/128x128.png"
|
||||
}
|
||||
}
|
||||
"manifest_version": 2,
|
||||
"offline_enabled": true,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"version": "5.1.1",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/16x16.png",
|
||||
"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'"
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for most modern browsers.",
|
||||
"version": "4.1.0",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"chrome_settings_overrides": {
|
||||
"homepage": "index.html"
|
||||
}
|
||||
"manifest_version": 2,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"version": "5.1.1",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/16x16.png",
|
||||
"48": "icons/48x48.png",
|
||||
"128": "icons/128x128.png"
|
||||
},
|
||||
"chrome_settings_overrides": {
|
||||
"homepage": "index.html"
|
||||
},
|
||||
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'"
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for most modern browsers.",
|
||||
"version": "4.1.0",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"background": {
|
||||
"scripts": [
|
||||
"./background-opera.js"
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
"tabs"
|
||||
]
|
||||
}
|
||||
124
package.json
@@ -1,54 +1,70 @@
|
||||
{
|
||||
"name": "mue",
|
||||
"private": true,
|
||||
"author": "David \"ohlookitsderpy\" Ralph <d.ralph@muetab.xyz> (https://derpyenterprises.org)",
|
||||
"maintainers": [
|
||||
"David \"ohlookitsderpy\" Ralph <d.ralph@muetab.xyz> (https://derpyenterprises.org)",
|
||||
"Alex \"TurboMarshmello\" Sparkes <a.sparkes@muetab.xyz> (https://github.com/alexsparkes)"
|
||||
],
|
||||
"description": "Fast, open and free-to-use new tab page for most modern browsers.",
|
||||
"repository": {
|
||||
"url": "github:mue/mue"
|
||||
},
|
||||
"homepage": "https://muetab.xyz",
|
||||
"bugs": "https://github.com/mue/mue/issues/new?assignees=&labels=bug&template=bug-report.md&title=%5BBUG%5D",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "4.1.0",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@muetab/quotes": "^1.0.0",
|
||||
"detect-browser-language": "0.0.2",
|
||||
"react": "^16.13.1",
|
||||
"react-clock": "^2.4.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-modal": "^3.11.2",
|
||||
"react-toastify": "^6.0.8",
|
||||
"supports-webp": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-scripts": "3.4.3",
|
||||
"node-sass": "^4.14.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json",
|
||||
"firefox": "cp manifest/firefox.json build/manifest.json",
|
||||
"opera": "cp manifest/opera.json build/manifest.json && cp manifest/background-opera.js build/"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version"
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "mue",
|
||||
"private": true,
|
||||
"author": "The Mue Authors (https://github.com/mue/mue/graphs/contributors)",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"repository": {
|
||||
"url": "github:mue/mue"
|
||||
},
|
||||
"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.1",
|
||||
"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",
|
||||
"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",
|
||||
"react-toastify": "7.0.4",
|
||||
"weather-icons-react": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/eslint-parser": "^7.14.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.13.15",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.5",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"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",
|
||||
"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",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"webpack": "^5.39.1",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"build": "webpack --mode=production",
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json",
|
||||
"firefox": "cp manifest/firefox.json build/manifest.json"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/icons/logo_horizontal.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="200" cy="200" r="200" fill="url(#paint0_linear)"/>
|
||||
<path d="M167 265.062L294.125 137.938L311 154.812L167 298.812L100.062 231.875L116.938 215L167 265.062Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="200" y1="0" x2="200" y2="400" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF5C25"/>
|
||||
<stop offset="1" stop-color="#FF456E"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 490 B |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 36 KiB |
@@ -9,8 +9,30 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to use Mue.</noscript>
|
||||
<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;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
*, a {
|
||||
color: white;
|
||||
background: #2f3640;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<h1>Error</h1>
|
||||
<h2>You need to enable JavaScript to use Mue</h2>
|
||||
<p>Having trouble? Contact us: <a href='https://muetab.com/contact'>https://muetab.com/contact</a></p>
|
||||
</noscript>
|
||||
<div id='root'></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
|
Before Width: | Height: | Size: 128 KiB |
BIN
public/offline-images/1.webp
Normal file
|
After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 200 KiB |
BIN
public/offline-images/10.webp
Normal file
|
After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 146 KiB |
BIN
public/offline-images/11.webp
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 230 KiB |
BIN
public/offline-images/12.webp
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 737 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 94 KiB |
BIN
public/offline-images/2.webp
Normal file
|
After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 148 KiB |
BIN
public/offline-images/3.webp
Normal file
|
After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 290 KiB |
BIN
public/offline-images/4.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 324 KiB |
BIN
public/offline-images/5.webp
Normal file
|
After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 264 KiB |
BIN
public/offline-images/6.webp
Normal file
|
After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 495 KiB |
BIN
public/offline-images/7.webp
Normal file
|
After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 472 KiB |
BIN
public/offline-images/8.webp
Normal file
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 280 KiB |
BIN
public/offline-images/9.webp
Normal file
|
After Width: | Height: | Size: 118 KiB |
145
src/App.jsx
@@ -1,126 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import Background from './components/widgets/Background';
|
||||
import Clock from './components/widgets/Clock';
|
||||
import Greeting from './components/widgets/Greeting';
|
||||
import Quote from './components/widgets/Quote';
|
||||
import Search from './components/widgets/Search';
|
||||
import Maximise from './components/widgets/Maximise';
|
||||
import Favourite from './components/widgets/Favourite';
|
||||
import Background from './components/widgets/background/Background';
|
||||
import Widgets from './components/widgets/Widgets';
|
||||
import Modals from './components/modals/Modals';
|
||||
|
||||
import Navbar from './components/Navbar';
|
||||
import EventBus from './modules/helpers/eventbus';
|
||||
import SettingsFunctions from './modules/helpers/settings';
|
||||
|
||||
import SettingsFunctions from './modules/settingsFunctions';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
import { merge } from './modules/merge';
|
||||
import RoomIcon from '@material-ui/icons/Room';
|
||||
|
||||
// Modals are lazy loaded as a user won't use them every time they open a tab
|
||||
const Settings = React.lazy(() => import('./components/modals/Settings'));
|
||||
const Update = React.lazy(() => import('./components/modals/Update'));
|
||||
const Marketplace = React.lazy(() => import('./components/modals/Marketplace'));
|
||||
const Addons = React.lazy(() => import('./components/modals/Addons'));
|
||||
//const Welcome = React.lazy(() => import('./components/modals/Welcome'));
|
||||
const renderLoader = () => <div></div>;
|
||||
|
||||
export default class App extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
settingsModal: false,
|
||||
updateModal: false,
|
||||
marketplaceModal: false,
|
||||
addonsModal: false,
|
||||
quickAccessmodal: false,
|
||||
welcomeModal: false
|
||||
};
|
||||
}
|
||||
|
||||
// Render all the components
|
||||
render() {
|
||||
if (!localStorage.getItem('firstRun')) SettingsFunctions.setDefaultSettings();
|
||||
|
||||
let modalClassList = 'Modal';
|
||||
if (localStorage.getItem('darkTheme') === 'true') modalClassList = 'Modal dark';
|
||||
|
||||
let overlayClassList = 'Overlay';
|
||||
if (localStorage.getItem('animations') === 'true') overlayClassList = 'Overlay modal-animation';
|
||||
|
||||
let language = require(`./translations/${localStorage.getItem('language') || 'en'}.json`);
|
||||
const en = require('./translations/en.json');
|
||||
language = merge(en, language);
|
||||
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
const style = document.createElement('link');
|
||||
style.href = theme;
|
||||
style.rel = 'stylesheet';
|
||||
document.head.appendChild(style);
|
||||
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('backgroundFilterAmount')) {
|
||||
SettingsFunctions.moveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
SettingsFunctions.loadSettings();
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'other') {
|
||||
SettingsFunctions.loadSettings(true);
|
||||
}
|
||||
});
|
||||
|
||||
window.analytics.tabLoad();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Background/>
|
||||
<ToastContainer className='toast' position='bottom-right' autoClose={2500} hideProgressBar={false} newestOnTop={true} closeOnClick rtl={false} pauseOnFocusLoss />
|
||||
<>
|
||||
{(localStorage.getItem('background') === 'true') ? <Background/> : null}
|
||||
<ToastContainer position='bottom-right' autoClose={localStorage.getItem('toastDisplayTime') || 2500} newestOnTop={true} closeOnClick pauseOnFocusLoss/>
|
||||
<div id='center'>
|
||||
<Search language={language.search} />
|
||||
<Navbar settingsModalOpen={() => this.setState({ settingsModal: true })} updateModalOpen={() => this.setState({ updateModal: true })} />
|
||||
<Greeting language={language.greeting} />
|
||||
<Clock/>
|
||||
<Quote language={language.toasts}/>
|
||||
<div className='credits' id='credits'>
|
||||
<h1 id='photographer'>{language.credit}</h1>
|
||||
<span id='credit' style={{'display': 'none'}}></span>
|
||||
<div id='backgroundCredits' className='tooltip'>
|
||||
<RoomIcon className='locationicon'/>
|
||||
<span className='tooltiptext' id='location'/>
|
||||
</div>
|
||||
</div>
|
||||
<Maximise/>
|
||||
<Favourite/>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Modal id={'modal'} onRequestClose={() => this.setState({ settingsModal: false })} isOpen={this.state.settingsModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
|
||||
<Settings
|
||||
language={language.settings}
|
||||
modalLanguage={language.modals}
|
||||
modalClose={() => this.setState({ settingsModal: false })}
|
||||
setDefaultSettings={() => SettingsFunctions.setDefaultSettings()}
|
||||
openMarketplace={() => this.setState({ marketplaceModal: true, settingsModal: false })}
|
||||
openAddons={() => this.setState({ settingsModal: false, addonsModal: true })}
|
||||
toastLanguage={language.toasts} />
|
||||
</Modal>
|
||||
<Modal onRequestClose={() => this.setState({ updateModal: false })} isOpen={this.state.updateModal} className={modalClassList} overlayClassName={overlayClassList} ariaHideApp={false}>
|
||||
<Update
|
||||
language={language.update}
|
||||
modalClose={() => this.setState({ updateModal: false })} />
|
||||
</Modal>
|
||||
<Modal onRequestClose={() => this.setState({ marketplaceModal: false })} isOpen={this.state.marketplaceModal} className={modalClassList} overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Marketplace
|
||||
language={language.marketplace}
|
||||
modalLanguage={language.modals}
|
||||
modalClose={() => this.setState({ marketplaceModal: false })}
|
||||
openSettings={() => this.setState({ marketplaceModal: false, settingsModal: true })}
|
||||
openAddons={() => this.setState({ marketplaceModal: false, addonsModal: true })}
|
||||
toastLanguage={language.toasts} />
|
||||
</Modal>
|
||||
<Modal onRequestClose={() => this.setState({ addonsModal: false })} isOpen={this.state.addonsModal} className={modalClassList} overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Addons
|
||||
language={language.addons}
|
||||
marketplaceLanguage={language.marketplace}
|
||||
modalLanguage={language.modals}
|
||||
modalClose={() => this.setState({ addonsModal: false })}
|
||||
openSettings={() => this.setState({ addonsModal: false, settingsModal: true })}
|
||||
openMarketplace={() => this.setState({ addonsModal: false, marketplaceModal: true })}
|
||||
toastLanguage={language.toasts} />
|
||||
</Modal>
|
||||
{/* <Modal onRequestClose={() => this.setState({ welcomeModal: false })} isOpen={this.state.welcomeModal} className={modalClassList} overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Welcome modalClose={() => this.setState({ welcomeModal: false })} />
|
||||
</Modal> */ }
|
||||
</React.Suspense>
|
||||
<Widgets/>
|
||||
<Modals/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import Gear from '@material-ui/icons/Settings';
|
||||
import NewReleases from '@material-ui/icons/NewReleases';
|
||||
|
||||
export default class Navbar extends React.PureComponent {
|
||||
render() {
|
||||
let refreshHTML = <div className='navbar2'><RefreshIcon className='refreshicon' onClick={() => window.location.reload()} /></div>;
|
||||
const refresh = localStorage.getItem('refresh');
|
||||
if (refresh === 'false') refreshHTML = '';
|
||||
|
||||
return (
|
||||
<div className='navbar-container'>
|
||||
<div className='navbar1'>
|
||||
<Gear className='settings-icon' onClick={this.props.settingsModalOpen} />
|
||||
</div>
|
||||
{refreshHTML}
|
||||
<div className={refresh === 'false' ? 'navbar2' : 'navbar3'}>
|
||||
<NewReleases className='refreshicon' onClick={this.props.updateModalOpen} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
75
src/components/helpers/autocomplete/Autocomplete.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
|
||||
import './autocomplete.scss';
|
||||
|
||||
export default class Autocomplete extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filtered: [],
|
||||
showList: false,
|
||||
input: ''
|
||||
};
|
||||
this.enabled = (localStorage.getItem('autocomplete') === 'true');
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
if (this.enabled === false) {
|
||||
return this.setState({
|
||||
input: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
filtered: this.props.suggestions.filter((suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1),
|
||||
showList: true,
|
||||
input: e.target.value
|
||||
});
|
||||
|
||||
this.props.onChange(e.target.value);
|
||||
};
|
||||
|
||||
onClick = (e) => {
|
||||
this.setState({
|
||||
filtered: [],
|
||||
showList: false,
|
||||
input: e.target.innerText
|
||||
});
|
||||
|
||||
this.props.onClick(e);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type='text'
|
||||
onChange={this.onChange}
|
||||
value={this.state.input}
|
||||
name={this.props.name || 'name'}
|
||||
placeholder={this.props.placeholder || ''}
|
||||
autoComplete='off'
|
||||
id={this.props.id || ''} />
|
||||
{autocomplete}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
50
src/components/helpers/autocomplete/autocomplete.scss
Normal file
@@ -0,0 +1,50 @@
|
||||
.suggestions {
|
||||
text-align: left;
|
||||
font-size: calc(5px + 1.2vmin);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: black;
|
||||
border-top-width: 0;
|
||||
list-style: none;
|
||||
margin-top: 40px;
|
||||
max-height: 143px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 40px;
|
||||
border-radius: 24px;
|
||||
width: 430px;
|
||||
opacity: 0;
|
||||
|
||||
li {
|
||||
padding: 0.5rem;
|
||||
padding-left: 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
input[type=text]:focus+.suggestions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1400px) {
|
||||
.suggestions {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .suggestions {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
|
||||
li {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/components/helpers/tooltip/Tooltip.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import './tooltip.scss';
|
||||
|
||||
export default function Tooltip(props) {
|
||||
return (
|
||||
<div className='tooltip'>
|
||||
{props.children}
|
||||
<span className='tooltipTitle'>{props.title}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
37
src/components/helpers/tooltip/tooltip.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
// todo: possibly add tooltip placement option
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.tooltipTitle {
|
||||
width: 60px;
|
||||
background-color: rgba(255, 255, 255, 0.89);
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
font-size: 0.6rem;
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -30px;
|
||||
visibility: hidden;
|
||||
cursor: initial;
|
||||
user-select: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tooltipTitle {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tooltipTitle {
|
||||
background-color: rgba(0, 0, 0, 0.79);
|
||||
color: #ffffff;
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
import React from 'react';
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
import { toast } from 'react-toastify';
|
||||
import Item from './marketplace/Item';
|
||||
import MarketplaceFunctions from '../../modules/marketplaceFunctions';
|
||||
|
||||
export default class Addons extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
installed: [],
|
||||
current_data: {
|
||||
type: '',
|
||||
name: '',
|
||||
content: {}
|
||||
},
|
||||
item_data: {
|
||||
name: 'Name',
|
||||
author: 'Author',
|
||||
description: 'Description',
|
||||
updated: '???',
|
||||
version: '1.0.0',
|
||||
icon: ''
|
||||
},
|
||||
button: <button className='removeFromMue' onClick={() => this.uninstall()}>{this.props.marketplaceLanguage.product.buttons.remove}</button>
|
||||
}
|
||||
}
|
||||
|
||||
toggle(type, type2, data) {
|
||||
if (type === 'item') {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
let info = installed.find(i => i.name === data).content;
|
||||
this.setState({
|
||||
current_data: { type: type2, name: data, content: info },
|
||||
item_data: {
|
||||
name: info.data.name,
|
||||
author: info.data.author,
|
||||
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
|
||||
updated: info.updated,
|
||||
version: info.data.version,
|
||||
icon: info.data.screenshot_url
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('item').style.display = 'block';
|
||||
document.getElementById('marketplace').style.display = 'none';
|
||||
} else {
|
||||
this.setState({
|
||||
button: <button className='removeFromMue' onClick={() => this.uninstall()}>{this.props.marketplaceLanguage.product.buttons.remove}</button>
|
||||
});
|
||||
document.getElementById('marketplace').style.display = 'block';
|
||||
document.getElementById('item').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
let type, data = this.state.current_data.type;
|
||||
if (data === undefined) data = this.state.current_data.content.data.type;
|
||||
switch (data) {
|
||||
case 'photos':
|
||||
type = 'photo_packs';
|
||||
break;
|
||||
case 'quotes':
|
||||
type = 'quote_packs';
|
||||
break;
|
||||
default:
|
||||
type = this.state.current_data.type;
|
||||
break;
|
||||
}
|
||||
MarketplaceFunctions.uninstall(this.state.current_data.name, type);
|
||||
toast(this.props.toastLanguage.removed);
|
||||
this.setState({
|
||||
button: '',
|
||||
installed: JSON.parse(localStorage.getItem('installed'))
|
||||
});
|
||||
}
|
||||
|
||||
install(input) {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
let button;
|
||||
|
||||
const installStuff = () => {
|
||||
installed.push({
|
||||
content: {
|
||||
updated: 'Unpublished',
|
||||
data: input
|
||||
}
|
||||
});
|
||||
localStorage.setItem('installed', JSON.stringify(installed));
|
||||
toast(this.props.toastLanguage.installed);
|
||||
button = <button className='removeFromMue' onClick={() => this.uninstall()}>{this.props.marketplaceLanguage.product.buttons.remove}</button>;
|
||||
this.setState({
|
||||
button: button,
|
||||
installed: JSON.parse(localStorage.getItem('installed'))
|
||||
});
|
||||
}
|
||||
|
||||
switch (input.type) {
|
||||
case 'settings':
|
||||
localStorage.removeItem('backup_settings');
|
||||
let oldSettings = [];
|
||||
for (const key of Object.keys(localStorage)) oldSettings.push({name: key, value: localStorage.getItem(key)});
|
||||
localStorage.setItem('backup_settings', JSON.stringify(oldSettings));
|
||||
input.settings.forEach(element => localStorage.setItem(element.name, element.value));
|
||||
installStuff();
|
||||
break;
|
||||
case 'photos':
|
||||
localStorage.setItem('photo_packs', JSON.stringify(input.photos));
|
||||
installStuff();
|
||||
break;
|
||||
case 'theme':
|
||||
localStorage.setItem('theme', input.theme);
|
||||
installStuff();
|
||||
break;
|
||||
case 'quote_packs':
|
||||
if (input.quote_api) localStorage.setItem('quote_api', JSON.stringify(input.quote_api));
|
||||
localStorage.setItem('quote_packs', JSON.stringify(input.quotes));
|
||||
installStuff();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.getElementById('file-input').onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file.type !== 'application/json') return console.error(`expected json, got ${file.type}`);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
|
||||
reader.onload = (readerEvent) => {
|
||||
const content = JSON.parse(readerEvent.target.result);
|
||||
this.install(content);
|
||||
};
|
||||
};
|
||||
|
||||
document.getElementById('backgroundImage').classList.toggle('backgroundEffects');
|
||||
document.getElementById('center').classList.toggle('backgroundEffects');
|
||||
this.setState({ installed: JSON.parse(localStorage.getItem('installed')) });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.getElementById('backgroundImage').classList.toggle('backgroundEffects');
|
||||
document.getElementById('center').classList.toggle('backgroundEffects');
|
||||
}
|
||||
|
||||
render() {
|
||||
let content = <div className='items'>
|
||||
{this.state.installed.map((item) =>
|
||||
<div className='item' onClick={() => this.toggle('item', item.type, item.name)}>
|
||||
<img alt='icon' src={item.content.data.icon_url} />
|
||||
<div className='details'>
|
||||
<h4>{item.content.data.name}</h4>
|
||||
<p>{item.content.data.author}</p>
|
||||
</div>
|
||||
</div>)}
|
||||
</div>;
|
||||
|
||||
if (this.state.installed.length === 0) {
|
||||
content = <div className='items'>
|
||||
<div className='emptyMessage'>
|
||||
<LocalMallIcon />
|
||||
<h1>{this.props.language.empty.title}</h1>
|
||||
<p className='description'>{this.props.language.empty.description}</p>
|
||||
<button className='goToMarket' onClick={this.props.openMarketplace}>{this.props.language.empty.button}</button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div className='content'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1>{this.props.modalLanguage.title}</h1>
|
||||
<div className='tab'>
|
||||
<button className='tablinks' onClick={this.props.openMarketplace}>{this.props.modalLanguage.marketplace}</button>
|
||||
<button className='tablinks' id='active'>{this.props.modalLanguage.addons}</button>
|
||||
<button className='tablinks' onClick={this.props.openSettings}>{this.props.modalLanguage.settings}</button>
|
||||
</div>
|
||||
<div id='marketplace'>
|
||||
<input id='file-input' type='file' name='name' className='hidden' />
|
||||
<button className='addToMue sideload' onClick={() => document.getElementById('file-input').click()}>{this.props.language.sideload}</button>
|
||||
<h1>{this.props.language.added}</h1>
|
||||
{content}
|
||||
</div>
|
||||
<Item button={this.state.button} data={this.state.item_data} function={() => this.toggle()} language={this.props.marketplaceLanguage.product} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
38
src/components/modals/ErrorBoundary.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
|
||||
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
|
||||
|
||||
export default class ErrorBoundary extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: false
|
||||
};
|
||||
this.language = window.language.modals.main.error_boundary;
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
console.log(error);
|
||||
window.analytics.postEvent('modal', 'Error occurred');
|
||||
return {
|
||||
error: true
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<ErrorOutlineIcon/>
|
||||
<h1>{this.language.title}</h1>
|
||||
<p>{this.language.message}</p>
|
||||
<button className='refresh' onClick={() => window.location.reload()}>{this.language.refresh}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
import React from 'react';
|
||||
import WifiOffIcon from '@material-ui/icons/WifiOff';
|
||||
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
|
||||
import { toast } from 'react-toastify';
|
||||
import Item from './marketplace/Item';
|
||||
import MarketplaceFunctions from '../../modules/marketplaceFunctions';
|
||||
import * as Constants from '../../modules/constants';
|
||||
import Items from './marketplace/Items';
|
||||
|
||||
export default class Marketplace extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
themes: [],
|
||||
settings: [],
|
||||
photo_packs: [],
|
||||
quote_packs: [],
|
||||
see_more: [],
|
||||
see_more_type: '',
|
||||
current_data: {
|
||||
type: '',
|
||||
name: '',
|
||||
content: {}
|
||||
},
|
||||
button: '',
|
||||
featured: {},
|
||||
done: false,
|
||||
item_data: {
|
||||
name: 'Name',
|
||||
author: 'Author',
|
||||
description: 'Description',
|
||||
updated: '???',
|
||||
version: '1.0.0',
|
||||
icon: ''
|
||||
}
|
||||
}
|
||||
|
||||
this.offlineHTML = <div className='content'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1>{this.props.modalLanguage.title}</h1>
|
||||
<div className='tab'>
|
||||
<button className='tablinks' id='active'>{this.props.modalLanguage.marketplace}</button>
|
||||
<button className='tablinks' onClick={this.props.openAddons}>{this.props.modalLanguage.addons}</button>
|
||||
<button className='tablinks'
|
||||
onClick={this.props.openSettings}>{this.props.modalLanguage.settings}</button>
|
||||
</div>
|
||||
<div id='marketplace'>
|
||||
<div className='emptyMessage' style={{'marginTop': '20px', 'transform': 'translateY(80%)'}}>
|
||||
<WifiOffIcon />
|
||||
<h1>{this.props.language.offline.title}</h1>
|
||||
<p className='description'>{this.props.language.offline.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
async toggle(type, type2, data) {
|
||||
if (type === 'seemore') {
|
||||
document.getElementById('marketplace').style.display = 'none';
|
||||
document.getElementById('seemore').style.display = 'block';
|
||||
return this.setState({
|
||||
see_more: this.state[type2],
|
||||
see_more_type: type2
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'item') {
|
||||
let info;
|
||||
try {
|
||||
info = await (await fetch(`${Constants.MARKETPLACE_URL}/item/${type2}/${data}`)).json();
|
||||
} catch (e) {
|
||||
return toast(this.props.toastLanguage.error);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
current_data: { type: type2, name: data, content: info },
|
||||
item_data: {
|
||||
name: info.data.name,
|
||||
author: info.data.author,
|
||||
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
|
||||
updated: info.updated,
|
||||
version: info.data.version,
|
||||
icon: info.data.screenshot_url
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('marketplace').style.display = 'none';
|
||||
document.getElementById('seemore').style.display = 'none';
|
||||
document.getElementById('item').style.display = 'block';
|
||||
|
||||
let button = <button className='addToMue' onClick={() => this.install()}>{this.props.language.product.buttons.addtomue}</button>;
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
if (installed.some(item => item.name === data)) button = <button className='removeFromMue' onClick={() => this.uninstall()}>{this.props.language.product.buttons.remove}</button>;
|
||||
this.setState({ button: button });
|
||||
} else {
|
||||
document.getElementById('marketplace').style.display = 'block';
|
||||
document.getElementById('item').style.display = 'none';
|
||||
document.getElementById('seemore').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async getItems() {
|
||||
const data = await (await fetch(Constants.MARKETPLACE_URL + '/all')).json();
|
||||
const featured = await (await fetch(Constants.MARKETPLACE_URL + '/featured')).json();
|
||||
this.setState({
|
||||
themes: data.data.themes,
|
||||
settings: data.data.settings,
|
||||
photo_packs: data.data.photo_packs,
|
||||
quote_packs: data.data.quote_packs,
|
||||
see_more: data.data.photo_packs,
|
||||
featured: featured.data,
|
||||
done: true
|
||||
});
|
||||
}
|
||||
|
||||
install() {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
let button;
|
||||
|
||||
const installStuff = () => {
|
||||
installed.push(this.state.current_data);
|
||||
localStorage.setItem('installed', JSON.stringify(installed));
|
||||
toast(this.props.toastLanguage.installed);
|
||||
button = <button className='removeFromMue' onClick={() => this.uninstall()}>{this.props.language.product.buttons.remove}</button>;
|
||||
this.setState({ button: button });
|
||||
}
|
||||
|
||||
switch (this.state.current_data.type) {
|
||||
case 'settings':
|
||||
localStorage.removeItem('backup_settings');
|
||||
let oldSettings = [];
|
||||
for (const key of Object.keys(localStorage)) oldSettings.push({name: key, value: localStorage.getItem(key)});
|
||||
localStorage.setItem('backup_settings', JSON.stringify(oldSettings));
|
||||
this.state.current_data.content.data.settings.forEach(element => localStorage.setItem(element.name, element.value));
|
||||
installStuff();
|
||||
break;
|
||||
case 'photo_packs':
|
||||
localStorage.setItem('photo_packs', JSON.stringify(this.state.current_data.content.data.photos));
|
||||
installStuff();
|
||||
break;
|
||||
case 'theme':
|
||||
localStorage.setItem('theme', this.state.current_data.content.data.theme);
|
||||
installStuff();
|
||||
break;
|
||||
case 'quote_packs':
|
||||
if (this.state.current_data.content.data.quote_api) localStorage.setItem('quote_api', JSON.stringify(this.state.current_data.content.data.quote_api));
|
||||
localStorage.setItem('quote_packs', JSON.stringify(this.state.current_data.content.data.quotes));
|
||||
installStuff();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
MarketplaceFunctions.uninstall(this.state.current_data.name, this.state.current_data.type);
|
||||
toast(this.props.toastLanguage.removed);
|
||||
this.setState({
|
||||
button: <button className='addToMue' onClick={() => this.install()}>{this.props.language.product.buttons.addtomue}</button>
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.getElementById('backgroundImage').classList.toggle('backgroundEffects');
|
||||
document.getElementById('center').classList.toggle('backgroundEffects');
|
||||
if (navigator.onLine === false) return;
|
||||
this.getItems();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.getElementById('backgroundImage').classList.toggle('backgroundEffects');
|
||||
document.getElementById('center').classList.toggle('backgroundEffects');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (navigator.onLine === false) return this.offlineHTML;
|
||||
if (this.state.done === false) {
|
||||
return <div className='content'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1>{this.props.modalLanguage.title}</h1>
|
||||
<div className='tab'>
|
||||
<button className='tablinks' id='active'>{this.props.modalLanguage.marketplace}</button>
|
||||
<button className='tablinks' onClick={this.props.openAddons}>{this.props.modalLanguage.addons}</button>
|
||||
<button className='tablinks'
|
||||
onClick={this.props.openSettings}>{this.props.modalLanguage.settings}</button>
|
||||
</div>
|
||||
<div id='marketplace'>
|
||||
<div className='emptyMessage' style={{'marginTop': '20px', 'transform': 'translateY(80%)'}}>
|
||||
<h1>Loading...</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div className='content'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1>{this.props.modalLanguage.title}</h1>
|
||||
<div className='tab'>
|
||||
<button className='tablinks' id='active'>{this.props.modalLanguage.marketplace}</button>
|
||||
<button className='tablinks' onClick={this.props.openAddons}>{this.props.modalLanguage.addons}</button>
|
||||
<button className='tablinks'
|
||||
onClick={this.props.openSettings}>{this.props.modalLanguage.settings}</button>
|
||||
</div>
|
||||
<div id='marketplace'>
|
||||
<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.location.href = this.state.featured.buttonLink}>{this.state.featured.buttonText}</button>
|
||||
</div>
|
||||
<Items
|
||||
title={this.props.language.photo_packs}
|
||||
seeMoreTitle={this.props.language.see_more}
|
||||
items={this.state.photo_packs.slice(0, 3)}
|
||||
toggleFunction={(input) => this.toggle('item', 'photo_packs', input)}
|
||||
seeMoreFunction={() => this.toggle('seemore', 'photo_packs')} />
|
||||
<Items
|
||||
title={this.props.language.preset_settings}
|
||||
seeMoreTitle={this.props.language.see_more}
|
||||
items={this.state.settings.slice(0, 3)}
|
||||
toggleFunction={(input) => this.toggle('item', 'settings', input)}
|
||||
seeMoreFunction={() => this.toggle('seemore', 'settings')} />
|
||||
<Items
|
||||
title={this.props.language.quote_packs}
|
||||
seeMoreTitle={this.props.language.see_more}
|
||||
items={this.state.quote_packs.slice(0, 3)}
|
||||
toggleFunction={(input) => this.toggle('item', 'quote_packs', input)}
|
||||
seeMoreFunction={() => this.toggle('seemore', 'quote_packs')} />
|
||||
<Items
|
||||
title={this.props.language.themes}
|
||||
seeMoreTitle={this.props.language.see_more}
|
||||
items={this.state.themes.slice(0, 3)}
|
||||
toggleFunction={(input) => this.toggle('item', 'theme', input)}
|
||||
seeMoreFunction={() => this.toggle('seemore', 'themes')} />
|
||||
</div>
|
||||
<Item
|
||||
button={this.state.button}
|
||||
data={this.state.item_data}
|
||||
content={this.state.current_data}
|
||||
function={() => this.toggle()} language={this.props.language.product}
|
||||
/>
|
||||
<div id='seemore'>
|
||||
<ArrowBackIcon className='backArrow' onClick={() => this.toggle()} />
|
||||
<Items
|
||||
title={this.props.language.see_more}
|
||||
seeMoreTitle={this.props.language.see_more}
|
||||
toggleFunction={(input) => this.toggle('item', this.state.see_more_type, input)}
|
||||
items={this.state.see_more}
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
72
src/components/modals/Modals.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
|
||||
import Main from './main/Main';
|
||||
import Navbar from '../widgets/navbar/Navbar';
|
||||
|
||||
import Modal from 'react-modal';
|
||||
|
||||
// These modals are lazy loaded as the user won't use them every time they open a tab
|
||||
// We used to lazy load the main modal, but doing so broke the main modal open animation on first click
|
||||
const Welcome = React.lazy(() => import('./welcome/Welcome'));
|
||||
const Feedback = React.lazy(() => import('./feedback/Feedback'));
|
||||
const renderLoader = () => <></>;
|
||||
|
||||
export default class Modals extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
mainModal: false,
|
||||
updateModal: false,
|
||||
welcomeModal: false,
|
||||
feedbackModal: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('showWelcome') === 'true' && window.location.search !== '?nointro=true') {
|
||||
this.setState({
|
||||
welcomeModal: true
|
||||
});
|
||||
window.analytics.postEvent('modal', 'Opened welcome');
|
||||
}
|
||||
|
||||
// hide refresh reminder once the user has refreshed the page
|
||||
localStorage.setItem('showReminder', false);
|
||||
}
|
||||
|
||||
closeWelcome() {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
this.setState({
|
||||
welcomeModal: false
|
||||
});
|
||||
}
|
||||
|
||||
toggleModal(type, action) {
|
||||
this.setState({
|
||||
[type]: action
|
||||
});
|
||||
|
||||
if (action !== false) {
|
||||
window.analytics.postEvent('modal', `Opened ${type.replace('Modal', '')}`);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Navbar openModal={(modal) => this.toggleModal(modal, true)}/>
|
||||
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.toggleModal('mainModal', false)} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Main modalClose={() => this.toggleModal('mainModal', false)}/>
|
||||
</Modal>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Welcome modalClose={() => this.closeWelcome()}/>
|
||||
</Modal>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.toggleModal('feedbackModal', false)} isOpen={this.state.feedbackModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Feedback modalClose={() => this.toggleModal('feedbackModal', false)}/>
|
||||
</Modal>
|
||||
</React.Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import React from 'react';
|
||||
import SettingsFunctions from '../../modules/settingsFunctions';
|
||||
import Checkbox from './settings/Checkbox';
|
||||
import Slider from './settings/Slider';
|
||||
import Section from './settings/Section';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackgroundSettings from './settings/sections/BackgroundSettings';
|
||||
import ExperimentalSettings from './settings/sections/ExperimentalSettings';
|
||||
import SearchSettings from './settings/sections/SearchSettings';
|
||||
import LanguageSettings from './settings/sections/LanguageSettings';
|
||||
|
||||
export default class Settings extends React.PureComponent {
|
||||
resetGreeting() {
|
||||
document.getElementById('greetingName').value = '';
|
||||
toast(this.props.toastLanguage.reset);
|
||||
}
|
||||
|
||||
updateCurrent() {
|
||||
document.getElementById('greetingName').value = localStorage.getItem('greetingName');
|
||||
document.getElementById('language').value = localStorage.getItem('language');
|
||||
|
||||
if (localStorage.getItem('darkTheme') === 'true') {
|
||||
const choices = document.getElementsByClassName('choices');
|
||||
for (let i = 0; i < choices.length; i++) choices[i].style.backgroundColor = '#2f3542';
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateCurrent();
|
||||
|
||||
document.getElementById('file-input').onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file.type !== 'application/json') return console.error(`expected json, got ${file.type}`);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
|
||||
reader.onload = (readerEvent) => {
|
||||
const content = JSON.parse(readerEvent.target.result);
|
||||
for (const key of Object.keys(content)) localStorage.setItem(key, content[key]);
|
||||
toast(this.props.toastLanguage.imported);
|
||||
};
|
||||
};
|
||||
|
||||
document.getElementById('backgroundImage').classList.toggle('backgroundEffects');
|
||||
document.getElementById('center').classList.toggle('backgroundEffects');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.getElementById('backgroundImage').classList.toggle('backgroundEffects');
|
||||
document.getElementById('center').classList.toggle('backgroundEffects');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='content'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1>{this.props.modalLanguage.title}</h1>
|
||||
<div className='tab'>
|
||||
<button className='tablinks' onClick={this.props.openMarketplace}>{this.props.modalLanguage.marketplace}</button>
|
||||
<button className='tablinks' onClick={this.props.openAddons}>{this.props.modalLanguage.addons}</button>
|
||||
<button className='tablinks' id='active'>{this.props.modalLanguage.settings}</button>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div className='columns'>
|
||||
<Section title={this.props.language.time.title} name='time'>
|
||||
<Checkbox name='seconds' text={this.props.language.time.seconds} />
|
||||
<Checkbox name='24hour' text={this.props.language.time.twentyfourhour} />
|
||||
<Checkbox name='ampm' text={this.props.language.time.ampm} />
|
||||
<Checkbox name='zero' text={this.props.language.time.zero} />
|
||||
<Checkbox name='analog' text={this.props.language.time.analog} />
|
||||
</Section>
|
||||
<Section title={this.props.language.greeting.title} name='greeting'>
|
||||
<Checkbox name='events' text={this.props.language.greeting.events} />
|
||||
<Checkbox name='defaultGreetingMessage' text={this.props.language.greeting.default} />
|
||||
<ul>
|
||||
<p>{this.props.language.greeting.name} <span className='modalLink' onClick={() => this.resetGreeting()}>{this.props.language.reset}</span></p>
|
||||
<input type='text' id='greetingName'></input>
|
||||
</ul>
|
||||
</Section>
|
||||
<Section title={this.props.language.quote.title} name='quote'>
|
||||
<Checkbox name='copyButton' text={this.props.language.quote.copy} />
|
||||
<Checkbox name='tweetButton' text={this.props.language.quote.tweet} />
|
||||
</Section>
|
||||
<Section title={this.props.language.background.title} name='background'>
|
||||
<BackgroundSettings language={this.props.language} toastLanguage={this.props.toastLanguage} />
|
||||
</Section>
|
||||
<Section title={this.props.language.searchbar.title} name='searchBar'>
|
||||
<SearchSettings language={this.props.language} toastLanguage={this.props.toastLanguage} />
|
||||
</Section>
|
||||
<div className='section'>
|
||||
<h4 class='nodropdown'>{this.props.language.offline}</h4>
|
||||
<Slider name='offlineMode'/>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4 class='nodropdown'>{this.props.language.experimental.dark}</h4>
|
||||
<Slider name='darkTheme'/>
|
||||
</div>
|
||||
<ExperimentalSettings language={this.props.language} />
|
||||
<LanguageSettings language={this.props.language} />
|
||||
|
||||
<button className='apply' onClick={() => SettingsFunctions.saveStuff()}>{this.props.language.apply}</button>
|
||||
<button className='reset' onClick={() => this.props.setDefaultSettings()}>{this.props.language.reset}</button>
|
||||
<button className='export' onClick={() => SettingsFunctions.exportSettings()}>{this.props.language.export}</button>
|
||||
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.props.language.import}</button>
|
||||
<input id='file-input' type='file' name='name' className='hidden' accept='application/json' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as Constants from '../../modules/constants';
|
||||
|
||||
export default class Update extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
title: this.props.language.title,
|
||||
date: '???',
|
||||
content: this.props.language.title,
|
||||
url: '',
|
||||
author: 'Mue'
|
||||
};
|
||||
}
|
||||
|
||||
async getUpdate() {
|
||||
if (localStorage.getItem('offlineMode') === 'true') return this.setState({
|
||||
title: this.props.language.offline.title,
|
||||
content: this.props.language.offline.description
|
||||
});
|
||||
|
||||
try { // Get update log from the API
|
||||
const data = await (await fetch(Constants.API_URL + '/getUpdate')).json();
|
||||
this.setState({
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
date: data.published,
|
||||
image: data.image,
|
||||
url: data.url,
|
||||
author: data.author
|
||||
});
|
||||
} catch (e) { // If it fails, we send an error
|
||||
this.setState({
|
||||
title: this.props.language.error.title,
|
||||
content: this.props.language.error.description
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className='updateContent'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1 style={{ 'marginBottom':'-10px' }}>{this.state.title}</h1>
|
||||
<h5 style={{ 'lineHeight':'0px' }}> By {this.state.author} • {this.state.date}</h5>
|
||||
<img draggable='false' src={this.state.image} alt='Update'></img>
|
||||
<p dangerouslySetInnerHTML={{__html: this.state.content + `<br/><p>Read on the blog here: <a target='_blank' class='modalLink' href='${this.state.url}'>${this.state.url}</a></p>`}}></p>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
|
||||
export default class Welcome extends React.PureComponent {
|
||||
render() {
|
||||
return <div className='welcomeContent'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<div className='welcomeModalText'>
|
||||
<h2 className='subtitle'>Welcome to</h2>
|
||||
<h1 className='welcometitle'>Mue Tab</h1>
|
||||
<img alt='celebration' style={{'height': '200px', 'width': 'auto'}} src='./././icons/undraw_celebration.svg' />
|
||||
<h2 className='subtitle'>Information</h2>
|
||||
<p>Thank you for downloading Mue Tab,<br/> we hope you enjoy your time with our extension.</p>
|
||||
<h2 className='subtitle'>Tutorials</h2>
|
||||
<a href=''>General Start</a>
|
||||
<br/>
|
||||
<a href='https://blog.muetab.xyz/welcome-to-marketplace/'>Marketplace</a>
|
||||
<br/>
|
||||
<a href=''>Submitting Photos</a>
|
||||
<br/>
|
||||
<a href=''>Settings</a>
|
||||
<h2 className='subtitle'>Support</h2>
|
||||
{/* <img alt='twitter' href='https://twitter.com/getmue' className='icon' src=''/>
|
||||
<img alt='discord' href='https://discord.gg/kJsufA9' className='icon' src=''/> */}
|
||||
<EmailIcon />
|
||||
<br/>
|
||||
<button className='close' onClick={this.props.modalClose}>Close</button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
93
src/components/modals/feedback/Feedback.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
|
||||
import './feedback.scss';
|
||||
|
||||
export default class FeedbackModal extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
questionone: 5,
|
||||
questionthree: 5,
|
||||
questiontwoerror: '',
|
||||
questionfourerror: '',
|
||||
formsubmit: ''
|
||||
};
|
||||
this.language = window.language.modals.feedback;
|
||||
}
|
||||
|
||||
async submitForm () {
|
||||
let questiontwoerror, questionfourerror;
|
||||
|
||||
if (document.getElementById('questiontwo').value.length <= 0) {
|
||||
questiontwoerror = this.language.not_filled;
|
||||
}
|
||||
|
||||
if (document.getElementById('questionfour').value.length <= 0) {
|
||||
questionfourerror = this.language.not_filled;
|
||||
}
|
||||
|
||||
if (questiontwoerror || questionfourerror) {
|
||||
this.setState({
|
||||
questiontwoerror: questiontwoerror,
|
||||
questionfourerror: questionfourerror
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
questiontwoerror: '',
|
||||
questionfourerror: ''
|
||||
});
|
||||
|
||||
await fetch(window.constants.FEEDBACK_FORM, {
|
||||
'method': 'POST'
|
||||
});
|
||||
|
||||
this.setState({
|
||||
formsubmit: this.language.success
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.props.modalClose();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='feedback'>
|
||||
<h1>{this.language.title}</h1>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<>
|
||||
<input type='hidden' name='version' value={window.constants.VERSION} />
|
||||
<>
|
||||
<label>{this.language.question_one}</label>
|
||||
<br/><br/>
|
||||
<label className='values'>0</label>
|
||||
<input className='range' type='range' min='0' max='10' name='question1' value={this.state.questionone} onChange={(e) => this.setState({ questionone: e.target.value })}/>
|
||||
<label className='values'>10 ({this.state.questionone})</label>
|
||||
</>
|
||||
<br/><br/>
|
||||
<>
|
||||
<label>{this.language.question_two}</label>
|
||||
<textarea name='question2' id='questiontwo'/>
|
||||
<p className='feedbackerror'>{this.state.questiontwoerror}</p>
|
||||
</>
|
||||
<>
|
||||
<label>{this.language.question_three}</label>
|
||||
<br/><br/>
|
||||
<label className='values'>0</label>
|
||||
<input className='range' type='range' min='0' max='10' name='question3' value={this.state.questionthree} onChange={(e) => this.setState({ questionthree: e.target.value })}/>
|
||||
<label className='values'>10 ({this.state.questionthree})</label>
|
||||
</>
|
||||
<br/><br/>
|
||||
<>
|
||||
<label>{this.language.question_four}</label>
|
||||
<textarea name='question4' id='questionfour'/>
|
||||
<p className='feedbackerror'>{this.state.questionfourerror}</p>
|
||||
</>
|
||||
{this.state.formsubmit}
|
||||
<button onClick={() => this.submitForm()}>{this.language.submit}</button>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
63
src/components/modals/feedback/feedback.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
@import '../main/scss/index.scss';
|
||||
|
||||
#feedbackmodal {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 400px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
width: 400px;
|
||||
padding: 5px;
|
||||
|
||||
h1,
|
||||
.closeModal {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 6em;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 50%;
|
||||
border-radius: 48px;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 15px 20px;
|
||||
font-size: 1.5em;
|
||||
background: linear-gradient(90deg, #ffb032 0%, #dd3b67 100%);
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
label.values {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 80%;
|
||||
background-color: var(--sidebar);
|
||||
}
|
||||
|
||||
.feedbackerror {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
50
src/components/modals/main/Main.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React 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 renderLoader = () => (
|
||||
<Tabs>
|
||||
<div label={window.language.modals.main.loading}>
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<h1>{window.language.modals.main.loading}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label='' style={{ 'display': 'none' }}></div>
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
export default function MainModal(props) {
|
||||
const language = window.language.modals.main.navbar;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<Tabs navbar={true}>
|
||||
<div label={language.settings}>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Settings/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
<div label={language.addons}>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Addons/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
<div label={language.marketplace}>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Marketplace/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
76
src/components/modals/main/marketplace/Item.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import Lightbox from './Lightbox';
|
||||
|
||||
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
|
||||
|
||||
export default class Item extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
showLightbox: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const language = window.language.modals.main.marketplace.product;
|
||||
|
||||
if (!this.props.data.display_name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let warningHTML;
|
||||
if (this.props.data.quote_api) {
|
||||
warningHTML = (
|
||||
<div className='productInformation'>
|
||||
<ul>
|
||||
<li className='header'>{language.quote_warning.title}</li>
|
||||
<li id='updated'>{language.quote_warning.description}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// prevent console error
|
||||
let iconsrc = window.constants.DDG_PROXY + this.props.data.icon;
|
||||
if (!this.props.data.icon) {
|
||||
iconsrc = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='item'>
|
||||
<br/>
|
||||
<ArrowBackIcon className='backArrow' onClick={this.props.toggleFunction}/>
|
||||
<br/>
|
||||
<h1>{this.props.data.display_name}</h1>
|
||||
<br/>
|
||||
{this.props.button}
|
||||
<br/>
|
||||
{iconsrc ? <img alt='product' draggable='false' src={iconsrc} onClick={() => this.setState({ showLightbox: true })}/> : null}
|
||||
<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}/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/components/modals/main/marketplace/Items.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export default function Items(props) {
|
||||
return (
|
||||
<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} />
|
||||
<div className='details'>
|
||||
<h4>{item.display_name || item.name}</h4>
|
||||
<p>{item.author}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
src/components/modals/main/marketplace/Lightbox.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
export default function Lightbox(props) {
|
||||
window.analytics.postEvent('modal', 'Opened lightbox');
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<img src={props.img} className='lightboximg' draggable={false} alt='Item screenshot'/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
128
src/components/modals/main/marketplace/sections/Added.jsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React from 'react';
|
||||
|
||||
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 { toast } from 'react-toastify';
|
||||
|
||||
export default class Added extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
installed: JSON.parse(localStorage.getItem('installed')),
|
||||
item: {},
|
||||
button: ''
|
||||
};
|
||||
this.buttons = {
|
||||
uninstall: <button className='removeFromMue' onClick={() => this.uninstall()}>{window.language.modals.main.marketplace.product.buttons.remove}</button>,
|
||||
};
|
||||
this.language = window.language.modals.main.addons;
|
||||
}
|
||||
|
||||
toggle(type, data) {
|
||||
if (type === 'item') {
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
const info = installed.find((i) => i.name === data);
|
||||
|
||||
this.setState({
|
||||
item: {
|
||||
type: info.type,
|
||||
name: data,
|
||||
display_name: info.name,
|
||||
author: info.author,
|
||||
description: MarketplaceFunctions.urlParser(info.description.replace(/\n/g, '<br>')),
|
||||
//updated: info.updated,
|
||||
version: info.version,
|
||||
icon: info.screenshot_url,
|
||||
quote_api: info.quote_api || null
|
||||
},
|
||||
button: this.buttons.uninstall
|
||||
});
|
||||
window.analytics.postEvent('marketplace', 'Item viewed');
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
|
||||
|
||||
toast(window.language.toasts.uninstalled);
|
||||
|
||||
this.setState({
|
||||
button: '',
|
||||
installed: JSON.parse(localStorage.getItem('installed'))
|
||||
});
|
||||
|
||||
window.analytics.postEvent('marketplace', 'Uninstall');
|
||||
}
|
||||
|
||||
sortAddons(value, sendEvent) {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
switch (value) {
|
||||
case 'newest':
|
||||
installed.reverse();
|
||||
break;
|
||||
case 'oldest':
|
||||
break;
|
||||
case 'a-z':
|
||||
installed.sort();
|
||||
break;
|
||||
case 'z-a':
|
||||
installed.sort();
|
||||
installed.reverse();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
installed: installed
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
window.analytics.postEvent('marketplace', 'Sort');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.sortAddons(localStorage.getItem('sortAddons'), false);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.installed.length === 0) {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<LocalMallIcon/>
|
||||
<h1>{this.language.empty.title}</h1>
|
||||
<p className='description'>{this.language.empty.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.item.display_name) {
|
||||
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown label={this.language.sort.title} name='sortAddons' onChange={(value) => this.sortAddons(value)}>
|
||||
<option value='newest'>{this.language.sort.newest}</option>
|
||||
<option value='oldest'>{this.language.sort.oldest}</option>
|
||||
<option value='a-z'>{this.language.sort.a_z}</option>
|
||||
<option value='z-a'>{this.language.sort.z_a}</option>
|
||||
</Dropdown>
|
||||
<br/>
|
||||
<Items items={this.state.installed} toggleFunction={(input) => this.toggle('item', input)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
221
src/components/modals/main/marketplace/sections/Marketplace.jsx
Normal file
@@ -0,0 +1,221 @@
|
||||
import React from 'react';
|
||||
|
||||
import WifiOffIcon from '@material-ui/icons/WifiOff';
|
||||
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 { toast } from 'react-toastify';
|
||||
|
||||
export default class Marketplace extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
items: [],
|
||||
button: '',
|
||||
featured: {},
|
||||
done: false,
|
||||
item: {}
|
||||
};
|
||||
this.buttons = {
|
||||
uninstall: <button className='removeFromMue' onClick={() => this.manage('uninstall')}>{window.language.modals.main.marketplace.product.buttons.remove}</button>,
|
||||
install: <button className='addToMue' onClick={() => this.manage('install')}>{window.language.modals.main.marketplace.product.buttons.addtomue}</button>
|
||||
};
|
||||
this.language = window.language.modals.main.marketplace;
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
|
||||
async toggle(type, data) {
|
||||
if (type === 'item') {
|
||||
let info;
|
||||
// get item info
|
||||
try {
|
||||
info = await (await fetch(`${window.constants.MARKETPLACE_URL}/item/${this.props.type}/${data}`, { signal: this.controller.signal })).json();
|
||||
} catch (e) {
|
||||
if (this.controller.signal.aborted === false) {
|
||||
return toast(window.language.toasts.error);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if already installed
|
||||
let button = this.buttons.install;
|
||||
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
|
||||
if (installed.some((item) => item.name === info.data.name)) {
|
||||
button = this.buttons.uninstall;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
item: {
|
||||
type: info.data.type,
|
||||
display_name: info.data.name,
|
||||
author: info.data.author,
|
||||
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
|
||||
//updated: info.updated,
|
||||
version: info.data.version,
|
||||
icon: info.data.screenshot_url,
|
||||
data: info.data
|
||||
},
|
||||
button: button
|
||||
});
|
||||
|
||||
window.analytics.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getItems() {
|
||||
const { data } = await (await fetch(window.constants.MARKETPLACE_URL + '/all', { 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
items: data[this.props.type],
|
||||
oldItems: data[this.props.type],
|
||||
featured: featured.data,
|
||||
done: true
|
||||
});
|
||||
|
||||
this.sortMarketplace(localStorage.getItem('sortMarketplace'), false);
|
||||
}
|
||||
|
||||
manage(type) {
|
||||
if (type === 'install') {
|
||||
MarketplaceFunctions.install(this.state.item.type, this.state.item.data);
|
||||
} else {
|
||||
MarketplaceFunctions.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.analytics.postEvent('marketplace-item', `${this.state.item.display_name} ${(type === 'install' ? 'installed': 'uninstalled')}`);
|
||||
window.analytics.postEvent('marketplace', (type === 'install' ? 'Install': 'Uninstall'));
|
||||
}
|
||||
|
||||
sortMarketplace(value, sendEvent) {
|
||||
let items = this.state.oldItems;
|
||||
switch (value) {
|
||||
case 'a-z':
|
||||
items.sort();
|
||||
// fix sort not working sometimes
|
||||
if (this.state.sortType === 'z-a') {
|
||||
items.reverse();
|
||||
}
|
||||
break;
|
||||
case 'z-a':
|
||||
items.sort();
|
||||
items.reverse();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
items: items,
|
||||
sortType: value
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
window.analytics.postEvent('marketplace', 'Sort');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getItems();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// stop making requests
|
||||
this.controller.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
const errorMessage = (msg) => {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
{msg}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const featured = () => {
|
||||
const openFeatured = () => {
|
||||
window.analytics.postEvent('marketplace', 'Featured clicked');
|
||||
window.open(this.state.featured.buttonLink);
|
||||
}
|
||||
return (
|
||||
<div className='featured' style={{ 'backgroundColor': this.state.featured.colour }}>
|
||||
<p>{this.state.featured.title}</p>
|
||||
<h1>{this.state.featured.name}</h1>
|
||||
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return errorMessage(<>
|
||||
<WifiOffIcon/>
|
||||
<h1>{this.language.offline.title}</h1>
|
||||
<p className='description'>{this.language.offline.description}</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
if (this.state.done === false) {
|
||||
return errorMessage(<h1>{window.language.modals.main.loading}</h1>);
|
||||
}
|
||||
|
||||
if (this.state.items.length === 0) {
|
||||
return (
|
||||
<>
|
||||
{featured()}
|
||||
{errorMessage(<>
|
||||
<LocalMallIcon/>
|
||||
<h1>{window.language.modals.main.addons.empty.title}</h1>
|
||||
<p className='description'>{this.language.no_items}</p>
|
||||
</>)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (this.state.item.display_name) {
|
||||
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{featured()}
|
||||
<br/>
|
||||
<Dropdown label={window.language.modals.main.addons.sort.title} name='sortMarketplace' onChange={(value) => this.sortMarketplace(value)}>
|
||||
<option value='a-z'>{window.language.modals.main.addons.sort.a_z}</option>
|
||||
<option value='z-a'>{window.language.modals.main.addons.sort.z_a}</option>
|
||||
</Dropdown>
|
||||
<br/>
|
||||
<Items items={this.state.items} toggleFunction={(input) => this.toggle('item', input)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
26
src/components/modals/main/marketplace/sections/Sideload.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
|
||||
import FileUpload from '../../settings/FileUpload';
|
||||
|
||||
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default function Sideload() {
|
||||
const install = (input) => {
|
||||
MarketplaceFunctions.install(input.type, input);
|
||||
toast(window.language.toasts.installed);
|
||||
window.analytics.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/>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
412
src/components/modals/main/scss/index.scss
Normal file
@@ -0,0 +1,412 @@
|
||||
@import '../../../../scss/variables';
|
||||
|
||||
@import 'settings/main';
|
||||
@import 'settings/buttons';
|
||||
@import 'settings/dropdown';
|
||||
@import 'settings/daypicker';
|
||||
|
||||
@import 'marketplace/main';
|
||||
@import 'marketplace/buttons';
|
||||
|
||||
.Modal {
|
||||
color: var(--modal-text);
|
||||
background-color: var(--background);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
border: none;
|
||||
opacity: 1;
|
||||
z-index: -2;
|
||||
transition-timing-function: ease-in;
|
||||
border-radius: map-get($modal, 'border-radius');
|
||||
user-select: none;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #34495e #bdc3c7;
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mainModal {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.resetLink {
|
||||
color: var(--modal-link);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 1.2rem;
|
||||
color: var(--modal-link);
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.modalLink {
|
||||
color: var(--modal-link);
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.closeModal {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 2rem;
|
||||
font-size: 4em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
|
||||
.ReactModal__Html--open,
|
||||
.ReactModal__Body--open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.Overlay {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.ReactModal__Content--after-open {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.ReactModal__Content--before-close {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 700px) {
|
||||
.ReactModal__Content {
|
||||
min-height: 500px;
|
||||
max-height: calc(100vh - 30vh);
|
||||
}
|
||||
}
|
||||
|
||||
ul.sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
background: var(--sidebar);
|
||||
border-radius: 12px 0 0 12px;
|
||||
text-align: left;
|
||||
font-size: 24px;
|
||||
min-height: 100vh;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 3px;
|
||||
background: rgba(196, 196, 196, 0.74);
|
||||
width: 75%;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
font-size: 24px;
|
||||
padding: 5px 30px 5px 30px;
|
||||
cursor: pointer;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
#modal {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1300px) {
|
||||
#modal {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1310px) {
|
||||
#modal {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-list-active {
|
||||
background: var(--tab-active);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
li.tab-list-item {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ul.sidebar {
|
||||
h1 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-list-item {
|
||||
&:hover {
|
||||
background: var(--tab-active);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
position: absolute;
|
||||
|
||||
h3 {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 2300px) {
|
||||
.tab-content {
|
||||
left: 350px;
|
||||
top: 7%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1920px) {
|
||||
.tab-content {
|
||||
left: 120px;
|
||||
top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1920px) {
|
||||
.tab-content {
|
||||
left: 350px;
|
||||
top: 7%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1400px), (min-width: 1400px) {
|
||||
.tab-content {
|
||||
left: 350px;
|
||||
top: 75px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.tab-content {
|
||||
left: 125px;
|
||||
top: 75px;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
display: inline-flex;
|
||||
|
||||
&:hover {
|
||||
color: rgb(165, 165, 165);
|
||||
background: none;
|
||||
}
|
||||
|
||||
span,
|
||||
svg {
|
||||
font-size: 1.1em !important;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1.2em !important;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (-webkit-hyphens: none) {
|
||||
.navbar-item {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.modalNavbar {
|
||||
position: absolute;
|
||||
left: 20rem;
|
||||
top: 1rem;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
margin-right: 0.5rem;
|
||||
padding: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.modalNavbar {
|
||||
left: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1650px) {
|
||||
li.navbar-item {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
ul.sidebar {
|
||||
width: 310px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-item-active {
|
||||
background: map-get($theme-colours, 'gradient');
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
|
||||
svg {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: map-get($theme-colours, 'gradient');
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-top-right-radius: map-get($modal, 'border-radius');
|
||||
border-bottom-right-radius: map-get($modal, 'border-radius');
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #636e72;
|
||||
border-top-right-radius: map-get($modal, 'border-radius');
|
||||
border-bottom-right-radius: map-get($modal, 'border-radius');
|
||||
}
|
||||
|
||||
.abouticon {
|
||||
width: 96px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.resetmodal {
|
||||
min-height: 300px !important;
|
||||
max-width: 300px !important;
|
||||
margin: auto;
|
||||
|
||||
h4 {
|
||||
cursor: initial;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.resetfooter {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
|
||||
button.reset {
|
||||
margin-right: 43px;
|
||||
}
|
||||
}
|
||||
|
||||
.resetoverlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.aboutIcon {
|
||||
color: var(--modal-text) !important;
|
||||
padding-right: 10px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.aboutLink {
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.aboutLogo {
|
||||
height: 100px;
|
||||
width: auto;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.MuiFormControl-root {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
|
||||
label,
|
||||
p,
|
||||
span.modalLink {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
48
src/components/modals/main/scss/marketplace/_buttons.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
%storebutton {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: block;
|
||||
padding: 5px 30px;
|
||||
background: none;
|
||||
border-radius: 24px;
|
||||
transition: ease 0.33s;
|
||||
border: 2px solid black;
|
||||
|
||||
&:hover {
|
||||
background: #2d3436;
|
||||
color: map-get($theme-colours, 'main');
|
||||
}
|
||||
}
|
||||
|
||||
.dark %storebutton {
|
||||
border: 2px solid map-get($theme-colours, 'main');
|
||||
color: map-get($theme-colours, 'main');
|
||||
|
||||
&:hover {
|
||||
background: map-get($theme-colours, 'main');
|
||||
color: #2d3436;
|
||||
}
|
||||
}
|
||||
|
||||
.removeFromMue {
|
||||
@extend %storebutton;
|
||||
|
||||
border: 2px solid #ff4757;
|
||||
color: #ff4757;
|
||||
margin-top: 5px;
|
||||
|
||||
&:hover {
|
||||
background: #ff4757;
|
||||
color: map-get($theme-colours, 'main');
|
||||
}
|
||||
}
|
||||
|
||||
.addToMue {
|
||||
@extend %storebutton;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.sideload {
|
||||
display: inline;
|
||||
margin-top: 0px;
|
||||
}
|
||||
217
src/components/modals/main/scss/marketplace/_main.scss
Normal file
@@ -0,0 +1,217 @@
|
||||
#item a {
|
||||
color: var(--modal-link);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyitems {
|
||||
width: 25vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 90px;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
margin-top: 15px;
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
height: 80px;
|
||||
width: 260px;
|
||||
background: var(--sidebar);
|
||||
transition: 0.5s;
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0 0 6px rgb(0 0 0 / 30%);
|
||||
|
||||
img {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
border-radius: 12px 0 0 12px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
img,
|
||||
.details {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.details {
|
||||
position: absolute;
|
||||
left: 85px;
|
||||
top: -15px;
|
||||
|
||||
img {
|
||||
margin-left: 10px;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 5px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 2100px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1870px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1079px), (max-width: 1869px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
p.author {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
#item {
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#item>h1,
|
||||
#item>.MuiSvgIcon-root {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.description {
|
||||
margin-top: 0px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.emptyMessage {
|
||||
text-align: center;
|
||||
background: var(--sidebar);
|
||||
padding: 25px;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
|
||||
svg {
|
||||
font-size: 50px;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.backArrow {
|
||||
cursor: pointer;
|
||||
width: 2rem !important;
|
||||
height: 2rem !important;
|
||||
|
||||
&:hover {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
|
||||
.informationContainer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.productInformation {
|
||||
padding: 10px;
|
||||
background: var(--sidebar);
|
||||
width: 350px;
|
||||
border-radius: 12px;
|
||||
|
||||
h4 {
|
||||
cursor: initial !important;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-left: -4px;
|
||||
list-style: none;
|
||||
font-size: 16px;
|
||||
cursor: initial !important;
|
||||
|
||||
&.header {
|
||||
text-transform: uppercase;
|
||||
color: #787878;
|
||||
margin-left: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#item>img, .updateimage, .updatechangelog>p>img {
|
||||
border-radius: 12px;
|
||||
height: 200px;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.featured {
|
||||
margin-top: 40px;
|
||||
border-radius: 15px;
|
||||
padding: 50px;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
|
||||
width: 85%;
|
||||
|
||||
button {
|
||||
float: left;
|
||||
margin-top: -7px;
|
||||
border: 2px solid map-get($theme-colours, 'main');
|
||||
color: map-get($theme-colours, 'main');
|
||||
|
||||
&:hover {
|
||||
background: map-get($theme-colours, 'main');
|
||||
color: #2d3436;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightboxmodal {
|
||||
margin: auto;
|
||||
max-width: 60%;
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.closeModal {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 20px rgb(0 0 0 / 30%);
|
||||
}
|
||||
}
|
||||
|
||||
.overview {
|
||||
font-size: 30px !important;
|
||||
}
|
||||
101
src/components/modals/main/scss/settings/_buttons.scss
Normal file
@@ -0,0 +1,101 @@
|
||||
@import '../../../../../scss/modules/buttons';
|
||||
|
||||
.refresh {
|
||||
@extend %settingsButton;
|
||||
|
||||
background-color: map-get($button-colours, 'confirm');
|
||||
border: 2px solid map-get($button-colours, 'confirm');
|
||||
|
||||
&:hover {
|
||||
border: 2px solid map-get($button-colours, 'confirm');
|
||||
color: map-get($button-colours, 'confirm');
|
||||
}
|
||||
}
|
||||
|
||||
.reset {
|
||||
@extend %settingsButton;
|
||||
|
||||
margin-left: 5px;
|
||||
background-color: map-get($button-colours, 'reset');
|
||||
border: 2px solid map-get($button-colours, 'reset');
|
||||
|
||||
&:hover {
|
||||
border: 2px solid map-get($button-colours, 'reset');
|
||||
color: map-get($button-colours, 'reset');
|
||||
}
|
||||
}
|
||||
|
||||
.add {
|
||||
@extend %settingsButton;
|
||||
|
||||
background-color: map-get($button-colours, 'other');
|
||||
border: 2px solid map-get($button-colours, 'other');
|
||||
|
||||
&:hover {
|
||||
color: map-get($button-colours, 'other');
|
||||
border: 2px solid map-get($button-colours, 'other');
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
@extend %settingsButton;
|
||||
|
||||
padding: 10px 50px 10px 50px;
|
||||
background-color: map-get($button-colours, 'other');
|
||||
border: 2px solid map-get($button-colours, 'other');
|
||||
|
||||
&:hover {
|
||||
color: map-get($button-colours, 'other');
|
||||
border: 2px solid map-get($button-colours, 'other');
|
||||
}
|
||||
}
|
||||
|
||||
.export,
|
||||
.uploadbg,
|
||||
.import {
|
||||
@extend %settingsButton;
|
||||
|
||||
background-color: map-get($button-colours, 'other');
|
||||
color: map-get($theme-colours, 'primary');
|
||||
border: 2px solid map-get($button-colours, 'other');
|
||||
|
||||
&:hover {
|
||||
color: map-get($button-colours, 'other');
|
||||
border: 2px solid map-get($button-colours, 'other');
|
||||
}
|
||||
}
|
||||
|
||||
.export,
|
||||
.import {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.MuiIconButton-label > svg.MuiSvgIcon-root {
|
||||
color: var(--modal-text) !important;
|
||||
}
|
||||
|
||||
.sortableitem {
|
||||
background: var(--sidebar) !important;
|
||||
padding: 10px 80px;
|
||||
padding-left: 10px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.325rem;
|
||||
color: var(--modal-text) !important;
|
||||
cursor: move;
|
||||
width: 150px;
|
||||
z-index: 999 !important;
|
||||
|
||||
svg {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.15);
|
||||
transition: 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiTouchRipple-root {
|
||||
background: transparent;
|
||||
}
|
||||
26
src/components/modals/main/scss/settings/_daypicker.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
.DayPickerInput,
|
||||
.input-container {
|
||||
input {
|
||||
width: 200px;
|
||||
color: var(--modal-text) !important;
|
||||
background: var(--sidebar);
|
||||
border: none;
|
||||
padding: 10px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.DayPicker-Day--selected {
|
||||
background-color: #ff4757 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.DayPicker-Months,
|
||||
.DayPickerInput-Overlay {
|
||||
background-color: var(--background) !important;
|
||||
color: var(--modal-text) !important;
|
||||
}
|
||||
|
||||
.DayPicker:not(.DayPicker--interactionDisabled) .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {
|
||||
color: black !important;
|
||||
}
|
||||
38
src/components/modals/main/scss/settings/_dropdown.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
select {
|
||||
margin-left: 10px;
|
||||
width: 120px;
|
||||
color: var(--modal-text);
|
||||
background: var(--sidebar);
|
||||
border: none;
|
||||
padding: 10px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
// firefox dropdown
|
||||
@supports (-moz-appearance: none) {
|
||||
select {
|
||||
-moz-appearance: none !important;
|
||||
background: url("data:image/svg+xml;utf8,<svg fill='black' 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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
option {
|
||||
font: -moz-pull-down-menu !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// safari dropdown
|
||||
@supports (-webkit-hyphens: none) {
|
||||
select {
|
||||
-webkit-appearance: none !important;
|
||||
background: url("data:image/svg+xml;utf8,<svg fill='black' 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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
222
src/components/modals/main/scss/settings/_main.scss
Normal file
@@ -0,0 +1,222 @@
|
||||
input {
|
||||
&[type=text] {
|
||||
width: 200px;
|
||||
color: var(--modal-text);
|
||||
background: var(--sidebar);
|
||||
border: none;
|
||||
padding: 10px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
&[type=color] {
|
||||
border-radius: 100%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border: none;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
|
||||
&::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&::-webkit-color-swatch {
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&[type=color]::-moz-color-swatch {
|
||||
border-radius: 100%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border: none;
|
||||
outline: none;
|
||||
-moz-appearance: none;
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
|
||||
&::-moz-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&::-moz-color-swatch {
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 0px;
|
||||
margin: 0;
|
||||
|
||||
>label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.range {
|
||||
-webkit-appearance: none;
|
||||
width: 200px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
outline: none;
|
||||
background: var(--sidebar);
|
||||
box-shadow: 0 0 100px rgba(0, 0, 0, 0.3);
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 12px;
|
||||
background: var(--modal-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 12px;
|
||||
border: 0;
|
||||
background: var(--modal-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.newFeature {
|
||||
color: #ff4757;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settingsTextarea {
|
||||
font-family: Consolas !important;
|
||||
padding: 15px;
|
||||
border-radius: 15px;
|
||||
background-color: var(--sidebar) !important;
|
||||
border: none;
|
||||
margin-left: 0;
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.MuiCheckbox-colorPrimary.Mui-checked,
|
||||
.MuiSwitch-colorPrimary.Mui-checked,
|
||||
.MuIconButton-colorPrimary.Mui-checked,
|
||||
.MuiSwitch-thumb,
|
||||
.MuiRadio-colorSecondary.Mui-checked,
|
||||
.PrivateSwitchBase-input-4,
|
||||
.MuiRadio-root,
|
||||
.aboutLink,
|
||||
legend {
|
||||
color: var(--modal-text) !important;
|
||||
}
|
||||
|
||||
.MuiFormControlLabel-labelPlacementStart {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
|
||||
.MuiSwitch-colorPrimary.Mui-checked+.MuiSwitch-track {
|
||||
background: darkgray !important;
|
||||
}
|
||||
|
||||
.reminder-info {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 15px;
|
||||
color: var(--modal-text);
|
||||
background: var(--sidebar);
|
||||
max-width: 300px;
|
||||
border-radius: 0.7em;
|
||||
|
||||
h1 {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-title {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
font-size: 1.17rem;
|
||||
}
|
||||
|
||||
.radio-title-small {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sortableitem {
|
||||
color: var(--modal-text) !important;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.updatechangelog {
|
||||
max-width: 75%;
|
||||
|
||||
li {
|
||||
cursor: initial;
|
||||
font-size: 1rem;
|
||||
list-style-type:disc;
|
||||
padding: 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--modal-link);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.changelogtab {
|
||||
h1 {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
.sliderText {
|
||||
color: var(--modal-text);
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.resetArea {
|
||||
h2 {
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
h2, span, svg {
|
||||
display: inline;
|
||||
}
|
||||
svg {
|
||||
vertical-align: sub;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// The following CSS is to work around some assumptions made by the react-color-gradient-picker
|
||||
* {
|
||||
// workaround for https://github.com/arthay/react-color-gradient-picker/issues/11
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
div.picker-area > div.preview > div.color-hue-alpha > div.alpha,
|
||||
div.color-preview-area > div > div:nth-child(5) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui-color-picker {
|
||||
margin: 8px -12px;
|
||||
background-color: var(--background) !important;
|
||||
}
|
||||
|
||||
.input-field .label {
|
||||
color: inherit;
|
||||
color: var(--modal-text) !important;
|
||||
}
|
||||
|
||||
// other styling for themes support etc
|
||||
.gradient-degrees {
|
||||
border: 3px solid var(--modal-text) !important;
|
||||
}
|
||||
|
||||
.gradient-degree-pointer {
|
||||
background-color: var(--modal-text) !important;
|
||||
}
|
||||
|
||||
.gradient-type-item.active::after {
|
||||
border: 2px solid var(--modal-text) !important;
|
||||
}
|
||||
55
src/components/modals/main/settings/Checkbox.jsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import CheckboxUI from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
|
||||
export default class Checkbox extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checked: (localStorage.getItem(this.props.name) === 'true')
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
SettingsFunctions.setItem(this.props.name);
|
||||
|
||||
this.setState({
|
||||
checked: (this.state.checked === true) ? false : true
|
||||
});
|
||||
|
||||
window.analytics.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';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
<br/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
66
src/components/modals/main/settings/Dropdown.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
export default class Dropdown extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name) || '',
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
return this.props.label ? <label>{this.props.label}</label> : null;
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
if (value === window.language.modals.main.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.analytics.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
|
||||
|
||||
this.setState({
|
||||
value: value,
|
||||
title: e.target[e.target.selectedIndex].text
|
||||
});
|
||||
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
// todo: find a better way to do this
|
||||
componentDidMount() {
|
||||
const element = document.getElementById(this.props.name);
|
||||
this.setState({
|
||||
title: element[element.selectedIndex].text
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{this.getLabel()}
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
31
src/components/modals/main/settings/FileUpload.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class FileUpload extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
document.getElementById(this.props.id).onchange = (e) => {
|
||||
const reader = new FileReader();
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (this.props.type === 'settings') {
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
} else {
|
||||
// background upload
|
||||
if (file.size > 2000000) {
|
||||
return toast(window.language.modals.main.file_upload_error);
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
reader.addEventListener('load', (e) => {
|
||||
this.props.loadFunction(e);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input id={this.props.id} type='file' style={{ display: 'none' }} accept={this.props.accept} />;
|
||||
}
|
||||
}
|
||||
56
src/components/modals/main/settings/Radio.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
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 {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name)
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
if (value === 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
this.setState({
|
||||
value: value
|
||||
});
|
||||
|
||||
window.analytics.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';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FormControl component='fieldset'>
|
||||
<FormLabel className={this.props.smallTitle ? 'radio-title-small' : 'radio-title'} component='legend'>{this.props.title}</FormLabel>
|
||||
<RadioGroup aria-label={this.props.name} name={this.props.name} onChange={this.handleChange} value={this.state.value}>
|
||||
{this.props.options.map((option) => (
|
||||
<FormControlLabel value={option.value} control={<RadioUI/>} label={option.name} key={option.name} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
23
src/components/modals/main/settings/ResetModal.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
export default function ResetModal(props) {
|
||||
const language = window.language.modals.main.settings.sections.advanced.reset_modal;
|
||||
|
||||
const reset = () => {
|
||||
window.analytics.postEvent('setting', 'Reset');
|
||||
SettingsFunctions.setDefaultSettings('reset');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 style={{ 'textAlign': 'center' }}>{language.title}</h3>
|
||||
<h4>{language.question}</h4>
|
||||
<p>{language.information}</p>
|
||||
<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>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
73
src/components/modals/main/settings/Slider.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
// todo: find a better method to do width of number input
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Slider extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name) || this.props.default,
|
||||
numberWidth: localStorage.getItem(this.props.name) ? ((localStorage.getItem(this.props.name).length + 1) * ((this.props.toast === true) ? 7.75 : 7)) : 32
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
this.widthCalculation = (this.props.toast === true) ? 7.75 : 7;
|
||||
}
|
||||
|
||||
handleChange = (e, text) => {
|
||||
let { value } = e.target;
|
||||
|
||||
if (text) {
|
||||
if (value === '') {
|
||||
return this.setState({
|
||||
value: 0
|
||||
});
|
||||
}
|
||||
|
||||
if (Number(value) > this.props.max) {
|
||||
value = this.props.max;
|
||||
}
|
||||
|
||||
if (Number(value) < this.props.min) {
|
||||
value = this.props.min;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(this.props.name, value);
|
||||
this.setState({
|
||||
value: value,
|
||||
numberWidth: ((value.length + 1) * this.widthCalculation)
|
||||
});
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
resetItem = () => {
|
||||
this.handleChange({
|
||||
target: {
|
||||
value: this.props.default || ''
|
||||
}
|
||||
});
|
||||
toast(window.language.toasts.reset);
|
||||
}
|
||||
|
||||
render() {
|
||||
const text = <input className='sliderText' type='number' min={this.props.min} max={this.props.max} onChange={(e) => this.handleChange(e, 'text')} value={this.state.value} style={{ width: this.state.numberWidth }}/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{this.props.title} ({text}{this.props.display}) <span className='modalLink' onClick={this.resetItem}>{this.language.buttons.reset}</span></p>
|
||||
<input className='range' type='range' min={this.props.min} max={this.props.max} step={this.props.step || 1} value={this.state.value} onChange={this.handleChange} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
56
src/components/modals/main/settings/Switch.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
import SwitchUI from '@material-ui/core/Switch';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
|
||||
export default class Switch extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checked: (localStorage.getItem(this.props.name) === 'true')
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
SettingsFunctions.setItem(this.props.name);
|
||||
|
||||
this.setState({
|
||||
checked: (this.state.checked === true) ? false : true
|
||||
});
|
||||
|
||||
window.analytics.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';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
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}
|
||||
labelPlacement='start'
|
||||
/>
|
||||
<br/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
59
src/components/modals/main/settings/Text.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventBus from '../../../../modules/helpers/eventbus';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Text extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name) || ''
|
||||
};
|
||||
this.language = window.language.modals.main.settings;
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
let { value } = e.target;
|
||||
|
||||
// Alex wanted font to work with montserrat and Montserrat, so I made it work
|
||||
if (this.props.upperCaseFirst === true) {
|
||||
value = value.charAt(0).toUpperCase() + value.slice(1);
|
||||
}
|
||||
|
||||
localStorage.setItem(this.props.name, value);
|
||||
this.setState({
|
||||
value: value
|
||||
});
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
resetItem = () => {
|
||||
this.handleChange({
|
||||
target: {
|
||||
value: this.props.default || ''
|
||||
}
|
||||
});
|
||||
toast(window.language.toasts.reset);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<p>{this.props.title} <span className='modalLink' onClick={this.resetItem}>{this.language.buttons.reset}</span></p>
|
||||
{(this.props.textarea === true) ?
|
||||
<textarea className='settingsTextarea' spellCheck={false} value={this.state.value} onChange={this.handleChange}/>
|
||||
: <input type='text' value={this.state.value} onChange={this.handleChange}/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
143
src/components/modals/main/settings/sections/About.jsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import React from 'react';
|
||||
|
||||
import Tooltip from '../../../../helpers/tooltip/Tooltip';
|
||||
import EmailIcon from '@material-ui/icons/Email';
|
||||
import TwitterIcon from '@material-ui/icons/Twitter';
|
||||
import ForumIcon from '@material-ui/icons/Forum';
|
||||
import 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 {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
contributors: [],
|
||||
sponsors: [],
|
||||
other_contributors: [],
|
||||
photographers: window.language.modals.main.loading,
|
||||
update: window.language.modals.main.settings.sections.about.version.checking_update,
|
||||
loading: window.language.modals.main.loading
|
||||
};
|
||||
this.language = window.language.modals.main.settings.sections.about;
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
|
||||
async getGitHubData() {
|
||||
let contributors, sponsors, photographers, versionData;
|
||||
|
||||
try {
|
||||
versionData = await (await fetch(window.constants.GITHUB_URL + '/repos/mue/mue/releases', { signal: this.controller.signal })).json();
|
||||
|
||||
contributors = await (await fetch(window.constants.GITHUB_URL + '/repos/mue/mue/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();
|
||||
} catch (e) {
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.setState({
|
||||
update: this.language.version.error.title,
|
||||
loading: this.language.version.error.description
|
||||
});
|
||||
}
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newVersion = versionData[0].tag_name;
|
||||
|
||||
let updateMsg = this.language.version.no_update;
|
||||
if (Number(window.constants.VERSION.replaceAll('.', '')) < Number(newVersion.replaceAll('.', ''))) {
|
||||
updateMsg = `${this.language.version.update_available}: ${newVersion}`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
contributors: contributors.filter((contributor) => !contributor.login.includes('bot')),
|
||||
sponsors: sponsors,
|
||||
update: updateMsg,
|
||||
other_contributors: other_contributors,
|
||||
photographers: photographers.sort().join(', '),
|
||||
loading: null
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
this.setState({
|
||||
update: this.language.version.offline_mode,
|
||||
loading: window.language.modals.main.marketplace.offline.description
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.getGitHubData();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// stop making requests
|
||||
this.controller.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
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>
|
||||
<p>{this.language.version.title} {window.constants.VERSION} ({this.state.update})</p>
|
||||
|
||||
<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>
|
||||
|
||||
<h3>{this.language.support_mue}</h3>
|
||||
<p>
|
||||
<a href='https://github.com/sponsors/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>GitHub Sponsors</a>
|
||||
• <a href='https://ko-fi.com/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
|
||||
• <a href='https://patreon.com/davidjcralph' className='aboutLink' target='_blank' rel='noopener noreferrer'>Patreon</a>
|
||||
</p>
|
||||
|
||||
<h3>{this.language.resources_used.title}</h3>
|
||||
<p>
|
||||
<a href='https://www.pexels.com' className='aboutLink' target='_blank' rel='noopener noreferrer'>Pexels</a>
|
||||
, <a href='https://unsplash.com' className='aboutLink' target='_blank' rel='noopener noreferrer'>Unsplash</a> ({this.language.resources_used.bg_images})
|
||||
</p>
|
||||
<p><a href='https://fonts.google.com/icons?selected=Material+Icons' className='aboutLink' target='_blank' rel='noopener noreferrer'>Google Fonts</a> ({this.language.resources_used.pin_icon})</p>
|
||||
<p><a href='https://undraw.co' className='aboutLink' target='_blank' rel='noopener noreferrer'>Undraw</a> ({this.language.resources_used.welcome_img})</p>
|
||||
|
||||
<h3>{this.language.contributors}</h3>
|
||||
<p>{this.state.loading}</p>
|
||||
{this.state.contributors.map((item) => (
|
||||
<Tooltip title={item.login} key={item.login}>
|
||||
<a href={'https://github.com/' + item.login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar_url + '&size=128'} alt={item.login}/></a>
|
||||
</Tooltip>
|
||||
))}
|
||||
{ // for those who contributed without opening a pull request
|
||||
this.state.other_contributors.map((item) => (
|
||||
<Tooltip title={item.login} key={item.login}>
|
||||
<a href={'https://github.com/' + item.login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar_url + '&size=128'} alt={item.login}/></a>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
<h3>{this.language.supporters}</h3>
|
||||
<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>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
<h3>{this.language.photographers}</h3>
|
||||
<p>{this.state.photographers}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
63
src/components/modals/main/settings/sections/Advanced.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import FileUpload from '../FileUpload';
|
||||
import Text from '../Text';
|
||||
import Switch from '../Switch';
|
||||
import ResetModal from '../ResetModal';
|
||||
|
||||
import SettingsFunctions from '../../../../../modules/helpers/settings';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
export default class AdvancedSettings extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
resetModal: false
|
||||
};
|
||||
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);
|
||||
window.analytics.postEvent('tab', 'Settings imported');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { advanced } = this.language.sections;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{advanced.title}</h2>
|
||||
<Checkbox name='offlineMode' text={advanced.offline_mode} element='.other' />
|
||||
|
||||
<h3>{advanced.data}</h3>
|
||||
<button className='reset' onClick={() => this.setState({ resetModal: true })}>{this.language.buttons.reset}</button>
|
||||
<button className='export' onClick={() => SettingsFunctions.exportSettings()}>{this.language.buttons.export}</button>
|
||||
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.language.buttons.import}</button>
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.settingsImport(e)}/>
|
||||
|
||||
<h3>{advanced.customisation}</h3>
|
||||
<Text title={advanced.tab_name} name='tabName' default={window.language.tabname} category='other'/>
|
||||
<Text title={advanced.custom_js} name='customjs' textarea={true} category='other' element='other'/>
|
||||
<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>
|
||||
<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}>
|
||||
<ResetModal modalClose={() => this.setState({ resetModal: false })} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
65
src/components/modals/main/settings/sections/Appearance.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { engineName } from 'react-device-detect';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
import Radio from '../Radio';
|
||||
import Slider from '../Slider';
|
||||
import Text from '../Text';
|
||||
|
||||
export default function AppearanceSettings() {
|
||||
const { appearance } = window.language.modals.main.settings.sections;
|
||||
|
||||
const themeOptions = [
|
||||
{
|
||||
name: appearance.theme.auto,
|
||||
value: 'auto'
|
||||
},
|
||||
{
|
||||
name: appearance.theme.light,
|
||||
value: 'light'
|
||||
},
|
||||
{
|
||||
name: appearance.theme.dark,
|
||||
value: 'dark'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{appearance.title}</h2>
|
||||
<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' />
|
||||
|
||||
<h3>{appearance.font.title}</h3>
|
||||
<Text title={appearance.font.custom} name='font' upperCaseFirst={true} category='other' />
|
||||
<br/>
|
||||
<Checkbox name='fontGoogle' text={appearance.font.google} category='other' />
|
||||
<Dropdown label={appearance.font.weight.title} name='fontweight' category='other'>
|
||||
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
||||
<option value='100'>{appearance.font.weight.thin}</option>
|
||||
<option value='200'>{appearance.font.weight.extra_light}</option>
|
||||
<option value='300'>{appearance.font.weight.light}</option>
|
||||
<option value='400'>{appearance.font.weight.normal}</option>
|
||||
<option value='500'>{appearance.font.weight.medium}</option>
|
||||
<option value='600'>{appearance.font.weight.semi_bold}</option>
|
||||
<option value='700'>{appearance.font.weight.bold}</option>
|
||||
<option value='800'>{appearance.font.weight.extra_bold}</option>
|
||||
</Dropdown>
|
||||
<br/><br/>
|
||||
<Dropdown label={appearance.font.style.title} name='fontstyle' category='other'>
|
||||
<option value='normal'>{appearance.font.style.normal}</option>
|
||||
<option value='italic'>{appearance.font.style.italic}</option>
|
||||
<option value='oblique'>{appearance.font.style.oblique}</option>
|
||||
</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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
114
src/components/modals/main/settings/sections/Changelog.jsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import Lightbox from '../../marketplace/Lightbox';
|
||||
|
||||
import WifiOffIcon from '@material-ui/icons/WifiOff';
|
||||
|
||||
export default class Changelog extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
title: null,
|
||||
showLightbox: false,
|
||||
lightboxImg: null
|
||||
};
|
||||
this.language = window.language.modals.update;
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
|
||||
async getUpdate() {
|
||||
const data = await (await fetch(window.constants.BLOG_POST + '/index.json', { signal: this.controller.signal })).json();
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
let date = new Date(data.date);
|
||||
date = date.toLocaleDateString(window.languagecode.replace('_', '-'), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
this.setState({
|
||||
title: data.title,
|
||||
date: date,
|
||||
image: data.featured_image || null,
|
||||
author: 'By ' + data.authors.join(', '),
|
||||
html: data.html
|
||||
});
|
||||
|
||||
// lightbox etc
|
||||
const content = document.querySelector('.tab-content');
|
||||
const images = content.getElementsByTagName('img');
|
||||
const links = content.getElementsByTagName('a');
|
||||
|
||||
for (const img of images) {
|
||||
img.draggable = false;
|
||||
img.onclick = () => {
|
||||
this.setState({
|
||||
showLightbox: true,
|
||||
lightboxImg: img.src
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
for (let link = 0; link < links.length; link++) {
|
||||
links[link].target = '_blank';
|
||||
links[link].rel = 'noopener noreferrer';
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getUpdate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// stop making requests
|
||||
this.controller.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
const errorMessage = (msg) => {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
{msg}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
const language = window.language.modals.main.marketplace;
|
||||
|
||||
return errorMessage(<>
|
||||
<WifiOffIcon/>
|
||||
<h1>{language.offline.title}</h1>
|
||||
<p className='description'>{language.offline.description}</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
if (!this.state.title) {
|
||||
return errorMessage(<h1>{window.language.modals.main.loading}</h1>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='changelogtab'>
|
||||
<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}>
|
||||
<Lightbox modalClose={() => this.setState({ showLightbox: false })} img={this.state.lightboxImg}/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import Checkbox from '../Checkbox';
|
||||
import Slider from '../Slider';
|
||||
|
||||
export default function ExperimentalSettings() {
|
||||
const { experimental } = window.language.modals.main.settings.sections;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{experimental.title}</h2>
|
||||
<p>{experimental.warning}</p>
|
||||
<Checkbox name='animations' text={window.language.modals.main.settings.sections.appearance.animations} element='.other'/>
|
||||
<h3>{experimental.developer}</h3>
|
||||
<Checkbox name='debug' text='Debug hotkey (Ctrl + #)' element='.other'/>
|
||||
<Slider title='Debug timeout' name='debugtimeout' min='0' max='5000' default='0' step='100' display=' miliseconds' element='.other' />
|
||||
<br/><br/>
|
||||
<button className='reset' style={{'marginLeft': '0px'}} onClick={() => localStorage.clear()}>Clear LocalStorage</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||