Compare commits
444 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8e921e3c12 | ||
|
|
7e2772812c | ||
|
|
9bd4f99006 | ||
|
|
0dbc586952 | ||
|
|
e09fc3f99f | ||
|
|
7dbd4976b7 | ||
|
|
66414461c4 | ||
|
|
2dd0e9150b | ||
|
|
d1b998da12 | ||
|
|
e76594f426 | ||
|
|
6553c49fbe | ||
|
|
7b256fc956 | ||
|
|
7cc88449d7 | ||
|
|
8aeb645046 | ||
|
|
f50c32778c | ||
|
|
ed38bc2cbb | ||
|
|
e8c5f546cd | ||
|
|
d3ce835956 | ||
|
|
c858734d7f | ||
|
|
97a76bd0d7 | ||
|
|
ee4e49a058 | ||
|
|
150eb06cd0 | ||
|
|
7b71d0759e | ||
|
|
f38749961b | ||
|
|
b5e5b8618e | ||
|
|
cec368ab5f | ||
|
|
9db5150b07 | ||
|
|
4dfe05ac8a | ||
|
|
97f680d738 | ||
|
|
8b79f82e29 | ||
|
|
89a8621de1 | ||
|
|
a27cf2eede | ||
|
|
3399705935 | ||
|
|
a1963fb31b | ||
|
|
1a12f487e7 | ||
|
|
f014a79065 | ||
|
|
4fbcc19ff8 | ||
|
|
7dca1c9de9 | ||
|
|
1249ce45d3 | ||
|
|
9387357854 | ||
|
|
05c4a2c30c | ||
|
|
70cf17140b | ||
|
|
9969a4db10 | ||
|
|
ec9429adcb | ||
|
|
7dafd2c08d | ||
|
|
ad4fffb519 | ||
|
|
4715bb6144 | ||
|
|
2dc682cd14 | ||
|
|
3cfac7418b | ||
|
|
3f540b7772 | ||
|
|
003f1e62b2 | ||
|
|
320ae1c922 | ||
|
|
b6f74d4305 | ||
|
|
0a670de8f1 | ||
|
|
88efb6356a | ||
|
|
254dec9e8f | ||
|
|
c35aedbd2d | ||
|
|
97ed47b8cd | ||
|
|
6f046d144f | ||
|
|
088008165d | ||
|
|
d7e0091136 | ||
|
|
7a443c16bf | ||
|
|
b5e55fd331 | ||
|
|
5016cf3e69 | ||
|
|
a672c60afd | ||
|
|
aa5513a5dd | ||
|
|
291e7bcb82 | ||
|
|
4c9c31c90b | ||
|
|
5a15ce3cd8 | ||
|
|
8f3fa2f898 | ||
|
|
afb0f5c061 | ||
|
|
3a0468dcb9 | ||
|
|
f56854127c | ||
|
|
66980a4c62 | ||
|
|
259a36fd5d | ||
|
|
8869e53daa | ||
|
|
92495ce6ce | ||
|
|
736a589b59 | ||
|
|
2814174a38 | ||
|
|
dec9475a75 | ||
|
|
7253a174a6 | ||
|
|
348d3e8578 |
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
|
||||
|
||||
132
README.md
@@ -1,7 +1,7 @@
|
||||
<img src="https://raw.githubusercontent.com/mue/branding/master/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) []()
|
||||
<br>
|
||||
@@ -14,39 +14,41 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
|
||||
## 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)
|
||||
* [Edge Chromium](#edge-chromium)
|
||||
* [Naver](#naver)
|
||||
* [Other](#other)
|
||||
* [Contributing](#development)
|
||||
* [Requirements](#requirements)
|
||||
* [Starting](#starting)
|
||||
* [Building](#building)
|
||||
* [Credits](#credits)
|
||||
* [Maintainers](#maintainers)
|
||||
* [Contributors](#contributors)
|
||||
* [Translators](#translators)
|
||||
* [Other](#other)
|
||||
|
||||
## Screenshot
|
||||
*May be updated in the future*
|
||||
|
||||

|
||||
* [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)
|
||||
|
||||
## 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>
|
||||
@@ -58,52 +60,43 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
|
||||
[Firefox Add-ons](https://addons.mozilla.org/firefox/addon/mue)
|
||||
|
||||
### Edge (Chromium)
|
||||
[Microsoft Edge Addons](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja)
|
||||
~~[Microsoft Edge Addons](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja)~~ Currently outdated, please use the Chrome Web Store version
|
||||
|
||||
### Naver
|
||||
[Whale Store](https://store.whale.naver.com/detail/ecllekeilcmicbfkkiknfdddbogibbnc)
|
||||
|
||||
### Other
|
||||
Please note that we have dropped support for Opera as of Mue 5.0
|
||||
|
||||
### 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>
|
||||
## Development
|
||||
### Requirements
|
||||
* [Git](https://git-scm.com/)
|
||||
* [Node.JS](https://nodejs.org/)
|
||||
* A suitable browser
|
||||
### Starting
|
||||
1. Clone the repository using `git clone https://github.com/mue/mue.git`
|
||||
2. Run `yarn` or `npm i` to install all needed dependencies
|
||||
3. Run `yarn start` or `npm start` to start testing
|
||||
4. Code your heart out! (See the sections below for how to build the extension)
|
||||
### Building
|
||||
<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> Rename <code>manifest-chrome.json</code> in the "manfiest" folder to <code>manifest.json</code> in "build"
|
||||
<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> Rename <code>manifest-opera.json</code> in the "manfiest" folder to <code>manifest.json</code> in "build"
|
||||
<li> Copy <code>background-opera.js</code> in the "manifest" folder to "build"
|
||||
<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!
|
||||
</ol>
|
||||
</details>
|
||||
<details>
|
||||
<summary><b>Firefox</b> (Click to expand)</summary>
|
||||
<ol>
|
||||
<li> <code>yarn run build</code> or <code>npm run build</code>
|
||||
<li> Rename <code>manifest-firefox.json</code> in the "manfiest" folder to <code>manifest.json</code> in "build"
|
||||
<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>
|
||||
@@ -113,31 +106,30 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
|
||||
<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>
|
||||
<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>
|
||||
|
||||
### 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
|
||||
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)
|
||||
[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 [Highholding](https://discord.bio/p/highholding), [Noa Shapira](#), [Roee Lupo](https://github.com/RoeeLupo), [Jeroen](#), [Gio](#), [Anders](https://github.com/FuryingFox), [Oded Shapira](https://twitter.com/dondishdev), Jacob Tyrrell and [Nikka Lai](#) for letting us use their wonderful photographs.
|
||||
|
||||
### Other
|
||||
[Pexels](https://pexels.com) - Stock photos used for offline mode
|
||||
[Opera Forum](https://forums.opera.com/topic/25046/how-to-disable-completely-the-speed-dial/14) - Portions of code to add Opera support
|
||||
[Google Fonts](https://fonts.google.com/specimen/Lexend+Deca) - Lexend Deca font
|
||||
|
||||
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
|
||||
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: 276 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,18 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"offline_enabled": true,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for most modern browsers.",
|
||||
"version": "4.0.0",
|
||||
"browser_action": {
|
||||
"default_icon": "./icons/extension-icon.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"icons": {
|
||||
"16": "./icons/16x16-circle.png",
|
||||
"48": "./icons/48x48-circle.png",
|
||||
"128": "./icons/128x128-circle.png"
|
||||
}
|
||||
}
|
||||
"manifest_version": 2,
|
||||
"offline_enabled": true,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"version": "5.0.1",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for most modern browsers.",
|
||||
"version": "4.0.0",
|
||||
"browser_action": {
|
||||
"default_icon": "./icons/extension-icon.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.0.1",
|
||||
"browser_action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "index.html"
|
||||
},
|
||||
"chrome_settings_overrides": {
|
||||
"homepage": "index.html"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.0.0",
|
||||
"browser_action": {
|
||||
"default_icon": "./icons/extension-icon.png"
|
||||
},
|
||||
"background": {
|
||||
"scripts": [
|
||||
"./background-opera.js"
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
"tabs"
|
||||
]
|
||||
}
|
||||
125
package.json
@@ -1,55 +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.0.0",
|
||||
"dependencies": {
|
||||
"@eartharoid/dtf": "^1.0.7",
|
||||
"@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.1",
|
||||
"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.0.1",
|
||||
"dependencies": {
|
||||
"@fontsource/lexend-deca": "^4.2.3",
|
||||
"@fontsource/montserrat": "^4.2.2",
|
||||
"@material-ui/core": "4.11.4",
|
||||
"@material-ui/icons": "4.11.2",
|
||||
"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.13.1",
|
||||
"react-sortable-hoc": "2.0.0",
|
||||
"react-toastify": "7.0.4",
|
||||
"weather-icons-react": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.13.16",
|
||||
"@babel/eslint-parser": "^7.13.14",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.13.13",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-transform-react-class-to-function": "^1.2.2",
|
||||
"copy-webpack-plugin": "^8.1.1",
|
||||
"css-loader": "^5.2.4",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-config-react-app": "^6.0.0",
|
||||
"eslint-webpack-plugin": "^2.5.4",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"mini-css-extract-plugin": "^1.5.1",
|
||||
"sass": "^1.32.11",
|
||||
"sass-loader": "^11.0.1",
|
||||
"source-map-loader": "^2.0.1",
|
||||
"webpack": "^5.36.0",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
BIN
public/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 673 B |
BIN
public/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 645 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
BIN
public/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/></svg>
|
||||
|
Before Width: | Height: | Size: 607 B |
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 |
@@ -3,14 +3,36 @@
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='./icons/favicon-32x32.png'>
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='./icons/favicon-16x16.png'>
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='./icons/32x32.png'>
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='./icons/16x16.png'>
|
||||
<title>New Tab</title>
|
||||
</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 |
142
src/App.jsx
@@ -1,120 +1,46 @@
|
||||
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 Navbar from './components/Navbar';
|
||||
import SettingsFunctions from './modules/settingsFunctions';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
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>;
|
||||
import Background from './components/widgets/background/Background';
|
||||
import Widgets from './components/widgets/Widgets';
|
||||
import Modals from './components/modals/Modals';
|
||||
|
||||
import EventBus from './modules/helpers/eventbus';
|
||||
import SettingsFunctions from './modules/helpers/settings';
|
||||
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
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')}.json`);
|
||||
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
let 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)
|
||||
if (!localStorage.getItem('order')) {
|
||||
SettingsFunctions.moveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
SettingsFunctions.loadSettings();
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'other') {
|
||||
SettingsFunctions.loadSettings(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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'>
|
||||
<h1 id='photographer'>{language.credit}</h1>
|
||||
<div id='backgroundCredits' className='tooltip'>
|
||||
<RoomIcon className='locationicon'/>
|
||||
<span className='tooltiptext' id='location'/>
|
||||
</div>
|
||||
</div>
|
||||
<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}
|
||||
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,28 +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 = '';
|
||||
|
||||
const viewedUpdate = localStorage.getItem('viewedUpdate');
|
||||
let update = <NewReleases className='refreshicon' onClick={this.props.updateModalOpen} />;
|
||||
if (viewedUpdate === 'false') update = <NewReleases className='refreshicon' onClick={this.props.updateModalOpen} />
|
||||
|
||||
return (
|
||||
<div className='navbar-container'>
|
||||
<div className='navbar1'>
|
||||
<Gear className='settings-icon' onClick={this.props.settingsModalOpen} />
|
||||
</div>
|
||||
{refreshHTML}
|
||||
<div className={refresh === 'false' ? 'navbar2' : 'navbar3'}>
|
||||
{update}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
12
src/components/helpers/tooltip/Tooltip.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
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,143 +0,0 @@
|
||||
import React from 'react';
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
import { toast } from 'react-toastify';
|
||||
import dtf from '@eartharoid/dtf';
|
||||
import Item from './marketplace/Item';
|
||||
import MarketplaceFunctions from '../../modules/marketplaceFunctions';
|
||||
|
||||
export default class Addons extends React.PureComponent {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.state = {
|
||||
installed: [],
|
||||
item_data: {
|
||||
name: 'Name',
|
||||
author: 'Author',
|
||||
description: 'Description',
|
||||
updated: '???',
|
||||
version: '1.0.0',
|
||||
icon: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async 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: dtf('n_D MMM YYYY', info.updated, 'en-GB'),
|
||||
version: info.data.version,
|
||||
icon: info.data.screenshot_url
|
||||
}
|
||||
});
|
||||
document.getElementById('marketplace').style.display = 'none';
|
||||
document.getElementById('item').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('marketplace').style.display = 'block';
|
||||
document.getElementById('item').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
|
||||
const uninstallStuff = () => {
|
||||
for (let i = 0; i < installed.length; i++) {
|
||||
if (installed[i].name === this.state.current_data.name) {
|
||||
installed.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
localStorage.setItem('installed', JSON.stringify(installed));
|
||||
toast(this.props.toastLanguage.removed);
|
||||
this.toggle();
|
||||
this.componentDidMount();
|
||||
}
|
||||
|
||||
switch (this.state.current_data.type) {
|
||||
case 'settings':
|
||||
let oldSettings = JSON.parse(localStorage.getItem('backup_settings'));
|
||||
localStorage.clear();
|
||||
oldSettings.forEach(item => localStorage.setItem(item.name, item.value));
|
||||
uninstallStuff();
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
localStorage.removeItem(this.state.current_data.type);
|
||||
uninstallStuff();
|
||||
} catch (e) {
|
||||
console.log('invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
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() {
|
||||
if (this.state.installed.length === 0) {
|
||||
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()}>Sideload</button> */}
|
||||
<h1>{this.props.language.added}</h1>
|
||||
<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>
|
||||
</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()}>Sideload</button> */}
|
||||
<h1>{this.props.language.added}</h1>
|
||||
<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>
|
||||
</div>
|
||||
<Item button={<button className="removeFromMue" onClick={() => this.uninstall()}>{this.props.language.product.buttons.remove}</button>} data={this.state.item_data} function={()=> this.toggle()} language={this.props.language.product} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
37
src/components/modals/ErrorBoundary.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
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);
|
||||
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,265 +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 dtf from '@eartharoid/dtf';
|
||||
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 fetch(`${Constants.MARKETPLACE_URL}/item/${type2}/${data}`);
|
||||
info = await info.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: dtf('n_D MMM YYYY', info.updated, 'en-GB'),
|
||||
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>;
|
||||
let 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() {
|
||||
let data = await fetch(Constants.MARKETPLACE_URL + '/all');
|
||||
data = await data.json();
|
||||
let data2 = await fetch(Constants.MARKETPLACE_URL + '/featured');
|
||||
data2 = await data2.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: data2.data,
|
||||
done: true
|
||||
});
|
||||
}
|
||||
|
||||
async 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':
|
||||
localStorage.setItem('quote_packs', JSON.stringify(this.state.current_data.content.data.quotes));
|
||||
installStuff();
|
||||
break;
|
||||
default:
|
||||
console.log('invalid');
|
||||
}
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
let installed = JSON.parse(localStorage.getItem('installed'));
|
||||
let button;
|
||||
|
||||
const uninstallStuff = () => {
|
||||
for (let i = 0; i < installed.length; i++) {
|
||||
if (installed[i].name === this.state.current_data.name) {
|
||||
installed.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
localStorage.setItem('installed', JSON.stringify(installed));
|
||||
toast(this.props.toastLanguage.removed);
|
||||
button = <button className="addToMue" onClick={() => this.install()}>{this.props.language.product.buttons.addtomue}</button>;
|
||||
this.setState({ button: button });
|
||||
}
|
||||
|
||||
switch (this.state.current_data.type) {
|
||||
case 'settings':
|
||||
let oldSettings = JSON.parse(localStorage.getItem('backup_settings'));
|
||||
localStorage.clear();
|
||||
oldSettings.forEach(item => localStorage.setItem(item.name, item.value));
|
||||
uninstallStuff();
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
localStorage.removeItem(this.state.current_data.type);
|
||||
uninstallStuff();
|
||||
} catch (e) {
|
||||
console.log('invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 || this.state.done === false) {
|
||||
return this.offlineHTML;
|
||||
}
|
||||
|
||||
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)}
|
||||
seeMore={true}
|
||||
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)}
|
||||
seeMore={true}
|
||||
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)}
|
||||
seeMore={true}
|
||||
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)}
|
||||
seeMore={true}
|
||||
seeMoreFunction={() => this.toggle('seemore', 'themes')} />
|
||||
</div>
|
||||
<Item button={this.state.button} data={this.state.item_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>;
|
||||
}
|
||||
}
|
||||
61
src/components/modals/Modals.jsx
Normal file
@@ -0,0 +1,61 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
// hide refresh reminder once the user has refreshed the page
|
||||
localStorage.setItem('showReminder', false);
|
||||
}
|
||||
|
||||
closeWelcome() {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
this.setState({
|
||||
welcomeModal: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Navbar openModal={(modal) => this.setState({ [modal]: true })}/>
|
||||
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.setState({ mainModal: false })} isOpen={this.state.mainModal} className='Modal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Main modalClose={() => this.setState({ mainModal: false })}/>
|
||||
</Modal>
|
||||
<React.Suspense fallback={renderLoader()}>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Welcome modalClose={() => this.closeWelcome()}/>
|
||||
</Modal>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.setState({ feedbackModal: false })} isOpen={this.state.feedbackModal} className='Modal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Feedback modalClose={() => this.setState({ feedbackModal: false })}/>
|
||||
</Modal>
|
||||
</React.Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
import React from 'react';
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||
import SettingsFunctions from '../../modules/settingsFunctions';
|
||||
import Checkbox from './settings/Checkbox';
|
||||
import Slider from './settings/Slider';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default class Settings extends React.PureComponent {
|
||||
resetItem(key) {
|
||||
switch (key) {
|
||||
case 'greetingName': document.getElementById('greetingName').value = ''; break;
|
||||
case 'customBackgroundColour':
|
||||
localStorage.setItem('customBackgroundColour', '');
|
||||
document.getElementById('customBackgroundColour').enabled = 'false';
|
||||
break;
|
||||
case 'customBackground': document.getElementById('customBackground').value = ''; break;
|
||||
case 'blur':
|
||||
localStorage.setItem('blur', 0);
|
||||
document.getElementById('blurRange').value = 0;
|
||||
document.getElementById('blurAmount').innerText = '0';
|
||||
break;
|
||||
case 'customSearchEngine': document.getElementById('searchEngine').value = 'DuckDuckGo'; break;
|
||||
default: console.log('[ERROR] resetItem requires a key!');
|
||||
}
|
||||
toast(this.props.toastLanguage.reset);
|
||||
}
|
||||
|
||||
updateCurrent() {
|
||||
document.getElementById('greetingName').value = localStorage.getItem('greetingName');
|
||||
document.getElementById('customBackground').value = localStorage.getItem('customBackground');
|
||||
document.getElementById('backgroundAPI').value = localStorage.getItem('backgroundAPI');
|
||||
document.getElementById('language').value = localStorage.getItem('language');
|
||||
document.getElementById('searchEngine').value = localStorage.getItem('searchEngine');
|
||||
// document.getElementById('backgroundImage').style.backgroundUrl = localStorage.getItem('customBackground');
|
||||
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
let value = localStorage.getItem(key);
|
||||
|
||||
if (key === 'blur') {
|
||||
document.getElementById('blurAmount').innerText = value;
|
||||
document.getElementById('blurRange').value = value;
|
||||
}
|
||||
|
||||
const tag = document.getElementById(`${key}Status`);
|
||||
|
||||
if (tag) {
|
||||
switch (value) {
|
||||
case 'true': value = true; break;
|
||||
case 'false': value = false; break;
|
||||
default: value = true;
|
||||
}
|
||||
tag.checked = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (localStorage.getItem('darkTheme') === 'true') {
|
||||
document.getElementById('blurRange').style.background = '#535c68';
|
||||
document.getElementById('customBackground').style.color = 'white';
|
||||
document.getElementById('backgroundAPI').style.color = 'white';
|
||||
document.getElementById('searchEngine').style.color = 'white';
|
||||
document.getElementById('language').style.color = 'white';
|
||||
/*[1, 2, 3, 4, 5].forEach((index) => {
|
||||
console.log(index)
|
||||
document.getElementsByClassName('choices')[index].style.background = 'black';
|
||||
})*/
|
||||
document.getElementById('greetingName').style.color = 'white';
|
||||
}
|
||||
}
|
||||
|
||||
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('Imported settings!');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('bg-input').onchange = (e) => {
|
||||
const allowed = [ 'image/svg+xml', 'image/jpeg', 'image/png', 'image/webp', 'image/webm', 'image/gif' ];
|
||||
const reader = new FileReader();
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (file.size > 2000000) return toast('File is over 2MB', '#ff0000', '#ffffff');
|
||||
if (!allowed.includes(file.type)) return console.error(`expected xml, svg, png or jpeg, got ${file.type}`);
|
||||
|
||||
reader.addEventListener('load', (e) => {
|
||||
localStorage.setItem('customBackground', e.target.result);
|
||||
document.getElementById('customBackground').src = e.target.result;
|
||||
document.getElementById('customBackground').value = e.target.result;
|
||||
document.getElementById('backgroundImage').setAttribute('style', `-webkit-filter:blur(${localStorage.getItem('blur')}px); background-image: url(${localStorage.getItem('customBackground')}`);
|
||||
document.getElementById('backgroundImage').style.backgroundImage = `url(${localStorage.getItem('customBackground')})`
|
||||
});
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
/*const hex = localStorage.getItem('customBackgroundColour');
|
||||
if (!hex === '') {
|
||||
document.getElementById('customBackgroundColour').value = hex;
|
||||
document.getElementById('customBackgroundHex').innerText = hex;
|
||||
}*/
|
||||
|
||||
|
||||
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() {
|
||||
// let expandClassList = '';
|
||||
//if (localStorage.getItem('animations') === 'true') expandClassList = 'all 0.5 ease 0s';
|
||||
|
||||
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'>
|
||||
<div className='section'>
|
||||
<h4 onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[0], document.getElementsByClassName('expandIcons')[0])}>{this.props.language.time.title}</h4>
|
||||
<ExpandMore style={{ 'transition': 'all 0.5s ease 0s' }} className='expandIcons' onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[0], document.getElementsByClassName('expandIcons')[0])} />
|
||||
<Slider name='time' />
|
||||
<li className='extraSettings'>
|
||||
<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} />
|
||||
</li>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4 onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[1], document.getElementsByClassName('expandIcons')[1])}>{this.props.language.greeting.title}</h4>
|
||||
<ExpandMore style={{ 'transition': 'all 0.5s ease 0s' }} className='expandIcons' onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[1], document.getElementsByClassName('expandIcons')[1])} />
|
||||
<Slider name='greeting' />
|
||||
<li className='extraSettings'>
|
||||
<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.resetItem('greetingName')}>Reset</span></p>
|
||||
<input type='text' id='greetingName'></input>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4 onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[2], document.getElementsByClassName('expandIcons')[2])}>{this.props.language.quote.title}</h4>
|
||||
<ExpandMore style={{ 'transition': 'all 0.5s ease 0s' }} className='expandIcons' onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[2], document.getElementsByClassName('expandIcons')[2])} />
|
||||
<Slider name='quote' />
|
||||
<li className='extraSettings'>
|
||||
<Checkbox name='copyButton' text={this.props.language.quote.copy} />
|
||||
</li>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4 onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[3], document.getElementsByClassName('expandIcons')[3])}>{this.props.language.background.title}</h4>
|
||||
<ExpandMore style={{ 'transition': 'all 0.5s ease 0s' }} className='expandIcons' onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[3], document.getElementsByClassName('expandIcons')[3])} />
|
||||
<Slider name='background' override='customBackground' />
|
||||
<li className='extraSettings'>
|
||||
<ul>
|
||||
<label htmlFor='8'>{this.props.language.background.API} </label>
|
||||
<label className='dropdown'>
|
||||
<select className='select-css' name='8' id='backgroundAPI' onChange={() => localStorage.setItem('backgroundAPI', document.getElementById('backgroundAPI').value)}>
|
||||
<option value='mue'>Mue</option>
|
||||
<option value='unsplash'>Unsplash</option>
|
||||
{ /* <option value='custom'>Custom</option> */ }
|
||||
</select>
|
||||
</label>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{this.props.language.background.blur} (<span id='blurAmount'></span>%) <span className='modalLink' onClick={() => this.resetItem('blur')}>Reset</span></p>
|
||||
<input className='range' type='range' min='0' max='100' id='blurRange' onInput={() => document.getElementById('blurAmount').innerText = document.getElementById('blurRange').value} />
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{this.props.language.background.customURL} <span className='modalLink' onClick={() => this.resetItem('customBackground')}>Reset</span></p>
|
||||
<input type='text' id='customBackground'></input>
|
||||
</ul>
|
||||
<ul>
|
||||
<p>{this.props.language.background.custombackground} <span className='modalLink' onClick={() => this.resetItem('customBackground')}>Reset</span></p>
|
||||
<button className='uploadbg' onClick={() => document.getElementById('bg-input').click()}>Upload</button>
|
||||
<input id='bg-input' type='file' name='name' className='hidden' />
|
||||
</ul>
|
||||
{ /* <ul>
|
||||
<p>{this.props.language.background.customcolour} <span className='modalLink' onClick={() => this.resetItem('customBackgroundColour')}>Reset</span></p>
|
||||
<input name='colour' type='color' id='customBackgroundColour' onChange={() => document.getElementById('customBackgroundHex').innerText = document.getElementById('customBackgroundColour').value}></input>
|
||||
<label htmlFor='colour' id='customBackgroundHex'>#00000</label>
|
||||
</ul> */ }
|
||||
</li>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4 onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[4], document.getElementsByClassName('expandIcons')[4])}>{this.props.language.searchbar.title}</h4>
|
||||
<ExpandMore style={{ 'transition': 'all 0.5s ease 0s' }} className='expandIcons' onClick={() => SettingsFunctions.toggleExtra(document.getElementsByClassName('extraSettings')[4], document.getElementsByClassName('expandIcons')[4])} />
|
||||
<Slider name='searchBar' />
|
||||
<li className='extraSettings'>
|
||||
<ul>
|
||||
<label htmlFor='4'>{this.props.language.searchbar.searchengine} </label>
|
||||
<select className='select-css' name='4' id='searchEngine' onChange={() => SettingsFunctions.setSearchEngine(document.getElementById('searchEngine').value)}>
|
||||
<option value='duckduckgo'>DuckDuckGo</option>
|
||||
<option value='google'>Google</option>
|
||||
<option value='bing'>Bing</option>
|
||||
<option value='yahoo'>Yahoo</option>
|
||||
<option value='ecosia'>Ecosia</option>
|
||||
<option value='yandex'>Yandex</option>
|
||||
<option value='qwant'>Qwant</option>
|
||||
<option value='ask'>Ask</option>
|
||||
<option value='startpage'>Startpage</option>
|
||||
{/* <option value='custom'>Custom</option> */}
|
||||
</select>
|
||||
</ul>
|
||||
<ul id='searchEngineInput' style={{ display: 'none' }}>
|
||||
<p style={{"marginTop": "0px"}}>Custom Search URL <span className='modalLink' onClick={() => this.resetItem('customSearchEngine')}>Reset</span></p>
|
||||
<input type='text' id='customSearchEngine'></input>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4>{this.props.language.offline}</h4>
|
||||
<Slider name='offlineMode' />
|
||||
</div>
|
||||
<h3>{this.props.language.experimental.title}</h3>
|
||||
<div className='section'>
|
||||
<h4>{this.props.language.experimental.webp}</h4>
|
||||
<Slider name='webp' />
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4>{this.props.language.experimental.dark}</h4>
|
||||
<Slider name='darkTheme' />
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4>{this.props.language.experimental.animations}</h4>
|
||||
<Slider name='animations' />
|
||||
</div>
|
||||
<div className='section'>
|
||||
<h4 htmlFor='9'>{this.props.language.language} </h4>
|
||||
<select className='select-css' name='9' id='language' onChange={() => localStorage.setItem('language', document.getElementById('language').value)}>
|
||||
<option className='choices' value='en'>English</option>
|
||||
<option className='choices' value='nl'>Nederlands</option>
|
||||
<option className='choices' value='fr'>Français</option>
|
||||
<option className='choices' value='no'>Norsk</option>
|
||||
<option className='choices' value='ru'>Russian</option>
|
||||
</select>
|
||||
</div>
|
||||
<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' />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +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: '26/08/2020',
|
||||
content: this.props.language.title
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
let data = await fetch(Constants.API_URL + '/getUpdate');
|
||||
data = await data.json();
|
||||
this.setState({
|
||||
title: data.title,
|
||||
content: data.content
|
||||
});
|
||||
} catch (e) { // If it fails, we send an error
|
||||
this.setState({
|
||||
title: this.props.language.error.title,
|
||||
content: this.props.language.error.description
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
localStorage.setItem('viewedUpdate', true);
|
||||
this.getUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className='updateContent'>
|
||||
<span className='closeModal' onClick={this.props.modalClose}>×</span>
|
||||
<h1 style={{ 'marginBottom':'-10px' }} dangerouslySetInnerHTML={{__html: this.state.title}}></h1>
|
||||
<h5 style={{ 'lineHeight':'0px' }}> By Mue • <span dangerouslySetInnerHTML={{__html: this.state.date}}></span></h5>
|
||||
<p dangerouslySetInnerHTML={{__html: this.state.content}}></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="./././icons/iconmonstr-twitter.svg"/>
|
||||
<img alt="discord" href="https://discord.gg/kJsufA9" className="icon" src="https://cdn.discordapp.com/attachments/252071498397777921/747860127171739758/Discord-Logo-Black.svg"/>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
75
src/components/modals/main/marketplace/Item.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
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;
|
||||
|
||||
let warningHTML;
|
||||
// For some reason it breaks sometimes so we use try/catch
|
||||
try {
|
||||
if (this.props.content.content.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>
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// prevent console error
|
||||
let iconsrc = window.constants.DDG_PROXY + this.props.data.icon;
|
||||
if (!this.props.data.icon) {
|
||||
iconsrc = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='item' style={{ 'display': this.props.display }}>
|
||||
<br/>
|
||||
<span><ArrowBackIcon className='backArrow' onClick={this.props.toggleFunction}/></span>
|
||||
<br/>
|
||||
<h1>{this.props.data.display_name}</h1>
|
||||
{this.props.button}
|
||||
<br/>
|
||||
<img alt='product' draggable='false' src={iconsrc} onClick={() => this.setState({ showLightbox: true })}/>
|
||||
<div className='informationContainer'>
|
||||
<h1>{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>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
src/components/modals/main/marketplace/Items.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
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.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 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Lightbox(props) {
|
||||
return (
|
||||
<>
|
||||
<span className='closeModal' onClick={props.modalClose}>×</span>
|
||||
<img src={props.img} className='lightboximg' draggable={false} alt='Item'/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
101
src/components/modals/main/marketplace/sections/Added.jsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
|
||||
import LocalMallIcon from '@material-ui/icons/LocalMall';
|
||||
import Item from '../Item';
|
||||
import Items from '../Items';
|
||||
|
||||
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: {
|
||||
name: 'Name',
|
||||
author: 'Author',
|
||||
description: 'Description',
|
||||
//updated: '???',
|
||||
version: '1.0.0',
|
||||
icon: ''
|
||||
},
|
||||
button: '',
|
||||
display: {
|
||||
marketplace: 'block',
|
||||
item: 'none'
|
||||
}
|
||||
};
|
||||
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
|
||||
},
|
||||
button: this.buttons.uninstall,
|
||||
display: {
|
||||
marketplace: 'none',
|
||||
item: 'block'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
display: {
|
||||
marketplace: 'block',
|
||||
item: 'none'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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'))
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ 'display': this.state.display.marketplace }}>
|
||||
<Items items={this.state.installed} toggleFunction={(input) => this.toggle('item', input)} />
|
||||
</div>
|
||||
<Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} display={this.state.display.item} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
180
src/components/modals/main/marketplace/sections/Marketplace.jsx
Normal file
@@ -0,0 +1,180 @@
|
||||
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 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: {
|
||||
name: 'Name',
|
||||
author: 'Author',
|
||||
description: 'Description',
|
||||
//updated: '???',
|
||||
version: '1.0.0',
|
||||
icon: ''
|
||||
},
|
||||
display: {
|
||||
marketplace: 'block',
|
||||
item: 'none'
|
||||
}
|
||||
};
|
||||
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: this.props.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,
|
||||
display: {
|
||||
item: 'block',
|
||||
marketplace: 'none'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
display: {
|
||||
marketplace: 'block',
|
||||
item: 'none'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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],
|
||||
featured: featured.data,
|
||||
done: true
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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 errorMessage(<>
|
||||
<LocalMallIcon/>
|
||||
<h1>{window.language.modals.main.addons.empty.title}</h1>
|
||||
<p className='description'>{this.language.no_items}</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ 'display': this.state.display.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.open(this.state.featured.buttonLink)}>{this.state.featured.buttonText}</button>
|
||||
</div>
|
||||
<Items items={this.state.items} toggleFunction={(input) => this.toggle('item', input)} />
|
||||
</div>
|
||||
<Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} display={this.state.display.item} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/components/modals/main/marketplace/sections/Sideload.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
384
src/components/modals/main/scss/index.scss
Normal file
@@ -0,0 +1,384 @@
|
||||
@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;
|
||||
padding: 25px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.modalLink {
|
||||
color: var(--modal-link);
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
|
||||
&: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);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-height: 700px) {
|
||||
.ReactModal__Content {
|
||||
min-height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.modalNavbar {
|
||||
position: absolute;
|
||||
left: 20rem;
|
||||
top: 1rem;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
margin-right: 0.5rem;
|
||||
padding: 3px;
|
||||
font-size: 1.4em !important;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
svg {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: map-get($theme-colours, 'gradient');
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 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;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
209
src/components/modals/main/scss/marketplace/_main.scss
Normal file
@@ -0,0 +1,209 @@
|
||||
#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%;
|
||||
|
||||
img {
|
||||
max-height: 80%;
|
||||
max-width: 80%;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
99
src/components/modals/main/scss/settings/_buttons.scss
Normal file
@@ -0,0 +1,99 @@
|
||||
@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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
z-index: 999 !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
22
src/components/modals/main/scss/settings/_dropdown.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
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/gif;base64,R0lGODlhBgAGAKEDAFVVVX9/f9TU1CgmNyH5BAEKAAMALAAAAAAGAAYAAAIODA4hCDKWxlhNvmCnGwUAOw==') right center no-repeat, var(--sidebar) !important;
|
||||
background-position: calc(100% - 15px) center !important;
|
||||
}
|
||||
|
||||
option {
|
||||
font: -moz-pull-down-menu !important;
|
||||
}
|
||||
}
|
||||
209
src/components/modals/main/scss/settings/_main.scss
Normal file
@@ -0,0 +1,209 @@
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
53
src/components/modals/main/settings/Checkbox.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
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
|
||||
});
|
||||
|
||||
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/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
64
src/components/modals/main/settings/Dropdown.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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} />;
|
||||
}
|
||||
}
|
||||
54
src/components/modals/main/settings/Radio.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
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
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
24
src/components/modals/main/settings/ResetModal.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
import SettingsFunctions from '../../../../modules/helpers/settings';
|
||||
|
||||
export default function ResetModal(props) {
|
||||
const language = window.language.modals.main.settings.sections.advanced.reset_modal;
|
||||
|
||||
const reset = () => {
|
||||
SettingsFunctions.setDefaultSettings('reset');
|
||||
window.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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
68
src/components/modals/main/settings/Slider.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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) || '',
|
||||
numberWidth: ((localStorage.getItem(this.props.name).length + 1) * ((this.props.toast === true) ? 7.75 : 7))
|
||||
};
|
||||
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)
|
||||
});
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
resetItem = () => {
|
||||
localStorage.setItem(this.props.name, this.props.default);
|
||||
this.setState({
|
||||
value: this.props.default,
|
||||
numberWidth: ((this.props.default.length + 1) * this.widthCalculation)
|
||||
});
|
||||
|
||||
toast(window.language.toasts.reset);
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||