Compare commits
451 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5689556d9 | ||
|
|
5046f3aa07 | ||
|
|
ea5cc55179 | ||
|
|
84af2d54c1 | ||
|
|
c647aa53f5 | ||
|
|
b83b5ba4a9 | ||
|
|
047bb97edf | ||
|
|
5f1e4bd76c | ||
|
|
a4f9d38ea8 | ||
|
|
e383fef979 | ||
|
|
34cc878a50 | ||
|
|
4c290e5d81 | ||
|
|
a1d5d23a42 | ||
|
|
b2204382c8 | ||
|
|
ae8222412a | ||
|
|
40b6bf3376 | ||
|
|
13a8c33aa2 | ||
|
|
6626e5ad90 | ||
|
|
845756c080 | ||
|
|
a32d42a9e6 | ||
|
|
9a5bddcf6d | ||
|
|
1b00c9b158 | ||
|
|
b07ef04914 | ||
|
|
856310cf35 | ||
|
|
500fb337dc | ||
|
|
a0430849ce | ||
|
|
e10a517e9a | ||
|
|
fc20997bfe | ||
|
|
b274efda36 | ||
|
|
82625a3e3f | ||
|
|
f45a36dc03 | ||
|
|
8cd44cdd7e | ||
|
|
167641ccb7 | ||
|
|
32504b53d7 | ||
|
|
bfa545128d | ||
|
|
e88a2f59c9 | ||
|
|
b47949d791 | ||
|
|
d6276c947f | ||
|
|
788143f4e5 | ||
|
|
f7c7990ce9 | ||
|
|
83aa6ff341 | ||
|
|
f6f057d354 | ||
|
|
717137ddb7 | ||
|
|
5ed163b2d3 | ||
|
|
59c132875f | ||
|
|
166926e174 | ||
|
|
46c52c3ef1 | ||
|
|
fa5fd00de9 | ||
|
|
c81c01d484 | ||
|
|
f892c4c690 | ||
|
|
8d57ce050d | ||
|
|
bef5bfa22a | ||
|
|
259eab31c6 | ||
|
|
6d315974cc | ||
|
|
0313408b66 | ||
|
|
b8eabc8717 | ||
|
|
5665370b93 | ||
|
|
e79a04cb35 | ||
|
|
46a5f90152 | ||
|
|
ad39f4a732 | ||
|
|
6b05aecaa8 | ||
|
|
0990774b41 | ||
|
|
e006febdbf | ||
|
|
cfd096ecc3 | ||
|
|
f5d9ad11a5 | ||
|
|
510dc1c023 | ||
|
|
76f5bff3dd | ||
|
|
9bb65a9704 | ||
|
|
b275de6c67 | ||
|
|
fb1841a093 | ||
|
|
d11888daf0 | ||
|
|
24e14f5e74 | ||
|
|
2228176661 | ||
|
|
b21c2bd9ac | ||
|
|
ccf1c012c9 | ||
|
|
d8eaba9b3c | ||
|
|
4babf9a6e8 | ||
|
|
fb902c1ae1 | ||
|
|
3814010e7a | ||
|
|
ebf16c5b61 | ||
|
|
21c1c7b94a | ||
|
|
0fbb2754e7 | ||
|
|
038b1f85a7 | ||
|
|
2287e9817c | ||
|
|
4d9ec940ca | ||
|
|
e439a79970 | ||
|
|
2d6e50115b | ||
|
|
e4a634d78d | ||
|
|
9ff32e590f | ||
|
|
2fda989b05 | ||
|
|
6250683b0e | ||
|
|
30cd4caa33 | ||
|
|
474b6aaa84 | ||
|
|
810ea969d9 | ||
|
|
0d7668346f | ||
|
|
c870aaf5fd | ||
|
|
916d33abab | ||
|
|
0cee22255e | ||
|
|
a9d0a20459 | ||
|
|
7ce391790a | ||
|
|
db6f3bdf0b | ||
|
|
9a6aeae0db | ||
|
|
990b91d892 | ||
|
|
39e0193680 | ||
|
|
684fdce102 | ||
|
|
1f6bbaff32 | ||
|
|
2bdf7d99aa | ||
|
|
4f9f91f3b3 | ||
|
|
24a5c52097 | ||
|
|
fd9e0a6cfa | ||
|
|
14efe14dfe | ||
|
|
e43d909961 | ||
|
|
400cb4bbe2 | ||
|
|
1d78dec65f | ||
|
|
ac6d9bcdbe | ||
|
|
439bfd43c1 | ||
|
|
ac2daf42db | ||
|
|
eb9411586a | ||
|
|
cc328161f6 | ||
|
|
e683a96d40 | ||
|
|
dde79904cf | ||
|
|
3038f00c4d | ||
|
|
d48cc9d3b5 | ||
|
|
25c7218f1b | ||
|
|
d7b85b2009 | ||
|
|
37241289ee | ||
|
|
f65881f79a | ||
|
|
4caaa8c576 | ||
|
|
a6b56cf2e4 | ||
|
|
19dc41fde1 | ||
|
|
b64a58f280 | ||
|
|
602ea813f2 | ||
|
|
4920ae7b80 | ||
|
|
51dca6d107 | ||
|
|
f3c185495d | ||
|
|
babc978dcf | ||
|
|
93e6b10b0f | ||
|
|
2b8183474c | ||
|
|
c23291116c | ||
|
|
cbf4b41f15 | ||
|
|
f42d460c23 | ||
|
|
eeb63a2523 | ||
|
|
c8ff3f7945 | ||
|
|
f67a7b6f78 | ||
|
|
3c8af1ca82 | ||
|
|
cebce2d5ef | ||
|
|
7cf3dfaa79 | ||
|
|
7be46b9a95 | ||
|
|
41293f075a | ||
|
|
b1ac03977e | ||
|
|
155c4f4ff5 | ||
|
|
5639979c1b | ||
|
|
440d20d7be | ||
|
|
2815c143f1 | ||
|
|
de83db8130 | ||
|
|
63009971a8 | ||
|
|
63a9e8907c | ||
|
|
4a5cb65a37 | ||
|
|
c22ecea9d6 | ||
|
|
e161b82ad3 | ||
|
|
8035b35436 | ||
|
|
84b99f815a | ||
|
|
a2c45ae056 | ||
|
|
822a8ff627 | ||
|
|
668928cbff | ||
|
|
59aa73d333 | ||
|
|
70ccf0ff45 | ||
|
|
e338795a0f | ||
|
|
307f110c80 | ||
|
|
dc7ac55939 | ||
|
|
0d6397d724 | ||
|
|
907735f8c6 | ||
|
|
247b5b16d3 | ||
|
|
91f3a5d8bc | ||
|
|
14f10cf9c3 | ||
|
|
ac9a5db009 | ||
|
|
deea70a23a | ||
|
|
3cb69c6b6e | ||
|
|
5a4e8f1a7a | ||
|
|
53e8aba7f5 | ||
|
|
f24d1949ba | ||
|
|
0cd6d9dc42 | ||
|
|
b256387410 | ||
|
|
370bb5a92a | ||
|
|
a88d0e3cd7 | ||
|
|
04af2350b6 | ||
|
|
a7b040642d | ||
|
|
25a674cabb | ||
|
|
146c74e0cb | ||
|
|
b18113d468 | ||
|
|
20ba85a8bb | ||
|
|
dd9aabc971 | ||
|
|
ed180ce6a2 | ||
|
|
c5dcb2937d | ||
|
|
a5292a26e2 | ||
|
|
d8d47fc238 | ||
|
|
ad6619b3b7 | ||
|
|
f70c9227ef | ||
|
|
7a55a6a430 | ||
|
|
1d151c277d | ||
|
|
acc6307a57 | ||
|
|
4da82dd820 | ||
|
|
333aabde1b | ||
|
|
f0968195cb | ||
|
|
5f89308f43 | ||
|
|
8931a4c15a | ||
|
|
8c8c5652ae | ||
|
|
713e0176c7 | ||
|
|
2684277a76 | ||
|
|
271af64d07 | ||
|
|
a9276a5fdb | ||
|
|
d762a7c504 | ||
|
|
a77097a7fa | ||
|
|
fadbb84b6e | ||
|
|
675f0d48bd | ||
|
|
05b787db5a | ||
|
|
0db2502498 | ||
|
|
a4c10bc3b3 | ||
|
|
3de74e4af9 | ||
|
|
acb5157b71 | ||
|
|
73501a4fb9 | ||
|
|
572725eaef | ||
|
|
bc45f98b51 | ||
|
|
66b3a10858 | ||
|
|
0205cdd8c4 | ||
|
|
60008e291e | ||
|
|
0efc6d4311 | ||
|
|
f1c5731005 | ||
|
|
8edd2ccd67 | ||
|
|
d5282ae64e | ||
|
|
8304c27fe2 | ||
|
|
482095ca65 | ||
|
|
90decae0f8 | ||
|
|
c987d1e070 | ||
|
|
34b1834cd7 | ||
|
|
2c511803a5 | ||
|
|
82bbd04cfb | ||
|
|
503474d486 | ||
|
|
cc2bd8bce9 | ||
|
|
8450d15be6 | ||
|
|
2d3e320375 | ||
|
|
48c94e4d30 | ||
|
|
6425cc1e1e | ||
|
|
3cc40fd15f | ||
|
|
252fe0aa9a | ||
|
|
75a15533d2 | ||
|
|
8c5c3fa52c | ||
|
|
6e713c5063 | ||
|
|
2c760038e0 | ||
|
|
ba9bab2b4d | ||
|
|
6cbc677183 | ||
|
|
b74b54f0c2 | ||
|
|
2cf00168eb | ||
|
|
e34ae8878d | ||
|
|
0ab4c4b63d | ||
|
|
2e6bf4c9c7 | ||
|
|
b364923738 | ||
|
|
1986a57662 | ||
|
|
532f7cff8c | ||
|
|
d8a6ba7101 | ||
|
|
cdd3a99ab7 | ||
|
|
9c271d1b2a | ||
|
|
517091cbda | ||
|
|
68783429d4 | ||
|
|
14c33eac1e | ||
|
|
59aa1f7164 | ||
|
|
8fd5b47545 | ||
|
|
7880489034 | ||
|
|
dc8abfe436 | ||
|
|
99569f3781 | ||
|
|
2563850fcc | ||
|
|
7279126bdc | ||
|
|
8ca70e7895 | ||
|
|
12f8cf6a90 | ||
|
|
77d68fc469 | ||
|
|
6a0722a697 | ||
|
|
bf1723e63c | ||
|
|
c2a7e5223b | ||
|
|
cbc2acff7a | ||
|
|
58d0469918 | ||
|
|
b97776cbb8 | ||
|
|
218f7bec7b | ||
|
|
b8f2b7fb1f | ||
|
|
2c2acbba46 | ||
|
|
cd311e0fa1 | ||
|
|
85933c4845 | ||
|
|
b25bf4f7a3 | ||
|
|
b93302153e | ||
|
|
484d16122c | ||
|
|
5bf313924a | ||
|
|
92ef43bcb6 | ||
|
|
f5a7328ed5 | ||
|
|
d7d131d4be | ||
|
|
e0d3686f5d | ||
|
|
8c6e62e1b4 | ||
|
|
5ddd26d492 | ||
|
|
70e068069d | ||
|
|
b26e07722f | ||
|
|
8613a71cf7 | ||
|
|
f701c9c146 | ||
|
|
3988055161 | ||
|
|
c77a37cbb8 | ||
|
|
ff49fa6d48 | ||
|
|
78bf7eca24 | ||
|
|
74c186ea08 | ||
|
|
c118cc5746 | ||
|
|
c81db8994d | ||
|
|
b8c9863a24 | ||
|
|
18a8c714ad | ||
|
|
a819ee9d36 | ||
|
|
bcde05d87d | ||
|
|
5f307409bb | ||
|
|
22ababfc0d | ||
|
|
99f653ab66 | ||
|
|
e9619382d4 | ||
|
|
4ee1e39a4a | ||
|
|
77a6bbc7c5 | ||
|
|
4db25439c9 | ||
|
|
c143f38a94 | ||
|
|
9f9f6d3644 | ||
|
|
12469a031e | ||
|
|
faa5080d64 | ||
|
|
bcee240993 | ||
|
|
72fd9f97da | ||
|
|
6f6b32207a | ||
|
|
30fe86a6df | ||
|
|
e3cadcb4e3 | ||
|
|
5b37478893 | ||
|
|
d245d4ba6a | ||
|
|
3725615262 | ||
|
|
7c7d7d526e | ||
|
|
45d5a3b9d7 | ||
|
|
cdda2abdd1 | ||
|
|
d21df83278 | ||
|
|
1a0ae2f59b | ||
|
|
c0166ceb0b | ||
|
|
f143dbdc1f | ||
|
|
7c7ace90c9 | ||
|
|
c022b56143 | ||
|
|
113fb6fbeb | ||
|
|
fc3340e374 | ||
|
|
9648b75f52 | ||
|
|
4a941e56d9 | ||
|
|
0b8bfe2cdc | ||
|
|
4e68a2c881 | ||
|
|
710ad50ab2 | ||
|
|
10c7bd98c2 | ||
|
|
9e061a8cb4 | ||
|
|
41678f6ac7 | ||
|
|
43bfd5deff | ||
|
|
215b294da4 | ||
|
|
e4dffc73d7 | ||
|
|
c9b61b862b | ||
|
|
c31e9bbcdb | ||
|
|
208929f846 | ||
|
|
61bed2b478 | ||
|
|
8c78a2722c | ||
|
|
7570dc6c52 | ||
|
|
3148a19685 | ||
|
|
7df77d33e4 | ||
|
|
f413f4e31c | ||
|
|
80723cbed1 | ||
|
|
109918a2c6 | ||
|
|
291e9774b4 | ||
|
|
d02b004dc7 | ||
|
|
433f9b81b8 | ||
|
|
cd9eed0c81 | ||
|
|
5f42b1a6ee | ||
|
|
c8a25fc92b | ||
|
|
2c89894372 | ||
|
|
4f29d376ca | ||
|
|
b070f4a1ce | ||
|
|
a16ffd1e0d | ||
|
|
645c592bd1 | ||
|
|
4416c336f5 | ||
|
|
bd05aa5ee0 | ||
|
|
1e8ab0bc8a | ||
|
|
f2247e0c67 | ||
|
|
be332b36aa | ||
|
|
cd708a9f9a | ||
|
|
c3df2d7b23 | ||
|
|
54c3245663 | ||
|
|
4a37a26733 | ||
|
|
c63620c649 | ||
|
|
9793152a71 | ||
|
|
39deb69c34 | ||
|
|
0745eac6c5 | ||
|
|
e4dd9d59f6 | ||
|
|
ed933e68e2 | ||
|
|
3a6950c1ba | ||
|
|
527fc951d3 | ||
|
|
c07e0a1951 | ||
|
|
84e9ead97a | ||
|
|
26bfab5114 | ||
|
|
f8d2d624b7 | ||
|
|
78c390b172 | ||
|
|
5385655b02 | ||
|
|
86cd8d55b2 | ||
|
|
3aa934c020 | ||
|
|
194e98256f | ||
|
|
024f4c81b4 | ||
|
|
8f25df9827 | ||
|
|
f8fbf9a7c7 | ||
|
|
11b952c3e7 | ||
|
|
4691ccf166 | ||
|
|
987a7eda6e | ||
|
|
ec602a0e50 | ||
|
|
73a82f4845 | ||
|
|
8e0511667a | ||
|
|
a699163b46 | ||
|
|
834c79a2fd | ||
|
|
af41947e33 | ||
|
|
98fed28706 | ||
|
|
7bb48ebc8e | ||
|
|
2c54fb0b20 | ||
|
|
4498f5b934 | ||
|
|
d2e6d6d319 | ||
|
|
db5bf0019c | ||
|
|
fae5e52f84 | ||
|
|
2bffab13a0 | ||
|
|
1f979cf22c | ||
|
|
f51c002f46 | ||
|
|
ceb583c8e7 | ||
|
|
361fae7f25 | ||
|
|
e0820c6b2a | ||
|
|
f58d74146d | ||
|
|
81b10d9795 | ||
|
|
faacd68e44 | ||
|
|
86ec24736f | ||
|
|
f219242966 | ||
|
|
5853925121 | ||
|
|
40ec029429 | ||
|
|
3d1ab88a25 | ||
|
|
6269a72df6 | ||
|
|
dc379563f1 | ||
|
|
482330c76b | ||
|
|
a3790094b8 | ||
|
|
66da158840 | ||
|
|
0a32b25bc8 | ||
|
|
66e22b05ef | ||
|
|
3044961de3 | ||
|
|
4857b2e17a | ||
|
|
deaaca537a | ||
|
|
17541d269f | ||
|
|
f82a283b19 | ||
|
|
6129f4c186 | ||
|
|
97756ca5e9 | ||
|
|
ac092ee2dc | ||
|
|
21e197a0d9 | ||
|
|
0548f6a3cf | ||
|
|
c6ce382188 |
3
.commitlintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
extends: 'react-app',
|
||||
parser: '@babel/eslint-parser'
|
||||
extends: ['react-app', 'prettier'],
|
||||
ignorePatterns: ['node_modules/', 'build/', 'coverage/', '*.scss', '*.css', '*.json'],
|
||||
};
|
||||
|
||||
2
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Automatically assigned to any PRs
|
||||
* @davidcralph @alexsparkes
|
||||
27
.github/workflows/automerge.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: automerge
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14.x"
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Build
|
||||
run: npm run build
|
||||
automerge:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: fastify/github-action-merge-dependabot@v3.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
27
.github/workflows/submit.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Submit
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag to submit, i.e 6.0.5"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Chrome
|
||||
uses: browser-actions/setup-chrome@latest
|
||||
with:
|
||||
chrome-version: latest
|
||||
- name: Download Github Release Assets
|
||||
uses: PlasmoHQ/download-release-asset@v1.0.0
|
||||
with:
|
||||
tag: ${{ github.event.inputs.tag }}
|
||||
- name: Browser Plugin Publish
|
||||
uses: PlasmoHQ/bpp@v2
|
||||
env:
|
||||
PUPPETEER_EXECUTABLE_PATH: /opt/hostedtoolcache/chromium/latest/x64/chrome
|
||||
with:
|
||||
keys: ${{ secrets.SUBMIT_KEYS }}
|
||||
8
.gitignore
vendored
@@ -1,12 +1,18 @@
|
||||
# Directories
|
||||
node_modules/
|
||||
.history/
|
||||
.vscode/
|
||||
build/
|
||||
.idea/
|
||||
dist/
|
||||
|
||||
# Files
|
||||
package-lock.json
|
||||
.stylelintcache
|
||||
yarn-error.log
|
||||
.eslintcache
|
||||
stats.json
|
||||
yarn.lock
|
||||
*.zip
|
||||
keys.json
|
||||
.DS_Store
|
||||
*.zip
|
||||
|
||||
4
.husky/commit-msg.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "${dirname "$0"}/_/husky/husky.sh"
|
||||
|
||||
npx commitlint --edit $1
|
||||
7
.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
9
.stylelintrc.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": ["stylelint-config-standard-scss"],
|
||||
"plugins": ["stylelint-scss"],
|
||||
"rules": {
|
||||
"selector-class-pattern": null,
|
||||
"no-descending-specificity": null,
|
||||
"scss/no-global-function-names": null
|
||||
}
|
||||
}
|
||||
5
LICENSE
@@ -1,6 +1,9 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018-2022 The Mue Authors
|
||||
Copyright (c) 2023- Kaiso One Ltd
|
||||
Copyright (c) 2019-2023 The Mue Authors
|
||||
Copyright (c) 2018-2019 David Ralph
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
83
README.md
@@ -12,73 +12,91 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
|
||||
<br>
|
||||
|
||||
## Table of contents
|
||||
* [Screenshots](#screenshot)
|
||||
* [Features](#features)
|
||||
* [Planned Features](#planned-features)
|
||||
* [Installation](#installation)
|
||||
* [Chrome](#chrome)
|
||||
* [Firefox](#firefox)
|
||||
* [Edge Chromium](#edge-chromium)
|
||||
* [Whale](#whale)
|
||||
* [Other](#other)
|
||||
* [Contributing](#development)
|
||||
* [Translations](#translations)
|
||||
* [Credits](#credits)
|
||||
* [Developers](#developers)
|
||||
* [Translators](#translators)
|
||||
* [Contributors](#contributors)
|
||||
* [Resources](#resources)
|
||||
- [Table of contents](#table-of-contents)
|
||||
- [Screenshots](#screenshots)
|
||||
- [Features](#features)
|
||||
- [Planned Features](#planned-features)
|
||||
- [Installation](#installation)
|
||||
- [Chrome](#chrome)
|
||||
- [Firefox](#firefox)
|
||||
- [Edge (Chromium)](#edge-chromium)
|
||||
- [Whale](#whale)
|
||||
- [Other](#other)
|
||||
- [Development](#development)
|
||||
- [Translations](#translations)
|
||||
- [Credits](#credits)
|
||||
- [Developers](#developers)
|
||||
- [Translators](#translators)
|
||||
- [Contributors](#contributors)
|
||||
- [Resources](#resources)
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
* Fast and free
|
||||
* Supports multiple browsers
|
||||
* Actively developed and open source
|
||||
* Automatically updating [API](https://github.com/mue/api) with new photos, quotes and offline mode
|
||||
* Widgets such as search bar, weather, quick links, clock, date, quote, greeting
|
||||
* Settings - enable/disable various features and customise parts of Mue
|
||||
* Navbar with copy button, favourite background, notes feature etc
|
||||
* [Marketplace](https://github.com/mue/marketplace) - download custom photo packs, quote packs and preset settings made by the community
|
||||
|
||||
- Fast and free
|
||||
- Supports multiple browsers
|
||||
- Actively developed and open source
|
||||
- Automatically updating [API](https://github.com/mue/api) with new photos, quotes and offline mode
|
||||
- Widgets such as search bar, weather, quick links, clock, date, quote, greeting
|
||||
- Settings - enable/disable various features and customise parts of Mue
|
||||
- Navbar with copy button, favourite background, notes feature etc
|
||||
- [Marketplace](https://github.com/mue/marketplace) - download custom photo packs, quote packs and preset settings made by the community
|
||||
|
||||
### Planned Features
|
||||
|
||||
Please see our [roadmap](https://trello.com/b/w7zhS7Hi/mue-50).
|
||||
|
||||
## Installation
|
||||
*A demo of the tab can be found [here](https://demo.muetab.com), and the latest GitHub commit build [here](https://mue.vercel.app)*
|
||||
|
||||
_A demo of the tab can be found [here](https://demo.muetab.com), and the latest GitHub commit build [here](https://mue.vercel.app)_
|
||||
|
||||
### Chrome
|
||||
|
||||
[](https://chrome.google.com/webstore/detail/mue/bngmbednanpcfochchhgbkookpiaiaid)
|
||||
<br>
|
||||
[Chrome Web Store](https://chrome.google.com/webstore/detail/mue/bngmbednanpcfochchhgbkookpiaiaid)
|
||||
|
||||
### Firefox
|
||||
|
||||
[](https://addons.mozilla.org/firefox/addon/mue)
|
||||
<br>
|
||||
[Firefox Add-ons](https://addons.mozilla.org/firefox/addon/mue)
|
||||
|
||||
### Edge (Chromium)
|
||||
|
||||
[Microsoft Edge Addons](https://microsoftedge.microsoft.com/addons/detail/aepnglgjfokepefimhbnibfjekidhmja)
|
||||
|
||||
### Whale
|
||||
|
||||
[Whale Store](https://store.whale.naver.com/detail/ecllekeilcmicbfkkiknfdddbogibbnc)
|
||||
|
||||
### Other
|
||||
|
||||
[GitHub Releases](https://github.com/mue/mue/releases)
|
||||
|
||||
## Development
|
||||
|
||||
Please see the [documentation](https://docs.muetab.com/development#mue-tab).
|
||||
|
||||
### Translations
|
||||
Please see the [documentation](https://docs.muetab.com/translations).
|
||||
|
||||
[](https://hosted.weblate.org/engage/mue/)
|
||||
|
||||
## Credits
|
||||
|
||||
### Developers
|
||||
|
||||
[David Ralph](https://github.com/davidcralph) - Lead development, photographer <br/>
|
||||
[Alex Sparkes](https://github.com/alexsparkes) - Name, lead design, photographer <br/>
|
||||
[Isaac Saunders](https://github.com/eartharoid) - QA, development, photographer <br/>
|
||||
[Wessel Tip](https://github.com/Wessel) - Development <br/>
|
||||
|
||||
### Translators
|
||||
|
||||
[Wessel Tip](https://github.com/Wessel), [Heimen Stoffels](https://github.com/Vistaus) - Dutch <br/>
|
||||
[Alex Sparkes](https://github.com/alexsparkes), [Maxime](https://github.com/exiam) - French <br/>
|
||||
[Anders](https://github.com/FuryingFox) - Norwegian <br/>
|
||||
@@ -86,10 +104,23 @@ Please see the [documentation](https://docs.muetab.com/translations).
|
||||
[Vicente](https://github.com/Vicente015) - Spanish <br/>
|
||||
[Austin Huang](https://github.com/austinhuang0131) - Chinese (Simplified) <br/>
|
||||
[FreeFun](https://github.com/xXFreeFunXx) - German <br/>
|
||||
[Aksal](https://github.com/aksalsf) - Indonesian <br/>
|
||||
[Kağan Can Şit](https://github.com/KaganCanSit) - Turkish <br/>
|
||||
efeaydal - Turkish <br/>
|
||||
### Contributors
|
||||
|
||||
Many thanks to the photographers [here](https://api.muetab.com/images/photographers) for letting us use their wonderful photographs.
|
||||
|
||||
And finally, a big thank you to all the other [contributors](https://github.com/mue/mue/graphs/contributors)!
|
||||
|
||||
### Resources
|
||||
|
||||
[Pexels](https://pexels.com), [Unsplash](https://unsplash.com) - Stock photos used for offline mode <br/>
|
||||
[Undraw](https://undraw.co) - Welcome modal images
|
||||
|
||||
<p>This project is supported by:</p>
|
||||
<p>
|
||||
<a href="https://www.digitalocean.com/">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 76 KiB |
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env', ['@babel/preset-react', {
|
||||
runtime: 'automatic'
|
||||
}]],
|
||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', '@babel/plugin-transform-react-inline-elements', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
|
||||
};
|
||||
40
index.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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/32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="./icons/16x16.png" />
|
||||
<title>New Tab</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
*,
|
||||
a {
|
||||
font-family: 'Lexend Deca', sans-serif;
|
||||
text-align: center;
|
||||
color: black;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
*,
|
||||
a {
|
||||
color: white;
|
||||
background: #2f3640 !important;
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
7
jsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6"
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
8
manifest/_locales/tr_TR/messages.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Mue"
|
||||
},
|
||||
"description": {
|
||||
"message": "Modern tarayıcılar için hızlı, açık ve kullanımı ücretsiz yeni sekme sayfası."
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ chrome.runtime.setUninstallURL('https://muetab.com/uninstall');
|
||||
chrome.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === 'install') {
|
||||
chrome.tabs.create({
|
||||
url: chrome.runtime.getURL('index.html')
|
||||
url: chrome.runtime.getURL('index.html'),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ browser.runtime.setUninstallURL('https://muetab.com/uninstall');
|
||||
browser.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === 'install') {
|
||||
browser.tabs.create({
|
||||
url: browser.runtime.getURL('index.html')
|
||||
url: browser.runtime.getURL('index.html'),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"offline_enabled": true,
|
||||
"default_locale": "en",
|
||||
"name": "__MSG_name__",
|
||||
"description": "__MSG_description__",
|
||||
"version": "6.0.4",
|
||||
"version": "7.0.0",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
@@ -17,9 +17,7 @@
|
||||
"48": "icons/48x48.png",
|
||||
"128": "icons/128x128.png"
|
||||
},
|
||||
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'",
|
||||
"background": {
|
||||
"persistent": false,
|
||||
"scripts": [ "background-chrome.js" ]
|
||||
"service_worker": "background.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"name": "Mue",
|
||||
"description": "Fast, open and free-to-use new tab page for modern browsers.",
|
||||
"version": "6.0.4",
|
||||
"version": "7.0.0",
|
||||
"homepage_url": "https://muetab.com",
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_icon": "icons/128x128.png"
|
||||
},
|
||||
"chrome_url_overrides": {
|
||||
@@ -17,6 +17,5 @@
|
||||
},
|
||||
"chrome_settings_overrides": {
|
||||
"homepage": "index.html"
|
||||
},
|
||||
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'"
|
||||
}
|
||||
}
|
||||
|
||||
100
package.json
@@ -9,66 +9,56 @@
|
||||
"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": "6.0.4",
|
||||
"version": "7.0.0",
|
||||
"dependencies": {
|
||||
"@eartharoid/i18n": "^1.0.2",
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@fontsource/lexend-deca": "4.4.5",
|
||||
"@fontsource/montserrat": "4.4.5",
|
||||
"@mui/icons-material": "5.2.5",
|
||||
"@mui/material": "5.2.6",
|
||||
"react": "17.0.2",
|
||||
"react-clock": "3.0.0",
|
||||
"react-color-gradient-picker": "0.1.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-hot-keys": "2.7.1",
|
||||
"react-modal": "3.14.4",
|
||||
"react-sortable-hoc": "2.0.0",
|
||||
"react-toastify": "8.1.0",
|
||||
"weather-icons-react": "1.2.0"
|
||||
"@eartharoid/i18n": "1.2.1",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@floating-ui/react-dom": "1.3.0",
|
||||
"@fontsource/lexend-deca": "5.0.5",
|
||||
"@fontsource/montserrat": "5.0.5",
|
||||
"@muetab/react-color-gradient-picker": "0.1.2",
|
||||
"@muetab/react-sortable-hoc": "^2.0.1",
|
||||
"@mui/material": "5.14.8",
|
||||
"@sentry/react": "^7.58.1",
|
||||
"embla-carousel-autoplay": "8.0.0-rc11",
|
||||
"embla-carousel-react": "8.0.0-rc11",
|
||||
"fast-blurhash": "^1.1.2",
|
||||
"image-conversion": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-clock": "4.5.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-modal": "3.16.1",
|
||||
"react-toastify": "9.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/eslint-parser": "^7.16.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.0",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.16.0",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.16.0",
|
||||
"@babel/plugin-transform-runtime": "^7.16.0",
|
||||
"@babel/preset-env": "^7.16.0",
|
||||
"@babel/preset-react": "^7.16.0",
|
||||
"@commitlint/cli": "^17.7.1",
|
||||
"@commitlint/config-conventional": "^17.6.6",
|
||||
"@eartharoid/deep-merge": "^0.0.2",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-transform-react-class-to-function": "^1.2.2",
|
||||
"copy-webpack-plugin": "9.1.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-react-app": "^7.0.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.4",
|
||||
"sass": "^1.43.4",
|
||||
"sass-loader": "^12.3.0",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"webpack": "^5.63.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"webpack-dev-server": "^4.4.0"
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"adm-zip": "^0.5.10",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"husky": "^8.0.3",
|
||||
"prettier": "^3.0.0",
|
||||
"sass": "^1.63.6",
|
||||
"stylelint": "^15.10.3",
|
||||
"stylelint-config-standard-scss": "^10.0.0",
|
||||
"stylelint-scss": "^5.0.1",
|
||||
"vite": "4.4.4",
|
||||
"vite-plugin-progress": "^0.0.7"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve",
|
||||
"updatetranslations": "cd scripts && node updatetranslations.js",
|
||||
"build": "webpack --mode=production",
|
||||
"chrome": "cp manifest/chrome.json build/manifest.json && cp -r manifest/_locales build/_locales && cp manifest/background-chrome.js build/background-chrome.js",
|
||||
"firefox": "rm -rf build/_locales && cp manifest/firefox.json build/manifest.json"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version"
|
||||
]
|
||||
"dev": "vite",
|
||||
"dev:host": "vite --host",
|
||||
"translations": "cd scripts && node updatetranslations.js",
|
||||
"build": "vite build",
|
||||
"pretty": "prettier --write \"./**/*.{js,jsx,json,scss,css}\"",
|
||||
"lint": "eslint \"./src/**/*.{js,jsx}\" && stylelint \"./src/**/*.{scss,css}\"",
|
||||
"lint:fix": "eslint \"./src/**/*.{js,jsx}\" --fix && stylelint \"./src/**/*.{scss,css}\" --fix",
|
||||
"postinstall": "husky install"
|
||||
}
|
||||
}
|
||||
|
||||
6330
pnpm-lock.yaml
generated
Normal file
|
Before Width: | Height: | Size: 9.9 KiB |
BIN
public/icons/mue_dark.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
public/icons/mue_light.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
1
public/icons/undraw_making_art.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<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/32x32.png'>
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='./icons/16x16.png'>
|
||||
<title>New Tab</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
*, a {
|
||||
font-family: 'Lexend Deca', sans-serif;
|
||||
text-align: center;
|
||||
color: black;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
*, a {
|
||||
color: white;
|
||||
background: #2f3640 !important;
|
||||
}
|
||||
}
|
||||
</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>
|
||||
@@ -2,13 +2,38 @@
|
||||
const fs = require('fs');
|
||||
const merge = require('@eartharoid/deep-merge');
|
||||
|
||||
/**
|
||||
* It recursively compares the keys of two JSON objects and removes the keys from the first object that
|
||||
* are not present in the second object
|
||||
* @param json1 - The JSON object that you want to remove keys from.
|
||||
* @param json2 - The JSON object that you want to compare against.
|
||||
*/
|
||||
const compareAndRemoveKeys = (json1, json2) => {
|
||||
for (let key in json1) {
|
||||
if (json2.hasOwnProperty(key)) {
|
||||
if (typeof json1[key] === 'object' && typeof json2[key] === 'object') {
|
||||
compareAndRemoveKeys(json1[key], json2[key]);
|
||||
}
|
||||
} else {
|
||||
delete json1[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fs.readdirSync('../src/translations').forEach((file) => {
|
||||
if (file === 'en_GB.json') {
|
||||
return;
|
||||
}
|
||||
|
||||
const newdata = merge(require('../src/translations/en_GB.json'), require('../src/translations/' + file));
|
||||
const en = require('../src/translations/en_GB.json');
|
||||
const newdata = merge(en, require('../src/translations/' + file));
|
||||
|
||||
// remove strings not in english file
|
||||
compareAndRemoveKeys(newdata, en);
|
||||
|
||||
// write new file
|
||||
fs.writeFileSync('../src/translations/' + file, JSON.stringify(newdata, null, 2));
|
||||
|
||||
// add new line
|
||||
fs.appendFileSync('../src/translations/' + file, '\n');
|
||||
});
|
||||
|
||||
102
src/App.jsx
@@ -1,46 +1,56 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import Background from 'components/widgets/background/Background';
|
||||
import Widgets from 'components/widgets/Widgets';
|
||||
import Modals from 'components/modals/Modals';
|
||||
|
||||
import { loadSettings, moveSettings } from 'modules/helpers/settings';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class App extends PureComponent {
|
||||
componentDidMount() {
|
||||
// 4.0 -> 5.0 (the key below is only on 5.0)
|
||||
// now featuring 5.0 -> 5.1
|
||||
// the firstRun check was moved here because the old function was useless
|
||||
if (!localStorage.getItem('firstRun') || !localStorage.getItem('stats')) {
|
||||
moveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'other') {
|
||||
loadSettings(true);
|
||||
}
|
||||
});
|
||||
|
||||
variables.stats.tabLoad();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{(localStorage.getItem('background') === 'true') ? <Background/> : null}
|
||||
<ToastContainer position='bottom-right' autoClose={localStorage.getItem('toastDisplayTime') || 2500} newestOnTop={true} closeOnClick pauseOnFocusLoss/>
|
||||
<div id='center'>
|
||||
<Widgets/>
|
||||
<Modals/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import Background from 'components/widgets/background/Background';
|
||||
import Widgets from 'components/widgets/Widgets';
|
||||
import Modals from 'components/modals/Modals';
|
||||
|
||||
import { loadSettings, moveSettings } from 'modules/helpers/settings';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class App extends PureComponent {
|
||||
componentDidMount() {
|
||||
// 4.0 -> 5.0 (the key below is only on 5.0)
|
||||
// now featuring 5.0 -> 5.1
|
||||
// the firstRun check was moved here because the old function was useless
|
||||
if (!localStorage.getItem('firstRun') || !localStorage.getItem('stats')) {
|
||||
moveSettings();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'other') {
|
||||
loadSettings(true);
|
||||
}
|
||||
});
|
||||
|
||||
variables.stats.tabLoad();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{localStorage.getItem('background') === 'true' ? <Background /> : null}
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
autoClose={localStorage.getItem('toastDisplayTime') || 2500}
|
||||
newestOnTop={true}
|
||||
closeOnClick
|
||||
pauseOnFocusLoss
|
||||
/>
|
||||
<div id="center">
|
||||
<Widgets />
|
||||
<Modals />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
import './autocomplete.scss';
|
||||
|
||||
export default class Autocomplete extends PureComponent {
|
||||
class Autocomplete extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filtered: [],
|
||||
input: '',
|
||||
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
|
||||
autocompleteDisabled: localStorage.getItem('autocomplete') !== 'true',
|
||||
};
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
if (this.state.autocompleteDisabled) {
|
||||
return this.setState({
|
||||
input: e.target.value
|
||||
input: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
filtered: this.props.suggestions.filter((suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1),
|
||||
input: e.target.value
|
||||
filtered: this.props.suggestions.filter(
|
||||
(suggestion) => suggestion.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1,
|
||||
),
|
||||
input: e.target.value,
|
||||
});
|
||||
|
||||
this.props.onChange(e.target.value);
|
||||
@@ -32,14 +35,14 @@ export default class Autocomplete extends PureComponent {
|
||||
onClick = (e) => {
|
||||
this.setState({
|
||||
filtered: [],
|
||||
input: e.target.innerText
|
||||
input: e.target.innerText,
|
||||
});
|
||||
|
||||
this.props.onClick({
|
||||
preventDefault: () => e.preventDefault(),
|
||||
target: {
|
||||
value: e.target.innerText
|
||||
}
|
||||
value: e.target.innerText,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -47,13 +50,13 @@ export default class Autocomplete extends PureComponent {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'search') {
|
||||
this.setState({
|
||||
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
|
||||
autocompleteDisabled: localStorage.getItem('autocomplete') !== 'true',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnount() {
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
@@ -63,21 +66,38 @@ export default class Autocomplete extends PureComponent {
|
||||
// length will only be > 0 if enabled
|
||||
if (this.state.filtered.length > 0 && this.state.input.length > 0) {
|
||||
autocomplete = (
|
||||
<ul className='suggestions'>
|
||||
<div className="suggestions">
|
||||
{this.state.filtered.map((suggestion) => (
|
||||
<li key={suggestion} onClick={this.onClick}>
|
||||
<div key={suggestion} onClick={this.onClick}>
|
||||
{suggestion}
|
||||
</li>
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<input type='text' onChange={this.onChange} value={this.state.input} placeholder={this.props.placeholder || ''} autoComplete='off' id={this.props.id || ''} />
|
||||
<div style={{ display: 'flex', flexFlow: 'column' }}>
|
||||
<input
|
||||
type="text"
|
||||
onChange={this.onChange}
|
||||
value={this.state.input}
|
||||
placeholder={this.props.placeholder || ''}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
id={this.props.id || ''}
|
||||
/>
|
||||
{autocomplete}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Autocomplete.propTypes = {
|
||||
suggestions: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Autocomplete;
|
||||
|
||||
@@ -1,50 +1,45 @@
|
||||
@use 'scss/variables';
|
||||
|
||||
.suggestions {
|
||||
@extend %basic;
|
||||
|
||||
text-align: left;
|
||||
font-size: calc(5px + 1.2vmin);
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: black;
|
||||
border-top-width: 0;
|
||||
list-style: none;
|
||||
margin-top: 40px;
|
||||
max-height: 143px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 40px;
|
||||
border-radius: 24px;
|
||||
width: 430px;
|
||||
opacity: 0;
|
||||
opacity: 1;
|
||||
margin-top: 5px;
|
||||
|
||||
li {
|
||||
padding: 0.5rem;
|
||||
padding-left: 20px;
|
||||
div {
|
||||
padding: 0.5rem 0.5rem 0.5rem 20px;
|
||||
font-size: 0.6em;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
background-color: rgb(255 255 255 / 80%);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
input[type=text]:focus+.suggestions {
|
||||
input[type='text']:focus + .suggestions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1400px) {
|
||||
.suggestions {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .suggestions {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background: rgb(0 0 0 / 70%);
|
||||
color: white;
|
||||
|
||||
li {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background: rgb(0 0 0 / 70%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.micActive {
|
||||
box-shadow: 0 0 50px 1px #e74c3c !important;
|
||||
}
|
||||
|
||||
85
src/components/helpers/carousel/Carousel.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React, { useState, useEffect, useCallback, useRef, memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MdOutlineArrowForwardIos, MdOutlineArrowBackIos } from 'react-icons/md';
|
||||
import useEmblaCarousel from 'embla-carousel-react';
|
||||
import Autoplay from 'embla-carousel-autoplay';
|
||||
|
||||
import './carousel.scss';
|
||||
|
||||
function EmblaCarousel({ data }) {
|
||||
const autoplay = useRef(
|
||||
Autoplay({ delay: 3000, stopOnInteraction: false }, (emblaRoot) => emblaRoot.parentElement),
|
||||
);
|
||||
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false }, [autoplay.current]);
|
||||
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
||||
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
||||
|
||||
const scroll = useCallback(
|
||||
(direction) => {
|
||||
if (!emblaApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'next') {
|
||||
emblaApi.scrollNext();
|
||||
} else {
|
||||
emblaApi.scrollPrev();
|
||||
}
|
||||
autoplay.current.reset();
|
||||
},
|
||||
[emblaApi],
|
||||
);
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
if (!emblaApi) {
|
||||
return;
|
||||
}
|
||||
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
||||
setNextBtnEnabled(emblaApi.canScrollNext());
|
||||
}, [emblaApi]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) {
|
||||
return;
|
||||
}
|
||||
onSelect();
|
||||
emblaApi.on('select', onSelect);
|
||||
}, [emblaApi, onSelect]);
|
||||
|
||||
return (
|
||||
<div className="carousel">
|
||||
<div className="carousel_viewport" ref={emblaRef}>
|
||||
<div className="carousel_container">
|
||||
{data.map((photo, index) => (
|
||||
<div className="carousel_slide" key={index}>
|
||||
<div className="carousel_slide_inner">
|
||||
<img src={photo.url.default} alt="Marketplace example screenshot" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="carousel_button prev"
|
||||
onClick={() => scroll('prev')}
|
||||
disabled={!prevBtnEnabled}
|
||||
>
|
||||
<MdOutlineArrowBackIos />
|
||||
</button>
|
||||
<button
|
||||
className="carousel_button next"
|
||||
onClick={() => scroll('next')}
|
||||
disabled={!nextBtnEnabled}
|
||||
>
|
||||
<MdOutlineArrowForwardIos />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EmblaCarousel.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default memo(EmblaCarousel);
|
||||
84
src/components/helpers/carousel/carousel.scss
Normal file
@@ -0,0 +1,84 @@
|
||||
.carousel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.carousel_viewport {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
&.is-draggable {
|
||||
cursor: move;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
&.is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
border-radius: 15px !important;
|
||||
}
|
||||
|
||||
.carousel_container {
|
||||
display: flex;
|
||||
-webkit-touch-callout: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.carousel_slide {
|
||||
position: relative;
|
||||
min-width: 100%;
|
||||
padding-left: 10px;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: auto;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
max-width: none;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.carousel_slide_inner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.carousel_button {
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border: 0;
|
||||
width: 30px !important;
|
||||
height: 30px !important;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
&.prev {
|
||||
left: 27px;
|
||||
}
|
||||
|
||||
&.next {
|
||||
right: 27px;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import variables from 'modules/variables';
|
||||
|
||||
import './preview.scss';
|
||||
|
||||
export default function Preview(props) {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
function Preview(props) {
|
||||
return (
|
||||
<div className='preview-mode'>
|
||||
<h1>{getMessage('modals.main.settings.reminder.title')}</h1>
|
||||
<p>{getMessage('modals.welcome.preview.description')}</p>
|
||||
<button className='pinNote' onClick={() => props.setup()}>{getMessage('modals.welcome.preview.continue')}</button>
|
||||
<div className="preview-mode">
|
||||
<span className="title">{variables.getMessage('modals.main.settings.reminder.title')}</span>
|
||||
<span className="subtitle">{variables.getMessage('modals.welcome.preview.description')}</span>
|
||||
<button onClick={() => props.setup()}>
|
||||
{variables.getMessage('modals.welcome.preview.continue')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Preview.propTypes = {
|
||||
setup: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default memo(Preview);
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
@import 'scss/variables';
|
||||
|
||||
.preview-mode {
|
||||
@extend %basic;
|
||||
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
padding: 15px;
|
||||
color: var(--modal-text);
|
||||
background: var(--background);
|
||||
max-width: 300px;
|
||||
border-radius: .7em;
|
||||
max-width: 250px;
|
||||
border-radius: 12px;
|
||||
z-index: 999;
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 15px;
|
||||
|
||||
h1 {
|
||||
font-size: 1rem;
|
||||
button {
|
||||
@include basicIconButton(10px, 14px, ui);
|
||||
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
132
src/components/helpers/sharemodal/ShareModal.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import variables from 'modules/variables';
|
||||
import { MdClose, MdEmail, MdContentCopy } from 'react-icons/md';
|
||||
import { FaTwitter, FaFacebookF } from 'react-icons/fa';
|
||||
import { AiFillWechat } from 'react-icons/ai';
|
||||
import { SiTencentqq } from 'react-icons/si';
|
||||
import Tooltip from '../tooltip/Tooltip';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import './sharemodal.scss';
|
||||
|
||||
function ShareModal({ modalClose, data }) {
|
||||
if (data.startsWith('https://cdn.')) {
|
||||
data = {
|
||||
url: data,
|
||||
name: 'this image',
|
||||
};
|
||||
} else if (data.startsWith('"')) {
|
||||
data = {
|
||||
url: data,
|
||||
name: 'this quote',
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
url: data,
|
||||
name: 'this marketplace item',
|
||||
};
|
||||
}
|
||||
|
||||
const copyLink = () => {
|
||||
navigator.clipboard.writeText(data.url);
|
||||
toast(variables.getMessage('modals.share.copy_link'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="smallModal">
|
||||
<div className="shareHeader">
|
||||
<span className="title">{variables.getMessage('widgets.quote.share')}</span>
|
||||
<Tooltip title={variables.getMessage('modals.welcome.buttons.close')}>
|
||||
<div className="close" onClick={modalClose}>
|
||||
<MdClose />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="shareButtons">
|
||||
<Tooltip title="Twitter">
|
||||
<button
|
||||
onClick={() =>
|
||||
window
|
||||
.open(
|
||||
`https://twitter.com/intent/tweet?text=Check out ${data.name} on @getmue: ${data.url}`,
|
||||
'_blank',
|
||||
)
|
||||
.focus()
|
||||
}
|
||||
>
|
||||
<FaTwitter />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Facebook">
|
||||
<button
|
||||
onClick={() =>
|
||||
window
|
||||
.open(`https://www.facebook.com/sharer/sharer.php?u=${data.url}`, '_blank')
|
||||
.focus()
|
||||
}
|
||||
>
|
||||
<FaFacebookF />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Email">
|
||||
<button
|
||||
onClick={() =>
|
||||
window
|
||||
.open(
|
||||
'mailto:email@example.com?subject=Check%20out%20this%20%on%20%Mue!&body=' +
|
||||
data.data.name +
|
||||
'on Mue: ' +
|
||||
data,
|
||||
'_blank',
|
||||
)
|
||||
.focus()
|
||||
}
|
||||
>
|
||||
<MdEmail />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title="WeChat">
|
||||
<button
|
||||
onClick={() =>
|
||||
window
|
||||
.open(
|
||||
`https://api.qrserver.com/v1/create-qr-code/?size=154x154&data=${data.url}`,
|
||||
'_blank',
|
||||
)
|
||||
.focus()
|
||||
}
|
||||
>
|
||||
<AiFillWechat />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Tencent QQ">
|
||||
<button
|
||||
onClick={() =>
|
||||
window
|
||||
.open(`http://connect.qq.com/widget/shareqq/index.html?url=${data.url}`, '_blank')
|
||||
.focus()
|
||||
}
|
||||
>
|
||||
<SiTencentqq />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="copy">
|
||||
<input type="text" value={data.url} className="left field" readOnly />
|
||||
<Tooltip title={variables.getMessage('modals.share.copy_link')} placement="top">
|
||||
<button onClick={() => copyLink()}>
|
||||
<MdContentCopy />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ShareModal.propTypes = {
|
||||
modalClose: PropTypes.func.isRequired,
|
||||
data: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default memo(ShareModal);
|
||||
110
src/components/helpers/sharemodal/sharemodal.scss
Normal file
@@ -0,0 +1,110 @@
|
||||
@import 'scss/variables';
|
||||
|
||||
.smallModal {
|
||||
@extend %tabText;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
width: 320px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-secondaryColour);
|
||||
}
|
||||
|
||||
.resetFooter {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
|
||||
button {
|
||||
gap: 20px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
}
|
||||
|
||||
.textButton {
|
||||
@include basicIconButton(11px, 1.3rem, modal-text);
|
||||
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.tooltipTitle {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.shareButtons {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
place-items: center;
|
||||
display: grid;
|
||||
|
||||
@include basicIconButton(11px, 1.3rem, modal);
|
||||
}
|
||||
|
||||
.copy {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 15px;
|
||||
|
||||
input[type='text'] {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
padding: 11px;
|
||||
flex: 1;
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
padding: 11px;
|
||||
flex: 1;
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.close {
|
||||
padding: 15px;
|
||||
place-items: center;
|
||||
display: grid;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shareHeader {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1,10 +1,54 @@
|
||||
import { useState, memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useFloating, flip, offset, shift } from '@floating-ui/react-dom';
|
||||
import './tooltip.scss';
|
||||
|
||||
export default function Tooltip({ children, title }) {
|
||||
function Tooltip({ children, title, style, placement, subtitle }) {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: placement || 'bottom',
|
||||
middleware: [flip(), offset(15), shift()],
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='tooltip'>
|
||||
{children}
|
||||
<span className='tooltipTitle'>{title}</span>
|
||||
</div>
|
||||
<>
|
||||
<div
|
||||
className="tooltip"
|
||||
style={style}
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
onFocus={() => setShowTooltip(true)}
|
||||
onBlur={() => setShowTooltip(false)}
|
||||
ref={reference}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{showTooltip && (
|
||||
<span
|
||||
ref={floating}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? '',
|
||||
left: x ?? '',
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
}}
|
||||
className="tooltipTitle"
|
||||
>
|
||||
{title}
|
||||
<span style={{ fontSize: '8px' }}>{subtitle}</span>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Tooltip.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default memo(Tooltip);
|
||||
|
||||
55
src/components/helpers/tooltip/infoTooltip.jsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import variables from 'modules/variables';
|
||||
import { useState, memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useFloating, flip, offset, shift } from '@floating-ui/react-dom';
|
||||
import { MdClose, MdInfo, MdOpenInNew } from 'react-icons/md';
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
import './tooltip.scss';
|
||||
|
||||
function InfoTooltip({ title, style, placement, subtitle }) {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const { x, y, reference, floating, strategy } = useFloating({
|
||||
placement: placement || 'top-start',
|
||||
middleware: [flip(), offset(10), shift()],
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="infoTooltip" style={style} ref={reference}>
|
||||
<MdInfo onClick={() => setShowTooltip(true)} />
|
||||
{showTooltip && (
|
||||
<div
|
||||
ref={floating}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? '',
|
||||
left: x ?? '',
|
||||
}}
|
||||
className="infoTooltipTitle"
|
||||
>
|
||||
<div className="tooltipHeader">
|
||||
<span className="title">{title}</span>
|
||||
<Tooltip title={variables.getMessage('modals.welcome.buttons.close')}>
|
||||
<div className="close" onClick={() => setShowTooltip(false)}>
|
||||
<MdClose />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="subtitle">{subtitle}</span>
|
||||
<span className="link">
|
||||
{variables.getMessage('modals.main.settings.open_knowledgebase')} <MdOpenInNew />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
InfoTooltip.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default memo(InfoTooltip);
|
||||
@@ -1,37 +1,147 @@
|
||||
// todo: possibly add tooltip placement option
|
||||
@import 'scss/variables';
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.tooltipTitle {
|
||||
min-width: 60px;
|
||||
background-color: rgba(255, 255, 255, 0.89);
|
||||
color: #000;
|
||||
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;
|
||||
@keyframes floating {
|
||||
0% {
|
||||
transform: translate(0, -5px);
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tooltipTitle {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
100% {
|
||||
transform: translate(0, -0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltipTitle {
|
||||
@extend %basic;
|
||||
|
||||
text-align: center;
|
||||
font-size: 0.6rem;
|
||||
padding: 5px 10px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
/* top: 100%;
|
||||
left: 50%;
|
||||
margin-top: 15px;
|
||||
margin-left: -30px; */
|
||||
cursor: initial;
|
||||
user-select: none;
|
||||
opacity: 1;
|
||||
animation-name: floating;
|
||||
animation-duration: 0.3s;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
#modal {
|
||||
.tooltipTitle {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark .tooltipTitle {
|
||||
background-color: rgba(0, 0, 0, 0.79);
|
||||
color: #fff;
|
||||
#root {
|
||||
.tooltipTitle {
|
||||
@extend %basic;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltipTitle::before {
|
||||
transform: scale3d(0.2, 0.2, 1);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tooltipTitle::after {
|
||||
transform: translate3d(0, 6px, 0);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.tooltipTitle:hover::before,
|
||||
.tooltipTitle:hover::after {
|
||||
opacity: 1;
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
|
||||
.tooltipTitle:hover::after {
|
||||
transition: all 0.2s 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
#arrow {
|
||||
position: absolute;
|
||||
background: #333;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.infoTooltip {
|
||||
position: relative;
|
||||
display: grid;
|
||||
|
||||
svg {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.infoTooltipTitle {
|
||||
min-width: 200px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
cursor: initial;
|
||||
user-select: none;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
padding: 25px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-flow: column;
|
||||
gap: 10px;
|
||||
|
||||
@include themed {
|
||||
background-color: t($modal-background);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
.tooltipHeader {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close {
|
||||
font-size: 20px;
|
||||
padding: 15px;
|
||||
place-items: center;
|
||||
display: grid;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,67 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class ErrorBoundary extends PureComponent {
|
||||
import { MdErrorOutline } from 'react-icons/md';
|
||||
import { captureException } from '@sentry/react';
|
||||
|
||||
class ErrorBoundary extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
error: false
|
||||
error: false,
|
||||
errorData: '',
|
||||
showReport: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If an error occurs, log the error, and set the state to error: true, and errorData: error.
|
||||
* @param {Error} error The error that occurred.
|
||||
* @returns An object with two properties: error and errorData.
|
||||
*/
|
||||
static getDerivedStateFromError(error) {
|
||||
console.log(error);
|
||||
variables.stats.postEvent('modal', 'Error occurred');
|
||||
return {
|
||||
error: true
|
||||
return {
|
||||
error: true,
|
||||
errorData: error,
|
||||
};
|
||||
}
|
||||
|
||||
reportError() {
|
||||
captureException(this.state.errorData);
|
||||
this.setState({
|
||||
showReport: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<ErrorOutline/>
|
||||
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.title')}</h1>
|
||||
<p>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.message')}</p>
|
||||
<button className='refresh' onClick={() => window.location.reload()}>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.refresh')}</button>
|
||||
<div className="emptyItems">
|
||||
<div className="emptyNewMessage">
|
||||
<MdErrorOutline />
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.error_boundary.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.error_boundary.message')}
|
||||
</span>
|
||||
<div className="buttonsRow">
|
||||
{this.state.showReport ? (
|
||||
<button onClick={() => this.reportError()}>
|
||||
{variables.getMessage('modals.main.error_boundary.report_error')}
|
||||
</button>
|
||||
) : (
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.error_boundary.sent')}
|
||||
</span>
|
||||
)}
|
||||
<button className="refresh" onClick={() => window.location.reload()}>
|
||||
{variables.getMessage('modals.main.error_boundary.refresh')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -35,3 +70,9 @@ export default class ErrorBoundary extends PureComponent {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent, Suspense, lazy } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
//import Hotkeys from 'react-hot-keys';
|
||||
|
||||
import Main from './main/Main';
|
||||
import Navbar from '../widgets/navbar/Navbar';
|
||||
@@ -9,10 +8,7 @@ import Preview from '../helpers/preview/Preview';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
// Welcome modal is lazy loaded as the user won't use it every time they open a tab
|
||||
// We used to lazy load the main and feedback modals, but doing so broke the modal open animation on first click
|
||||
const Welcome = lazy(() => import('./welcome/Welcome'));
|
||||
const renderLoader = () => <></>;
|
||||
import Welcome from './welcome/Welcome';
|
||||
|
||||
export default class Modals extends PureComponent {
|
||||
constructor() {
|
||||
@@ -21,15 +17,17 @@ export default class Modals extends PureComponent {
|
||||
mainModal: false,
|
||||
updateModal: false,
|
||||
welcomeModal: false,
|
||||
feedbackModal: false,
|
||||
preview: false
|
||||
preview: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('showWelcome') === 'true' && window.location.search !== '?nointro=true') {
|
||||
if (
|
||||
localStorage.getItem('showWelcome') === 'true' &&
|
||||
window.location.search !== '?nointro=true'
|
||||
) {
|
||||
this.setState({
|
||||
welcomeModal: true
|
||||
welcomeModal: true,
|
||||
});
|
||||
variables.stats.postEvent('modal', 'Opened welcome');
|
||||
}
|
||||
@@ -37,8 +35,8 @@ export default class Modals extends PureComponent {
|
||||
if (window.location.search === '?nointro=true') {
|
||||
if (localStorage.getItem('showWelcome') === 'true') {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
EventBus.dispatch('refresh', 'widgets');
|
||||
EventBus.dispatch('refresh', 'backgroundwelcome');
|
||||
EventBus.emit('refresh', 'widgets');
|
||||
EventBus.emit('refresh', 'backgroundwelcome');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +47,11 @@ export default class Modals extends PureComponent {
|
||||
closeWelcome() {
|
||||
localStorage.setItem('showWelcome', false);
|
||||
this.setState({
|
||||
welcomeModal: false
|
||||
welcomeModal: false,
|
||||
});
|
||||
EventBus.dispatch('refresh', 'widgetsWelcomeDone');
|
||||
EventBus.dispatch('refresh', 'widgets');
|
||||
EventBus.dispatch('refresh', 'backgroundwelcome');
|
||||
EventBus.emit('refresh', 'widgetsWelcomeDone');
|
||||
EventBus.emit('refresh', 'widgets');
|
||||
EventBus.emit('refresh', 'backgroundwelcome');
|
||||
}
|
||||
|
||||
previewWelcome() {
|
||||
@@ -61,14 +59,14 @@ export default class Modals extends PureComponent {
|
||||
localStorage.setItem('welcomePreview', true);
|
||||
this.setState({
|
||||
welcomeModal: false,
|
||||
preview: true
|
||||
preview: true,
|
||||
});
|
||||
EventBus.dispatch('refresh', 'widgetsWelcome');
|
||||
EventBus.emit('refresh', 'widgetsWelcome');
|
||||
}
|
||||
|
||||
toggleModal(type, action) {
|
||||
this.setState({
|
||||
[type]: action
|
||||
[type]: action,
|
||||
});
|
||||
|
||||
if (action !== false) {
|
||||
@@ -79,17 +77,32 @@ export default class Modals extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{this.state.welcomeModal === false ? <Navbar openModal={(modal) => this.toggleModal(modal, true)}/> : null}
|
||||
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.toggleModal('mainModal', false)} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
|
||||
<Main modalClose={() => this.toggleModal('mainModal', false)}/>
|
||||
{this.state.welcomeModal === false ? (
|
||||
<Navbar openModal={(modal) => this.toggleModal(modal, true)} />
|
||||
) : null}
|
||||
<Modal
|
||||
closeTimeoutMS={300}
|
||||
id="modal"
|
||||
onRequestClose={() => this.toggleModal('mainModal', false)}
|
||||
isOpen={this.state.mainModal}
|
||||
className="Modal mainModal"
|
||||
overlayClassName="Overlay"
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<Main modalClose={() => this.toggleModal('mainModal', false)} />
|
||||
</Modal>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Modal closeTimeoutMS={300} onRequestClose={() => this.closeWelcome()} isOpen={this.state.welcomeModal} className='Modal welcomemodal mainModal' overlayClassName='Overlay welcomeoverlay' shouldCloseOnOverlayClick={false} ariaHideApp={false}>
|
||||
<Welcome modalClose={() => this.closeWelcome()} modalSkip={() => this.previewWelcome()}/>
|
||||
</Modal>
|
||||
</Suspense>
|
||||
{this.state.preview ? <Preview setup={() => window.location.reload()}/> : null}
|
||||
{/*variables.keybinds.toggleModal && variables.keybinds.toggleModal !== '' ? <Hotkeys keyName={variables.keybinds.toggleModal} onKeyDown={() => this.toggleModal('mainModal', (this.state.mainModal === true ? false : true))}/> : null*/}
|
||||
<Modal
|
||||
closeTimeoutMS={300}
|
||||
onRequestClose={() => this.closeWelcome()}
|
||||
isOpen={this.state.welcomeModal}
|
||||
className="Modal welcomemodal mainModal"
|
||||
overlayClassName="Overlay mainModal"
|
||||
shouldCloseOnOverlayClick={false}
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<Welcome modalClose={() => this.closeWelcome()} modalSkip={() => this.previewWelcome()} />
|
||||
</Modal>
|
||||
{this.state.preview ? <Preview setup={() => window.location.reload()} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import variables from 'modules/variables';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { Suspense, lazy, useState, memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Tabs from './tabs/backend/Tabs';
|
||||
import { MdClose } from 'react-icons/md';
|
||||
|
||||
import './scss/index.scss';
|
||||
import Tooltip from 'components/helpers/tooltip/Tooltip';
|
||||
|
||||
// Lazy load all the tabs instead of the modal itself
|
||||
const Settings = lazy(() => import('./tabs/Settings'));
|
||||
@@ -11,46 +13,64 @@ const Addons = lazy(() => import('./tabs/Addons'));
|
||||
const Marketplace = lazy(() => import('./tabs/Marketplace'));
|
||||
|
||||
const renderLoader = () => (
|
||||
<Tabs>
|
||||
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.loading')}>
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.loading')}</h1>
|
||||
<div style={{ display: 'flex', width: '100%', minHeight: '100%' }}>
|
||||
<ul className="sidebar">
|
||||
<span className="mainTitle">Mue</span>
|
||||
</ul>
|
||||
<div className="tab-content" style={{ width: '100%' }}>
|
||||
<div className="emptyItems">
|
||||
<div className="emptyMessage">
|
||||
<div className="loaderHolder">
|
||||
<div id="loader"></div>
|
||||
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label='' style={{ display: 'none' }}></div>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function MainModal({ modalClose }) {
|
||||
const display = (localStorage.getItem('showReminder') === 'true') ? 'block' : 'none';
|
||||
function MainModal({ modalClose }) {
|
||||
const [currentTab, setCurrentTab] = useState(0);
|
||||
|
||||
const changeTab = (type) => {
|
||||
switch (type) {
|
||||
case 'settings':
|
||||
setCurrentTab(<Settings changeTab={changeTab} />);
|
||||
break;
|
||||
case 'addons':
|
||||
setCurrentTab(<Addons changeTab={changeTab} />);
|
||||
break;
|
||||
case 'marketplace':
|
||||
setCurrentTab(<Marketplace changeTab={changeTab} />);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (currentTab === 0) {
|
||||
setCurrentTab(<Settings changeTab={changeTab} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className='closeModal' onClick={modalClose}>×</span>
|
||||
<Tabs navbar={true}>
|
||||
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.settings')} name='settings'>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Settings/>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.addons')} name='addons'>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Addons/>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.marketplace')} name='marketplace'>
|
||||
<Suspense fallback={renderLoader()}>
|
||||
<Marketplace/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</Tabs>
|
||||
<div className='reminder-info' style={{ display }}>
|
||||
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.reminder.title')}</h1>
|
||||
<p>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.reminder.message')}</p>
|
||||
<button className='pinNote' onClick={() => window.location.reload()}>{variables.language.getMessage(variables.languagecode, 'modals.main.error_boundary.refresh')}</button>
|
||||
</div>
|
||||
</>
|
||||
<div className="frame">
|
||||
<Tooltip
|
||||
style={{ position: 'absolute', top: '1rem', right: '1rem' }}
|
||||
title={variables.getMessage('modals.welcome.buttons.close')}
|
||||
key="closeTooltip"
|
||||
>
|
||||
<span className="closeModal" onClick={modalClose}>
|
||||
<MdClose />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Suspense fallback={renderLoader()}>{currentTab}</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MainModal.propTypes = {
|
||||
modalClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default memo(MainModal);
|
||||
|
||||
@@ -1,50 +1,72 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Tooltip from 'components/helpers/tooltip/Tooltip';
|
||||
import ImageCarousel from 'components/helpers/carousel/Carousel';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ArrowBack } from '@mui/icons-material';
|
||||
import {
|
||||
MdIosShare,
|
||||
MdFlag,
|
||||
MdAccountCircle,
|
||||
MdBugReport,
|
||||
MdFormatQuote,
|
||||
MdImage,
|
||||
MdTranslate,
|
||||
MdOutlineKeyboardArrowRight,
|
||||
MdExpandMore,
|
||||
MdExpandLess,
|
||||
MdStyle,
|
||||
} from 'react-icons/md';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import { install, uninstall } from 'modules/helpers/marketplace';
|
||||
|
||||
import Lightbox from './Lightbox';
|
||||
import ShareModal from 'components/helpers/sharemodal/ShareModal';
|
||||
|
||||
export default class Item extends PureComponent {
|
||||
class Item extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showLightbox: false,
|
||||
showUpdateButton: (this.props.addonInstalled === true && this.props.addonInstalledVersion !== this.props.data.version)
|
||||
showUpdateButton:
|
||||
this.props.addonInstalled === true &&
|
||||
this.props.addonInstalledVersion !== this.props.data.version,
|
||||
showMore: false,
|
||||
shareModal: false,
|
||||
count: 5,
|
||||
};
|
||||
}
|
||||
|
||||
updateAddon() {
|
||||
uninstall(this.props.data.type, this.props.data.display_name);
|
||||
install(this.props.data.type, this.props.data);
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.updated'));
|
||||
this.setState({
|
||||
showUpdateButton: false
|
||||
toast(variables.getMessage('toasts.updated'));
|
||||
this.setState({
|
||||
showUpdateButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
toggleShowMore() {
|
||||
if (this.state.showMore === true) {
|
||||
this.setState({ showMore: false });
|
||||
} else {
|
||||
this.setState({ showMore: true });
|
||||
}
|
||||
}
|
||||
|
||||
incrementCount(type) {
|
||||
if (this.state.count !== this.props.data.data[type].length) {
|
||||
this.setState({ count: this.props.data.data[type].length });
|
||||
} else {
|
||||
this.setState({ count: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.data.display_name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let warningHTML;
|
||||
if (this.props.data.quote_api) {
|
||||
warningHTML = (
|
||||
<div className='productInformation'>
|
||||
<ul>
|
||||
<li className='header'>{getMessage('modals.main.marketplace.product.quote_warning.title')}</li>
|
||||
<li id='updated'>{getMessage('modals.main.marketplace.product.quote_warning.description')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// prevent console error
|
||||
let iconsrc = variables.constants.DDG_IMAGE_PROXY + this.props.data.icon;
|
||||
if (!this.props.data.icon) {
|
||||
@@ -54,49 +76,264 @@ export default class Item extends PureComponent {
|
||||
let updateButton;
|
||||
if (this.state.showUpdateButton) {
|
||||
updateButton = (
|
||||
<Fragment key='update'>
|
||||
<br/><br/>
|
||||
<button className='removeFromMue' onClick={() => this.updateAddon()}>
|
||||
{getMessage('modals.main.addons.product.buttons.update_addon')}
|
||||
<Fragment key="update">
|
||||
<button className="removeFromMue" onClick={() => this.updateAddon()}>
|
||||
{variables.getMessage('modals.main.addons.product.buttons.update_addon')}
|
||||
</button>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='item'>
|
||||
<br/>
|
||||
<ArrowBack className='backArrow' onClick={this.props.toggleFunction}/>
|
||||
<br/>
|
||||
<h1>{this.props.data.display_name}</h1>
|
||||
{this.props.button}
|
||||
{updateButton}
|
||||
<br/><br/>
|
||||
{iconsrc ? <img alt='product' draggable='false' src={iconsrc} onClick={() => this.setState({ showLightbox: true })}/> : null}
|
||||
<div className='side'>
|
||||
<div className='productInformation'>
|
||||
<ul>
|
||||
<li className='header'>{getMessage('modals.main.marketplace.product.version')}</li>
|
||||
{updateButton ? <li>{this.props.data.version} (Installed: {this.props.data.addonInstalledVersion})</li> : <li>{this.props.data.version}</li>}
|
||||
<br/>
|
||||
<li className='header'>{getMessage('modals.main.marketplace.product.author')}</li>
|
||||
<li>{this.props.data.author}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/>
|
||||
{warningHTML}
|
||||
</div>
|
||||
<div className='sidebr'>
|
||||
<br/><br/>
|
||||
</div>
|
||||
<div className='informationContainer'>
|
||||
<h1 className='overview'>{getMessage('modals.main.marketplace.product.overview')}</h1>
|
||||
<p className='description' dangerouslySetInnerHTML={{ __html: this.props.data.description }}></p>
|
||||
</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}/>
|
||||
<div id="item">
|
||||
<Modal
|
||||
closeTimeoutMS={300}
|
||||
isOpen={this.state.shareModal}
|
||||
className="Modal mainModal"
|
||||
overlayClassName="Overlay"
|
||||
ariaHideApp={false}
|
||||
onRequestClose={() => this.setState({ shareModal: false })}
|
||||
>
|
||||
<ShareModal
|
||||
data={variables.constants.MARKETPLACE_URL + '/share/' + btoa(this.props.data.api_name)}
|
||||
modalClose={() => this.setState({ shareModal: false })}
|
||||
/>
|
||||
</Modal>
|
||||
<div className="flexTopMarketplace">
|
||||
<span className="mainTitle" onClick={this.props.toggleFunction}>
|
||||
<span className="backTitle">
|
||||
{variables.getMessage('modals.main.navbar.marketplace')}
|
||||
</span>
|
||||
<MdOutlineKeyboardArrowRight /> {this.props.data.display_name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="itemPage">
|
||||
<div className="itemShowcase">
|
||||
{this.props.data.data.photos ? (
|
||||
<div className="carousel">
|
||||
<div className="carousel_container">
|
||||
<ImageCarousel data={this.props.data.data.photos} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.props.data.data.settings ? (
|
||||
<img
|
||||
alt="product"
|
||||
draggable={false}
|
||||
src={iconsrc}
|
||||
onClick={() => this.setState({ showLightbox: true })}
|
||||
/>
|
||||
) : null}
|
||||
{this.props.data.data.quotes ? (
|
||||
<>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{variables.getMessage('modals.main.settings.sections.quote.title')}</th>
|
||||
<th>{variables.getMessage('modals.main.settings.sections.quote.author')}</th>
|
||||
</tr>
|
||||
{this.props.data.data.quotes.slice(0, this.state.count).map((quote, index) => (
|
||||
<tr key={index}>
|
||||
<td>{quote.quote}</td>
|
||||
<td>{quote.author}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="showMoreItems">
|
||||
<span className="link" onClick={() => this.incrementCount('quotes')}>
|
||||
{this.state.count !== this.props.data.data.quotes.length ? (
|
||||
<>
|
||||
<MdExpandMore />{' '}
|
||||
{variables.getMessage('modals.main.marketplace.product.show_all')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MdExpandLess />{' '}
|
||||
{variables.getMessage('modals.main.marketplace.product.show_less')}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
{this.props.data.data.settings ? (
|
||||
<>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{variables.getMessage('modals.main.marketplace.product.setting')}</th>
|
||||
<th>{variables.getMessage('modals.main.marketplace.product.value')}</th>
|
||||
</tr>
|
||||
{Object.entries(this.props.data.data.settings)
|
||||
.slice(0, this.state.count)
|
||||
.map(([key, value]) => (
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
<td>{value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="showMoreItems">
|
||||
<span className="link" onClick={() => this.incrementCount('settings')}>
|
||||
{this.state.count !== this.props.data.data.settings.length ? (
|
||||
<>
|
||||
<MdExpandMore />{' '}
|
||||
{variables.getMessage('modals.main.marketplace.product.show_all')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MdExpandLess />{' '}
|
||||
{variables.getMessage('modals.main.marketplace.product.show_less')}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<div>
|
||||
<p className="title">
|
||||
{variables.getMessage('modals.main.marketplace.product.description')}
|
||||
</p>
|
||||
<p dangerouslySetInnerHTML={{ __html: this.props.data.description }} />
|
||||
</div>
|
||||
<div className="moreInfo">
|
||||
<div className="infoItem">
|
||||
<MdBugReport />
|
||||
<div className="text">
|
||||
<span className="header">
|
||||
{variables.getMessage('modals.main.marketplace.product.version')}
|
||||
</span>
|
||||
{updateButton ? (
|
||||
<span>
|
||||
{this.props.data.version} (Installed: {this.props.data.addonInstalledVersion})
|
||||
</span>
|
||||
) : (
|
||||
<span>{this.props.data.version}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="infoItem">
|
||||
<MdAccountCircle />
|
||||
<div className="text">
|
||||
<span className="header">
|
||||
{variables.getMessage('modals.main.marketplace.product.author')}
|
||||
</span>
|
||||
<span>{this.props.data.author}</span>
|
||||
</div>
|
||||
</div>
|
||||
{this.props.data.data.quotes ? (
|
||||
<div className="infoItem">
|
||||
<MdFormatQuote />
|
||||
<div className="text">
|
||||
<span className="header">
|
||||
{variables.getMessage('modals.main.marketplace.product.no_quotes')}
|
||||
</span>
|
||||
<span>{this.props.data.data.quotes.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.props.data.data.photos ? (
|
||||
<div className="infoItem">
|
||||
<MdImage />
|
||||
<div className="text">
|
||||
<span className="header">
|
||||
{variables.getMessage('modals.main.marketplace.product.no_images')}
|
||||
</span>
|
||||
<span>{this.props.data.data.photos.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.props.data.data.quotes && this.props.data.data.language !== '' ? (
|
||||
<div className="infoItem">
|
||||
<MdTranslate />
|
||||
<div className="text">
|
||||
<span className="header">
|
||||
{variables.getMessage('modals.main.settings.sections.language.title')}
|
||||
</span>
|
||||
<span>{this.props.data.data.language}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="infoItem">
|
||||
<MdStyle />
|
||||
<div className="text">
|
||||
<span className="header">
|
||||
{' '}
|
||||
{variables.getMessage('modals.main.settings.sections.background.type.title')}
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
{variables.getMessage(
|
||||
'modals.main.addons.create.types.' + this.props.data.data.type,
|
||||
) || 'marketplace'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="itemInfo"
|
||||
style={{
|
||||
backgroundImage: `url("${
|
||||
variables.constants.DDG_IMAGE_PROXY + this.props.data.data.icon_url
|
||||
}")`,
|
||||
}}
|
||||
>
|
||||
<div className="front">
|
||||
<img
|
||||
className="icon"
|
||||
alt="icon"
|
||||
draggable={false}
|
||||
src={variables.constants.DDG_IMAGE_PROXY + this.props.data.data.icon_url}
|
||||
/>
|
||||
{this.props.button}
|
||||
<div className="iconButtons">
|
||||
<Tooltip title={variables.getMessage('widgets.quote.share')} key="share">
|
||||
<button onClick={() => this.setState({ shareModal: true })}>
|
||||
<MdIosShare />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.marketplace.product.buttons.report')}
|
||||
key="report"
|
||||
>
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
variables.constants.REPORT_ITEM +
|
||||
this.props.data.display_name.split(' ').join('+'),
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
>
|
||||
<MdFlag />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{this.props.data.data.collection ? (
|
||||
<div className="inCollection">
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.marketplace.product.part_of')}
|
||||
</span>
|
||||
<span className="title">{this.props.data.data.collection}</span>
|
||||
<button>{variables.getMessage('modals.main.marketplace.product.explore')}</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Item.propTypes = {
|
||||
data: PropTypes.object,
|
||||
addonInstalled: PropTypes.bool,
|
||||
addonInstalledVersion: PropTypes.string,
|
||||
toggleFunction: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Item;
|
||||
|
||||
@@ -1,17 +1,132 @@
|
||||
import variables from 'modules/variables';
|
||||
import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MdAutoFixHigh, MdOutlineArrowForward, MdOutlineOpenInNew } from 'react-icons/md';
|
||||
|
||||
export default function Items({ items, toggleFunction }) {
|
||||
function Items({
|
||||
type,
|
||||
items,
|
||||
collection,
|
||||
toggleFunction,
|
||||
collectionFunction,
|
||||
onCollection,
|
||||
filter,
|
||||
}) {
|
||||
return (
|
||||
<div className='items'>
|
||||
{items.map((item) => (
|
||||
<div className='item' onClick={() => toggleFunction(item.name)} key={item.name}>
|
||||
<img alt='icon' draggable='false' src={variables.constants.DDG_IMAGE_PROXY + item.icon_url} />
|
||||
<div className='details'>
|
||||
<h4>{item.display_name || item.name}</h4>
|
||||
<p>{item.author}</p>
|
||||
<>
|
||||
{(type === 'all' && !onCollection && (filter === null || filter === '')) ||
|
||||
(type === 'collections' && !onCollection && (filter === null || filter === '')) ? (
|
||||
<>
|
||||
<div
|
||||
className="collection"
|
||||
style={
|
||||
collection.news
|
||||
? { backgroundColor: collection.background_colour }
|
||||
: {
|
||||
backgroundImage: `linear-gradient(to right, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.7), transparent, rgba(0, 0, 0, 0.7), rgba(0 ,0, 0, 0.9)), url('${collection.img}')`,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div className="content">
|
||||
<span className="title">{collection.display_name}</span>
|
||||
<span className="subtitle">{collection.description}</span>
|
||||
</div>
|
||||
{collection.news === true ? (
|
||||
<a
|
||||
className="collectionButton"
|
||||
href={collection.news_link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{variables.getMessage('modals.main.marketplace.learn_more')} <MdOutlineOpenInNew />
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
className="collectionButton"
|
||||
onClick={() => collectionFunction(collection.name)}
|
||||
>
|
||||
<MdOutlineArrowForward />
|
||||
{variables.getMessage('modals.main.marketplace.explore_collection')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<div className="items">
|
||||
{items
|
||||
?.filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
filter === '' ||
|
||||
item.author.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
item.type.toLowerCase().includes(filter.toLowerCase()),
|
||||
)
|
||||
.map((item) => (
|
||||
<div className="item" onClick={() => toggleFunction(item)} key={item.name}>
|
||||
<img
|
||||
className="item-back"
|
||||
alt=""
|
||||
draggable={false}
|
||||
src={variables.constants.DDG_IMAGE_PROXY + item.icon_url}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<img
|
||||
className="item-icon"
|
||||
alt="icon"
|
||||
draggable={false}
|
||||
src={variables.constants.DDG_IMAGE_PROXY + item.icon_url}
|
||||
/>
|
||||
<div className="card-details">
|
||||
<span className="card-title">{item.display_name || item.name}</span>
|
||||
<span className="card-subtitle">
|
||||
{variables.getMessage('modals.main.marketplace.by', { author: item.author })}
|
||||
</span>
|
||||
{type === 'all' && !onCollection ? (
|
||||
<span className="card-type">
|
||||
{variables.getMessage(
|
||||
`modals.main.addons.create.types.${
|
||||
item.type.split('_')[0] === 'preset'
|
||||
? 'settings'
|
||||
: item.type.split('_')[0] + 's'
|
||||
}`,
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="loader"></div>
|
||||
{type === 'all' && !onCollection ? (
|
||||
<div className="createYourOwn">
|
||||
<MdAutoFixHigh />
|
||||
<span className="title">{variables.getMessage('modals.main.marketplace.cant_find')}</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.marketplace.knowledgebase_one') + ' '}
|
||||
<a
|
||||
className="link"
|
||||
target="_blank"
|
||||
href={variables.constants.KNOWLEDGEBASE}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{variables.getMessage('modals.main.marketplace.knowledgebase_two')}
|
||||
</a>
|
||||
{' ' + variables.getMessage('modals.main.marketplace.knowledgebase_three')}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Items.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
collection: PropTypes.object,
|
||||
toggleFunction: PropTypes.func.isRequired,
|
||||
collectionFunction: PropTypes.func.isRequired,
|
||||
onCollection: PropTypes.bool.isRequired,
|
||||
filter: PropTypes.string,
|
||||
};
|
||||
|
||||
export default memo(Items);
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import variables from 'modules/variables';
|
||||
|
||||
export default function Lightbox({ modalClose, img }) {
|
||||
function Lightbox({ modalClose, img }) {
|
||||
variables.stats.postEvent('modal', 'Opened lightbox');
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className='closeModal' onClick={modalClose}>×</span>
|
||||
<img src={img} className='lightboximg' draggable={false} alt='Item screenshot'/>
|
||||
<span className="closeModal" onClick={modalClose}>
|
||||
×
|
||||
</span>
|
||||
<img src={img} className="lightboximg" draggable={false} alt="Item screenshot" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Lightbox.propTypes = {
|
||||
modalClose: PropTypes.func.isRequired,
|
||||
img: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default memo(Lightbox);
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import variables from 'modules/variables';
|
||||
import { Close } from '@mui/icons-material';
|
||||
|
||||
export default function SideloadFailedModal({ modalClose, reason }) {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
import { MdClose } from 'react-icons/md';
|
||||
import Tooltip from 'components/helpers/tooltip/Tooltip';
|
||||
|
||||
function SideloadFailedModal({ modalClose, reason }) {
|
||||
return (
|
||||
<>
|
||||
<h1>{getMessage('modals.main.error_boundary.title')}</h1>
|
||||
<span>{getMessage('modals.main.addons.sideload.failed')}</span>
|
||||
<br/><br/>
|
||||
<span>{reason}</span>
|
||||
<div className='resetfooter'>
|
||||
<button className='round import' style={{ marginLeft: '-30px' }} onClick={modalClose}>
|
||||
<Close/>
|
||||
</button>
|
||||
<div className="smallModal">
|
||||
<div className="shareHeader">
|
||||
<span className="title">{variables.getMessage('modals.main.error_boundary.title')}</span>
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.reset_modal.cancel')}
|
||||
>
|
||||
<div className="close" onClick={modalClose}>
|
||||
<MdClose />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
<span>{variables.getMessage('modals.main.addons.sideload.failed')}</span>
|
||||
<span className="subtitle">{reason}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SideloadFailedModal.propTypes = {
|
||||
modalClose: PropTypes.func.isRequired,
|
||||
reason: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default memo(SideloadFailedModal);
|
||||
|
||||
25
src/components/modals/main/marketplace/examples/photos.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Example Photos",
|
||||
"description": "This is an example.",
|
||||
"type": "photos",
|
||||
"version": "1.0.0",
|
||||
"author": "Mue",
|
||||
"icon_url": "https://raw.githubusercontent.com/mue/branding/main/logo/logo_square.png",
|
||||
"screenshot_url": "https://github.com/mue/mue/raw/main/assets/screenshot.webp",
|
||||
"photos": [
|
||||
{
|
||||
"photographer": "Example photographer",
|
||||
"location": "Example location",
|
||||
"url": {
|
||||
"default": "https://github.com/mue/mue/raw/main/assets/screenshot.webp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"photographer": "Example photographer 2",
|
||||
"location": "Example location 2",
|
||||
"url": {
|
||||
"default": "https://github.com/mue/mue/raw/main/assets/screenshot2.webp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
20
src/components/modals/main/marketplace/examples/quotes.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Example Quotes",
|
||||
"description": "This is an example.",
|
||||
"type": "quotes",
|
||||
"version": "1.0.0",
|
||||
"author": "Mue",
|
||||
"icon_url": "https://raw.githubusercontent.com/mue/branding/main/logo/logo_square.png",
|
||||
"screenshot_url": "https://github.com/mue/mue/raw/main/assets/screenshot.webp",
|
||||
"language": "en",
|
||||
"quotes": [
|
||||
{
|
||||
"quote": "This is an example quote.",
|
||||
"author": "Example 1"
|
||||
},
|
||||
{
|
||||
"quote": "This is another example quote.",
|
||||
"author": "Example 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Example Settings",
|
||||
"description": "This is an example.",
|
||||
"type": "settings",
|
||||
"version": "1.0.0",
|
||||
"author": "Mue",
|
||||
"icon_url": "https://raw.githubusercontent.com/mue/branding/main/logo/logo_square.png",
|
||||
"screenshot_url": "https://github.com/mue/mue/raw/main/assets/screenshot.webp",
|
||||
"settings": {
|
||||
"searchBar": false,
|
||||
"weather": true
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,111 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { LocalMall } from '@mui/icons-material';
|
||||
import { MdUpdate, MdOutlineExtensionOff, MdCode } from 'react-icons/md';
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import SideloadFailedModal from '../SideloadFailedModal';
|
||||
import FileUpload from '../../settings/FileUpload';
|
||||
import Item from '../Item';
|
||||
import Items from '../Items';
|
||||
import Dropdown from '../../settings/Dropdown';
|
||||
|
||||
import { uninstall, urlParser } from 'modules/helpers/marketplace';
|
||||
import { install, uninstall, urlParser } from 'modules/helpers/marketplace';
|
||||
|
||||
export default class Added extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
installed: JSON.parse(localStorage.getItem('installed')),
|
||||
item: {},
|
||||
button: ''
|
||||
button: '',
|
||||
showFailed: false,
|
||||
failedReason: '',
|
||||
};
|
||||
this.buttons = {
|
||||
uninstall: <button className='removeFromMue' onClick={() => this.uninstall()}>{this.getMessage('modals.main.marketplace.product.buttons.remove')}</button>,
|
||||
uninstall: (
|
||||
<button className="removeFromMue" onClick={() => this.uninstall()}>
|
||||
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
installAddon(input) {
|
||||
let failedReason = '';
|
||||
if (!input.name) {
|
||||
failedReason = variables.getMessage('modals.main.addons.sideload.errors.no_name');
|
||||
} else if (!input.author) {
|
||||
failedReason = variables.getMessage('modals.main.addons.sideload.errors.no_author');
|
||||
} else if (!input.type) {
|
||||
failedReason = variables.getMessage('modals.main.addons.sideload.errors.no_type');
|
||||
} else if (!input.version) {
|
||||
failedReason = variables.getMessage('modals.main.addons.sideload.errors.no_version');
|
||||
} else if (
|
||||
input.type === 'photos' &&
|
||||
(!input.photos ||
|
||||
!input.photos.length ||
|
||||
!input.photos[0].url ||
|
||||
!input.photos[0].url.default ||
|
||||
!input.photos[0].photographer ||
|
||||
!input.photos[0].location)
|
||||
) {
|
||||
failedReason = variables.getMessage('modals.main.addons.sideload.errors.invalid_photos');
|
||||
} else if (
|
||||
input.type === 'quotes' &&
|
||||
(!input.quotes || !input.quotes.length || !input.quotes[0].quote || !input.quotes[0].author)
|
||||
) {
|
||||
failedReason = variables.getMessage('modals.main.addons.sideload.errors.invalid_quotes');
|
||||
}
|
||||
|
||||
if (failedReason !== '') {
|
||||
return this.setState({
|
||||
failedReason,
|
||||
showFailed: true,
|
||||
});
|
||||
}
|
||||
|
||||
install(input.type, input);
|
||||
toast(variables.getMessage('toasts.installed'));
|
||||
variables.stats.postEvent('marketplace', 'Sideload');
|
||||
}
|
||||
|
||||
toggle(type, data) {
|
||||
if (type === 'item') {
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
const info = installed.find((i) => i.name === data);
|
||||
const info = {
|
||||
data: installed.find((i) => i.name === data.name),
|
||||
};
|
||||
|
||||
this.setState({
|
||||
item: {
|
||||
type: info.type,
|
||||
name: data,
|
||||
display_name: info.name,
|
||||
author: info.author,
|
||||
description: urlParser(info.description.replace(/\n/g, '<br>')),
|
||||
type: info.data.type,
|
||||
display_name: info.data.name,
|
||||
author: info.data.author,
|
||||
description: urlParser(info.data.description.replace(/\n/g, '<br>')),
|
||||
//updated: info.updated,
|
||||
version: info.version,
|
||||
icon: info.screenshot_url,
|
||||
quote_api: info.quote_api || null
|
||||
version: info.data.version,
|
||||
icon: info.data.screenshot_url,
|
||||
data: info.data,
|
||||
},
|
||||
button: this.buttons.uninstall
|
||||
button: this.buttons.uninstall,
|
||||
});
|
||||
variables.stats.postEvent('marketplace', 'Item viewed');
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
item: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
uninstall(this.state.item.type, this.state.item.display_name);
|
||||
|
||||
toast(this.getMessage('toasts.uninstalled'));
|
||||
|
||||
toast(variables.getMessage('toasts.uninstalled'));
|
||||
|
||||
this.setState({
|
||||
button: '',
|
||||
installed: JSON.parse(localStorage.getItem('installed'))
|
||||
installed: JSON.parse(localStorage.getItem('installed')),
|
||||
});
|
||||
|
||||
variables.stats.postEvent('marketplace', 'Uninstall');
|
||||
@@ -84,7 +131,7 @@ export default class Added extends PureComponent {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
installed: installed
|
||||
installed,
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
@@ -95,18 +142,22 @@ export default class Added extends PureComponent {
|
||||
updateCheck() {
|
||||
let updates = 0;
|
||||
this.state.installed.forEach(async (item) => {
|
||||
const data = await (await fetch(variables.constants.MARKETPLACE_URL + '/item/' + item.name)).json();
|
||||
const data = await (
|
||||
await fetch(variables.constants.MARKETPLACE_URL + '/item/' + item.name)
|
||||
).json();
|
||||
if (data.version !== item.version) {
|
||||
updates++;
|
||||
}
|
||||
});
|
||||
|
||||
if (updates > 0) {
|
||||
toast(this.getMessage('modals.main.addons.updates_available', {
|
||||
amount: updates
|
||||
}));
|
||||
|
||||
if (updates > 0) {
|
||||
toast(
|
||||
variables.getMessage('modals.main.addons.updates_available', {
|
||||
amount: updates,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
toast(this.getMessage('modals.main.addons.no_updates'));
|
||||
toast(variables.getMessage('modals.main.addons.no_updates'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,33 +166,106 @@ export default class Added extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const sideLoadBackendElements = () => (
|
||||
<>
|
||||
<Modal
|
||||
closeTimeoutMS={100}
|
||||
onRequestClose={() => this.setState({ showFailed: false })}
|
||||
isOpen={this.state.showFailed}
|
||||
className="Modal resetmodal mainModal resetmodal"
|
||||
overlayClassName="Overlay resetoverlay"
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<SideloadFailedModal
|
||||
modalClose={() => this.setState({ showFailed: false })}
|
||||
reason={this.state.failedReason}
|
||||
/>
|
||||
</Modal>
|
||||
<FileUpload
|
||||
id="file-input"
|
||||
type="settings"
|
||||
accept="application/json"
|
||||
loadFunction={(e) => this.installAddon(JSON.parse(e))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
if (this.state.installed.length === 0) {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<LocalMall/>
|
||||
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
|
||||
<p className='description'>{this.getMessage('modals.main.addons.empty.description')}</p>
|
||||
<>
|
||||
<div className="flexTopMarketplace">
|
||||
<span className="mainTitle">{variables.getMessage('modals.main.navbar.addons')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter">
|
||||
{sideLoadBackendElements()}
|
||||
<button
|
||||
className="sideload"
|
||||
onClick={() => document.getElementById('file-input').click()}
|
||||
ref={this.customDnd}
|
||||
>
|
||||
{variables.getMessage('modals.main.addons.sideload.title')}
|
||||
<MdCode />
|
||||
</button>
|
||||
</div>
|
||||
<div className="emptyItems">
|
||||
<div className="emptyNewMessage">
|
||||
<MdOutlineExtensionOff />
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.addons.empty.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.addons.empty.description')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.item.display_name) {
|
||||
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} />;
|
||||
return (
|
||||
<Item
|
||||
data={this.state.item}
|
||||
button={this.state.button}
|
||||
toggleFunction={() => this.toggle()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown label={this.getMessage('modals.main.addons.sort.title')} name='sortAddons' onChange={(value) => this.sortAddons(value)}>
|
||||
<option value='newest'>{this.getMessage('modals.main.addons.sort.newest')}</option>
|
||||
<option value='oldest'>{this.getMessage('modals.main.addons.sort.oldest')}</option>
|
||||
<option value='a-z'>{this.getMessage('modals.main.addons.sort.a_z')}</option>
|
||||
<option value='z-a'>{this.getMessage('modals.main.addons.sort.z_a')}</option>
|
||||
</Dropdown>
|
||||
<button className='addToMue sideload updateCheck' onClick={() => this.updateCheck()}>{this.getMessage('modals.main.addons.check_updates')}</button>
|
||||
<br/>
|
||||
<Items items={this.state.installed} toggleFunction={(input) => this.toggle('item', input)} />
|
||||
<span className="mainTitle">{variables.getMessage('modals.main.addons.added')}</span>
|
||||
<div className="filter">
|
||||
{sideLoadBackendElements()}
|
||||
<div className="buttonSection">
|
||||
<button
|
||||
className="sideload"
|
||||
onClick={() => document.getElementById('file-input').click()}
|
||||
>
|
||||
{variables.getMessage('modals.main.addons.sideload.title')}
|
||||
<MdCode />
|
||||
</button>
|
||||
<button className="addToMue sideload updateCheck" onClick={() => this.updateCheck()}>
|
||||
<MdUpdate />
|
||||
{variables.getMessage('modals.main.addons.check_updates')}
|
||||
</button>
|
||||
</div>
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.addons.sort.title')}
|
||||
name="sortAddons"
|
||||
onChange={(value) => this.sortAddons(value)}
|
||||
>
|
||||
<option value="newest">{variables.getMessage('modals.main.addons.sort.newest')}</option>
|
||||
<option value="oldest">{variables.getMessage('modals.main.addons.sort.oldest')}</option>
|
||||
<option value="a-z">{variables.getMessage('modals.main.addons.sort.a_z')}</option>
|
||||
<option value="z-a">{variables.getMessage('modals.main.addons.sort.z_a')}</option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Items
|
||||
items={this.state.installed}
|
||||
filter=""
|
||||
toggleFunction={(input) => this.toggle('item', input)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
// todo: refactor all of this
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import {
|
||||
SettingsRounded as Settings,
|
||||
PhotoOutlined as Photos,
|
||||
FormatQuoteOutlined as Quotes,
|
||||
Upload as ImportIcon,
|
||||
Download as ExportIcon,
|
||||
} from '@mui/icons-material';
|
||||
import {
|
||||
MdSettings as Settings,
|
||||
MdOutlineInsertPhoto as Photos,
|
||||
MdOutlineFormatQuote as Quotes,
|
||||
MdUpload as ImportIcon,
|
||||
MdDownload as ExportIcon,
|
||||
MdArrowBack,
|
||||
MdDownload,
|
||||
MdOpenInNew,
|
||||
} from 'react-icons/md';
|
||||
import { TextField } from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
import SettingsItem from '../../../main/settings/SettingsItem';
|
||||
|
||||
import { saveFile } from 'modules/helpers/settings/modals';
|
||||
import InfoTooltip from '../../../../helpers/tooltip/infoTooltip';
|
||||
import Tooltip from '../../../../helpers/tooltip/Tooltip';
|
||||
|
||||
import FileUpload from '../../settings/FileUpload';
|
||||
import Dropdown from '../../settings/Dropdown';
|
||||
|
||||
import photos from '../examples/photos.json';
|
||||
import quotes from '../examples/quotes.json';
|
||||
import settings from '../examples/settings.json';
|
||||
|
||||
import '../../../welcome/welcome.scss';
|
||||
|
||||
@@ -29,37 +40,53 @@ export default class Create extends PureComponent {
|
||||
version: '',
|
||||
author: '',
|
||||
icon_url: '',
|
||||
screenshot_url: ''
|
||||
screenshot_url: '',
|
||||
},
|
||||
addonData: '',
|
||||
settingsClasses: {
|
||||
current: 'toggle lightTheme',
|
||||
json: 'toggle lightTheme'
|
||||
}
|
||||
json: 'toggle lightTheme',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
changeTab(tab, type) {
|
||||
changeTab(tab, type) {
|
||||
if (type) {
|
||||
return this.setState({
|
||||
return this.setState({
|
||||
currentTab: tab,
|
||||
addonMetadata: {
|
||||
type: type
|
||||
}
|
||||
type,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
currentTab: tab,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentTab: tab
|
||||
});
|
||||
}
|
||||
|
||||
importSettings(input) {
|
||||
const data = input || localStorage;
|
||||
let settings = {};
|
||||
const ignore = [
|
||||
'statsData',
|
||||
'firstRun',
|
||||
'showWelcome',
|
||||
'language',
|
||||
'installed',
|
||||
'stats',
|
||||
'backup_settings',
|
||||
'showReminder',
|
||||
'experimental',
|
||||
'debugtimeout',
|
||||
'quoteLanguage',
|
||||
'birthday',
|
||||
'location',
|
||||
'greetingName',
|
||||
'backgroundStartTime',
|
||||
];
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (key === 'statsData' || key === 'firstRun' || key === 'showWelcome' || key === 'language' || key === 'installed' || key === 'stats' || key === 'backup_settings' || key === 'showReminder'
|
||||
|| key === 'experimental' || key === 'debugtimeout' || key === 'quotelanguage') {
|
||||
if (ignore.includes(key)) {
|
||||
return;
|
||||
}
|
||||
settings[key] = localStorage.getItem(key);
|
||||
@@ -69,63 +96,19 @@ export default class Create extends PureComponent {
|
||||
addonData: settings,
|
||||
settingsClasses: {
|
||||
current: input ? 'toggle lightTheme active' : 'toggle lightTheme',
|
||||
json: input ? 'toggle lightTheme active' : 'toggle lightTheme'
|
||||
}
|
||||
json: input ? 'toggle lightTheme active' : 'toggle lightTheme',
|
||||
},
|
||||
});
|
||||
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
|
||||
}
|
||||
|
||||
updateQuotePackType(type) {
|
||||
if (type === 'quotePack') {
|
||||
this.setState({
|
||||
addonMetadata: {
|
||||
type,
|
||||
name: this.state.addonMetadata.name,
|
||||
description: this.state.addonMetadata.description,
|
||||
version: this.state.addonMetadata.version,
|
||||
author: this.state.addonMetadata.author,
|
||||
icon_url: this.state.addonMetadata.icon_url,
|
||||
screenshot_url: this.state.addonMetadata.screenshot_url,
|
||||
quotes: []
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
addonMetadata: {
|
||||
type,
|
||||
name: this.state.addonMetadata.name,
|
||||
description: this.state.addonMetadata.description,
|
||||
version: this.state.addonMetadata.version,
|
||||
author: this.state.addonMetadata.author,
|
||||
icon_url: this.state.addonMetadata.icon_url,
|
||||
screenshot_url: this.state.addonMetadata.screenshot_url
|
||||
},
|
||||
addonData: {
|
||||
url: '',
|
||||
name: '',
|
||||
author: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateQuotePackAPI(type, data) {
|
||||
this.setState({
|
||||
addonData: {
|
||||
url: (type === 'url') ? data : this.state.addonData.url || '',
|
||||
name: (type === 'name') ? data : this.state.addonData.name || '',
|
||||
author: (type === 'author') ? data : this.state.addonData.author || '',
|
||||
}
|
||||
});
|
||||
toast(variables.getMessage('toasts.imported'));
|
||||
}
|
||||
|
||||
importQuotes() {
|
||||
this.setState({
|
||||
addonData: JSON.parse(localStorage.getItem('customQuote')) || []
|
||||
addonData: JSON.parse(localStorage.getItem('customQuote')) || [],
|
||||
});
|
||||
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
|
||||
toast(variables.getMessage('toasts.imported'));
|
||||
}
|
||||
|
||||
importPhotos() {
|
||||
@@ -137,198 +120,403 @@ export default class Create extends PureComponent {
|
||||
photographer: '???',
|
||||
location: '???',
|
||||
url: {
|
||||
default: item
|
||||
}
|
||||
}
|
||||
default: item,
|
||||
},
|
||||
};
|
||||
});
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
|
||||
toast(variables.getMessage('toasts.imported'));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.error'));
|
||||
toast(variables.getMessage('toasts.error'));
|
||||
}
|
||||
|
||||
this.setState({
|
||||
addonData: data
|
||||
addonData: data,
|
||||
});
|
||||
}
|
||||
|
||||
downloadAddon() {
|
||||
saveFile({
|
||||
name: this.state.addonMetadata.name,
|
||||
description: this.state.addonMetadata.description,
|
||||
type: (this.state.addonMetadata.type === 'quote_api') ? 'quotes' : this.state.addonMetadata.type,
|
||||
version: this.state.addonMetadata.version,
|
||||
author: this.state.addonMetadata.author,
|
||||
icon_url: this.state.addonMetadata.icon_url,
|
||||
screenshot_url: this.state.addonMetadata.screenshot_url,
|
||||
[this.state.addonMetadata.type]: this.state.addonData
|
||||
}, this.state.addonMetadata.name + '.json');
|
||||
saveFile(
|
||||
{
|
||||
name: this.state.addonMetadata.name,
|
||||
description: this.state.addonMetadata.description,
|
||||
type: this.state.addonMetadata.type,
|
||||
version: this.state.addonMetadata.version,
|
||||
author: this.state.addonMetadata.author,
|
||||
icon_url: this.state.addonMetadata.icon_url,
|
||||
screenshot_url: this.state.addonMetadata.screenshot_url,
|
||||
[this.state.addonMetadata.type]: this.state.addonData,
|
||||
},
|
||||
this.state.addonMetadata.name + '.json',
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let tabContent;
|
||||
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
const chooseType = (
|
||||
<>
|
||||
<h3>{getMessage('modals.main.settings.sections.time.type')}</h3>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='options'>
|
||||
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'photos')}>
|
||||
<Photos/>
|
||||
<span>{getMessage('modals.main.marketplace.photo_packs')}</span>
|
||||
{/*<div className="smallBanner">
|
||||
<div className="content">
|
||||
<span className="title">Help Centre</span>
|
||||
<span className="subtitle">
|
||||
Home of all docs and guides on creating addons for Mue's marketplace
|
||||
</span>
|
||||
</div>
|
||||
<button>
|
||||
Open Site
|
||||
<MdOpenInNew />
|
||||
</button>
|
||||
</div>*/}
|
||||
<div className="themesToggleArea">
|
||||
<div className="options">
|
||||
<div className="toggle lightTheme" onClick={() => this.changeTab(2, 'photos')}>
|
||||
<Photos />
|
||||
<span>{variables.getMessage('modals.main.marketplace.photo_packs')}</span>
|
||||
</div>
|
||||
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'quotes')}>
|
||||
<Quotes/>
|
||||
<span>{getMessage('modals.main.marketplace.quote_packs')}</span>
|
||||
</div>
|
||||
<div className='toggle lightTheme' onClick={() => this.changeTab(2, 'settings')}>
|
||||
<Settings/>
|
||||
<span>{getMessage('modals.main.marketplace.preset_settings')}</span>
|
||||
<div className="toggle lightTheme" onClick={() => this.changeTab(2, 'quotes')}>
|
||||
<Quotes />
|
||||
<span>{variables.getMessage('modals.main.marketplace.quote_packs')}</span>
|
||||
</div>
|
||||
<div className="toggle lightTheme" onClick={() => this.changeTab(2, 'settings')}>
|
||||
<Settings />
|
||||
<span>{variables.getMessage('modals.main.marketplace.preset_settings')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// todo: find a better way to do all this
|
||||
const nextDescriptionDisabled = (this.state.addonMetadata.name !== undefined &&
|
||||
const nextDescriptionDisabled = !(
|
||||
this.state.addonMetadata.name !== undefined &&
|
||||
this.state.addonMetadata.description !== undefined &&
|
||||
this.state.addonMetadata.version !== undefined && this.state.addonMetadata.author !== undefined &&
|
||||
this.state.addonMetadata.icon_url !== undefined && this.state.addonMetadata.screenshot_url !== undefined)
|
||||
? false : true;
|
||||
|
||||
this.state.addonMetadata.version !== undefined &&
|
||||
this.state.addonMetadata.author !== undefined &&
|
||||
this.state.addonMetadata.icon_url !== undefined &&
|
||||
this.state.addonMetadata.screenshot_url !== undefined
|
||||
);
|
||||
|
||||
const setMetadata = (data, type) => {
|
||||
this.setState({
|
||||
addonMetadata: {
|
||||
name: (type === 'name') ? data : this.state.addonMetadata.name,
|
||||
description: (type === 'description') ? data : this.state.addonMetadata.description,
|
||||
version: (type === 'version') ? data : this.state.addonMetadata.version,
|
||||
author: (type === 'author') ? data : this.state.addonMetadata.author,
|
||||
icon_url: (type === 'icon_url') ? data : this.state.addonMetadata.icon_url,
|
||||
screenshot_url: (type === 'screenshot_url') ? data : this.state.addonMetadata.screenshot_url,
|
||||
type: this.state.addonMetadata.type
|
||||
}
|
||||
name: type === 'name' ? data : this.state.addonMetadata.name,
|
||||
description: type === 'description' ? data : this.state.addonMetadata.description,
|
||||
version: type === 'version' ? data : this.state.addonMetadata.version,
|
||||
author: type === 'author' ? data : this.state.addonMetadata.author,
|
||||
icon_url: type === 'icon_url' ? data : this.state.addonMetadata.icon_url,
|
||||
screenshot_url:
|
||||
type === 'screenshot_url' ? data : this.state.addonMetadata.screenshot_url,
|
||||
type: this.state.addonMetadata.type,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const writeDescription = (
|
||||
const writeDescription = (
|
||||
<>
|
||||
<h3>{getMessage('modals.main.marketplace.product.information')}</h3>
|
||||
<TextField label={getMessage('modals.main.addons.create.metadata.name')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.name} onInput={(e) => setMetadata(e.target.value, 'name')}/>
|
||||
<TextField label={getMessage('modals.main.marketplace.product.version')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.version} onInput={(e) => setMetadata(e.target.value, 'version')}/>
|
||||
<TextField label={getMessage('modals.main.marketplace.product.author')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.author} onInput={(e) => setMetadata(e.target.value, 'author')}/>
|
||||
<TextField label={getMessage('modals.main.addons.create.metadata.icon_url')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.icon_url} onInput={(e) => setMetadata(e.target.value, 'icon_url')}/>
|
||||
<TextField label={getMessage('modals.main.addons.create.metadata.screenshot_url')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.screenshot_url} onInput={(e) => setMetadata(e.target.value, 'screenshot_url')}/>
|
||||
<TextField label={getMessage('modals.main.addons.create.metadata.description')} varient='outlined' InputLabelProps={{ shrink: true }} multiline spellCheck={false} rows={4} InputLabelProps={{ shrink: true }} value={this.state.addonMetadata.description} onInput={(e) => setMetadata(e.target.value, 'description')}/>
|
||||
<br/>
|
||||
<button onClick={() => this.changeTab(1)} className='uploadbg' style={{ marginRight: '10px' }}>{getMessage('modals.welcome.buttons.previous')}</button>
|
||||
<button onClick={() => this.changeTab(this.state.addonMetadata.type)} className='uploadbg' disabled={nextDescriptionDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
|
||||
<div className="smallBanner">
|
||||
<div className="content">
|
||||
<span className="title" style={{ textTransform: 'capitalize' }}>
|
||||
{variables.getMessage(
|
||||
'modals.main.addons.create.types.' + this.state.addonMetadata.type,
|
||||
) || 'marketplace'}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.addons.create.descriptions.' + this.state.addonMetadata.type,
|
||||
) || 'marketplace'}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() =>
|
||||
saveFile(
|
||||
this.state.addonMetadata.type,
|
||||
`mue-marketplace-${this.state.addonMetadata.type}.json`,
|
||||
)
|
||||
}
|
||||
>
|
||||
{variables.getMessage('modals.main.addons.create.metadata.example')}
|
||||
<MdDownload />
|
||||
</button>
|
||||
</div>
|
||||
<SettingsItem title={variables.getMessage('modals.main.addons.create.metadata.name')}>
|
||||
<TextField
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={this.state.addonMetadata.name}
|
||||
onInput={(e) => setMetadata(e.target.value, 'name')}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<div className="settingsRow">
|
||||
<div className="content">
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.marketplace.product.version')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
<InfoTooltip
|
||||
title={variables.getMessage('modals.main.addons.create.information')}
|
||||
subtitle={variables.getMessage('modals.main.addons.create.information_subtitle')}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className="action">
|
||||
<TextField
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={this.state.addonMetadata.version}
|
||||
onInput={(e) => setMetadata(e.target.value, 'version')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsItem title={variables.getMessage('modals.main.marketplace.product.author')}>
|
||||
<TextField
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={this.state.addonMetadata.author}
|
||||
onInput={(e) => setMetadata(e.target.value, 'author')}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem title={variables.getMessage('modals.main.addons.create.metadata.icon_url')}>
|
||||
<TextField
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={this.state.addonMetadata.icon_url}
|
||||
onInput={(e) => setMetadata(e.target.value, 'icon_url')}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.addons.create.metadata.screenshot_url')}
|
||||
>
|
||||
<TextField
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={this.state.addonMetadata.screenshot_url}
|
||||
onInput={(e) => setMetadata(e.target.value, 'screenshot_url')}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.addons.create.metadata.description')}
|
||||
final={true}
|
||||
>
|
||||
<TextField
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
spellCheck={false}
|
||||
rows={4}
|
||||
value={this.state.addonMetadata.description}
|
||||
onInput={(e) => setMetadata(e.target.value, 'description')}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<div className="createButtons">
|
||||
<button onClick={() => this.changeTab(1)}>
|
||||
{variables.getMessage('modals.welcome.buttons.previous')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => this.changeTab(this.state.addonMetadata.type)}
|
||||
disabled={nextDescriptionDisabled}
|
||||
>
|
||||
{variables.getMessage('modals.welcome.buttons.next')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
// settings
|
||||
const nextSettingsDisabled = (this.state.addonData === '') ? true : false;
|
||||
const nextSettingsDisabled = this.state.addonData === '';
|
||||
const importSettings = (
|
||||
<>
|
||||
<h3>{getMessage('modals.welcome.sections.settings.title')}</h3>
|
||||
<div className='themesToggleArea' >
|
||||
<div className='options' style={{ maxWidth: '512px' }}>
|
||||
<div className={this.state.settingsClasses.current} onClick={() => this.importSettings()}>
|
||||
<ExportIcon/>
|
||||
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
|
||||
</div>
|
||||
<div className={this.state.settingsClasses.json} onClick={() => document.getElementById('file-input').click()}>
|
||||
<ImportIcon/>
|
||||
<span>{getMessage('modals.main.addons.create.settings.json')}</span>
|
||||
<SettingsItem
|
||||
final={true}
|
||||
title={variables.getMessage('modals.welcome.sections.settings.title')}
|
||||
>
|
||||
<div className="themesToggleArea">
|
||||
<div className="options" style={{ maxWidth: '512px' }}>
|
||||
<div
|
||||
className={this.state.settingsClasses.current}
|
||||
onClick={() => this.importSettings()}
|
||||
>
|
||||
<ExportIcon />
|
||||
<span>{variables.getMessage('modals.main.addons.create.settings.current')}</span>
|
||||
</div>
|
||||
<div
|
||||
className={this.state.settingsClasses.json}
|
||||
onClick={() => document.getElementById('file-input').click()}
|
||||
>
|
||||
<ImportIcon />
|
||||
<span>{variables.getMessage('modals.main.addons.create.settings.json')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
<SettingsItem final={true}>
|
||||
<FileUpload
|
||||
id="file-input"
|
||||
type="settings"
|
||||
accept="application/json"
|
||||
loadFunction={(e) => this.importSettings(JSON.parse(e))}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<div className="createButtons">
|
||||
<button onClick={() => this.changeTab(2)}>
|
||||
{variables.getMessage('modals.welcome.buttons.previous')}
|
||||
</button>
|
||||
<button onClick={() => this.changeTab(3)} disabled={nextSettingsDisabled}>
|
||||
{variables.getMessage('modals.welcome.buttons.next')}
|
||||
</button>
|
||||
</div>
|
||||
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.importSettings(JSON.parse(e.target.result))} />
|
||||
<br/><br/>
|
||||
<button onClick={() => this.changeTab(2)} className='uploadbg' style={{ margin: '10px' }}>{getMessage('modals.welcome.buttons.previous')}</button>
|
||||
<button onClick={() => this.changeTab(3)} className='uploadbg' style={{ margin: '10px' }} disabled={nextSettingsDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
// quotes
|
||||
const nextQuotesDisabled = ((this.state.addonMetadata.type === 'quote_api' && this.state.addonData.url !== '' && this.state.addonData.name !== '' && this.state.addonData.author !== '')
|
||||
|| (this.state.addonMetadata.type === 'quotes' && this.state.addonData.quotes !== '')) ? false : true;
|
||||
const nextQuotesDisabled = !(
|
||||
this.state.addonMetadata.type === 'quotes' && this.state.addonData.quotes !== ''
|
||||
);
|
||||
const addQuotes = (
|
||||
<>
|
||||
<h3>{getMessage('modals.main.addons.create.quotes.title')}</h3>
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.time.type')} noSetting onChange={(e) => this.updateQuotePackType(e)}>
|
||||
<option value='quotes'>{getMessage('modals.main.addons.create.quotes.local.title')}</option>
|
||||
<option value='quote_api'>{getMessage('modals.main.addons.create.quotes.api.title')}</option>
|
||||
</Dropdown>
|
||||
{this.state.addonMetadata.type === 'quote_api' ? <>
|
||||
<TextField label={getMessage('modals.main.addons.create.quotes.api.url')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonData.url} onInput={(e) => this.updateQuotePack(e.target.value, 'url')}/>
|
||||
<TextField label={getMessage('modals.main.addons.create.quotes.api.name')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonData.name} onInput={(e) => this.updateQuotePack(e.target.value, 'name')}/>
|
||||
<TextField label={getMessage('modals.main.addons.create.quotes.api.author')} varient='outlined' InputLabelProps={{ shrink: true }} value={this.state.addonData.author} onInput={(e) => this.updateQuotePack(e.target.value, 'author')}/>
|
||||
<br/><br/>
|
||||
</> : <>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='options'>
|
||||
<div onClick={() => this.importQuotes()} className='toggle lightTheme' style={{ width: '60%', margin: '10px 0 10px 0' }}>
|
||||
<ExportIcon/>
|
||||
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
|
||||
<SettingsItem
|
||||
final={true}
|
||||
title={variables.getMessage('modals.main.addons.create.quotes.title')}
|
||||
/>
|
||||
<SettingsItem
|
||||
final={true}
|
||||
title={variables.getMessage('modals.main.addons.create.settings.current')}
|
||||
>
|
||||
<div className="themesToggleArea">
|
||||
<div className="options">
|
||||
<div
|
||||
onClick={() => this.importQuotes()}
|
||||
className="toggle lightTheme"
|
||||
style={{ width: '60%', margin: '10px 0 10px 0' }}
|
||||
>
|
||||
<ExportIcon />
|
||||
<span>{variables.getMessage('modals.main.addons.create.settings.current')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
<div className="createButtons">
|
||||
<button onClick={() => this.changeTab(2)}>
|
||||
{variables.getMessage('modals.welcome.buttons.previous')}
|
||||
</button>
|
||||
<button onClick={() => this.changeTab(3)} disabled={nextQuotesDisabled}>
|
||||
{variables.getMessage('modals.welcome.buttons.next')}
|
||||
</button>
|
||||
</div>
|
||||
<br/>
|
||||
</>}
|
||||
<button onClick={() => this.changeTab(2)} className='uploadbg'>{getMessage('modals.welcome.buttons.previous')}</button>
|
||||
<button onClick={() => this.changeTab(3)} className='uploadbg' style={{ margin: '10px' }} disabled={nextQuotesDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
// photos
|
||||
const nextPhotosDisabled = (this.state.addonData.photos !== '' && this.state.addonData.photos !== []) ? false : true;
|
||||
const nextPhotosDisabled = !(this.state.addonData.photos !== '');
|
||||
const addPhotos = (
|
||||
<>
|
||||
<h3>{getMessage('modals.main.addons.create.photos.title')}</h3>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='options'>
|
||||
<div onClick={() => this.importPhotos()} className='toggle lightTheme' style={{ width: '60%', margin: '10px 0 10px 0' }}>
|
||||
<ExportIcon/>
|
||||
<span>{getMessage('modals.main.addons.create.settings.current')}</span>
|
||||
<SettingsItem
|
||||
final={true}
|
||||
title={variables.getMessage('modals.main.addons.create.photos.title')}
|
||||
subtitle={variables.getMessage('modals.main.addons.create.import_custom')}
|
||||
>
|
||||
<div className="themesToggleArea">
|
||||
<div className="options">
|
||||
<div
|
||||
onClick={() => this.importPhotos()}
|
||||
className="toggle lightTheme"
|
||||
style={{ width: '60%', margin: '10px 0 10px 0' }}
|
||||
>
|
||||
<ExportIcon />
|
||||
<span>{variables.getMessage('modals.main.addons.create.settings.current')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
<br />
|
||||
<div className="createButtons">
|
||||
<button onClick={() => this.changeTab(2)}>
|
||||
{variables.getMessage('modals.welcome.buttons.previous')}
|
||||
</button>
|
||||
<button onClick={() => this.changeTab(3)} disabled={nextPhotosDisabled}>
|
||||
{variables.getMessage('modals.welcome.buttons.next')}
|
||||
</button>
|
||||
</div>
|
||||
<br/>
|
||||
<button onClick={() => this.changeTab(2)} className='uploadbg'>{getMessage('modals.welcome.buttons.previous')}</button>
|
||||
<button onClick={() => this.changeTab(3)} className='uploadbg' style={{ margin: '10px' }} disabled={nextPhotosDisabled}>{getMessage('modals.welcome.buttons.next')}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
const downloadAddon = (
|
||||
<>
|
||||
<div className='themesToggleArea'>
|
||||
<div className='options'>
|
||||
<div onClick={() => this.downloadAddon()} className='toggle lightTheme' style={{ width: '60%', margin: '10px 0 10px 0' }}>
|
||||
<ExportIcon/>
|
||||
<span>{getMessage('modals.main.addons.create.finish.download')}</span>
|
||||
<div className="smallBanner">
|
||||
<div className="content">
|
||||
<span className="title" style={{ textTransform: 'capitalize' }}>
|
||||
{variables.getMessage('modals.main.addons.create.publishing.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.addons.create.publishing.subtitle')}
|
||||
</span>
|
||||
</div>
|
||||
<button>
|
||||
{variables.getMessage('modals.main.addons.create.publishing.button')}
|
||||
<MdOpenInNew />
|
||||
</button>
|
||||
</div>
|
||||
<SettingsItem final={true}>
|
||||
<div className="themesToggleArea">
|
||||
<div className="options">
|
||||
<div
|
||||
onClick={() => this.downloadAddon()}
|
||||
className="toggle lightTheme"
|
||||
style={{ width: '60%', margin: '10px 0 10px 0' }}
|
||||
>
|
||||
<ExportIcon />
|
||||
<span>{variables.getMessage('modals.main.addons.create.finish.download')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
<div className="createButtons">
|
||||
<button
|
||||
onClick={() => this.changeTab(this.state.addonMetadata.type)}
|
||||
disabled={nextDescriptionDisabled}
|
||||
>
|
||||
{variables.getMessage('modals.welcome.buttons.previous')}
|
||||
</button>
|
||||
</div>
|
||||
<br/>
|
||||
<button onClick={() => this.changeTab((this.state.addonMetadata.type === 'quote_api') ? 'quotes' : this.state.addonMetadata.type)} className='uploadbg' style={{ marginRight: '10px' }}>{getMessage('modals.welcome.buttons.previous')}</button>
|
||||
</>
|
||||
);
|
||||
|
||||
switch (this.state.currentTab) {
|
||||
case 2: tabContent = writeDescription; break;
|
||||
case 'settings': tabContent = importSettings; break;
|
||||
case 'quotes': tabContent = addQuotes; break;
|
||||
case 'photos': tabContent = addPhotos; break;
|
||||
case 3: tabContent = downloadAddon; break;
|
||||
default: tabContent = chooseType;
|
||||
case 2:
|
||||
tabContent = writeDescription;
|
||||
break;
|
||||
case 'settings':
|
||||
tabContent = importSettings;
|
||||
break;
|
||||
case 'quotes':
|
||||
tabContent = addQuotes;
|
||||
break;
|
||||
case 'photos':
|
||||
tabContent = addPhotos;
|
||||
break;
|
||||
case 3:
|
||||
tabContent = downloadAddon;
|
||||
break;
|
||||
default:
|
||||
tabContent = chooseType;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{getMessage('modals.main.addons.create.other_title')}</h2>
|
||||
<div className="flexTopMarketplace">
|
||||
{this.state.currentTab !== 1 && (
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.marketplace.product.buttons.back')}
|
||||
key="backArrow"
|
||||
>
|
||||
<div className="returnButton">
|
||||
<MdArrowBack className="backArrow" onClick={() => this.changeTab(1)} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.addons.create.other_title')}
|
||||
</span>
|
||||
</div>
|
||||
{tabContent}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { toast } from 'react-toastify';
|
||||
import { WifiOff, LocalMall } from '@mui/icons-material';
|
||||
import {
|
||||
MdWifiOff,
|
||||
MdLocalMall,
|
||||
MdOutlineKeyboardArrowRight,
|
||||
MdSearch,
|
||||
MdOutlineArrowForward,
|
||||
MdLibraryAdd,
|
||||
} from 'react-icons/md';
|
||||
|
||||
import Item from '../Item';
|
||||
import Items from '../Items';
|
||||
@@ -9,9 +17,7 @@ import Dropdown from '../../settings/Dropdown';
|
||||
|
||||
import { install, urlParser, uninstall } from 'modules/helpers/marketplace';
|
||||
|
||||
export default class Marketplace extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
class Marketplace extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@@ -19,11 +25,22 @@ export default class Marketplace extends PureComponent {
|
||||
button: '',
|
||||
featured: {},
|
||||
done: false,
|
||||
item: {}
|
||||
item: {},
|
||||
collection: false,
|
||||
filter: '',
|
||||
};
|
||||
this.buttons = {
|
||||
uninstall: <button className='removeFromMue' onClick={() => this.manage('uninstall')}>{this.getMessage('modals.main.marketplace.product.buttons.remove')}</button>,
|
||||
install: <button className='addToMue' onClick={() => this.manage('install')}>{this.getMessage('modals.main.marketplace.product.buttons.addtomue')}</button>
|
||||
uninstall: (
|
||||
<button onClick={() => this.manage('uninstall')}>
|
||||
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
|
||||
</button>
|
||||
),
|
||||
install: (
|
||||
<button onClick={() => this.manage('install')}>
|
||||
{variables.getMessage('modals.main.marketplace.product.buttons.addtomue')}
|
||||
<MdLibraryAdd />
|
||||
</button>
|
||||
),
|
||||
};
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
@@ -33,10 +50,18 @@ export default class Marketplace extends PureComponent {
|
||||
let info;
|
||||
// get item info
|
||||
try {
|
||||
info = await (await fetch(`${variables.constants.MARKETPLACE_URL}/item/${this.props.type}/${data}`, { signal: this.controller.signal })).json();
|
||||
let type = this.props.type;
|
||||
if (type === 'all' || type === 'collections') {
|
||||
type = data.type;
|
||||
}
|
||||
info = await (
|
||||
await fetch(`${variables.constants.MARKETPLACE_URL}/item/${type}/${data.name}`, {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
} catch (e) {
|
||||
if (this.controller.signal.aborted === false) {
|
||||
return toast(this.getMessage('toasts.error'));
|
||||
return toast(variables.getMessage('toasts.error'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,22 +98,57 @@ export default class Marketplace extends PureComponent {
|
||||
icon: info.data.screenshot_url,
|
||||
data: info.data,
|
||||
addonInstalled,
|
||||
addonInstalledVersion
|
||||
addonInstalledVersion,
|
||||
api_name: data.name,
|
||||
},
|
||||
button: button
|
||||
button: button,
|
||||
});
|
||||
|
||||
variables.stats.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
|
||||
} else if (type === 'collection') {
|
||||
this.setState({
|
||||
done: false,
|
||||
});
|
||||
const collection = await (
|
||||
await fetch(`${variables.constants.MARKETPLACE_URL}/collection/${data}`, {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
this.setState({
|
||||
items: collection.data.items,
|
||||
collectionTitle: collection.data.name,
|
||||
collectionDescription: collection.data.description,
|
||||
collectionImg: collection.data.img,
|
||||
collection: true,
|
||||
done: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
item: {}
|
||||
item: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getItems() {
|
||||
const { data } = await (await fetch(variables.constants.MARKETPLACE_URL + '/items/' + this.props.type, { signal: this.controller.signal })).json();
|
||||
const featured = await (await fetch(variables.constants.MARKETPLACE_URL + '/featured', { signal: this.controller.signal })).json();
|
||||
const dataURL =
|
||||
this.props.type === 'collections'
|
||||
? variables.constants.MARKETPLACE_URL + '/collections'
|
||||
: variables.constants.MARKETPLACE_URL + '/items/' + this.props.type;
|
||||
const { data } = await (
|
||||
await fetch(dataURL, {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
const featured = await (
|
||||
await fetch(variables.constants.MARKETPLACE_URL + '/featured', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
const collections = await (
|
||||
await fetch(variables.constants.MARKETPLACE_URL + '/collections', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
@@ -98,7 +158,8 @@ export default class Marketplace extends PureComponent {
|
||||
items: data,
|
||||
oldItems: data,
|
||||
featured: featured.data,
|
||||
done: true
|
||||
collections: collections.data,
|
||||
done: true,
|
||||
});
|
||||
|
||||
this.sortMarketplace(localStorage.getItem('sortMarketplace'), false);
|
||||
@@ -111,13 +172,40 @@ export default class Marketplace extends PureComponent {
|
||||
uninstall(this.state.item.type, this.state.item.display_name);
|
||||
}
|
||||
|
||||
toast(this.getMessage('toasts.' + type + 'ed'));
|
||||
toast(variables.getMessage('toasts.' + type + 'ed'));
|
||||
this.setState({
|
||||
button: (type === 'install') ? this.buttons.uninstall : this.buttons.install
|
||||
button: type === 'install' ? this.buttons.uninstall : this.buttons.install,
|
||||
});
|
||||
|
||||
variables.stats.postEvent('marketplace-item', `${this.state.item.display_name} ${(type === 'install' ? 'installed': 'uninstalled')}`);
|
||||
variables.stats.postEvent('marketplace', (type === 'install' ? 'Install': 'Uninstall'));
|
||||
variables.stats.postEvent(
|
||||
'marketplace-item',
|
||||
`${this.state.item.display_name} ${type === 'install' ? 'installed' : 'uninstalled'}`,
|
||||
);
|
||||
variables.stats.postEvent('marketplace', type === 'install' ? 'Install' : 'Uninstall');
|
||||
}
|
||||
|
||||
async installCollection() {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const installed = JSON.parse(localStorage.getItem('installed'));
|
||||
for (const item of this.state.items) {
|
||||
if (installed.some((i) => i.name === item.display_name)) continue; // don't install if already installed
|
||||
let { data } = await (
|
||||
await fetch(`${variables.constants.MARKETPLACE_URL}/item/${item.type}/${item.name}`, {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
install(data.type, data);
|
||||
variables.stats.postEvent('marketplace-item', `${item.display_name} installed}`);
|
||||
variables.stats.postEvent('marketplace', 'Install');
|
||||
}
|
||||
toast(variables.getMessage('toasts.installed'));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast(variables.getMessage('toasts.error'));
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
sortMarketplace(value, sendEvent) {
|
||||
@@ -140,7 +228,7 @@ export default class Marketplace extends PureComponent {
|
||||
|
||||
this.setState({
|
||||
items: items,
|
||||
sortType: value
|
||||
sortType: value,
|
||||
});
|
||||
|
||||
if (sendEvent) {
|
||||
@@ -148,6 +236,21 @@ export default class Marketplace extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
returnToMain() {
|
||||
this.setState({
|
||||
items: this.state.oldItems,
|
||||
collection: false,
|
||||
});
|
||||
}
|
||||
|
||||
reloadItems() {
|
||||
this.setState({
|
||||
done: false,
|
||||
});
|
||||
|
||||
this.getItems();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return;
|
||||
@@ -164,24 +267,40 @@ export default class Marketplace extends PureComponent {
|
||||
render() {
|
||||
const errorMessage = (msg) => {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
{msg}
|
||||
<>
|
||||
<div className="flexTopMarketplace">
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.navbar.marketplace')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="emptyItems">
|
||||
<div className="emptyMessage">{msg}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return errorMessage(<>
|
||||
<WifiOff/>
|
||||
<h1>{this.getMessage('modals.main.marketplace.offline.title')}</h1>
|
||||
<p className='description'>{this.getMessage('modals.main.marketplace.offline.description')}</p>
|
||||
</>);
|
||||
return errorMessage(
|
||||
<>
|
||||
<MdWifiOff />
|
||||
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
|
||||
<p className="description">
|
||||
{variables.getMessage('modals.main.marketplace.offline.description')}
|
||||
</p>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.done === false) {
|
||||
return errorMessage(<h1>{this.getMessage('modals.main.loading')}</h1>);
|
||||
return errorMessage(
|
||||
<>
|
||||
<div className="loaderHolder">
|
||||
<div id="loader"></div>
|
||||
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
|
||||
</div>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
const featured = () => {
|
||||
@@ -191,41 +310,170 @@ export default class Marketplace extends PureComponent {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='featured' style={{ backgroundColor: this.state.featured.colour }}>
|
||||
<div className="featured" style={{ backgroundColor: this.state.featured.colour }}>
|
||||
<p>{this.state.featured.title}</p>
|
||||
<h1>{this.state.featured.name}</h1>
|
||||
<button className='addToMue' onClick={() => openFeatured()}>{this.state.featured.buttonText}</button>
|
||||
<button className="addToMue" onClick={() => openFeatured()}>
|
||||
{this.state.featured.buttonText}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.items.length === 0) {
|
||||
if (this.state.items?.length === 0) {
|
||||
return (
|
||||
<>
|
||||
{featured()}
|
||||
{errorMessage(<>
|
||||
<LocalMall/>
|
||||
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
|
||||
<p className='description'>{this.getMessage('modals.main.marketplace.no_items')}</p>
|
||||
</>)}
|
||||
{errorMessage(
|
||||
<>
|
||||
<MdLocalMall />
|
||||
<h1>{variables.getMessage('modals.main.addons.empty.title')}</h1>
|
||||
<p className="description">
|
||||
{variables.getMessage('modals.main.marketplace.no_items')}
|
||||
</p>
|
||||
</>,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.item.display_name) {
|
||||
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} addonInstalled={this.state.item.addonInstalled} addonInstalledVersion={this.state.item.addonInstalledVersion}/>;
|
||||
return (
|
||||
<Item
|
||||
data={this.state.item}
|
||||
button={this.state.button}
|
||||
toggleFunction={() => this.toggle()}
|
||||
addonInstalled={this.state.item.addonInstalled}
|
||||
addonInstalledVersion={this.state.item.addonInstalledVersion}
|
||||
icon={this.state.item.screenshot_url}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{featured()}
|
||||
<br/>
|
||||
<Dropdown label={this.getMessage('modals.main.addons.sort.title')} name='sortMarketplace' onChange={(value) => this.sortMarketplace(value)}>
|
||||
<option value='a-z'>{this.getMessage('modals.main.addons.sort.a_z')}</option>
|
||||
<option value='z-a'>{this.getMessage('modals.main.addons.sort.z_a')}</option>
|
||||
</Dropdown>
|
||||
<Items items={this.state.items} toggleFunction={(input) => this.toggle('item', input)} />
|
||||
{this.state.collection === true ? (
|
||||
<>
|
||||
<div className="flexTopMarketplace">
|
||||
<span className="mainTitle" onClick={() => this.returnToMain()}>
|
||||
<span className="backTitle">
|
||||
{variables.getMessage('modals.main.navbar.marketplace')}
|
||||
</span>
|
||||
<MdOutlineKeyboardArrowRight />{' '}
|
||||
{variables.getMessage('modals.main.marketplace.collection')}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="collectionPage"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(to bottom, transparent, black), url('${this.state.collectionImg}')`,
|
||||
}}
|
||||
>
|
||||
<div className="nice-tag">
|
||||
{variables.getMessage('modals.main.marketplace.collection')}
|
||||
</div>
|
||||
<div className="content">
|
||||
<span className="mainTitle">{this.state.collectionTitle}</span>
|
||||
<span className="subtitle">{this.state.collectionDescription}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="addAllButton"
|
||||
onClick={() => this.installCollection()}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{variables.getMessage('modals.main.marketplace.add_all')}
|
||||
<MdLibraryAdd />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flexTopMarketplace">
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.navbar.marketplace')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="headerExtras marketplaceCondition">
|
||||
{this.props.type !== 'collections' ? (
|
||||
<div>
|
||||
<form className="marketplaceSearch">
|
||||
<input
|
||||
label={variables.getMessage('widgets.search')}
|
||||
placeholder={variables.getMessage('widgets.search')}
|
||||
name="filter"
|
||||
id="filter"
|
||||
value={this.state.filter}
|
||||
onChange={(event) => this.setState({ filter: event.target.value })}
|
||||
/>
|
||||
<MdSearch />
|
||||
</form>
|
||||
{/*<span className="link marketplaceRefresh" onClick={() => this.reloadItems()}>
|
||||
<MdRefresh /> {variables.getMessage('widgets.navbar.tooltips.refresh')}
|
||||
</span>*/}
|
||||
</div>
|
||||
) : null}
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.addons.sort.title')}
|
||||
name="sortMarketplace"
|
||||
onChange={(value) => this.sortMarketplace(value)}
|
||||
>
|
||||
<option value="a-z">{variables.getMessage('modals.main.addons.sort.a_z')}</option>
|
||||
<option value="z-a">{variables.getMessage('modals.main.addons.sort.z_a')}</option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{this.props.type === 'collections' && !this.state.collection ? (
|
||||
this.state.items.map((item) => (
|
||||
<>
|
||||
{!item.news ? (
|
||||
<div
|
||||
className="collection"
|
||||
style={
|
||||
item.news
|
||||
? { backgroundColor: item.background_colour }
|
||||
: {
|
||||
backgroundImage: `linear-gradient(to left, #000, transparent, #000), url('${item.img}')`,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div className="content">
|
||||
<span className="title">{item.display_name}</span>
|
||||
<span className="subtitle">{item.description}</span>
|
||||
</div>
|
||||
<button
|
||||
className="collectionButton"
|
||||
onClick={() => this.toggle('collection', item.name)}
|
||||
>
|
||||
<MdOutlineArrowForward />{' '}
|
||||
{variables.getMessage('modals.main.marketplace.explore_collection')}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
))
|
||||
) : (
|
||||
<Items
|
||||
type={this.props.type}
|
||||
items={this.state.items}
|
||||
collection={
|
||||
this.state.collections[Math.floor(Math.random() * this.state.collections.length)] ||
|
||||
[]
|
||||
}
|
||||
onCollection={this.state.collection}
|
||||
toggleFunction={(input) => this.toggle('item', input)}
|
||||
collectionFunction={(input) => this.toggle('collection', input)}
|
||||
filter={this.state.filter}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Marketplace.propTypes = {
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Marketplace;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { LocalMall } from '@mui/icons-material';
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import SideloadFailedModal from '../SideloadFailedModal';
|
||||
|
||||
import FileUpload from '../../settings/FileUpload';
|
||||
|
||||
import { install } from 'modules/helpers/marketplace';
|
||||
|
||||
export default class Sideload extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showFailed: false,
|
||||
failedReason: ''
|
||||
}
|
||||
}
|
||||
|
||||
installAddon(input) {
|
||||
let failedReason = '';
|
||||
if (!input.name) {
|
||||
failedReason = this.getMessage('modals.main.addons.sideload.errors.no_name');
|
||||
} else if (!input.author) {
|
||||
failedReason = this.getMessage('modals.main.addons.sideload.errors.no_author');
|
||||
} else if (!input.type) {
|
||||
failedReason = this.getMessage('modals.main.addons.sideload.errors.no_type');
|
||||
} else if (!input.version) {
|
||||
failedReason = this.getMessage('modals.main.addons.sideload.errors.no_version');
|
||||
} else if (input.type === 'photos' && (!input.photos || !input.photos.length || !input.photos[0].url || !input.photos[0].url.default || !input.photos[0].photographer || !input.photos[0].location)) {
|
||||
failedReason = this.getMessage('modals.main.addons.sideload.errors.invalid_photos');
|
||||
} else if (input.type === 'quotes' && (!input.quotes || !input.quotes.length || !input.quotes[0].quote || !input.quotes[0].author)) {
|
||||
failedReason = this.getMessage('modals.main.addons.sideload.errors.invalid_quotes');
|
||||
}
|
||||
|
||||
if (failedReason !== '') {
|
||||
return this.setState({
|
||||
failedReason,
|
||||
showFailed: true
|
||||
});
|
||||
}
|
||||
|
||||
install(input.type, input);
|
||||
toast(this.getMessage('toasts.installed'));
|
||||
variables.stats.postEvent('marketplace', 'Sideload');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.installAddon(JSON.parse(e.target.result))} />
|
||||
<LocalMall/>
|
||||
<h1>{this.getMessage('modals.main.addons.sideload.title')}</h1>
|
||||
<button className='addToMue sideload' onClick={() => document.getElementById('file-input').click()}>{this.getMessage('modals.main.settings.sections.background.source.upload')}</button>
|
||||
</div>
|
||||
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showFailed: false })} isOpen={this.state.showFailed} className='Modal resetmodal mainModal sideloadModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
|
||||
<SideloadFailedModal modalClose={() => this.setState({ showFailed: false })} reason={this.state.failedReason}/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,12 @@
|
||||
@import '../../../../scss/variables';
|
||||
|
||||
@import 'scss/variables';
|
||||
@import 'modules/sidebar';
|
||||
@import 'modules/navbar';
|
||||
@import 'modules/tab-content';
|
||||
@import 'modules/links';
|
||||
@import 'modules/scrollbars';
|
||||
|
||||
@import 'settings/main';
|
||||
@import 'settings/buttons';
|
||||
|
||||
@import 'marketplace/main';
|
||||
|
||||
.Modal {
|
||||
color: var(--modal-text);
|
||||
background-color: var(--background);
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
border: none;
|
||||
opacity: 1;
|
||||
z-index: -2;
|
||||
transition-timing-function: ease-in;
|
||||
border-radius: map-get($modal, 'border-radius');
|
||||
user-select: none;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #34495e #bdc3c7;
|
||||
position: relative;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
@@ -56,18 +16,55 @@
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.ReactModal__Content {
|
||||
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
.Modal {
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
box-shadow: 0 0 20px rgb(0 0 0 / 30%);
|
||||
opacity: 1;
|
||||
z-index: -2;
|
||||
transition-timing-function: ease-in;
|
||||
border-radius: map-get($modal, 'border-radius');
|
||||
user-select: none;
|
||||
overflow-y: auto;
|
||||
transform: scale(0);
|
||||
transition: all 300ms cubic-bezier(0.47, 1.64, 0.41, 0.8);
|
||||
transition: all 0.3s cubic-bezier(0.47, 1.64, 0.41, 0.8);
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.closePositioning {
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
right: 3rem;
|
||||
}
|
||||
|
||||
.closeModal {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0.5em;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgb(121 121 121 / 22.6%);
|
||||
}
|
||||
}
|
||||
|
||||
.ReactModal__Html--open,
|
||||
.ReactModal__Body--open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* modal transition */
|
||||
@@ -81,32 +78,12 @@
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 700px) {
|
||||
.ReactModal__Content {
|
||||
min-height: 500px;
|
||||
max-height: calc(100vh - 30vh);
|
||||
}
|
||||
}
|
||||
|
||||
/* main modal */
|
||||
.mainModal {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
#modal {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 80%;
|
||||
width: 60%;
|
||||
}
|
||||
height: 80vh;
|
||||
width: clamp(60vw, 1200px, 90vw);
|
||||
|
||||
@media (max-width: 1700px) {
|
||||
#modal {
|
||||
width: 80% !important;
|
||||
@include themed {
|
||||
background-color: t($modal-background);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,12 +107,264 @@ h5 {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
hr {
|
||||
height: 5px;
|
||||
background: rgba(196, 196, 196, 0.74);
|
||||
outline: none;
|
||||
border: none;
|
||||
margin: 50px 0 30px 0;
|
||||
.loaderHolder {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
@include themed {
|
||||
border: 3px solid t($modal-sidebar);
|
||||
border-radius: 50%;
|
||||
border-top-color: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.headerExtras {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: -1px;
|
||||
padding: 10px 0;
|
||||
|
||||
div:nth-child(1) {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.marketplaceCondition {
|
||||
display: flex;
|
||||
flex-flow: row !important;
|
||||
gap: -1px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.languageSettings {
|
||||
margin-bottom: 15px;
|
||||
|
||||
.MuiFormGroup-root {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.MuiFormControl-root {
|
||||
width: 100% !important;
|
||||
gap: 15px;
|
||||
|
||||
.MuiFormControlLabel-root {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
padding: 5px 5px 5px 20px;
|
||||
transition: 0.3s;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sliderTitle {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 15px 0;
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.moreSettings {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
padding: 25px;
|
||||
margin-top: 20px;
|
||||
transition: 0.5s;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.reminder-info {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 15px;
|
||||
gap: 15px;
|
||||
|
||||
@include themed {
|
||||
background-color: t($modal-secondaryColour);
|
||||
border-radius: t($borderRadius);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
button {
|
||||
@include basicIconButton(5px, 5px, modal);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
|
||||
svg {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
@extend %tabText;
|
||||
}
|
||||
|
||||
.quoteSkeleton {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.skeletonAuthor {
|
||||
font-size: smaller;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
padding: 0 20px 0 5px;
|
||||
|
||||
svg {
|
||||
@include themed {
|
||||
background-color: t($modal-sidebar);
|
||||
padding: 10px;
|
||||
border-radius: t($borderRadius);
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@include themed {
|
||||
background-color: t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: medium !important;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: smaller !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quickLinksSkeleton {
|
||||
.circles {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 5px;
|
||||
|
||||
div {
|
||||
margin-top: 10px;
|
||||
padding: 3px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@include themed {
|
||||
background-color: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clockSkeleton {
|
||||
font-size: 30px !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -1,104 +1,305 @@
|
||||
// this file is too long
|
||||
@import 'modules/item';
|
||||
@import 'modules/buttons';
|
||||
@import 'modules/featured';
|
||||
@import 'modules/lightbox';
|
||||
@import 'scss/variables';
|
||||
|
||||
.items {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 1.5rem;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
height: 80px;
|
||||
width: 260px;
|
||||
background: var(--sidebar);
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
transition: 0.5s;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 15px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
img {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
border-radius: 12px 0 0 12px;
|
||||
background: white;
|
||||
}
|
||||
@include themed {
|
||||
background-color: t($modal-secondaryColour);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
&:hover {
|
||||
background-color: t($modal-sidebarActive);
|
||||
|
||||
img,
|
||||
.details {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.details {
|
||||
position: absolute;
|
||||
left: 85px;
|
||||
top: -15px;
|
||||
|
||||
img {
|
||||
margin-left: 10px;
|
||||
height: 15px;
|
||||
img {
|
||||
background-color: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 5px;
|
||||
line-height: 20px;
|
||||
.tags {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.item-back {
|
||||
filter: blur(60px) saturate(180%) brightness(90%);
|
||||
position: absolute;
|
||||
object-fit: cover !important;
|
||||
height: 90px !important;
|
||||
width: 100px !important;
|
||||
border-radius: 100px;
|
||||
transition: 0.5s;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
object-fit: cover !important;
|
||||
height: 60px !important;
|
||||
width: 60px !important;
|
||||
border-radius: 12px;
|
||||
transition: 0.5s;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.card-details {
|
||||
padding: 10px;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 5px;
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 14px;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.card-type {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: bolder;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
border-radius: 150px;
|
||||
padding: 2px 8px;
|
||||
background-color: rgb(255 255 255 / 10%);
|
||||
border: 1px solid rgb(209 213 219 / 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.itemPage {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
|
||||
.itemShowcase {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 25px;
|
||||
width: 60%;
|
||||
max-width: 650px;
|
||||
|
||||
.description {
|
||||
max-lines: 3;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
max-width: 650px !important;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
}
|
||||
|
||||
.itemInfo {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 15px;
|
||||
width: 25%;
|
||||
max-height: 450px;
|
||||
|
||||
.front {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
box-sizing: border-box !important;
|
||||
border-radius: 12px 12px 0 0;
|
||||
backdrop-filter: blur(40px) saturate(150%) brightness(75%);
|
||||
|
||||
@include themed {
|
||||
background-image: linear-gradient(to bottom, transparent, t($modal-background));
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 5px 25px black;
|
||||
}
|
||||
|
||||
.divider {
|
||||
text-transform: uppercase;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
.iconButtons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-gap: 20px;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 2px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
box-shadow: 0 0 0 3px t($modal-sidebarActive);
|
||||
|
||||
span {
|
||||
&::before {
|
||||
content: '#';
|
||||
color: t($subColor);
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--tab-active);
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1920px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
.moreTag {
|
||||
padding: 2px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
box-shadow: 0 0 0 3px t($modal-sidebarActive);
|
||||
|
||||
span {
|
||||
&::before {
|
||||
content: '+';
|
||||
color: t($subColor);
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1680px) and (min-width: 1500px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.items {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.emptyitems {
|
||||
width: 25vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 90px;
|
||||
.emptyItems {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
grid-gap: 5px;
|
||||
padding: 50px;
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
@include themed {
|
||||
.sideloadIcon {
|
||||
font-size: 50px;
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyNewMessage {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-flow: column;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
|
||||
img {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 50px;
|
||||
margin-bottom: -20px;
|
||||
font-size: 70px;
|
||||
|
||||
/* background: -webkit-linear-gradient(90deg,rgba(255,92,39,.7) 37%,rgba(255,70,110,.67) 60%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent; */
|
||||
}
|
||||
|
||||
button {
|
||||
svg {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsRow {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,24 +307,310 @@ p.author {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
#item>img,
|
||||
.updateimage,
|
||||
.updatechangelog>p>img {
|
||||
#item > img,
|
||||
.updateImage,
|
||||
.updateChangelog > p > img {
|
||||
border-radius: 12px;
|
||||
height: 200px;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* sideload failed modal */
|
||||
.sideloadModal {
|
||||
min-width: 250px;
|
||||
max-width: 250px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.returnButton {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
@media (max-height: 1080px) {
|
||||
.dropdownsortAddons {
|
||||
margin-top: 40px !important;
|
||||
svg {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgb(121 121 121 / 22.6%);
|
||||
}
|
||||
}
|
||||
|
||||
.flexTopMarketplace {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.tooltip {
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.mainTitle {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.itemWarning {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
box-shadow: 0 0 0 4px t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.topRow {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
|
||||
.truncate {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
padding: 15px;
|
||||
gap: 15px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: 0 0 0 4px t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
.MuiFormControl-root {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.collectionPage {
|
||||
// height: 200px;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@include themed {
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
|
||||
.nice-tag {
|
||||
border-radius: 150px;
|
||||
padding: 1px 12px;
|
||||
backdrop-filter: blur(16px) saturate(180%);
|
||||
background-color: rgb(255 255 255 / 10%);
|
||||
border: 1px solid rgb(209 213 219 / 30%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
text-align: center;
|
||||
text-shadow: #000 0 0 15px;
|
||||
|
||||
.mainTitle {
|
||||
justify-content: center;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #ccc !important;
|
||||
}
|
||||
}
|
||||
|
||||
.addAllButton {
|
||||
margin: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 1px 12px;
|
||||
backdrop-filter: blur(16px) saturate(180%) !important;
|
||||
background-color: rgb(255 255 255 / 10%) !important;
|
||||
border: 1px solid rgb(209 213 219 / 30%) !important;
|
||||
color: #fff !important;
|
||||
|
||||
&:hover {
|
||||
backdrop-filter: blur(16px) saturate(180%) !important;
|
||||
background-color: rgb(17 25 40 / 20%) !important;
|
||||
border: 1px solid rgb(255 255 255 / 12.5%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collection {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 36px 48px;
|
||||
margin: 15px 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
align-items: center;
|
||||
|
||||
@include themed {
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 15px;
|
||||
max-width: 250px;
|
||||
text-shadow: #000 0 0 15px;
|
||||
|
||||
.title {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #ccc !important;
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
button.collectionButton,
|
||||
a.collectionButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 1px 12px;
|
||||
backdrop-filter: blur(16px) saturate(180%);
|
||||
background-color: rgb(255 255 255 / 10%);
|
||||
border: 1px solid rgb(209 213 219 / 30%);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
backdrop-filter: blur(16px) saturate(180%);
|
||||
background-color: rgb(17 25 40 / 20%);
|
||||
border: 1px solid rgb(255 255 255 / 12.5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.collectionButton {
|
||||
height: 40px;
|
||||
text-decoration: none;
|
||||
|
||||
@include themed {
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
}
|
||||
|
||||
.smallBanner {
|
||||
button {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
align-items: center;
|
||||
|
||||
@include themed {
|
||||
box-shadow: 0 0 0 4px t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
background: t($modal-sidebar);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 15px;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.marketplaceRefresh {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.marketplaceSearch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 30px;
|
||||
border-radius: 10px;
|
||||
font-size: 18px;
|
||||
|
||||
@include themed {
|
||||
box-shadow: 0 0 0 3px t($modal-sidebarActive);
|
||||
background: t($modal-sidebar);
|
||||
}
|
||||
|
||||
input {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
@include themed {
|
||||
&:focus-within {
|
||||
background: t($modal-sidebarActive);
|
||||
box-shadow: 0 0 0 1px t($color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: t($modal-sidebarActive);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inCollection {
|
||||
background-image: linear-gradient(to left, transparent, #000),
|
||||
url('https://external-preview.redd.it/JyhsEoGMhKIMi3kvfBS24L0IllAO_KrIm4UI-dA1Ax4.jpg?auto=webp&s=b5adf9859b2c1855a5b3085f9453a6e878548505');
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
|
||||
@include themed {
|
||||
box-shadow: 0 0 0 4px t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
}
|
||||
|
||||
.createYourOwn {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
|
||||
svg {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%storebutton {
|
||||
%storeButton {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: block;
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dark %storebutton {
|
||||
.dark %storeButton {
|
||||
border: 2px solid map-get($theme-colours, 'main');
|
||||
color: map-get($theme-colours, 'main');
|
||||
|
||||
@@ -24,44 +24,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.removeFromMue {
|
||||
@extend %storebutton;
|
||||
|
||||
border: 2px solid #ff4757;
|
||||
color: #ff4757;
|
||||
float: right;
|
||||
margin-top: -10px;
|
||||
|
||||
&:hover {
|
||||
background: #ff4757;
|
||||
color: map-get($theme-colours, 'main');
|
||||
}
|
||||
}
|
||||
|
||||
.addToMue {
|
||||
@extend %storebutton;
|
||||
|
||||
float: right;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.sideload {
|
||||
display: inline;
|
||||
margin-top: 0px;
|
||||
margin-top: 0;
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
button.round {
|
||||
margin-left: 5px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 3px;
|
||||
vertical-align: middle;
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.updateCheck {
|
||||
margin-top: 15px;
|
||||
flex-flow: row !important;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
.featured {
|
||||
margin-top: 40px;
|
||||
border-radius: 15px;
|
||||
padding: 50px;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
|
||||
width: 85%;
|
||||
width: calc(100% - 6rem);
|
||||
margin-top: 13px;
|
||||
|
||||
button {
|
||||
float: left;
|
||||
|
||||
@@ -1,90 +1,101 @@
|
||||
#item {
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
float: left;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--modal-link);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#item>h1,
|
||||
#item>.MuiSvgIcon-root {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.description {
|
||||
margin-top: 0px;
|
||||
margin-top: 0;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.backArrow {
|
||||
cursor: pointer;
|
||||
width: 2rem !important;
|
||||
height: 2rem !important;
|
||||
|
||||
&:hover {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
|
||||
.informationContainer {
|
||||
margin-top: 150px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.productInformation {
|
||||
padding: 10px;
|
||||
background: var(--sidebar);
|
||||
width: 350px;
|
||||
border-radius: 12px;
|
||||
min-height: 180px;
|
||||
.moreInfo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
width: calc(100% - 30px);
|
||||
gap: 25px;
|
||||
|
||||
h4 {
|
||||
cursor: initial !important;
|
||||
.items {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-left: -4px;
|
||||
list-style: none;
|
||||
font-size: 16px;
|
||||
cursor: initial !important;
|
||||
.item {
|
||||
flex: 1 0 40% !important;
|
||||
}
|
||||
|
||||
&.header {
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
box-shadow: t(boxshadow);
|
||||
border-radius: t($borderRadius);
|
||||
padding: 15px;
|
||||
|
||||
.infoItem {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex: 1 0 44%;
|
||||
|
||||
svg {
|
||||
font-size: 25px;
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
text-transform: uppercase;
|
||||
color: #787878;
|
||||
margin-left: -5px;
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
span {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.side {
|
||||
margin-top: 222px;
|
||||
float: none !important;
|
||||
}
|
||||
.itemTitle {
|
||||
font-size: 38px;
|
||||
font-weight: 600;
|
||||
|
||||
.overview {
|
||||
margin-top: -160px !important;
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.overview {
|
||||
font-size: 30px !important;
|
||||
margin-top: 33px;
|
||||
.titleTop {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.showMore {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: 0.5s;
|
||||
cursor: pointer;
|
||||
|
||||
@include themed {
|
||||
&:hover {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.showMoreItems {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.lightboxmodal {
|
||||
.lightBoxModal {
|
||||
margin: auto;
|
||||
max-width: 60%;
|
||||
background: none !important;
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
.navbar-item {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: var(--photo-info);
|
||||
flex-flow: row !important;
|
||||
padding: 0 15px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-secondaryColour) !important;
|
||||
border-radius: t($borderRadius) !important;
|
||||
box-shadow: t($boxShadow) !important;
|
||||
border: 0 !important;
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
background: var(--tab-active);
|
||||
}
|
||||
|
||||
color: var(--modal-text)
|
||||
color: var(--modal-text);
|
||||
}
|
||||
|
||||
span,
|
||||
@@ -21,9 +28,6 @@
|
||||
|
||||
svg {
|
||||
font-size: 1.2em !important;
|
||||
width: 60px;
|
||||
padding: 5px;
|
||||
border-radius: 20px;
|
||||
color: var(--photo-info);
|
||||
}
|
||||
}
|
||||
@@ -35,38 +39,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.modalNavbar {
|
||||
position: absolute;
|
||||
left: 20rem;
|
||||
top: 1rem;
|
||||
justify-content: center;
|
||||
#modal {
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
margin-right: 0.5rem;
|
||||
padding: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.modalNavbar {
|
||||
left: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
li.navbar-item {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.modalNavbar {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 25px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.navbar-item-active {
|
||||
color: var(--modal-text);
|
||||
|
||||
svg {
|
||||
background: var(--sidebar);
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
@import 'scss/variables';
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-top-right-radius: map-get($modal, 'border-radius');
|
||||
border-bottom-right-radius: map-get($modal, 'border-radius');
|
||||
border-radius: 12px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #636e72;
|
||||
border-top-right-radius: map-get($modal, 'border-radius');
|
||||
border-bottom-right-radius: map-get($modal, 'border-radius');
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -1,87 +1,75 @@
|
||||
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: 110vh;
|
||||
@import 'scss/variables';
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.sidebar {
|
||||
@include themed {
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: sticky;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: t($modal-sidebar);
|
||||
border-radius: 12px 0 0 12px;
|
||||
overflow-y: auto;
|
||||
height: 80vh;
|
||||
min-width: 250px;
|
||||
overflow-x: hidden;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 3px;
|
||||
background: rgba(196, 196, 196, 0.74);
|
||||
width: 75%;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 999px) and (min-height: 920px) {
|
||||
ul.sidebar {
|
||||
min-height: 160vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 919px) and (min-height: 700px) {
|
||||
ul.sidebar {
|
||||
min-height: 200vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 699px) and (min-height: 400px) {
|
||||
ul.sidebar {
|
||||
min-height: 260vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
ul.sidebar {
|
||||
width: 310px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
font-size: 24px;
|
||||
padding: 5px 30px 5px 30px;
|
||||
cursor: pointer;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.tab-list-active {
|
||||
background: var(--tab-active);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
li.tab-list-item {
|
||||
span {
|
||||
display: none;
|
||||
.mainTitle {
|
||||
text-align: center;
|
||||
font-size: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
ul.sidebar {
|
||||
h1 {
|
||||
display: none;
|
||||
svg {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
color: t($subColor);
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
background: #ccc;
|
||||
margin: 0 1.75rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
color: t($color);
|
||||
font-size: 18px;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.2rem;
|
||||
padding: 0.5rem;
|
||||
transition: 0.5s;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: none;
|
||||
min-width: calc(100% - 1.2em);
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: t($modal-sidebarActive);
|
||||
box-shadow: 0 0 0 0.5px t($color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: t($modal-sidebarActive);
|
||||
box-shadow: 0 0 0 0.5px t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-list-active {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-list-item {
|
||||
&:hover {
|
||||
background: var(--tab-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,194 @@
|
||||
@import 'scss/variables';
|
||||
|
||||
.tab-content {
|
||||
position: absolute;
|
||||
button {
|
||||
@include modal-button(standard);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
@include themed {
|
||||
padding: 1rem 3rem 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
background: t($modal-background);
|
||||
|
||||
@extend %tabText;
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
background: rgb(196 196 196 / 74%);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settingsRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 100px;
|
||||
justify-content: space-between;
|
||||
|
||||
/* border-top: 1px solid #ccc; */
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
&.settingsNoBorder {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: flex-end;
|
||||
width: 300px;
|
||||
|
||||
button {
|
||||
margin-top: 10px;
|
||||
width: 283px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 2300px) {
|
||||
.tab-content {
|
||||
left: 350px;
|
||||
top: 7%;
|
||||
.activityButtons {
|
||||
justify-content: space-between !important;
|
||||
align-items: flex-end !important;
|
||||
align-content: space-between !important;
|
||||
flex-flow: row wrap !important;
|
||||
|
||||
button:not(:first-child) {
|
||||
width: 40% !important;
|
||||
height: 99px !important;
|
||||
flex-flow: column-reverse !important;
|
||||
}
|
||||
|
||||
button {
|
||||
@include modal-button(standard);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1920px) {
|
||||
.tab-content {
|
||||
left: 120px;
|
||||
top: 60px;
|
||||
table {
|
||||
border-collapse: separate;
|
||||
|
||||
@include themed {
|
||||
border-radius: t($borderRadius);
|
||||
margin-top: 20px;
|
||||
|
||||
tr:first-child {
|
||||
background: t($modal-sidebarActive);
|
||||
|
||||
th {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
tr {
|
||||
th:last-child {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
tr:not(:first-child) {
|
||||
background: t($modal-sidebar);
|
||||
|
||||
textarea {
|
||||
width: 90%;
|
||||
margin: 10px;
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1920px) {
|
||||
.tab-content {
|
||||
left: 350px;
|
||||
top: 9%;
|
||||
.donateButton {
|
||||
@include modal-button(standard);
|
||||
|
||||
flex-flow: row !important;
|
||||
text-decoration: none;
|
||||
height: auto !important;
|
||||
|
||||
svg {
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1400px),
|
||||
(min-width: 1400px) {
|
||||
.tab-content {
|
||||
left: 350px;
|
||||
.flexGrow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.messageMap {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 25px;
|
||||
padding: 25px;
|
||||
justify-content: space-between;
|
||||
|
||||
div:nth-child(1) {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 25px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
border-radius: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 15px;
|
||||
font-size: 25px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
|
||||
.messageText {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex-grow: 3;
|
||||
|
||||
textarea {
|
||||
@include themed {
|
||||
color: t($color);
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageAction {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.tab-content {
|
||||
left: 125px;
|
||||
top: 90px;
|
||||
}
|
||||
.messagesContainer {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
@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;
|
||||
|
||||
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,
|
||||
.close {
|
||||
@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 {
|
||||
padding: 10px 50px 10px 50px;
|
||||
}
|
||||
|
||||
.export,
|
||||
.uploadbg,
|
||||
.import {
|
||||
width: 440px;
|
||||
height: 60px;
|
||||
background-color: var(--sidebar);
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--modal-text);
|
||||
border-radius: 12px;
|
||||
margin-right: 25px;
|
||||
width: 220px;
|
||||
cursor: pointer;
|
||||
border-radius: 24px;
|
||||
border: 3px solid var(--tab-active);
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--tab-active);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
background: none;
|
||||
border: 1px solid var(--tab-active);
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
border: 1px solid var(--tab-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.export,
|
||||
.import {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.round-small {
|
||||
height: 10px !important;
|
||||
width: 10px !important;
|
||||
}
|
||||
|
||||
.data-buttons-row {
|
||||
width: 350px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
button {
|
||||
background: var(--sidebar);
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
border: 3px solid var(--tab-active);
|
||||
height: 40px;
|
||||
font-size: 1rem;
|
||||
margin: 0 10px 10px 0;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
color: var(--modal-text);
|
||||
|
||||
&:hover {
|
||||
background: var(--tab-active);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.customvideoicon {
|
||||
position: absolute;
|
||||
margin-bottom: 45px;
|
||||
font-size: 3em !important;
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
@import 'modules/resetmodal';
|
||||
@import 'scss/variables';
|
||||
@import 'modules/material-ui';
|
||||
@import 'modules/reminder';
|
||||
|
||||
@import 'modules/tabs/about';
|
||||
@import 'modules/tabs/changelog';
|
||||
@import 'modules/tabs/order';
|
||||
|
||||
input {
|
||||
/* colour picker */
|
||||
&[type=color] {
|
||||
&[type='color'] {
|
||||
border-radius: 100%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border: none;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
|
||||
@include themed {
|
||||
border: t($modal-sidebarActive) 1px solid;
|
||||
}
|
||||
|
||||
&::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -29,13 +32,13 @@ input {
|
||||
}
|
||||
|
||||
/* firefox fixes for colour picker (using "," didn't work) */
|
||||
&[type=color]::-moz-color-swatch {
|
||||
&[type='color']::-moz-color-swatch {
|
||||
border-radius: 100%;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border: none;
|
||||
outline: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
|
||||
@@ -50,11 +53,15 @@ input {
|
||||
}
|
||||
|
||||
/* date picker */
|
||||
&[type=date] {
|
||||
width: 280px;
|
||||
color: var(--modal-text);
|
||||
background: var(--background);
|
||||
border: solid var(--modal-text) 1px;
|
||||
&[type='date'] {
|
||||
width: 260px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebar);
|
||||
border: 3px solid t($modal-sidebarActive);
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
display: flex !important;
|
||||
@@ -77,10 +84,187 @@ h4 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.keybind-table {
|
||||
text-align: left;
|
||||
.photosEmpty {
|
||||
height: 400px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
th {
|
||||
padding-right: 10px;
|
||||
button {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.imagesTopBar {
|
||||
padding-top: 25px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
div:nth-child(1) {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
svg {
|
||||
font-size: 30px;
|
||||
|
||||
@include themed {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
.topbarbuttons {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 30px;
|
||||
width: 100%;
|
||||
|
||||
.rightPanel {
|
||||
.statIcon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.statGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-gap: 10px;
|
||||
div {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
padding-top: 20px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
.settingsReturn {
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
|
||||
svg {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgb(121 121 121 / 22.6%);
|
||||
}
|
||||
}
|
||||
|
||||
.returnButton {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
margin-right: 25px;
|
||||
|
||||
svg {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgb(121 121 121 / 22.6%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.achievements {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.achievement {
|
||||
padding: 20px 10px;
|
||||
display: flex;
|
||||
flex-flow: row !important;
|
||||
align-items: center;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-secondaryColour);
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
border-radius: t($borderRadius);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 20px !important;
|
||||
padding: 15px;
|
||||
border-radius: 100%;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.statSection.rightPanel {
|
||||
padding: 25px;
|
||||
|
||||
@include themed {
|
||||
border-radius: t($borderRadius);
|
||||
background: t($modal-secondaryColour);
|
||||
box-shadow: 0 0 0 1px t($modal-sidebarActive);
|
||||
|
||||
svg {
|
||||
font-size: 50px;
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.achievementContent {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 2px;
|
||||
|
||||
span:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.customcss textarea {
|
||||
font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter',
|
||||
'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco,
|
||||
'Courier New', Courier, monospace !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/* these are overrides for the material ui default styles */
|
||||
|
||||
@import 'scss/variables';
|
||||
|
||||
.MuiCheckbox-colorPrimary.Mui-checked,
|
||||
.MuiSwitch-colorPrimary.Mui-checked,
|
||||
.MuIconButton-colorPrimary.Mui-checked,
|
||||
@@ -9,19 +12,31 @@
|
||||
.aboutLink,
|
||||
.MuiSlider-colorPrimary,
|
||||
legend {
|
||||
color: var(--modal-text) !important;
|
||||
@include themed {
|
||||
color: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiFormControlLabel-labelPlacementStart {
|
||||
margin-left: 0px !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.MuiSwitch-colorPrimary.Mui-checked+.MuiSwitch-track {
|
||||
background: darkgray !important;
|
||||
.MuiSwitch-colorPrimary.Mui-checked + .MuiSwitch-track {
|
||||
@include themed {
|
||||
background-color: t($subColor) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiIconButton-label>svg.MuiSvgIcon-root {
|
||||
color: var(--modal-text) !important;
|
||||
.MuiSwitch-track {
|
||||
@include themed {
|
||||
background-color: t($subColor) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiIconButton-label > svg.MuiSvgIcon-root {
|
||||
@include themed {
|
||||
color: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiTouchRipple-root {
|
||||
@@ -33,7 +48,9 @@ legend {
|
||||
}
|
||||
|
||||
.checkbox svg {
|
||||
fill: var(--modal-text) !important;
|
||||
@include themed {
|
||||
fill: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-title {
|
||||
@@ -51,11 +68,15 @@ legend {
|
||||
}
|
||||
|
||||
.MuiOutlinedInput-notchedOutline {
|
||||
border-color: var(--modal-text) !important;
|
||||
@include themed {
|
||||
border-color: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiFormLabel-root-MuiInputLabel-root {
|
||||
color: var(--modal-text) !important;
|
||||
@include themed {
|
||||
color: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiInputLabel-root,
|
||||
@@ -66,23 +87,37 @@ legend {
|
||||
.Mui-focused,
|
||||
legend,
|
||||
.MuiOutlinedInput-input {
|
||||
color: var(--modal-text) !important;
|
||||
@include themed {
|
||||
color: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiMenu-list {
|
||||
background-color: var(--background) !important;
|
||||
color: var(--modal-text) !important;
|
||||
@include themed {
|
||||
background-color: t($modal-background);
|
||||
color: t($color);
|
||||
}
|
||||
|
||||
li {
|
||||
&:hover {
|
||||
@include themed {
|
||||
background-color: t($modal-sidebarActive);
|
||||
transition: 0.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Mui-selected {
|
||||
background-color: var(--tab-active) !important;
|
||||
@include themed {
|
||||
background-color: t($modal-sidebarActive) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiTextField-root,
|
||||
.MuiFormControl-root,
|
||||
.MuiSlider-root {
|
||||
width: 300px !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.Mui-disabled {
|
||||
@@ -95,9 +130,43 @@ legend,
|
||||
}
|
||||
|
||||
.MuiPaper-root {
|
||||
background-color: var(--background) !important;
|
||||
@include themed {
|
||||
background-color: t($modal-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiSlider-valueLabel {
|
||||
background-color: var(--tab-active) !important;
|
||||
@include themed {
|
||||
background-color: t($modal-sidebarActive) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MuiFormControlLabel-labelPlacementStart {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.settingsRow {
|
||||
.MuiFormControlLabel-root {
|
||||
flex-direction: row-reverse;
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.MuiFormControlLabel-root {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.css-w66kx-MuiChip-root {
|
||||
@include themed {
|
||||
color: t($color) !important;
|
||||
border: 1px solid t($modal-sidebarActive);
|
||||
background: t($modal-sidebar) !important;
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
.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: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
.resetmodal {
|
||||
min-height: 300px !important;
|
||||
max-width: 300px !important;
|
||||
margin: auto;
|
||||
font-size: 1rem;
|
||||
|
||||
h4 {
|
||||
cursor: initial;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.resetfooter {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
width: 300px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
button.reset {
|
||||
margin-right: 43px;
|
||||
}
|
||||
}
|
||||
|
||||
.resetoverlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
.aboutIcon {
|
||||
color: var(--modal-text) !important;
|
||||
padding-right: 10px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.aboutLink {
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
@@ -16,17 +7,43 @@
|
||||
.aboutLogo {
|
||||
height: 100px;
|
||||
width: auto;
|
||||
margin-left: -15px;
|
||||
margin: calc(1rem - 15px) 0 1rem 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.abouticon {
|
||||
width: 96px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
padding-right: 5px;
|
||||
.aboutContact {
|
||||
flex-flow: row;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
|
||||
a {
|
||||
@include basicIconButton(11px, 1.2rem, modal);
|
||||
}
|
||||
}
|
||||
|
||||
.contacth3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.8em !important;
|
||||
.contributorImages {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
|
||||
img {
|
||||
width: 75px;
|
||||
height: auto;
|
||||
|
||||
@include themed {
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle-photographers {
|
||||
font-size: 16px;
|
||||
|
||||
@include themed {
|
||||
color: t($color);
|
||||
|
||||
span {
|
||||
color: t($subColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.updatechangelog {
|
||||
.updateChangelog {
|
||||
max-width: 75%;
|
||||
white-space: pre-wrap;
|
||||
|
||||
li {
|
||||
cursor: initial;
|
||||
@@ -26,7 +27,7 @@
|
||||
}
|
||||
|
||||
h5 {
|
||||
line-height: 0px !important;
|
||||
line-height: 0 !important;
|
||||
}
|
||||
|
||||
img {
|
||||
|
||||
@@ -1,54 +1,163 @@
|
||||
.sortableitem {
|
||||
background: var(--sidebar) !important;
|
||||
padding: 10px 80px;
|
||||
padding-left: 10px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.325rem;
|
||||
color: var(--modal-text) !important;
|
||||
cursor: move;
|
||||
width: 150px;
|
||||
z-index: 999 !important;
|
||||
@import 'scss/variables';
|
||||
|
||||
svg {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--tab-active) !important;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 0px;
|
||||
margin: 0;
|
||||
|
||||
>label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.images-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 500px;
|
||||
|
||||
div {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-color: var(--sidebar);
|
||||
border: 20px solid var(--sidebar);
|
||||
border-radius: 20px;
|
||||
margin: 0 20px 20px 0;
|
||||
.sortableItem {
|
||||
@include themed {
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.325rem;
|
||||
color: t($color) !important;
|
||||
cursor: move;
|
||||
width: 200px;
|
||||
z-index: 999 !important;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: t($modal-secondaryColour);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: t($boxShadow);
|
||||
|
||||
svg {
|
||||
font-size: 1.9em;
|
||||
font-size: 1.3rem;
|
||||
color: t($subColor);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive) !important;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
-webkit-padding-start: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
> label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sortablecontainer {
|
||||
-webkit-padding-start: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.images-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
padding: 20px;
|
||||
grid-gap: 20px;
|
||||
|
||||
@include themed {
|
||||
div {
|
||||
border-radius: t($borderRadius);
|
||||
|
||||
// border: 5px t($modal-sidebar) solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-flow: column;
|
||||
gap: 5px;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: t($borderRadius);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0 30px;
|
||||
background: t($modal-background);
|
||||
border: none !important;
|
||||
|
||||
&:hover {
|
||||
background: t($modal-sidebarActive);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute !important;
|
||||
top: 5px !important;
|
||||
right: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.iconButton {
|
||||
width: calc(100% - 22px);
|
||||
margin-top: 10px;
|
||||
|
||||
@include basicIconButton(11px, 1.3rem, modal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overviewGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-gap: 30px;
|
||||
}
|
||||
|
||||
.tabPreview {
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-secondaryColour);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: t($boxShadow);
|
||||
}
|
||||
|
||||
.previewItem {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
|
||||
.quotediv .quote {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.quotediv .author-holder .author .author-content .title {
|
||||
font-size: 1em !important;
|
||||
|
||||
@include themed {
|
||||
color: t($color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overviewNews {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-top: 16px;
|
||||
padding: 25px;
|
||||
|
||||
.title {
|
||||
font-size: 36px !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 12px !important;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 20px 20px 0;
|
||||
}
|
||||
|
||||
@include themed {
|
||||
background: t($modal-secondaryColour);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: t($boxShadow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import 'scss/variables';
|
||||
|
||||
// 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
|
||||
@@ -10,8 +12,13 @@ div.color-preview-area > div > div:nth-child(5) {
|
||||
}
|
||||
|
||||
.ui-color-picker {
|
||||
margin: 8px -12px;
|
||||
background-color: var(--background) !important;
|
||||
padding: 10px;
|
||||
|
||||
@include themed {
|
||||
background: t($modal-secondaryColour);
|
||||
border-radius: t($borderRadius);
|
||||
box-shadow: t($boxShadow);
|
||||
}
|
||||
}
|
||||
|
||||
.input-field .label {
|
||||
@@ -32,7 +39,33 @@ div.color-preview-area > div > div:nth-child(5) {
|
||||
border: 2px solid var(--modal-text) !important;
|
||||
}
|
||||
|
||||
.text-input, .number-input {
|
||||
background-color: var(--sidebar) !important;
|
||||
color: var(--modal-text) !important;
|
||||
.text-input,
|
||||
.number-input {
|
||||
@include themed {
|
||||
background-color: t($modal-secondaryColour) !important;
|
||||
color: t($color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.colourInput {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-flow: row-reverse;
|
||||
}
|
||||
|
||||
.gradient-controls {
|
||||
margin-bottom: 10px !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.colourReset {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-field .input-container .input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,50 +1,71 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Checkbox as CheckboxUI, FormControlLabel } from '@mui/material';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class Checkbox extends PureComponent {
|
||||
class Checkbox extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checked: (localStorage.getItem(this.props.name) === 'true')
|
||||
checked: localStorage.getItem(this.props.name) === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
const value = (this.state.checked === true) ? false : true;
|
||||
const value = this.state.checked !== true;
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
this.setState({
|
||||
checked: value
|
||||
checked: value,
|
||||
});
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
variables.stats.postEvent('setting', `${this.props.name} ${(this.state.checked === true) ? 'enabled' : 'disabled'}`);
|
||||
variables.stats.postEvent(
|
||||
'setting',
|
||||
`${this.props.name} ${this.state.checked === true ? 'enabled' : 'disabled'}`,
|
||||
);
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
EventBus.emit('refresh', this.props.category);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} disabled={this.props.disabled || false} />}
|
||||
label={this.props.text}
|
||||
/>
|
||||
<br/>
|
||||
</>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<CheckboxUI
|
||||
name={this.props.name}
|
||||
color="primary"
|
||||
className="checkbox"
|
||||
checked={this.state.checked}
|
||||
onChange={this.handleChange}
|
||||
disabled={this.props.disabled || false}
|
||||
/>
|
||||
}
|
||||
label={this.props.text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Checkbox.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
category: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
|
||||
64
src/components/modals/main/settings/ChipSelect.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useState, memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Select from '@mui/material/Select';
|
||||
import Chip from '@mui/material/Chip';
|
||||
|
||||
function ChipSelect({ label, options, name }) {
|
||||
let start = (localStorage.getItem('apiCategories') || '').split(',');
|
||||
if (start[0] === '') {
|
||||
start = [];
|
||||
}
|
||||
|
||||
const [optionsSelected, setoptionsSelected] = useState(start);
|
||||
|
||||
const handleChange = (event) => {
|
||||
const {
|
||||
target: { value },
|
||||
} = event;
|
||||
setoptionsSelected(typeof value === 'string' ? value.split(',') : value);
|
||||
localStorage.setItem('apiCategories', value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormControl>
|
||||
<InputLabel id="chipSelect-label">{label}</InputLabel>
|
||||
<Select
|
||||
labelId="chipSelect-label"
|
||||
id="chipSelect"
|
||||
multiple
|
||||
value={optionsSelected}
|
||||
onChange={handleChange}
|
||||
input={<OutlinedInput id="select-multiple-chip" label={label} />}
|
||||
renderValue={(optionsSelected) => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{optionsSelected.map((value) => (
|
||||
<Chip key={value} label={value} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem key={option.name} value={option.name}>
|
||||
{option.name.charAt(0).toUpperCase() + option.name.slice(1)} ({option.count})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ChipSelect.propTypes = {
|
||||
label: PropTypes.string,
|
||||
options: PropTypes.array,
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
export default memo(ChipSelect);
|
||||
@@ -1,15 +1,16 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { InputLabel, MenuItem, FormControl, Select } from '@mui/material';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class Dropdown extends PureComponent {
|
||||
class Dropdown extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name) || this.props.children[0].props.value,
|
||||
title: ''
|
||||
title: '',
|
||||
};
|
||||
this.dropdown = createRef();
|
||||
}
|
||||
@@ -17,33 +18,34 @@ export default class Dropdown extends PureComponent {
|
||||
onChange = (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
if (value === variables.language.getMessage(variables.languagecode, 'modals.main.loading')) {
|
||||
if (value === variables.getMessage('modals.main.loading')) {
|
||||
return;
|
||||
}
|
||||
|
||||
variables.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
|
||||
|
||||
this.setState({
|
||||
value
|
||||
value,
|
||||
});
|
||||
|
||||
|
||||
if (!this.props.noSetting) {
|
||||
localStorage.setItem(this.props.name, value);
|
||||
localStorage.setItem(this.props.name2, this.props.value2);
|
||||
}
|
||||
|
||||
|
||||
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';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
EventBus.emit('refresh', this.props.category);
|
||||
};
|
||||
|
||||
render() {
|
||||
const id = 'dropdown' + this.props.name;
|
||||
@@ -52,12 +54,40 @@ export default class Dropdown extends PureComponent {
|
||||
return (
|
||||
<FormControl fullWidth className={id}>
|
||||
<InputLabel id={id}>{label}</InputLabel>
|
||||
<Select labelId={id} id={this.props.name} value={this.state.value} label={label} onChange={this.onChange} ref={this.dropdown} key={id}>
|
||||
{this.props.manual ? this.props.children : this.props.children.map((e, index) => {
|
||||
return e ? <MenuItem key={index} value={e.props ? e.props.value : ''}>{e.props ? e.props.children : ''}</MenuItem> : null
|
||||
})}
|
||||
<Select
|
||||
labelId={id}
|
||||
id={this.props.name}
|
||||
value={this.state.value}
|
||||
label={label}
|
||||
onChange={this.onChange}
|
||||
ref={this.dropdown}
|
||||
key={id}
|
||||
>
|
||||
{this.props.manual
|
||||
? this.props.children
|
||||
: this.props.children.map((e, index) => {
|
||||
return e ? (
|
||||
<MenuItem key={index} value={e.props ? e.props.value : ''}>
|
||||
{e.props ? e.props.children : ''}
|
||||
</MenuItem>
|
||||
) : null;
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dropdown.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
category: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
noSetting: PropTypes.bool,
|
||||
manual: PropTypes.bool,
|
||||
value2: PropTypes.string,
|
||||
name2: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { toast } from 'react-toastify';
|
||||
import { compressAccurately, filetoDataURL } from 'image-conversion';
|
||||
import { videoCheck } from 'modules/helpers/background/widget';
|
||||
|
||||
export default class FileUpload extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
class FileUpload extends PureComponent {
|
||||
componentDidMount() {
|
||||
document.getElementById(this.props.id).onchange = (e) => {
|
||||
const reader = new FileReader();
|
||||
@@ -12,22 +13,61 @@ export default class FileUpload extends PureComponent {
|
||||
|
||||
if (this.props.type === 'settings') {
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
reader.onload = (e) => {
|
||||
return this.props.loadFunction(e.target.result);
|
||||
};
|
||||
} else {
|
||||
// background upload
|
||||
if (file.size > 2000000) {
|
||||
return toast(this.getMessage('modals.main.file_upload_error'));
|
||||
const settings = {};
|
||||
|
||||
Object.keys(localStorage).forEach((key) => {
|
||||
settings[key] = localStorage.getItem(key);
|
||||
});
|
||||
|
||||
const settingsSize = new TextEncoder().encode(JSON.stringify(settings)).length;
|
||||
if (videoCheck(file.type) === true) {
|
||||
if (settingsSize + file.size > 4850000) {
|
||||
return toast(variables.getMessage('toasts.no_storage'));
|
||||
}
|
||||
|
||||
return this.props.loadFunction(file);
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
compressAccurately(file, {
|
||||
size: 450,
|
||||
accuracy: 0.9,
|
||||
}).then(async (res) => {
|
||||
if (settingsSize + res.size > 4850000) {
|
||||
return toast(variables.getMessage('toasts.no_storage'));
|
||||
}
|
||||
|
||||
reader.addEventListener('load', (e) => {
|
||||
this.props.loadFunction(e);
|
||||
});
|
||||
this.props.loadFunction({
|
||||
target: {
|
||||
result: await filetoDataURL(res),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input id={this.props.id} type='file' style={{ display: 'none' }} accept={this.props.accept} />;
|
||||
return (
|
||||
<input
|
||||
id={this.props.id}
|
||||
type="file"
|
||||
style={{ display: 'none' }}
|
||||
accept={this.props.accept}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FileUpload.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
loadFunction: PropTypes.func.isRequired,
|
||||
accept: PropTypes.string,
|
||||
};
|
||||
|
||||
export default FileUpload;
|
||||
|
||||
@@ -1,24 +1,106 @@
|
||||
import variables from 'modules/variables';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { /*MdHelpOutline,*/ MdFlag, MdArrowBack } from 'react-icons/md';
|
||||
|
||||
import Slider from './Slider';
|
||||
import Switch from './Switch';
|
||||
import SettingsItem from './SettingsItem';
|
||||
|
||||
import { values } from 'modules/helpers/settings/modals';
|
||||
import Tooltip from 'components/helpers/tooltip/Tooltip';
|
||||
|
||||
export default class Header extends PureComponent {
|
||||
class Header extends PureComponent {
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{this.props.title}</h2>
|
||||
<Switch name={this.props.setting} text={getMessage('modals.main.settings.enabled')} category={this.props.category} element={this.props.element || null} />
|
||||
{this.props.zoomSetting ?
|
||||
<><Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.widget_zoom')} name={this.props.zoomSetting} min='10' max='400' default='100' display='%' marks={values('zoom')} category={this.props.zoomCategory || this.props.category}/></>
|
||||
: <br/>}
|
||||
<div className="flexTopMarketplace">
|
||||
{this.props.backButton ? (
|
||||
<div className="returnButton" onClick={this.props.clickEffect}>
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.navbar.marketplace.product.buttons.back')}
|
||||
key="backArrow"
|
||||
>
|
||||
<MdArrowBack className="backArrow" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
<span className="mainTitle">{this.props.title}</span>
|
||||
</div>
|
||||
<div className="headerExtras">
|
||||
{/*<span
|
||||
className="link"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
variables.constants.KNOWLEDGEBASE +
|
||||
'/settings/' +
|
||||
this.props.setting.toLowerCase().replace('enabled', ''),
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
>
|
||||
<MdHelpOutline /> {variables.getMessage('modals.main.settings.sections.header.more_info')}
|
||||
</span>*/}
|
||||
<span
|
||||
className="link"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
variables.constants.BUG_REPORT + this.props.title.split(' ').join('+'),
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
>
|
||||
<MdFlag /> {variables.getMessage('modals.main.settings.sections.header.report_issue')}
|
||||
</span>
|
||||
</div>
|
||||
{this.props.switch ? (
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.enabled')}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.header.enabled')}
|
||||
>
|
||||
<Switch
|
||||
name={this.props.setting}
|
||||
text={variables.getMessage('modals.main.settings.enabled')}
|
||||
category={this.props.category}
|
||||
element={this.props.element || null}
|
||||
header={true}
|
||||
/>
|
||||
</SettingsItem>
|
||||
) : null}
|
||||
{this.props.zoomSetting ? (
|
||||
<SettingsItem
|
||||
title={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.widget_zoom',
|
||||
)}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.header.size')}
|
||||
>
|
||||
<Slider
|
||||
name={this.props.zoomSetting}
|
||||
min="10"
|
||||
max="400"
|
||||
default="100"
|
||||
display="%"
|
||||
marks={values('zoom')}
|
||||
category={this.props.zoomCategory || this.props.category}
|
||||
/>
|
||||
</SettingsItem>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
setting: PropTypes.string.isRequired,
|
||||
category: PropTypes.string.isRequired,
|
||||
element: PropTypes.string,
|
||||
backButton: PropTypes.bool,
|
||||
clickEffect: PropTypes.func,
|
||||
switch: PropTypes.bool,
|
||||
zoomSetting: PropTypes.string,
|
||||
zoomCategory: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import variables from 'modules/variables';
|
||||
import { Cancel } from '@mui/icons-material';
|
||||
import { TextField } from '@mui/material';
|
||||
|
||||
export default function KeybindInput(props) {
|
||||
const value = props.state[props.setting];
|
||||
|
||||
const getButton = () => {
|
||||
if (!value) {
|
||||
return <button className='cleanButton' style={{ visibility: 'hidden' }} onClick={() => props.action('reset', props.setting)}><Cancel/></button>;;
|
||||
} else if (value === variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.keybinds.recording')) {
|
||||
return <button className='cleanButton' onClick={() => props.action('cancel', props.setting)}><Cancel/></button>;
|
||||
} else {
|
||||
return <button className='cleanButton' onClick={() => props.action('reset', props.setting)}><Cancel/></button>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextField label={props.name} onClick={() => props.action('listen', props.setting)} value={value || variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.keybinds.click_to_record')} readOnly spellCheck={false} varient='outlined' InputLabelProps={{ shrink: true }} />
|
||||
{getButton()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { Radio as RadioUI, RadioGroup, FormControlLabel, FormControl, FormLabel } from '@mui/material';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Radio as RadioUI,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
} from '@mui/material';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
import { translations } from 'modules/translations';
|
||||
|
||||
export default class Radio extends PureComponent {
|
||||
class Radio extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name)
|
||||
value: localStorage.getItem(this.props.name),
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
|
||||
handleChange = async (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
if (value === 'loading') {
|
||||
@@ -21,15 +29,15 @@ export default class Radio extends PureComponent {
|
||||
|
||||
if (this.props.name === 'language') {
|
||||
// old tab name
|
||||
if (localStorage.getItem('tabName') === variables.language.getMessage(variables.languagecode, 'tabname')) {
|
||||
localStorage.setItem('tabName', require(`translations/${value.replace('-', '_')}.json`).tabname);
|
||||
if (localStorage.getItem('tabName') === variables.getMessage('tabname')) {
|
||||
localStorage.setItem('tabName', translations[value.replace('-', '_')].tabname);
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
|
||||
this.setState({
|
||||
value
|
||||
value,
|
||||
});
|
||||
|
||||
if (this.props.onChange) {
|
||||
@@ -37,27 +45,59 @@ export default class Radio extends PureComponent {
|
||||
}
|
||||
|
||||
variables.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
|
||||
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
|
||||
EventBus.emit('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}>
|
||||
<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} />
|
||||
<FormControlLabel
|
||||
value={option.value}
|
||||
control={<RadioUI />}
|
||||
label={option.name}
|
||||
key={option.name}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Radio.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
onChange: PropTypes.func,
|
||||
category: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
smallTitle: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Radio;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import variables from 'modules/variables';
|
||||
import { Close, Delete } from '@mui/icons-material';
|
||||
import { MdClose, MdRestartAlt } from 'react-icons/md';
|
||||
import { setDefaultSettings } from 'modules/helpers/settings';
|
||||
import Tooltip from 'components/helpers/tooltip/Tooltip';
|
||||
|
||||
export default function ResetModal({ modalClose }) {
|
||||
function ResetModal({ modalClose }) {
|
||||
const reset = () => {
|
||||
variables.stats.postEvent('setting', 'Reset');
|
||||
setDefaultSettings('reset');
|
||||
@@ -10,19 +13,41 @@ export default function ResetModal({ modalClose }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 style={{ textAlign: 'center' }}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.advanced.reset_modal.title')}</h1>
|
||||
<span>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.advanced.reset_modal.question')}</span>
|
||||
<br/><br/>
|
||||
<span>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.advanced.reset_modal.information')}</span>
|
||||
<div className='resetfooter'>
|
||||
<button className='round reset' style={{ marginLeft: 0 }} onClick={() => reset()}>
|
||||
<Delete/>
|
||||
<div className="smallModal">
|
||||
<div className="shareHeader">
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.title')}
|
||||
</span>
|
||||
<Tooltip
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.reset_modal.cancel')}
|
||||
>
|
||||
<div className="close" onClick={modalClose}>
|
||||
<MdClose />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.question')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.information')}
|
||||
</span>
|
||||
<div className="resetFooter">
|
||||
<button className="textButton" onClick={modalClose}>
|
||||
<MdClose />
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.reset_modal.cancel')}
|
||||
</button>
|
||||
<button className='round add' style={{ marginLeft: '5px' }} onClick={modalClose}>
|
||||
<Close/>
|
||||
<button onClick={() => reset()}>
|
||||
<MdRestartAlt />
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ResetModal.propTypes = {
|
||||
modalClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default memo(ResetModal);
|
||||
|
||||
23
src/components/modals/main/settings/SettingsItem.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function SettingsItem({ final, title, subtitle, children }) {
|
||||
return (
|
||||
<div className={final ? 'settingsRow settingsNoBorder' : 'settingsRow'}>
|
||||
<div className="content">
|
||||
<span className="title">{title}</span>
|
||||
<span className="subtitle">{subtitle}</span>
|
||||
</div>
|
||||
<div className="action">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsItem.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
final: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default memo(SettingsItem);
|
||||
@@ -1,15 +1,17 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Slider } from '@mui/material';
|
||||
import { MdRefresh } from 'react-icons/md';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class SliderComponent extends PureComponent {
|
||||
class SliderComponent extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name) || this.props.default
|
||||
value: localStorage.getItem(this.props.name) || this.props.default,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,59 +22,80 @@ export default class SliderComponent extends PureComponent {
|
||||
if (text) {
|
||||
if (value === '') {
|
||||
return this.setState({
|
||||
value: 0
|
||||
value: 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (value > this.props.max) {
|
||||
value = this.props.max;
|
||||
}
|
||||
|
||||
|
||||
if (value < this.props.min) {
|
||||
value = this.props.min;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
EventBus.emit('refresh', this.props.category);
|
||||
};
|
||||
|
||||
resetItem = () => {
|
||||
this.handleChange({
|
||||
target: {
|
||||
value: this.props.default || ''
|
||||
}
|
||||
value: this.props.default || '',
|
||||
},
|
||||
});
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.reset'));
|
||||
}
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<p>{this.props.title}<span className='modalLink' onClick={this.resetItem}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.buttons.reset')}</span></p>
|
||||
<Slider
|
||||
value={Number(this.state.value)}
|
||||
onChange={this.handleChange}
|
||||
valueLabelDisplay='auto'
|
||||
default={Number(this.props.default)}
|
||||
min={Number(this.props.min)}
|
||||
max={Number(this.props.max)}
|
||||
step={Number(this.props.step) || 1}
|
||||
getAriaValueText={(value) => `${value}`}
|
||||
<span className={'sliderTitle'}>
|
||||
{this.props.title}
|
||||
<span>{Number(this.state.value)}</span>
|
||||
<span className="link" onClick={this.resetItem}>
|
||||
<MdRefresh />
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>
|
||||
</span>
|
||||
<Slider
|
||||
value={Number(this.state.value)}
|
||||
onChange={this.handleChange}
|
||||
valueLabelDisplay="auto"
|
||||
default={Number(this.props.default)}
|
||||
min={Number(this.props.min)}
|
||||
max={Number(this.props.max)}
|
||||
step={Number(this.props.step) || 1}
|
||||
getAriaValueText={(value) => `${value}`}
|
||||
marks={this.props.marks || []}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SliderComponent.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
default: PropTypes.number.isRequired,
|
||||
min: PropTypes.number.isRequired,
|
||||
max: PropTypes.number.isRequired,
|
||||
step: PropTypes.number,
|
||||
marks: PropTypes.array,
|
||||
element: PropTypes.string,
|
||||
category: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SliderComponent;
|
||||
|
||||
@@ -1,46 +1,65 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Switch as SwitchUI, FormControlLabel } from '@mui/material';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class Switch extends PureComponent {
|
||||
class Switch extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checked: (localStorage.getItem(this.props.name) === 'true')
|
||||
checked: localStorage.getItem(this.props.name) === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
const value = (this.state.checked === true) ? false : true;
|
||||
const value = this.state.checked !== true;
|
||||
localStorage.setItem(this.props.name, value);
|
||||
|
||||
this.setState({
|
||||
checked: value
|
||||
checked: value,
|
||||
});
|
||||
|
||||
variables.stats.postEvent('setting', `${this.props.name} ${(this.state.checked === true) ? 'enabled' : 'disabled'}`);
|
||||
variables.stats.postEvent(
|
||||
'setting',
|
||||
`${this.props.name} ${this.state.checked === true ? 'enabled' : 'disabled'}`,
|
||||
);
|
||||
|
||||
if (this.props.element) {
|
||||
if (!document.querySelector(this.props.element)) {
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
EventBus.emit('refresh', this.props.category);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={<SwitchUI name={this.props.name} color='primary' checked={this.state.checked} onChange={this.handleChange} />}
|
||||
label={this.props.text}
|
||||
labelPlacement='start'
|
||||
/>
|
||||
</>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<SwitchUI
|
||||
name={this.props.name}
|
||||
color="primary"
|
||||
checked={this.state.checked}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
}
|
||||
label={this.props.header ? '' : this.props.text}
|
||||
labelPlacement="start"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Switch.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
category: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
header: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Switch;
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { toast } from 'react-toastify';
|
||||
import { TextField } from '@mui/material';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class Text extends PureComponent {
|
||||
class Text extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: localStorage.getItem(this.props.name) || ''
|
||||
value: localStorage.getItem(this.props.name) || '',
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
let { value } = e.target;
|
||||
|
||||
|
||||
// Alex wanted font to work with montserrat and Montserrat, so I made it work
|
||||
if (this.props.upperCaseFirst === true) {
|
||||
value = value.charAt(0).toUpperCase() + value.slice(1);
|
||||
@@ -23,37 +24,70 @@ export default class Text extends PureComponent {
|
||||
|
||||
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';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
return localStorage.setItem('showReminder', true);
|
||||
}
|
||||
}
|
||||
|
||||
EventBus.dispatch('refresh', this.props.category);
|
||||
}
|
||||
EventBus.emit('refresh', this.props.category);
|
||||
};
|
||||
|
||||
resetItem = () => {
|
||||
this.handleChange({
|
||||
target: {
|
||||
value: this.props.default || ''
|
||||
}
|
||||
value: this.props.default || '',
|
||||
},
|
||||
});
|
||||
toast(variables.language.getMessage(variables.languagecode, 'toasts.reset'));
|
||||
}
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{(this.props.textarea === true) ?
|
||||
<TextField label={this.props.title} value={this.state.value} onChange={this.handleChange} varient='outlined' multiline spellCheck={false} minRows={4} maxRows={10} InputLabelProps={{ shrink: true }} />
|
||||
: <TextField label={this.props.title} value={this.state.value} onChange={this.handleChange} varient='outlined' InputLabelProps={{ shrink: true }} />
|
||||
}
|
||||
<span className='modalLink' onClick={this.resetItem}>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.buttons.reset')}</span>
|
||||
{this.props.textarea === true ? (
|
||||
<TextField
|
||||
label={this.props.title}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
varient="outlined"
|
||||
className={this.props.customcss ? 'customcss' : ''}
|
||||
multiline
|
||||
spellCheck={false}
|
||||
minRows={4}
|
||||
maxRows={10}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
) : (
|
||||
<TextField
|
||||
label={this.props.title}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
)}
|
||||
<span className="link" onClick={this.resetItem}>
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Text.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
category: PropTypes.string.isRequired,
|
||||
default: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
customcss: PropTypes.bool,
|
||||
textarea: PropTypes.bool,
|
||||
upperCaseFirst: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Text;
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { Email, Twitter, Chat, Instagram, Facebook } from '@mui/icons-material';
|
||||
import { MdEmail, MdContactPage } from 'react-icons/md';
|
||||
import { FaDiscord, FaTwitter } from 'react-icons/fa';
|
||||
import { SiGithubsponsors, SiOpencollective } from 'react-icons/si';
|
||||
import { BiDonateHeart } from 'react-icons/bi';
|
||||
|
||||
import Tooltip from 'components/helpers/tooltip/Tooltip';
|
||||
|
||||
const other_contributors = require('modules/other_contributors.json');
|
||||
import other_contributors from 'modules/other_contributors.json';
|
||||
|
||||
export default class About extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
contributors: [],
|
||||
sponsors: [],
|
||||
other_contributors: [],
|
||||
photographers: this.getMessage('modals.main.loading'),
|
||||
update: this.getMessage('modals.main.settings.sections.about.version.checking_update'),
|
||||
loading: this.getMessage('modals.main.loading')
|
||||
photographers: [],
|
||||
update: variables.getMessage('modals.main.settings.sections.about.version.checking_update'),
|
||||
loading: variables.getMessage('modals.main.loading'),
|
||||
image: document.body.classList.contains('dark')
|
||||
? 'icons/mue_dark.png'
|
||||
: 'icons/mue_light.png',
|
||||
};
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
@@ -26,22 +30,54 @@ export default class About extends PureComponent {
|
||||
let contributors, sponsors, photographers, versionData;
|
||||
|
||||
try {
|
||||
versionData = await (await fetch(variables.constants.GITHUB_URL + '/repos/' + variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME + '/releases', { signal: this.controller.signal })).json();
|
||||
contributors = await (await fetch(variables.constants.GITHUB_URL + '/repos/'+ variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME + '/contributors', { signal: this.controller.signal })).json();
|
||||
sponsors = (await (await fetch(variables.constants.SPONSORS_URL + '/list', { signal: this.controller.signal })).json()).sponsors;
|
||||
photographers = await (await fetch(variables.constants.API_URL + '/images/photographers', { signal: this.controller.signal })).json();
|
||||
versionData = await (
|
||||
await fetch(
|
||||
variables.constants.GITHUB_URL +
|
||||
'/repos/' +
|
||||
variables.constants.ORG_NAME +
|
||||
'/' +
|
||||
variables.constants.REPO_NAME +
|
||||
'/releases',
|
||||
{ signal: this.controller.signal },
|
||||
)
|
||||
).json();
|
||||
contributors = await (
|
||||
await fetch(
|
||||
variables.constants.GITHUB_URL +
|
||||
'/repos/' +
|
||||
variables.constants.ORG_NAME +
|
||||
'/' +
|
||||
variables.constants.REPO_NAME +
|
||||
'/contributors',
|
||||
{ signal: this.controller.signal },
|
||||
)
|
||||
).json();
|
||||
sponsors = (
|
||||
await (
|
||||
await fetch(variables.constants.SPONSORS_URL + '/list', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json()
|
||||
).sponsors;
|
||||
photographers = await (
|
||||
await fetch(variables.constants.API_URL + '/images/photographers', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
} catch (e) {
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.setState({
|
||||
update: this.getMessage('modals.main.settings.sections.about.version.error.title'),
|
||||
loading: this.getMessage('modals.main.settings.sections.about.version.error.description')
|
||||
update: variables.getMessage('modals.main.settings.sections.about.version.error.title'),
|
||||
loading: variables.getMessage(
|
||||
'modals.main.settings.sections.about.version.error.description',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (sponsors.length === 0) {
|
||||
if (sponsors.length === 0) {
|
||||
sponsors = [{ handle: 'empty' }];
|
||||
}
|
||||
|
||||
@@ -51,9 +87,14 @@ export default class About extends PureComponent {
|
||||
|
||||
const newVersion = versionData[0].tag_name;
|
||||
|
||||
let update = this.getMessage('modals.main.settings.sections.about.version.no_update');
|
||||
if (Number(variables.constants.VERSION.replaceAll('.', '')) < Number(newVersion.replaceAll('.', ''))) {
|
||||
update = `${this.getMessage('modals.main.settings.sections.about.version.update_available')}: ${newVersion}`;
|
||||
let update = variables.getMessage('modals.main.settings.sections.about.version.no_update');
|
||||
if (
|
||||
Number(variables.constants.VERSION.replaceAll('.', '')) <
|
||||
Number(newVersion.replaceAll('.', ''))
|
||||
) {
|
||||
update = `${variables.getMessage(
|
||||
'modals.main.settings.sections.about.version.update_available',
|
||||
)}: ${newVersion}`;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -62,16 +103,16 @@ export default class About extends PureComponent {
|
||||
sponsors,
|
||||
update,
|
||||
other_contributors,
|
||||
photographers: photographers.sort().join(', '),
|
||||
loading: null
|
||||
photographers,
|
||||
loading: null,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
this.setState({
|
||||
update: this.getMessage('modals.main.settings.sections.about.version.checking_update'),
|
||||
loading: this.getMessage('modals.main.marketplace.offline.description')
|
||||
update: variables.getMessage('modals.main.settings.sections.about.version.checking_update'),
|
||||
loading: variables.getMessage('modals.main.marketplace.offline.description'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -87,63 +128,239 @@ export default class About extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h2>{this.getMessage('modals.main.settings.sections.about.title')}</h2>
|
||||
<img draggable='false' className='aboutLogo' src='./././icons/logo_horizontal.png' alt='Logo'></img>
|
||||
<p>{this.getMessage('modals.main.settings.sections.about.copyright')} {variables.constants.COPYRIGHT_YEAR}-{new Date().getFullYear()} <a href={'https://github.com/' + variables.constants.ORG_NAME + '/' + variables.constants.REPO_NAME + '/graphs/contributors'} className='aboutLink' target='_blank' rel='noopener noreferrer'>{variables.constants.COPYRIGHT_NAME}</a> ({variables.constants.COPYRIGHT_LICENSE})</p>
|
||||
<p>{this.getMessage('modals.main.settings.sections.about.version.title')} {variables.constants.VERSION} ({this.state.update})</p>
|
||||
<a href={variables.constants.PRIVACY_URL} className='aboutLink' target='_blank' rel='noopener noreferrer' style={{ fontSize: '1rem' }}>{this.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}</a>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.about.title')}
|
||||
</span>
|
||||
<div className="settingsRow" style={{ justifyContent: 'center' }}>
|
||||
<div style={{ display: 'flex', flexFlow: 'column', gap: '5px' }}>
|
||||
<img draggable={false} className="aboutLogo" src={this.state.image} alt="Logo" />
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.version.title')}{' '}
|
||||
{variables.constants.VERSION}
|
||||
</span>
|
||||
<span className="subtitle">({this.state.update})</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.about.copyright')}{' '}
|
||||
{variables.constants.COPYRIGHT_YEAR}-{new Date().getFullYear()}{' '}
|
||||
<a
|
||||
className="link"
|
||||
href={
|
||||
'https://github.com/' +
|
||||
variables.constants.ORG_NAME +
|
||||
'/' +
|
||||
variables.constants.REPO_NAME +
|
||||
'/graphs/contributors'
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{variables.constants.COPYRIGHT_NAME}
|
||||
</a>{' '}
|
||||
({variables.constants.COPYRIGHT_LICENSE})
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
<a
|
||||
href={variables.constants.PRIVACY_URL}
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{variables.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className='contacth3'>{this.getMessage('modals.main.settings.sections.about.contact_us')}</h3>
|
||||
<a href={'mailto:' + variables.constants.EMAIL} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Email/></a>
|
||||
<a href={'https://twitter.com/' + variables.constants.TWITTER_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Twitter/></a>
|
||||
<a href={'https://instagram.com/' + variables.constants.INSTAGRAM_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Instagram/></a>
|
||||
<a href={'https://facebook.com/' + variables.constants.FACEBOOK_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Facebook/></a>
|
||||
<a href={'https://discord.gg/' + variables.constants.DISCORD_SERVER} className='aboutIcon' target='_blank' rel='noopener noreferrer'><Chat/></a>
|
||||
|
||||
<h3>{this.getMessage('modals.main.settings.sections.about.support_mue')}</h3>
|
||||
<p>
|
||||
<a href={'https://github.com/sponsors/' + variables.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>GitHub Sponsors</a>
|
||||
• <a href={'https://ko-fi.com/' + variables.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
|
||||
• <a href={'https://patreon.com/' + variables.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Patreon</a>
|
||||
</p>
|
||||
|
||||
<h3>{this.getMessage('modals.main.settings.sections.about.resources_used.title')}</h3>
|
||||
<p>
|
||||
<a href='https://www.pexels.com' className='aboutLink' target='_blank' rel='noopener noreferrer'>Pexels</a>
|
||||
, <a href='https://unsplash.com' className='aboutLink' target='_blank' rel='noopener noreferrer'>Unsplash</a> ({this.getMessage('modals.main.settings.sections.about.resources_used.bg_images')})
|
||||
</p>
|
||||
<p><a href='https://undraw.co' className='aboutLink' target='_blank' rel='noopener noreferrer'>Undraw</a> ({this.getMessage('modals.main.settings.sections.about.resources_used.welcome_img')})</p>
|
||||
|
||||
<h3>{this.getMessage('modals.main.settings.sections.about.contributors')}</h3>
|
||||
<p>{this.state.loading}</p>
|
||||
{this.state.contributors.map(({ login, id }) => (
|
||||
<Tooltip title={login} key={login}>
|
||||
<a href={'https://github.com/' + login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={'https://avatars.githubusercontent.com/u/' + id + '?s=128'} alt={login}></img></a>
|
||||
</Tooltip>
|
||||
))}
|
||||
{ // for those who contributed without opening a pull request
|
||||
this.state.other_contributors.map(({ login, avatar_url }) => (
|
||||
<Tooltip title={login} key={login}>
|
||||
<a href={'https://github.com/' + login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={avatar_url + '&s=128'} alt={login}></img></a>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
<h3>{this.getMessage('modals.main.settings.sections.about.supporters')}</h3>
|
||||
<p>{this.state.loading}</p>
|
||||
{this.state.sponsors.map(({ handle, avatar }) => {
|
||||
if (handle === 'empty') {
|
||||
return <p>{this.getMessage('modals.main.settings.sections.about.no_supporters')}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={handle} key={handle}>
|
||||
<a href={'https://github.com/' + handle} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={avatar.split('?')[0] + '?s=128'} alt={handle}></img></a>
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.contact_us')}
|
||||
</span>
|
||||
<div className="aboutContact">
|
||||
<a
|
||||
className="donateButton"
|
||||
href="https://muetab.com/contact"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<MdContactPage />
|
||||
{variables.getMessage('modals.main.settings.sections.about.form_button')}
|
||||
</a>
|
||||
<Tooltip title={'Email'}>
|
||||
<a
|
||||
href={'mailto:' + variables.constants.EMAIL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<MdEmail />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
<Tooltip title={'Twitter'}>
|
||||
<a
|
||||
href={'https://twitter.com/' + variables.constants.TWITTER_HANDLE}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaTwitter />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Discord'}>
|
||||
<a
|
||||
href={'https://discord.gg/' + variables.constants.DISCORD_SERVER}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FaDiscord />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{this.getMessage('modals.main.settings.sections.about.photographers')}</h3>
|
||||
<p>{this.state.photographers}</p>
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.support_mue')}
|
||||
</span>
|
||||
<p>{variables.getMessage('modals.main.settings.sections.about.support_subtitle')}</p>
|
||||
<div className="aboutContact">
|
||||
<a
|
||||
className="donateButton"
|
||||
href={'https://opencollective.com/' + variables.constants.OPENCOLLECTIVE_USERNAME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<BiDonateHeart />
|
||||
{variables.getMessage('modals.main.settings.sections.about.support_donate')}
|
||||
</a>
|
||||
<Tooltip title={'GitHub Sponsors'}>
|
||||
<a
|
||||
href={'https://github.com/sponsors/' + variables.constants.ORG_NAME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SiGithubsponsors />
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip title={'Open Collective'}>
|
||||
<a
|
||||
href={'https://opencollective.com/' + variables.constants.OPENCOLLECTIVE_USERNAME}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SiOpencollective />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="settingsRow"
|
||||
style={{ flexFlow: 'column', alignItems: 'flex-start', minHeight: '70px' }}
|
||||
>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.resources_used.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
<a
|
||||
href="https://www.pexels.com"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Pexels
|
||||
</a>
|
||||
,{' '}
|
||||
<a
|
||||
href="https://unsplash.com"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Unsplash
|
||||
</a>{' '}
|
||||
({variables.getMessage('modals.main.settings.sections.about.resources_used.bg_images')})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.contributors')}
|
||||
</span>
|
||||
<p>{this.state.loading}</p>
|
||||
<div className="contributorImages">
|
||||
{this.state.contributors.map(({ login, id }) => (
|
||||
<Tooltip title={login} key={login}>
|
||||
<a href={'https://github.com/' + login} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
draggable={false}
|
||||
src={'https://avatars.githubusercontent.com/u/' + id + '?s=128'}
|
||||
alt={login}
|
||||
></img>
|
||||
</a>
|
||||
</Tooltip>
|
||||
))}
|
||||
{
|
||||
// for those who contributed without opening a pull request
|
||||
this.state.other_contributors.map(({ login, avatar_url }) => (
|
||||
<Tooltip title={login} key={login}>
|
||||
<a href={'https://github.com/' + login} target="_blank" rel="noopener noreferrer">
|
||||
<img draggable={false} src={avatar_url + '&s=128'} alt={login}></img>
|
||||
</a>
|
||||
</Tooltip>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settingsRow" style={{ flexFlow: 'column', alignItems: 'flex-start' }}>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.supporters')}
|
||||
</span>
|
||||
<p>{this.state.loading}</p>
|
||||
<div className="contributorImages">
|
||||
{this.state.sponsors.map(({ handle, avatar }) => {
|
||||
if (handle === 'empty') {
|
||||
return (
|
||||
<p>{variables.getMessage('modals.main.settings.sections.about.no_supporters')}</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={handle} key={handle}>
|
||||
<a
|
||||
href={'https://github.com/' + handle}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img draggable={false} src={avatar.split('?')[0] + '?s=128'} alt={handle}></img>
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="settingsRow"
|
||||
style={{
|
||||
flexFlow: 'column',
|
||||
alignItems: 'flex-start',
|
||||
minHeight: '10px',
|
||||
borderBottom: '0',
|
||||
}}
|
||||
>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.about.photographers')}
|
||||
</span>
|
||||
{!!this.state.loading ? <p>{this.state.loading}</p> : <></>}
|
||||
<ul>
|
||||
{this.state.photographers.map(({ name, count }) => (
|
||||
<>
|
||||
<li className="subtitle-photographers">
|
||||
{name}
|
||||
<span> ({count} images)</span>
|
||||
</li>
|
||||
</>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,75 +2,129 @@ import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import { MenuItem } from '@mui/material';
|
||||
import { Upload as ImportIcon, Download as ExportIcon, RestartAlt as ResetIcon } from '@mui/icons-material';
|
||||
import {
|
||||
MdUpload as ImportIcon,
|
||||
MdDownload as ExportIcon,
|
||||
MdRestartAlt as ResetIcon,
|
||||
} from 'react-icons/md';
|
||||
|
||||
import { exportSettings, importSettings } from 'modules/helpers/settings/modals';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import FileUpload from '../FileUpload';
|
||||
import Text from '../Text';
|
||||
import Switch from '../Switch';
|
||||
import ResetModal from '../ResetModal';
|
||||
import Dropdown from '../Dropdown';
|
||||
import SettingsItem from '../SettingsItem';
|
||||
|
||||
const time_zones = require('components/widgets/time/timezones.json');
|
||||
import time_zones from 'components/widgets/time/timezones.json';
|
||||
|
||||
export default class AdvancedSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
resetModal: false
|
||||
resetModal: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{getMessage('modals.main.settings.sections.advanced.title')}</h2>
|
||||
<Checkbox name='offlineMode' text={getMessage('modals.main.settings.sections.advanced.offline_mode')} element='.other' />
|
||||
<Dropdown name='timezone' label={getMessage('modals.main.settings.sections.advanced.timezone.title')} category='timezone' manual={true}>
|
||||
<MenuItem value='auto'>{getMessage('modals.main.settings.sections.advanced.timezone.automatic')}</MenuItem>
|
||||
{time_zones.map((timezone) => (
|
||||
<MenuItem value={timezone} key={timezone}>{timezone}</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
{localStorage.getItem('welcomePreview') !== 'true' ?
|
||||
<>
|
||||
<h3>{getMessage('modals.main.settings.sections.advanced.data')}</h3>
|
||||
<br/>
|
||||
<div className='data-buttons-row'>
|
||||
<button onClick={() => this.setState({ resetModal: true })}>
|
||||
{getMessage('modals.main.settings.buttons.reset')}
|
||||
<ResetIcon/>
|
||||
</button>
|
||||
<button onClick={() => exportSettings()}>
|
||||
{getMessage('modals.main.settings.buttons.export')}
|
||||
<ExportIcon/>
|
||||
</button>
|
||||
<button onClick={() => document.getElementById('file-input').click()}>
|
||||
{getMessage('modals.main.settings.buttons.import')}
|
||||
<ImportIcon/>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
: null}
|
||||
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => importSettings(e)}/>
|
||||
|
||||
<h3>{getMessage('modals.main.settings.sections.advanced.customisation')}</h3>
|
||||
<Text title={getMessage('modals.main.settings.sections.advanced.tab_name')} name='tabName' default={getMessage('tabname')} category='other'/>
|
||||
{window.location.href.startsWith('http://') || window.location.href.startsWith('https://') ?
|
||||
<Text title={getMessage('modals.main.settings.sections.advanced.custom_js')} name='customjs' textarea={true} category='other' element='other'/>
|
||||
: null}
|
||||
<Text title={getMessage('modals.main.settings.sections.advanced.custom_css')} name='customcss' textarea={true} category='other'/>
|
||||
|
||||
<h3>{getMessage('modals.main.settings.sections.experimental.title')}</h3>
|
||||
<p style={{ maxWidth: '75%' }}>{getMessage('modals.main.settings.sections.advanced.experimental_warning')}</p>
|
||||
<Switch name='experimental' text={getMessage('modals.main.settings.enabled')} element='.other'/>
|
||||
|
||||
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ resetModal: false })} isOpen={this.state.resetModal} className='Modal resetmodal mainModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.title')}
|
||||
</span>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.offline_mode')}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.advanced.offline_subtitle')}
|
||||
>
|
||||
<Switch name="offlineMode" element=".other" />
|
||||
</SettingsItem>
|
||||
{localStorage.getItem('welcomePreview') !== 'true' ? (
|
||||
<div className="settingsRow">
|
||||
<div className="content">
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.data')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.data_subtitle')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="action activityButtons">
|
||||
<button onClick={() => this.setState({ resetModal: true })}>
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
<ResetIcon />
|
||||
</button>
|
||||
<button onClick={() => exportSettings()}>
|
||||
{variables.getMessage('modals.main.settings.buttons.export')}
|
||||
<ExportIcon />
|
||||
</button>
|
||||
<button onClick={() => document.getElementById('file-input').click()}>
|
||||
{variables.getMessage('modals.main.settings.buttons.import')}
|
||||
<ImportIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.timezone.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.timezone.subtitle',
|
||||
)}
|
||||
>
|
||||
<Dropdown name="timezone" category="timezone" manual={true}>
|
||||
<MenuItem value="auto">
|
||||
{variables.getMessage('modals.main.settings.sections.advanced.timezone.automatic')}
|
||||
</MenuItem>
|
||||
{time_zones.map((timezone) => (
|
||||
<MenuItem value={timezone} key={timezone}>
|
||||
{timezone}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.tab_name')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.tab_name_subtitle',
|
||||
)}
|
||||
>
|
||||
<Text name="tabName" default={variables.getMessage('tabname')} category="other" />
|
||||
</SettingsItem>
|
||||
<FileUpload
|
||||
id="file-input"
|
||||
accept="application/json"
|
||||
type="settings"
|
||||
loadFunction={(e) => importSettings(e)}
|
||||
/>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.advanced.custom_css')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.custom_css_subtitle',
|
||||
)}
|
||||
>
|
||||
<Text name="customcss" textarea={true} category="other" />
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.experimental.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.advanced.experimental_warning',
|
||||
)}
|
||||
final={true}
|
||||
>
|
||||
<Switch
|
||||
name="experimental"
|
||||
text={variables.getMessage('modals.main.settings.enabled')}
|
||||
element=".other"
|
||||
/>
|
||||
</SettingsItem>
|
||||
<Modal
|
||||
closeTimeoutMS={100}
|
||||
onRequestClose={() => this.setState({ resetModal: false })}
|
||||
isOpen={this.state.resetModal}
|
||||
className="Modal resetmodal mainModal"
|
||||
overlayClassName="Overlay resetoverlay"
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<ResetModal modalClose={() => this.setState({ resetModal: false })} />
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import variables from 'modules/variables';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
@@ -5,58 +7,208 @@ import Dropdown from '../Dropdown';
|
||||
import Radio from '../Radio';
|
||||
import Slider from '../Slider';
|
||||
import Text from '../Text';
|
||||
import SettingsItem from '../SettingsItem';
|
||||
|
||||
import { values } from 'modules/helpers/settings/modals';
|
||||
|
||||
export default function AppearanceSettings() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
const themeOptions = [
|
||||
{
|
||||
name: getMessage('modals.main.settings.sections.appearance.theme.auto'),
|
||||
value: 'auto'
|
||||
},
|
||||
{
|
||||
name: getMessage('modals.main.settings.sections.appearance.theme.light'),
|
||||
value: 'light'
|
||||
},
|
||||
{
|
||||
name: getMessage('modals.main.settings.sections.appearance.theme.dark'),
|
||||
value: 'dark'
|
||||
}
|
||||
];
|
||||
|
||||
function AppearanceSettings() {
|
||||
return (
|
||||
<>
|
||||
<h2>{getMessage('modals.main.settings.sections.appearance.title')}</h2>
|
||||
<Radio name='theme' title={getMessage('modals.main.settings.sections.appearance.theme.title')} options={themeOptions} category='other' />
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.title')}
|
||||
</span>
|
||||
<div className="settingsRow">
|
||||
<div className="content">
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.theme.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{' '}
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.theme.description')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="action">
|
||||
<Radio
|
||||
name="theme"
|
||||
options={[
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.theme.auto'),
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.theme.light'),
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.theme.dark'),
|
||||
value: 'dark',
|
||||
},
|
||||
]}
|
||||
category="other"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="settingsRow">
|
||||
<div className="content">
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.description')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="action">
|
||||
<Checkbox
|
||||
name="fontGoogle"
|
||||
text={variables.getMessage('modals.main.settings.sections.appearance.font.google')}
|
||||
category="other"
|
||||
/>
|
||||
<Text
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.font.custom')}
|
||||
name="font"
|
||||
upperCaseFirst={true}
|
||||
category="other"
|
||||
/>
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.weight.title',
|
||||
)}
|
||||
name="fontweight"
|
||||
category="other"
|
||||
>
|
||||
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
||||
<option value="100">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.thin')}
|
||||
</option>
|
||||
<option value="200">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.weight.extra_light',
|
||||
)}
|
||||
</option>
|
||||
<option value="300">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.light')}
|
||||
</option>
|
||||
<option value="400">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.normal')}
|
||||
</option>
|
||||
<option value="500">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.medium')}
|
||||
</option>
|
||||
<option value="600">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.weight.semi_bold',
|
||||
)}
|
||||
</option>
|
||||
<option value="700">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.weight.bold')}
|
||||
</option>
|
||||
<option value="800">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.weight.extra_bold',
|
||||
)}
|
||||
</option>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.font.style.title',
|
||||
)}
|
||||
name="fontstyle"
|
||||
category="other"
|
||||
>
|
||||
<option value="normal">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.style.normal')}
|
||||
</option>
|
||||
<option value="italic">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.style.italic')}
|
||||
</option>
|
||||
<option value="oblique">
|
||||
{variables.getMessage('modals.main.settings.sections.appearance.font.style.oblique')}
|
||||
</option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.style.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.style.description',
|
||||
)}
|
||||
>
|
||||
<Radio
|
||||
name="widgetStyle"
|
||||
element=".other"
|
||||
options={[
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.style.legacy'),
|
||||
value: 'legacy',
|
||||
},
|
||||
{
|
||||
name: variables.getMessage('modals.main.settings.sections.appearance.style.new'),
|
||||
value: 'new',
|
||||
},
|
||||
]}
|
||||
category="widgets"
|
||||
/>
|
||||
</SettingsItem>
|
||||
|
||||
<h3>{getMessage('modals.main.settings.sections.appearance.font.title')}</h3>
|
||||
<Text title={getMessage('modals.main.settings.sections.appearance.font.custom')} name='font' upperCaseFirst={true} category='other' />
|
||||
<br/>
|
||||
<Checkbox name='fontGoogle' text={getMessage('modals.main.settings.sections.appearance.font.google')} category='other' />
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.appearance.font.weight.title')} name='fontweight' category='other'>
|
||||
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
||||
<option value='100'>{getMessage('modals.main.settings.sections.appearance.font.weight.thin')}</option>
|
||||
<option value='200'>{getMessage('modals.main.settings.sections.appearance.font.weight.extra_light')}</option>
|
||||
<option value='300'>{getMessage('modals.main.settings.sections.appearance.font.weight.light')}</option>
|
||||
<option value='400'>{getMessage('modals.main.settings.sections.appearance.font.weight.normal')}</option>
|
||||
<option value='500'>{getMessage('modals.main.settings.sections.appearance.font.weight.medium')}</option>
|
||||
<option value='600'>{getMessage('modals.main.settings.sections.appearance.font.weight.semi_bold')}</option>
|
||||
<option value='700'>{getMessage('modals.main.settings.sections.appearance.font.weight.bold')}</option>
|
||||
<option value='800'>{getMessage('modals.main.settings.sections.appearance.font.weight.extra_bold')}</option>
|
||||
</Dropdown>
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.appearance.font.style.title')} name='fontstyle' category='other'>
|
||||
<option value='normal'>{getMessage('modals.main.settings.sections.appearance.font.style.normal')}</option>
|
||||
<option value='italic'>{getMessage('modals.main.settings.sections.appearance.font.style.italic')}</option>
|
||||
<option value='oblique'>{getMessage('modals.main.settings.sections.appearance.font.style.oblique')}</option>
|
||||
</Dropdown>
|
||||
|
||||
<h3>{getMessage('modals.main.settings.sections.appearance.accessibility.title')}</h3>
|
||||
<Checkbox text={getMessage('modals.main.settings.sections.appearance.accessibility.text_shadow')} name='textBorder' category='other'/>
|
||||
<Checkbox text={getMessage('modals.main.settings.sections.appearance.accessibility.animations')} name='animations' category='other'/>
|
||||
<Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.toast_duration')} name='toastDisplayTime' default='2500' step='100' min='500' max='5000' marks={values('toast')} toast={true}
|
||||
display={' ' + getMessage('modals.main.settings.sections.appearance.accessibility.milliseconds')} />
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.accessibility.title')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.description',
|
||||
)}
|
||||
final={true}
|
||||
>
|
||||
<Dropdown
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.title',
|
||||
)}
|
||||
name="textBorder"
|
||||
category="other"
|
||||
>
|
||||
<option value="new">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.new',
|
||||
)}
|
||||
</option>{' '}
|
||||
{/* default */}
|
||||
<option value="true">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.old',
|
||||
)}
|
||||
</option>{' '}
|
||||
{/* old checkbox setting */}
|
||||
<option value="none">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.text_shadow.none',
|
||||
)}
|
||||
</option>
|
||||
</Dropdown>
|
||||
<Checkbox
|
||||
text={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.animations',
|
||||
)}
|
||||
name="animations"
|
||||
category="other"
|
||||
/>
|
||||
<Slider
|
||||
title={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.toast_duration',
|
||||
)}
|
||||
name="toastDisplayTime"
|
||||
default="2500"
|
||||
step="100"
|
||||
min="500"
|
||||
max="5000"
|
||||
marks={values('toast')}
|
||||
display={
|
||||
' ' +
|
||||
variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.accessibility.milliseconds',
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SettingsItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(AppearanceSettings);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import { WifiOff } from '@mui/icons-material';
|
||||
import { MdOutlineWifiOff } from 'react-icons/md';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
import Lightbox from '../../marketplace/Lightbox';
|
||||
@@ -11,35 +11,43 @@ export default class Changelog extends PureComponent {
|
||||
this.state = {
|
||||
title: null,
|
||||
showLightbox: false,
|
||||
lightboxImg: null
|
||||
lightboxImg: null,
|
||||
};
|
||||
this.offlineMode = (localStorage.getItem('offlineMode') === 'true');
|
||||
this.offlineMode = localStorage.getItem('offlineMode') === 'true';
|
||||
this.controller = new AbortController();
|
||||
this.changelog = createRef();
|
||||
}
|
||||
|
||||
async getUpdate() {
|
||||
const data = await (await fetch(variables.constants.BLOG_POST + '/index.json', { signal: this.controller.signal })).json();
|
||||
const res = await fetch(variables.constants.BLOG_POST + '/index.json', {
|
||||
signal: this.controller.signal,
|
||||
});
|
||||
|
||||
if (res.status === 404) {
|
||||
this.setState({ error: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
let date = new Date(data.date.split(' ')[0]);
|
||||
date = date.toLocaleDateString(variables.languagecode.replace('_', '-'), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
date = date.toLocaleDateString(variables.languagecode.replace('_', '-'), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
||||
this.setState({
|
||||
title: data.title,
|
||||
date,
|
||||
image: data.featured_image || null,
|
||||
author: variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.changelog.by', {
|
||||
author: data.authors.join(', ')
|
||||
author: variables.getMessage('modals.main.settings.sections.changelog.by', {
|
||||
author: data.authors.join(', '),
|
||||
}),
|
||||
html: data.html
|
||||
content: data.markdown,
|
||||
});
|
||||
|
||||
// lightbox etc
|
||||
@@ -51,7 +59,7 @@ export default class Changelog extends PureComponent {
|
||||
img.onclick = () => {
|
||||
this.setState({
|
||||
showLightbox: true,
|
||||
lightboxImg: img.src
|
||||
lightboxImg: img.src,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -62,12 +70,12 @@ export default class Changelog extends PureComponent {
|
||||
links[link].rel = 'noopener noreferrer';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || this.offlineMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.getUpdate();
|
||||
}
|
||||
|
||||
@@ -77,38 +85,74 @@ export default class Changelog extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
const errorMessage = (msg) => {
|
||||
return (
|
||||
<div className='emptyitems'>
|
||||
<div className='emptyMessage'>
|
||||
{msg}
|
||||
</div>
|
||||
<div className="emptyItems">
|
||||
<div className="emptyMessage">{msg}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (navigator.onLine === false || this.offlineMode) {
|
||||
return errorMessage(<>
|
||||
<WifiOff/>
|
||||
<h1>{getMessage('modals.main.marketplace.offline.title')}</h1>
|
||||
<p className='description'>{getMessage('modals.main.marketplace.offline.description')}</p>
|
||||
</>);
|
||||
if (navigator.onLine === false || this.offlineMode) {
|
||||
return errorMessage(
|
||||
<>
|
||||
<MdOutlineWifiOff />
|
||||
<h1>{variables.getMessage('modals.main.marketplace.offline.title')}</h1>
|
||||
<p className="description">
|
||||
{variables.getMessage('modals.main.marketplace.offline.description')}
|
||||
</p>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (this.state.error === true) {
|
||||
return errorMessage(
|
||||
<>
|
||||
<MdOutlineWifiOff />
|
||||
<span className="title">{variables.getMessage('modals.main.error_boundary.title')}</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.error_boundary.message')}
|
||||
</span>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.title) {
|
||||
return errorMessage(<h1>{getMessage('modals.main.loading')}</h1>);
|
||||
return errorMessage(
|
||||
<div className="loaderHolder">
|
||||
<div id="loader"></div>
|
||||
<span className="subtitle">{variables.getMessage('modals.main.loading')}</span>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='changelogtab' ref={this.changelog}>
|
||||
<div className="changelogtab" ref={this.changelog}>
|
||||
<h1>{this.state.title}</h1>
|
||||
<h5>{this.state.author} • {this.state.date}</h5>
|
||||
{this.state.image ? <img draggable='false' src={this.state.image} alt={this.state.title} className='updateimage'/> : null}
|
||||
<div className='updatechangelog' dangerouslySetInnerHTML={{ __html: this.state.html }}/>
|
||||
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ showLightbox: false })} isOpen={this.state.showLightbox} className='Modal lightboxmodal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
|
||||
<Lightbox modalClose={() => this.setState({ showLightbox: false })} img={this.state.lightboxImg}/>
|
||||
<h5>
|
||||
{this.state.author} • {this.state.date}
|
||||
</h5>
|
||||
{this.state.image ? (
|
||||
<img
|
||||
draggable={false}
|
||||
src={this.state.image}
|
||||
alt={this.state.title}
|
||||
className="updateImage"
|
||||
/>
|
||||
) : null}
|
||||
<div className="updateChangelog">{this.state.content}</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={this.state.lightboxImg}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,62 +4,122 @@ import { PureComponent } from 'react';
|
||||
import Header from '../Header';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
import SettingsItem from '../SettingsItem';
|
||||
|
||||
export default class DateSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
dateType: localStorage.getItem('dateType') || 'long'
|
||||
dateType: localStorage.getItem('dateType') || 'long',
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
let dateSettings;
|
||||
|
||||
const longSettings = (
|
||||
<>
|
||||
<Checkbox name='dayofweek' text={getMessage('modals.main.settings.sections.date.day_of_week')} category='date' />
|
||||
<Checkbox name='datenth' text={getMessage('modals.main.settings.sections.date.datenth')} category='date' />
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.settings.sections.date.long_format')}
|
||||
name="longFormat"
|
||||
category="date"
|
||||
>
|
||||
<option value="DMY">DMY</option>
|
||||
<option value="MDY">MDY</option>
|
||||
<option value="YMD">YMD</option>
|
||||
</Dropdown>
|
||||
<Checkbox
|
||||
name="dayofweek"
|
||||
text={variables.getMessage('modals.main.settings.sections.date.day_of_week')}
|
||||
category="date"
|
||||
/>
|
||||
<Checkbox
|
||||
name="datenth"
|
||||
text={variables.getMessage('modals.main.settings.sections.date.datenth')}
|
||||
category="date"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const shortSettings = (
|
||||
<>
|
||||
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.date.short_format')} name='dateFormat' category='date'>
|
||||
<option value='DMY'>DMY</option>
|
||||
<option value='MDY'>MDY</option>
|
||||
<option value='YMD'>YMD</option>
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.settings.sections.date.short_format')}
|
||||
name="dateFormat"
|
||||
category="date"
|
||||
>
|
||||
<option value="DMY">DMY</option>
|
||||
<option value="MDY">MDY</option>
|
||||
<option value="YMD">YMD</option>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.settings.sections.date.short_separator.title')}
|
||||
name="shortFormat"
|
||||
category="date"
|
||||
>
|
||||
<option value="dash">
|
||||
{variables.getMessage('modals.main.settings.sections.date.short_separator.dash')}
|
||||
</option>
|
||||
<option value="dots">
|
||||
{variables.getMessage('modals.main.settings.sections.date.short_separator.dots')}
|
||||
</option>
|
||||
<option value="gaps">
|
||||
{variables.getMessage('modals.main.settings.sections.date.short_separator.gaps')}
|
||||
</option>
|
||||
<option value="slashes">
|
||||
{variables.getMessage('modals.main.settings.sections.date.short_separator.slashes')}
|
||||
</option>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.date.short_separator.title')} name='shortFormat' category='date'>
|
||||
<option value='dash'>{getMessage('modals.main.settings.sections.date.short_separator.dash')}</option>
|
||||
<option value='dots'>{getMessage('modals.main.settings.sections.date.short_separator.dots')}</option>
|
||||
<option value='gaps'>{getMessage('modals.main.settings.sections.date.short_separator.gaps')}</option>
|
||||
<option value='slashes'>{getMessage('modals.main.settings.sections.date.short_separator.slashes')}</option>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
|
||||
switch (this.state.dateType) {
|
||||
case 'short': dateSettings = shortSettings; break;
|
||||
case 'long': dateSettings = longSettings; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title={getMessage('modals.main.settings.sections.date.title')} setting='date' category='date' element='.date' zoomSetting='zoomDate'/>
|
||||
<Checkbox name='weeknumber' text={getMessage('modals.main.settings.sections.date.week_number')} category='date'/>
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.time.type')} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date'>
|
||||
<option value='long'>{getMessage('modals.main.settings.sections.date.type.long')}</option>
|
||||
<option value='short'>{getMessage('modals.main.settings.sections.date.type.short')}</option>
|
||||
</Dropdown>
|
||||
|
||||
<Checkbox name='datezero' text={getMessage('modals.main.settings.sections.time.digital.zero')} category='date'/>
|
||||
{dateSettings}
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.date.title')}
|
||||
setting="date"
|
||||
category="date"
|
||||
element=".date"
|
||||
zoomSetting="zoomDate"
|
||||
switch={true}
|
||||
/>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.time.type')}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.date.type.subtitle')}
|
||||
>
|
||||
<Dropdown
|
||||
name="dateType"
|
||||
onChange={(value) => this.setState({ dateType: value })}
|
||||
category="date"
|
||||
>
|
||||
<option value="long">
|
||||
{variables.getMessage('modals.main.settings.sections.date.type.long')}
|
||||
</option>
|
||||
<option value="short">
|
||||
{variables.getMessage('modals.main.settings.sections.date.type.short')}
|
||||
</option>
|
||||
</Dropdown>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={
|
||||
this.state.dateType === 'long'
|
||||
? variables.getMessage('modals.main.settings.sections.date.type.long')
|
||||
: variables.getMessage('modals.main.settings.sections.date.type.short')
|
||||
}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.date.type_settings')}
|
||||
final={true}
|
||||
>
|
||||
{this.state.dateType === 'long' ? longSettings : shortSettings}
|
||||
<Checkbox
|
||||
name="weeknumber"
|
||||
text={variables.getMessage('modals.main.settings.sections.date.week_number')}
|
||||
category="date"
|
||||
/>
|
||||
<Checkbox
|
||||
name="datezero"
|
||||
text={variables.getMessage('modals.main.settings.sections.time.digital.zero')}
|
||||
category="date"
|
||||
/>
|
||||
</SettingsItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,71 @@
|
||||
import variables from 'modules/variables';
|
||||
import { useState } from 'react';
|
||||
import { useState, memo } from 'react';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Slider from '../Slider';
|
||||
import { TextField } from '@mui/material';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
import { values } from 'modules/helpers/settings/modals';
|
||||
import SettingsItem from '../SettingsItem';
|
||||
|
||||
export default function ExperimentalSettings() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
function ExperimentalSettings() {
|
||||
const [eventType, setEventType] = useState();
|
||||
const [eventName, setEventName] = useState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{getMessage('modals.main.settings.sections.experimental.title')}</h2>
|
||||
<p>{getMessage('modals.main.settings.sections.experimental.warning')}</p>
|
||||
<h3>{getMessage('modals.main.settings.sections.experimental.developer')}</h3>
|
||||
<Checkbox name='debug' text='Debug hotkey (Ctrl + #)' element='.other'/>
|
||||
<Slider title='Debug timeout' name='debugtimeout' min='0' max='5000' default='0' step='100' marks={values('experimental')} element='.other' />
|
||||
<p>Send Event</p>
|
||||
<TextField label={'Type'} value={eventType} onChange={(e) => setEventType(e.target.value)} spellCheck={false} varient='outlined' InputLabelProps={{ shrink: true }} />
|
||||
<TextField label={'Name'} value={eventName} onChange={(e) => setEventName(e.target.value)} spellCheck={false} varient='outlined' InputLabelProps={{ shrink: true }} />
|
||||
<br/>
|
||||
<button className='uploadbg' onClick={() => EventBus.dispatch(eventType, eventName)}>Send</button>
|
||||
<br/><br/>
|
||||
<button className='reset' style={{ marginLeft: '0px' }} onClick={() => localStorage.clear()}>Clear LocalStorage</button>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.experimental.title')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.experimental.warning')}
|
||||
</span>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.experimental.developer')}
|
||||
>
|
||||
<Checkbox name="debug" text="Debug hotkey (Ctrl + #)" element=".other" />
|
||||
<Slider
|
||||
title="Debug timeout"
|
||||
name="debugtimeout"
|
||||
min="0"
|
||||
max="5000"
|
||||
default="0"
|
||||
step="100"
|
||||
marks={values('experimental')}
|
||||
element=".other"
|
||||
/>
|
||||
<p style={{ textAlign: 'left' }}>Send Event</p>
|
||||
<TextField
|
||||
label={'Type'}
|
||||
value={eventType}
|
||||
onChange={(e) => setEventType(e.target.value)}
|
||||
spellCheck={false}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<TextField
|
||||
label={'Name'}
|
||||
value={eventName}
|
||||
onChange={(e) => setEventName(e.target.value)}
|
||||
spellCheck={false}
|
||||
varient="outlined"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<button className="uploadbg" onClick={() => EventBus.emit(eventType, eventName)}>
|
||||
Send
|
||||
</button>
|
||||
</SettingsItem>
|
||||
<SettingsItem title="Data" final={true}>
|
||||
<button
|
||||
className="reset"
|
||||
style={{ marginLeft: '0px' }}
|
||||
onClick={() => localStorage.clear()}
|
||||
>
|
||||
Clear LocalStorage
|
||||
</button>
|
||||
</SettingsItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ExperimentalSettings);
|
||||
|
||||
@@ -5,12 +5,13 @@ import Header from '../Header';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Switch from '../Switch';
|
||||
import Text from '../Text';
|
||||
import SettingsItem from '../SettingsItem';
|
||||
|
||||
export default class GreetingSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
birthday: new Date(localStorage.getItem('birthday')) || new Date()
|
||||
birthday: new Date(localStorage.getItem('birthday')) || new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,26 +19,68 @@ export default class GreetingSettings extends PureComponent {
|
||||
localStorage.setItem('birthday', e.target.value || new Date());
|
||||
|
||||
this.setState({
|
||||
birthday: e.target.value ? new Date(e.target.value) : new Date()
|
||||
birthday: e.target.value ? new Date(e.target.value) : new Date(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title={getMessage('modals.main.settings.sections.greeting.title')} setting='greeting' category='greeting' element='.greeting' zoomSetting='zoomGreeting'/>
|
||||
<Checkbox name='events' text={getMessage('modals.main.settings.sections.greeting.events')} category='greeting'/>
|
||||
<Checkbox name='defaultGreetingMessage' text={getMessage('modals.main.settings.sections.greeting.default')} category='greeting'/>
|
||||
<Text title={getMessage('modals.main.settings.sections.greeting.name')} name='greetingName' category='greeting'/>
|
||||
|
||||
<h3>{getMessage('modals.main.settings.sections.greeting.birthday')}</h3>
|
||||
<Switch name='birthdayenabled' text={getMessage('modals.main.settings.enabled')} category='greeting'/>
|
||||
<br/>
|
||||
<Checkbox name='birthdayage' text={getMessage('modals.main.settings.sections.greeting.birthday_age')} category='greeting'/>
|
||||
<p>{getMessage('modals.main.settings.sections.greeting.birthday_date')}</p>
|
||||
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)} required/>
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.greeting.title')}
|
||||
setting="greeting"
|
||||
category="greeting"
|
||||
element=".greeting"
|
||||
zoomSetting="zoomGreeting"
|
||||
switch={true}
|
||||
/>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.additional_settings')}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.greeting.additional')}
|
||||
>
|
||||
<Checkbox
|
||||
name="events"
|
||||
text={variables.getMessage('modals.main.settings.sections.greeting.events')}
|
||||
category="greeting"
|
||||
/>
|
||||
<Checkbox
|
||||
name="defaultGreetingMessage"
|
||||
text={variables.getMessage('modals.main.settings.sections.greeting.default')}
|
||||
category="greeting"
|
||||
/>
|
||||
<Text
|
||||
title={variables.getMessage('modals.main.settings.sections.greeting.name')}
|
||||
name="greetingName"
|
||||
category="greeting"
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.greeting.birthday')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.greeting.birthday_subtitle',
|
||||
)}
|
||||
final={true}
|
||||
>
|
||||
<Switch
|
||||
name="birthdayenabled"
|
||||
text={variables.getMessage('modals.main.settings.enabled')}
|
||||
category="greeting"
|
||||
/>
|
||||
<Checkbox
|
||||
name="birthdayage"
|
||||
text={variables.getMessage('modals.main.settings.sections.greeting.birthday_age')}
|
||||
category="greeting"
|
||||
/>
|
||||
<p style={{ marginRight: 'auto' }}>
|
||||
{variables.getMessage('modals.main.settings.sections.greeting.birthday_date')}
|
||||
</p>
|
||||
<input
|
||||
type="date"
|
||||
onChange={this.changeDate}
|
||||
value={this.state.birthday.toISOString().substr(0, 10)}
|
||||
required
|
||||
/>
|
||||
</SettingsItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import Header from '../Header';
|
||||
import KeybindInput from '../KeybindInput';
|
||||
|
||||
export default class KeybindSettings extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
keybinds: JSON.parse(localStorage.getItem('keybinds')) || {},
|
||||
cancelled: false
|
||||
};
|
||||
}
|
||||
|
||||
showReminder() {
|
||||
document.querySelector('.reminder-info').style.display = 'none';
|
||||
return localStorage.setItem('showReminder', false);
|
||||
}
|
||||
|
||||
listen(type) {
|
||||
const currentKeybinds = this.state.keybinds;
|
||||
currentKeybinds[type] = this.getMessage('modals.main.settings.sections.keybinds.recording');
|
||||
this.setState({
|
||||
keybinds: currentKeybinds,
|
||||
cancelled: false
|
||||
});
|
||||
this.forceUpdate();
|
||||
|
||||
let keys = '';
|
||||
let previouskey = '';
|
||||
this.keydown = document.addEventListener('keydown', (event) => {
|
||||
if (event.key === previouskey && this.state.cancelled === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keys === '') {
|
||||
keys = event.key;
|
||||
} else {
|
||||
keys = `${keys}+${event.key}`;
|
||||
}
|
||||
|
||||
previouskey = event.key
|
||||
});
|
||||
|
||||
this.keyup = document.addEventListener('keyup', () => {
|
||||
if (this.state.cancelled === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.removeEventListener('keydown', this.keydown);
|
||||
const keybinds = this.state.keybinds;
|
||||
keybinds[type] = keys.split('+').slice(0, 4).join('+');
|
||||
localStorage.setItem('keybinds', JSON.stringify(keybinds));
|
||||
this.setState({
|
||||
keybinds: JSON.parse(localStorage.getItem('keybinds')) || {}
|
||||
});
|
||||
});
|
||||
|
||||
document.removeEventListener('keyup', this.keyup);
|
||||
|
||||
this.showReminder();
|
||||
}
|
||||
|
||||
cancel(type) {
|
||||
document.removeEventListener('keydown', this.keydown);
|
||||
document.removeEventListener('keyup', this.keyup);
|
||||
|
||||
const currentKeybinds = this.state.keybinds;
|
||||
delete currentKeybinds[type];
|
||||
|
||||
this.setState({
|
||||
keybinds: currentKeybinds,
|
||||
cancelled: true
|
||||
});
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
reset(type) {
|
||||
const keybinds = this.state.keybinds;
|
||||
keybinds[type] = '';
|
||||
localStorage.setItem('keybinds', JSON.stringify(keybinds));
|
||||
|
||||
this.setState({
|
||||
keybinds: JSON.parse(localStorage.getItem('keybinds')) || {},
|
||||
cancelled: true
|
||||
});
|
||||
|
||||
this.showReminder();
|
||||
}
|
||||
|
||||
action(action, e) {
|
||||
switch (action) {
|
||||
case 'listen':
|
||||
this.listen(e);
|
||||
break;
|
||||
case 'cancel':
|
||||
this.cancel(e);
|
||||
break;
|
||||
case 'reset':
|
||||
this.reset(e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Header title={this.getMessage('modals.main.settings.sections.keybinds.title')} setting='keybindsEnabled' element='.other' />
|
||||
<table className='keybind-table'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.background.favourite')} state={this.state.keybinds} setting='favouriteBackground' action={(type, e) => this.action(type, e)}/></th>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.background.maximise')} state={this.state.keybinds} setting='maximiseBackground' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.background.download')} state={this.state.keybinds} setting='downloadBackground' action={(type, e) => this.action(type, e)}/></th>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.background.show_info')} state={this.state.keybinds} setting='showBackgroundInformation' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.background.show_info')} state={this.state.keybinds} setting='showBackgroundInformation' action={(type, e) => this.action(type, e)}/></th>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.quote.favourite')} state={this.state.keybinds} setting='favouriteQuote' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.quote.copy')} state={this.state.keybinds} setting='copyQuote' action={(type, e) => this.action(type, e)}/></th>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.quote.tweet')} state={this.state.keybinds} setting='tweetQuote' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.notes.pin')} state={this.state.keybinds} setting='pinNotes' action={(type, e) => this.action(type, e)}/></th>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.notes.copy')} state={this.state.keybinds} setting='copyNotes' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.search')} state={this.state.keybinds} setting='focusSearch' action={(type, e) => this.action(type, e)}/></th>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.quicklinks')} state={this.state.keybinds} setting='toggleQuicklinks' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><KeybindInput name={this.getMessage('modals.main.settings.sections.keybinds.modal')} state={this.state.keybinds} setting='toggleModal' action={(type, e) => this.action(type, e)}/></th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,53 +3,60 @@ import { PureComponent } from 'react';
|
||||
|
||||
import Radio from '../Radio';
|
||||
|
||||
const languages = require('modules/languages.json');
|
||||
import languages from 'modules/languages.json';
|
||||
|
||||
export default class LanguageSettings extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
quoteLanguages: [{
|
||||
name: this.getMessage('modals.main.loading'),
|
||||
value: 'loading'
|
||||
}]
|
||||
quoteLanguages: [
|
||||
{
|
||||
name: variables.getMessage('modals.main.loading'),
|
||||
value: 'loading',
|
||||
},
|
||||
],
|
||||
};
|
||||
this.controller = new AbortController();
|
||||
}
|
||||
|
||||
async getQuoteLanguages() {
|
||||
const data = await (await fetch(variables.constants.API_URL + '/quotes/languages', { signal: this.controller.signal })).json();
|
||||
async getquoteLanguages() {
|
||||
const data = await (
|
||||
await fetch(variables.constants.API_URL + '/quotes/languages', {
|
||||
signal: this.controller.signal,
|
||||
})
|
||||
).json();
|
||||
|
||||
if (this.controller.signal.aborted === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const quoteLanguages = [];
|
||||
data.forEach((item) => {
|
||||
quoteLanguages.push({
|
||||
name: item,
|
||||
value: item
|
||||
});
|
||||
const quoteLanguages = data.map((language) => {
|
||||
return {
|
||||
name: languages.find((l) => l.value === language.name)
|
||||
? languages.find((l) => l.value === language.name).name
|
||||
: 'English',
|
||||
value: language,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({
|
||||
quoteLanguages
|
||||
quoteLanguages,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
|
||||
return this.setState({
|
||||
quoteLanguages: [{
|
||||
name: this.getMessage('modals.main.marketplace.offline.description'),
|
||||
value: 'loading'
|
||||
}]
|
||||
quoteLanguages: [
|
||||
{
|
||||
name: variables.getMessage('modals.main.marketplace.offline.description'),
|
||||
value: 'loading',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
this.getQuoteLanguages();
|
||||
this.getquoteLanguages();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -60,10 +67,25 @@ export default class LanguageSettings extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h2>{this.getMessage('modals.main.settings.sections.language.title')}</h2>
|
||||
<Radio name='language' options={languages} element='.other' />
|
||||
<h3>{this.getMessage('modals.main.settings.sections.language.quote')}</h3>
|
||||
<Radio name='quotelanguage' options={this.state.quoteLanguages} category='quote' />
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.language.title')}
|
||||
</span>
|
||||
<div className="languageSettings">
|
||||
<Radio name="language" options={languages} element=".other" />
|
||||
</div>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.settings.sections.language.quote')}
|
||||
</span>
|
||||
<div className="languageSettings">
|
||||
<Radio
|
||||
name="quoteLanguage"
|
||||
options={this.state.quoteLanguages.map((language) => {
|
||||
return { name: language.name, value: language.value.name };
|
||||
})}
|
||||
defaultValue={this.state.quoteLanguages[0].name}
|
||||
category="quote"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { Cancel, Add } from '@mui/icons-material';
|
||||
import { MdCancel, MdAdd, MdOutlineTextsms } from 'react-icons/md';
|
||||
import { toast } from 'react-toastify';
|
||||
import { TextField } from '@mui/material';
|
||||
import { TextareaAutosize } from '@mui/material';
|
||||
|
||||
import SettingsItem from '../SettingsItem';
|
||||
import Header from '../Header';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class Message extends PureComponent {
|
||||
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
messages: JSON.parse(localStorage.getItem('messages')) || [''],
|
||||
messages: JSON.parse(localStorage.getItem('messages')) || [],
|
||||
};
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
localStorage.setItem('messages', '[""]');
|
||||
localStorage.setItem('messages', '[]');
|
||||
this.setState({
|
||||
messages: ['']
|
||||
messages: [],
|
||||
});
|
||||
toast(this.getMessage(this.languagecode, 'toasts.reset'));
|
||||
EventBus.dispatch('refresh', 'message');
|
||||
}
|
||||
toast(variables.getMessage(this.languagecode, 'toasts.reset'));
|
||||
EventBus.emit('refresh', 'message');
|
||||
};
|
||||
|
||||
modifyMessage(type, index) {
|
||||
const messages = this.state.messages;
|
||||
@@ -36,7 +35,7 @@ export default class Message extends PureComponent {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
messages
|
||||
messages,
|
||||
});
|
||||
this.forceUpdate();
|
||||
|
||||
@@ -44,37 +43,92 @@ export default class Message extends PureComponent {
|
||||
}
|
||||
|
||||
message(e, text, index) {
|
||||
const result = (text === true) ? e.target.value : e.target.result;
|
||||
const result = text === true ? e.target.value : e.target.result;
|
||||
|
||||
const messages = this.state.messages;
|
||||
messages[index] = result;
|
||||
this.setState({
|
||||
messages
|
||||
messages,
|
||||
});
|
||||
this.forceUpdate();
|
||||
|
||||
localStorage.setItem('messages', JSON.stringify(messages));
|
||||
document.querySelector('.reminder-info').style.display = 'block';
|
||||
document.querySelector('.reminder-info').style.display = 'flex';
|
||||
localStorage.setItem('showReminder', true);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Header title={this.getMessage('modals.main.settings.sections.message.title')} setting='message' category='message' element='.message' zoomSetting='zoomMessage'/>
|
||||
<p>{this.getMessage('modals.main.settings.sections.message.text')}</p>
|
||||
<div className='data-buttons-row'>
|
||||
<button onClick={() => this.modifyMessage('add')}>{this.getMessage('modals.main.settings.sections.message.add')} <Add/></button>
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.message.title')}
|
||||
setting="message"
|
||||
category="message"
|
||||
element=".message"
|
||||
zoomSetting="zoomMessage"
|
||||
switch={true}
|
||||
/>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.message.messages')}
|
||||
final={true}
|
||||
>
|
||||
<button onClick={() => this.modifyMessage('add')}>
|
||||
{variables.getMessage('modals.main.settings.sections.message.add')} <MdAdd />
|
||||
</button>
|
||||
</SettingsItem>
|
||||
<div className="messagesContainer">
|
||||
{this.state.messages.map((_url, index) => (
|
||||
<div className="messageMap" key={index}>
|
||||
<div className="flexGrow">
|
||||
<div className="icon">
|
||||
<MdOutlineTextsms />
|
||||
</div>
|
||||
<div className="messageText">
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.message.title')}
|
||||
</span>
|
||||
<TextareaAutosize
|
||||
value={this.state.messages[index]}
|
||||
placeholder={variables.getMessage(
|
||||
'modals.main.settings.sections.message.content',
|
||||
)}
|
||||
onChange={(e) => this.message(e, true, index)}
|
||||
varient="outlined"
|
||||
style={{ padding: '0' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="messageAction">
|
||||
<button
|
||||
className="deleteButton"
|
||||
onClick={() => this.modifyMessage('remove', index)}
|
||||
>
|
||||
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
|
||||
<MdCancel />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{this.state.messages.map((_url, index) => (
|
||||
<div style={{ display: 'flex' }} key={index}>
|
||||
<TextField value={this.state.messages[index]} onChange={(e) => this.message(e, true, index)} varient='outlined' />
|
||||
{this.state.messages.length > 1 ? <button className='cleanButton' onClick={() => this.modifyMessage('remove', index)}>
|
||||
<Cancel/>
|
||||
</button> : null}
|
||||
{this.state.messages.length === 0 ? (
|
||||
<div className="photosEmpty">
|
||||
<div className="emptyNewMessage">
|
||||
<MdOutlineTextsms />
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.message.no_messages')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.message.add_some')}
|
||||
</span>
|
||||
<button onClick={() => this.modifyMessage('add')}>
|
||||
{variables.getMessage('modals.main.settings.sections.message.add')}
|
||||
<MdAdd />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<br/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,90 @@
|
||||
import variables from 'modules/variables';
|
||||
|
||||
import { PureComponent } from 'react';
|
||||
import { useState, memo } from 'react';
|
||||
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
import Slider from '../Slider';
|
||||
|
||||
import { values } from 'modules/helpers/settings/modals';
|
||||
import SettingsItem from '../SettingsItem';
|
||||
import Header from '../Header';
|
||||
|
||||
export default class Navbar extends PureComponent {
|
||||
render() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
function Navbar() {
|
||||
const [showRefreshOptions, setShowRefreshOptions] = useState(
|
||||
localStorage.getItem('refresh') === 'true',
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{getMessage('modals.main.settings.sections.appearance.navbar.title')}</h2>
|
||||
<Slider title={getMessage('modals.main.settings.sections.appearance.accessibility.widget_zoom')} name='zoomNavbar' min='10' max='400' default='100' display='%' marks={values('zoom')} category='navbar' />
|
||||
<Checkbox name='navbarHover' text={getMessage('modals.main.settings.sections.appearance.navbar.hover')} category='navbar'/>
|
||||
<Checkbox name='notesEnabled' text={getMessage('modals.main.settings.sections.appearance.navbar.notes')} category='navbar' />
|
||||
<Checkbox name='view' text={getMessage('modals.main.settings.sections.background.buttons.view')} category='navbar' />
|
||||
<Checkbox name='favouriteEnabled' text={getMessage('modals.main.settings.sections.background.buttons.favourite')} category='navbar' />
|
||||
<Dropdown label={getMessage('modals.main.settings.sections.appearance.navbar.refresh')} name='refresh' category='navbar'>
|
||||
<option value='false'>{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.none')}</option>
|
||||
<option value='background'>{getMessage('modals.main.settings.sections.background.title')}</option>
|
||||
<option value='quote'>{getMessage('modals.main.settings.sections.quote.title')}</option>
|
||||
<option value='quotebackground'>{getMessage('modals.main.settings.sections.quote.title')} + {getMessage('modals.main.settings.sections.background.title')}</option>
|
||||
{/* before it was just a checkbox */}
|
||||
<option value='true'>{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.page')}</option>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.navbar.title')}
|
||||
setting="navbar"
|
||||
category="widgets"
|
||||
zoomSetting="zoomNavbar"
|
||||
zoomCategory="navbar"
|
||||
/>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.additional_settings')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.navbar.additional',
|
||||
)}
|
||||
final={!showRefreshOptions}
|
||||
>
|
||||
<Checkbox
|
||||
name="navbarHover"
|
||||
text={variables.getMessage('modals.main.settings.sections.appearance.navbar.hover')}
|
||||
category="navbar"
|
||||
/>
|
||||
<Checkbox
|
||||
name="notesEnabled"
|
||||
text={variables.getMessage('modals.main.settings.sections.appearance.navbar.notes')}
|
||||
category="navbar"
|
||||
/>
|
||||
<Checkbox
|
||||
name="view"
|
||||
text={variables.getMessage('modals.main.settings.sections.background.buttons.view')}
|
||||
category="navbar"
|
||||
/>
|
||||
<Checkbox
|
||||
name="refresh"
|
||||
text={variables.getMessage('modals.main.settings.sections.appearance.navbar.refresh')}
|
||||
category="navbar"
|
||||
onChange={setShowRefreshOptions}
|
||||
/>
|
||||
<Checkbox
|
||||
name="todoEnabled"
|
||||
text={variables.getMessage('widgets.navbar.todo.title')}
|
||||
category="navbar"
|
||||
/>
|
||||
</SettingsItem>
|
||||
{showRefreshOptions ? (
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.appearance.navbar.refresh')}
|
||||
subtitle={variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.navbar.refresh_subtitle',
|
||||
)}
|
||||
final={true}
|
||||
>
|
||||
<Dropdown name="refreshOption" category="navbar">
|
||||
<option value="page">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.appearance.navbar.refresh_options.page',
|
||||
)}
|
||||
</option>
|
||||
<option value="background">
|
||||
{variables.getMessage('modals.main.settings.sections.background.title')}
|
||||
</option>
|
||||
<option value="quote">
|
||||
{variables.getMessage('modals.main.settings.sections.quote.title')}
|
||||
</option>
|
||||
<option value="quotebackground">
|
||||
{variables.getMessage('modals.main.settings.sections.quote.title')} +{' '}
|
||||
{variables.getMessage('modals.main.settings.sections.background.title')}
|
||||
</option>
|
||||
</Dropdown>
|
||||
</SettingsItem>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Navbar);
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { DragIndicator } from '@mui/icons-material';
|
||||
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
const widget_name = {
|
||||
greeting: getMessage('modals.main.settings.sections.greeting.title'),
|
||||
time: getMessage('modals.main.settings.sections.time.title'),
|
||||
quicklinks: getMessage('modals.main.settings.sections.quicklinks.title'),
|
||||
quote: getMessage('modals.main.settings.sections.quote.title'),
|
||||
date: getMessage('modals.main.settings.sections.date.title'),
|
||||
message: getMessage('modals.main.settings.sections.message.title')
|
||||
};
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => (
|
||||
<li className='sortableitem'>
|
||||
<DragIndicator style={{ verticalAlign: 'middle' }} />
|
||||
{widget_name[value]}
|
||||
</li>
|
||||
));
|
||||
|
||||
const SortableContainer = sortableContainer(({ children }) => (
|
||||
<ul className='sortablecontainer'>{children}</ul>
|
||||
));
|
||||
|
||||
export default class OrderSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
items: JSON.parse(localStorage.getItem('order'))
|
||||
};
|
||||
}
|
||||
|
||||
arrayMove(array, oldIndex, newIndex) {
|
||||
const result = Array.from(array);
|
||||
const [removed] = result.splice(oldIndex, 1);
|
||||
result.splice(newIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
this.setState({
|
||||
items: this.arrayMove(this.state.items, oldIndex, newIndex)
|
||||
});
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
localStorage.setItem('order', JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']));
|
||||
|
||||
this.setState({
|
||||
items: JSON.parse(localStorage.getItem('order'))
|
||||
});
|
||||
|
||||
toast(getMessage('toats.reset'));
|
||||
}
|
||||
|
||||
enabled = (setting) => {
|
||||
switch (setting) {
|
||||
case 'quicklinks':
|
||||
return (localStorage.getItem('quicklinksenabled') === 'true');
|
||||
default:
|
||||
return (localStorage.getItem(setting) === 'true');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('order', JSON.stringify(this.state.items));
|
||||
variables.stats.postEvent('setting', 'Widget order');
|
||||
EventBus.dispatch('refresh', 'widgets');
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h2>{getMessage('modals.main.settings.sections.order.title')}</h2>
|
||||
<span className='modalLink' onClick={this.reset}>{getMessage('modals.main.settings.buttons.reset')}</span>
|
||||
<SortableContainer onSortEnd={this.onSortEnd} lockAxis='y' lockToContainerEdges disableAutoscroll>
|
||||
{this.state.items.map((value, index) => {
|
||||
if (!this.enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem key={`item-${value}`} index={index} value={value} />
|
||||
);
|
||||
})}
|
||||
</SortableContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
195
src/components/modals/main/settings/sections/Overview.jsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import variables from 'modules/variables';
|
||||
import { PureComponent } from 'react';
|
||||
import { MdOutlineDragIndicator } from 'react-icons/md';
|
||||
import { sortableContainer, sortableElement } from '@muetab/react-sortable-hoc';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Greeting from './overview_skeletons/Greeting';
|
||||
import Clock from './overview_skeletons/Clock';
|
||||
import Quote from './overview_skeletons/Quote';
|
||||
import QuickLinks from './overview_skeletons/QuickLinks';
|
||||
import Date from './overview_skeletons/Date';
|
||||
import Message from './overview_skeletons/Message';
|
||||
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
const widget_name = {
|
||||
greeting: variables.getMessage('modals.main.settings.sections.greeting.title'),
|
||||
time: variables.getMessage('modals.main.settings.sections.time.title'),
|
||||
quicklinks: variables.getMessage('modals.main.settings.sections.quicklinks.title'),
|
||||
quote: variables.getMessage('modals.main.settings.sections.quote.title'),
|
||||
date: variables.getMessage('modals.main.settings.sections.date.title'),
|
||||
message: variables.getMessage('modals.main.settings.sections.message.title'),
|
||||
};
|
||||
|
||||
const SortableItem = sortableElement(({ value }) => (
|
||||
<li className="sortableItem">
|
||||
{widget_name[value]}
|
||||
<MdOutlineDragIndicator />
|
||||
</li>
|
||||
));
|
||||
|
||||
const SortableContainer = sortableContainer(({ children }) => (
|
||||
<ul className="sortablecontainer">{children}</ul>
|
||||
));
|
||||
|
||||
export default class OrderSettings extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
items: JSON.parse(localStorage.getItem('order')),
|
||||
news: {
|
||||
title: '',
|
||||
date: '',
|
||||
description: '',
|
||||
link: '',
|
||||
linkText: '',
|
||||
},
|
||||
newsDone: false,
|
||||
};
|
||||
}
|
||||
|
||||
arrayMove(array, oldIndex, newIndex) {
|
||||
const result = Array.from(array);
|
||||
const [removed] = result.splice(oldIndex, 1);
|
||||
result.splice(newIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
this.setState({
|
||||
items: this.arrayMove(this.state.items, oldIndex, newIndex),
|
||||
});
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
localStorage.setItem(
|
||||
'order',
|
||||
JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']),
|
||||
);
|
||||
|
||||
this.setState({
|
||||
items: JSON.parse(localStorage.getItem('order')),
|
||||
});
|
||||
|
||||
toast(variables.getMessage('toasts.reset'));
|
||||
};
|
||||
|
||||
enabled = (setting) => {
|
||||
switch (setting) {
|
||||
case 'quicklinks':
|
||||
return localStorage.getItem('quicklinksenabled') === 'true';
|
||||
default:
|
||||
return localStorage.getItem(setting) === 'true';
|
||||
}
|
||||
};
|
||||
|
||||
getTab = (value) => {
|
||||
switch (value) {
|
||||
case 'greeting':
|
||||
return <Greeting />;
|
||||
case 'time':
|
||||
return <Clock />;
|
||||
case 'quicklinks':
|
||||
return <QuickLinks />;
|
||||
case 'quote':
|
||||
return <Quote />;
|
||||
case 'date':
|
||||
return <Date />;
|
||||
case 'message':
|
||||
return <Message />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
async getNews() {
|
||||
const data = await (await fetch(variables.constants.API_URL + '/news')).json();
|
||||
data.date = new window.Date(data.date).toLocaleDateString(
|
||||
variables.languagecode.replace('_', '-'),
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
},
|
||||
);
|
||||
|
||||
this.setState({
|
||||
news: data,
|
||||
newsDone: true,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
localStorage.setItem('order', JSON.stringify(this.state.items));
|
||||
variables.stats.postEvent('setting', 'Widget order');
|
||||
EventBus.emit('refresh', 'widgets');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getNews();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<span className="mainTitle">
|
||||
{variables.getMessage('modals.main.marketplace.product.overview')}
|
||||
</span>
|
||||
{/*<span className="title">{variables.getMessage('modals.main.marketplace.product.overview')}</span>
|
||||
<span className="link" onClick={this.reset}>
|
||||
{variables.getMessage('modals.main.settings.buttons.reset')}
|
||||
</span>*/}
|
||||
<div className="overviewGrid">
|
||||
<div>
|
||||
<span className="title">{variables.getMessage('modals.welcome.buttons.preview')}</span>
|
||||
<div className="tabPreview">
|
||||
<div className="previewItem" style={{ maxWidth: '50%' }}>
|
||||
{this.state.items.map((value, index) => {
|
||||
if (!this.enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="previewItem" key={`item-${value}`} index={index}>
|
||||
{' '}
|
||||
{this.getTab(value)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="overviewNews">
|
||||
<span className="title">{this.state.news.title}</span>
|
||||
<span className="subtitle">{this.state.news.date}</span>
|
||||
<span className="content">{this.state.news.description}</span>
|
||||
<a className="link" href={this.state.news.link}>
|
||||
{this.state.news.linkText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.order.title')}
|
||||
</span>
|
||||
<SortableContainer
|
||||
onSortEnd={this.onSortEnd}
|
||||
lockAxis="y"
|
||||
lockToContainerEdges
|
||||
disableAutoscroll
|
||||
>
|
||||
{this.state.items.map((value, index) => {
|
||||
if (!this.enabled(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SortableItem key={`item-${value}`} index={index} value={value} />;
|
||||
})}
|
||||
</SortableContainer>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,299 @@
|
||||
import variables from 'modules/variables';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import { MdAddLink, MdLinkOff, MdCancel, MdEdit } from 'react-icons/md';
|
||||
import Header from '../Header';
|
||||
import Checkbox from '../Checkbox';
|
||||
import Dropdown from '../Dropdown';
|
||||
import Modal from 'react-modal';
|
||||
|
||||
export default function QuickLinks() {
|
||||
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
|
||||
const [textOnly, setTextOnly] = useState(localStorage.getItem('quicklinksText') === 'true');
|
||||
import SettingsItem from '../SettingsItem';
|
||||
import AddModal from './quicklinks/AddModal';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title={getMessage('modals.main.settings.sections.quicklinks.title')} setting='quicklinksenabled' category='quicklinks' element='.quicklinks-container' zoomSetting='zoomQuicklinks'/>
|
||||
<Checkbox name='quicklinksText' text={getMessage('modals.main.settings.sections.quicklinks.text_only')} category='quicklinks' onChange={(value) => setTextOnly(value)}/>
|
||||
<Checkbox name='quicklinksddgProxy' text={getMessage('modals.main.settings.sections.background.ddg_image_proxy')} category='quicklinks' disabled={textOnly}/>
|
||||
<Checkbox name='quicklinksnewtab' text={getMessage('modals.main.settings.sections.quicklinks.open_new')} category='quicklinks'/>
|
||||
<Checkbox name='quicklinkstooltip' text={getMessage('modals.main.settings.sections.quicklinks.tooltip')} category='quicklinks' disabled={textOnly}/>
|
||||
</>
|
||||
);
|
||||
import EventBus from 'modules/helpers/eventbus';
|
||||
|
||||
export default class QuickLinks extends PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
items: JSON.parse(localStorage.getItem('quicklinks')),
|
||||
showAddModal: false,
|
||||
urlError: '',
|
||||
iconError: '',
|
||||
edit: false,
|
||||
editData: '',
|
||||
};
|
||||
this.quicklinksContainer = createRef();
|
||||
}
|
||||
|
||||
deleteLink(key, event) {
|
||||
event.preventDefault();
|
||||
|
||||
// remove link from array
|
||||
const data = JSON.parse(localStorage.getItem('quicklinks')).filter((i) => i.key !== key);
|
||||
|
||||
localStorage.setItem('quicklinks', JSON.stringify(data));
|
||||
this.setState({
|
||||
items: data,
|
||||
});
|
||||
|
||||
variables.stats.postEvent('feature', 'Quicklink delete');
|
||||
}
|
||||
|
||||
addLink(name, url, icon) {
|
||||
const data = JSON.parse(localStorage.getItem('quicklinks'));
|
||||
|
||||
// regex: https://ihateregex.io/expr/url/
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/;
|
||||
if (url.length <= 0 || urlRegex.test(url) === false) {
|
||||
return this.setState({
|
||||
urlError: variables.getMessage('widgets.quicklinks.url_error'),
|
||||
});
|
||||
}
|
||||
|
||||
if (icon.length > 0 && urlRegex.test(icon) === false) {
|
||||
return this.setState({
|
||||
iconError: variables.getMessage('widgets.quicklinks.url_error'),
|
||||
});
|
||||
}
|
||||
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
|
||||
data.push({
|
||||
name: name || url,
|
||||
url,
|
||||
icon: icon || '',
|
||||
key: Math.random().toString(36).substring(7) + 1,
|
||||
});
|
||||
|
||||
localStorage.setItem('quicklinks', JSON.stringify(data));
|
||||
|
||||
this.setState({
|
||||
items: data,
|
||||
showAddModal: false,
|
||||
urlError: '',
|
||||
iconError: '',
|
||||
});
|
||||
|
||||
variables.stats.postEvent('feature', 'Quicklink add');
|
||||
}
|
||||
|
||||
startEditLink(data) {
|
||||
this.setState({
|
||||
edit: true,
|
||||
editData: data,
|
||||
showAddModal: true,
|
||||
});
|
||||
}
|
||||
|
||||
editLink(og, name, url, icon) {
|
||||
const data = JSON.parse(localStorage.getItem('quicklinks'));
|
||||
const dataobj = data.find((i) => i.key === og.key);
|
||||
dataobj.name = name || url;
|
||||
dataobj.url = url;
|
||||
dataobj.icon = icon || '';
|
||||
|
||||
localStorage.setItem('quicklinks', JSON.stringify(data));
|
||||
|
||||
this.setState({
|
||||
items: data,
|
||||
showAddModal: false,
|
||||
edit: false,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventBus.on('refresh', (data) => {
|
||||
if (data === 'quicklinks') {
|
||||
if (localStorage.getItem('quicklinksenabled') === 'false') {
|
||||
return (this.quicklinksContainer.current.style.display = 'none');
|
||||
}
|
||||
|
||||
this.quicklinksContainer.current.style.display = 'block';
|
||||
|
||||
this.setState({
|
||||
items: JSON.parse(localStorage.getItem('quicklinks')),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventBus.off('refresh');
|
||||
}
|
||||
|
||||
render() {
|
||||
let target,
|
||||
rel = null;
|
||||
if (localStorage.getItem('quicklinksnewtab') === 'true') {
|
||||
target = '_blank';
|
||||
rel = 'noopener noreferrer';
|
||||
}
|
||||
|
||||
const useText = localStorage.getItem('quicklinksText') === 'true';
|
||||
|
||||
const quickLink = (item) => {
|
||||
if (useText) {
|
||||
return (
|
||||
<a
|
||||
className="quicklinkstext"
|
||||
key={item.key}
|
||||
onContextMenu={(e) => this.deleteLink(item.key, e)}
|
||||
href={item.url}
|
||||
target={target}
|
||||
rel={rel}
|
||||
draggable={false}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const img =
|
||||
item.icon ||
|
||||
'https://icon.horse/icon/ ' + item.url.replace('https://', '').replace('http://', '');
|
||||
|
||||
const link = (
|
||||
<div className="messageMap" key={item.key}>
|
||||
<div className="icon">
|
||||
<img
|
||||
src={img}
|
||||
alt={item.name}
|
||||
draggable={false}
|
||||
style={{ height: '30px', width: '30px' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="messageText">
|
||||
<div className="title">{item.name}</div>
|
||||
<div className="subtitle">
|
||||
<a
|
||||
className="quicklinknostyle"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={item.url}
|
||||
>
|
||||
{item.url}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="messageAction">
|
||||
<button className="deleteButton" onClick={() => this.startEditLink(item)}>
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.edit')}
|
||||
<MdEdit />
|
||||
</button>
|
||||
<button className="deleteButton" onClick={(e) => this.deleteLink(item.key, e)}>
|
||||
{variables.getMessage('modals.main.marketplace.product.buttons.remove')}
|
||||
<MdCancel />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return link;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title={variables.getMessage('modals.main.settings.sections.quicklinks.title')}
|
||||
setting="quicklinksenabled"
|
||||
category="quicklinks"
|
||||
element=".quicklinks-container"
|
||||
zoomSetting="zoomQuicklinks"
|
||||
switch={true}
|
||||
/>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.additional_settings')}
|
||||
subtitle={variables.getMessage('modals.main.settings.sections.quicklinks.additional')}
|
||||
>
|
||||
<Checkbox
|
||||
name="quicklinksnewtab"
|
||||
text={variables.getMessage('modals.main.settings.sections.quicklinks.open_new')}
|
||||
category="quicklinks"
|
||||
/>
|
||||
<Checkbox
|
||||
name="quicklinkstooltip"
|
||||
text={variables.getMessage('modals.main.settings.sections.quicklinks.tooltip')}
|
||||
category="quicklinks"
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.quicklinks.styling')}
|
||||
description={variables.getMessage(
|
||||
'modals.main.settings.sections.quicklinks.styling_description',
|
||||
)}
|
||||
>
|
||||
<Dropdown
|
||||
label={variables.getMessage('modals.main.settings.sections.quicklinks.style')}
|
||||
name="quickLinksStyle"
|
||||
category="quicklinks"
|
||||
>
|
||||
<option value="icon">
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.options.icon')}
|
||||
</option>
|
||||
<option value="text">
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.options.text_only')}
|
||||
</option>
|
||||
<option value="metro">
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.options.metro')}
|
||||
</option>
|
||||
</Dropdown>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem
|
||||
title={variables.getMessage('modals.main.settings.sections.quicklinks.title')}
|
||||
final={true}
|
||||
>
|
||||
<button onClick={() => this.setState({ showAddModal: true })}>
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.add_link')}{' '}
|
||||
<MdAddLink />
|
||||
</button>
|
||||
</SettingsItem>
|
||||
|
||||
{this.state.items.length === 0 ? (
|
||||
<div className="photosEmpty">
|
||||
<div className="emptyNewMessage">
|
||||
<MdLinkOff />
|
||||
<span className="title">
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.no_quicklinks')}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage('modals.main.settings.sections.message.add_some')}
|
||||
</span>
|
||||
<button onClick={() => this.setState({ showAddModal: true })}>
|
||||
{variables.getMessage('modals.main.settings.sections.quicklinks.add_link')}
|
||||
<MdAddLink />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="messagesContainer" ref={this.quicklinksContainer}>
|
||||
{this.state.items.map((item) => quickLink(item))}
|
||||
</div>
|
||||
<Modal
|
||||
closeTimeoutMS={100}
|
||||
onRequestClose={() => this.setState({ showAddModal: false, urlError: '', iconError: '' })}
|
||||
isOpen={this.state.showAddModal}
|
||||
className="Modal resetmodal mainModal"
|
||||
overlayClassName="Overlay resetoverlay"
|
||||
ariaHideApp={false}
|
||||
>
|
||||
<AddModal
|
||||
urlError={this.state.urlError}
|
||||
addLink={(name, url, icon) => this.addLink(name, url, icon)}
|
||||
editLink={(og, name, url, icon) => this.editLink(og, name, url, icon)}
|
||||
edit={this.state.edit}
|
||||
editData={this.state.editData}
|
||||
closeModal={() => this.setState({ showAddModal: false, urlError: '', iconError: '' })}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||