Compare commits

...

176 Commits
5.2.0 ... 6.0.3

Author SHA1 Message Date
David Ralph
a170981772 chore: release 6.0.3 2021-11-28 20:01:16 +00:00
David Ralph
14d3589551 fix: create tab
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-11-28 20:00:22 +00:00
David Ralph
c736dcf57a fix: enable photo map and update, fix welcome error, fix preview 2021-11-28 14:13:24 +00:00
David Ralph
623b54eca0 fix: create tab, modal ui
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-11-28 12:39:54 +00:00
David Ralph
1dc0389a96 fix: autocomplete search 2021-11-28 11:31:00 +00:00
David Ralph
3c7be2d64d fix: background categories dropdown 2021-11-27 23:11:40 +00:00
David Ralph
5703abb685 fix: marketplace item page responsiveness 2021-11-27 21:54:58 +00:00
dependabot[bot]
8f08b4d09d chore(deps): bump @mui/material from 5.2.0 to 5.2.1 (#215)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.2.0 to 5.2.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.2.1/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-26 10:58:57 +00:00
dependabot[bot]
441d440c74 chore(deps): bump @mui/icons-material from 5.1.1 to 5.2.0 (#213)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.1.1 to 5.2.0.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.2.0/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-24 10:40:07 +00:00
dependabot[bot]
fe5f15be24 chore(deps): bump @mui/material from 5.1.1 to 5.2.0 (#212)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.1.1 to 5.2.0.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.2.0/packages/mui-material)

---
updated-dependencies:
- dependency-name: "@mui/material"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-24 10:36:45 +00:00
David Ralph
bd941896aa chore: release 6.0.2 2021-11-23 18:39:43 +00:00
David Ralph
d9563ce343 build: fix demo 2021-11-23 18:22:24 +00:00
David Ralph
af2edeb737 fix: various ui fixes, fix create tab
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-11-23 18:11:36 +00:00
David Ralph
f8ee23eb40 fix: notes breaking mue when there are many rows 2021-11-22 22:41:17 +00:00
dependabot[bot]
234d489a29 chore(deps-dev): bump copy-webpack-plugin from 9.1.0 to 10.0.0 (#209)
Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 9.1.0 to 10.0.0.
- [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v9.1.0...v10.0.0)

---
updated-dependencies:
- dependency-name: copy-webpack-plugin
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-22 14:02:54 +00:00
Vicente
7881e47590 fix(translations): fix typo (#211) 2021-11-18 17:59:03 +00:00
Vicente
40397ac33c feat(translations): update spanish translation (#210)
* feat(translations): update spanish translations

* feat(translations): update spanish translations
2021-11-18 17:58:24 +00:00
David Ralph
9da0cb611e chore: release 6.0.1 2021-11-18 15:08:33 +00:00
David Ralph
7c7994c63f fix: greeting date picker, addons sort dropdown 2021-11-18 14:49:20 +00:00
David Ralph
42f89b3fc3 chore: release 6.0, fix firefox bug 2021-11-17 22:10:07 +00:00
David Ralph
da6c1bba63 chore: comment out unfinished things 2021-11-17 21:50:34 +00:00
dependabot[bot]
f96399c9df chore(deps): bump @mui/icons-material from 5.1.0 to 5.1.1 (#207)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.1.1/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:03:20 +00:00
dependabot[bot]
d17948f744 chore(deps): bump @mui/material from 5.1.0 to 5.1.1 (#208)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.1.1/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 09:08:31 +00:00
David Ralph
cf39845892 fix(translations): add missing translations 2021-11-14 16:01:45 +00:00
David Ralph
a3fb4a6522 fix: update experimental tab 2021-11-14 14:44:16 +00:00
David Ralph
f4e88cef2e fix: widget order 2021-11-12 20:20:34 +00:00
David Ralph
e786ad654c fix: background filters, addons ui, photoinformation 2021-11-12 18:34:03 +00:00
David Ralph
a356c8fe61 fix: favourite button, greeting, lightbox etc (thanks @eartharoid) 2021-11-11 22:38:10 +00:00
David Ralph
c627599590 fix: various widget fixes and cleanup etc
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-11-11 20:43:33 +00:00
4a6f93e701 refractor(widgets): Change one-lined if statements 2021-11-11 15:26:25 +01:00
David Ralph
8aca9ae31c fix: settings fix, keybinds fix (WIP), modal ui fix 2021-11-11 10:51:45 +00:00
dependabot[bot]
8966336b93 chore(deps): bump react-modal from 3.14.3 to 3.14.4 (#206)
Bumps [react-modal](https://github.com/reactjs/react-modal) from 3.14.3 to 3.14.4.
- [Release notes](https://github.com/reactjs/react-modal/releases)
- [Changelog](https://github.com/reactjs/react-modal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reactjs/react-modal/compare/v3.14.3...v3.14.4)

---
updated-dependencies:
- dependency-name: react-modal
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-11 09:13:20 +00:00
David Ralph
41750ff0e8 fix: modal ui fixes, search and quote widget fix 2021-11-10 16:20:40 +00:00
dependabot[bot]
62066fef05 chore(deps): bump @mui/icons-material from 5.0.5 to 5.1.0 (#204)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.0.5 to 5.1.0.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.1.0/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-10 15:29:25 +00:00
dependabot[bot]
eb0ffc3cc2 chore(deps): bump @mui/material from 5.0.6 to 5.1.0 (#205)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.6 to 5.1.0.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.1.0/packages/mui-material)

---
updated-dependencies:
- dependency-name: "@mui/material"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-10 15:06:53 +00:00
David Ralph
3bc1409f4f fix: custom video backgrounds 2021-11-06 11:00:25 +00:00
David Ralph
c15f9d20f8 Merge branch 'main' of https://github.com/mue/mue 2021-11-04 13:58:56 +00:00
David Ralph
7f5b826683 fix: various modal things, improve ui, add missing translations etc 2021-11-04 13:58:53 +00:00
dependabot[bot]
cb2c5e3117 chore(deps): bump react-toastify from 8.0.3 to 8.1.0 (#203)
Bumps [react-toastify](https://github.com/fkhadra/react-toastify) from 8.0.3 to 8.1.0.
- [Release notes](https://github.com/fkhadra/react-toastify/releases)
- [Commits](https://github.com/fkhadra/react-toastify/compare/v8.0.3...v8.1.0)

---
updated-dependencies:
- dependency-name: react-toastify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-04 11:45:24 +00:00
David Ralph
2824e7c995 feat: improve custom settings ui 2021-11-02 22:53:17 +00:00
dependabot[bot]
8888cb2144 chore(deps): bump @mui/icons-material from 5.0.4 to 5.0.5 (#200)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.5/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-01 13:28:30 +00:00
dependabot[bot]
1c80d37bda chore(deps): bump @mui/material from 5.0.4 to 5.0.6 (#202)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.4 to 5.0.6.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.6/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-01 13:25:21 +00:00
David Ralph
1b4b37d4b1 feat: improved settings and marketplace ui, better custom background support, fix webpack
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-11-01 13:24:09 +00:00
dependabot[bot]
8820343628 chore(deps): bump @mui/icons-material from 5.0.3 to 5.0.4 (#197)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.0.3 to 5.0.4.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.4/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-15 09:21:55 +01:00
dependabot[bot]
00f30c306e chore(deps): bump @mui/material from 5.0.3 to 5.0.4 (#198)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.3 to 5.0.4.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.4/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-15 09:13:44 +01:00
David Ralph
ed964c1354 feat: add preview mue button to welcome modal 2021-10-13 20:00:40 +01:00
David Ralph
bc8ef0d9f2 feat: new sliders, dropdowns, text inputs, update settings ui, fix language bug
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-10-12 22:18:47 +01:00
dependabot[bot]
14121fb975 chore(deps): bump @mui/material from 5.0.2 to 5.0.3 (#194)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.2 to 5.0.3.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.3/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-11 18:47:13 +01:00
dependabot[bot]
bd8d9fb8e4 chore(deps): bump @mui/icons-material from 5.0.1 to 5.0.3 (#195)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.0.1 to 5.0.3.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.3/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-11 18:46:14 +01:00
dependabot[bot]
2a6505799b chore(deps-dev): bump eslint from 7.32.0 to 8.0.0 (#196)
Bumps [eslint](https://github.com/eslint/eslint) from 7.32.0 to 8.0.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.32.0...v8.0.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-11 18:46:02 +01:00
David Ralph
33ef113f6d feat: add show navbar on hover setting back 2021-09-30 19:35:02 +01:00
David Ralph
a5dc2c98a0 fix: various settings tabs 2021-09-30 17:48:38 +01:00
David Ralph
c9854d5362 fix: settings tab bugs 2021-09-30 17:40:36 +01:00
dependabot[bot]
9a6119e861 chore(deps): bump @mui/material from 5.0.1 to 5.0.2 (#193)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.2/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-30 09:11:40 +01:00
David Ralph
9ac410d989 fix: background 2021-09-29 19:19:03 +01:00
David Ralph
80a7b6a56c refactor: cleanup 2021-09-28 23:04:04 +01:00
David Ralph
883f025fbb feat(translations): placeholder support, fix weather text 2021-09-27 18:37:16 +01:00
David Ralph
d1b23dc07b Merge branch 'main' of https://github.com/mue/mue 2021-09-27 17:58:34 +01:00
David Ralph
364fb4b0d7 perf: reduce custom background lag on large images, improve h3 style 2021-09-27 17:58:28 +01:00
dependabot[bot]
cef4026b97 chore(deps-dev): bump @eartharoid/deep-merge from 0.0.1 to 0.0.2 (#191)
Bumps [@eartharoid/deep-merge](https://github.com/eartharoid/deep-merge) from 0.0.1 to 0.0.2.
- [Release notes](https://github.com/eartharoid/deep-merge/releases)
- [Commits](https://github.com/eartharoid/deep-merge/compare/v0.0.1...v0.0.2)

---
updated-dependencies:
- dependency-name: "@eartharoid/deep-merge"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 10:17:44 +01:00
dependabot[bot]
5702c45187 chore(deps): bump react-toastify from 8.0.2 to 8.0.3 (#190)
Bumps [react-toastify](https://github.com/fkhadra/react-toastify) from 8.0.2 to 8.0.3.
- [Release notes](https://github.com/fkhadra/react-toastify/releases)
- [Commits](https://github.com/fkhadra/react-toastify/compare/v8.0.2...v8.0.3)

---
updated-dependencies:
- dependency-name: react-toastify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 10:17:34 +01:00
David Ralph
e7dc9f04d1 feat: new settings tab header, update keybinds and widget order ui
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-09-26 18:03:32 +01:00
David Ralph
bc9f68e0c1 fix: first load not working 2021-09-23 15:56:41 +01:00
dependabot[bot]
d2d7083d3d chore(deps): bump @mui/icons-material from 5.0.0 to 5.0.1 (#188)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.1/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-23 09:23:09 +01:00
dependabot[bot]
43ad2c31f8 chore(deps): bump @mui/material from 5.0.0 to 5.0.1 (#189)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.1/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-23 09:14:58 +01:00
David Ralph
fa485c35c3 Merge branch 'main' of https://github.com/mue/mue 2021-09-20 21:23:42 +01:00
David Ralph
231966546f feat: improve settings tabs and sidebar 2021-09-20 21:23:34 +01:00
BigTear
95c6216b0c feat(translations): Update Chinese translation (#187)
Add new items translation and fix some localized words
2021-09-20 10:16:03 +01:00
David Ralph
52745a267f feat: background interval for custom 2021-09-19 20:10:22 +01:00
David Ralph
cbbc911cc3 feat: create tab local quote pack/photo pack import (WIP) 2021-09-19 19:50:55 +01:00
David Ralph
e78611cb05 chore: username change 2021-09-19 16:11:19 +01:00
David Ralph
2049359a96 feat(translations): lazy load 2021-09-19 11:42:37 +01:00
David Ralph
96f26042d2 fix: toast duration setting, create quote pack section 2021-09-19 11:22:53 +01:00
David Ralph
f7ba4c0ed9 fix: reset button 2021-09-18 21:03:44 +01:00
David Ralph
4ad9d4810e feat: add create quote pack (WIP) 2021-09-18 20:51:00 +01:00
David Ralph
94e9c5d65e feat: random colour/gradient background, fix toasts 2021-09-17 19:40:22 +01:00
David Ralph
e3edc3d89e fix: quote widget zoom, add missing translations 2021-09-17 14:38:13 +01:00
David Ralph
8b4d1d18f1 feat: multiple custom quotes, fix background settings 2021-09-17 14:36:47 +01:00
David Ralph
0fd50e949c refactor(translations): cleanup and fix import settings text 2021-09-17 14:13:31 +01:00
dependabot[bot]
42b4aa1d62 chore(deps): bump @mui/icons-material from 5.0.0-rc.0 to 5.0.0 (#185)
Bumps [@mui/icons-material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-icons-material) from 5.0.0-rc.0 to 5.0.0.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0/packages/mui-icons-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-17 13:41:14 +01:00
dependabot[bot]
39fbd9336f chore(deps): bump @mui/material from 5.0.0-rc.0 to 5.0.0 (#186)
Bumps [@mui/material](https://github.com/mui-org/material-ui/tree/HEAD/packages/mui-material) from 5.0.0-rc.0 to 5.0.0.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0/packages/mui-material)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-17 13:40:18 +01:00
David Ralph
93ba1cd9bf feat: random messages 2021-09-17 13:39:55 +01:00
David Ralph
a946d07c9c fix(translations): no longer crashes on welcome or file upload 2021-09-16 12:47:53 +01:00
David Ralph
3f3ee2dd82 fix: no animations setting not working on some things 2021-09-15 23:27:26 +01:00
David Ralph
4953298b9f fix(translations): add missing translated text 2021-09-15 22:39:29 +01:00
David Ralph
f931abf3fb feat: quick links custom icon and text only options, message widget, fix error 2021-09-15 22:38:24 +01:00
David Ralph
b97b925978 fix: translation and colour picker bugs 2021-09-14 20:15:15 +01:00
David Ralph
1c65447c55 fix: optimise background settings, add missing translation 2021-09-11 13:33:26 +01:00
David Ralph
66a3629b51 fix: photo information undefined text 2021-09-10 19:33:56 +01:00
David Ralph
7e3fc8085e refactor: cleanup background and keybinds, add translation for add custom background 2021-09-10 19:25:09 +01:00
David Ralph
6cd934acdc chore: switch from material-ui to mui 2021-09-10 19:00:45 +01:00
David Ralph
a7e7743028 feat: better remove background button
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-09-10 18:52:51 +01:00
dependabot[bot]
27668172ec chore(deps): bump react-hot-keys from 2.7.0 to 2.7.1 (#184)
Bumps [react-hot-keys](https://github.com/jaywcjlove/react-hotkeys) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/jaywcjlove/react-hotkeys/releases)
- [Commits](https://github.com/jaywcjlove/react-hotkeys/compare/v2.7.0...v2.7.1)

---
updated-dependencies:
- dependency-name: react-hot-keys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-10 17:08:06 +01:00
David Ralph
b0eeff1bf8 feat(translations): new translation system 2021-09-10 16:38:53 +01:00
David Ralph
941c168486 fix: about tab images 2021-09-09 15:18:59 +01:00
xXFreeFunXx
aceba529ba Update german translation (#183)
The Github link on the setup page needs to be adjusted.
More details in the Discord.
2021-09-09 14:48:20 +01:00
David Ralph
2d7db1558d fix: custom background settings 2021-09-08 21:30:03 +01:00
David Ralph
102078744e feat: multiple custom backgrounds (WIP) 2021-09-08 20:58:07 +01:00
David Ralph
6bb461b8a2 fix: use constants, improve photoinformation ui 2021-09-08 11:05:34 +01:00
David Ralph
2984e162cc feat: check for addon updates, add attribution to photo location map 2021-09-07 20:42:56 +01:00
David Ralph
b86e73f2fc feat: clicking on location map now opens in openstreetmap 2021-09-07 19:10:04 +01:00
David Ralph
22ef3ad139 feat: allow duckduckgo proxy on map 2021-09-06 19:55:04 +01:00
David Ralph
26ae76accc feat: map for background location
Co-authored-by: Alex Sparkes <turbomarshmello@gmail.com>
2021-09-06 19:35:31 +01:00
dependabot[bot]
da2c70f897 chore(deps): bump react-hot-keys from 2.6.2 to 2.7.0 (#182)
Bumps [react-hot-keys](https://github.com/jaywcjlove/react-hotkeys) from 2.6.2 to 2.7.0.
- [Release notes](https://github.com/jaywcjlove/react-hotkeys/releases)
- [Commits](https://github.com/jaywcjlove/react-hotkeys/compare/v2.6.2...v2.7.0)

---
updated-dependencies:
- dependency-name: react-hot-keys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-06 09:29:06 +01:00
David Ralph
c63a13a4c2 feat: update addons, sideload error modal, show reminder for preset settings 2021-09-05 20:52:26 +01:00
David Ralph
5baf067ff1 refactor: css cleanup 2021-09-05 14:19:19 +01:00
David Ralph
7f71321e67 feat: better keybinds ui, fix notes, improve search dropdown etc 2021-09-04 12:20:58 +01:00
David Ralph
3cc8031c84 fix: search dropdown selected hover effects 2021-09-03 12:34:34 +01:00
David Ralph
b5c3ca201e fix: demo 2021-09-03 12:31:12 +01:00
David Ralph
604834c4d7 fix: notes position, navbar hot reload 2021-09-02 15:27:00 +01:00
David Ralph
d368270553 Merge branch 'main' of https://github.com/mue/mue 2021-09-02 13:55:51 +01:00
David Ralph
8cbe3b909b fix: responsive welcome modal on smaller resolutions 2021-09-02 13:55:42 +01:00
dependabot[bot]
b0399b6314 chore(deps): bump react-toastify from 8.0.0 to 8.0.2 (#181)
Bumps [react-toastify](https://github.com/fkhadra/react-toastify) from 8.0.0 to 8.0.2.
- [Release notes](https://github.com/fkhadra/react-toastify/releases)
- [Commits](https://github.com/fkhadra/react-toastify/compare/v8.0.0...v8.0.2)

---
updated-dependencies:
- dependency-name: react-toastify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-01 11:12:36 +01:00
David Ralph
a00037747b fix: modal navbar tab not working 2021-08-29 18:02:25 +01:00
David Ralph
5af8dfac11 style: change about and search to use recent code 2021-08-29 11:02:55 +01:00
David Ralph
21f8b63b80 fix: search dropdown select, cleanup functional components 2021-08-28 19:14:22 +01:00
David Ralph
67ab7c8674 feat: new refresh button options 2021-08-28 18:56:59 +01:00
David Ralph
6b23e56bd6 chore: update readme 2021-08-28 18:04:00 +01:00
David Ralph
8b25175b9c fix: search dropdown css 2021-08-28 16:59:09 +01:00
David Ralph
06038a201e refactor: webpack alias imports 2021-08-28 15:34:12 +01:00
David Ralph
4db47d9fec refactor: cleanup, use createRef etc 2021-08-27 23:08:32 +01:00
David Ralph
fcff3e44a6 refactor: codacy and some style changes etc 2021-08-27 19:42:40 +01:00
David Ralph
abe70617d8 fix: console error and codacy 2021-08-27 18:27:11 +01:00
dependabot[bot]
feb7e1d93c chore(deps): bump react-toastify from 7.0.4 to 8.0.0 (#179)
Bumps [react-toastify](https://github.com/fkhadra/react-toastify) from 7.0.4 to 8.0.0.
- [Release notes](https://github.com/fkhadra/react-toastify/releases)
- [Commits](https://github.com/fkhadra/react-toastify/compare/v7.0.4...v8.0.0)

---
updated-dependencies:
- dependency-name: react-toastify
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-27 18:12:40 +01:00
David Ralph
de4ffbcb0a fix: add hover effect to search dropdown, custom support etc 2021-08-26 19:48:20 +01:00
David Ralph
e5f8cf3e66 feat: add #143 2021-08-25 13:55:20 +01:00
David Ralph
10f27d24b0 feat: new keybinds, language support etc 2021-08-25 13:28:41 +01:00
David Ralph
4a4e4604e8 feat: add no animations setting back, text shadow/border option 2021-08-25 12:01:48 +01:00
David Ralph
3ed2171a34 feat: keybinds (WIP) 2021-08-25 11:25:46 +01:00
dependabot[bot]
d33a879281 chore(deps): bump @material-ui/core from 5.0.0-beta.4 to 5.0.0-beta.5 (#178)
Bumps [@material-ui/core](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui) from 5.0.0-beta.4 to 5.0.0-beta.5.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui-org/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v5.0.0-beta.5/packages/material-ui)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-09 10:55:36 +01:00
David Ralph
eefc594ec1 fix: improvements to feedback modal 2021-08-05 17:25:46 +01:00
145 changed files with 6097 additions and 3510 deletions

View File

@@ -12,7 +12,7 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
<br>
## Table of contents
* [Screenshot](#screenshot)
* [Screenshots](#screenshot)
* [Features](#features)
* [Planned Features](#planned-features)
* [Installation](#installation)
@@ -37,13 +37,13 @@ Mue is a fast, open and free-to-use browser extension that gives a new, fresh an
* 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 searchbar, weather, quick links, clock, date, quote, greeting
* 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://github.com/mue/mue/projects)
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)*
@@ -74,7 +74,7 @@ Please see the [documentation](https://docs.muetab.com/translations).
## Credits
### Developers
[David Ralph](https://github.com/davidjcralph) - Lead development, photographer <br/>
[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/>

View File

@@ -2,5 +2,5 @@ module.exports = {
presets: ['@babel/preset-env', ['@babel/preset-react', {
runtime: 'automatic'
}]],
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
plugins: ['@babel/plugin-proposal-class-properties', '@babel/transform-runtime', '@babel/plugin-transform-react-inline-elements', 'babel-plugin-transform-react-class-to-function', '@babel/plugin-transform-react-constant-elements']
};

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
"default_locale": "en",
"name": "__MSG_name__",
"description": "__MSG_description__",
"version": "5.2.0",
"version": "6.0.3",
"homepage_url": "https://muetab.com",
"browser_action": {
"default_icon": "icons/128x128.png"
@@ -17,5 +17,9 @@
"48": "icons/48x48.png",
"128": "icons/128x128.png"
},
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'"
"content_security_policy": "script-src 'self' https://api.bing.com https://www.google.com; object-src 'self'",
"background": {
"persistent": false,
"scripts": [ "background-chrome.js" ]
}
}

View File

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

View File

@@ -9,53 +9,55 @@
"homepage": "https://muetab.com",
"bugs": "https://github.com/mue/mue/issues/new?assignees=&labels=bug&template=bug-report.md&title=%5BBUG%5D",
"license": "BSD-3-Clause",
"version": "5.2.0",
"version": "6.0.3",
"dependencies": {
"@emotion/react": "^11.4.0",
"@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",
"@material-ui/core": "5.0.0-beta.2",
"@material-ui/icons": "5.0.0-beta.1",
"date-fns-tz": "^1.1.6",
"@mui/icons-material": "5.2.0",
"@mui/material": "5.2.1",
"react": "17.0.2",
"react-clock": "3.0.0",
"react-color-gradient-picker": "0.1.2",
"react-dom": "17.0.2",
"react-modal": "3.14.3",
"react-hot-keys": "2.7.1",
"react-modal": "3.14.4",
"react-sortable-hoc": "2.0.0",
"react-toastify": "7.0.4",
"react-toastify": "8.1.0",
"weather-icons-react": "1.2.0"
},
"devDependencies": {
"@babel/core": "^7.14.8",
"@babel/eslint-parser": "^7.14.9",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-transform-react-constant-elements": "^7.13.15",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.9",
"@babel/preset-react": "^7.14.5",
"@eartharoid/deep-merge": "^0.0.1",
"babel-loader": "^8.2.2",
"@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",
"@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.0.1",
"css-loader": "^6.2.0",
"date-fns": "^2.23.0",
"eslint": "^7.32.0",
"copy-webpack-plugin": "9.1.0",
"css-loader": "^6.5.1",
"eslint": "^8.2.0",
"eslint-config-react-app": "^6.0.0",
"html-webpack-plugin": "^5.3.2",
"mini-css-extract-plugin": "^2.1.0",
"sass": "^1.37.0",
"sass-loader": "^12.1.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.47.1",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.0.0-rc.0"
"webpack": "^5.63.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0"
},
"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",
"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": {

View File

@@ -1,3 +1,4 @@
// tl;dr this function merges the translation file with the english file in order to add untranslated strings
const fs = require('fs');
const merge = require('@eartharoid/deep-merge');
@@ -8,5 +9,6 @@ fs.readdirSync('../src/translations').forEach((file) => {
const newdata = merge(require('../src/translations/en_GB.json'), require('../src/translations/' + file));
fs.writeFileSync('../src/translations/' + file, JSON.stringify(newdata, null, 2));
// add new line
fs.appendFileSync('../src/translations/' + file, '\n');
});

View File

@@ -1,33 +1,34 @@
import React from 'react';
import Background from './components/widgets/background/Background';
import Widgets from './components/widgets/Widgets';
import Modals from './components/modals/Modals';
import EventBus from './modules/helpers/eventbus';
import SettingsFunctions from './modules/helpers/settings';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { ToastContainer } from 'react-toastify';
export default class App extends React.PureComponent {
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')) {
SettingsFunctions.moveSettings();
moveSettings();
window.location.reload();
}
SettingsFunctions.loadSettings();
loadSettings();
EventBus.on('refresh', (data) => {
if (data === 'other') {
SettingsFunctions.loadSettings(true);
loadSettings(true);
}
});
window.stats.tabLoad();
variables.stats.tabLoad();
}
render() {

View File

@@ -1,18 +1,21 @@
import React from 'react';
import { PureComponent } from 'react';
import EventBus from 'modules/helpers/eventbus';
import './autocomplete.scss';
export default class Autocomplete extends React.PureComponent {
export default class Autocomplete extends PureComponent {
constructor(props) {
super(props);
this.state = {
filtered: [],
input: ''
input: '',
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
};
}
onChange = (e) => {
if (localStorage.getItem('autocomplete') !== 'true') {
if (this.state.autocompleteDisabled) {
return this.setState({
input: e.target.value
});
@@ -32,9 +35,28 @@ export default class Autocomplete extends React.PureComponent {
input: e.target.innerText
});
this.props.onClick(e);
this.props.onClick({
preventDefault: () => e.preventDefault(),
target: {
value: e.target.innerText
}
});
};
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'search') {
this.setState({
autocompleteDisabled: (localStorage.getItem('autocomplete') !== 'true')
});
}
});
}
componentWillUnount() {
EventBus.off('refresh');
}
render() {
let autocomplete = null;
@@ -42,26 +64,18 @@ export default class Autocomplete extends React.PureComponent {
if (this.state.filtered.length > 0 && this.state.input.length > 0) {
autocomplete = (
<ul className='suggestions'>
{this.state.filtered.map((suggestion) => {
return (
<li key={suggestion} onClick={this.onClick}>
{suggestion}
</li>
);
})}
{this.state.filtered.map((suggestion) => (
<li key={suggestion} onClick={this.onClick}>
{suggestion}
</li>
))}
</ul>
);
}
return (
<>
<input
type='text'
onChange={this.onChange}
value={this.state.input}
placeholder={this.props.placeholder || ''}
autoComplete='off'
id={this.props.id || ''} />
<input type='text' onChange={this.onChange} value={this.state.input} placeholder={this.props.placeholder || ''} autoComplete='off' id={this.props.id || ''} />
{autocomplete}
</>
);

View File

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

View File

@@ -0,0 +1,15 @@
import variables from 'modules/variables';
import './preview.scss';
export default function Preview(props) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
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>
);
}

View File

@@ -0,0 +1,17 @@
.preview-mode {
position: absolute;
bottom: 20px;
right: 20px;
padding: 15px;
color: var(--modal-text);
background: var(--background);
max-width: 300px;
border-radius: .7em;
z-index: 999;
text-align: left;
cursor: default;
h1 {
font-size: 1rem;
}
}

View File

@@ -1,10 +1,10 @@
import './tooltip.scss';
export default function Tooltip(props) {
export default function Tooltip({ children, title }) {
return (
<div className='tooltip'>
{props.children}
<span className='tooltipTitle'>{props.title}</span>
{children}
<span className='tooltipTitle'>{title}</span>
</div>
);
}

View File

@@ -4,9 +4,9 @@
display: inline-block;
.tooltipTitle {
width: 60px;
min-width: 60px;
background-color: rgba(255, 255, 255, 0.89);
color: #000000;
color: #000;
text-align: center;
font-size: 0.6rem;
border-radius: 6px;
@@ -20,7 +20,7 @@
cursor: initial;
user-select: none;
opacity: 0;
transition: opacity 0.8s;
transition: 0.2s;
}
&:hover {
@@ -33,5 +33,5 @@
.dark .tooltipTitle {
background-color: rgba(0, 0, 0, 0.79);
color: #ffffff;
color: #fff;
}

View File

@@ -1,19 +1,18 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { ErrorOutline } from '@mui/icons-material';
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
export default class ErrorBoundary extends React.PureComponent {
export default class ErrorBoundary extends PureComponent {
constructor(props) {
super(props);
this.state = {
error: false
};
this.language = window.language.modals.main.error_boundary;
}
static getDerivedStateFromError(error) {
console.log(error);
window.stats.postEvent('modal', 'Error occurred');
variables.stats.postEvent('modal', 'Error occurred');
return {
error: true
};
@@ -24,10 +23,10 @@ export default class ErrorBoundary extends React.PureComponent {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<ErrorOutlineIcon/>
<h1>{this.language.title}</h1>
<p>{this.language.message}</p>
<button className='refresh' onClick={() => window.location.reload()}>{this.language.refresh}</button>
<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>
</div>
);

View File

@@ -1,26 +1,28 @@
import React from 'react';
import EventBus from '../../modules/helpers/eventbus';
import variables from 'modules/variables';
import { PureComponent, Suspense, lazy } from 'react';
import Modal from 'react-modal';
//import Hotkeys from 'react-hot-keys';
import Main from './main/Main';
import Navbar from '../widgets/navbar/Navbar';
import Preview from '../helpers/preview/Preview';
import Modal from 'react-modal';
import EventBus from 'modules/helpers/eventbus';
// These modals are lazy loaded as the user won't use them every time they open a tab
// We used to lazy load the main modal, but doing so broke the main modal open animation on first click
const Welcome = React.lazy(() => import('./welcome/Welcome'));
const Feedback = React.lazy(() => import('./feedback/Feedback'));
// Welcome modal is lazy loaded as the user won't use it every time they open a tab
// We used to lazy load the main and feedback modals, but doing so broke the modal open animation on first click
const Welcome = lazy(() => import('./welcome/Welcome'));
const renderLoader = () => <></>;
export default class Modals extends React.PureComponent {
export default class Modals extends PureComponent {
constructor() {
super();
this.state = {
mainModal: false,
updateModal: false,
welcomeModal: false,
feedbackModal: false
feedbackModal: false,
preview: false
};
}
@@ -29,7 +31,15 @@ export default class Modals extends React.PureComponent {
this.setState({
welcomeModal: true
});
window.stats.postEvent('modal', 'Opened welcome');
variables.stats.postEvent('modal', 'Opened welcome');
}
if (window.location.search === '?nointro=true') {
if (localStorage.getItem('showWelcome') === 'true') {
localStorage.setItem('showWelcome', false);
EventBus.dispatch('refresh', 'widgets');
EventBus.dispatch('refresh', 'backgroundwelcome');
}
}
// hide refresh reminder once the user has refreshed the page
@@ -41,35 +51,45 @@ export default class Modals extends React.PureComponent {
this.setState({
welcomeModal: false
});
EventBus.dispatch('refresh', 'widgetsWelcomeDone');
EventBus.dispatch('refresh', 'widgets');
EventBus.dispatch('refresh', 'backgroundwelcome');
}
previewWelcome() {
localStorage.setItem('showWelcome', false);
localStorage.setItem('welcomePreview', true);
this.setState({
welcomeModal: false,
preview: true
});
EventBus.dispatch('refresh', 'widgetsWelcome');
}
toggleModal(type, action) {
this.setState({
[type]: action
});
if (action !== false) {
window.stats.postEvent('modal', `Opened ${type.replace('Modal', '')}`);
variables.stats.postEvent('modal', `Opened ${type.replace('Modal', '')}`);
}
}
render() {
return (
<>
<Navbar openModal={(modal) => this.toggleModal(modal, true)}/>
{this.state.welcomeModal === false ? <Navbar openModal={(modal) => this.toggleModal(modal, true)}/> : null}
<Modal closeTimeoutMS={300} id='modal' onRequestClose={() => this.toggleModal('mainModal', false)} isOpen={this.state.mainModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
<Main modalClose={() => this.toggleModal('mainModal', false)}/>
</Modal>
<React.Suspense fallback={renderLoader()}>
<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()}/>
<Welcome modalClose={() => this.closeWelcome()} modalSkip={() => this.previewWelcome()}/>
</Modal>
<Modal closeTimeoutMS={300} onRequestClose={() => this.toggleModal('feedbackModal', false)} isOpen={this.state.feedbackModal} className='Modal mainModal' overlayClassName='Overlay' ariaHideApp={false}>
<Feedback modalClose={() => this.toggleModal('feedbackModal', false)}/>
</Modal>
</React.Suspense>
</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*/}
</>
);
}

View File

@@ -1,93 +0,0 @@
import React from 'react';
import './feedback.scss';
export default class FeedbackModal extends React.PureComponent {
constructor() {
super();
this.state = {
questionone: 5,
questionthree: 5,
questiontwoerror: '',
questionfourerror: '',
formsubmit: ''
};
this.language = window.language.modals.feedback;
}
async submitForm () {
let questiontwoerror, questionfourerror;
if (document.getElementById('questiontwo').value.length <= 0) {
questiontwoerror = this.language.not_filled;
}
if (document.getElementById('questionfour').value.length <= 0) {
questionfourerror = this.language.not_filled;
}
if (questiontwoerror || questionfourerror) {
this.setState({
questiontwoerror: questiontwoerror,
questionfourerror: questionfourerror
});
} else {
this.setState({
questiontwoerror: '',
questionfourerror: ''
});
await fetch(window.constants.FEEDBACK_FORM, {
'method': 'POST'
});
this.setState({
formsubmit: this.language.success
});
setTimeout(() => {
this.props.modalClose();
}, 3000);
}
}
render() {
return (
<div className='feedback'>
<h1>{this.language.title}</h1>
<span className='closeModal' onClick={this.props.modalClose}>&times;</span>
<>
<input type='hidden' name='version' value={window.constants.VERSION} />
<>
<label>{this.language.question_one}</label>
<br/><br/>
<label className='values'>0</label>
<input className='range' type='range' min='0' max='10' name='question1' value={this.state.questionone} onChange={(e) => this.setState({ questionone: e.target.value })}/>
<label className='values'>10 ({this.state.questionone})</label>
</>
<br/><br/>
<>
<label>{this.language.question_two}</label>
<textarea name='question2' id='questiontwo'/>
<p className='feedbackerror'>{this.state.questiontwoerror}</p>
</>
<>
<label>{this.language.question_three}</label>
<br/><br/>
<label className='values'>0</label>
<input className='range' type='range' min='0' max='10' name='question3' value={this.state.questionthree} onChange={(e) => this.setState({ questionthree: e.target.value })}/>
<label className='values'>10 ({this.state.questionthree})</label>
</>
<br/><br/>
<>
<label>{this.language.question_four}</label>
<textarea name='question4' id='questionfour'/>
<p className='feedbackerror'>{this.state.questionfourerror}</p>
</>
{this.state.formsubmit}
<button onClick={() => this.submitForm()}>{this.language.submit}</button>
</>
</div>
);
}
}

View File

@@ -1,63 +0,0 @@
@import '../main/scss/index.scss';
#feedbackmodal {
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 400px;
height: 100px;
}
.feedback {
width: 400px;
padding: 5px;
h1,
.closeModal {
font-size: 2.5em;
}
span {
font-size: 6em;
}
button {
width: 50%;
border-radius: 48px;
outline: none;
border: none;
padding: 15px 20px;
font-size: 1.5em;
background: linear-gradient(90deg, #ffb032 0%, #dd3b67 100%);
color: #fff;
text-transform: uppercase;
cursor: pointer;
}
input[type=text] {
width: 100%;
font-size: 1em;
}
input[type=range] {
margin-left: 20px;
margin-right: 20px;
vertical-align: middle;
}
label.values {
font-weight: 700;
}
textarea {
width: 80%;
background-color: var(--sidebar);
}
.feedbackerror {
color: red;
}
}

View File

@@ -1,20 +1,21 @@
import React from 'react';
import variables from 'modules/variables';
import { Suspense, lazy } from 'react';
import Tabs from './tabs/backend/Tabs';
import './scss/index.scss';
// Lazy load all the tabs instead of the modal itself
const Settings = React.lazy(() => import('./tabs/Settings'));
const Addons = React.lazy(() => import('./tabs/Addons'));
const Marketplace = React.lazy(() => import('./tabs/Marketplace'));
const Settings = lazy(() => import('./tabs/Settings'));
const Addons = lazy(() => import('./tabs/Addons'));
const Marketplace = lazy(() => import('./tabs/Marketplace'));
const renderLoader = () => (
<Tabs>
<div label={window.language.modals.main.loading}>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.loading')}>
<div className='emptyitems'>
<div className='emptyMessage'>
<h1>{window.language.modals.main.loading}</h1>
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.loading')}</h1>
</div>
</div>
</div>
@@ -22,29 +23,34 @@ const renderLoader = () => (
</Tabs>
);
export default function MainModal(props) {
const language = window.language.modals.main.navbar;
export default function MainModal({ modalClose }) {
const display = (localStorage.getItem('showReminder') === 'true') ? 'block' : 'none';
return (
<>
<span className='closeModal' onClick={props.modalClose}>&times;</span>
<span className='closeModal' onClick={modalClose}>&times;</span>
<Tabs navbar={true}>
<div label={language.settings} name='settings'>
<React.Suspense fallback={renderLoader()}>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.settings')} name='settings'>
<Suspense fallback={renderLoader()}>
<Settings/>
</React.Suspense>
</Suspense>
</div>
<div label={language.addons} name='addons'>
<React.Suspense fallback={renderLoader()}>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.addons')} name='addons'>
<Suspense fallback={renderLoader()}>
<Addons/>
</React.Suspense>
</Suspense>
</div>
<div label={language.marketplace} name='marketplace'>
<React.Suspense fallback={renderLoader()}>
<div label={variables.language.getMessage(variables.languagecode, 'modals.main.navbar.marketplace')} name='marketplace'>
<Suspense fallback={renderLoader()}>
<Marketplace/>
</React.Suspense>
</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>
</>
);
}

View File

@@ -1,21 +1,33 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent, Fragment } from 'react';
import { toast } from 'react-toastify';
import { ArrowBack } from '@mui/icons-material';
import Modal from 'react-modal';
import { install, uninstall } from 'modules/helpers/marketplace';
import Lightbox from './Lightbox';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
export default class Item extends React.PureComponent {
constructor() {
super();
export default class Item extends PureComponent {
constructor(props) {
super(props);
this.state = {
showLightbox: false
showLightbox: false,
showUpdateButton: (this.props.addonInstalled === true && this.props.addonInstalledVersion !== this.props.data.version)
};
}
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
});
}
render() {
const language = window.language.modals.main.marketplace.product;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
if (!this.props.data.display_name) {
return null;
@@ -26,35 +38,48 @@ export default class Item extends React.PureComponent {
warningHTML = (
<div className='productInformation'>
<ul>
<li className='header'>{language.quote_warning.title}</li>
<li id='updated'>{language.quote_warning.description}</li>
<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 = window.constants.DDG_IMAGE_PROXY + this.props.data.icon;
let iconsrc = variables.constants.DDG_IMAGE_PROXY + this.props.data.icon;
if (!this.props.data.icon) {
iconsrc = null;
}
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')}
</button>
</Fragment>
);
}
return (
<div id='item'>
<br/>
<ArrowBackIcon className='backArrow' onClick={this.props.toggleFunction}/>
<ArrowBack className='backArrow' onClick={this.props.toggleFunction}/>
<br/>
<h1>{this.props.data.display_name}</h1>
{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'>{language.version}</li>
<li>{this.props.data.version}</li>
<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'>{language.author}</li>
<li className='header'>{getMessage('modals.main.marketplace.product.author')}</li>
<li>{this.props.data.author}</li>
</ul>
</div>
@@ -65,7 +90,7 @@ export default class Item extends React.PureComponent {
<br/><br/>
</div>
<div className='informationContainer'>
<h1 className='overview'>{language.overview}</h1>
<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}>

View File

@@ -1,9 +1,11 @@
export default function Items(props) {
import variables from 'modules/variables';
export default function Items({ items, toggleFunction }) {
return (
<div className='items'>
{props.items.map((item) => (
<div className='item' onClick={() => props.toggleFunction(item.name)} key={item.name}>
<img alt='icon' draggable='false' src={window.constants.DDG_IMAGE_PROXY + item.icon_url} />
{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>

View File

@@ -1,10 +1,12 @@
export default function Lightbox(props) {
window.stats.postEvent('modal', 'Opened lightbox');
import variables from 'modules/variables';
export default function Lightbox({ modalClose, img }) {
variables.stats.postEvent('modal', 'Opened lightbox');
return (
<>
<span className='closeModal' onClick={props.modalClose}>&times;</span>
<img src={props.img} className='lightboximg' draggable={false} alt='Item screenshot'/>
<span className='closeModal' onClick={modalClose}>&times;</span>
<img src={img} className='lightboximg' draggable={false} alt='Item screenshot'/>
</>
);
}

View File

@@ -0,0 +1,20 @@
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);
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>
</>
);
}

View File

@@ -1,15 +1,17 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { LocalMall } from '@mui/icons-material';
import { toast } from 'react-toastify';
import LocalMallIcon from '@material-ui/icons/LocalMall';
import Item from '../Item';
import Items from '../Items';
import Dropdown from '../../settings/Dropdown';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { uninstall, urlParser } from 'modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Added extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class Added extends React.PureComponent {
constructor() {
super();
this.state = {
@@ -18,9 +20,8 @@ export default class Added extends React.PureComponent {
button: ''
};
this.buttons = {
uninstall: <button className='removeFromMue' onClick={() => this.uninstall()}>{window.language.modals.main.marketplace.product.buttons.remove}</button>,
uninstall: <button className='removeFromMue' onClick={() => this.uninstall()}>{this.getMessage('modals.main.marketplace.product.buttons.remove')}</button>,
};
this.language = window.language.modals.main.addons;
}
toggle(type, data) {
@@ -34,7 +35,7 @@ export default class Added extends React.PureComponent {
name: data,
display_name: info.name,
author: info.author,
description: MarketplaceFunctions.urlParser(info.description.replace(/\n/g, '<br>')),
description: urlParser(info.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.version,
icon: info.screenshot_url,
@@ -42,7 +43,7 @@ export default class Added extends React.PureComponent {
},
button: this.buttons.uninstall
});
window.stats.postEvent('marketplace', 'Item viewed');
variables.stats.postEvent('marketplace', 'Item viewed');
} else {
this.setState({
item: {}
@@ -51,16 +52,16 @@ export default class Added extends React.PureComponent {
}
uninstall() {
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
uninstall(this.state.item.type, this.state.item.display_name);
toast(window.language.toasts.uninstalled);
toast(this.getMessage('toasts.uninstalled'));
this.setState({
button: '',
installed: JSON.parse(localStorage.getItem('installed'))
});
window.stats.postEvent('marketplace', 'Uninstall');
variables.stats.postEvent('marketplace', 'Uninstall');
}
sortAddons(value, sendEvent) {
@@ -87,7 +88,25 @@ export default class Added extends React.PureComponent {
});
if (sendEvent) {
window.stats.postEvent('marketplace', 'Sort');
variables.stats.postEvent('marketplace', 'Sort');
}
}
updateCheck() {
let updates = 0;
this.state.installed.forEach(async (item) => {
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
}));
} else {
toast(this.getMessage('modals.main.addons.no_updates'));
}
}
@@ -100,9 +119,9 @@ export default class Added extends React.PureComponent {
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<LocalMallIcon/>
<h1>{this.language.empty.title}</h1>
<p className='description'>{this.language.empty.description}</p>
<LocalMall/>
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
<p className='description'>{this.getMessage('modals.main.addons.empty.description')}</p>
</div>
</div>
);
@@ -114,12 +133,13 @@ export default class Added extends React.PureComponent {
return (
<>
<Dropdown label={this.language.sort.title} name='sortAddons' onChange={(value) => this.sortAddons(value)}>
<option value='newest'>{this.language.sort.newest}</option>
<option value='oldest'>{this.language.sort.oldest}</option>
<option value='a-z'>{this.language.sort.a_z}</option>
<option value='z-a'>{this.language.sort.z_a}</option>
<Dropdown 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)} />
</>

View File

@@ -0,0 +1,336 @@
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 { TextField } from '@mui/material';
import { toast } from 'react-toastify';
import { saveFile } from 'modules/helpers/settings/modals';
import FileUpload from '../../settings/FileUpload';
import Dropdown from '../../settings/Dropdown';
import '../../../welcome/welcome.scss';
export default class Create extends PureComponent {
constructor() {
super();
this.state = {
currentTab: 1,
addonMetadata: {
name: '',
description: '',
type: '',
version: '',
author: '',
icon_url: '',
screenshot_url: ''
},
addonData: '',
settingsClasses: {
current: 'toggle lightTheme',
json: 'toggle lightTheme'
}
};
}
changeTab(tab, type) {
if (type) {
return this.setState({
currentTab: tab,
addonMetadata: {
type: type
}
});
}
this.setState({
currentTab: tab
});
}
importSettings(input) {
const data = input || localStorage;
let settings = {};
Object.keys(data).forEach((key) => {
if (key === 'statsData' || key === 'firstRun' || key === 'showWelcome' || key === 'language' || key === 'installed' || key === 'stats' || key === 'backup_settings' || key === 'showReminder'
|| key === 'experimental' || key === 'debugtimeout' || key === 'quotelanguage') {
return;
}
settings[key] = localStorage.getItem(key);
});
this.setState({
addonData: settings,
settingsClasses: {
current: 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 || '',
}
});
}
importQuotes() {
this.setState({
addonData: JSON.parse(localStorage.getItem('customQuote')) || []
});
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
}
importPhotos() {
let data = [];
try {
const current = JSON.parse(localStorage.getItem('customBackground')) || [];
data = current.map((item) => {
return {
photographer: '???',
location: '???',
url: {
default: item
}
}
});
toast(variables.language.getMessage(variables.languagecode, 'toasts.imported'));
} catch (e) {
console.log(e);
toast(variables.language.getMessage(variables.languagecode, 'toasts.error'));
}
this.setState({
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');
}
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>
<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>
</div>
</div>
</>
);
// todo: find a better way to do all this
const nextDescriptionDisabled = (this.state.addonMetadata.name !== undefined &&
this.state.addonMetadata.description !== undefined &&
this.state.addonMetadata.version !== undefined && this.state.addonMetadata.author !== undefined &&
this.state.addonMetadata.icon_url !== undefined && this.state.addonMetadata.screenshot_url !== undefined)
? false : true;
const setMetadata = (data, type) => {
this.setState({
addonMetadata: {
name: (type === 'name') ? data : this.state.addonMetadata.name,
description: (type === 'description') ? data : this.state.addonMetadata.description,
version: (type === 'version') ? data : this.state.addonMetadata.version,
author: (type === 'author') ? data : this.state.addonMetadata.author,
icon_url: (type === 'icon_url') ? data : this.state.addonMetadata.icon_url,
screenshot_url: (type === 'screenshot_url') ? data : this.state.addonMetadata.screenshot_url,
type: this.state.addonMetadata.type
}
});
};
const writeDescription = (
<>
<h3>{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>
</>
);
// settings
const nextSettingsDisabled = (this.state.addonData === '') ? true : false;
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>
</div>
</div>
</div>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => this.importSettings(JSON.parse(e.target.result))} />
<br/><br/>
<button onClick={() => this.changeTab(2)} className='uploadbg' style={{ 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 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>
</div>
</div>
</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 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>
</div>
</div>
</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>
</div>
</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;
}
return (
<>
<h2>{getMessage('modals.main.addons.create.other_title')}</h2>
{tabContent}
</>
);
}
}

View File

@@ -1,17 +1,17 @@
import React from 'react';
import WifiOffIcon from '@material-ui/icons/WifiOff';
import LocalMallIcon from '@material-ui/icons/LocalMall';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { WifiOff, LocalMall } from '@mui/icons-material';
import Item from '../Item';
import Items from '../Items';
import Dropdown from '../../settings/Dropdown';
import MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { install, urlParser, uninstall } from 'modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Marketplace extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class Marketplace extends React.PureComponent {
constructor() {
super();
this.state = {
@@ -22,10 +22,9 @@ export default class Marketplace extends React.PureComponent {
item: {}
};
this.buttons = {
uninstall: <button className='removeFromMue' onClick={() => this.manage('uninstall')}>{window.language.modals.main.marketplace.product.buttons.remove}</button>,
install: <button className='addToMue' onClick={() => this.manage('install')}>{window.language.modals.main.marketplace.product.buttons.addtomue}</button>
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>
};
this.language = window.language.modals.main.marketplace;
this.controller = new AbortController();
}
@@ -34,10 +33,10 @@ export default class Marketplace extends React.PureComponent {
let info;
// get item info
try {
info = await (await fetch(`${window.constants.MARKETPLACE_URL}/item/${this.props.type}/${data}`, { signal: this.controller.signal })).json();
info = await (await fetch(`${variables.constants.MARKETPLACE_URL}/item/${this.props.type}/${data}`, { signal: this.controller.signal })).json();
} catch (e) {
if (this.controller.signal.aborted === false) {
return toast(window.language.toasts.error);
return toast(this.getMessage('toasts.error'));
}
}
@@ -47,11 +46,20 @@ export default class Marketplace extends React.PureComponent {
// check if already installed
let button = this.buttons.install;
let addonInstalled = false;
let addonInstalledVersion;
const installed = JSON.parse(localStorage.getItem('installed'));
if (installed.some((item) => item.name === info.data.name)) {
button = this.buttons.uninstall;
addonInstalled = true;
for (let i = 0; i < installed.length; i++) {
if (installed[i].name === info.data.name) {
addonInstalledVersion = installed[i].version;
break;
}
}
}
this.setState({
@@ -59,16 +67,18 @@ export default class Marketplace extends React.PureComponent {
type: info.data.type,
display_name: info.data.name,
author: info.data.author,
description: MarketplaceFunctions.urlParser(info.data.description.replace(/\n/g, '<br>')),
description: urlParser(info.data.description.replace(/\n/g, '<br>')),
//updated: info.updated,
version: info.data.version,
icon: info.data.screenshot_url,
data: info.data
data: info.data,
addonInstalled,
addonInstalledVersion
},
button: button
});
window.stats.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
variables.stats.postEvent('marketplace-item', `${this.state.item.display_name} viewed`);
} else {
this.setState({
item: {}
@@ -77,8 +87,8 @@ export default class Marketplace extends React.PureComponent {
}
async getItems() {
const { data } = await (await fetch(window.constants.MARKETPLACE_URL + '/items/' + this.props.type, { signal: this.controller.signal })).json();
const featured = await (await fetch(window.constants.MARKETPLACE_URL + '/featured', { signal: this.controller.signal })).json();
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();
if (this.controller.signal.aborted === true) {
return;
@@ -96,18 +106,18 @@ export default class Marketplace extends React.PureComponent {
manage(type) {
if (type === 'install') {
MarketplaceFunctions.install(this.state.item.type, this.state.item.data);
install(this.state.item.type, this.state.item.data);
} else {
MarketplaceFunctions.uninstall(this.state.item.type, this.state.item.display_name);
uninstall(this.state.item.type, this.state.item.display_name);
}
toast(window.language.toasts[type + 'ed']);
toast(this.getMessage('toasts.' + type + 'ed'));
this.setState({
button: (type === 'install') ? this.buttons.uninstall : this.buttons.install
});
window.stats.postEvent('marketplace-item', `${this.state.item.display_name} ${(type === 'install' ? 'installed': 'uninstalled')}`);
window.stats.postEvent('marketplace', (type === 'install' ? 'Install': 'Uninstall'));
variables.stats.postEvent('marketplace-item', `${this.state.item.display_name} ${(type === 'install' ? 'installed': 'uninstalled')}`);
variables.stats.postEvent('marketplace', (type === 'install' ? 'Install': 'Uninstall'));
}
sortMarketplace(value, sendEvent) {
@@ -134,7 +144,7 @@ export default class Marketplace extends React.PureComponent {
});
if (sendEvent) {
window.stats.postEvent('marketplace', 'Sort');
variables.stats.postEvent('marketplace', 'Sort');
}
}
@@ -162,11 +172,24 @@ export default class Marketplace extends React.PureComponent {
);
};
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>
</>);
}
if (this.state.done === false) {
return errorMessage(<h1>{this.getMessage('modals.main.loading')}</h1>);
}
const featured = () => {
const openFeatured = () => {
window.stats.postEvent('marketplace', 'Featured clicked');
variables.stats.postEvent('marketplace', 'Featured clicked');
window.open(this.state.featured.buttonLink);
}
};
return (
<div className='featured' style={{ backgroundColor: this.state.featured.colour }}>
<p>{this.state.featured.title}</p>
@@ -176,44 +199,31 @@ export default class Marketplace extends React.PureComponent {
);
}
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return errorMessage(<>
<WifiOffIcon/>
<h1>{this.language.offline.title}</h1>
<p className='description'>{this.language.offline.description}</p>
</>);
}
if (this.state.done === false) {
return errorMessage(<h1>{window.language.modals.main.loading}</h1>);
}
if (this.state.items.length === 0) {
return (
<>
{featured()}
{errorMessage(<>
<LocalMallIcon/>
<h1>{window.language.modals.main.addons.empty.title}</h1>
<p className='description'>{this.language.no_items}</p>
<LocalMall/>
<h1>{this.getMessage('modals.main.addons.empty.title')}</h1>
<p className='description'>{this.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()}/>;
return <Item data={this.state.item} button={this.state.button} toggleFunction={() => this.toggle()} addonInstalled={this.state.item.addonInstalled} addonInstalledVersion={this.state.item.addonInstalledVersion}/>;
}
return (
<>
{featured()}
<br/>
<Dropdown label={window.language.modals.main.addons.sort.title} name='sortMarketplace' onChange={(value) => this.sortMarketplace(value)}>
<option value='a-z'>{window.language.modals.main.addons.sort.a_z}</option>
<option value='z-a'>{window.language.modals.main.addons.sort.z_a}</option>
<Dropdown 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>
<br/>
<Items items={this.state.items} toggleFunction={(input) => this.toggle('item', input)} />
</>
);

View File

@@ -1,26 +1,67 @@
import LocalMallIcon from '@material-ui/icons/LocalMall';
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 MarketplaceFunctions from '../../../../../modules/helpers/marketplace';
import { install } from 'modules/helpers/marketplace';
import { toast } from 'react-toastify';
export default class Sideload extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default function Sideload() {
const install = (input) => {
MarketplaceFunctions.install(input.type, input);
toast(window.language.toasts.installed);
window.stats.postEvent('marketplace', 'Sideload');
};
constructor(props) {
super(props);
this.state = {
showFailed: false,
failedReason: ''
}
}
return (
<div className='emptyitems'>
<div className='emptyMessage'>
<FileUpload id='file-input' type='settings' accept='application/json' loadFunction={(e) => install(JSON.parse(e.target.result))} />
<LocalMallIcon/>
<h1>{window.language.modals.main.addons.sideload}</h1>
<button className='addToMue sideload' onClick={() => document.getElementById('file-input').click()}>{window.language.modals.main.settings.sections.background.source.upload}</button>
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>
</div>
);
);
}
}

View File

@@ -1,12 +1,15 @@
@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 'settings/dropdown';
@import 'settings/daypicker';
@import 'marketplace/main';
@import 'marketplace/buttons';
.Modal {
color: var(--modal-text);
@@ -27,36 +30,6 @@
}
}
.mainModal {
padding: 25px;
}
.resetLink {
color: var(--modal-link);
cursor: pointer;
&:hover {
opacity: 0.8;
}
span {
font-size: 1.2rem;
color: var(--modal-link);
vertical-align: text-bottom;
margin-left: 5px;
}
}
.modalLink {
color: var(--modal-link);
cursor: pointer;
margin-left: 5px;
&:hover {
opacity: 0.8;
}
}
.closeModal {
position: absolute;
top: 1rem;
@@ -89,18 +62,15 @@
}
.ReactModal__Content {
//min-height: calc(100vh - 30vh);
//max-height: calc(100vh - 10vh);
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
overflow-y: auto;
position: relative;
// animation
opacity: 0;
transform: scale(0);
transition: all 300ms cubic-bezier(0.47, 1.64, 0.41, 0.8);
}
/* modal transition */
.ReactModal__Content--after-open {
opacity: 1;
transform: scale(1);
@@ -118,43 +88,9 @@
}
}
ul.sidebar {
position: absolute;
top: 0;
left: 0;
margin: 0;
padding-left: 0;
background: var(--sidebar);
border-radius: 12px 0 0 12px;
text-align: left;
font-size: 24px;
min-height: 100vh;
h1 {
text-align: center;
font-size: 1.8em;
}
svg {
vertical-align: middle;
padding: 5px;
}
hr {
height: 3px;
background: rgba(196, 196, 196, 0.74);
width: 75%;
outline: none;
border: none;
}
}
li {
list-style: none;
font-size: 24px;
padding: 5px 30px 5px 30px;
cursor: pointer;
margin-top: 2px;
/* main modal */
.mainModal {
padding: 25px;
}
#modal {
@@ -165,238 +101,16 @@ li {
bottom: 0;
left: 0;
height: 80%;
width: 60%;
}
@media only screen and (max-width: 1300px) {
@media (max-width: 1700px) {
#modal {
width: 90% !important;
width: 80% !important;
}
}
@media only screen and (min-width: 1310px) {
#modal {
width: 60%;
}
}
.tab-list-active {
background: var(--tab-active);
}
@media only screen and (max-width: 1200px) {
li.tab-list-item {
span {
display: none;
}
}
ul.sidebar {
h1 {
display: none;
}
}
}
.tab-list-item {
&:hover {
background: var(--tab-active);
}
}
.tab-content {
position: absolute;
h3 {
text-transform: uppercase;
}
}
@media only screen and (min-width: 2300px) {
.tab-content {
left: 350px;
top: 7%;
}
}
@media only screen and (max-width: 1920px) {
.tab-content {
left: 120px;
top: 50px;
}
}
@media only screen and (min-width: 1920px) {
.tab-content {
left: 350px;
top: 7%;
}
}
@media only screen and (max-width: 1400px), (min-width: 1400px) {
.tab-content {
left: 350px;
top: 75px;
}
}
@media only screen and (max-width: 1200px) {
.tab-content {
left: 125px;
top: 75px;
}
}
.navbar-item {
font-size: 22px;
font-weight: 500;
display: inline-flex;
&:hover {
color: rgb(165, 165, 165);
background: none;
}
span,
svg {
font-size: 1.1em !important;
}
svg {
font-size: 1.2em !important;
}
}
@supports (-webkit-hyphens: none) {
.navbar-item {
display: inline-block !important;
}
}
.modalNavbar {
position: absolute;
left: 20rem;
top: 1rem;
justify-content: center;
svg {
margin-right: 0.5rem;
padding: 3px;
vertical-align: middle;
}
}
@media only screen and (max-width: 1200px) {
.modalNavbar {
left: 6rem;
}
}
@media only screen and (max-width: 1650px) {
li.navbar-item {
span {
display: none;
}
}
}
@media only screen and (min-width: 1200px) {
ul.sidebar {
width: 310px;
align-items: center;
}
}
.navbar-item-active {
background: map-get($theme-colours, 'gradient');
-webkit-background-clip: text;
background-clip: text;
color: transparent;
svg {
color: orange;
}
&:hover {
background: map-get($theme-colours, 'gradient');
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
border-top-right-radius: map-get($modal, 'border-radius');
border-bottom-right-radius: map-get($modal, 'border-radius');
}
::-webkit-scrollbar-thumb {
background: #636e72;
border-top-right-radius: map-get($modal, 'border-radius');
border-bottom-right-radius: map-get($modal, 'border-radius');
}
.abouticon {
width: 96px;
height: auto;
border-radius: 50%;
padding-right: 5px;
}
.resetmodal {
min-height: 300px !important;
max-width: 300px !important;
margin: auto;
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);
}
.aboutIcon {
color: var(--modal-text) !important;
padding-right: 10px;
&:hover {
opacity: 0.8;
}
}
.aboutLink {
&:hover {
opacity: 0.8;
}
}
.aboutLogo {
height: 100px;
width: auto;
margin-left: -15px;
}
.MuiFormControl-root {
margin-top: 10px !important;
}
/* fixes for font size on extension */
label,
p,
span.modalLink {
@@ -404,7 +118,8 @@ span.modalLink {
}
h2 {
font-size: 1.5rem;
font-size: 2rem;
margin-bottom: 0;
}
h3 {
@@ -415,6 +130,12 @@ h5 {
font-size: 0.8rem;
}
.checkbox svg {
fill: var(--modal-text) !important;
}
.tab-content {
hr {
height: 5px;
background: rgba(196, 196, 196, 0.74);
outline: none;
border: none;
margin: 50px 0 30px 0;
}
}

View File

@@ -1,22 +1,11 @@
#item a {
color: var(--modal-link);
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
.emptyitems {
width: 25vw;
display: flex;
justify-content: center;
margin-top: 90px;
}
@import 'modules/item';
@import 'modules/buttons';
@import 'modules/featured';
@import 'modules/lightbox';
.items {
display: inline-grid;
grid-template-columns: repeat(6, 1fr);
grid-template-columns: repeat(4, 1fr);
margin-top: 15px;
.item {
@@ -25,11 +14,9 @@
height: 80px;
width: 260px;
background: var(--sidebar);
transition: 0.5s;
cursor: pointer;
margin-right: 20px;
margin-top: 20px;
box-shadow: 0 0 6px rgb(0 0 0 / 30%);
img {
height: 80px;
@@ -66,67 +53,34 @@
}
&:hover {
transform: scale(1.1);
background: var(--tab-active);
}
}
}
@media only screen and (max-width: 2100px) {
@media (max-width: 1920px) {
.items {
grid-template-columns: repeat(3, 1fr);
}
}
@media only screen and (max-width: 1870px) {
@media (max-width: 1680px) and (min-width: 1500px) {
.items {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (min-width: 1079px), (max-width: 1869px) {
@media (max-width: 1440px) {
.items {
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(1, 1fr);
}
}
@media only screen and (max-width: 1680px) {
.side {
float: none !important;
}
.sidebr {
display: none;
}
}
p.author {
margin-top: -5px;
}
#item {
h1 {
font-size: 40px;
line-height: 20px;
}
img {
float: left;
}
}
.side {
float: right;
margin-left: 20px;
}
#item>h1,
#item>.MuiSvgIcon-root {
display: inline;
}
p.description {
margin-top: 0px;
max-width: 800px;
.emptyitems {
width: 25vw;
display: flex;
justify-content: center;
margin-top: 90px;
}
.emptyMessage {
@@ -148,95 +102,28 @@ p.description {
}
}
.backArrow {
cursor: pointer;
width: 2rem !important;
height: 2rem !important;
&:hover {
color: grey;
}
p.author {
margin-top: -5px;
}
.informationContainer {
margin-top: 150px;
position: absolute;
}
.productInformation {
padding: 10px;
background: var(--sidebar);
width: 350px;
border-radius: 12px;
h4 {
cursor: initial !important;
}
li {
margin-left: -4px;
list-style: none;
font-size: 16px;
cursor: initial !important;
&.header {
text-transform: uppercase;
color: #787878;
margin-left: -5px;
}
}
}
#item>img, .updateimage, .updatechangelog>p>img {
#item>img,
.updateimage,
.updatechangelog>p>img {
border-radius: 12px;
height: 200px;
width: auto;
cursor: pointer;
}
.featured {
margin-top: 40px;
border-radius: 15px;
padding: 50px;
color: #fff;
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
width: 85%;
button {
float: left;
margin-top: -7px;
border: 2px solid map-get($theme-colours, 'main');
color: map-get($theme-colours, 'main');
&:hover {
background: map-get($theme-colours, 'main');
color: #2d3436;
}
}
h1 {
margin-top: -20px;
font-size: 2rem;
}
/* sideload failed modal */
.sideloadModal {
min-width: 250px;
max-width: 250px;
overflow-x: hidden;
}
.lightboxmodal {
margin: auto;
max-width: 60%;
background: none !important;
box-shadow: none !important;
img {
height: auto;
width: 100%;
}
.closeModal {
color: #fff;
text-shadow: 0 0 20px rgb(0 0 0 / 30%);
@media (max-height: 1080px) {
.dropdownsortAddons {
margin-top: 40px !important;
}
}
.overview {
font-size: 30px !important;
}

View File

@@ -40,6 +40,7 @@
.addToMue {
@extend %storebutton;
float: right;
margin-top: -10px;
}
@@ -60,3 +61,7 @@ button.round {
vertical-align: middle;
padding: 10px;
}
.updateCheck {
margin-top: 15px;
}

View File

@@ -0,0 +1,25 @@
.featured {
margin-top: 40px;
border-radius: 15px;
padding: 50px;
color: #fff;
box-shadow: 0 0 10px rgb(0 0 0 / 30%);
width: 85%;
button {
float: left;
margin-top: -7px;
border: 2px solid map-get($theme-colours, 'main');
color: map-get($theme-colours, 'main');
&:hover {
background: map-get($theme-colours, 'main');
color: #2d3436;
}
}
h1 {
margin-top: -20px;
font-size: 2rem;
}
}

View File

@@ -0,0 +1,90 @@
#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;
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;
h4 {
cursor: initial !important;
}
li {
margin-left: -4px;
list-style: none;
font-size: 16px;
cursor: initial !important;
&.header {
text-transform: uppercase;
color: #787878;
margin-left: -5px;
}
}
}
@media only screen and (max-width: 1200px) {
.side {
margin-top: 222px;
float: none !important;
}
.overview {
margin-top: -160px !important;
}
}
.overview {
font-size: 30px !important;
margin-top: 33px;
}

View File

@@ -0,0 +1,16 @@
.lightboxmodal {
margin: auto;
max-width: 60%;
background: none !important;
box-shadow: none !important;
img {
height: auto;
width: 100%;
}
.closeModal {
color: #fff;
text-shadow: 0 0 20px rgb(0 0 0 / 30%);
}
}

View File

@@ -0,0 +1,25 @@
.resetLink {
color: var(--modal-link);
cursor: pointer;
&:hover {
opacity: 0.8;
}
span {
font-size: 1.2rem;
color: var(--modal-link);
vertical-align: text-bottom;
margin-left: 5px;
}
}
.modalLink {
color: var(--modal-link);
cursor: pointer;
margin-left: 5px;
&:hover {
opacity: 0.8;
}
}

View File

@@ -0,0 +1,72 @@
.navbar-item {
font-size: 22px;
font-weight: 500;
display: flex;
flex-direction: column;
align-items: center;
color: var(--photo-info);
&:hover {
svg {
background: var(--tab-active);
}
color: var(--modal-text)
}
span,
svg {
font-size: 1.1em !important;
}
svg {
font-size: 1.2em !important;
width: 60px;
padding: 5px;
border-radius: 20px;
color: var(--photo-info);
}
}
/* safari fix */
@supports (-webkit-hyphens: none) {
.navbar-item {
display: inline-block !important;
}
}
.modalNavbar {
position: absolute;
left: 20rem;
top: 1rem;
justify-content: center;
display: flex;
svg {
margin-right: 0.5rem;
padding: 3px;
vertical-align: middle;
}
}
@media only screen and (max-width: 1200px) {
.modalNavbar {
left: 6rem;
}
}
@media only screen and (max-width: 800px) {
li.navbar-item {
span {
display: none;
}
}
}
.navbar-item-active {
color: var(--modal-text);
svg {
background: var(--sidebar);
}
}

View File

@@ -0,0 +1,12 @@
::-webkit-scrollbar {
width: 6px;
height: 6px;
border-top-right-radius: map-get($modal, 'border-radius');
border-bottom-right-radius: map-get($modal, 'border-radius');
}
::-webkit-scrollbar-thumb {
background: #636e72;
border-top-right-radius: map-get($modal, 'border-radius');
border-bottom-right-radius: map-get($modal, 'border-radius');
}

View File

@@ -0,0 +1,87 @@
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;
h1 {
text-align: center;
font-size: 1.8em;
}
svg {
vertical-align: middle;
padding: 5px;
}
hr {
height: 3px;
background: rgba(196, 196, 196, 0.74);
width: 75%;
outline: none;
border: none;
}
}
@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;
}
}
ul.sidebar {
h1 {
display: none;
}
}
}
.tab-list-item {
&:hover {
background: var(--tab-active);
}
}

View File

@@ -0,0 +1,43 @@
.tab-content {
position: absolute;
h3 {
font-size: 1.5rem;
margin-bottom: 0;
}
}
@media only screen and (min-width: 2300px) {
.tab-content {
left: 350px;
top: 7%;
}
}
@media only screen and (max-width: 1920px) {
.tab-content {
left: 120px;
top: 60px;
}
}
@media only screen and (min-width: 1920px) {
.tab-content {
left: 350px;
top: 9%;
}
}
@media only screen and (max-width: 1400px),
(min-width: 1400px) {
.tab-content {
left: 350px;
}
}
@media only screen and (max-width: 1200px) {
.tab-content {
left: 125px;
top: 90px;
}
}

View File

@@ -24,7 +24,8 @@
}
}
.add {
.add,
.close {
@extend %settingsButton;
background-color: map-get($button-colours, 'other');
@@ -37,30 +38,39 @@
}
.close {
@extend %settingsButton;
padding: 10px 50px 10px 50px;
background-color: map-get($button-colours, 'other');
border: 2px solid map-get($button-colours, 'other');
&:hover {
color: map-get($button-colours, 'other');
border: 2px solid map-get($button-colours, 'other');
}
}
.export,
.uploadbg,
.import {
@extend %settingsButton;
background-color: map-get($button-colours, 'other');
color: map-get($theme-colours, 'primary');
border: 2px solid map-get($button-colours, 'other');
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 {
color: map-get($button-colours, 'other');
border: 2px solid map-get($button-colours, 'other');
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);
}
}
}
@@ -69,32 +79,39 @@
margin-left: 20px;
}
.MuiIconButton-label > svg.MuiSvgIcon-root {
color: var(--modal-text) !important;
.round-small {
height: 10px !important;
width: 10px !important;
}
.sortableitem {
background: var(--sidebar) !important;
padding: 10px 80px;
padding-left: 10px;
border-radius: 15px;
margin-bottom: 10px;
font-size: 1.325rem;
color: var(--modal-text) !important;
cursor: move;
width: 150px;
z-index: 999 !important;
.data-buttons-row {
width: 350px;
display: flex;
flex-direction: row;
svg {
font-size: 1.3rem;
}
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 {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.15);
transition: 0.3s;
&:hover {
background: var(--tab-active);
cursor: pointer;
}
}
}
.MuiTouchRipple-root {
background: transparent;
.customvideoicon {
position: absolute;
margin-bottom: 45px;
font-size: 3em !important;
}

View File

@@ -1,26 +0,0 @@
.DayPickerInput,
.input-container {
input {
width: 200px;
color: var(--modal-text) !important;
background: var(--sidebar);
border: none;
padding: 10px 10px;
border-radius: 5px;
}
}
.DayPicker-Day--selected {
background-color: #ff4757 !important;
color: white;
}
.DayPicker-Months,
.DayPickerInput-Overlay {
background-color: var(--background) !important;
color: var(--modal-text) !important;
}
.DayPicker:not(.DayPicker--interactionDisabled) .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {
color: black !important;
}

View File

@@ -1,37 +0,0 @@
select {
margin-left: 10px;
width: 120px;
color: var(--modal-text);
background: var(--sidebar);
border: none;
padding: 10px 10px;
border-radius: 5px;
}
// firefox dropdown
@supports (-moz-appearance: none) {
select {
-moz-appearance: none !important;
background: url("data:image/svg+xml;utf8,<svg fill='black' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>") right center no-repeat, var(--sidebar) !important;
}
.dark select {
background: url("data:image/svg+xml;utf8,<svg fill='white' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>") right center no-repeat, var(--sidebar) !important;
}
option {
font: -moz-pull-down-menu !important;
}
}
// safari dropdown
@supports (-webkit-hyphens: none) {
select {
-webkit-appearance: none !important;
background: url("data:image/svg+xml;utf8,<svg fill='black' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>") right center no-repeat, var(--sidebar) !important;
}
.dark select {
background: url("data:image/svg+xml;utf8,<svg fill='white' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>") right center no-repeat, var(--sidebar) !important;
}
}

View File

@@ -1,13 +1,13 @@
input {
&[type=text] {
width: 200px;
color: var(--modal-text);
background: var(--sidebar);
border: none;
padding: 10px 10px;
border-radius: 5px;
}
@import 'modules/resetmodal';
@import 'modules/material-ui';
@import 'modules/reminder';
@import 'modules/tabs/about';
@import 'modules/tabs/changelog';
@import 'modules/tabs/order';
input {
/* colour picker */
&[type=color] {
border-radius: 100%;
height: 30px;
@@ -17,17 +17,18 @@ input {
-webkit-appearance: none;
vertical-align: middle;
background: none;
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border: none;
border-radius: 100%;
}
}
/* firefox fixes for colour picker (using "," didn't work) */
&[type=color]::-moz-color-swatch {
border-radius: 100%;
height: 30px;
@@ -37,27 +38,35 @@ input {
-moz-appearance: none;
vertical-align: middle;
background: none;
&::-moz-color-swatch-wrapper {
padding: 0;
}
&::-moz-color-swatch {
border: none;
border-radius: 100%;
}
}
/* date picker */
&[type=date] {
width: 200px;
width: 280px;
color: var(--modal-text);
background: var(--sidebar);
border: none;
padding: 10px 10px;
border-radius: 5px;
background: var(--background);
border: solid var(--modal-text) 1px;
padding: 15px 20px;
border-radius: 4px;
display: flex !important;
cursor: pointer;
&::-webkit-calendar-picker-indicator {
cursor: pointer;
}
}
}
/* dark theme date picker fix */
.dark {
::-webkit-calendar-picker-indicator {
filter: invert(1);
@@ -68,171 +77,10 @@ h4 {
cursor: pointer;
}
ul {
padding-left: 0px;
margin: 0;
.keybind-table {
text-align: left;
>label {
vertical-align: middle;
}
}
.range {
-webkit-appearance: none;
width: 200px;
height: 12px;
border-radius: 12px;
outline: none;
background: var(--sidebar);
box-shadow: 0 0 100px rgba(0, 0, 0, 0.3);
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 12px;
background: var(--modal-text);
cursor: pointer;
}
&::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 12px;
border: 0;
background: var(--modal-text);
cursor: pointer;
}
}
.newFeature {
color: #ff4757;
font-size: 12px;
}
.settingsTextarea {
font-family: Consolas !important;
padding: 15px;
border-radius: 15px;
background-color: var(--sidebar) !important;
border: none;
margin-left: 0;
width: 400px;
height: 200px;
max-width: 60%;
}
.MuiCheckbox-colorPrimary.Mui-checked,
.MuiSwitch-colorPrimary.Mui-checked,
.MuIconButton-colorPrimary.Mui-checked,
.MuiSwitch-thumb,
.MuiRadio-colorSecondary.Mui-checked,
.PrivateSwitchBase-input-4,
.MuiRadio-root,
.aboutLink,
legend {
color: var(--modal-text) !important;
}
.MuiFormControlLabel-labelPlacementStart {
margin-left: 0px !important;
}
.MuiSwitch-colorPrimary.Mui-checked+.MuiSwitch-track {
background: darkgray !important;
}
.reminder-info {
position: absolute;
bottom: 20px;
right: 20px;
padding: 15px;
color: var(--modal-text);
background: var(--sidebar);
max-width: 300px;
border-radius: 0.7em;
h1 {
font-size: 1rem;
}
}
.radio-title {
text-transform: uppercase;
font-weight: bold;
font-size: 1.17rem;
}
.radio-title-small {
text-transform: uppercase;
font-weight: bold;
font-size: 1rem;
}
.sortableitem {
color: var(--modal-text) !important;
cursor: move;
}
.updatechangelog {
max-width: 75%;
li {
cursor: initial;
font-size: 1rem;
list-style-type:disc;
padding: 0;
margin-left: 20px;
}
a {
color: var(--modal-link);
&:hover {
opacity: 0.8;
}
}
}
.changelogtab {
h1 {
max-width: 85%;
font-size: 2rem;
}
img {
max-width: 95%;
}
}
.sliderText {
color: var(--modal-text);
background: none;
border: none;
border-radius: 0;
font-size: 1rem;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
.resetArea {
h2 {
font-size: 2rem !important;
}
h2, span, svg {
display: inline;
}
svg {
vertical-align: sub;
font-size: 1.4rem;
th {
padding-right: 10px;
}
}

View File

@@ -0,0 +1,103 @@
/* these are overrides for the material ui default styles */
.MuiCheckbox-colorPrimary.Mui-checked,
.MuiSwitch-colorPrimary.Mui-checked,
.MuIconButton-colorPrimary.Mui-checked,
.MuiSwitch-thumb,
.MuiRadio-colorSecondary.Mui-checked,
.PrivateSwitchBase-input-4,
.MuiRadio-root,
.aboutLink,
.MuiSlider-colorPrimary,
legend {
color: var(--modal-text) !important;
}
.MuiFormControlLabel-labelPlacementStart {
margin-left: 0px !important;
}
.MuiSwitch-colorPrimary.Mui-checked+.MuiSwitch-track {
background: darkgray !important;
}
.MuiIconButton-label>svg.MuiSvgIcon-root {
color: var(--modal-text) !important;
}
.MuiTouchRipple-root {
background: transparent;
}
.MuiFormControl-root {
margin-top: 10px !important;
}
.checkbox svg {
fill: var(--modal-text) !important;
}
.radio-title {
font-weight: bold;
font-size: 1.17rem;
}
.radio-title-small {
font-weight: bold;
font-size: 1rem;
}
.MuiSlider-root {
margin-bottom: 30px !important;
}
.MuiOutlinedInput-notchedOutline {
border-color: var(--modal-text) !important;
}
.MuiFormLabel-root-MuiInputLabel-root {
color: var(--modal-text) !important;
}
.MuiInputLabel-root,
.MuiSlider-markLabel,
.MuiInputLabel-root,
.MuiSelect-icon,
.MuiSelect-select,
.Mui-focused,
legend,
.MuiOutlinedInput-input {
color: var(--modal-text) !important;
}
.MuiMenu-list {
background-color: var(--background) !important;
color: var(--modal-text) !important;
}
.Mui-selected {
background-color: var(--tab-active) !important;
}
.MuiTextField-root,
.MuiFormControl-root,
.MuiSlider-root {
width: 300px !important;
display: flex !important;
}
.Mui-disabled {
color: #818181 !important;
cursor: not-allowed;
.checkbox svg {
fill: #818181 !important;
}
}
.MuiPaper-root {
background-color: var(--background) !important;
}
.MuiSlider-valueLabel {
background-color: var(--tab-active) !important;
}

View File

@@ -0,0 +1,14 @@
.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;
}
}

View File

@@ -0,0 +1,27 @@
.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);
}

View File

@@ -0,0 +1,32 @@
.aboutIcon {
color: var(--modal-text) !important;
padding-right: 10px;
&:hover {
opacity: 0.8;
}
}
.aboutLink {
&:hover {
opacity: 0.8;
}
}
.aboutLogo {
height: 100px;
width: auto;
margin-left: -15px;
}
.abouticon {
width: 96px;
height: auto;
border-radius: 50%;
padding-right: 5px;
}
.contacth3 {
font-size: 1.5rem;
margin-bottom: 0.8em !important;
}

View File

@@ -0,0 +1,35 @@
.updatechangelog {
max-width: 75%;
li {
cursor: initial;
font-size: 1rem;
list-style-type: disc;
padding: 0;
margin-left: 20px;
}
a {
color: var(--modal-link);
&:hover {
opacity: 0.8;
}
}
}
.changelogtab {
h1 {
max-width: 85%;
font-size: 2rem;
margin-bottom: -10px !important;
}
h5 {
line-height: 0px !important;
}
img {
max-width: 95%;
}
}

View File

@@ -0,0 +1,54 @@
.sortableitem {
background: var(--sidebar) !important;
padding: 10px 80px;
padding-left: 10px;
border-radius: 15px;
margin-bottom: 10px;
font-size: 1.325rem;
color: var(--modal-text) !important;
cursor: move;
width: 150px;
z-index: 999 !important;
svg {
font-size: 1.3rem;
}
&:hover {
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;
display: flex;
align-items: flex-end;
justify-content: center;
svg {
font-size: 1.9em;
}
}
}

View File

@@ -31,3 +31,8 @@ div.color-preview-area > div > div:nth-child(5) {
.gradient-type-item.active::after {
border: 2px solid var(--modal-text) !important;
}
.text-input, .number-input {
background-color: var(--sidebar) !important;
color: var(--modal-text) !important;
}

View File

@@ -1,11 +1,10 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Checkbox as CheckboxUI, FormControlLabel } from '@mui/material';
import EventBus from '../../../../modules/helpers/eventbus';
import EventBus from 'modules/helpers/eventbus';
import CheckboxUI from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
export default class Checkbox extends React.PureComponent {
export default class Checkbox extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -21,7 +20,11 @@ export default class Checkbox extends React.PureComponent {
checked: value
});
window.stats.postEvent('setting', `${this.props.name} ${(this.state.checked === true) ? 'enabled' : 'disabled'}`);
if (this.props.onChange) {
this.props.onChange(value);
}
variables.stats.postEvent('setting', `${this.props.name} ${(this.state.checked === true) ? 'enabled' : 'disabled'}`);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {
@@ -34,19 +37,11 @@ export default class Checkbox extends React.PureComponent {
}
render() {
let text = this.props.text;
if (this.props.newFeature) {
text = <>{this.props.text} <span className='newFeature'> NEW</span></>;
} else if (this.props.betaFeature) {
text = <>{this.props.text} <span className='newFeature'> BETA</span></>;
}
return (
<>
<FormControlLabel
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} />}
label={text}
control={<CheckboxUI name={this.props.name} color='primary' className='checkbox' checked={this.state.checked} onChange={this.handleChange} disabled={this.props.disabled || false} />}
label={this.props.text}
/>
<br/>
</>

View File

@@ -1,35 +1,35 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { InputLabel, MenuItem, FormControl, Select } from '@mui/material';
import EventBus from '../../../../modules/helpers/eventbus';
import EventBus from 'modules/helpers/eventbus';
export default class Dropdown extends React.PureComponent {
export default class Dropdown extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || '',
value: localStorage.getItem(this.props.name) || this.props.children[0].props.value,
title: ''
};
}
getLabel() {
return this.props.label ? <label>{this.props.label}</label> : null;
this.dropdown = createRef();
}
onChange = (e) => {
const { value } = e.target;
if (value === window.language.modals.main.loading) {
if (value === variables.language.getMessage(variables.languagecode, 'modals.main.loading')) {
return;
}
window.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
variables.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
this.setState({
value: value,
title: e.target[e.target.selectedIndex].text
value
});
localStorage.setItem(this.props.name, value);
if (!this.props.noSetting) {
localStorage.setItem(this.props.name, value);
}
if (this.props.onChange) {
this.props.onChange(value);
@@ -45,22 +45,19 @@ export default class Dropdown extends React.PureComponent {
EventBus.dispatch('refresh', this.props.category);
}
// todo: find a better way to do this
componentDidMount() {
const element = document.getElementById(this.props.name);
this.setState({
title: element[element.selectedIndex].text
});
}
render() {
const id = 'dropdown' + this.props.name;
const label = this.props.label || '';
return (
<>
{this.getLabel()}
<select id={this.props.name} value={this.state.value} onChange={this.onChange} style={{ width: `${(8*this.state.title.length) + 50}px` }}>
{this.props.children}
</select>
</>
<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>
</FormControl>
);
}
}

View File

@@ -1,8 +1,10 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
export default class FileUpload extends React.PureComponent {
export default class FileUpload extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
componentDidMount() {
document.getElementById(this.props.id).onchange = (e) => {
const reader = new FileReader();
@@ -13,7 +15,7 @@ export default class FileUpload extends React.PureComponent {
} else {
// background upload
if (file.size > 2000000) {
return toast(window.language.modals.main.file_upload_error);
return toast(this.getMessage('modals.main.file_upload_error'));
}
reader.readAsDataURL(file);

View File

@@ -0,0 +1,24 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Slider from './Slider';
import Switch from './Switch';
import { values } from 'modules/helpers/settings/modals';
export default 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/>}
</>
);
}
}

View File

@@ -0,0 +1,24 @@
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()}
</>
);
}

View File

@@ -1,14 +1,10 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Radio as RadioUI, RadioGroup, FormControlLabel, FormControl, FormLabel } from '@mui/material';
import EventBus from '../../../../modules/helpers/eventbus';
import EventBus from 'modules/helpers/eventbus';
import RadioUI from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import FormLabel from '@material-ui/core/FormLabel';
export default class Radio extends React.PureComponent {
export default class Radio extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -23,13 +19,24 @@ export default class Radio extends React.PureComponent {
return;
}
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);
}
}
localStorage.setItem(this.props.name, value);
this.setState({
value: value
value
});
window.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
if (this.props.onChange) {
this.props.onChange(value);
}
variables.stats.postEvent('setting', `${this.props.name} from ${this.state.value} to ${value}`);
if (this.props.element) {
if (!document.querySelector(this.props.element)) {

View File

@@ -1,29 +1,26 @@
import CloseIcon from '@material-ui/icons/Close';
import DeleteIcon from '@material-ui/icons/Delete';
import SettingsFunctions from '../../../../modules/helpers/settings';
export default function ResetModal(props) {
const language = window.language.modals.main.settings.sections.advanced.reset_modal;
import variables from 'modules/variables';
import { Close, Delete } from '@mui/icons-material';
import { setDefaultSettings } from 'modules/helpers/settings';
export default function ResetModal({ modalClose }) {
const reset = () => {
window.stats.postEvent('setting', 'Reset');
SettingsFunctions.setDefaultSettings('reset');
variables.stats.postEvent('setting', 'Reset');
setDefaultSettings('reset');
window.location.reload();
};
return (
<>
<h1 style={{ textAlign: 'center' }}>{language.title}</h1>
<span>{language.question}</span>
<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>{language.information}</span>
<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()}>
<DeleteIcon/>
<Delete/>
</button>
<button className='round import' style={{ marginLeft: '5px' }} onClick={props.modalClose}>
<CloseIcon/>
<button className='round add' style={{ marginLeft: '5px' }} onClick={modalClose}>
<Close/>
</button>
</div>
</>

View File

@@ -1,23 +1,21 @@
// todo: find a better method to do width of number input
import React from 'react';
import EventBus from '../../../../modules/helpers/eventbus';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { Slider } from '@mui/material';
export default class Slider extends React.PureComponent {
import EventBus from 'modules/helpers/eventbus';
export default class SliderComponent extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || this.props.default,
numberWidth: localStorage.getItem(this.props.name) ? ((localStorage.getItem(this.props.name).length + 1) * ((this.props.toast === true) ? 7.75 : 7)) : 32
value: localStorage.getItem(this.props.name) || this.props.default
};
this.language = window.language.modals.main.settings;
this.widthCalculation = (this.props.toast === true) ? 7.75 : 7;
}
handleChange = (e, text) => {
let { value } = e.target;
value = Number(value);
if (text) {
if (value === '') {
@@ -26,19 +24,18 @@ export default class Slider extends React.PureComponent {
});
}
if (Number(value) > this.props.max) {
if (value > this.props.max) {
value = this.props.max;
}
if (Number(value) < this.props.min) {
if (value < this.props.min) {
value = this.props.min;
}
}
localStorage.setItem(this.props.name, value);
this.setState({
value: value,
numberWidth: ((value.length + 1) * this.widthCalculation)
value
});
if (this.props.element) {
@@ -57,16 +54,24 @@ export default class Slider extends React.PureComponent {
value: this.props.default || ''
}
});
toast(window.language.toasts.reset);
toast(variables.language.getMessage(variables.languagecode, 'toasts.reset'));
}
render() {
const text = <input className='sliderText' type='number' min={this.props.min} max={this.props.max} onChange={(e) => this.handleChange(e, 'text')} value={this.state.value} style={{ width: this.state.numberWidth }}/>;
return (
<>
<p>{this.props.title} ({text}{this.props.display}) <span className='modalLink' onClick={this.resetItem}>{this.language.buttons.reset}</span></p>
<input className='range' type='range' min={this.props.min} max={this.props.max} step={this.props.step || 1} value={this.state.value} onChange={this.handleChange} />
<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}`}
marks={this.props.marks || []}
/>
</>
);
}

View File

@@ -1,11 +1,10 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Switch as SwitchUI, FormControlLabel } from '@mui/material';
import EventBus from '../../../../modules/helpers/eventbus';
import EventBus from 'modules/helpers/eventbus';
import SwitchUI from '@material-ui/core/Switch';
import FormControlLabel from '@material-ui/core/FormControlLabel';
export default class Switch extends React.PureComponent {
export default class Switch extends PureComponent {
constructor(props) {
super(props);
this.state = {
@@ -21,7 +20,7 @@ export default class Switch extends React.PureComponent {
checked: value
});
window.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)) {
@@ -34,22 +33,13 @@ export default class Switch extends React.PureComponent {
}
render() {
let text = this.props.text;
if (this.props.newFeature) {
text = <>{this.props.text} <span className='newFeature'> NEW</span></>;
} else if (this.props.betaFeature) {
text = <>{this.props.text} <span className='newFeature'> BETA</span></>;
}
return (
<>
<FormControlLabel
control={<SwitchUI name={this.props.name} color='primary' checked={this.state.checked} onChange={this.handleChange} />}
label={text}
label={this.props.text}
labelPlacement='start'
/>
<br/>
</>
);
}

View File

@@ -1,16 +1,16 @@
import React from 'react';
import EventBus from '../../../../modules/helpers/eventbus';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { TextField } from '@mui/material';
export default class Text extends React.PureComponent {
import EventBus from 'modules/helpers/eventbus';
export default class Text extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: localStorage.getItem(this.props.name) || ''
};
this.language = window.language.modals.main.settings;
}
handleChange = (e) => {
@@ -23,7 +23,7 @@ export default class Text extends React.PureComponent {
localStorage.setItem(this.props.name, value);
this.setState({
value: value
value
});
if (this.props.element) {
@@ -42,17 +42,17 @@ export default class Text extends React.PureComponent {
value: this.props.default || ''
}
});
toast(window.language.toasts.reset);
toast(variables.language.getMessage(variables.languagecode, 'toasts.reset'));
}
render() {
return (
<>
<p>{this.props.title} <span className='modalLink' onClick={this.resetItem}>{this.language.buttons.reset}</span></p>
{(this.props.textarea === true) ?
<textarea className='settingsTextarea' spellCheck={false} value={this.state.value} onChange={this.handleChange}/>
: <input type='text' value={this.state.value} onChange={this.handleChange}/>
{(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>
</>
);
}

View File

@@ -1,26 +1,24 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Email, Twitter, Chat, Instagram, Facebook } from '@mui/icons-material';
import Tooltip from '../../../../helpers/tooltip/Tooltip';
import EmailIcon from '@material-ui/icons/Email';
import TwitterIcon from '@material-ui/icons/Twitter';
import ChatIcon from '@material-ui/icons/Chat';
import InstagramIcon from '@material-ui/icons/Instagram';
import FacebookIcon from '@material-ui/icons/Facebook';
import Tooltip from 'components/helpers/tooltip/Tooltip';
const other_contributors = require('../../../../../modules/other_contributors.json');
const other_contributors = require('modules/other_contributors.json');
export default class About extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class About extends React.PureComponent {
constructor() {
super();
this.state = {
contributors: [],
sponsors: [],
other_contributors: [],
photographers: window.language.modals.main.loading,
update: window.language.modals.main.settings.sections.about.version.checking_update,
loading: window.language.modals.main.loading
photographers: this.getMessage('modals.main.loading'),
update: this.getMessage('modals.main.settings.sections.about.version.checking_update'),
loading: this.getMessage('modals.main.loading')
};
this.language = window.language.modals.main.settings.sections.about;
this.controller = new AbortController();
}
@@ -28,40 +26,42 @@ export default class About extends React.PureComponent {
let contributors, sponsors, photographers, versionData;
try {
versionData = await (await fetch(window.constants.GITHUB_URL + '/repos/' + window.constants.ORG_NAME + '/' + window.constants.REPO_NAME + '/releases', { signal: this.controller.signal })).json();
contributors = await (await fetch(window.constants.GITHUB_URL + '/repos/'+ window.constants.ORG_NAME + '/' + window.constants.REPO_NAME + '/contributors', { signal: this.controller.signal })).json();
sponsors = (await (await fetch(window.constants.SPONSORS_URL + '/list', { signal: this.controller.signal })).json()).sponsors;
photographers = await (await fetch(window.constants.API_URL + '/images/photographers', { signal: this.controller.signal })).json();
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.language.version.error.title,
loading: this.language.version.error.description
update: this.getMessage('modals.main.settings.sections.about.version.error.title'),
loading: this.getMessage('modals.main.settings.sections.about.version.error.description')
});
}
if (sponsors.length === 0) {
sponsors = [{ handle: 'empty' }];
}
if (this.controller.signal.aborted === true) {
return;
}
const newVersion = versionData[0].tag_name;
let updateMsg = this.language.version.no_update;
if (Number(window.constants.VERSION.replaceAll('.', '')) < Number(newVersion.replaceAll('.', ''))) {
updateMsg = `${this.language.version.update_available}: ${newVersion}`;
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}`;
}
this.setState({
// exclude bots
contributors: contributors.filter((contributor) => !contributor.login.includes('bot')),
sponsors: sponsors,
update: updateMsg,
other_contributors: other_contributors,
sponsors,
update,
other_contributors,
photographers: photographers.sort().join(', '),
loading: null
});
@@ -70,8 +70,8 @@ export default class About extends React.PureComponent {
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
this.setState({
update: this.language.version.offline_mode,
loading: window.language.modals.main.marketplace.offline.description
update: this.getMessage('modals.main.settings.sections.about.version.checking_update'),
loading: this.getMessage('modals.main.marketplace.offline.description')
});
return;
}
@@ -87,57 +87,62 @@ export default class About extends React.PureComponent {
render() {
return (
<>
<h2>{this.language.title}</h2>
<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.language.copyright} {window.constants.COPYRIGHT_YEAR}-{new Date().getFullYear()} <a href={'https://github.com/' + window.constants.ORG_NAME + '/' + window.constants.REPO_NAME + '/graphs/contributors'} className='aboutLink' target='_blank' rel='noopener noreferrer'>{window.constants.COPYRIGHT_NAME}</a> ({window.constants.COPYRIGHT_LICENSE})</p>
<p>{this.language.version.title} {window.constants.VERSION} ({this.state.update})</p>
<a href={window.constants.PRIVACY_URL} className='aboutLink' target='_blank' rel='noopener noreferrer' style={{ fontSize: '1rem' }}>{window.language.modals.welcome.sections.privacy.links.privacy_policy}</a>
<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>
<h3>{this.language.contact_us}</h3>
<a href={'mailto:' + window.constants.EMAIL} className='aboutIcon' target='_blank' rel='noopener noreferrer'><EmailIcon/></a>
<a href={'https://twitter.com/' + window.constants.TWITTER_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><TwitterIcon/></a>
<a href={'https://instagram.com/' + window.constants.INSTAGRAM_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><InstagramIcon/></a>
<a href={'https://facebook.com/' + window.constants.FACEBOOK_HANDLE} className='aboutIcon' target='_blank' rel='noopener noreferrer'><FacebookIcon/></a>
<a href={'https://discord.gg/' + window.constants.DISCORD_SERVER} className='aboutIcon' target='_blank' rel='noopener noreferrer'><ChatIcon/></a>
<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.language.support_mue}</h3>
<h3>{this.getMessage('modals.main.settings.sections.about.support_mue')}</h3>
<p>
<a href={'https://github.com/sponsors/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>GitHub Sponsors</a>
&nbsp; &nbsp;<a href={'https://ko-fi.com/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
&nbsp; &nbsp;<a href={'https://patreon.com/' + window.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Patreon</a>
<a href={'https://github.com/sponsors/' + variables.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>GitHub Sponsors</a>
&nbsp; &nbsp;<a href={'https://ko-fi.com/' + variables.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Ko-Fi</a>
&nbsp; &nbsp;<a href={'https://patreon.com/' + variables.constants.DONATE_USERNAME} className='aboutLink' target='_blank' rel='noopener noreferrer'>Patreon</a>
</p>
<h3>{this.language.resources_used.title}</h3>
<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.language.resources_used.bg_images})
, <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://fonts.google.com/icons?selected=Material+Icons' className='aboutLink' target='_blank' rel='noopener noreferrer'>Google Fonts</a> ({this.language.resources_used.pin_icon})</p>
<p><a href='https://undraw.co' className='aboutLink' target='_blank' rel='noopener noreferrer'>Undraw</a> ({this.language.resources_used.welcome_img})</p>
<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.language.contributors}</h3>
<h3>{this.getMessage('modals.main.settings.sections.about.contributors')}</h3>
<p>{this.state.loading}</p>
{this.state.contributors.map((item) => (
<Tooltip title={item.login} key={item.login}>
<a href={'https://github.com/' + item.login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar_url + '&size=128'} alt={item.login}/></a>
{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((item) => (
<Tooltip title={item.login} key={item.login}>
<a href={'https://github.com/' + item.login} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar_url + '&size=128'} alt={item.login}/></a>
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.language.supporters}</h3>
<h3>{this.getMessage('modals.main.settings.sections.about.supporters')}</h3>
<p>{this.state.loading}</p>
{this.state.sponsors.map((item) => (
<Tooltip title={item.handle} key={item.handle}>
<a href={'https://github.com/' + item.handle} target='_blank' rel='noopener noreferrer'><img draggable='false' className='abouticon' src={item.avatar + '&size=128'} alt={item.handle}></img></a>
</Tooltip>
))}
{this.state.sponsors.map(({ handle, avatar }) => {
if (handle === 'empty') {
return <p>{this.getMessage('modals.main.settings.sections.about.no_supporters')}</p>;
}
<h3>{this.language.photographers}</h3>
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>
</Tooltip>
)
})}
<h3>{this.getMessage('modals.main.settings.sections.about.photographers')}</h3>
<p>{this.state.photographers}</p>
</>
);

View File

@@ -1,4 +1,10 @@
import React from 'react';
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 { exportSettings, importSettings } from 'modules/helpers/settings/modals';
import Checkbox from '../Checkbox';
import FileUpload from '../FileUpload';
@@ -7,49 +13,62 @@ import Switch from '../Switch';
import ResetModal from '../ResetModal';
import Dropdown from '../Dropdown';
import SettingsFunctions from '../../../../../modules/helpers/settings/modals';
const time_zones = require('components/widgets/time/timezones.json');
import Modal from 'react-modal';
const time_zones = require('../../../../widgets/time/timezones.json');
export default class AdvancedSettings extends React.PureComponent {
export default class AdvancedSettings extends PureComponent {
constructor() {
super();
this.state = {
resetModal: false
};
this.language = window.language.modals.main.settings;
}
render() {
const { advanced } = this.language.sections;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<>
<h2>{advanced.title}</h2>
<Checkbox name='offlineMode' text={advanced.offline_mode} element='.other' />
<Dropdown name='timezone' label={advanced.timezone.title} category='timezone'>
<option value='auto'>{advanced.timezone.automatic}</option>
<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) => (
<option value={timezone} key={timezone}>{timezone}</option>
<MenuItem value={timezone} key={timezone}>{timezone}</MenuItem>
))}
</Dropdown>
<h3>{advanced.data}</h3>
<button className='reset' onClick={() => this.setState({ resetModal: true })}>{this.language.buttons.reset}</button>
<button className='export' onClick={() => SettingsFunctions.exportSettings()}>{this.language.buttons.export}</button>
<button className='import' onClick={() => document.getElementById('file-input').click()}>{this.language.buttons.import}</button>
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => SettingsFunctions.importSettings(e)}/>
{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>{advanced.customisation}</h3>
<Text title={advanced.tab_name} name='tabName' default={window.language.tabname} category='other'/>
<Text title={advanced.custom_js} name='customjs' textarea={true} category='other' element='other'/>
<Text title={advanced.custom_css} name='customcss' textarea={true} category='other'/>
<h3>{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>{this.language.sections.experimental.title}</h3>
<p style={{ maxWidth: '75%' }}>{advanced.experimental_warning}</p>
<Switch name='experimental' text={this.language.enabled} element='.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}>
<ResetModal modalClose={() => this.setState({ resetModal: false })} />

View File

@@ -1,63 +1,62 @@
import variables from 'modules/variables';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Radio from '../Radio';
import Slider from '../Slider';
import Text from '../Text';
import { values } from 'modules/helpers/settings/modals';
export default function AppearanceSettings() {
const { appearance } = window.language.modals.main.settings.sections;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const themeOptions = [
{
name: appearance.theme.auto,
name: getMessage('modals.main.settings.sections.appearance.theme.auto'),
value: 'auto'
},
{
name: appearance.theme.light,
name: getMessage('modals.main.settings.sections.appearance.theme.light'),
value: 'light'
},
{
name: appearance.theme.dark,
name: getMessage('modals.main.settings.sections.appearance.theme.dark'),
value: 'dark'
}
];
return (
<>
<h2>{appearance.title}</h2>
<Radio name='theme' title={appearance.theme.title} options={themeOptions} category='other' />
<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' />
<h3>{appearance.navbar.title}</h3>
<Checkbox name='notesEnabled' text={appearance.navbar.notes} category='navbar' />
<Checkbox name='refresh' text={appearance.navbar.refresh} category='navbar' />
<h3>{appearance.font.title}</h3>
<Text title={appearance.font.custom} name='font' upperCaseFirst={true} category='other' />
<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={appearance.font.google} category='other' />
<Dropdown label={appearance.font.weight.title} name='fontweight' category='other'>
<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'>{appearance.font.weight.thin}</option>
<option value='200'>{appearance.font.weight.extra_light}</option>
<option value='300'>{appearance.font.weight.light}</option>
<option value='400'>{appearance.font.weight.normal}</option>
<option value='500'>{appearance.font.weight.medium}</option>
<option value='600'>{appearance.font.weight.semi_bold}</option>
<option value='700'>{appearance.font.weight.bold}</option>
<option value='800'>{appearance.font.weight.extra_bold}</option>
<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>
<br/><br/>
<Dropdown label={appearance.font.style.title} name='fontstyle' category='other'>
<option value='normal'>{appearance.font.style.normal}</option>
<option value='italic'>{appearance.font.style.italic}</option>
<option value='oblique'>{appearance.font.style.oblique}</option>
<Dropdown 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>{appearance.accessibility.title}</h3>
{(navigator.userAgent.includes('Chrome') && typeof InstallTrigger === 'undefined') ?
<Slider title={appearance.accessibility.widget_zoom} name='widgetzoom' default='100' step='10' min='50' max='200' display='%' category='other'/>
: null}
<Slider title={appearance.accessibility.toast_duration} name='toastDisplayTime' default='2500' step='100' min='500' max='5000' toast={true} display={' ' + appearance.accessibility.milliseconds} />
<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')} />
</>
);
}

View File

@@ -1,12 +1,11 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { WifiOff } from '@mui/icons-material';
import Modal from 'react-modal';
import Lightbox from '../../marketplace/Lightbox';
import WifiOffIcon from '@material-ui/icons/WifiOff';
export default class Changelog extends React.PureComponent {
export default class Changelog extends PureComponent {
constructor() {
super();
this.state = {
@@ -14,19 +13,20 @@ export default class Changelog extends React.PureComponent {
showLightbox: false,
lightboxImg: null
};
this.language = window.language.modals.update;
this.offlineMode = (localStorage.getItem('offlineMode') === 'true');
this.controller = new AbortController();
this.changelog = createRef();
}
async getUpdate() {
const data = await (await fetch(window.constants.BLOG_POST + '/index.json', { signal: this.controller.signal })).json();
const data = await (await fetch(variables.constants.BLOG_POST + '/index.json', { signal: this.controller.signal })).json();
if (this.controller.signal.aborted === true) {
return;
}
let date = new Date(data.date.split(' ')[0]);
date = date.toLocaleDateString(window.languagecode.replace('_', '-'), {
date = date.toLocaleDateString(variables.languagecode.replace('_', '-'), {
year: 'numeric',
month: 'long',
day: 'numeric'
@@ -34,16 +34,17 @@ export default class Changelog extends React.PureComponent {
this.setState({
title: data.title,
date: date,
date,
image: data.featured_image || null,
author: 'By ' + data.authors.join(', '),
author: variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.changelog.by', {
author: data.authors.join(', ')
}),
html: data.html
});
// lightbox etc
const content = document.querySelector('.tab-content');
const images = content.getElementsByTagName('img');
const links = content.getElementsByTagName('a');
const images = this.changelog.current.getElementsByTagName('img');
const links = this.changelog.current.getElementsByTagName('a');
for (const img of images) {
img.draggable = false;
@@ -55,6 +56,7 @@ export default class Changelog extends React.PureComponent {
};
}
// open in new tab
for (let link = 0; link < links.length; link++) {
links[link].target = '_blank';
links[link].rel = 'noopener noreferrer';
@@ -62,7 +64,7 @@ export default class Changelog extends React.PureComponent {
}
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
if (navigator.onLine === false || this.offlineMode) {
return;
}
@@ -75,6 +77,8 @@ export default class Changelog extends React.PureComponent {
}
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const errorMessage = (msg) => {
return (
<div className='emptyitems'>
@@ -85,25 +89,23 @@ export default class Changelog extends React.PureComponent {
);
};
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
const language = window.language.modals.main.marketplace;
if (navigator.onLine === false || this.offlineMode) {
return errorMessage(<>
<WifiOffIcon/>
<h1>{language.offline.title}</h1>
<p className='description'>{language.offline.description}</p>
<WifiOff/>
<h1>{getMessage('modals.main.marketplace.offline.title')}</h1>
<p className='description'>{getMessage('modals.main.marketplace.offline.description')}</p>
</>);
}
if (!this.state.title) {
return errorMessage(<h1>{window.language.modals.main.loading}</h1>);
return errorMessage(<h1>{getMessage('modals.main.loading')}</h1>);
}
return (
<div className='changelogtab'>
<h1 style={{ marginBottom: '-10px' }}>{this.state.title}</h1>
<h5 style={{ lineHeight: '0px' }}>{this.state.author} {this.state.date}</h5>
{this.state.image ? <img draggable='false' src={this.state.image} alt={window.language.modals.update.title} className='updateimage'/> : null}
<div className='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}/>

View File

@@ -0,0 +1,66 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
export default class DateSettings extends PureComponent {
constructor() {
super();
this.state = {
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' />
</>
);
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>
<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}
</>
);
}
}

View File

@@ -1,28 +1,29 @@
import variables from 'modules/variables';
import { useState } from 'react';
import Checkbox from '../Checkbox';
import Slider from '../Slider';
import EventBus from '../../../../../modules/helpers/eventbus';
import { TextField } from '@mui/material';
import EventBus from 'modules/helpers/eventbus';
import { values } from 'modules/helpers/settings/modals';
export default function ExperimentalSettings() {
const { experimental } = window.language.modals.main.settings.sections;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const [eventType, setEventType] = useState();
const [eventName, setEventName] = useState();
return (
<>
<h2>{experimental.title}</h2>
<p>{experimental.warning}</p>
<Checkbox name='animations' text={window.language.modals.main.settings.sections.appearance.animations} element='.other'/>
<h3>Usage Stats</h3>
<p>Allows you to see stats such as how many tabs you have opened, quotes favourited etc. It also sends this data anonymously to our<a className='modalLink' href='https://github.com/mue/umami'>umami</a> instance.</p>
<Checkbox name='stats' text='Enable Usage Stats' element='.other'/>
<h3>{experimental.developer}</h3>
<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' display=' miliseconds' element='.other' />
<br/>
<Slider title='Debug timeout' name='debugtimeout' min='0' max='5000' default='0' step='100' marks={values('experimental')} element='.other' />
<p>Send Event</p>
Type <input type='text' id='eventType'/>
<br/><br/>
Name <input type='text' id='eventName'/>
<br/><br/>
<button className='uploadbg' onClick={() => EventBus.dispatch(document.getElementById('eventType').value, document.getElementById('eventName').value)}>Send</button>
<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>
</>

View File

@@ -1,44 +1,43 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Switch from '../Switch';
import Text from '../Text';
import Slider from '../Slider';
export default class GreetingSettings extends React.PureComponent {
export default class GreetingSettings extends PureComponent {
constructor() {
super();
this.state = {
birthday: new Date(localStorage.getItem('birthday')) || new Date()
};
this.language = window.language.modals.main.settings;
}
changeDate = (e) => {
localStorage.setItem('birthday', e.target.value);
localStorage.setItem('birthday', e.target.value || new Date());
this.setState({
birthday: new Date(e.target.value)
birthday: e.target.value ? new Date(e.target.value) : new Date()
});
}
render() {
const { greeting } = this.language.sections;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<>
<h2>{greeting.title}</h2>
<Switch name='greeting' text={this.language.enabled} category='greeting' element='.greeting'/>
<Checkbox name='events' text={greeting.events} category='greeting' element='.greeting'/>
<Checkbox name='defaultGreetingMessage' text={greeting.default} category='greeting' element='.greeting'/>
<Text title={greeting.name} name='greetingName' category='greeting' element='.greeting'/>
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomGreeting' min='10' max='400' default='100' display='%' category='greeting' element='.greeting' />
<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>{greeting.birthday}</h3>
<Switch name='birthdayenabled' text={this.language.enabled} category='greeting' element='.greeting'/>
<Checkbox name='birthdayage' text={greeting.birthday_age} category='greeting' element='.greeting'/>
<p>{greeting.birthday_date}</p>
<input type='date' onChange={this.changeDate} value={this.state.birthday.toISOString().substr(0, 10)}/>
<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/>
</>
);
}

View File

@@ -0,0 +1,148 @@
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>
</>
);
}
}

View File

@@ -1,15 +1,18 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Radio from '../Radio';
const languages = require('../../../../../modules/languages.json');
const languages = require('modules/languages.json');
export default class LanguageSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class BackgroundSettings extends React.PureComponent {
constructor() {
super();
this.state = {
quoteLanguages: [{
name: window.language.modals.main.loading,
name: this.getMessage('modals.main.loading'),
value: 'loading'
}]
};
@@ -17,22 +20,22 @@ export default class BackgroundSettings extends React.PureComponent {
}
async getQuoteLanguages() {
const data = await (await fetch(window.constants.API_URL + '/quotes/languages', { signal: this.controller.signal })).json();
const data = await (await fetch(variables.constants.API_URL + '/quotes/languages', { signal: this.controller.signal })).json();
if (this.controller.signal.aborted === true) {
return;
}
let array = [];
const quoteLanguages = [];
data.forEach((item) => {
array.push({
quoteLanguages.push({
name: item,
value: item
});
});
this.setState({
quoteLanguages: array
quoteLanguages
});
}
@@ -40,7 +43,7 @@ export default class BackgroundSettings extends React.PureComponent {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return this.setState({
quoteLanguages: [{
name: window.language.modals.main.marketplace.offline.description,
name: this.getMessage('modals.main.marketplace.offline.description'),
value: 'loading'
}]
});
@@ -55,13 +58,11 @@ export default class BackgroundSettings extends React.PureComponent {
}
render() {
const language = window.language.modals.main.settings.sections.language;
return (
<>
<h2>{language.title}</h2>
<Radio name='language' options={languages} element='.language' />
<h3>{language.quote}</h3>
<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' />
</>
);

View File

@@ -0,0 +1,81 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Cancel, Add } from '@mui/icons-material';
import { toast } from 'react-toastify';
import { TextField } from '@mui/material';
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')) || [''],
};
}
reset = () => {
localStorage.setItem('messages', '[""]');
this.setState({
messages: ['']
});
toast(this.getMessage(this.languagecode, 'toasts.reset'));
EventBus.dispatch('refresh', 'message');
}
modifyMessage(type, index) {
const messages = this.state.messages;
if (type === 'add') {
messages.push('');
} else {
messages.splice(index, 1);
}
this.setState({
messages
});
this.forceUpdate();
localStorage.setItem('messages', JSON.stringify(messages));
}
message(e, text, index) {
const result = (text === true) ? e.target.value : e.target.result;
const messages = this.state.messages;
messages[index] = result;
this.setState({
messages
});
this.forceUpdate();
localStorage.setItem('messages', JSON.stringify(messages));
document.querySelector('.reminder-info').style.display = 'block';
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>
</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}
</div>
))}
<br/>
</>
);
}
}

View File

@@ -0,0 +1,34 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Slider from '../Slider';
import { values } from 'modules/helpers/settings/modals';
export default class Navbar extends PureComponent {
render() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
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>
</>
);
}
}

View File

@@ -1,66 +1,46 @@
import React from 'react';
import EventBus from '../../../../../modules/helpers/eventbus';
import DragHandleIcon from '@material-ui/icons/DragIndicator';
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';
const enabled = (setting) => {
switch (setting) {
case 'quicklinks':
return (localStorage.getItem('quicklinksenabled') === 'true');
default:
return (localStorage.getItem(setting) === 'true');
}
};
import EventBus from 'modules/helpers/eventbus';
const settings = window.language.modals.main.settings.sections;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const widget_name = {
greeting: settings.greeting.title,
time: settings.time.title,
quicklinks: settings.quicklinks.title,
quote: settings.quote.title,
date: settings.time.date.title
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' style={{ display: enabled(value) ? 'block' : 'none' }}>
<DragHandleIcon style={{ verticalAlign: 'middle' }} />
<li className='sortableitem'>
<DragIndicator style={{ verticalAlign: 'middle' }} />
{widget_name[value]}
</li>
));
const SortableContainer = sortableContainer(({ children }) => {
return <ul className='sortablecontainer'>{children}</ul>;
});
const SortableContainer = sortableContainer(({ children }) => (
<ul className='sortablecontainer'>{children}</ul>
));
export default class OrderSettings extends React.PureComponent {
export default class OrderSettings extends PureComponent {
constructor() {
super();
this.state = {
items: JSON.parse(localStorage.getItem('order'))
};
this.language = window.language.modals.main.settings;
}
// based on https://stackoverflow.com/a/48301905
arrayMove(array, oldIndex, newIndex) {
if (oldIndex === newIndex) {
return array;
}
const result = Array.from(array);
const [removed] = result.splice(oldIndex, 1);
result.splice(newIndex, 0, removed);
const newArray = [...array];
const target = newArray[oldIndex];
const inc = newIndex < oldIndex ? -1 : 1;
for (let i = oldIndex; i !== newIndex; i += inc) {
newArray[i] = newArray[i + inc];
}
newArray[newIndex] = target;
return newArray;
return result;
}
onSortEnd = ({ oldIndex, newIndex }) => {
@@ -70,30 +50,45 @@ export default class OrderSettings extends React.PureComponent {
}
reset = () => {
localStorage.setItem('order', JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date']));
localStorage.setItem('order', JSON.stringify(['greeting', 'time', 'quicklinks', 'quote', 'date', 'message']));
this.setState({
items: JSON.parse(localStorage.getItem('order'))
});
toast(window.language.toasts.reset);
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));
window.stats.postEvent('setting', 'Widget order');
variables.stats.postEvent('setting', 'Widget order');
EventBus.dispatch('refresh', 'widgets');
}
render() {
return (
<>
<h2>{this.language.sections.order.title}</h2>
<span className='modalLink' onClick={this.reset}>{this.language.buttons.reset}</span>
<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) => (
<SortableItem key={`item-${value}`} index={index} value={value} />
))}
{this.state.items.map((value, index) => {
if (!this.enabled(value)) {
return null;
}
return (
<SortableItem key={`item-${value}`} index={index} value={value} />
);
})}
</SortableContainer>
</>
);

View File

@@ -1,18 +1,20 @@
import Switch from '../Switch';
import variables from 'modules/variables';
import { useState } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Slider from '../Slider';
export default function QuickLinks() {
const language = window.language.modals.main.settings.sections.quicklinks;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const [textOnly, setTextOnly] = useState(localStorage.getItem('quicklinksText') === 'true');
return (
<>
<h2>{language.title}</h2>
<Switch name='quicklinksenabled' text={window.language.modals.main.settings.enabled} category='quicklinks' element='.quicklinks-container' />
<Checkbox name='quicklinksddgProxy' text={window.language.modals.main.settings.sections.background.ddg_image_proxy} element='.other' />
<Checkbox name='quicklinksnewtab' text={language.open_new} category='quicklinks' />
<Checkbox name='quicklinkstooltip' text={language.tooltip} category='quicklinks' />
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuicklinks' min='10' max='400' default='100' display='%' category='quicklinks' element='.quicklinks-container' />
<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}/>
</>
);
}

View File

@@ -1,49 +1,118 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Cancel, Add } from '@mui/icons-material';
import { TextField } from '@mui/material';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Text from '../Text';
import Switch from '../Switch';
import Slider from '../Slider';
import Dropdown from '../Dropdown';
export default class QuoteSettings extends React.PureComponent {
export default class QuoteSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
quoteType: localStorage.getItem('quoteType') || 'api',
customQuote: this.getCustom()
};
}
marketplaceType = () => {
if (localStorage.getItem('quote_packs')) {
return <option value='quote_pack'>{window.language.modals.main.navbar.marketplace}</option>;
return <option value='quote_pack'>{this.getMessage('modals.main.navbar.marketplace')}</option>;
}
}
render() {
const { quote, background } = window.language.modals.main.settings.sections;
resetCustom = () => {
localStorage.setItem('customQuote', '[{"quote": "", "author": ""}]');
this.setState({
customQuote: [{
quote: '',
author: ''
}]
});
toast(this.getMessage('toasts.reset'));
EventBus.dispatch('refresh', 'background');
}
customQuote(e, text, index, type) {
const result = (text === true) ? e.target.value : e.target.result;
const customQuote = this.state.customQuote;
customQuote[index][type] = result;
this.setState({
customQuote
});
this.forceUpdate();
localStorage.setItem('customQuote', JSON.stringify(customQuote));
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
}
modifyCustomQuote(type, index) {
const customQuote = this.state.customQuote;
if (type === 'add') {
customQuote.push({
quote: '',
author: ''
});
} else {
customQuote.splice(index, 1);
}
this.setState({
customQuote
});
this.forceUpdate();
localStorage.setItem('customQuote', JSON.stringify(customQuote));
}
getCustom() {
let data = JSON.parse(localStorage.getItem('customQuote'));
if (data === null) {
data = [{
quote: localStorage.getItem('customQuote') || '',
author: localStorage.getItem('customQuoteAuthor') || ''
}];
}
return data;
}
render() {
let customSettings;
if (this.state.quoteType === 'custom') {
customSettings = (
<>
<Text title={quote.custom} name='customQuote' category='quote' element='.quotediv' />
<Text title={quote.custom_author} name='customQuoteAuthor' category='quote' element='.quotediv'/>
<p>{this.getMessage('modals.main.settings.sections.quote.custom')} <span className='modalLink' onClick={this.resetCustom}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<div className='data-buttons-row'>
<button onClick={() => this.modifyCustomQuote('add')}>{this.getMessage('modals.main.settings.sections.quote.add')} <Add/></button>
</div>
{this.state.customQuote.map((_url, index) => (
<div style={{ display: 'flex' }} key={index}>
<TextField value={this.state.customQuote[index].quote} placeholder='Quote' onChange={(e) => this.customQuote(e, true, index, 'quote')} varient='outlined' style={{ marginRight: '10px' }} />
<TextField value={this.state.customQuote[index].author} placeholder='Author' onChange={(e) => this.customQuote(e, true, index, 'author')} varient='outlined' />
{this.state.customQuote.length > 1 ? <button className='cleanButton' onClick={() => this.modifyCustomQuote('remove', index)} style={{ marginBottom: '-14px' }}>
<Cancel/>
</button> : null}
</div>
))}
</>
);
} else {
// api
customSettings = (
<>
<br/><br/>
<Dropdown label={background.interval.title} name='quotechange'>
<option value='refresh'>{window.language.tabname}</option>
<option value='60000'>{background.interval.minute}</option>
<option value='1800000'>{background.interval.half_hour}</option>
<option value='3600000'>{background.interval.hour}</option>
<option value='86400000'>{background.interval.day}</option>
<option value='604800000'>{window.language.widgets.date.week}</option>
<option value='2628000000'>{background.interval.month}</option>
<Dropdown label={this.getMessage('modals.main.settings.sections.background.interval.title')} name='quotechange'>
<option value='refresh'>{this.getMessage('tabname')}</option>
<option value='60000'>{this.getMessage('modals.main.settings.sections.background.interval.minute')}</option>
<option value='1800000'>{this.getMessage('modals.main.settings.sections.background.interval.half_hour')}</option>
<option value='3600000'>{this.getMessage('modals.main.settings.sections.background.interval.hour')}</option>
<option value='86400000'>{this.getMessage('modals.main.settings.sections.background.interval.day')}</option>
<option value='604800000'>{this.getMessage('widgets.date.week')}</option>
<option value='2628000000'>{this.getMessage('modals.main.settings.sections.background.interval.month')}</option>
</Dropdown>
</>
);
@@ -51,22 +120,20 @@ export default class QuoteSettings extends React.PureComponent {
return (
<>
<h2>{quote.title}</h2>
<Switch name='quote' text={window.language.modals.main.settings.enabled} category='quote' element='.quotediv' />
<Checkbox name='authorLink' text={quote.author_link} element='.other' />
<Dropdown label={window.language.modals.main.settings.sections.background.type.title} name='quoteType' onChange={(value) => this.setState({ quoteType: value })} category='quote'>
<Header title={this.getMessage('modals.main.settings.sections.quote.title')} setting='quote' category='quote' element='.quotediv' zoomSetting='zoomQuote'/>
<Checkbox name='authorLink' text={this.getMessage('modals.main.settings.sections.quote.author_link')} element='.other' />
<Dropdown label={this.getMessage('modals.main.settings.sections.background.type.title')} name='quoteType' onChange={(value) => this.setState({ quoteType: value })} category='quote'>
{this.marketplaceType()}
<option value='api'>{window.language.modals.main.settings.sections.background.type.api}</option>
<option value='custom'>{quote.custom}</option>
<option value='api'>{this.getMessage('modals.main.settings.sections.background.type.api')}</option>
<option value='custom'>{this.getMessage('modals.main.settings.sections.quote.custom')}</option>
</Dropdown>
{customSettings}
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomQuote' min='10' max='400' default='100' display='%' category='quote' element='.quotediv' />
<h3>{quote.buttons.title}</h3>
<Checkbox name='copyButton' text={quote.buttons.copy} category='quote'/>
<Checkbox name='tweetButton' text={quote.buttons.tweet} category='quote'/>
<Checkbox name='favouriteQuoteEnabled' text={quote.buttons.favourite} category='quote'/>
<h3>{this.getMessage('modals.main.settings.sections.quote.buttons.title')}</h3>
<Checkbox name='copyButton' text={this.getMessage('modals.main.settings.sections.quote.buttons.copy')} category='quote'/>
<Checkbox name='tweetButton' text={this.getMessage('modals.main.settings.sections.quote.buttons.tweet')} category='quote'/>
<Checkbox name='favouriteQuoteEnabled' text={this.getMessage('modals.main.settings.sections.quote.buttons.favourite')} category='quote'/>
</>
);
}
}
}

View File

@@ -1,18 +1,21 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { MenuItem, TextField } from '@mui/material';
import Header from '../Header';
import Dropdown from '../Dropdown';
import Checkbox from '../Checkbox';
import Switch from '../Switch';
import Radio from '../Radio';
import EventBus from '../../../../../modules/helpers/eventbus';
import EventBus from 'modules/helpers/eventbus';
import { toast } from 'react-toastify';
const searchEngines = require('components/widgets/search/search_engines.json');
const autocompleteProviders = require('components/widgets/search/autocomplete_providers.json');
const searchEngines = require('../../../../widgets/search/search_engines.json');
const autocompleteProviders = require('../../../../widgets/search/autocomplete_providers.json');
export default class SearchSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class SearchSettings extends React.PureComponent {
constructor() {
super();
this.state = {
@@ -28,7 +31,7 @@ export default class SearchSettings extends React.PureComponent {
customValue: ''
});
toast(window.language.toasts.reset);
toast(this.getMessage('toasts.reset'));
}
componentDidMount() {
@@ -68,30 +71,26 @@ export default class SearchSettings extends React.PureComponent {
}
render() {
const language = window.language.modals.main.settings;
const { search } = language.sections;
return (
<>
<h2>{search.title}</h2>
<Switch name='searchBar' text={language.enabled} category='widgets' />
<Header title={this.getMessage('modals.main.settings.sections.search.title')} setting='searchBar' category='widgets'/>
{/* not supported on firefox */}
{(navigator.userAgent.includes('Chrome') && typeof InstallTrigger === 'undefined') ?
<Checkbox name='voiceSearch' text={search.voice_search} category='search'/>
<Checkbox name='voiceSearch' text={this.getMessage('modals.main.settings.sections.search.voice_search')} category='search'/>
: null}
<Dropdown label={search.search_engine} name='searchEngine' onChange={(value) => this.setSearchEngine(value)}>
<Checkbox name='searchDropdown' text={this.getMessage('modals.main.settings.sections.search.dropdown')} category='search' element='.other'/>
<Dropdown label={this.getMessage('modals.main.settings.sections.search.search_engine')} name='searchEngine' onChange={(value) => this.setSearchEngine(value)} manual={true}>
{searchEngines.map((engine) => (
<option key={engine.name} value={engine.settingsName}>{engine.name}</option>
<MenuItem key={engine.name} value={engine.settingsName}>{engine.name}</MenuItem>
))}
<option value='custom'>{search.custom.split(' ')[0]}</option>
<MenuItem value='custom'>{this.getMessage('modals.main.settings.sections.search.custom').split(' ')[0]}</MenuItem>
</Dropdown>
<ul style={{ display: this.state.customDisplay }}>
<br/>
<p style={{ marginTop: '0px' }}>{search.custom} <span className='modalLink' onClick={() => this.resetSearch()}>{language.buttons.reset}</span></p>
<input type='text' value={this.state.customValue} onInput={(e) => this.setState({ customValue: e.target.value })}></input>
<p style={{ marginTop: '0px' }}><span className='modalLink' onClick={() => this.resetSearch()}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<TextField label={this.getMessage('modals.main.settings.sections.search.custom')} value={this.state.customValue} onInput={(e) => this.setState({ customValue: e.target.value })} varient='outlined' InputLabelProps={{ shrink: true }} />
</ul>
<br/>
<Checkbox name='autocomplete' text={search.autocomplete} category='search' element='.other'/>
<Radio title={search.autocomplete_provider} options={autocompleteProviders} name='autocompleteProvider' category='search'/>
<Checkbox name='autocomplete' text={this.getMessage('modals.main.settings.sections.search.autocomplete')} category='search' />
<Radio title={this.getMessage('modals.main.settings.sections.search.autocomplete_provider')} options={autocompleteProviders} name='autocompleteProvider' category='search'/>
</>
);
}

View File

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

View File

@@ -1,55 +1,52 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Header from '../Header';
import Checkbox from '../Checkbox';
import Dropdown from '../Dropdown';
import Switch from '../Switch';
import Radio from '../Radio';
import Slider from '../Slider';
export default class TimeSettings extends React.PureComponent {
export default class TimeSettings extends PureComponent {
constructor() {
super();
this.state = {
timeType: localStorage.getItem('timeType') || 'digital',
dateType: localStorage.getItem('dateType') || 'long'
timeType: localStorage.getItem('timeType') || 'digital'
};
this.language = window.language.modals.main.settings;
}
render() {
const { time } = this.language.sections;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
let timeSettings;
const digitalOptions = [
{
name: time.digital.twentyfourhour,
name: getMessage('modals.main.settings.sections.time.digital.twentyfourhour'),
value: 'twentyfourhour'
},
{
name: time.digital.twelvehour,
name: getMessage('modals.main.settings.sections.time.digital.twelvehour'),
value: 'twelvehour'
}
];
const digitalSettings = (
<>
<h3>{time.digital.title}</h3>
<Radio title={time.format} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' element='.clock-container' />
<br/>
<Checkbox name='seconds' text={time.digital.seconds} category='clock' element='.clock-container' />
<Checkbox name='zero' text={time.digital.zero} category='clock' element='.clock-container' />
<h3>{getMessage('modals.main.settings.sections.time.digital.title')}</h3>
<Radio title={getMessage('modals.main.settings.sections.time.format')} name='timeformat' options={digitalOptions} smallTitle={true} category='clock' />
<Checkbox name='seconds' text={getMessage('modals.main.settings.sections.time.digital.seconds')} category='clock' />
<Checkbox name='zero' text={getMessage('modals.main.settings.sections.time.digital.zero')} category='clock' />
</>
);
const analogSettings = (
<>
<h3>{time.analogue.title}</h3>
<Checkbox name='secondHand' text={time.analogue.second_hand} category='clock' element='.clock-container' />
<Checkbox name='minuteHand' text={time.analogue.minute_hand} category='clock' element='.clock-container' />
<Checkbox name='hourHand' text={time.analogue.hour_hand} category='clock' element='.clock-container' />
<Checkbox name='hourMarks' text={time.analogue.hour_marks} category='clock' element='.clock-container' />
<Checkbox name='minuteMarks' text={time.analogue.minute_marks} category='clock' element='.clock-container' />
<h3>{getMessage('modals.main.settings.sections.time.analogue.title')}</h3>
<Checkbox name='secondHand' text={getMessage('modals.main.settings.sections.time.analogue.second_hand')} category='clock' />
<Checkbox name='minuteHand' text={getMessage('modals.main.settings.sections.time.analogue.minute_hand')} category='clock' />
<Checkbox name='hourHand' text={getMessage('modals.main.settings.sections.time.analogue.hour_hand')} category='clock' />
<Checkbox name='hourMarks' text={getMessage('modals.main.settings.sections.time.analogue.hour_marks')} category='clock' />
<Checkbox name='minuteMarks' text={getMessage('modals.main.settings.sections.time.analogue.minute_marks')} category='clock' />
</>
);
@@ -59,64 +56,15 @@ export default class TimeSettings extends React.PureComponent {
default: timeSettings = null; break;
}
let dateSettings;
const longSettings = (
<>
<Checkbox name='dayofweek' text={time.date.day_of_week} category='date' element='.date' />
<Checkbox name='datenth' text={time.date.datenth} category='date' element='.date' />
</>
);
const shortSettings = (
<>
<br/>
<Dropdown label={time.date.short_format} name='dateFormat' category='date' element='.date'>
<option value='DMY'>DMY</option>
<option value='MDY'>MDY</option>
<option value='YMD'>YMD</option>
</Dropdown>
<br/><br/>
<Dropdown label={time.date.short_separator.title} name='shortFormat' category='date' element='.date'>
<option value='dots'>{time.date.short_separator.dots}</option>
<option value='dash'>{time.date.short_separator.dash}</option>
<option value='gaps'>{time.date.short_separator.gaps}</option>
<option value='slashes'>{time.date.short_separator.slashes}</option>
</Dropdown>
</>
);
switch (this.state.dateType) {
case 'short': dateSettings = shortSettings; break;
case 'long': dateSettings = longSettings; break;
default: break;
}
return (
<>
<h2>{time.title}</h2>
<Switch name='time' text={this.language.enabled} category='clock' element='.clock-container' />
<Dropdown label={time.type} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock' element='.clock-container'>
<option value='digital'>{time.digital.title}</option>
<option value='analogue'>{time.analogue.title}</option>
<option value='percentageComplete'>{time.percentage_complete}</option>
<Header title={getMessage('modals.main.settings.sections.time.title')} setting='time' category='clock' element='.clock-container' zoomSetting='zoomClock'/>
<Dropdown label={getMessage('modals.main.settings.sections.time.type')} name='timeType' onChange={(value) => this.setState({ timeType: value })} category='clock'>
<option value='digital'>{getMessage('modals.main.settings.sections.time.digital.title')}</option>
<option value='analogue'>{getMessage('modals.main.settings.sections.time.analogue.title')}</option>
<option value='percentageComplete'>{getMessage('modals.main.settings.sections.time.percentage_complete')}</option>
</Dropdown>
{timeSettings}
{this.state.timeType !== 'analogue' ?
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomClock' min='10' max='400' default='100' display='%' category='clock' element='.clock-container' />
: null}
<h3>{time.date.title}</h3>
<Switch name='date' text={this.language.enabled} category='date' element='.date'/>
<Dropdown label={time.type} name='dateType' onChange={(value) => this.setState({ dateType: value })} category='date' element='.date'>
<option value='long'>{time.date.type.long}</option>
<option value='short'>{time.date.type.short}</option>
</Dropdown>
<br/>
<Checkbox name='datezero' text={time.digital.zero} category='date' element='.date' />
<Checkbox name='weeknumber' text={time.date.week_number} category='date' element='.date'/>
{dateSettings}
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomDate' min='10' max='400' default='100' display='%' category='date' element='.date' />
</>
);
}

View File

@@ -1,41 +1,45 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Switch from '../Switch';
import Header from '../Header';
import Radio from '../Radio';
import Checkbox from '../Checkbox';
import Slider from '../Slider';
import { TextField } from '@mui/material';
export default class TimeSettings extends React.PureComponent {
export default class TimeSettings extends PureComponent {
constructor() {
super();
this.state = {
location: localStorage.getItem('location') || 'London'
location: localStorage.getItem('location') || '',
windSpeed: (localStorage.getItem('windspeed') !== 'true')
};
this.language = window.language.modals.main.settings;
}
componentDidUpdate() {
localStorage.setItem('location', this.state.location);
}
showReminder() {
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
}
changeLocation(e) {
this.setState({
location: e.target.value
});
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
this.showReminder();
}
getAuto() {
navigator.geolocation.getCurrentPosition(async (position) => {
const data = await (await fetch(`${window.constants.PROXY_URL}/weather/autolocation?lat=${position.coords.latitude}&lon=${position.coords.longitude}`)).json();
const data = await (await fetch(`${variables.constants.PROXY_URL}/weather/autolocation?lat=${position.coords.latitude}&lon=${position.coords.longitude}`)).json();
this.setState({
location: data[0].name
});
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
this.showReminder();
}, (error) => {
// firefox requires this 2nd function
console.log(error);
@@ -45,46 +49,41 @@ export default class TimeSettings extends React.PureComponent {
}
render() {
const language = window.language.modals.main.settings.sections.weather;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
const tempFormat = [
{
name: language.temp_format.celsius + ' (°C)',
name: getMessage('modals.main.settings.sections.weather.temp_format.celsius') + ' (°C)',
value: 'celsius'
},
{
name: language.temp_format.fahrenheit + ' (°F)',
name: getMessage('modals.main.settings.sections.weather.temp_format.fahrenheit') + ' (°F)',
value: 'fahrenheit'
},
{
name: language.temp_format.kelvin + ' (K)',
name: getMessage('modals.main.settings.sections.weather.temp_format.kelvin') + ' (K)',
value: 'kelvin'
}
];
return (
<>
<h2>{language.title}</h2>
<Switch name='weatherEnabled' text={this.language.enabled} category='widgets'/>
<ul>
<p>{language.location} <span className='modalLink' onClick={() => this.getAuto()}>{language.auto}</span></p>
<input type='text' value={this.state.location} onChange={(e) => this.changeLocation(e)}></input>
</ul>
<br/>
<Radio name='tempformat' title={language.temp_format.title} options={tempFormat} category='weather'/>
<Slider title={window.language.modals.main.settings.sections.appearance.accessibility.widget_zoom} name='zoomWeather' min='10' max='400' default='100' display='%' category='weather'/>
<Header title={getMessage('modals.main.settings.sections.weather.title')} setting='weatherEnabled' category='widgets' zoomSetting='zoomWeather' zoomCategory='weather'/>
<TextField label={getMessage('modals.main.settings.sections.weather.location')} value={this.state.location} onChange={(e) => this.changeLocation(e)} placeholder='London' varient='outlined' InputLabelProps={{ shrink: true }} />
<span className='modalLink' onClick={() => this.getAuto()}>{getMessage('modals.main.settings.sections.weather.auto')}</span>
<Radio name='tempformat' title={getMessage('modals.main.settings.sections.weather.temp_format.title')} options={tempFormat} category='weather'/>
<h3>{language.extra_info.title}</h3>
<Checkbox name='showlocation' text={language.extra_info.show_location} category='weather'/>
<Checkbox name='weatherdescription' text={language.extra_info.show_description} category='weather'/>
<Checkbox name='cloudiness' text={language.extra_info.cloudiness} category='weather'/>
<Checkbox name='humidity' text={language.extra_info.humidity} category='weather'/>
<Checkbox name='visibility' text={language.extra_info.visibility} category='weather'/>
<Checkbox name='windspeed' text={language.extra_info.wind_speed} category='weather'/>
<Checkbox name='windDirection' text={language.extra_info.wind_direction} category='weather'/>
<Checkbox name='mintemp' text={language.extra_info.min_temp} category='weather'/>
<Checkbox name='maxtemp' text={language.extra_info.max_temp} category='weather'/>
<Checkbox name='atmosphericpressure' text={language.extra_info.atmospheric_pressure} category='weather'/>
<h3>{getMessage('modals.main.settings.sections.weather.extra_info.title')}</h3>
<Checkbox name='showlocation' text={getMessage('modals.main.settings.sections.weather.extra_info.show_location')} category='weather'/>
<Checkbox name='weatherdescription' text={getMessage('modals.main.settings.sections.weather.extra_info.show_description')} category='weather'/>
<Checkbox name='cloudiness' text={getMessage('modals.main.settings.sections.weather.extra_info.cloudiness')} category='weather'/>
<Checkbox name='humidity' text={getMessage('modals.main.settings.sections.weather.extra_info.humidity')} category='weather'/>
<Checkbox name='visibility' text={getMessage('modals.main.settings.sections.weather.extra_info.visibility')} category='weather'/>
<Checkbox name='windspeed' text={getMessage('modals.main.settings.sections.weather.extra_info.wind_speed')} category='weather' onChange={() => this.setState({ windSpeed: (localStorage.getItem('windspeed') !== 'true') })}/>
<Checkbox name='windDirection' text={getMessage('modals.main.settings.sections.weather.extra_info.wind_direction')} category='weather' disabled={this.state.windSpeed}/>
<Checkbox name='mintemp' text={getMessage('modals.main.settings.sections.weather.extra_info.min_temp')} category='weather'/>
<Checkbox name='maxtemp' text={getMessage('modals.main.settings.sections.weather.extra_info.max_temp')} category='weather'/>
<Checkbox name='atmosphericpressure' text={getMessage('modals.main.settings.sections.weather.extra_info.atmospheric_pressure')} category='weather'/>
</>
);
}

View File

@@ -1,71 +1,35 @@
import React from 'react';
import EventBus from '../../../../../../modules/helpers/eventbus';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { MenuItem } from '@mui/material';
import Header from '../../Header';
import Checkbox from '../../Checkbox';
import Dropdown from '../../Dropdown';
import FileUpload from '../../FileUpload';
import Slider from '../../Slider';
import Switch from '../../Switch';
import Radio from '../../Radio';
import ColourSettings from './Colour';
import CustomSettings from './Custom';
import { toast } from 'react-toastify';
import { values } from 'modules/helpers/settings/modals';
export default class BackgroundSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class BackgroundSettings extends React.PureComponent {
constructor() {
super();
this.state = {
customBackground: localStorage.getItem('customBackground') || '',
backgroundType: localStorage.getItem('backgroundType') || 'api',
backgroundCategories: [window.language.modals.main.loading]
backgroundFilter: localStorage.getItem('backgroundFilter') || 'none',
backgroundCategories: [this.getMessage('modals.main.loading')],
backgroundAPI: localStorage.getItem('backgroundAPI') || 'mue',
marketplaceEnabled: localStorage.getItem('photo_packs')
};
this.language = window.language.modals.main.settings;
this.controller = new AbortController();
}
resetCustom = () => {
localStorage.setItem('customBackground', '');
this.setState({
customBackground: ''
});
toast(window.language.toasts.reset);
EventBus.dispatch('refresh', 'background');
}
customBackground(e, text) {
const result = (text === true) ? e.target.value : e.target.result;
localStorage.setItem('customBackground', result);
this.setState({
customBackground: result
});
EventBus.dispatch('refresh', 'background');
}
videoCustomSettings = () => {
const customBackground = this.state.customBackground;
if (customBackground.startsWith('data:video/') || customBackground.endsWith('.mp4') || customBackground.endsWith('.webm') || customBackground.endsWith('.ogg')) {
return (
<>
<Checkbox name='backgroundVideoLoop' text={this.language.sections.background.source.loop_video}/>
<Checkbox name='backgroundVideoMute' text={this.language.sections.background.source.mute_video}/>
</>
);
} else {
return null;
}
}
marketplaceType = () => {
if (localStorage.getItem('photo_packs')) {
return <option value='photo_pack'>{window.language.modals.main.navbar.marketplace}</option>;
}
}
async getBackgroundCategories() {
const data = await (await fetch(window.constants.API_URL + '/images/categories', { signal: this.controller.signal })).json();
const data = await (await fetch(variables.constants.API_URL + '/images/categories', { signal: this.controller.signal })).json();
if (this.controller.signal.aborted === true) {
return;
@@ -79,7 +43,7 @@ export default class BackgroundSettings extends React.PureComponent {
componentDidMount() {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
return this.setState({
backgroundCategories: [window.language.modals.update.offline.title]
backgroundCategories: [this.getMessage('modals.update.offline.title')]
});
}
@@ -92,7 +56,7 @@ export default class BackgroundSettings extends React.PureComponent {
}
render() {
const { background } = this.language.sections;
const { getMessage } = this;
let backgroundSettings;
@@ -111,94 +75,104 @@ export default class BackgroundSettings extends React.PureComponent {
}
];
const APISettings = (
<>
<br/>
<Radio title={background.source.api} options={apiOptions} name='backgroundAPI' category='background' element='#backgroundImage'/>
<br/>
<Dropdown label={background.category} name='apiCategory'>
{this.state.backgroundCategories.map((category) => (
<option value={category} key={category}>{category.charAt(0).toUpperCase() + category.slice(1)}</option>
))}
</Dropdown>
<br/><br/>
<Dropdown label={background.source.quality.title} name='apiQuality' category='background' element='.other'>
<option value='original'>{background.source.quality.original}</option>
<option value='high'>{background.source.quality.high}</option>
<option value='normal'>{background.source.quality.normal}</option>
<option value='datasaver'>{background.source.quality.datasaver}</option>
</Dropdown>
<br/><br/>
<Dropdown label={background.interval.title} name='backgroundchange'>
<option value='refresh'>{window.language.tabname}</option>
<option value='60000'>{background.interval.minute}</option>
<option value='1800000'>{background.interval.half_hour}</option>
<option value='3600000'>{background.interval.hour}</option>
<option value='86400000'>{background.interval.day}</option>
<option value='604800000'>{window.language.widgets.date.week}</option>
<option value='2628000000'>{background.interval.month}</option>
</Dropdown>
</>
const interval = (
<Dropdown label={getMessage('modals.main.settings.sections.background.interval.title')} name='backgroundchange'>
<option value='refresh'>{getMessage('tabname')}</option>
<option value='60000'>{getMessage('modals.main.settings.sections.background.interval.minute')}</option>
<option value='1800000'>{getMessage('modals.main.settings.sections.background.interval.half_hour')}</option>
<option value='3600000'>{getMessage('modals.main.settings.sections.background.interval.hour')}</option>
<option value='86400000'>{getMessage('modals.main.settings.sections.background.interval.day')}</option>
<option value='604800000'>{getMessage('widgets.date.week')}</option>
<option value='2628000000'>{getMessage('modals.main.settings.sections.background.interval.month')}</option>
</Dropdown>
);
const customSettings = (
const APISettings = (
<>
<ul>
<p>{background.source.custom_background} <span className='modalLink' onClick={this.resetCustom}>{this.language.buttons.reset}</span></p>
<input type='text' value={this.state.customBackground} onChange={(e) => this.customBackground(e, true)}></input>
<span className='modalLink' onClick={() => document.getElementById('bg-input').click()}>{background.source.upload}</span>
<FileUpload id='bg-input' accept='image/jpeg, image/png, image/webp, image/webm, image/gif, video/mp4, video/webm, video/ogg' loadFunction={(e) => this.customBackground(e)} />
</ul>
{this.videoCustomSettings()}
<Radio title={getMessage('modals.main.settings.sections.background.source.api')} options={apiOptions} name='backgroundAPI' category='background' element='#backgroundImage' onChange={(e) => this.setState({ backgroundAPI: e })}/>
{this.state.backgroundCategories[0] === getMessage('modals.main.loading') ?
<>
<Dropdown label={getMessage('modals.main.settings.sections.background.category')} name='apiCategory'>
<MenuItem value='loading' key='loading'>{getMessage('modals.main.loading')}</MenuItem>
<MenuItem value='loading' key='loading'>{getMessage('modals.main.loading')}</MenuItem>
</Dropdown>
</> :
<Dropdown label={getMessage('modals.main.settings.sections.background.category')} name='apiCategory' >
{this.state.backgroundCategories.map((category) => (
<MenuItem value={category} key={category}>{category.charAt(0).toUpperCase() + category.slice(1)}</MenuItem>
))}
</Dropdown>
}
<Dropdown label={getMessage('modals.main.settings.sections.background.source.quality.title')} name='apiQuality' element='.other'>
<option value='original'>{getMessage('modals.main.settings.sections.background.source.quality.original')}</option>
<option value='high'>{getMessage('modals.main.settings.sections.background.source.quality.high')}</option>
<option value='normal'>{getMessage('modals.main.settings.sections.background.source.quality.normal')}</option>
<option value='datasaver'>{getMessage('modals.main.settings.sections.background.source.quality.datasaver')}</option>
</Dropdown>
{interval}
</>
);
switch (this.state.backgroundType) {
case 'custom': backgroundSettings = customSettings; break;
case 'custom': backgroundSettings = <CustomSettings interval={interval}/>; break;
case 'colour': backgroundSettings = <ColourSettings/>; break;
case 'random_colour': backgroundSettings = <></>; break;
case 'random_gradient': backgroundSettings = <></>; break;
default: backgroundSettings = APISettings; break;
}
if (localStorage.getItem('photo_packs') && this.state.backgroundType !== 'custom' && this.state.backgroundType !== 'colour' && this.state.backgroundType !== 'api') {
backgroundSettings = null;
}
const usingImage = this.state.backgroundType !== 'colour' && this.state.backgroundType !== 'random_colour' && this.state.backgroundType !== 'random_gradient';
return (
<>
<h2>{background.title}</h2>
<Switch name='background' text={this.language.enabled} category='background' element='#backgroundImage' />
<Checkbox name='ddgProxy' text={background.ddg_image_proxy} />
<Checkbox name='bgtransition' text={background.transition} />
<Checkbox name='photoInformation' text={background.photo_information} category='background' element='.other' />
<Header title={getMessage('modals.main.settings.sections.background.title')} setting='background' category='background' element='#backgroundImage'/>
<Checkbox name='ddgProxy' text={getMessage('modals.main.settings.sections.background.ddg_image_proxy')} element='.other' disabled={!usingImage} />
<Checkbox name='bgtransition' text={getMessage('modals.main.settings.sections.background.transition')} element='.other' disabled={!usingImage} />
<Checkbox name='photoInformation' text={getMessage('modals.main.settings.sections.background.photo_information')} element='.other' disabled={this.state.backgroundType !== 'api' && this.state.backgroundType !== 'marketplace'} />
<Checkbox name='photoMap' text={getMessage('modals.main.settings.sections.background.show_map')} element='.other' disabled={this.state.backgroundAPI !== 'unsplash'}/>
<h3>{background.source.title}</h3>
<Dropdown label={background.type.title} name='backgroundType' onChange={(value) => this.setState({ backgroundType: value })} category='background'>
{this.marketplaceType()}
<option value='api'>{background.type.api}</option>
<option value='custom'>{background.type.custom_image}</option>
<option value='colour'>{background.type.custom_colour}</option>
<h3>{getMessage('modals.main.settings.sections.background.source.title')}</h3>
<Dropdown label={getMessage('modals.main.settings.sections.background.type.title')} name='backgroundType' onChange={(value) => this.setState({ backgroundType: value })} category='background'>
{this.state.marketplaceEnabled ? <option value='photo_pack'>{this.getMessage('modals.main.navbar.marketplace')}</option> : null}
<option value='api'>{getMessage('modals.main.settings.sections.background.type.api')}</option>
<option value='custom'>{getMessage('modals.main.settings.sections.background.type.custom_image')}</option>
<option value='colour'>{getMessage('modals.main.settings.sections.background.type.custom_colour')}</option>
<option value='random_colour'>{getMessage('modals.main.settings.sections.background.type.random_colour')}</option>
<option value='random_gradient'>{getMessage('modals.main.settings.sections.background.type.random_gradient')}</option>
</Dropdown>
<br/>
{backgroundSettings}
<h3>{background.buttons.title}</h3>
<Checkbox name='view' text={background.buttons.view} category='navbar' />
<Checkbox name='favouriteEnabled' text={background.buttons.favourite} category='navbar' />
<Checkbox name='downloadbtn' text={background.buttons.download} element='.other' />
<h3>{background.effects.title}</h3>
<Slider title={background.effects.blur} name='blur' min='0' max='100' default='0' display='%' category='background' element='#backgroundImage' />
<Slider title={background.effects.brightness} name='brightness' min='0' max='100' default='90' display='%' category='background' element='#backgroundImage' />
<br/><br/>
<Dropdown label={background.effects.filters.title} name='backgroundFilter' category='background' element='#backgroundImage'>
<option value='grayscale'>{background.effects.filters.grayscale}</option>
<option value='sepia'>{background.effects.filters.sepia}</option>
<option value='invert'>{background.effects.filters.invert}</option>
<option value='saturate'>{background.effects.filters.saturate}</option>
<option value='contrast'>{background.effects.filters.contrast}</option>
</Dropdown>
<Slider title={background.effects.filters.amount} name='backgroundFilterAmount' min='0' max='100' default='0' display='%' category='background' element='#backgroundImage' />
{this.state.backgroundType === 'api' && APISettings && this.state.backgroundAPI === 'mue' ?
<>
<h3>{getMessage('modals.main.settings.sections.background.buttons.title')}</h3>
<Checkbox name='downloadbtn' text={getMessage('modals.main.settings.sections.background.buttons.download')} element='.other' />
</>
: null}
{this.state.backgroundType === 'api' || this.state.backgroundType === 'custom' || this.state.marketplaceEnabled ?
<>
<h3>{getMessage('modals.main.settings.sections.background.effects.title')}</h3>
<Slider title={getMessage('modals.main.settings.sections.background.effects.blur')} name='blur' min='0' max='100' default='0' display='%' marks={values('background')} category='background' element='#backgroundImage' />
<Slider title={getMessage('modals.main.settings.sections.background.effects.brightness')} name='brightness' min='0' max='100' default='90' display='%' marks={values('background')} category='background' element='#backgroundImage' />
<br/>
<Dropdown label={getMessage('modals.main.settings.sections.background.effects.filters.title')} name='backgroundFilter' onChange={(value) => this.setState({ backgroundFilter: value })} category='background' element='#backgroundImage'>
<option value='none'>{getMessage('modals.main.settings.sections.appearance.navbar.refresh_options.none')}</option>
<option value='grayscale'>{getMessage('modals.main.settings.sections.background.effects.filters.grayscale')}</option>
<option value='sepia'>{getMessage('modals.main.settings.sections.background.effects.filters.sepia')}</option>
<option value='invert'>{getMessage('modals.main.settings.sections.background.effects.filters.invert')}</option>
<option value='saturate'>{getMessage('modals.main.settings.sections.background.effects.filters.saturate')}</option>
<option value='contrast'>{getMessage('modals.main.settings.sections.background.effects.filters.contrast')}</option>
</Dropdown>
{this.state.backgroundFilter !== 'none' ?
<Slider title={getMessage('modals.main.settings.sections.background.effects.filters.amount')} name='backgroundFilterAmount' min='0' max='100' default='0' display='%' marks={values('background')} category='background' element='#backgroundImage' />
: null}
</>
: null}
</>
);
}

View File

@@ -1,25 +1,25 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent, Fragment } from 'react';
import { ColorPicker } from 'react-color-gradient-picker';
import hexToRgb from '../../../../../../modules/helpers/background/hexToRgb';
import rgbToHex from '../../../../../../modules/helpers/background/rgbToHex';
import { toast } from 'react-toastify';
import hexToRgb from 'modules/helpers/background/hexToRgb';
import rgbToHex from 'modules/helpers/background/rgbToHex';
import 'react-color-gradient-picker/dist/index.css';
import '../../../scss/settings/react-color-picker-gradient-picker-custom-styles.scss';
export default class ColourSettings extends React.PureComponent {
export default class ColourSettings extends PureComponent {
DefaultGradientSettings = { angle: '180', gradient: [{ colour: '#ffb032', stop: 0 }], type: 'linear' };
GradientPickerInitalState = undefined;
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
gradientSettings: this.DefaultGradientSettings
};
this.language = window.language.modals.main.settings;
}
resetColour() {
@@ -27,7 +27,7 @@ export default class ColourSettings extends React.PureComponent {
this.setState({
gradientSettings: this.DefaultGradientSettings
});
toast(window.language.toasts.reset);
toast(this.getMessage('toasts.reset'));
}
initialiseColourPickerState(gradientSettings) {
@@ -69,7 +69,7 @@ export default class ColourSettings extends React.PureComponent {
}
componentDidUpdate() {
localStorage.setItem('customBackgroundColour', document.getElementById('customBackgroundHex').value);
localStorage.setItem('customBackgroundColour', this.currentGradientSettings());
}
onGradientChange = (event, index) => {
@@ -85,11 +85,7 @@ export default class ColourSettings extends React.PureComponent {
return newState;
});
const reminderInfo = document.querySelector('.reminder-info');
if (reminderInfo.style.display !== 'block') {
reminderInfo.style.display = 'block';
localStorage.setItem('showReminder', true);
}
this.showReminder();
}
addColour = () => {
@@ -104,18 +100,18 @@ export default class ColourSettings extends React.PureComponent {
return newState;
});
window.stats.postEvent('setting', 'Changed backgroundtype from colour to gradient');
variables.stats.postEvent('setting', 'Changed backgroundtype from colour to gradient');
}
currentGradientSettings = () => {
if (typeof this.state.gradientSettings === 'object' && this.state.gradientSettings.gradient.every(g => g.colour !== this.language.sections.background.source.disabled)) {
if (typeof this.state.gradientSettings === 'object' && this.state.gradientSettings.gradient.every(g => g.colour !== this.getMessage('modals.main.settings.sections.background.source.disabled'))) {
const clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
return JSON.stringify({
...this.state.gradientSettings,
gradient: [...this.state.gradientSettings.gradient.map(g => { return { ...g, stop: clampNumber(+g.stop, 0, 100) } })].sort((a, b) => (a.stop > b.stop) ? 1 : -1)
});
}
return this.language.sections.background.source.disabled;
return this.getMessage('modals.main.settings.sections.background.source.disabled');
}
onColourPickerChange = (attrs, name) => {
@@ -135,16 +131,18 @@ export default class ColourSettings extends React.PureComponent {
}
});
this.showReminder();
};
showReminder() {
const reminderInfo = document.querySelector('.reminder-info');
if (reminderInfo.style.display !== 'block') {
reminderInfo.style.display = 'block';
localStorage.setItem('showReminder', true);
}
};
}
render() {
const { background } = this.language.sections;
let colourSettings = null;
if (typeof this.state.gradientSettings === 'object') {
const gradientHasMoreThanOneColour = this.state.gradientSettings.gradient.length > 1;
@@ -166,10 +164,10 @@ export default class ColourSettings extends React.PureComponent {
} else {
gradientInputs = this.state.gradientSettings.gradient.map((g, i) => {
return (
<React.Fragment key={i}>
<Fragment key={i}>
<input id={'colour_' + i} type='color' name='colour' className='colour' onChange={(event) => this.onGradientChange(event, i)} value={g.colour}></input>
<label htmlFor={'colour_' + i} className='customBackgroundHex'>{g.colour}</label>
</React.Fragment>
</Fragment>
);
});
}
@@ -177,15 +175,14 @@ export default class ColourSettings extends React.PureComponent {
colourSettings = (
<>
{gradientInputs}
{!gradientHasMoreThanOneColour ? (<><br/><br/><button type='button' className='add' onClick={this.addColour}>{background.source.add_colour}</button></>) : null}
{!gradientHasMoreThanOneColour ? (<><br/><br/><button type='button' className='add' onClick={this.addColour}>{this.getMessage('modals.main.settings.sections.background.source.add_colour')}</button></>) : null}
</>
);
}
return (
<>
<p>{background.source.custom_colour} <span className='modalLink' onClick={() => this.resetColour()}>{this.language.buttons.reset}</span></p>
<input id='customBackgroundHex' type='hidden' value={this.currentGradientSettings()} />
<p>{this.getMessage('modals.main.settings.sections.background.source.custom_colour')} <span className='modalLink' onClick={() => this.resetColour()}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
{colourSettings}
</>
);

View File

@@ -0,0 +1,141 @@
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { toast } from 'react-toastify';
import { Cancel, AddLink, AddPhotoAlternate, PersonalVideo } from '@mui/icons-material';
import EventBus from 'modules/helpers/eventbus';
import Checkbox from '../../Checkbox';
import FileUpload from '../../FileUpload';
import Modal from 'react-modal';
import CustomURLModal from './CustomURLModal';
export default class CustomSettings extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
customBackground: this.getCustom(),
customURLModal: false
};
}
resetCustom = () => {
localStorage.setItem('customBackground', '[]');
this.setState({
customBackground: []
});
toast(this.getMessage('toasts.reset'));
EventBus.dispatch('refresh', 'background');
}
customBackground(e, text, index) {
const result = (text === true) ? e.target.value : e.target.result;
const customBackground = this.state.customBackground;
customBackground[index] = result;
this.setState({
customBackground
});
this.forceUpdate();
localStorage.setItem('customBackground', JSON.stringify(customBackground));
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
}
modifyCustomBackground(type, index) {
const customBackground = this.state.customBackground;
if (type === 'add') {
customBackground.push('');
} else {
customBackground.splice(index, 1);
}
this.setState({
customBackground
});
this.forceUpdate();
localStorage.setItem('customBackground', JSON.stringify(customBackground));
document.querySelector('.reminder-info').style.display = 'block';
localStorage.setItem('showReminder', true);
}
videoCheck(url) {
return url.startsWith('data:video/') || url.endsWith('.mp4') || url.endsWith('.webm') || url.endsWith('.ogg');
}
videoCustomSettings = () => {
const hasVideo = this.state.customBackground.filter(bg => this.videoCheck(bg));
if (hasVideo.length > 0) {
return (
<>
<Checkbox name='backgroundVideoLoop' text={this.getMessage('modals.main.settings.sections.background.source.loop_video')}/>
<Checkbox name='backgroundVideoMute' text={this.getMessage('modals.main.settings.sections.background.source.mute_video')}/>
</>
);
} else {
return null;
}
}
getCustom() {
let data;
try {
data = JSON.parse(localStorage.getItem('customBackground'));
} catch (e) {
data = [localStorage.getItem('customBackground')];
}
return data;
}
uploadCustomBackground() {
document.getElementById('bg-input').setAttribute('index', this.state.customBackground.length);
document.getElementById('bg-input').click();
// to fix loadFunction
this.setState({
currentBackgroundIndex: this.state.customBackground.length
});
}
addCustomURL(e) {
this.setState({
customURLModal: false,
currentBackgroundIndex: this.state.customBackground.length
});
this.customBackground({ target: { value: e }}, true, this.state.customBackground.length);
}
render() {
return (
<ul>
<p>{this.getMessage('modals.main.settings.sections.background.source.custom_background')} <span className='modalLink' onClick={this.resetCustom}>{this.getMessage('modals.main.settings.buttons.reset')}</span></p>
<div className='data-buttons-row'>
<button onClick={() => this.uploadCustomBackground()}>{this.getMessage('modals.main.settings.sections.background.source.add_background')} <AddPhotoAlternate/></button>
<button onClick={() => this.setState({ customURLModal: true })}>{this.getMessage('modals.main.settings.sections.background.source.add_url')} <AddLink/></button>
</div>
<div className='images-row'>
{this.state.customBackground.map((url, index) => (
<div style={{ backgroundImage: `url(${!this.videoCheck(url) ? this.state.customBackground[index] : ''})` }} key={index}>
{this.videoCheck(url) ? <PersonalVideo className='customvideoicon'/> : null}
{this.state.customBackground.length > 0 ? <button className='cleanButton' onClick={() => this.modifyCustomBackground('remove', index)}>
<Cancel/>
</button> : null}
</div>
))}
</div>
<FileUpload id='bg-input' accept='image/jpeg, image/png, image/webp, image/webm, image/gif, video/mp4, video/webm, video/ogg' loadFunction={(e) => this.customBackground(e, false, this.state.currentBackgroundIndex)} />
{this.props.interval}
{this.videoCustomSettings()}
<Modal closeTimeoutMS={100} onRequestClose={() => this.setState({ customURLModal: false })} isOpen={this.state.customURLModal} className='Modal resetmodal mainModal' overlayClassName='Overlay resetoverlay' ariaHideApp={false}>
<CustomURLModal modalClose={(e) => this.addCustomURL(e)} modalCloseOnly={() => this.setState({ customURLModal: false })} />
</Modal>
</ul>
);
}
}

View File

@@ -0,0 +1,21 @@
import variables from 'modules/variables';
import { useState } from 'react';
import { Add } from '@mui/icons-material';
import { TextField } from '@mui/material';
export default function CustomURLModal({ modalClose, modalCloseOnly }) {
const [url, setURL] = useState();
return (
<>
<span className='closeModal' onClick={modalCloseOnly}>&times;</span>
<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.background.source.add_url')}</h1>
<TextField value={url} onChange={(e) => setURL(e.target.value)} varient='outlined'/>
<div className='resetfooter'>
<button className='round import' style={{ marginLeft: '5px' }} onClick={() => modalClose(url)}>
<Add/>
</button>
</div>
</>
);
}

View File

@@ -1,15 +1,18 @@
import Added from '../marketplace/sections/Added';
import Sideload from '../marketplace/sections/Sideload';
import variables from 'modules/variables';
import Tabs from './backend/Tabs';
import Added from '../marketplace/sections/Added';
import Sideload from '../marketplace/sections/Sideload';
import Create from '../marketplace/sections/Create';
export default function Addons() {
const addons = window.language.modals.main.addons;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<Tabs>
<div label={addons.added} name='added'><Added/></div>
<div label={addons.sideload} name='sideload'><Sideload/></div>
<div label={getMessage('modals.main.addons.added')} name='added'><Added/></div>
<div label={getMessage('modals.main.addons.sideload.title')} name='sideload'><Sideload/></div>
<div label={getMessage('modals.main.addons.create.title')} name='create'><Create/></div>
</Tabs>
);
}

View File

@@ -1,15 +1,16 @@
import MarketplaceTab from '../marketplace/sections/Marketplace';
import variables from 'modules/variables';
import Tabs from './backend/Tabs';
import MarketplaceTab from '../marketplace/sections/Marketplace';
export default function Marketplace() {
const marketplace = window.language.modals.main.marketplace;
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<Tabs>
<div label={marketplace.photo_packs} name='photo_packs'><MarketplaceTab type='photo_packs'/></div>
<div label={marketplace.quote_packs} name='quote_packs'><MarketplaceTab type='quote_packs'/></div>
<div label={marketplace.preset_settings} name='preset_settings'><MarketplaceTab type='preset_settings'/></div>
<div label={getMessage('modals.main.marketplace.photo_packs')} name='photo_packs'><MarketplaceTab type='photo_packs'/></div>
<div label={getMessage('modals.main.marketplace.quote_packs')} name='quote_packs'><MarketplaceTab type='quote_packs'/></div>
<div label={getMessage('modals.main.marketplace.preset_settings')} name='preset_settings'><MarketplaceTab type='preset_settings'/></div>
</Tabs>
);
}

View File

@@ -1,51 +1,52 @@
import About from '../settings/sections/About';
import Language from '../settings/sections/Language';
import Search from '../settings/sections/Search';
import Greeting from '../settings/sections/Greeting';
import Time from '../settings/sections/Time';
import Quote from '../settings/sections/Quote';
import Appearance from '../settings/sections/Appearance';
import Background from '../settings/sections/background/Background';
import Advanced from '../settings/sections/Advanced';
import Changelog from '../settings/sections/Changelog';
import Order from '../settings/sections/Order';
import Experimental from '../settings/sections/Experimental';
import QuickLinks from '../settings/sections/QuickLinks';
import Weather from '../settings/sections/Weather';
import variables from 'modules/variables';
import Tabs from './backend/Tabs';
export default function Settings() {
const { reminder, sections } = window.language.modals.main.settings;
import Navbar from '../settings/sections/Navbar';
import Greeting from '../settings/sections/Greeting';
import Time from '../settings/sections/Time';
import QuickLinks from '../settings/sections/QuickLinks';
import Quote from '../settings/sections/Quote';
import Date from '../settings/sections/Date';
import Message from '../settings/sections/Message';
import Background from '../settings/sections/background/Background';
import Search from '../settings/sections/Search';
import Weather from '../settings/sections/Weather';
import Order from '../settings/sections/Order';
import Appearance from '../settings/sections/Appearance';
import Language from '../settings/sections/Language';
import Advanced from '../settings/sections/Advanced';
//import Keybinds from '../settings/sections/Keybinds';
import Stats from '../settings/sections/Stats';
import Experimental from '../settings/sections/Experimental';
import Changelog from '../settings/sections/Changelog';
import About from '../settings/sections/About';
let display = 'none';
if (localStorage.getItem('showReminder') === 'true') {
display = 'block';
}
export default function Settings() {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
return (
<>
<Tabs>
<div label={sections.time.title} name='time'><Time/></div>
<div label={sections.quote.title} name='quote'><Quote/></div>
<div label={sections.greeting.title} name='greeting'><Greeting/></div>
<div label={sections.background.title} name='background'><Background/></div>
<div label={sections.search.title} name='search'><Search/></div>
<div label={sections.quicklinks.title} name='quicklinks'><QuickLinks/></div>
<div label={sections.weather.title} name='weather'><Weather/></div>
<div label={sections.appearance.title} name='appearance'><Appearance/></div>
<div label={sections.order.title} name='order'><Order/></div>
<div label={sections.language.title} name='language'><Language/></div>
<div label={sections.advanced.title} name='advanced'><Advanced/></div>
<div label={sections.experimental.title} name='experimental'><Experimental/></div>
<div label={sections.changelog} name='changelog'><Changelog/></div>
<div label={sections.about.title} name='about'><About/></div>
<div label={getMessage('modals.main.settings.sections.appearance.navbar.title')} name='navbar'><Navbar/></div>
<div label={getMessage('modals.main.settings.sections.greeting.title')} name='greeting'><Greeting/></div>
<div label={getMessage('modals.main.settings.sections.time.title')} name='time'><Time/></div>
<div label={getMessage('modals.main.settings.sections.quicklinks.title')} name='quicklinks'><QuickLinks/></div>
<div label={getMessage('modals.main.settings.sections.quote.title')} name='quote'><Quote/></div>
<div label={getMessage('modals.main.settings.sections.date.title')} name='date'><Date/></div>
<div label={getMessage('modals.main.settings.sections.message.title')} name='message'><Message/></div>
<div label={getMessage('modals.main.settings.sections.background.title')} name='background'><Background/></div>
<div label={getMessage('modals.main.settings.sections.search.title')} name='search'><Search/></div>
<div label={getMessage('modals.main.settings.sections.weather.title')} name='weather'><Weather/></div>
<div label={getMessage('modals.main.settings.sections.order.title')} name='order'><Order/></div>
<div label={getMessage('modals.main.settings.sections.appearance.title')} name='appearance'><Appearance/></div>
<div label={getMessage('modals.main.settings.sections.language.title')} name='language'><Language/></div>
<div label={getMessage('modals.main.settings.sections.advanced.title')} name='advanced'><Advanced/></div>
{/*<div label={getMessage('modals.main.settings.sections.keybinds.title')} name='keybinds'><Keybinds/></div>*/}
<div label={getMessage('modals.main.settings.sections.stats.title')} name='stats'><Stats/></div>
<div label={getMessage('modals.main.settings.sections.experimental.title')} name='experimental'><Experimental/></div>
<div label={getMessage('modals.main.settings.sections.changelog.title')} name='changelog'><Changelog/></div>
<div label={getMessage('modals.main.settings.sections.about.title')} name='about'><About/></div>
</Tabs>
<div className='reminder-info' style={{ display: display }}>
<h1>{reminder.title}</h1>
<p>{reminder.message}</p>
<button className='pinNote' onClick={() => window.location.reload()}>{window.language.modals.main.error_boundary.refresh}</button>
</div>
</>
);
}

View File

@@ -1,82 +1,89 @@
import React from 'react';
import variables from 'modules/variables';
import { memo } from 'react';
import {
SettingsRounded as Settings,
Widgets as Addons,
ShoppingBasket as Marketplace,
MenuOutlined as Navbar,
EmojiPeopleOutlined as Greeting,
AccessAlarm as Time,
FormatQuoteOutlined as Quote,
Link as QuickLinks,
DateRangeOutlined as Date,
SmsOutlined as Message,
PhotoOutlined as Background,
Search,
CloudOutlined as Weather,
List as Order,
FormatPaintOutlined as Appearance,
Translate as Language,
SettingsOutlined as Advanced,
BugReportOutlined as Experimental,
//KeyboardAltOutlined as Keybinds,
AssessmentOutlined as Stats,
NewReleasesOutlined as Changelog,
InfoOutlined as About,
// Navbar
import Settings from '@material-ui/icons/SettingsRounded';
import Addons from '@material-ui/icons/Widgets';
import Marketplace from '@material-ui/icons/ShoppingBasket';
Code as Sideload,
AddCircleOutline as Added,
CreateNewFolderOutlined as Create
} from '@mui/icons-material';
// Settings
import Time from '@material-ui/icons/AccessAlarm';
import Greeting from '@material-ui/icons/EmojiPeopleOutlined';
import Quote from '@material-ui/icons/FormatQuoteOutlined';
import Background from '@material-ui/icons/PhotoOutlined';
import Search from '@material-ui/icons/Search';
import Appearance from '@material-ui/icons/FormatPaintOutlined';
import Language from '@material-ui/icons/Translate';
import Changelog from '@material-ui/icons/NewReleasesOutlined';
import About from '@material-ui/icons/InfoOutlined';
import Experimental from '@material-ui/icons/BugReportOutlined';
import Order from '@material-ui/icons/List';
import Weather from '@material-ui/icons/CloudOutlined';
import Advanced from '@material-ui/icons/SettingsOutlined';
import QuickLinks from '@material-ui/icons/Link';
function Tab({ label, currentTab, onClick, navbarTab }) {
const getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
// Addons
import Sideload from '@material-ui/icons/Code';
import Added from '@material-ui/icons/AddCircleOutline';
function Tab(props) {
let className = 'tab-list-item';
if (props.currentTab === props.label) {
if (currentTab === label) {
className += ' tab-list-active';
}
if (props.navbar === true) {
if (navbarTab === true) {
className = 'navbar-item';
if (props.currentTab === props.label) {
if (currentTab === label) {
className += ' navbar-item-active';
}
}
const settings = window.language.modals.main.settings.sections;
const { navbar, marketplace, addons } = window.language.modals.main;
let icon, divider;
switch (props.label) {
// Navbar
case navbar.settings: icon = <Settings/>; break;
case navbar.addons: icon = <Addons/>; break;
case navbar.marketplace: icon = <Marketplace/>; break;
switch (label) {
case getMessage('modals.main.navbar.settings'): icon = <Settings/>; break;
case getMessage('modals.main.navbar.addons'): icon = <Addons/>; break;
case getMessage('modals.main.navbar.marketplace'): icon = <Marketplace/>; break;
// Settings
case settings.time.title: icon = <Time/>; break;
case settings.greeting.title: icon = <Greeting/>; break;
case settings.quote.title: icon = <Quote/>; break;
case settings.background.title: icon = <Background/>; break;
case settings.search.title: icon = <Search/>; break;
case settings.weather.title: icon = <Weather/>; break;
case settings.quicklinks.title: icon = <QuickLinks/>; break;
case settings.appearance.title: icon = <Appearance/>; break;
case settings.order.title: icon = <Order/>; break;
case settings.language.title: icon = <Language/>; divider = true; break;
case settings.advanced.title: icon = <Advanced/>; break;
case settings.experimental.title: icon = <Experimental/>; divider = true; break;
case settings.changelog: icon = <Changelog/>; break;
case settings.about.title: icon = <About/>; break;
case getMessage('modals.main.settings.sections.appearance.navbar.title'): icon = <Navbar/>; break;
case getMessage('modals.main.settings.sections.greeting.title'): icon = <Greeting/>; break;
case getMessage('modals.main.settings.sections.time.title'): icon = <Time/>; break;
case getMessage('modals.main.settings.sections.quicklinks.title'): icon = <QuickLinks/>; break;
case getMessage('modals.main.settings.sections.quote.title'): icon = <Quote/>; break;
case getMessage('modals.main.settings.sections.date.title'): icon = <Date/>; break;
case getMessage('modals.main.settings.sections.message.title'): icon = <Message/>; break;
case getMessage('modals.main.settings.sections.background.title'): icon = <Background/>; break;
case getMessage('modals.main.settings.sections.search.title'): icon = <Search/>; break;
case getMessage('modals.main.settings.sections.weather.title'): icon = <Weather/>; divider = true; break;
case getMessage('modals.main.settings.sections.order.title'): icon = <Order/>; break;
// Addons
case addons.added: icon = <Added/>; break;
case addons.sideload: icon = <Sideload/>; break;
case getMessage('modals.main.settings.sections.appearance.title'): icon = <Appearance/>; break;
case getMessage('modals.main.settings.sections.language.title'): icon = <Language/>; divider = true; break;
case getMessage('modals.main.settings.sections.advanced.title'): icon = <Advanced/>; break;
//case getMessage('modals.main.settings.sections.keybinds.title'): icon = <Keybinds/>; break;
case getMessage('modals.main.settings.sections.stats.title'): icon = <Stats/>; break;
case getMessage('modals.main.settings.sections.experimental.title'): icon = <Experimental/>; divider = true; break;
case getMessage('modals.main.settings.sections.changelog.title'): icon = <Changelog/>; break;
case getMessage('modals.main.settings.sections.about.title'): icon = <About/>; break;
// Marketplace
case marketplace.photo_packs: icon = <Background/>; break;
case marketplace.quote_packs: icon = <Quote/>; break;
case marketplace.preset_settings: icon = <Advanced/>; break;
case getMessage('modals.main.addons.added'): icon = <Added/>; break;
case getMessage('modals.main.addons.sideload.title'): icon = <Sideload/>; break;
case getMessage('modals.main.addons.create.title'): icon = <Create/>; break;
case getMessage('modals.main.marketplace.photo_packs'): icon = <Background/>; break;
case getMessage('modals.main.marketplace.quote_packs'): icon = <Quote/>; break;
case getMessage('modals.main.marketplace.preset_settings'): icon = <Advanced/>; break;
default: break;
}
if (props.label === settings.experimental.title) {
if (label === getMessage('modals.main.settings.sections.experimental.title')) {
if (localStorage.getItem('experimental') === 'false') {
return <hr/>;
}
@@ -84,12 +91,12 @@ function Tab(props) {
return (
<>
<li className={className} onClick={() => props.onClick(props.label)}>
{icon} <span>{props.label}</span>
<li className={className} onClick={() => onClick(label)}>
{icon} <span>{label}</span>
</li>
{(divider === true) ? <hr/> : null}
</>
);
}
export default React.memo(Tab);
export default memo(Tab);

View File

@@ -1,9 +1,10 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import Tab from './Tab';
import ErrorBoundary from '../../../ErrorBoundary';
export default class Tabs extends React.PureComponent {
export default class Tabs extends PureComponent {
constructor(props) {
super(props);
@@ -15,7 +16,7 @@ export default class Tabs extends React.PureComponent {
onClick = (tab, name) => {
if (name !== this.state.currentName) {
window.stats.postEvent('tab', `Changed ${this.state.currentName} to ${name}`);
variables.stats.postEvent('tab', `Opened ${name}`);
}
this.setState({
@@ -27,7 +28,7 @@ export default class Tabs extends React.PureComponent {
render() {
let className = 'sidebar';
let tabClass = 'tab-content';
let optionsText = (<h1>{window.language.modals.main.title}</h1>);
let optionsText = (<h1>{variables.language.getMessage(variables.languagecode, 'modals.main.title')}</h1>);
if (this.props.navbar) {
className = 'modalNavbar';
@@ -45,7 +46,7 @@ export default class Tabs extends React.PureComponent {
key={index}
label={tab.props.label}
onClick={(nextTab) => this.onClick(nextTab, tab.props.name)}
navbar={this.props.navbar || false}
navbarTab={this.props.navbar || false}
/>
))}
</ul>

View File

@@ -1,15 +1,15 @@
export default function ProgressBar(props) {
export default function ProgressBar({ count, currentTab, switchTab }) {
return (
<div className='progressbar'>
{props.count.map((num) => {
{count.map((num) => {
let className = 'step';
const index = props.count.indexOf(num);
if (index === props.currentTab) {
const index = count.indexOf(num);
if (index === currentTab) {
className = 'step active';
}
return <div className={className} key={index} onClick={() => props.switchTab(index)}></div>;
return <div className={className} key={index} onClick={() => switchTab(index)}></div>;
})}
</div>
);

View File

@@ -1,22 +1,24 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import EventBus from 'modules/helpers/eventbus';
import WelcomeSections from './WelcomeSections';
import ProgressBar from './ProgressBar';
import './welcome.scss';
export default class WelcomeModal extends React.PureComponent {
export default class WelcomeModal extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
constructor() {
super();
this.state = {
image: './././icons/undraw_celebration.svg',
currentTab: 0,
finalTab: 4,
buttonText: window.language.modals.welcome.buttons.next
buttonText: this.getMessage('modals.welcome.buttons.next')
};
this.language = window.language.modals.welcome;
this.images = [
'./././icons/undraw_celebration.svg',
'./././icons/undraw_around_the_world_modified.svg',
@@ -35,18 +37,18 @@ export default class WelcomeModal extends React.PureComponent {
return this.setState({
currentTab: this.state.currentTab - 1,
image: this.images[this.state.currentTab - 1],
buttonText: this.language.buttons.next
buttonText: this.getMessage('modals.welcome.buttons.next')
});
}
if (this.state.buttonText === this.language.buttons.close) {
if (this.state.buttonText === this.getMessage('modals.main.addons.create.finish.title')) {
return this.props.modalClose();
}
this.setState({
currentTab: this.state.currentTab + 1,
image: this.images[this.state.currentTab + 1],
buttonText: (this.state.currentTab !== this.state.finalTab) ? this.language.buttons.next : this.language.buttons.close
buttonText: (this.state.currentTab !== this.state.finalTab) ? this.getMessage('modals.welcome.buttons.next') : this.getMessage('modals.main.addons.create.finish.title')
});
}
@@ -55,7 +57,7 @@ export default class WelcomeModal extends React.PureComponent {
this.setState({
currentTab: tab,
image: this.images[tab],
buttonText: (tab !== this.state.finalTab + 1) ? this.language.buttons.next : this.language.buttons.close
buttonText: (tab !== this.state.finalTab + 1) ? this.getMessage('modals.welcome.buttons.next') : this.getMessage('modals.main.addons.create.finish.title')
});
localStorage.setItem('bgtransition', true);
@@ -68,7 +70,7 @@ export default class WelcomeModal extends React.PureComponent {
this.setState({
currentTab: Number(welcomeTab),
image: this.images[Number(welcomeTab)],
buttonText: (Number(welcomeTab) !== this.state.finalTab + 1) ? this.language.buttons.next : this.language.buttons.close
buttonText: (Number(welcomeTab) !== this.state.finalTab + 1) ? this.getMessage('modals.welcome.buttons.next') : this.getMessage('modals.main.addons.create.finish.title')
});
}
@@ -81,11 +83,15 @@ export default class WelcomeModal extends React.PureComponent {
});
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
return (
<div className='welcomeContent'>
<section>
<img className='showcaseimg' alt='celebration' draggable={false} src={this.state.image} />
<img className='showcaseimg' alt='sidebar icon' draggable={false} src={this.state.image} />
<ProgressBar count={this.images} currentTab={this.state.currentTab} switchTab={(tab) => this.switchTab(tab)}/>
</section>
<section>
@@ -93,7 +99,8 @@ export default class WelcomeModal extends React.PureComponent {
<WelcomeSections currentTab={this.state.currentTab} switchTab={(tab) => this.switchTab(tab)}/>
</div>
<div className='buttons'>
{(this.state.currentTab !== 0) ? <button className='close' style={{ marginRight: '20px' }} onClick={() => this.changeTab(true)}>{this.language.buttons.previous}</button> : null}
{(this.state.currentTab === 0) ? <button className='close' style={{ marginRight: '20px' }} onClick={() => this.props.modalSkip()}>{this.getMessage('modals.welcome.buttons.preview')}</button> : null}
{(this.state.currentTab !== 0) ? <button className='close' style={{ marginRight: '20px' }} onClick={() => this.changeTab(true)}>{this.getMessage('modals.welcome.buttons.previous')}</button> : null}
<button className='close' onClick={() => this.changeTab()}>{this.state.buttonText}</button>
</div>
</section>

View File

@@ -1,21 +1,20 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { CloudUpload, AutoAwesome, LightMode, DarkMode } from '@mui/icons-material';
import Radio from '../main/settings/Radio';
import Checkbox from '../main/settings/Checkbox';
import FileUpload from '../main/settings/FileUpload';
import UploadIcon from '@material-ui/icons/CloudUpload';
import AutoIcon from '@material-ui/icons/AutoAwesome';
import LightModeIcon from '@material-ui/icons/LightMode';
import DarkModeIcon from '@material-ui/icons/DarkMode';
import { loadSettings } from 'modules/helpers/settings';
import { importSettings } from 'modules/helpers/settings/modals';
import SettingsFunctions from '../../../modules/helpers/settings';
import SettingsFunctionsModal from '../../../modules/helpers/settings/modals';
const languages = require('modules/languages.json');
const default_settings = require('modules/default_settings.json');
const languages = require('../../../modules/languages.json');
const default_settings = require('../../../modules/default_settings.json');
export default class WelcomeSections extends PureComponent {
getMessage = (text) => variables.language.getMessage(variables.languagecode, text);
export default class WelcomeSections extends React.PureComponent {
constructor() {
super();
this.state = {
@@ -40,7 +39,7 @@ export default class WelcomeSections extends React.PureComponent {
});
localStorage.setItem('theme', type);
SettingsFunctions.loadSettings(true);
loadSettings(true);
}
getSetting(name) {
@@ -49,11 +48,12 @@ export default class WelcomeSections extends React.PureComponent {
}
importSettings(e) {
SettingsFunctionsModal.importSettings(e);
importSettings(e);
let settings = [];
const data = JSON.parse(e.target.result);
Object.keys(data).forEach((setting) => {
// language and theme already shown, the others are only used internally
if (setting === 'language' || setting === 'theme'|| setting === 'firstRun' || setting === 'showWelcome' || setting === 'showReminder') {
return;
}
@@ -92,9 +92,16 @@ export default class WelcomeSections extends React.PureComponent {
this.timeout = setTimeout(this.changeWelcomeImg, 3 * 1000);
}
componentWillUnmount() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
// cancel welcome image timer if not on welcome tab
componentDidUpdate() {
if (this.props.currentTab !== 0) {
if (this.props.currentTab !== 0) {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
@@ -107,13 +114,12 @@ export default class WelcomeSections extends React.PureComponent {
}
render() {
const language = window.language.modals.welcome;
let tabContent;
const intro = (
<>
<h1>{language.sections.intro.title}</h1>
<p>{language.sections.intro.description}</p>
<h1>{this.getMessage('modals.welcome.sections.intro.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.intro.description')}</p>
<h3 className='quicktip'>#shareyourmue</h3>
<div className='examples'>
<img src={this.welcomeImages[this.state.welcomeImage]} alt='Example Mue setup' draggable={false}/>
@@ -123,81 +129,78 @@ export default class WelcomeSections extends React.PureComponent {
const chooseLanguage = (
<>
<h1>{language.sections.language.title}</h1>
<p>{language.sections.language.description}</p>
<h1>{this.getMessage('modals.welcome.sections.language.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.language.description')} <a href={variables.constants.TRANSLATIONS_URL} className='resetLink' target='_blank' rel='noopener noreferrer'>GitHub</a>!</p>
<Radio name='language' options={languages} category='welcomeLanguage'/>
</>
);
const { appearance, advanced, background, quicklinks } = window.language.modals.main.settings.sections;
const languageSettings = window.language.modals.main.settings.sections.language;
const theme = (
<>
<h1>{language.sections.theme.title}</h1>
<p>{language.sections.theme.description}</p>
<h1>{this.getMessage('modals.welcome.sections.theme.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.theme.description')}</p>
<div className='themesToggleArea'>
<div className={this.state.autoClass} onClick={() => this.changeTheme('auto')}>
<AutoIcon/>
<span>{appearance.theme.auto}</span>
<AutoAwesome/>
<span>{this.getMessage('modals.main.settings.sections.appearance.theme.auto')}</span>
</div>
<div className='options'>
<div className={this.state.lightClass} onClick={() => this.changeTheme('light')}>
<LightModeIcon/>
<span>{appearance.theme.light}</span>
<LightMode/>
<span>{this.getMessage('modals.main.settings.sections.appearance.theme.light')}</span>
</div>
<div className={this.state.darkClass} onClick={() => this.changeTheme('dark')}>
<DarkModeIcon/>
<span>{appearance.theme.dark}</span>
<DarkMode/>
<span>{this.getMessage('modals.main.settings.sections.appearance.theme.dark')}</span>
</div>
</div>
<h3 className='quicktip'>{language.tip}</h3>
<p>{language.sections.theme.tip}</p>
<h3 className='quicktip'>{this.getMessage('modals.welcome.tip')}</h3>
<p>{this.getMessage('modals.welcome.sections.theme.tip')}</p>
</div>
</>
);
const settings = (
<>
<h1>{language.sections.settings.title}</h1>
<p>{language.sections.settings.description}</p>
<h1>{this.getMessage('modals.welcome.sections.settings.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.settings.description')}</p>
<button className='upload' onClick={() => document.getElementById('file-input').click()}>
<UploadIcon/>
<CloudUpload/>
<br/>
<span>{window.language.modals.main.settings.buttons.import}</span>
<span>{this.getMessage('modals.main.settings.buttons.import')}</span>
</button>
<FileUpload id='file-input' accept='application/json' type='settings' loadFunction={(e) => this.importSettings(e)}/>
<h3 className='quicktip'>{language.tip}</h3>
<p>{language.sections.settings.tip}</p>
<h3 className='quicktip'>{this.getMessage('modals.welcome.tip')}</h3>
<p>{this.getMessage('modals.welcome.sections.settings.tip')}</p>
</>
);
const privacy = (
<>
<h1>{language.sections.privacy.title}</h1>
<p>{language.sections.privacy.description}</p>
<Checkbox name='offlineMode' text={advanced.offline_mode} element='.other' />
<p>{language.sections.privacy.offline_mode_description}</p>
<Checkbox name='quicklinksddgProxy' text={background.ddg_image_proxy + ' (' + quicklinks.title + ')'}/>
<Checkbox name='ddgProxy' text={background.ddg_image_proxy + ' (' + background.title + ')'}/>
<p>{language.sections.privacy.ddg_proxy_description}</p>
<h3 className='quicktip'>{language.sections.privacy.links.title}</h3>
<a className='privacy' href={window.constants.PRIVACY_URL} target='_blank' rel='noopener noreferrer'>{language.sections.privacy.links.privacy_policy}</a>
<h1>{this.getMessage('modals.welcome.sections.privacy.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.privacy.description')}</p>
<Checkbox name='offlineMode' text={this.getMessage('modals.main.settings.sections.advanced.offline_mode')} element='.other' />
<p>{this.getMessage('modals.welcome.sections.privacy.offline_mode_description')}</p>
<Checkbox name='quicklinksddgProxy' text={this.getMessage('modals.main.settings.sections.background.ddg_image_proxy') + ' (' + this.getMessage('modals.main.settings.sections.quicklinks.title') + ')'}/>
<Checkbox name='ddgProxy' text={this.getMessage('modals.main.settings.sections.background.ddg_image_proxy') + ' (' +this.getMessage('modals.main.settings.sections.background.title') + ')'}/>
<p>{this.getMessage('modals.welcome.sections.privacy.ddg_proxy_description')}</p>
<h3 className='quicktip'>{this.getMessage('modals.welcome.sections.privacy.links.title')}</h3>
<a className='privacy' href={variables.constants.PRIVACY_URL} target='_blank' rel='noopener noreferrer'>{this.getMessage('modals.welcome.sections.privacy.links.privacy_policy')}</a>
<br/><br/>
<a className='privacy' href={'https://github.com/' + window.constants.ORG_NAME} target='_blank' rel='noopener noreferrer'>{language.sections.privacy.links.source_code}</a>
<a className='privacy' href={'https://github.com/' + variables.constants.ORG_NAME} target='_blank' rel='noopener noreferrer'>{this.getMessage('modals.welcome.sections.privacy.links.source_code')}</a>
</>
);
const final = (
<>
<h1>{language.sections.final.title}</h1>
<p>{language.sections.final.description}</p>
<h3 className='quicktip'>{language.sections.final.changes}</h3>
<p>{language.sections.final.changes_description}</p>
<h1>{this.getMessage('modals.welcome.sections.final.title')}</h1>
<p>{this.getMessage('modals.welcome.sections.final.description')}</p>
<h3 className='quicktip'>{this.getMessage('modals.welcome.sections.final.changes')}</h3>
<p>{this.getMessage('modals.welcome.sections.final.changes_description')}</p>
<div className='themesToggleArea'>
<div className='toggle' onClick={() => this.props.switchTab(1)}><span>{languageSettings.title}: {languages.find((i) => i.value === localStorage.getItem('language')).name}</span></div>
<div className='toggle' onClick={() => this.props.switchTab(3)}><span>{appearance.theme.title}: {this.getSetting('theme')}</span></div>
{(this.state.importedSettings.length !== 0) ? <div className='toggle' onClick={() => this.props.switchTab(2)}>{language.sections.final.imported} {this.state.importedSettings.length} {language.sections.final.settings}</div> : null}
<div className='toggle' onClick={() => this.props.switchTab(1)}><span>{this.getMessage('modals.main.settings.sections.language.title')}: {languages.find((i) => i.value === localStorage.getItem('language')).name}</span></div>
<div className='toggle' onClick={() => this.props.switchTab(3)}><span>{this.getMessage('modals.main.settings.sections.appearance.theme.title')}: {this.getSetting('theme')}</span></div>
{(this.state.importedSettings.length !== 0) ? <div className='toggle' onClick={() => this.props.switchTab(2)}>{this.getMessage('modals.main.settings.sections.final.imported', { amount: this.state.importedSettings.length })} {this.state.importedSettings.length}</div> : null}
</div>
</>
);

View File

@@ -67,7 +67,7 @@
background: #8395a7;
height: 4px;
margin: 10px;
transition: .2s ease;
transition: 0.2s ease;
cursor: pointer;
border-radius: 15px;
}
@@ -77,27 +77,38 @@
}
}
.themesToggleArea {
.active {
background: var(--tab-active) !important;
background: var(--tab-active);
}
.toggle {
background: var(--sidebar);
text-align: center;
border-radius: 40px;
border-radius: 20px;
padding: 20px;
margin: 10px;
border: 3px solid var(--tab-active);
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: center;
cursor: pointer;
&:hover {
background: var(--tab-active);
cursor: pointer;
}
span {
font-size: 1rem;
}
svg {
font-size: 2.5em;
}
}
.auto {
@@ -123,6 +134,31 @@
}
}
.upload {
width: 100%;
height: 100%;
border-radius: 20px;
border: none;
outline: none;
padding: 50px;
background: var(--sidebar);
color: var(--modal-text);
cursor: pointer;
border: 3px solid var(--tab-active);
&:hover {
background: var(--tab-active);
}
svg {
font-size: 4em;
}
span {
font-size: 2em;
}
}
a.privacy {
text-decoration: none;
color: var(--modal-text);
@@ -153,18 +189,22 @@ a.privacy {
height: auto;
}
@media only screen and (max-width: 1440px) {
.buttons {
position: relative !important;
bottom: 0rem !important;
@media (max-width: 1820px) and (min-width: 1200px) {
.welcomemodal {
width: 85%;
height: 90%;
}
.examples img {
width: 15rem !important;
section {
height: 90vh !important;
}
}
@media only screen and (max-width: 1600px) {
@media (max-width: 1300px) {
.welcomemodal {
overflow-x: hidden !important;
}
.examples img {
width: 20rem !important;
}
@@ -172,5 +212,32 @@ a.privacy {
.buttons {
position: relative !important;
bottom: 1rem !important;
right: -1rem !important;
}
section {
height: 100vh !important;
}
}
@media (max-width: 1190px) {
.welcomemodal {
width: 90%;
height: 90%;
}
}
@media (max-width: 800px) {
.welcomemodal {
width: 100%;
height: 100%;
}
.examples img {
width: 15rem !important;
}
section {
height: 140vh !important;
}
}

View File

@@ -1,6 +1,4 @@
import React from 'react';
import EventBus from '../../modules/helpers/eventbus';
import { PureComponent, Fragment, Suspense, lazy } from 'react';
import Clock from './time/Clock';
import Greeting from './greeting/Greeting';
@@ -8,15 +6,20 @@ import Quote from './quote/Quote';
import Search from './search/Search';
import QuickLinks from './quicklinks/QuickLinks';
import Date from './time/Date';
import Message from './message/Message';
const Weather = React.lazy(() => import('./weather/Weather'));
import EventBus from 'modules/helpers/eventbus';
const Weather = lazy(() => import('./weather/Weather'));
const renderLoader = () => <></>;
export default class Widgets extends React.PureComponent {
export default class Widgets extends PureComponent {
online = (localStorage.getItem('offlineMode') === 'false');
constructor() {
super();
this.state = {
order: JSON.parse(localStorage.getItem('order'))
order: JSON.parse(localStorage.getItem('order')),
welcome: localStorage.getItem('showWelcome')
};
// widgets we can re-order
this.widgets = {
@@ -24,7 +27,8 @@ export default class Widgets extends React.PureComponent {
greeting: this.enabled('greeting') ? <Greeting/> : null,
quote: this.enabled('quote') ? <Quote/> : null,
date: this.enabled('date') ? <Date/> : null,
quicklinks: this.enabled('quicklinksenabled') ? <QuickLinks/> : null
quicklinks: this.enabled('quicklinksenabled') && this.online ? <QuickLinks/> : null,
message: this.enabled('message') ? <Message/> : null
};
}
@@ -39,12 +43,29 @@ export default class Widgets extends React.PureComponent {
order: JSON.parse(localStorage.getItem('order'))
});
}
if (data === 'widgetsWelcome') {
this.setState({
welcome: localStorage.getItem('showWelcome')
});
localStorage.setItem('showWelcome', true);
window.onbeforeunload = () => {
localStorage.clear();
}
}
if (data === 'widgetsWelcomeDone') {
this.setState({
welcome: localStorage.getItem('showWelcome')
});
window.onbeforeunload = null;
}
});
}
render() {
// don't show when welcome is there
if (localStorage.getItem('showWelcome') !== 'false') {
if (this.state.welcome !== 'false') {
return <div id='widgets'></div>;
}
@@ -53,20 +74,20 @@ export default class Widgets extends React.PureComponent {
if (this.state.order) {
this.state.order.forEach((element) => {
elements.push(<React.Fragment key={element}>{this.widgets[element]}</React.Fragment>);
elements.push(<Fragment key={element}>{this.widgets[element]}</Fragment>);
});
} else {
// prevent error
elements = [<Greeting/>, <Clock/>, <QuickLinks/>, <Quote/>, <Date/>];
elements = [<Greeting/>, <Clock/>, <QuickLinks/>, <Quote/>, <Date/>, <Message/>];
}
return (
<div id='widgets'>
<React.Suspense fallback={renderLoader()}>
<Suspense fallback={renderLoader()}>
{this.enabled('searchBar') ? <Search/> : null}
{elements}
{this.enabled('weatherEnabled') && (localStorage.getItem('offlineMode') === 'false') ? <Weather/> : null}
</React.Suspense>
{this.enabled('weatherEnabled') && this.online ? <Weather/> : null}
</Suspense>
</div>
);
}

View File

@@ -1,14 +1,16 @@
// todo: rewrite this mess
import React from 'react';
import EventBus from '../../../modules/helpers/eventbus';
import Interval from '../../../modules/helpers/interval';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import PhotoInformation from './PhotoInformation';
import EventBus from 'modules/helpers/eventbus';
import Interval from 'modules/helpers/interval';
import { videoCheck, offlineBackground, getGradient, randomColourStyleBuilder } from 'modules/helpers/background/widget';
import './scss/index.scss';
export default class Background extends React.PureComponent {
export default class Background extends PureComponent {
constructor() {
super();
this.state = {
@@ -22,66 +24,23 @@ export default class Background extends React.PureComponent {
photoURL: ''
}
};
this.language = window.language.widgets.background;
}
gradientStyleBuilder(gradientSettings) {
const { type, angle, gradient } = gradientSettings;
let style = `background: ${gradient[0].colour};`;
if (gradient.length > 1) {
// Note: Append the gradient for additional browser support.
const stepStyles = gradient.map((g) => ` ${g.colour} ${g.stop}%`).join();
style += ` background: ${type}-gradient(${(type === 'linear' ? (`${angle}deg,`) : '')}${stepStyles})`;
}
this.setState({
type: 'colour',
style: style
});
}
videoCheck(url) {
return url.startsWith('data:video/') || url.endsWith('.mp4') || url.endsWith('.webm') || url.endsWith('.ogg');
}
offlineBackground() {
const offlineImages = require('./offline_images.json');
// Get all photographers from the keys in offlineImages.json
const photographers = Object.keys(offlineImages);
const photographer = photographers[Math.floor(Math.random() * photographers.length)];
const randomImage = offlineImages[photographer].photo[
Math.floor(Math.random() * offlineImages[photographer].photo.length)
];
const object = {
url: `./offline-images/${randomImage}.webp`,
photoInfo: {
offline: true,
credit: photographer
}
}
this.setState(object);
localStorage.setItem('currentBackground', JSON.stringify(object));
}
setBackground() {
const backgroundImage = document.getElementById('backgroundImage');
if (this.state.url !== '') {
const url = (localStorage.getItem('ddgProxy') === 'true' && this.state.photoInfo.offline !== true && !this.state.url.startsWith('data:')) ? window.constants.DDG_IMAGE_PROXY + this.state.url : this.state.url;
let url = this.state.url;
if (localStorage.getItem('ddgProxy') === 'true' && this.state.photoInfo.offline !== true && !this.state.url.startsWith('data:')) {
url = variables.constants.DDG_IMAGE_PROXY + this.state.url;
}
const photoInformation = document.querySelector('.photoInformation');
// just set the background
if (localStorage.getItem('bgtransition') === 'false') {
if (photoInformation) {
photoInformation.style.display = 'block';
}
backgroundImage.style.background = null;
return backgroundImage.style.background = `url(${url})`;
photoInformation?.[photoInformation.style.display = 'block'];
return backgroundImage.style.background = `url(${url})`;
}
// firstly we set the background as hidden and make sure there is no background set currently
@@ -89,9 +48,7 @@ export default class Background extends React.PureComponent {
backgroundImage.style.background = null;
// same with photo information if not using custom background
if (photoInformation) {
photoInformation.classList.add('backgroundPreload');
}
photoInformation?.classList.add('backgroundPreload');
// preloader for background transition, required so it loads in nice
const preloader = document.createElement('img');
@@ -124,41 +81,53 @@ export default class Background extends React.PureComponent {
offline = true;
}
switch (localStorage.getItem('backgroundType')) {
const setFavourited = ({ type, url, credit, location, camera }) => {
console.log(type)
if (type === 'random_colour' || type === 'random_gradient') {
return this.setState({
type: 'colour',
style: `background:${url}`
});
}
this.setState({
url,
photoInfo: {
credit,
location,
camera
}
});
}
const favourited = JSON.parse(localStorage.getItem('favourite'));
if (favourited) {
return setFavourited(favourited);
}
const type = localStorage.getItem('backgroundType');
switch (type) {
case 'api':
if (offline) {
return this.offlineBackground();
}
// favourite button
const favourited = JSON.parse(localStorage.getItem('favourite'));
if (favourited) {
return this.setState({
url: favourited.url,
photoInfo: {
credit: favourited.credit,
location: favourited.location,
camera: favourited.camera
}
});
return this.setState(offlineBackground());
}
// API background
const backgroundAPI = localStorage.getItem('backgroundAPI');
const apiCategory = localStorage.getItem('apiCategory');
const apiQuality = localStorage.getItem('apiQuality');
const photoMap = (localStorage.getItem('photoMap') === 'true');
let requestURL, data;
switch (backgroundAPI) {
case 'unsplash':
requestURL = `${window.constants.PROXY_URL}/images/unsplash?quality=${apiQuality}`;
requestURL = `${variables.constants.PROXY_URL}/images/unsplash?quality=${apiQuality}&map=${photoMap}`;
break;
case 'pexels':
requestURL = `${window.constants.PROXY_URL}/images/pexels?quality=${apiQuality}`;
requestURL = `${variables.constants.PROXY_URL}/images/pexels?quality=${apiQuality}`;
break;
// Defaults to Mue
default:
requestURL = `${window.constants.API_URL}/images/random?category=${apiCategory}&quality=${apiQuality}`;
requestURL = `${variables.constants.API_URL}/images/random?category=${apiCategory}&quality=${apiQuality}`;
break;
}
@@ -166,18 +135,12 @@ export default class Background extends React.PureComponent {
data = await (await fetch(requestURL)).json();
} catch (e) {
// if requesting to the API fails, we get an offline image
return this.offlineBackground();
}
return this.setState(offlineBackground());
}
let credit = data.photographer;
let photoURL, photographerURL;
if (backgroundAPI === 'unsplash') {
credit = data.photographer + ` ${this.language.unsplash}`;
photoURL = data.photo_page;
photographerURL = data.photographer_page;
} else if (backgroundAPI === 'pexels') {
credit = data.photographer + ` ${this.language.pexels}`;
if (backgroundAPI === 'unsplash' || backgroundAPI === 'pexels') {
photoURL = data.photo_page;
photographerURL = data.photographer_page;
}
@@ -188,62 +151,78 @@ export default class Background extends React.PureComponent {
currentAPI: backgroundAPI,
photoInfo: {
hidden: false,
credit: credit,
credit: data.photographer,
location: data.location,
camera: data.camera,
url: data.file,
photographerURL: photographerURL,
photoURL: photoURL
photographerURL,
photoURL,
latitude: data.latitude || null,
longitude: data.longitude || null,
// location map token from mapbox
maptoken: data.maptoken || null
}
}
};
this.setState(object);
localStorage.setItem('currentBackground', JSON.stringify(object));
break;
break;
case 'colour':
const customBackgroundColour = localStorage.getItem('customBackgroundColour') || {'angle':'180','gradient':[{'colour':'#ffb032','stop':0}],'type':'linear'};
let gradientSettings = '';
try {
gradientSettings = JSON.parse(customBackgroundColour);
} catch (e) {
const hexColorRegex = /#[0-9a-fA-F]{6}/s;
if (hexColorRegex.exec(customBackgroundColour)) {
// Colour use to be simply a hex colour or a NULL value before it was a JSON object. This automatically upgrades the hex colour value to the new standard. (NULL would not trigger an exception)
gradientSettings = { 'type': 'linear', 'angle': '180', 'gradient': [{ 'colour': customBackgroundColour, 'stop': 0 }] };
localStorage.setItem('customBackgroundColour', JSON.stringify(gradientSettings));
}
const gradient = getGradient();
if (gradient) {
this.setState(gradient);
}
break;
if (typeof gradientSettings === 'object' && gradientSettings !== null) {
return this.gradientStyleBuilder(gradientSettings);
}
break;
case 'random_colour':
case 'random_gradient':
this.setState(randomColourStyleBuilder(type));
break;
case 'custom':
const customBackground = localStorage.getItem('customBackground');
let customBackground;
try {
customBackground = JSON.parse(localStorage.getItem('customBackground'));
} catch (e) {
// move to new format
customBackground = [localStorage.getItem('customBackground')];
localStorage.setItem('customBackground', JSON.stringify(customBackground));
}
// pick random
customBackground = customBackground[Math.floor(Math.random() * customBackground.length)];
// allow users to use offline images
if (offline && !customBackground.startsWith('data:')) {
return this.offlineBackground();
return this.setState(offlineBackground());
}
if (customBackground !== '' && customBackground !== 'undefined') {
this.setState({
if (customBackground !== '' && customBackground !== 'undefined' && customBackground !== [''] && customBackground !== undefined) {
const object = {
url: customBackground,
type: 'custom',
video: this.videoCheck(customBackground),
video: videoCheck(customBackground),
photoInfo: {
hidden: true
}
});
};
this.setState(object);
localStorage.setItem('currentBackground', JSON.stringify(object));
}
break;
break;
case 'photo_pack':
if (offline) {
return this.offlineBackground();
return this.setState(offlineBackground());
}
const photofavourited = JSON.parse(localStorage.getItem('favourite'));
if (photofavourited) {
return setFavourited(photofavourited);
}
const photoPack = JSON.parse(localStorage.getItem('photo_packs'));
@@ -259,8 +238,8 @@ export default class Background extends React.PureComponent {
}
});
}
break;
default:
break;
default:
break;
}
}
@@ -302,7 +281,7 @@ export default class Background extends React.PureComponent {
return element.style.display = 'none';
}
}
// video backgrounds
if (this.state.video === true) {
document.getElementById('backgroundVideo').style.display = 'block';
@@ -325,24 +304,23 @@ export default class Background extends React.PureComponent {
if (backgroundType !== this.state.type || (this.state.type === 'api' && localStorage.getItem('backgroundAPI') !== this.state.currentAPI) || (this.state.type === 'custom' && localStorage.getItem('customBackground') !== this.state.url)) {
return refresh();
}
} else {
if (backgroundType !== this.state.type) {
return refresh();
}
} else if (backgroundType !== this.state.type) {
return refresh();
}
// background effects so we don't get another image again
const backgroundFilter = localStorage.getItem('backgroundFilter');
const backgroundFilterSetting = localStorage.getItem('backgroundFilter');
const backgroundFilter = backgroundFilterSetting && backgroundFilterSetting !== 'none';
if (this.state.video === true) {
document.getElementById('backgroundVideo').style.webkitFilter = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}`;
document.getElementById('backgroundVideo').style.webkitFilter = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilterSetting + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}`;
} else {
element.style.webkitFilter = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}`;
element.style.webkitFilter = `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilterSetting + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}`;
}
}
// uninstall photo pack reverts your background to what you had previously
if (data === 'marketplacebackgrounduninstall' || data === 'backgroundwelcome') {
if (data === 'marketplacebackgrounduninstall' || data === 'backgroundwelcome' || data === 'backgroundrefresh') {
refresh();
}
});
@@ -352,34 +330,41 @@ export default class Background extends React.PureComponent {
}
const interval = localStorage.getItem('backgroundchange');
if (interval && interval !== 'refresh' && localStorage.getItem('backgroundType') === 'api') {
Interval(() => {
try {
document.getElementById('backgroundImage').classList.remove('fade-in');
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
} catch (e) {
// Disregard exception
}
this.getBackground();
}, Number(interval), 'background');
if (interval && interval !== 'refresh') {
const type = localStorage.getItem('backgroundType');
try {
// todo: refactor this mess
const current = JSON.parse(localStorage.getItem('currentBackground'));
const offline = localStorage.getItem('offlineMode')
if (current.url.startsWith('http') && offline === 'false') {
this.setState(current);
} else if (current.url.startsWith('http')) {
this.offlineBackground();
} else {
if (offline === 'false') {
localStorage.removeItem('currentBackground');
return this.getBackground();
if (type === 'api' || type === 'custom') {
Interval(() => {
try {
document.getElementById('backgroundImage').classList.remove('fade-in');
document.getElementsByClassName('photoInformation')[0].classList.remove('fade-in');
} catch (e) {
// Disregard exception
}
this.setState(current);
this.getBackground();
}, Number(interval), 'background');
try {
// todo: refactor this mess
const current = JSON.parse(localStorage.getItem('currentBackground'));
if (current.type !== type) {
this.getBackground();
}
const offline = localStorage.getItem('offlineMode');
if (current.url.startsWith('http') && offline === 'false') {
this.setState(current);
} else if (current.url.startsWith('http')) {
this.setState(offlineBackground());
} else {
if (offline === 'false') {
localStorage.removeItem('currentBackground');
return this.getBackground();
}
this.setState(current);
}
} catch (e) {
this.setBackground();
}
} catch (e) {
this.setBackground();
}
} else {
this.getBackground();
@@ -395,6 +380,10 @@ export default class Background extends React.PureComponent {
this.setBackground();
}
componentWillUnmount() {
EventBus.off('refresh');
}
render() {
if (this.state.video === true) {
const enabled = (setting) => {
@@ -413,8 +402,8 @@ export default class Background extends React.PureComponent {
return (
<>
<div style={{ WebkitFilter: `blur(${localStorage.getItem('blur')}px) brightness(${localStorage.getItem('brightness')}%) ${backgroundFilter ? backgroundFilter + '(' + localStorage.getItem('backgroundFilterAmount') + '%)' : ''}` }} id='backgroundImage'/>
{(this.state.photoInfo.credit !== '') ?
<PhotoInformation className={this.props.photoInformationClass} info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
{(this.state.photoInfo.credit !== '') ?
<PhotoInformation info={this.state.photoInfo} api={this.state.currentAPI} url={this.state.url}/>
: null}
</>
);

View File

@@ -1,14 +1,14 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Star, StarBorder } from '@mui/icons-material';
//import Hotkeys from 'react-hot-keys';
import Tooltip from '../../helpers/tooltip/Tooltip';
import Tooltip from 'components/helpers/tooltip/Tooltip';
import StarIcon from '@material-ui/icons/Star';
import StarIcon2 from '@material-ui/icons/StarBorder';
export default class Favourite extends React.PureComponent {
export default class Favourite extends PureComponent {
buttons = {
favourited: <StarIcon onClick={() => this.favourite()} className='topicons' />,
unfavourited: <StarIcon2 onClick={() => this.favourite()} className='topicons' />
favourited: <Star onClick={() => this.favourite()} className='topicons' />,
unfavourited: <StarBorder onClick={() => this.favourite()} className='topicons' />
}
constructor() {
@@ -24,34 +24,65 @@ export default class Favourite extends React.PureComponent {
this.setState({
favourited: this.buttons.unfavourited
});
window.stats.postEvent('feature', 'Background favourite');
variables.stats.postEvent('feature', 'Background favourite');
} else {
const url = document.getElementById('backgroundImage').style.backgroundImage.replace('url("', '').replace('")', '');
const type = localStorage.getItem('backgroundType');
switch (type) {
case 'colour':
return;
case 'random_colour':
case 'random_gradient':
localStorage.setItem('favourite', JSON.stringify({
type: localStorage.getItem('backgroundType'),
url: document.getElementById('backgroundImage').style.background
}));
break;
default:
const url = document.getElementById('backgroundImage').style.backgroundImage.replace('url("', '').replace('")', '').replace(variables.constants.DDG_IMAGE_PROXY, '');
if (!url) {
return;
if (!url) {
return;
}
if (type === 'custom') {
localStorage.setItem('favourite', JSON.stringify({
type,
url
}));
} else {
// photo information now hides information if it isn't sent, unless if photoinformation hover is hidden
const location = document.getElementById('infoLocation');
const camera = document.getElementById('infoCamera');
localStorage.setItem('favourite', JSON.stringify({
type,
url,
credit: document.getElementById('credit').textContent || '',
location: location ? location.innerText : 'N/A',
camera: camera ? camera.innerText : 'N/A',
resolution: document.getElementById('infoResolution').textContent || '',
}));
}
}
localStorage.setItem('favourite', JSON.stringify({
url: url,
credit: document.getElementById('credit').textContent,
location: document.getElementById('infoLocation').textContent,
camera: document.getElementById('infoCamera').textContent
}));
this.setState({
favourited: this.buttons.favourited
});
window.stats.postEvent('feature', 'Background unfavourite');
variables.stats.postEvent('feature', 'Background unfavourite');
}
}
render() {
const backgroundType = localStorage.getItem('backgroundType');
if (backgroundType === 'colour' || backgroundType === 'custom') {
if (backgroundType === 'colour') {
return null;
}
return <Tooltip title={window.language.modals.main.settings.sections.background.buttons.favourite}>{this.state.favourited}</Tooltip>;
return (
<Tooltip title={variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.background.buttons.favourite')}>
{this.state.favourited}
{/*variables.keybinds.favouriteBackground && variables.keybinds.favouriteBackground !== '' ? <Hotkeys keyName={variables.keybinds.favouriteBackground} onKeyDown={() => this.favourite()} /> : null*/}
</Tooltip>
);
}
}

View File

@@ -1,10 +1,11 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent } from 'react';
import { Fullscreen } from '@mui/icons-material';
//import Hotkeys from 'react-hot-keys';
import Tooltip from '../../helpers/tooltip/Tooltip';
import Tooltip from 'components/helpers/tooltip/Tooltip';
import FullscreenIcon from '@material-ui/icons/Fullscreen';
export default class Maximise extends React.PureComponent {
export default class Maximise extends PureComponent {
constructor() {
super();
this.state = {
@@ -15,7 +16,7 @@ export default class Maximise extends React.PureComponent {
setAttribute(blur, brightness, filter) {
// don't attempt to modify the background if it isn't an image
const backgroundType = localStorage.getItem('backgroundType');
if (backgroundType === 'colour') {
if (backgroundType === 'colour' || backgroundType === 'random_colour' || backgroundType === 'random_gradient') {
return;
}
@@ -43,21 +44,22 @@ export default class Maximise extends React.PureComponent {
});
this.setAttribute(0, 100);
window.stats.postEvent('feature', 'Background maximise');
variables.stats.postEvent('feature', 'Background maximise');
} else {
this.setState({
hidden: false
});
this.setAttribute(localStorage.getItem('blur'), localStorage.getItem('brightness'), true);
window.stats.postEvent('feature', 'Background unmaximise');
variables.stats.postEvent('feature', 'Background unmaximise');
}
}
render() {
return (
<Tooltip title={window.language.modals.main.settings.sections.background.buttons.view}>
<FullscreenIcon onClick={this.maximise} className='topicons' />
<Tooltip title={variables.language.getMessage(variables.languagecode, 'modals.main.settings.sections.background.buttons.view')}>
<Fullscreen onClick={this.maximise} className='topicons' />
{/*variables.keybinds.maximiseBackground && variables.keybinds.maximiseBackground !== '' ? <Hotkeys keyName={variables.keybinds.maximiseBackground} onKeyDown={this.maximise} /> : null*/}
</Tooltip>
);
}

View File

@@ -1,88 +1,157 @@
import React from 'react';
import Info from '@material-ui/icons/Info';
import Location from '@material-ui/icons/LocationOn';
import Camera from '@material-ui/icons/PhotoCamera';
import Resolution from '@material-ui/icons/Crop';
import Photographer from '@material-ui/icons/Person';
import Download from '@material-ui/icons/GetApp';
import variables from 'modules/variables';
import { useState, Fragment } from 'react';
import { Info, LocationOn, PhotoCamera, Crop as Resolution, Person as Photographer, GetApp as Download } from '@mui/icons-material';
//import Hotkeys from 'react-hot-keys';
const toDataURL = async (url) => {
const res = await fetch(url);
return URL.createObjectURL(await res.blob());
};
const formatText = (text) => {
return text.toLowerCase().replaceAll(',', '').replaceAll(' ', '-');
};
const downloadImage = async (info) => {
const link = document.createElement('a');
link.href = await toDataURL(info.url);
// todo: make this a bit cleaner
link.download = `mue-${info.credit.toLowerCase().replaceAll(' ', '-')}-${info.location.toLowerCase().replaceAll(',', '').replaceAll(' ', '-')}.jpg`;
link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.stats.postEvent('feature', 'Background download');
variables.stats.postEvent('feature', 'Background download');
};
export default function PhotoInformation(props) {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
export default function PhotoInformation({ info, url, api }) {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [usePhotoMap, setPhotoMap] = useState(false);
const language = window.language.widgets.background;
if (props.info.hidden === true || !props.info.credit) {
if (info.hidden === true || !info.credit) {
return null;
}
// remove unsplash and pexels text
const photographer = props.info.credit.split(` ${language.unsplash}`)[0].split(` ${language.pexels}`);
const unsplash = variables.language.getMessage(variables.languagecode, 'widgets.background.unsplash');
const pexels = variables.language.getMessage(variables.languagecode, 'widgets.background.pexels');
const photographer = info.credit.split(` ${unsplash}`)[0].split(` ${pexels}`);
let credit = props.info.credit;
let photo = language.credit;
let credit = info.credit;
let photo = variables.language.getMessage(variables.languagecode, 'widgets.background.credit');
// unsplash and pexels credit
if (props.info.photographerURL && props.info.photographerURL !== '' && !props.info.offline && props.api) {
if (props.api === 'unsplash') {
photo = <a href={props.info.photoURL + '?utm_source=mue'} target='_blank' rel='noopener noreferrer'>{language.credit}</a>;
credit = <><a href={props.info.photographerURL} target='_blank' rel='noopener noreferrer'>{photographer}</a> <a href='https://unsplash.com?utm_source=mue' target='_blank' rel='noopener noreferrer'>{language.unsplash}</a></>;
if (info.photographerURL && info.photographerURL !== '' && !info.offline && api) {
if (api === 'unsplash') {
photo = <a href={info.photoURL + '?utm_source=mue'} target='_blank' rel='noopener noreferrer'>{photo}</a>;
credit = <><a href={info.photographerURL} target='_blank' rel='noopener noreferrer'>{info.credit}</a> <a href='https://unsplash.com?utm_source=mue' target='_blank' rel='noopener noreferrer'>{unsplash}</a></>;
} else {
photo = <a href={props.info.photoURL} target='_blank' rel='noopener noreferrer'>{language.credit}</a>;
credit = <><a href={props.info.photographerURL} target='_blank' rel='noopener noreferrer'>{photographer}</a> <a href='https://pexels.com' target='_blank' rel='noopener noreferrer'>{language.pexels}</a></>;
photo = <a href={info.photoURL} target='_blank' rel='noopener noreferrer'>{photo}</a>;
credit = <><a href={info.photographerURL} target='_blank' rel='noopener noreferrer'>{info.credit}</a> <a href='https://pexels.com' target='_blank' rel='noopener noreferrer'>{pexels}</a></>;
}
}
const ddgProxy = (localStorage.getItem('ddgProxy') === 'true');
// get resolution
const img = new Image();
img.onload = (event) => {
setWidth(event.target.width);
setHeight(event.target.height);
};
img.src = (ddgProxy && !info.offline && !url.startsWith('data:')) ? variables.constants.DDG_IMAGE_PROXY + url : url;
// info is still there because we want the favourite button to work
if (localStorage.getItem('photoInformation') === 'false') {
return (
<div className='photoInformation'>
<h1>{photo} <span id='credit'>{credit}</span></h1>
<div style={{ display: 'none' }}>
<span id='infoLocation'>{info.location || 'N/A'}</span>
<span id='infoCamera'>{info.camera || 'N/A'}</span>
<span id='infoResolution'>{width}x{height}</span>
</div>
</div>
);
}
img.src = (localStorage.getItem('ddgProxy') === 'true' && !props.info.offline && !props.url.startsWith('data:')) ? window.constants.DDG_IMAGE_PROXY + props.url : props.url;
const downloadEnabled = (localStorage.getItem('downloadbtn') === 'true') && !info.offline && !info.photographerURL && api;
const downloadBackground = () => {
if (downloadEnabled) {
downloadImage(info);
}
};
const showBackgroundInformation = () => {
const element = document.querySelector('.infoCard');
if (element) {
if (element.style.display === 'none' || element.style.display === '') {
element.style.display = 'block';
} else {
element.style.display = 'none';
}
}
};
const photoMap = () => {
if (localStorage.getItem('photoMap') !== 'true' || !info.latitude || !info.longitude || usePhotoMap === false) {
return null;
}
const zoom = 12;
const tile = `${variables.constants.MAPBOX_URL}/styles/v1/mapbox/streets-v11/static/pin-s+555555(${info.longitude},${info.latitude})/${info.longitude},${info.latitude},${zoom},0/300x100?access_token=${info.maptoken}`;
return (
<Fragment key='photomap'>
<a href={`${variables.constants.OPENSTREETMAP_URL}/?mlat=${info.latitude}&mlon=${info.longitude}`} target='_blank' rel='noopener noreferrer'>
<img className='locationMap' src={tile} alt='location' draggable={false}/>
</a>
<br/>
<span className='mapCopyright'>
<a href='https://www.mapbox.com/about/maps/' target='_blank' rel='noopener noreferrer'> © Mapbox</a>, <a href='https://www.openstreetmap.org/about/' target='_blank' rel='noopener noreferrer'>© OpenStreetMap</a>. <a href='https://www.mapbox.com/map-feedback/' target='_blank' rel='noopener noreferrer'>Improve this map</a>.
</span>
</Fragment>
);
}
// only request map image if the user looks at the photo information
// this is to reduce requests to the api
try {
document.getElementsByClassName('photoInformation')[0].onmouseover = () => {
setPhotoMap(true);
}
} catch (e) {}
return (
<div className='photoInformation'>
<h1>{photo} <span id='credit'>{credit}</span></h1>
{localStorage.getItem('photoInformation') !== 'false' ?
<>
<Info className='photoInformationHover'/>
<div className={props.className || 'infoCard'}>
<Info className='infoIcon'/>
<h1>{language.information}</h1>
<hr/>
<Location/>
<span id='infoLocation'>{props.info.location || 'N/A'}</span>
<Camera/>
<span id='infoCamera'>{props.info.camera || 'N/A'}</span>
<Resolution/>
<span id='infoResolution'>{width}x{height}</span>
<Photographer/>
<span>{photographer}</span>
{(localStorage.getItem('downloadbtn') === 'true') && !props.info.offline && !props.info.photographerURL ?
<>
<Download/>
<span className='download' onClick={() => downloadImage(props.info)}>{language.download}</span>
</> : null}
</div>
</> : null}
<Info className='photoInformationHover'/>
<div className='infoCard'>
<Info className='infoIcon'/>
<h1>{variables.language.getMessage(variables.languagecode, 'widgets.background.information')}</h1>
<hr/>
{photoMap()}
{/* fix console error by using fragment and key */}
{info.location && info.location !== 'N/A' ? <Fragment key='location'>
<LocationOn/>
<span id='infoLocation'>{info.location}</span>
</Fragment> : null}
{info.camera && info.camera !== 'N/A' ? <Fragment key='camera'>
<PhotoCamera/>
<span id='infoCamera'>{info.camera}</span>
</Fragment> : null}
<Resolution/>
<span id='infoResolution'>{width}x{height}</span>
<Photographer/>
<span>{photographer}</span>
{downloadEnabled ?
<>
<Download/>
<span className='download' onClick={() => downloadImage(info)}>{variables.language.getMessage(variables.languagecode, 'widgets.background.download')}</span>
</>
: null}
</div>
{/*variables.keybinds.downloadBackground && variables.keybinds.downloadBackground !== '' ? <Hotkeys keyName={variables.keybinds.downloadBackground} onKeyDown={() => downloadBackground()} /> : null*/}
{/*variables.keybinds.showBackgroundInformation && variables.keybinds.showBackgroundInformation !== '' ? <Hotkeys keyName={variables.keybinds.showBackgroundInformation} onKeyDown={() => showBackgroundInformation()} /> : null*/}
</div>
);
}

View File

@@ -55,11 +55,28 @@
padding: 2px;
}
.locationMap {
height: 100px;
object-fit: cover;
width: 100%;
background-position: center center;
background-repeat: no-repeat;
}
.mapboxLogo {
height: 20px;
width: auto;
}
span,
svg {
font-size: 2em;
}
span {
user-select: text;
}
h1,
.MuiSvgIcon-root {
user-select: none;
@@ -82,7 +99,7 @@
border: none;
}
&:hover,
&:hover,
span {
display: block !important;
}
@@ -95,4 +112,8 @@
opacity: 0.8;
}
}
.mapCopyright {
font-size: 0.9em;
}
}

View File

@@ -1,20 +1,19 @@
import React from 'react';
import variables from 'modules/variables';
import { PureComponent, createRef } from 'react';
import { utcToZonedTime } from 'date-fns-tz';
import EventBus from '../../../modules/helpers/eventbus';
import dtf from '../../../modules/helpers/date';
import { nth, convertTimezone } from 'modules/helpers/date';
import EventBus from 'modules/helpers/eventbus';
import './greeting.scss';
export default class Greeting extends React.PureComponent {
export default class Greeting extends PureComponent {
constructor() {
super();
this.state = {
greeting: ''
};
this.timer = undefined;
this.language = window.language.widgets.greeting;
this.greeting = createRef();
}
doEvents(time, message) {
@@ -28,13 +27,13 @@ export default class Greeting extends React.PureComponent {
// If it's December 25th, set the greeting string to "Merry Christmas"
if (month === 11 && date === 25) {
message = this.language.christmas;
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.christmas');
// If the date is January 1st, set the greeting string to "Happy new year"
} else if (month === 0 && date === 1) {
message = this.language.newyear;
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.newyear');
// If it's October 31st, set the greeting string to "Happy Halloween"
} else if (month === 9 && date === 31) {
message = this.language.halloween;
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.halloween');
}
return message;
@@ -50,28 +49,28 @@ export default class Greeting extends React.PureComponent {
this.timer = setTimeout(() => {
let now = new Date();
const timezone = localStorage.getItem('timezone');
if (timezone) {
now = utcToZonedTime(now, timezone);
if (timezone && timezone !== 'auto') {
now = convertTimezone(now, timezone);
}
const hour = now.getHours();
// Set the default greeting string to "Good evening"
let message = this.language.evening;
let message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.evening');
// If it's before 12am, set the greeting string to "Good morning"
if (hour < 12) {
message = this.language.morning;
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.morning');
// If it's before 6pm, set the greeting string to "Good afternoon"
} else if (hour < 18) {
message = this.language.afternoon;
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.afternoon');
}
// Events
message = this.doEvents(now, message);
// Events and custom
const custom = localStorage.getItem('defaultGreetingMessage');
if (custom === 'false') {
message = '';
} else {
message = this.doEvents(now, message);
}
// Name
@@ -84,18 +83,23 @@ export default class Greeting extends React.PureComponent {
}
}
if (custom === 'false') {
const birthday = localStorage.getItem('birthdayenabled');
if (custom === 'false' && birthday !== 'true') {
name = name.replace(',', '');
}
// Birthday
const birth = new Date(localStorage.getItem('birthday'));
if (localStorage.getItem('birthdayenabled') === 'true' && birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true') {
const text = this.language.birthday.split(' ');
message = `${text[0]} ${dtf.nth(this.calculateAge(birth))} ${text[1]}`;
} else {
message = this.language.birthday;
if (birthday === 'true') {
const birth = new Date(localStorage.getItem('birthday'));
if (birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true') {
const text = variables.language.getMessage(variables.languagecode, 'widgets.greeting.birthday').split(' ');
message = `${text[0]} ${nth(this.calculateAge(birth))} ${text[1]}`;
} else {
message = variables.language.getMessage(variables.languagecode, 'widgets.greeting.birthday');
}
}
}
@@ -111,34 +115,34 @@ export default class Greeting extends React.PureComponent {
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'greeting' || data === 'timezone') {
const element = document.querySelector('.greeting');
if (localStorage.getItem('greeting') === 'false') {
return element.style.display = 'none';
return this.greeting.current.style.display = 'none';
}
this.timer = null;
this.getGreeting(0);
element.style.display = 'block';
element.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
this.greeting.current.style.display = 'block';
this.greeting.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
}
});
// this comment can apply to all widget zoom features apart from the general one in the Accessibility section
// in a nutshell: 1.6 is the current font size and we do "localstorage || 100" so we don't have to try that 4.0 -> 5.0 thing again
document.querySelector('.greeting').style.fontSize = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
this.greeting.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomGreeting') || 100) / 100)}em`;
this.getGreeting(0);
}
componentWillUnmount() {
EventBus.remove('refresh');
EventBus.off('refresh');
}
render() {
return <h1 className='greeting'>
{this.state.greeting}
</h1>;
return (
<h1 className='greeting' ref={this.greeting}>
{this.state.greeting}
</h1>
);
}
}

View File

@@ -3,4 +3,6 @@
font-size: 1.6em;
cursor: initial;
user-select: none;
--shadow-shift: 0.2rem;
}

View File

@@ -0,0 +1,42 @@
import { PureComponent, createRef } from 'react';
import EventBus from 'modules/helpers/eventbus';
import './message.scss';
export default class Message extends PureComponent {
constructor(props) {
super(props);
this.state = {
messageText: ''
};
this.message = createRef();
}
componentDidMount() {
EventBus.on('refresh', (data) => {
if (data === 'message') {
if (localStorage.getItem('message') === 'false') {
return this.message.current.style.display = 'none';
}
this.message.current.style.display = 'block';
this.message.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomMessage') || 100) / 100)}em`;
}
});
this.message.current.style.fontSize = `${1.6 * Number((localStorage.getItem('zoomMessage') || 100) / 100)}em`;
const messages = JSON.parse(localStorage.getItem('messages')) || [];
this.setState({
messageText: messages[Math.floor(Math.random() * messages.length)]
});
}
render() {
return (
<h2 className='message' ref={this.message}>
{this.state.messageText}
</h2>
);
}
}

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