Compare commits

...

187 Commits

Author SHA1 Message Date
Aj - Thomas
7b6fd5ed6a 🚀 🔖 Release v0.3.0 2022-05-15 19:50:17 +02:00
Walkx
8603395329 ✏️ Remove space because not everyone is french 2022-05-15 19:43:16 +02:00
Thomas "ajnart" Camlong
468b1912b8 🔖 Upgrade Homarr to v0.3.0 2022-05-15 19:32:26 +02:00
Thomas "ajnart" Camlong
c243256180 💄 Improve layout styling 2022-05-15 19:32:02 +02:00
Thomas "ajnart" Camlong
a9370881f4 💄 Improve styling of modules 2022-05-15 19:17:16 +02:00
Thomas "ajnart" Camlong
81a63cd1b7 👷 Add back raw dev tag 2022-05-15 19:13:42 +02:00
Thomas "ajnart" Camlong
f03219fd42 👷 Fix branches for CI pushes 2022-05-15 18:59:27 +02:00
Walkx
2e8f3d7d1f Merge pull request #102 from ajnart/better-tooltip
✏️ Improves the searchbar tooltip
2022-05-15 18:52:23 +02:00
Walkx
9ddb08cc9c ✏️ Improves the searchbar tooltip 2022-05-15 15:57:58 +02:00
Walkx
d44eed6581 🔥 Removes fixed known issues 2022-05-15 14:46:46 +02:00
Thomas "ajnart" Camlong
59b9baa579 Add custom search querry url in settings menu 2022-05-15 13:09:25 +02:00
Thomas "ajnart" Camlong
027ac94e80 🍱 Update logo and favicon 2022-05-15 12:42:53 +02:00
Aj - Thomas
b1cec402c3 👷 Remove test name from GitHub CI 2022-05-14 22:27:15 +02:00
Aj - Thomas
f2a7f83e12 ️ Work on responsiveness for the AppShelf
Fixes #1, Fixes #42, Fixes #82, Fixes #85
2022-05-14 21:45:54 +02:00
Thomas "ajnart" Camlong
477ff8241e 📱 More work on responsiveness 2022-05-14 21:42:30 +02:00
Thomas "ajnart" Camlong
95bae5929c 💄 Improve minor styles 2022-05-14 21:42:11 +02:00
Thomas "ajnart" Camlong
4545a6bbf5 Rework header, footer, logo
Fixes Align Homarr logo to the left #96
2022-05-14 21:41:30 +02:00
Aj - Thomas
b3a97431b3 Merge pull request #94 from ajnart/new-logo
🍱️ Update logo and add better accessibility
2022-05-14 20:02:13 +02:00
Aj - Thomas
b757046de8 Merge pull request #91 from c00ldude1oo/master
📝 Add build status badges
2022-05-14 20:00:40 +02:00
Walkx
0d1a1b899a 🧑‍💻 Adds template for ideas 2022-05-14 16:50:54 +02:00
Chris
71be4101a5 📝 Add Badges. Moved under name
Removed docker dev build status badge
Added docker pulls(downloads) badge
Added Release version badge
moved them under name
2022-05-14 10:27:40 -04:00
Walkx
db453d0f74 ✏️ Fixes yaml issue
Removes the : in the description
2022-05-14 13:56:10 +02:00
Walkx
1d450428c9 🧑‍💻 Improves feature request issue template 2022-05-14 13:55:20 +02:00
WalkxCode
e16601d113 ️ (favicon): Changes the favicon to .png
This improves the accessibility because some browsers don't support .svg. And some services fetch their icons from favicons!
2022-05-14 13:45:22 +02:00
WalkxCode
56b52d0808 🍱 (logo): Adds new logo & changes favicon
favicon will need to be changed from .svg to .png in code
2022-05-14 13:42:32 +02:00
Walkx
3fc0a2c64f 🧑‍💻 Adds back label & idiot check 2022-05-14 13:35:29 +02:00
Thomas "ajnart" Camlong
f3f2006f14 💄 Improving style 2022-05-14 11:40:05 +02:00
Thomas "ajnart" Camlong
2139f48df3 Work on responsiveness for the AppShelf 2022-05-14 11:19:04 +02:00
Thomas "ajnart" Camlong
09483ada01 👷 Update CI image name for easier deployment 2022-05-14 11:19:04 +02:00
Thomas "ajnart" Camlong
c593334be8 👷 Update CI image name for easier deployment 2022-05-14 11:18:50 +02:00
Thomas "ajnart" Camlong
af7d078293 👷 Fix CI 2022-05-14 10:28:24 +02:00
Thomas "ajnart" Camlong
96ab5dd9a7 🐛 Fix storybook and remove Jest 2022-05-14 10:10:18 +02:00
Thomas "ajnart" Camlong
9a3ef24619 💄 SearchBar styling 2022-05-14 10:09:22 +02:00
Thomas "ajnart" Camlong
3287752e45 ⚰️ Remove dead code 2022-05-14 10:08:26 +02:00
Chris
1c560f9a5b 📝 Add build status badges
Added build status badges 
made image bit bigger
2022-05-14 02:56:39 -04:00
Aj - Thomas
534cf3571a 🐳 Fix docker build 2022-05-14 01:29:50 +02:00
Aj - Thomas
cf17aa61cc Update basic layout 2022-05-14 01:19:12 +02:00
Aj - Thomas
32f81cefe7 🏗️ 💥 Change the whole folder structure.
Now using src as a subfolder to the source files
2022-05-14 01:14:56 +02:00
Aj - Thomas
15bb08e5f3 Merge branch 'master' into dev 2022-05-14 00:56:38 +02:00
Aj - Thomas
0a6346b383 📝 Update README.md 2022-05-14 00:55:04 +02:00
Walkx
43ee6899ae 📝 Adds known issues 2022-05-14 00:55:04 +02:00
Walkx
5159edbea1 📝 Adds documentation for volume mounting 2022-05-14 00:55:04 +02:00
Aj - Thomas
79dd1f3296 🔥 Remove labels from issue templates 2022-05-14 00:55:04 +02:00
Aj - Thomas
7a3ac58e4d 🔥 Remove labels from issue templates 2022-05-14 00:55:03 +02:00
Chris
527eb373a9 👷 Add dev builder
New workflow to build dev branch on push/pulls and upload to ghcr.io with dev tag
2022-05-14 00:52:11 +02:00
Aj - Thomas
16ecf59196 📝 Adds known issues
Thanks to @walkxcode
2022-05-13 23:29:49 +02:00
Aj - Thomas
66dd59f076 📝 Update README.md 2022-05-13 23:29:30 +02:00
Aj - Thomas
e95168288c 📝 Adds documentation for volume mounting
Thanks to @walkxcode
2022-05-13 23:26:53 +02:00
Aj - Thomas
4f5121b337 🔥 Remove labels from issue templates 2022-05-13 22:14:12 +02:00
Aj - Thomas
f0152d84d8 🔥 Remove labels from issue templates 2022-05-13 22:13:57 +02:00
Walkx
cc324dd8ec 📝 Adds known issues 2022-05-13 20:56:34 +02:00
Walkx
d332245cfc 📝 Adds documentation for volume mounting 2022-05-13 18:58:06 +02:00
Aj - Thomas
ac9ebe4160 Fix docker image volume mounting
Fixes #62 [🐛 Bug] Permission denied when writing configs
2022-05-13 18:14:08 +02:00
Aj - Thomas
b51e8861b7 Update README.md 2022-05-13 17:31:51 +02:00
Aj - Thomas
51fcba7109 Update README.md 2022-05-13 17:29:03 +02:00
Aj - Thomas
ce3afc6916 Delete preview.png 2022-05-13 17:19:43 +02:00
Aj - Thomas
fb1d2c3e80 Merge pull request #71 from ajnart/dev-experience
🧑‍💻 Aims to get 100% of the Community Standards
2022-05-13 16:58:58 +02:00
Aj - Thomas
d372b66e44 Update feature-request.yml 2022-05-13 16:24:02 +02:00
Walkx
4417168ef4 🔥 Remove label from title 2022-05-13 16:23:29 +02:00
Walkx
9a4fdc6f01 🔥 Remove label from title 2022-05-13 16:23:17 +02:00
Aj - Thomas
405537202d Update module.yml 2022-05-13 16:20:44 +02:00
Aj - Thomas
873d590606 Update bug.yml 2022-05-13 16:20:32 +02:00
Aj - Thomas
463670ebdf Update feature-request.yml 2022-05-13 16:20:13 +02:00
Walkx
eeb3d4eb23 🔥 Remove auto-assign for bugs 2022-05-13 15:32:49 +02:00
Walkx
651003f1d7 📝 Adds preview of Homarr
(Temporary until we have good video preview (.mov or .gif))
2022-05-13 14:17:58 +02:00
Walkx
2ee923d773 📝 Adds preview for docs 2022-05-13 14:16:54 +02:00
Aj - Thomas
e9c758b63d Create module.yml 2022-05-13 14:10:18 +02:00
Walkx
6a462f640a 📝 Adds set of rules for modules 2022-05-13 13:50:23 +02:00
Walkx
6120eb53cd 🧑‍💻 Adds pull request template 2022-05-13 13:41:56 +02:00
Walkx
a3a3846848 📝 Adds Contributor Covenant Code of Conduct 2022-05-13 13:35:01 +02:00
Walkx
f547c56c1f 📝 Updates Readme to point to Contribution Guidelines 2022-05-13 13:32:00 +02:00
Walkx
242d09e932 🧑‍💻 Adds contribution guide 2022-05-13 13:29:32 +02:00
Aj - Thomas
036922328f Merge pull request #66 from ajnart/note
fix: Make note look better
2022-05-13 13:16:20 +02:00
Aj - Thomas
c7257b8c2f Merge pull request #65 from ajnart/discordcta
fix: Center align Discord CTA
2022-05-13 13:16:00 +02:00
Walkx
eecd8b0faa fix: Make note look better 2022-05-13 13:06:13 +02:00
Walkx
63940ab5ff fix: Center align Discord CTA 2022-05-13 13:03:33 +02:00
Aj - Thomas
62f62db17c Update README.md 2022-05-13 10:37:36 +02:00
Aj - Thomas
7618bc4e1f Merge pull request #63 from ajnart/dev
Adding persistent storage
2022-05-13 04:27:33 +02:00
Aj - Thomas
7937a16a9d Update package.json 2022-05-13 04:10:50 +02:00
Aj - Thomas
f3de28642f Update next.config.js 2022-05-13 04:10:23 +02:00
Walkx
664374e9cc fix: Update ports 2022-05-13 04:10:09 +02:00
Aj - Thomas
d32c384232 Update docker.yml 2022-05-13 04:05:31 +02:00
Aj - Thomas
81149369df Update default configs and version 2022-05-13 03:54:32 +02:00
Aj - Thomas
5b453c829e Delete tryconfig.tsx 2022-05-13 02:44:05 +02:00
Aj - Thomas
efd564d6db Delete cringe.json 2022-05-13 02:43:53 +02:00
Aj - Thomas
35df8c6d26 Merge branch 'dev' 2022-05-13 02:42:18 +02:00
Walkx
a822e44b16 feat: Add link to discussions page 2022-05-13 02:40:47 +02:00
Aj - Thomas
4a0a717d99 Merge pull request #60 from c00ldude1oo/fixaction
🛠️ fix: Docker action build
2022-05-13 02:38:12 +02:00
Aj - Thomas
b95453b614 Merge pull request #61 from ajnart/multiple-configs
Multiple configs
2022-05-13 02:37:33 +02:00
Chris
c587bd4bf1 Fix docker build 2022-05-12 20:34:05 -04:00
Aj - Thomas
770b19243d Changing the torrent search url
For @WalkxCode
2022-05-12 23:53:29 +02:00
Aj - Thomas
3cbc7683e8 Fix default search engine in settings menu 2022-05-12 23:05:55 +02:00
Aj - Thomas
7040760a84 Add ConfigChanger indicator 2022-05-12 23:05:55 +02:00
Aj - Thomas
ff2fd2febd Add a ConfigChanger in the settings menu 2022-05-12 23:05:54 +02:00
Aj - Thomas
84b7d4bbdc Fix cookie in Upload form and fix download name 2022-05-12 23:05:54 +02:00
Aj - Thomas
98e4da5a3b Remove JSON and add 🍪 Tryconfig save cookies 2022-05-12 23:05:54 +02:00
Aj - Thomas
3ce9c98e03 🎉 Persistent config 🎉
After sweat and tears... It's there!
2022-05-12 23:05:54 +02:00
Aj - Thomas
91f636ca97 Basic backend support and config loading from file 2022-05-12 23:05:54 +02:00
Aj - Thomas
e61e986028 Styling modules 2022-05-12 23:04:59 +02:00
Walkx
85a863e1eb feat: Add link to discussions page 2022-05-12 14:36:04 +02:00
Aj - Thomas
fce9b297df Merge pull request #51 from ajnart/dev
Version 0.1.6 : Rename MyHomePage to Homarr
2022-05-12 14:26:19 +02:00
Aj - Thomas
e3af7629aa Rename MyHomePage to Homarr 2022-05-12 14:24:15 +02:00
Aj - Thomas
5dee33284d Merge pull request #50 from walkxcode/devel
Multiple QoL updates!
2022-05-12 14:19:42 +02:00
Walkx
cc3e1ce848 fix: Fix type error 2022-05-12 14:14:48 +02:00
Aj - Thomas
74e735608f Merge pull request #52 from ajnart/master
Dev
2022-05-12 14:13:56 +02:00
Aj - Thomas
f2f2a3df39 Fix error in GitHub CI 2022-05-12 14:10:55 +02:00
Aj - Thomas
bb61a19c16 Fix error in GitHub CI 2022-05-12 14:10:55 +02:00
Walkx
2c225c308d feat: Add Back to Top buttons 2022-05-12 14:10:40 +02:00
Aj - Thomas
153693b3e8 Trying to fix CI tags 2022-05-12 13:56:02 +02:00
Aj - Thomas
a1094be40b Update docker.yml 2022-05-12 13:56:02 +02:00
Aj - Thomas
bcb9669e44 Update docker.yml 2022-05-12 13:56:02 +02:00
Aj - Thomas
07c088507d Update docker.yml 2022-05-12 13:56:02 +02:00
Chris
d5377423b1 Revert "Change to alpine base image"
This reverts commit 49241f5614.
2022-05-12 13:56:02 +02:00
Chris
e38c4f6be0 Update docker.yml
Add cache for yarn and nextjs.
`yarn install` takes about 25s off
`yarn export` takes about 40s
2022-05-12 13:56:01 +02:00
Chris
8469d1c477 Change nginx base to alpine
Changed nginx to use alpine instead of debian/ubuntu
Image size is 93MB smaller and build time is shorter.
2022-05-12 13:56:01 +02:00
WalkxCode
99a9aef4a3 feat: Add Contribution guide 2022-05-12 13:50:14 +02:00
Walkx
8f3ba2a709 Merge branch 'master' into devel 2022-05-12 13:15:39 +02:00
WalkxCode
4c042ccb05 feat: Multiple QoL updates 2022-05-12 13:07:47 +02:00
Aj - Thomas
8ae8cb7d5a Trying to fix CI tags 2022-05-12 10:04:05 +02:00
Aj - Thomas
0b34abc7d5 Update docker.yml 2022-05-12 09:56:05 +02:00
Aj - Thomas
07869ae3ed Update docker.yml 2022-05-12 09:49:12 +02:00
Aj - Thomas
91a6b6972a Update docker.yml 2022-05-12 09:44:51 +02:00
Aj - Thomas
9a6607f736 Merge pull request #46 from c00ldude1oo/action-speedup
Add cache for Action to speed up builds.
2022-05-12 09:36:07 +02:00
Aj - Thomas
36e308d11d Merge branch 'master' into dev 2022-05-12 09:23:06 +02:00
Aj - Thomas
9597a8bb93 Merge pull request #45 from c00ldude1oo/alpine
Change nginx base to alpine
2022-05-12 09:22:51 +02:00
Aj - Thomas
fac7dd1fae Fix error in GitHub CI 2022-05-12 09:21:16 +02:00
Aj - Thomas
d7a052c1b1 Fix error in GitHub CI 2022-05-12 09:21:16 +02:00
Chris
bdb9711c19 Change nginx base to alpine
Changed nginx to use alpine instead of debian/ubuntu
Image size is 93MB smaller and build time is shorter.
2022-05-12 02:18:37 -04:00
Chris
1f6b2756c4 Revert "Change to alpine base image"
This reverts commit 49241f5614.
2022-05-12 02:05:59 -04:00
Chris
3db65dbb1f Update docker.yml
Add cache for yarn and nextjs.
`yarn install` takes about 25s off
`yarn export` takes about 40s
2022-05-12 02:05:21 -04:00
Aj - Thomas
0c7606793a Add new update notification on main dashboard 2022-05-12 02:41:13 +02:00
Chris
5d01fb353b Merge branch 'master' of https://github.com/c00ldude1oo/myhomepage-alpine 2022-05-11 18:50:10 -04:00
Chris
49241f5614 Change to alpine base image 2022-05-11 18:49:45 -04:00
Aj - Thomas
9282ea5f0f Fix error in GitHub CI 2022-05-11 21:43:17 +02:00
Aj - Thomas
92e4474269 Fix error in GitHub CI 2022-05-11 21:41:20 +02:00
Aj - Thomas
d2ca47f761 Merge pull request #34 from ajnart/dev
v0.1.5
2022-05-11 21:27:53 +02:00
Aj - Thomas
7493d83147 Merge branch 'master' into dev 2022-05-11 21:25:52 +02:00
WalkxCode
86590e7279 feat: Move to Dashboard Icons 2022-05-11 15:10:58 +02:00
WalkxCode
c914b54a43 docs: Update docs progress 2022-05-11 15:10:05 +02:00
Aj - Thomas
d05d9b6e81 Add needs to completion after build 2022-05-11 10:53:44 +02:00
Aj - Thomas
161319ea4e Add needs to completion after build 2022-05-11 10:25:47 +02:00
Aj - Thomas
22564e5593 Add needs to completion after build 2022-05-11 10:20:47 +02:00
Aj - Thomas
a5b3ccc756 Add needs to completion after build 2022-05-11 10:16:28 +02:00
Aj - Thomas
838fc3ae3a Add needs to completion after build 2022-05-11 10:08:02 +02:00
Aj - Thomas
16ffd545a6 Trying a completely new Docker CI 2022-05-11 10:06:29 +02:00
Aj - Thomas
79311b794e Add load flag to Github Ci 2022-05-11 10:01:19 +02:00
Aj - Thomas
7b6ecdf3fc Add load flag to Github Ci 2022-05-11 09:51:05 +02:00
Aj - Thomas
a3d6d876a8 Add load flag to Github Ci 2022-05-11 09:46:50 +02:00
Aj - Thomas
45cb417e76 Add load flag to Github Ci 2022-05-11 09:46:07 +02:00
Aj - Thomas
c8cbb37247 Add load flag to Github Ci 2022-05-11 09:32:03 +02:00
Aj - Thomas
4210a455c6 Trying to implement manual restarts for the CI 2022-05-11 09:19:28 +02:00
Aj - Thomas
7ba85aeb4e Trying to implement manual restarts for the CI 2022-05-11 09:13:36 +02:00
Aj - Thomas
49e454b6ce Trying to implement manual restarts for the CI 2022-05-11 09:12:43 +02:00
Aj - Thomas
f526883544 Trying to implement manual restarts for the CI 2022-05-11 09:10:50 +02:00
Aj - Thomas
a4163e0eb5 Trying to implement manual restarts for the CI 2022-05-11 09:10:44 +02:00
Aj - Thomas
ef0d4fddce Trying to implement multi platform build 2022-05-11 09:10:31 +02:00
Aj - Thomas
60cc1883ff Trying to implement manual restarts for the CI 2022-05-11 09:10:00 +02:00
Aj - Thomas
47c7f46e28 Trying to implement manual restarts for the CI 2022-05-11 09:06:45 +02:00
Aj - Thomas
80ab928c20 Trying to implement multi platform build 2022-05-11 08:59:37 +02:00
Aj - Thomas
846f2c8670 Update dependencies
Upgrade Mantine to v4.2.4 (from v4.2.1)
2022-05-11 08:56:21 +02:00
Aj - Thomas
d58465142c Add regex matching for add shelf item
Resolves not fetching when the users didn't put a / at the end of the service url
2022-05-11 08:55:38 +02:00
Aj - Thomas
a7f4187a82 Remove year in Date module
You don't know the year we're in?
2022-05-10 21:00:18 +02:00
Aj - Thomas
b92462d056 Remove useless code
Old code supposed to interract with qBittorrent
2022-05-10 20:59:57 +02:00
Aj - Thomas
034ef3fd8f Add notifications for Calendar module 2022-05-10 20:57:04 +02:00
Aj - Thomas
d5b17d5303 Improve Module matching
More TypeScript magic ! 🪄
2022-05-10 20:56:48 +02:00
Aj - Thomas
f03bcde983 Update package.json
Update version and scripts
2022-05-10 20:39:54 +02:00
Aj - Thomas
f3478587b1 Improve default arguments and type checking 2022-05-10 20:36:38 +02:00
Aj - Thomas
2c461a6695 Add a module enabler
Add module enabler in settings.
This loops over all the exported Modules in the components/modules folder and renders them. (Interpreted language magic/ metaclasses)
2022-05-10 20:33:11 +02:00
Aj - Thomas
1245f97e72 Merge pull request #33 from ajnart/dev
Merge dev into master for v0.1.4
2022-05-10 19:23:17 +02:00
Aj - Thomas
55fd4fff76 Update README.md 2022-05-10 19:23:02 +02:00
Aj - Thomas
b52755c87f Update README.md 2022-05-10 19:07:55 +02:00
Aj - Thomas
a0d86e2914 Linting and formatting
CRLF > LF is REALLY annoying
2022-05-10 19:03:41 +02:00
Aj - Thomas
5d80d36be3 Fix AppShelf circular imports 2022-05-10 19:02:16 +02:00
Aj - Thomas
72f19d450c Basic layout styling 2022-05-10 18:58:21 +02:00
Aj - Thomas
6024391414 AppShelf styling 2022-05-10 18:58:13 +02:00
Aj - Thomas
905f445641 Use custom theme
This is a step thowards personalisation for Changeable wallpaper feature. #32
2022-05-10 18:57:41 +02:00
Aj - Thomas
2462671700 Add Module wrapper 2022-05-10 18:57:04 +02:00
Aj - Thomas
0c0f8247d8 Update Calendar Module styling 2022-05-10 18:56:50 +02:00
Aj - Thomas
fa45eb3b3b Add Date module 2022-05-10 18:56:29 +02:00
Aj - Thomas
b8ce86d783 Update module interface 2022-05-10 18:56:16 +02:00
Aj - Thomas
cc58fbe263 Add GitHub Action auto deploy to Github containers
Ajnart/issue18
2022-05-10 00:21:39 +02:00
Aj - Thomas
524c143a60 Update README.md 2022-05-09 23:27:30 +02:00
Aj - Thomas
50448c9fb6 Merge pull request #26 from ajnart/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-05-09 22:10:38 +02:00
dependabot[bot]
0f1a948682 Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 20:09:19 +00:00
Aj - Thomas
8f71c898a2 Merge pull request #25 from ajnart/dev
Merge dev into master
2022-05-09 22:08:16 +02:00
Aj - Thomas
7be22833b0 Merge pull request #22 from ajnart/ajnart/issue21
Add darkmode switch in settings menu #21
2022-05-09 22:06:26 +02:00
Aj - Thomas
43c0465e52 Add darkmode switch in settings menu #21 2022-05-09 01:35:11 +02:00
84 changed files with 2410 additions and 871 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.git

50
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: 🐛 Bug Report
description: Report something that's broken, or not working like intented!
title: '<title>'
labels: ['🐛 Bug']
body:
- type: dropdown
id: environment
attributes:
label: Environment
description: How have you deployed Homarr?
options:
- Docker
- NodeJS
- Cloud Service (Static)
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of Homarr are you running?
placeholder: 0.1.0
validations:
required: false
- type: textarea
id: repro
attributes:
label: Describe the problem
description: Please describe the problem exactly, how to reproduce it, actual results, and expected results.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Additional info
description: Logs? Screenshots? More info?
validations:
required: false
- type: checkboxes
id: idiot-check
attributes:
label: Please tick the boxes
description: Before submitting, please ensure that
options:
- label: You've read the [docs](https://github.com/ajnart/homarr#readme)
required: true
- label: You've checked for [duplicate issues](https://github.com/ajnart/homarr/issues)
required: true
- label: You've tried to debug yourself
required: true

View File

@@ -0,0 +1,34 @@
name: ✨ Feature Request
description: Request a feature to help improve Homarr!
title: '<title>'
labels: ['✨ Feature']
body:
- type: textarea
id: feature
attributes:
label: Description
description: Describe the feature you would like to see. Tell us how you imagine it and try to provide as much useful information as possible. **PLEASE** use images/screenshots, include details about X & Y when requesting changes like X & Y service does, make your description atleast 300 characters. Having an unclear issue with too little detail will result in your issue being marked as invalid and closed.
placeholder: An outline of the feature you would like to see implemented, include as much detail as possible!
validations:
required: true
- type: dropdown
id: priority
attributes:
label: Priority
description: How urgent is the development of this feature?
options:
- Low (Nice-to-have)
- Medium (Would be very useful)
- High (App breaking feature)
validations:
required: true
- type: checkboxes
id: idiot-check
attributes:
label: Please tick the boxes
description: Before submitting, please ensure that
options:
- label: You've read the [docs](https://github.com/ajnart/homarr#readme)
required: true
- label: You've checked for [duplicate issues](https://github.com/ajnart/homarr/issues)
required: true

23
.github/ISSUE_TEMPLATE/idea.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: 🤔 Idea
description: Tell us your idea! We may implement it.
title: '<title>'
labels: ['🤔 Idea']
body:
- type: textarea
id: feature
attributes:
label: Description
description: Tell us your idea! Please add as much details as possible.
placeholder: Maybe move ... to ...! Maybe add the version of Homarr somewhere...! Etc.
validations:
required: true
- type: checkboxes
id: idiot-check
attributes:
label: Please tick the boxes
description: Before submitting, please ensure that
options:
- label: You've read the [docs](https://github.com/ajnart/homarr#readme)
required: true
- label: You've checked for [duplicate issues](https://github.com/ajnart/homarr/issues)
required: true

23
.github/ISSUE_TEMPLATE/module.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: 🏗️ Module request
description: Request for a module to be added / an integration with you favourite service !
title: '<title>'
body:
- type: textarea
id: name
attributes:
label: Name the integration
description: Please describe the name of the Module/Integration you want to see and info that could help us with adding it. For example screenshots/mockups for inspiration. API links or already existing JavaScript/TypeScript integration of the API
validations:
required: true
- type: checkboxes
id: idiot-check
attributes:
label: Please tick the boxes
description: Before submitting, please ensure that
options:
- label: You've read the [docs](https://github.com/ajnart/homarr#readme)
required: true
- label: You've checked for [duplicate issues](https://github.com/ajnart/homarr/issues)
required: true
- label: You're not just putting an idea out there and actually give usefull information about how to implement your module idea
required: true

23
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,23 @@
*Thank you for contributing to Homarr! So that your Pull Request can be handled effectively, please populate the following fields (delete sections that are not applicable)*
### Category
> One of: Bugfix / Feature / Code style update / Refactoring Only / Build related changes / Documentation / Other (Please specify!)
### Overview
> Briefly outline your new changes...
### Issue Number _(if applicable)_
> Related issue: #00
### New Vars _(if applicable)_
> If you've added any new build scripts, environmental variables, config file options, dependency please outline here.
### Screenshot _(if applicable)_
> If you've introduced any significant UI changes, please include a screenshot.
### Code Quality Checklist _(Please complete)_
- [ ] All changes are backwards compatible
- [ ] There are no (new) build warnings or errors
- [ ] _(If a new config option is added)_ Attribute is outlined in the schema and documented
- [ ] _(If a new dependency is added)_ Package is essential, and has been checked out for security or performance
- [ ] Bumps version, if new feature added

View File

@@ -1,64 +1,115 @@
name: Demo Push
name: Master docker CI
# Workflow to build and publish docker image
on:
on:
push:
branches: [master]
tags:
- v*
workflow_dispatch:
env:
IMAGE_NAME: mhp
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
# Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/
build:
yarn_install_and_build:
# Will run yarn install && yarn build
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
- uses: actions/checkout@v3
- name: Setup
uses: actions/setup-node@v3
- name: Checkout
uses: actions/checkout@v3
- name: Get yarn cache directory path
# to help speed up build times
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
# to help speed up build times
uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-
- name: Nextjs cache
uses: actions/cache@v2
with:
# See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
~/.npm
${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: yarn install --frozen-lockfile
- run: yarn export
- uses: actions/cache@v2
- run: yarn build
- name: Cache build output
# to copy needed files to docker build job
uses: actions/cache@v2
id: restore-build
with:
path: ./out/
path: |
./next.config.js
./pages/
./public/
./.next/static/
./.next/standalone/
./packages.json
key: ${{ github.sha }}
push:
docker_image_build_and_push:
needs: [yarn_install_and_build]
runs-on: ubuntu-latest
needs: [build]
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v2
- uses: actions/cache@v2
id: restore-build
with:
path: ./out/
path: |
./next.config.js
./pages/
./public/
./.next/static/
./.next/standalone/
./packages.json
key: ${{ github.sha }}
- name: Build image
run: docker build . --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}"
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# list of Docker images to use as base name for tags
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: |
type=raw,value=latest
type=pep440,pattern={{version}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
- name: Push image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "master" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:latest
docker push $IMAGE_ID:latest
- name: Build and push
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

116
.github/workflows/docker_dev.yml vendored Normal file
View File

@@ -0,0 +1,116 @@
name: Development CI
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [dev]
pull_request:
workflow_dispatch:
inputs:
tags:
requierd: true
description: 'Tags to deploy to'
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
# Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/
yarn_install_and_build:
runs-on: ubuntu-latest
steps:
- name: Setup
uses: actions/setup-node@v3
- name: Checkout
uses: actions/checkout@v3
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-
- name: Nextjs cache
uses: actions/cache@v2
with:
# See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
~/.npm
${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: yarn install --frozen-lockfile
- run: yarn build
- name: Cache build output
uses: actions/cache@v2
id: restore-build
with:
path: |
./next.config.js
./pages/
./public/
./.next/static/
./.next/standalone/
./packages.json
key: ${{ github.sha }}
docker_image_build_and_push:
needs: [yarn_install_and_build]
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/cache@v2
id: restore-build
with:
path: |
./next.config.js
./pages/
./public/
./.next/static/
./.next/standalone/
./packages.json
key: ${{ github.sha }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# list of Docker images to use as base name for tags
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# generate Docker tags based on the following events/attributes
tags: |
type=ref,event=pr
tpye=raw,value=dev,priority=1
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"printWidth": 100,
"tabWidth": 2,
"parser": "typescript",
"singleQuote": true,
"trailingComma": "es5",
"useTabs": false,
"endOfLine": "lf"
}

View File

@@ -1 +0,0 @@
module.exports = require('eslint-config-mantine/.prettierrc.js');

View File

@@ -1,5 +1,5 @@
module.exports = {
stories: ['../components/**/*.story.mdx', '../components/**/*.story.*'],
stories: ['../src/components/**/*.story.mdx', '../src/components/**/*.story.*'],
addons: [
'storybook-dark-mode',
'@storybook/addon-links',

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
ajnart@pm.me.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

106
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,106 @@
# Contributing to Homarr
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Styleguides](#styleguides)
- [Commit Messages](#commit-messages)
## Code of Conduct
This project and everyone participating in it is governed by the
[Homarr Code of Conduct](https://github.com/ajnart/homarr/blob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
to [@ajnart](https://github.com/ajnart).
## I Have a Question
> If you want to ask a question, we assume that you have read the available [Documentation](https://github.com/ajnart/homarr/#readme).
Before you ask a question, it is best to search for existing [Issues](https://github.com/ajnart/homarr/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an [Issue](https://github.com/ajnart/homarr/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (nodejs, docker, etc), depending on what seems relevant.
We will then take care of the issue as soon as possible.
## I Want To Contribute
> ### Legal Notice
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
### Reporting Bugs
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://github.com/ajnart/homarr/#readme). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/ajnart/homarr/issues?q=is%3Aopen+is%3Aissue+label%3A%22%F0%9F%90%9B+Bug%22).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of yarn, nodejs, docker, npm, next, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to ajnart@pm.me.
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an [Issue](https://github.com/ajnart/homarr/issues/new).
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for Homarr, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the [documentation](https://github.com/ajnart/homarr/#readme) carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](https://github.com/ajnart/homarr/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://github.com/ajnart/homarr//issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
- **Explain why this enhancement would be useful** to most Homarr users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
## Styleguides
### Commit Messages
Homarr uses [GitMoji](https://gitmoji.dev/).
We would appreciate it if everyone keeps their commit messages withing these rulings.

View File

@@ -1,2 +1,19 @@
FROM nginx:1.21.6
COPY ./out /usr/share/nginx/html
FROM node:16-alpine
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY /next.config.js ./
COPY /public ./public
COPY /package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY /.next/standalone ./
COPY /.next/static ./.next/static
EXPOSE 7575
ENV PORT 7575
VOLUME /app/data/configs
CMD ["node", "server.js"]

125
README.md
View File

@@ -1,32 +1,101 @@
# MyHomePage, a home page for your home server
Join the discord ! : https://discord.gg/C2WTXkzkwK
## What is MyHomePage ?
<h3 align="center">Homarr</h3>
<br/>
<p align="center">
<a href="https://github.com/ajnart/homarr/actions/workflows/docker.yml">
<img title="Docker CI Status" src="https://github.com/ajnart/homarr/actions/workflows/docker.yml/badge.svg" alt="CI Status"></a>
<a href="https://github.com/ajnart/homarr/releases/latest">
<img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/ajnart/homarr"></a>
<a href="https://github.com/ajnart/homarr/pkgs/container/homarr">
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/ajnart/homarr?label=Downloads%20"></a>
</p>
<p align="center">
<a href="">
<img align="end" width=600 src="https://user-images.githubusercontent.com/49837342/168315259-b778c816-10fe-44db-bd25-3eea6f31b233.png" />
<a/>
</p>
<p align = "center">
A homepage for <i>your</i> server.
<br/>
<a href = "https://github.com/ajnart/homarr/deployments/activity_log?environment=Production" > <strong> Demo ↗️ </strong> </a> • <a href = "#-installation" > <strong> Install ➡️ </strong> </a>
<br />
<br />
<i>Join the discord!</i>
<br />
<a href = "https://discord.gg/aCsmEV5RgA" > <img title="Discord" src="https://discordapp.com/api/guilds/972958686051962910/widget.png?style=shield" > </a>
<br/>
<br/>
</p>
HomePage is a web page for your home server, it provides a user friendly interface to access docker containers or other services.
# 📃 Table of Contents
- [📃 Table of Contents](#-table-of-contents)
- [🚀 Getting Started](#-getting-started)
- [ About](#-about)
- [🐛 Known Issues](#-known-issues)
- [⚡ Installation](#-installation)
- [Deploying from Docker Image 🐳](#deploying-from-docker-image-)
- [Building from Source 🛠️](#building-from-source-)
- [💖 Contributing](#-contributing)
## Install
### Docker installation
Required : Docker
#### Standard docker install
To install the MyHomePage docker image simply execute ``docker pull ajnart/mhp``
To run the docker file ``docker run --name my-home-page -p 7575:80 -d ajnart/mhp``
<!-- Getting Started -->
# 🚀 Getting Started
*Note: Currently the port used is 80 (Nginx default port) It will change to be 7575 by default*
#### Docker compose
Here's a docker compose example on how to integrate MHP into your container stack
```docker
services:
mhp:
image: ajnart/mhp
ports:
- '7575:80'
restart: always
## About
Homarr is a simple and lightweight homepage for your server, that helps you easily access all of your services in one place.
**[⤴️ Back to Top](#-table-of-contents)**
## ⚡ Installation
### Deploying from Docker Image 🐳
> Supported architectures: x86-64, ARM, ARM64
_Requirements_:
- [Docker](https://docs.docker.com/get-docker/)
**Standard Docker Install**
```sh
docker run --name homarr -p 7575:7575 -v /data/docker/homarr:/app/data/configs -d ghcr.io/ajnart/homarr:latest
```
### Local installation
Required: Node (LTS)
#### Install using node
To install MyHomePage locally:
- Clone the source code or download it.
- Execute ``npm install`` or ``yarn install`` *(prefered)* to install the dependencies
- Execute ``yarn export`` to build the source code into the final HTML pages in the ``./out`` folder
- Run a web server to serve the content of the ``./out`` folder. Example: ``python -m http.server 7575 --directory out``
**Docker Compose**
```yml
---
version: '3'
#--------------------------------------------------------------------------------------------#
# Homarr - A homepage for your server. #
#--------------------------------------------------------------------------------------------#
services:
homarr:
container_name: homarr
image: ghcr.io/ajnart/homarr:latest
restart: unless-stopped
volumes:
- /data/docker/homarr:/app/data/configs
ports:
- '7575:7575'
```
***Getting EACCESS errors in the logs? Try running `sudo chmod 775 /directory-you-mounted-to`!***
### Building from Source 🛠️
_Requirements_:
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/) _(Latest or LTS)_
- [Yarn](https://yarnpkg.com/)
**Installing**
- Clone the GitHub repo: `git clone https://github.com/ajnart/homarr.git` & `cd homarr`
- Install all dependencies: `yarn install`
- Build the source: `yarn build`
- Start the NextJS web server: ``yarn start``
- *Note: If you want to update the code in real time, launch with ``yarn dev``*
# 💖 Contributing
**Please read our [Contribution Guidelines](/CONTRIBUTING.md)**
All contributions are highly appreciated.
**[⤴️ Back to Top](#-table-of-contents)**

View File

@@ -1,136 +0,0 @@
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import {
Text,
AspectRatio,
createStyles,
SimpleGrid,
Card,
useMantineTheme,
Image,
Group,
Space,
} from '@mantine/core';
import AppShelfMenu from './AppShelfMenu';
import AddItemShelfItem from './AddAppShelfItem';
import { useConfig } from '../../tools/state';
import { pingQbittorrent } from '../../tools/api';
import { serviceItem } from '../../tools/types';
const useStyles = createStyles((theme) => ({
main: {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
//TODO: #3 Fix this temporary fix and make the width and height dynamic / responsive
width: 200,
height: 180,
},
}));
const AppShelf = (props: any) => {
const { config, addService, removeService, setConfig } = useConfig();
/* A hook that is used to load the config from local storage. */
useEffect(() => {
const localConfig = localStorage.getItem('config');
if (localConfig) {
setConfig(JSON.parse(localConfig));
}
}, []);
if (config.services && config.services.length === 0) {
config.services.forEach((service) => {
if (service.type === 'qBittorrent') {
pingQbittorrent(service);
}
});
}
return (
<SimpleGrid m="xl" cols={5} spacing="xl">
{config.services.map((service, i) => (
<AppShelfItem key={service.name} service={service} />
))}
<AddItemShelfItem />
</SimpleGrid>
);
};
export function AppShelfItem(props: any) {
const { service }: { service: serviceItem } = props;
const theme = useMantineTheme();
const { removeService } = useConfig();
const { classes } = useStyles();
const [hovering, setHovering] = useState(false);
return (
<motion.div
key={service.name}
onHoverStart={(e) => {
setHovering(true);
}}
onHoverEnd={(e) => {
setHovering(false);
}}
>
<Card
className={classes.main}
style={{
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
}}
radius="md"
>
<Card.Section>
<Group position="apart" mx="lg">
<Space />
<Text
// TODO: #1 Remove this hack to get the text to be centered.
ml={15}
style={{
alignSelf: 'center',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
justifyItems: 'center',
}}
mt="sm"
weight={500}
>
{service.name}
</Text>
<motion.div
style={{
alignSelf: 'flex-end',
}}
animate={{
opacity: hovering ? 1 : 0,
}}
>
<AppShelfMenu service={service} removeitem={removeService} />
</motion.div>
</Group>
</Card.Section>
<Card.Section>
<AspectRatio ratio={5 / 3} m="xl">
<motion.i
whileHover={{
cursor: 'pointer',
scale: 1.1,
}}
>
<Image
onClick={() => {
window.open(service.url);
}}
style={{
maxWidth: '50%',
marginBottom: 10,
}}
src={service.icon}
/>
</motion.i>
</AspectRatio>
</Card.Section>
</Card>
</motion.div>
);
}
export default AppShelf;

View File

@@ -1,103 +0,0 @@
import {
ActionIcon,
Group,
Modal,
Switch,
Title,
Text,
Tooltip,
SegmentedControl,
} from '@mantine/core';
import { useState } from 'react';
import { Settings as SettingsIcon } from 'tabler-icons-react';
import { useConfig } from '../../tools/state';
import SaveConfigComponent from '../Config/SaveConfig';
function SettingsMenu(props: any) {
const { config, setConfig } = useConfig();
const matches = [
{ label: 'Google', value: 'https://google.com/search?q=' },
{ label: 'DuckDuckGo', value: 'https://duckduckgo.com/?q=' },
{ label: 'Bing', value: 'https://bing.com/search?q=' },
];
return (
<Group direction="column" grow>
<Group>
<SegmentedControl
title="Search engine"
defaultValue={
// Match config.settings.searchUrl with a key in the matches array
matches.find((match) => match.value === config.settings.searchUrl)?.value || 'Google'
}
onChange={
// Set config.settings.searchUrl to the value of the selected item
(e) =>
setConfig({
...config,
settings: {
...config.settings,
searchUrl: e,
},
})
}
data={matches}
/>
<Text>Search engine</Text>
</Group>
<Group direction="column">
<Switch
onChange={(e) =>
setConfig({
...config,
settings: {
...config.settings,
searchBar: e.currentTarget.checked,
},
})
}
checked={config.settings.searchBar}
label="Enable search bar"
/>
</Group>
<SaveConfigComponent />
<Text
style={{
alignSelf: 'center',
fontSize: '0.75rem',
textAlign: 'center',
color: '#a0aec0',
}}
>
tip: You can upload your config file by dragging and dropping it onto the page
</Text>
</Group>
);
}
export function SettingsMenuButton(props: any) {
const [opened, setOpened] = useState(false);
return (
<>
<Modal
size="md"
title={<Title order={3}>Settings</Title>}
opened={props.opened || opened}
onClose={() => setOpened(false)}
>
<SettingsMenu />
</Modal>
<ActionIcon
variant="default"
radius="xl"
size="xl"
color="blue"
style={props.style}
onClick={() => setOpened(true)}
>
<Tooltip label="Settings">
<SettingsIcon />
</Tooltip>
</ActionIcon>
</>
);
}

View File

@@ -1,7 +0,0 @@
import { Welcome } from './Welcome';
export default {
title: 'Welcome',
};
export const Usage = () => <Welcome />;

View File

@@ -1,14 +0,0 @@
import { createStyles } from '@mantine/core';
export default createStyles((theme) => ({
title: {
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
fontSize: 100,
fontWeight: 900,
letterSpacing: -2,
[theme.fn.smallerThan('md')]: {
fontSize: 50,
},
},
}));

View File

@@ -1,12 +0,0 @@
import { render, screen } from '@testing-library/react';
import { Welcome } from './Welcome';
describe('Welcome component', () => {
it('has correct Next.js theming section link', () => {
render(<Welcome />);
expect(screen.getByText('this guide')).toHaveAttribute(
'href',
'https://mantine.dev/theming/next/'
);
});
});

View File

@@ -1,25 +0,0 @@
import { Title, Text, Anchor } from '@mantine/core';
import useStyles from './Welcome.styles';
export function Welcome() {
const { classes } = useStyles();
return (
<>
<Title className={classes.title} align="center" mt={100}>
Welcome to{' '}
<Text inherit variant="gradient" component="span">
Mantine
</Text>
</Title>
<Text color="dimmed" align="center" size="lg" sx={{ maxWidth: 580 }} mx="auto" mt="xl">
This starter Next.js project includes a minimal setup for server side rendering, if you want
to learn more on Mantine + Next.js integration follow{' '}
<Anchor href="https://mantine.dev/theming/next/" size="lg">
this guide
</Anchor>
. To get started edit index.tsx file.
</Text>
</>
);
}

View File

@@ -1,15 +0,0 @@
import { Text } from '@mantine/core';
import * as React from 'react';
export function Logo({ style }: any) {
return (
<Text
sx={style}
weight="bold"
variant="gradient"
gradient={{ from: 'red', to: 'orange', deg: 145 }}
>
MyHomePage
</Text>
);
}

12
data/configs/config.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "config",
"services": [],
"settings": {
"searchBar": true,
"searchUrl": "https://duckduckgo.com/?q=",
"enabledModules": [
"Date",
"Calendar"
]
}
}

View File

@@ -0,0 +1,16 @@
{
"name": "config_new",
"services": [
{
"type": "Other",
"name": "example",
"icon": "https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
],
"settings": {
"searchBar": true,
"searchUrl": "https://duckduckgo.com/?q=",
"enabledModules": []
}
}

16
data/configs/default.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "default",
"services": [
{
"type": "Other",
"name": "example",
"icon": "https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
],
"settings": {
"searchBar": true,
"searchUrl": "https://bing.com/search?q=",
"enabledModules": []
}
}

2
data/constants.ts Normal file
View File

@@ -0,0 +1,2 @@
export const REPO_URL = 'ajnart/homarr';
export const CURRENT_VERSION = 'v0.3.0';

View File

@@ -1,16 +0,0 @@
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/pages/(.*)$': '<rootDir>/pages/$1',
},
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);

View File

@@ -1 +0,0 @@
import '@testing-library/jest-dom/extend-expect';

View File

@@ -7,4 +7,7 @@ module.exports = withBundleAnalyzer({
eslint: {
ignoreDuringBuilds: true,
},
experimental: {
outputStandalone: true,
},
});

View File

@@ -1,17 +1,17 @@
{
"name": "homarr",
"version": "0.0.1",
"private": "false",
"description": "Customizable browser's home page to interact with your homeserver's Docker containers (i.e. Sonarr/Radarr)",
"repository": {
"type": "git",
"url": "https://github.com/ajnart/myhomepage"
},
"name": "homarr",
"version": "0.3.0",
"private": "false",
"description": "Homarr - A homepage for your server.",
"repository": {
"type": "git",
"url": "https://github.com/ajnart/homarr"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"analyze": "ANALYZE=true next build",
"start": "next start",
"start": "next start --port 7575",
"typecheck": "tsc --noEmit",
"export": "next build && next export",
"lint": "next lint",
@@ -21,22 +21,25 @@
"prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
"test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest",
"storybook": "start-storybook -p 7001",
"storybook:build": "build-storybook"
"storybook:build": "build-storybook",
"ci": "yarn test && yarn lint --fix && yarn typecheck && yarn prettier:write"
},
"dependencies": {
"@mantine/core": "^4.2.1",
"@mantine/dates": "^4.2.1",
"@mantine/dropzone": "^4.2.1",
"@mantine/form": "^4.2.1",
"@mantine/hooks": "^4.2.1",
"@mantine/modals": "^4.2.1",
"@mantine/next": "^4.2.1",
"@mantine/notifications": "^4.2.1",
"@mantine/prism": "^4.2.1",
"@mantine/spotlight": "^4.2.1",
"@mantine/core": "^4.2.4",
"@mantine/dates": "^4.2.4",
"@mantine/dropzone": "^4.2.4",
"@mantine/form": "^4.2.4",
"@mantine/hooks": "^4.2.4",
"@mantine/modals": "^4.2.4",
"@mantine/next": "^4.2.4",
"@mantine/notifications": "^4.2.4",
"@mantine/prism": "^4.2.4",
"@mantine/rte": "^4.2.4",
"@mantine/spotlight": "^4.2.4",
"@modulz/radix-icons": "^4.0.0",
"axios": "^0.27.2",
"cookies-next": "^2.0.4",
"dayjs": "^1.11.1",
"dayjs": "^1.11.2",
"framer-motion": "^6.3.1",
"js-file-download": "^0.4.12",
"next": "12.1.5-canary.4",
@@ -81,4 +84,4 @@
"ts-jest": "^27.1.4",
"typescript": "4.6.3"
}
}
}

View File

@@ -1,16 +0,0 @@
import { Group } from '@mantine/core';
import AppShelf from '../components/AppShelf/AppShelf';
import LoadConfigComponent from '../components/Config/LoadConfig';
import SearchBar from '../components/SearchBar/SearchBar';
export default function HomePage() {
return (
<>
<SearchBar />
<Group align="start" position="apart" noWrap>
<AppShelf />
</Group>
<LoadConfigComponent />
</>
);
}

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/imgs/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/imgs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

247
public/imgs/logo.svg Normal file
View File

@@ -0,0 +1,247 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1000.000000pt" height="1000.000000pt" viewBox="0 0 1000.000000 1000.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,1000.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M6470 9752 c-179 -11 -423 -57 -605 -113 -94 -29 -116 -37 -212 -73
-381 -144 -693 -333 -1030 -621 -6 -5 -63 -61 -126 -123 l-114 -113 -179 88
c-223 110 -305 143 -386 158 -12 3 -34 8 -48 11 -14 3 -100 8 -191 10 -154 4
-171 3 -253 -21 -159 -46 -241 -93 -355 -201 -55 -53 -101 -100 -101 -104 0
-10 76 18 110 41 39 26 206 108 253 124 61 20 202 42 294 44 56 1 105 -6 178
-24 55 -15 110 -28 122 -31 76 -15 317 -112 410 -165 l52 -30 -78 -97 c-152
-190 -244 -324 -230 -333 8 -5 17 -9 20 -9 4 0 12 -13 19 -30 18 -44 5 -139
-39 -269 -20 -62 -34 -114 -31 -116 4 -2 24 14 45 35 22 22 43 40 49 40 5 0
23 -10 40 -23 56 -42 210 -135 276 -167 36 -17 72 -35 80 -39 37 -20 139 -43
187 -43 35 0 64 6 88 21 l36 21 57 -38 c31 -21 76 -46 100 -55 52 -21 149 -32
188 -23 l28 7 -29 23 -29 24 54 34 c78 49 180 84 191 67 37 -57 119 -207 165
-304 75 -157 122 -306 134 -421 16 -157 43 -193 152 -199 56 -4 62 -2 90 26
29 29 30 33 25 89 -13 122 -191 536 -326 759 -53 87 -58 100 -41 102 6 1 13 2
18 3 4 1 39 5 77 10 104 11 156 17 195 21 19 3 62 7 95 10 112 11 246 45 470
120 370 124 716 314 905 497 278 269 418 532 380 714 l-12 58 -35 -43 c-48
-57 -116 -90 -216 -102 -117 -15 -165 -29 -198 -56 -23 -19 -32 -22 -48 -14
-51 27 -95 7 -160 -75 -30 -37 -60 -64 -73 -66 -13 -2 -50 2 -83 10 -79 17
-112 10 -189 -43 -107 -73 -120 -78 -196 -70 -84 8 -97 2 -176 -82 -56 -59
-63 -63 -94 -58 -18 3 -37 8 -41 11 -4 2 -25 7 -47 10 -37 5 -43 2 -111 -60
l-71 -66 -65 0 c-90 0 -160 -28 -221 -90 -47 -47 -52 -50 -103 -50 -49 0 -57
-3 -94 -41 -48 -47 -65 -57 -92 -53 -11 1 -39 6 -62 9 -29 4 -43 11 -43 21 0
30 20 52 53 58 23 4 38 15 45 31 16 35 49 53 112 59 66 7 99 29 169 113 28 34
60 67 72 73 11 6 55 14 97 16 84 5 91 9 133 71 20 29 40 45 65 52 35 10 47 9
141 -10 61 -12 82 -4 163 65 57 49 103 62 173 52 41 -6 43 -5 85 44 68 79 92
86 268 80 84 -3 113 9 175 75 95 100 156 119 227 73 44 -29 61 -28 110 6 57
39 107 56 141 50 26 -6 38 1 92 49 72 66 120 79 184 53 21 -9 39 -15 40 -14 1
2 9 14 18 28 51 75 -5 176 -155 283 -146 104 -224 134 -478 182 -61 11 -221
34 -280 40 -69 8 -327 12 -400 7z"/>
<path d="M8256 8794 c-3 -8 -3 -46 0 -82 4 -47 1 -86 -10 -128 -9 -34 -16 -93
-16 -131 0 -44 -7 -92 -20 -128 -23 -66 -25 -108 -9 -186 9 -47 8 -60 -10
-102 -26 -60 -26 -101 -1 -196 20 -74 20 -75 0 -145 -13 -48 -19 -102 -19
-166 1 -135 -25 -322 -56 -403 -28 -75 -31 -112 -14 -185 13 -54 8 -78 -42
-189 -23 -52 -23 -53 -5 -108 l17 -55 -35 -79 c-33 -71 -36 -87 -36 -168 0
-122 -47 -273 -85 -273 -21 0 -19 21 14 138 17 57 32 131 36 165 3 34 17 104
31 155 23 81 25 99 15 137 -17 67 -14 90 21 158 34 67 36 86 57 467 6 95 14
160 25 190 19 49 15 93 -10 121 -15 16 -14 23 10 83 31 78 32 109 6 176 -27
70 -25 89 15 146 40 56 46 111 19 189 -23 69 -21 113 9 188 26 62 27 74 21
156 -6 80 -5 93 15 124 28 45 27 54 -10 73 -92 47 -184 -42 -338 -326 -161
-298 -197 -386 -285 -705 -103 -375 -123 -584 -96 -1025 12 -204 25 -312 52
-458 11 -62 18 -116 14 -119 -3 -3 -79 10 -168 30 -175 39 -357 74 -429 83
-24 3 -51 7 -58 10 -24 8 -159 18 -241 19 l-75 0 -3 -81 c-4 -127 -9 -124 233
-139 55 -3 111 -7 125 -10 14 -2 48 -6 77 -9 28 -3 77 -9 110 -15 32 -6 76
-13 98 -16 22 -4 56 -10 75 -15 19 -5 51 -11 70 -14 47 -8 85 -15 148 -30 l32
-8 0 -64 c0 -77 13 -110 75 -195 66 -90 81 -119 63 -130 -21 -14 -51 -10 -91
11 -44 24 -45 20 -18 -71 45 -149 37 -167 -56 -132 -10 4 5 -19 34 -50 28 -32
73 -92 99 -135 138 -222 189 -263 378 -302 107 -22 170 -49 187 -82 13 -23 14
-73 3 -120 -5 -25 -3 -28 21 -28 34 0 52 10 136 75 73 56 115 70 181 60 68
-11 138 46 104 84 -20 22 -21 18 62 191 35 74 80 183 100 242 l35 107 56 -40
c74 -53 164 -142 201 -199 91 -142 192 -648 151 -754 -19 -47 -5 -41 24 11 58
105 70 156 69 308 -1 121 -5 152 -27 226 -32 107 -94 250 -142 329 -45 75
-166 204 -234 250 -28 19 -51 39 -51 44 0 5 29 99 65 210 35 111 73 235 84
276 30 112 75 399 86 550 4 47 9 99 11 115 8 66 4 496 -6 593 -32 335 -162
788 -297 1042 -130 243 -283 386 -487 456 -82 28 -82 28 -90 8z"/>
<path d="M6300 7695 c-1 -143 -35 -481 -66 -659 -49 -276 -106 -466 -195 -644
-56 -112 -56 -113 -35 -127 41 -29 54 -19 102 76 149 298 211 539 229 904 7
142 -10 484 -25 499 -7 7 -10 -12 -10 -49z"/>
<path d="M4095 7698 c-3 -7 -6 -56 -7 -108 -3 -89 -4 -95 -23 -92 -11 2 -35
19 -52 39 -18 20 -34 34 -34 32 -1 -2 -3 -33 -4 -69 -4 -82 -13 -153 -27 -207
l-10 -43 -46 40 c-24 22 -48 40 -52 40 -11 0 -24 -221 -17 -309 6 -83 37 -178
66 -205 73 -66 245 -71 421 -12 58 19 130 45 160 56 30 12 69 26 86 31 31 9
31 10 36 87 3 56 12 94 33 140 15 34 25 65 22 68 -3 4 -18 7 -32 7 -40 0 -35
14 50 133 59 81 99 160 91 180 -3 8 -44 13 -123 17 -65 2 -127 7 -138 10 -127
33 -204 66 -321 137 -74 45 -73 45 -79 28z"/>
<path d="M3450 7544 c-123 -70 -410 -433 -412 -522 -2 -71 84 -177 137 -170
19 3 41 40 96 158 9 19 31 61 48 93 22 38 36 81 42 127 10 68 62 184 136 303
28 44 16 47 -47 11z"/>
<path d="M4972 7109 c-21 -6 -32 -15 -29 -23 3 -6 13 -40 22 -75 17 -60 19
-62 46 -56 15 3 45 2 66 -4 90 -23 132 -20 98 9 -33 28 -15 32 98 26 109 -7
226 -20 273 -32 22 -6 24 -4 18 27 -4 19 -9 37 -13 41 -7 8 -256 73 -316 82
-71 12 -229 14 -263 5z"/>
<path d="M4810 7085 c-36 -7 -81 -18 -100 -24 -67 -21 -66 -18 -35 -135 15
-58 33 -106 38 -106 14 0 135 68 142 80 3 5 26 12 50 16 25 3 45 8 45 11 0 3
-6 31 -14 62 -8 31 -17 68 -20 84 -7 30 -9 31 -106 12z"/>
<path d="M7315 6989 c-227 -53 -672 -275 -915 -457 -73 -55 -231 -196 -284
-252 l-48 -53 26 -25 25 -25 83 84 c119 121 227 202 478 361 113 72 265 152
437 232 216 100 260 123 268 136 7 12 -15 11 -70 -1z"/>
<path d="M2974 6954 c-7 -29 1 -307 14 -484 10 -129 25 -174 70 -212 48 -40
97 -53 127 -34 13 9 29 25 35 37 20 37 22 160 6 313 -9 82 -16 172 -16 202 l0
53 -30 -9 c-17 -5 -33 -9 -35 -10 -32 -5 -130 93 -140 141 -8 36 -23 37 -31 3z"/>
<path d="M2484 6858 c-73 -124 -162 -305 -184 -376 -24 -78 -17 -102 47 -142
57 -36 90 -39 103 -7 17 41 49 232 59 359 6 69 13 131 16 139 11 30 22 109 15
109 -4 0 -29 -37 -56 -82z"/>
<path d="M4563 6824 c-64 -39 -317 -95 -423 -94 -96 0 -119 4 -180 25 l-42 15
7 -28 c8 -32 34 -67 105 -142 28 -30 77 -84 108 -120 30 -36 89 -100 131 -142
l76 -78 -63 0 c-34 0 -62 -3 -62 -7 0 -32 78 -102 230 -208 47 -33 135 -96
195 -141 61 -44 120 -86 133 -93 29 -16 50 -4 127 69 33 31 80 70 105 86 25
16 47 31 50 35 5 6 110 66 185 106 85 45 76 75 -57 189 -57 48 -208 192 -336
318 -127 127 -235 232 -240 233 -4 1 -26 -9 -49 -23z"/>
<path d="M5840 6684 c-30 -8 -82 -14 -115 -13 -33 0 -66 0 -73 0 -10 -1 -12
-21 -10 -82 3 -75 5 -84 29 -101 49 -37 193 -36 216 1 14 23 29 211 17 210 -5
-1 -34 -7 -64 -15z"/>
<path d="M3914 6633 c-49 -114 -125 -336 -135 -397 -15 -86 -7 -135 27 -175
46 -55 140 -50 163 7 5 15 11 59 13 97 3 56 13 91 51 179 26 60 47 114 47 122
0 7 -20 36 -45 64 -25 28 -54 68 -65 90 -28 54 -37 56 -56 13z"/>
<path d="M5620 6407 c-12 -34 -36 -88 -53 -120 -16 -31 -28 -60 -25 -62 3 -3
41 3 84 13 43 11 112 20 153 21 41 1 78 4 81 6 4 3 10 30 14 60 3 31 13 72 21
92 14 33 14 35 -3 28 -9 -4 -51 -9 -92 -11 -57 -2 -85 1 -117 16 l-41 18 -22
-61z"/>
<path d="M2257 6308 c7 -119 45 -313 89 -448 54 -167 71 -198 116 -202 42 -4
98 9 120 29 22 20 19 36 -48 243 -61 190 -84 277 -84 314 0 17 -7 29 -17 32
-46 15 -100 41 -134 66 -22 16 -40 28 -42 28 -2 0 -2 -28 0 -62z"/>
<path d="M5935 6220 c-31 -44 -212 -222 -264 -259 -54 -38 -24 2 65 89 91 89
149 158 141 167 -7 6 -266 -22 -357 -39 -225 -42 -501 -216 -745 -470 -199
-206 -250 -272 -266 -349 -32 -150 60 -415 211 -604 152 -190 373 -343 525
-361 161 -19 456 6 511 43 23 16 231 377 296 513 36 76 104 281 153 464 35
128 30 177 -34 351 -69 185 -71 193 -71 248 0 44 -2 48 -16 36 -24 -20 -69
-114 -108 -229 -43 -124 -64 -154 -35 -49 33 121 103 284 150 351 11 14 19 30
19 35 0 5 -25 24 -55 42 -30 18 -59 39 -66 47 -17 21 -24 18 -54 -26z"/>
<path d="M6446 6230 c-44 -16 -83 -35 -88 -42 -9 -14 -2 -158 11 -204 12 -45
34 -52 78 -23 21 14 50 33 66 43 l27 17 -1 119 c0 66 -4 120 -7 119 -4 0 -43
-13 -86 -29z"/>
<path d="M3010 6219 c0 -59 289 -869 310 -869 4 0 14 24 20 53 25 105 54 140
113 133 20 -2 37 -3 37 -1 0 1 -24 70 -54 152 -30 83 -83 241 -119 351 l-63
200 -28 -34 c-23 -30 -33 -34 -72 -34 -53 0 -108 23 -129 54 -15 20 -15 20
-15 -5z"/>
<path d="M6244 6124 c-99 -79 -102 -86 -80 -181 10 -43 30 -114 43 -157 25
-80 25 -80 38 -50 7 16 36 60 64 99 42 56 51 74 46 95 -4 14 -12 73 -19 133
-7 59 -15 109 -17 112 -3 2 -36 -20 -75 -51z"/>
<path d="M3980 6022 c0 -6 -14 -19 -32 -28 -32 -16 -118 -16 -161 1 -12 5 12
-54 79 -191 53 -110 143 -298 201 -419 57 -121 111 -227 119 -235 13 -13 18
-11 41 20 15 19 50 59 80 88 29 29 53 57 53 63 0 6 -63 122 -139 257 -77 136
-162 294 -190 351 -28 57 -51 99 -51 93z"/>
<path d="M6710 5911 c0 -5 20 -53 44 -107 73 -168 156 -442 156 -520 0 -45 4
-55 27 -74 30 -23 30 -23 118 14 33 13 76 26 95 28 31 4 35 8 38 38 6 62 -52
170 -92 170 -12 0 -14 -5 -6 -25 10 -25 4 -55 -10 -55 -4 0 -17 30 -29 68 -34
105 -161 299 -270 415 -51 54 -71 68 -71 48z"/>
<path d="M2571 5645 c-46 -37 -105 -47 -129 -23 -24 24 -11 -5 45 -100 127
-218 184 -296 292 -402 58 -58 138 -126 176 -152 l70 -46 5 53 c5 56 23 85 72
115 l28 17 -55 37 c-69 48 -253 262 -405 473 -25 34 -47 63 -50 63 -3 0 -25
-16 -49 -35z"/>
<path d="M3451 5493 c-38 -19 -78 -74 -87 -121 -6 -27 0 -40 35 -86 81 -105
479 -476 511 -476 6 0 122 145 128 159 7 19 -301 349 -441 473 -42 37 -85 68
-95 68 -9 0 -32 -8 -51 -17z"/>
<path d="M6261 5478 c-5 -18 -26 -95 -46 -172 -43 -164 -75 -256 -122 -350
-19 -37 -33 -69 -31 -70 87 -59 168 -125 365 -303 50 -44 100 -86 112 -92 18
-10 21 -9 21 9 0 11 -9 38 -20 60 -35 69 -24 89 27 46 15 -13 96 -70 178 -127
152 -105 292 -181 354 -193 l33 -6 -16 25 c-76 115 -126 250 -166 440 -12 61
-31 146 -41 190 l-18 80 -88 83 c-47 46 -159 137 -247 203 -89 65 -187 139
-218 164 -32 25 -59 45 -62 45 -2 0 -9 -15 -15 -32z"/>
<path d="M4348 5238 c-144 -142 -397 -435 -508 -588 -30 -41 -57 -77 -61 -80
-3 -3 -47 -66 -96 -140 -171 -258 -272 -462 -317 -640 -15 -63 -15 -69 5 -140
25 -90 96 -235 155 -315 75 -101 203 -224 329 -313 233 -166 540 -300 669
-293 26 1 149 124 355 352 192 213 185 205 287 334 298 379 438 604 535 862
22 57 39 106 39 108 0 3 -10 0 -22 -6 -59 -31 -124 -39 -318 -35 -214 4 -266
13 -379 68 -226 110 -420 341 -526 628 -25 66 -28 90 -29 192 -1 65 -2 118 -2
118 -1 0 -53 -51 -116 -112z"/>
<path d="M7119 5202 c-40 -20 -88 -35 -123 -39 l-58 -6 6 -30 c3 -16 6 -54 6
-83 0 -70 22 -84 90 -59 25 9 71 25 103 34 32 9 62 23 66 29 8 13 -4 139 -17
169 -6 17 -12 16 -73 -15z"/>
<path d="M7547 5062 c-107 -101 -138 -112 -183 -67 -19 19 -25 35 -24 63 1 20
2 41 1 45 -1 5 -19 -14 -41 -42 -25 -31 -60 -61 -91 -77 -55 -27 -60 -29 -159
-49 -63 -13 -90 -32 -90 -64 0 -75 49 -233 138 -441 53 -125 57 -130 106 -130
22 0 68 7 101 14 33 8 105 17 160 20 55 4 108 9 118 12 16 5 16 7 -3 31 -13
17 -17 30 -11 36 5 5 76 16 158 24 81 7 159 16 173 19 l25 6 -22 18 c-13 10
-23 22 -23 27 0 4 63 8 139 8 124 0 181 9 143 22 -31 11 -107 70 -110 84 -2
10 16 31 46 53 28 20 53 43 56 51 17 46 -45 76 -176 85 -80 6 -105 12 -145 35
-71 41 -115 93 -163 189 -23 47 -46 86 -52 86 -5 0 -37 -26 -71 -58z"/>
<path d="M3110 5050 c-30 -32 -60 -89 -60 -113 0 -30 218 -189 335 -245 28
-13 70 -34 95 -45 25 -12 83 -46 129 -76 l83 -54 19 24 c38 49 99 140 99 148
0 7 -271 201 -294 210 -6 2 -38 23 -71 47 -94 65 -234 134 -273 134 -25 0 -41
-8 -62 -30z"/>
<path d="M6768 4376 c-23 -32 -125 -100 -168 -114 -19 -5 -61 -13 -94 -16 -32
-4 -66 -11 -77 -16 -18 -10 -17 -11 3 -45 16 -29 19 -47 16 -93 -3 -31 -9 -65
-13 -76 -8 -18 -4 -18 61 -11 90 9 200 47 272 92 31 20 91 69 132 111 69 68
73 75 56 86 -11 6 -48 30 -84 54 -82 56 -84 56 -104 28z"/>
<path d="M7617 4137 l-168 -258 19 -42 c29 -64 55 -82 118 -81 64 0 107 30
167 114 45 64 47 78 8 51 -59 -42 -69 -12 -20 62 37 57 51 147 47 306 l-3 105
-168 -257z"/>
<path d="M5727 4246 c-18 -28 -87 -175 -87 -186 0 -10 16 -15 90 -29 142 -26
251 -34 442 -35 240 -1 239 -2 245 86 5 62 -18 117 -57 135 -14 6 -63 14 -110
18 -47 4 -98 9 -115 10 -39 4 -264 17 -337 20 -49 2 -60 -1 -71 -19z"/>
<path d="M7380 3778 c-132 -121 -284 -208 -435 -248 -44 -12 -84 -26 -89 -31
-12 -11 56 -94 103 -124 51 -34 83 -37 155 -15 61 19 75 27 181 101 28 19 86
56 130 82 57 34 95 66 133 112 28 35 52 66 52 69 0 4 -18 3 -39 0 -51 -8 -98
18 -120 68 l-16 36 -55 -50z"/>
<path d="M5472 3742 c-73 -100 -95 -133 -91 -136 11 -10 139 -84 169 -98 83
-39 250 -94 340 -113 8 -2 54 -12 101 -24 99 -24 102 -24 94 -11 -3 5 5 31 19
57 23 45 32 58 91 124 l23 27 -96 36 c-272 104 -445 166 -472 171 -16 3 -40 9
-54 15 -15 5 -26 7 -26 5 0 -2 -11 0 -24 5 -23 8 -29 3 -74 -58z"/>
<path d="M3247 3598 c-86 -146 -197 -363 -197 -384 0 -31 93 -210 170 -329
111 -171 289 -335 485 -450 61 -36 171 -90 205 -100 14 -4 30 -12 37 -17 6 -5
34 -12 60 -15 l49 -5 175 150 c194 167 261 232 239 233 -16 1 -36 4 -90 15
-19 4 -40 8 -47 10 -15 3 -103 30 -168 51 -27 9 -66 26 -85 37 -38 22 -192
127 -200 136 -3 3 -36 31 -75 62 -150 119 -296 276 -368 395 -21 34 -59 116
-86 183 l-48 122 -56 -94z"/>
<path d="M6223 3515 c-36 -15 -91 -77 -99 -112 -9 -34 5 -72 36 -94 79 -58
450 -92 655 -60 86 13 205 49 205 63 0 5 -4 7 -8 4 -5 -3 -31 5 -58 18 -57 26
-130 103 -139 147 -4 16 -10 27 -14 25 -26 -16 -282 -25 -341 -12 -140 31
-200 36 -237 21z"/>
<path d="M5280 3493 c0 -4 -13 -24 -29 -43 -124 -152 -122 -149 -103 -167 10
-10 60 -58 112 -107 52 -50 145 -138 205 -196 169 -162 200 -185 252 -185 39
0 49 5 79 37 19 22 34 48 34 62 0 55 -269 393 -388 487 -94 75 -162 122 -162
112z"/>
<path d="M7664 3103 c-32 -43 -63 -83 -68 -89 -6 -6 -29 -31 -51 -55 -63 -68
-155 -144 -205 -169 -113 -56 -129 -89 -73 -151 19 -21 51 -48 70 -60 l34 -21
22 20 c12 11 39 25 59 31 32 11 108 79 108 98 0 4 -5 2 -12 -5 -18 -18 -38
-15 -38 6 0 10 34 69 75 132 74 113 127 229 138 303 3 20 4 37 2 37 -2 0 -30
-35 -61 -77z"/>
<path d="M2978 3107 c-21 -34 -53 -87 -73 -116 -66 -103 -66 -103 10 -253 37
-73 93 -169 124 -212 69 -96 201 -232 279 -284 125 -86 361 -182 444 -182 15
0 47 16 75 36 53 40 173 154 173 166 0 3 -26 12 -57 18 -159 33 -220 63 -387
189 -235 178 -341 305 -483 576 -35 69 -65 125 -66 125 -1 0 -19 -28 -39 -63z"/>
<path d="M5845 2923 c28 -66 6 -126 -53 -142 -20 -6 -43 -11 -51 -11 -25 0 7
-27 125 -106 208 -138 505 -293 577 -301 57 -7 65 7 28 50 -35 39 -53 80 -45
101 3 8 21 20 41 27 20 6 39 21 45 35 13 28 4 42 -32 49 -44 8 -95 28 -145 55
-90 49 -178 96 -320 170 -77 40 -144 77 -149 81 -17 15 -29 10 -21 -8z"/>
<path d="M2810 2819 c0 -6 -41 -92 -91 -192 l-90 -182 80 -160 c44 -88 100
-186 125 -217 98 -123 325 -272 503 -329 71 -22 72 -22 115 -4 28 13 87 63
172 149 l130 130 -55 11 c-174 36 -347 131 -537 295 -107 92 -167 175 -260
357 -70 139 -92 173 -92 142z"/>
<path d="M7160 2680 c-33 -16 -114 -36 -305 -74 -88 -18 -270 -25 -297 -12
-15 7 -18 3 -18 -20 0 -39 -12 -56 -51 -68 -18 -6 -34 -17 -37 -24 -6 -18 66
-83 122 -111 55 -27 122 -24 310 13 321 64 429 96 464 139 11 12 7 16 -20 21
-57 12 -118 81 -118 136 0 23 -4 24 -50 0z"/>
<path d="M2517 2261 c-49 -79 -87 -151 -87 -165 0 -136 113 -336 251 -444 112
-88 271 -171 355 -186 16 -3 40 -8 54 -11 106 -21 112 -20 169 31 29 26 75 80
103 118 l50 71 -29 3 c-128 12 -337 119 -478 245 -87 78 -147 164 -231 334
l-70 144 -87 -140z"/>
<path d="M1575 2228 c-31 -18 -52 -48 -91 -126 -55 -113 -65 -150 -71 -254
-10 -168 27 -325 92 -394 30 -31 38 -35 65 -29 71 16 287 113 626 281 l251
125 -24 87 c-59 212 -46 189 -106 201 -28 6 -65 13 -82 16 -28 5 -46 8 -105
20 -14 2 -41 7 -60 10 -19 3 -71 12 -115 20 -44 8 -96 17 -115 20 -19 3 -46 7
-60 10 -117 21 -185 25 -205 13z"/>
<path d="M2365 1759 c-44 -21 -168 -81 -275 -133 -240 -116 -441 -226 -497
-272 l-42 -34 15 -53 c21 -69 86 -190 145 -269 96 -128 273 -278 329 -278 27
0 34 8 165 175 52 66 277 383 375 529 l91 136 -102 120 c-56 66 -107 120 -113
119 -6 0 -47 -18 -91 -40z"/>
<path d="M2635 1413 c-44 -60 -136 -187 -205 -283 -69 -96 -147 -204 -172
-240 -26 -36 -58 -79 -71 -95 -14 -17 -35 -47 -47 -68 -31 -53 -16 -84 71
-147 96 -70 184 -113 294 -145 309 -90 341 -79 360 120 7 71 65 426 111 673
13 73 24 140 24 148 0 8 -24 25 -52 38 -29 13 -92 42 -140 65 -48 22 -88 41
-90 41 -2 0 -39 -48 -83 -107z"/>
<path d="M3130 1390 c-14 -4 -39 -8 -57 -9 -17 -1 -34 -5 -37 -10 -22 -36
-127 -689 -133 -833 -7 -162 -9 -160 106 -157 47 2 100 6 116 10 17 4 44 10
60 12 86 13 298 79 368 114 66 33 74 54 47 134 -29 88 -159 348 -283 564 -95
166 -105 180 -133 182 -16 1 -40 -2 -54 -7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,5 +1,4 @@
import {
useMantineTheme,
Modal,
Center,
Group,
@@ -10,79 +9,109 @@ import {
AspectRatio,
Text,
Card,
LoadingOverlay,
ActionIcon,
Tooltip,
} from '@mantine/core';
import { useForm } from '@mantine/hooks';
import { UseForm } from '@mantine/hooks/lib/use-form/use-form';
import { useForm } from '@mantine/form';
import { motion } from 'framer-motion';
import { useState } from 'react';
import { Apps } from 'tabler-icons-react';
import { useConfig } from '../../tools/state';
import { ServiceTypeList } from '../../tools/types';
import { AppShelfItemWrapper } from './AppShelfItemWrapper';
export default function AddItemShelfItem(props: any) {
const { addService } = useConfig();
export function AddItemShelfButton(props: any) {
const [opened, setOpened] = useState(false);
const theme = useMantineTheme();
return (
<>
<Modal
size="xl"
radius="lg"
radius="md"
opened={props.opened || opened}
onClose={() => setOpened(false)}
title="Add a service"
>
<AddAppShelfItemForm setOpened={setOpened} />
</Modal>
<AspectRatio
style={{
minHeight: 120,
minWidth: 120,
}}
ratio={4 / 3}
<ActionIcon
variant="default"
radius="md"
size="xl"
color="blue"
style={props.style}
onClick={() => setOpened(true)}
>
<Card
style={{
backgroundColor:
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
width: 200,
height: 180,
}}
radius="md"
>
<Group direction="column" position="center">
<motion.div whileHover={{ scale: 1.2 }}>
<Apps style={{ cursor: 'pointer' }} onClick={() => setOpened(true)} size={60} />
</motion.div>
<Text>Add Service</Text>
</Group>
</Card>
</AspectRatio>
<Tooltip label="Add a service">
<Apps />
</Tooltip>
</ActionIcon>
</>
);
}
function MatchIcon(
name: string,
form: UseForm<{
type: any;
name: any;
icon: any;
url: any;
apiKey: any;
}>
) {
// TODO: In order to avoid all the requests, we could fetch
// https://data.jsdelivr.com/v1/package/gh/IceWhaleTech/AppIcon@main
// and then iterate over the files -> files -> name and then remove the extension (.png)
// Compare it to the input and then fetch the icon
fetch(`https://cdn.jsdelivr.net/gh/IceWhaleTech/AppIcon@main/all/${name.toLowerCase()}.png`)
export default function AddItemShelfItem(props: any) {
const [opened, setOpened] = useState(false);
return (
<>
<Modal
size="xl"
radius="md"
opened={props.opened || opened}
onClose={() => setOpened(false)}
title="Add a service"
>
<AddAppShelfItemForm setOpened={setOpened} />
</Modal>
<AppShelfItemWrapper>
<Card.Section>
<Group position="center" mx="lg">
<Text
// TODO: #1 Remove this hack to get the text to be centered.
ml={15}
style={{
alignSelf: 'center',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
justifyItems: 'center',
}}
mt="sm"
weight={500}
>
Add a service
</Text>
</Group>
</Card.Section>
<Card.Section>
<AspectRatio ratio={5 / 3} m="xl">
<motion.i
whileHover={{
cursor: 'pointer',
scale: 1.1,
}}
>
<Apps style={{ cursor: 'pointer' }} onClick={() => setOpened(true)} size={60} />
</motion.i>
</AspectRatio>
</Card.Section>
</AppShelfItemWrapper>
</>
);
}
function MatchIcon(name: string, form: any) {
fetch(
`https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/${name
.replace(/\s+/g, '-')
.toLowerCase()}.png`
)
.then((res) => {
if (res.status === 200) {
form.setFieldValue('icon', res.url);
}
})
.catch((e) => {
.catch(() => {
// Do nothing
});
@@ -91,7 +120,9 @@ function MatchIcon(
export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & any) {
const { setOpened } = props;
const { addService, config, setConfig } = useConfig();
const { config, setConfig } = useConfig();
const [isLoading, setLoading] = useState(false);
const form = useForm({
initialValues: {
type: props.type ?? 'Other',
@@ -100,6 +131,23 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
url: props.url ?? '',
apiKey: props.apiKey ?? (undefined as unknown as string),
},
validate: {
apiKey: () => null,
// Validate icon with a regex
icon: (value: string) => {
if (!value.match(/^https?:\/\/.+\.(png|jpg|jpeg|gif)$/)) {
return 'Please enter a valid icon URL';
}
return null;
},
// Validate url with a regex http/https
url: (value: string) => {
if (!value.match(/^https?:\/\/.+\/$/)) {
return 'Please enter a valid URL (that ends with a /)';
}
return null;
},
},
});
return (
@@ -123,7 +171,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
}),
});
} else {
addService(form.values);
setConfig({
...config,
services: [...config.services, form.values],
});
}
setOpened(false);
form.reset();
@@ -142,44 +193,40 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
form.setFieldValue('icon', match);
}
}}
error={form.errors.name && 'Invalid name'}
error={form.errors.name && 'Invalid icon url'}
/>
<TextInput
required
label="Icon url"
placeholder="https://i.gifer.com/ANPC.gif"
value={form.values.icon}
onChange={(event) => {
form.setFieldValue('icon', event.currentTarget.value);
}}
error={form.errors.icon && 'Icon url is invalid'}
{...form.getInputProps('icon')}
/>
<TextInput
required
label="Service url"
placeholder="http://localhost:8989"
value={form.values.url}
onChange={(event) => form.setFieldValue('url', event.currentTarget.value)}
error={form.errors.url && 'Service url is invalid'}
{...form.getInputProps('url')}
/>
<Select
label="Select the type of service (used for API calls)"
defaultValue="Other"
placeholder="Pick one"
value={form.values.type}
required
searchable
onChange={(value) => form.setFieldValue('type', value ?? 'Other')}
data={ServiceTypeList}
{...form.getInputProps('type')}
/>
<LoadingOverlay visible={isLoading} />
{(form.values.type === 'Sonarr' || form.values.type === 'Radarr') && (
<TextInput
required
label="API key"
placeholder="Your API key"
value={form.values.apiKey}
onChange={(event) => form.setFieldValue('apiKey', event.currentTarget.value)}
onChange={(event) => {
form.setFieldValue('apiKey', event.currentTarget.value);
}}
error={form.errors.apiKey && 'Invalid API key'}
/>
)}

View File

@@ -1,3 +1,4 @@
import { SimpleGrid } from '@mantine/core';
import AppShelf, { AppShelfItem } from './AppShelf';
export default {
@@ -16,3 +17,10 @@ export default {
export const Default = (args: any) => <AppShelf {...args} />;
export const One = (args: any) => <AppShelfItem {...args} />;
export const Ten = (args: any) => (
<SimpleGrid>
{Array.from(Array(10)).map((_, i) => (
<AppShelfItem {...args} key={i} />
))}
</SimpleGrid>
);

View File

@@ -0,0 +1,104 @@
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { Text, AspectRatio, SimpleGrid, Card, Image, useMantineTheme } from '@mantine/core';
import { useConfig } from '../../tools/state';
import { serviceItem } from '../../tools/types';
import AppShelfMenu from './AppShelfMenu';
const AppShelf = () => {
const { config } = useConfig();
return (
<SimpleGrid
cols={7}
spacing="xl"
breakpoints={[
{ maxWidth: 2400, cols: 6, spacing: 'xl' },
{ maxWidth: 1800, cols: 5, spacing: 'xl' },
{ maxWidth: 1500, cols: 4, spacing: 'lg' },
{ maxWidth: 800, cols: 3, spacing: 'md' },
{ maxWidth: 400, cols: 3, spacing: 'sm' },
{ maxWidth: 400, cols: 2, spacing: 'sm' },
]}
>
{config.services.map((service) => (
<AppShelfItem key={service.name} service={service} />
))}
</SimpleGrid>
);
};
export function AppShelfItem(props: any) {
const { service }: { service: serviceItem } = props;
const [hovering, setHovering] = useState(false);
const theme = useMantineTheme();
return (
<motion.div
key={service.name}
onHoverStart={() => {
setHovering(true);
}}
onHoverEnd={() => {
setHovering(false);
}}
>
<Card
style={{
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
backgroundColor:
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
}}
radius="md"
>
<Card.Section>
<Text mt="sm" align="center" lineClamp={1} weight={500}>
{service.name}
</Text>
<motion.div
style={{
position: 'absolute',
top: 5,
right: 5,
alignSelf: 'flex-end',
}}
animate={{
opacity: hovering ? 1 : 0,
}}
>
<AppShelfMenu service={service} />
</motion.div>
</Card.Section>
<Card.Section>
<AspectRatio
ratio={3 / 5}
m="xl"
style={{
width: 150,
height: 90,
}}
>
<motion.i
whileHover={{
cursor: 'pointer',
scale: 1.1,
}}
>
<Image
style={{
maxWidth: 80,
}}
fit="contain"
onClick={() => {
window.open(service.url);
}}
src={service.icon}
/>
</motion.i>
</AspectRatio>
</Card.Section>
</Card>
</motion.div>
);
}
export default AppShelf;

View File

@@ -0,0 +1,17 @@
import { useMantineTheme, Card } from '@mantine/core';
export function AppShelfItemWrapper(props: any) {
const { children, hovering } = props;
const theme = useMantineTheme();
return (
<Card
style={{
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
}}
radius="md"
>
{children}
</Card>
);
}

View File

@@ -2,10 +2,12 @@ import { Menu, Modal, Text } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { useState } from 'react';
import { Check, Edit, Trash } from 'tabler-icons-react';
import { useConfig } from '../../tools/state';
import { AddAppShelfItemForm } from './AddAppShelfItem';
export default function AppShelfMenu(props: any) {
const { service, removeitem: removeItem } = props;
const { service } = props;
const { config, setConfig } = useConfig();
const [opened, setOpened] = useState(false);
return (
<>
@@ -40,7 +42,10 @@ export default function AppShelfMenu(props: any) {
<Menu.Item
color="red"
onClick={(e: any) => {
removeItem(service.name);
setConfig({
...config,
services: config.services.filter((s) => s.name !== service.name),
});
showNotification({
autoClose: 5000,
title: (

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { createStyles, Switch, Group, useMantineColorScheme } from '@mantine/core';
import { Sun, MoonStars } from 'tabler-icons-react';
const useStyles = createStyles((theme) => ({
root: {
position: 'relative',
'& *': {
cursor: 'pointer',
},
},
icon: {
pointerEvents: 'none',
position: 'absolute',
zIndex: 1,
top: 3,
},
iconLight: {
left: 4,
color: theme.white,
},
iconDark: {
right: 4,
color: theme.colors.gray[6],
},
}));
export function ColorSchemeSwitch() {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const { classes, cx } = useStyles();
return (
<Group>
<div className={classes.root}>
<Sun className={cx(classes.icon, classes.iconLight)} size={18} />
<MoonStars className={cx(classes.icon, classes.iconDark)} size={18} />
<Switch checked={colorScheme === 'dark'} onChange={() => toggleColorScheme()} size="md" />
</div>
Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode
</Group>
);
}

View File

@@ -0,0 +1,37 @@
import { Center, Loader, Select, Tooltip } from '@mantine/core';
import { setCookies } from 'cookies-next';
import { useEffect, useState } from 'react';
import { useConfig } from '../../tools/state';
export default function ConfigChanger() {
const { config, loadConfig, setConfig, getConfigs } = useConfig();
const [configList, setConfigList] = useState([] as string[]);
useEffect(() => {
getConfigs().then((configs) => setConfigList(configs));
// setConfig(initialConfig);
}, [config]);
// If configlist is empty, return a loading indicator
if (configList.length === 0) {
return (
<Center>
<Tooltip label={"Loading your configs. This doesn't load in vercel."}>
<Loader />
</Tooltip>
</Center>
);
}
return (
<Select
defaultValue={config.name}
label="Config loader"
onChange={(e) => {
loadConfig(e ?? 'default');
setCookies('config-name', e ?? 'default', { maxAge: 60 * 60 * 24 * 30 });
}}
data={
// If config list is empty, return the current config
configList.length === 0 ? [config.name] : configList
}
/>
);
}

View File

@@ -4,6 +4,7 @@ import { DropzoneStatus, FullScreenDropzone } from '@mantine/dropzone';
import { showNotification } from '@mantine/notifications';
import { useRef } from 'react';
import { useRouter } from 'next/router';
import { setCookies } from 'cookies-next';
import { useConfig } from '../../tools/state';
import { Config } from '../../tools/types';
@@ -48,7 +49,7 @@ export const dropzoneChildren = (status: DropzoneStatus, theme: MantineTheme) =>
);
export default function LoadConfigComponent(props: any) {
const { saveConfig, setConfig } = useConfig();
const { setConfig } = useConfig();
const theme = useMantineTheme();
const router = useRouter();
const openRef = useRef<() => void>();
@@ -69,15 +70,21 @@ export default function LoadConfigComponent(props: any) {
});
return;
}
const newConfig: Config = JSON.parse(e);
showNotification({
autoClose: 5000,
radius: 'md',
title: <Text>Config loaded successfully</Text>,
title: (
<Text>
Config <b>{newConfig.name}</b> loaded successfully
</Text>
),
color: 'green',
icon: <Check />,
message: undefined,
});
setConfig(JSON.parse(e));
setCookies('config-name', newConfig.name, { maxAge: 60 * 60 * 24 * 30 });
setConfig(newConfig);
});
}}
accept={['application/json']}

View File

@@ -7,7 +7,7 @@ export default function SaveConfigComponent(props: any) {
const { config } = useConfig();
function onClick(e: any) {
if (config) {
fileDownload(JSON.stringify(config, null, '\t'), 'config.json');
fileDownload(JSON.stringify(config, null, '\t'), `${config.name}.json`);
}
}
return (

View File

@@ -0,0 +1,16 @@
import { Select } from '@mantine/core';
import { useState } from 'react';
export default function SelectConfig(props: any) {
const [value, setValue] = useState<string | null>('');
return (
<Select
value={value}
onChange={setValue}
data={[
{ value: 'default', label: 'Default' },
{ value: 'yourmom', label: 'Your mom' },
]}
/>
);
}

View File

@@ -22,6 +22,7 @@ export default function SearchBar(props: any) {
return (
<Box
mb={"xl"}
style={{
width: '100%',
}}
@@ -48,7 +49,7 @@ export default function SearchBar(props: any) {
if (isYoutube) {
window.open(`https://www.youtube.com/results?search_query=${querry.substring(3)}`);
} else if (isTorrent) {
window.open(`https://thepiratebay.org/search.php?q=${querry.substring(3)}`);
window.open(`https://bitsearch.to/search?q=${querry.substring(3)}`);
} else {
window.open(`${querryUrl}${values.querry}`);
}
@@ -80,8 +81,7 @@ export default function SearchBar(props: any) {
}
>
<Text>
tip: You can prefix your querry with <b>!yt</b> or <b>!t</b> to research on youtube or
for a torrent
tip: Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube or for a Torrent respectively.
</Text>
</Popover>
</form>

View File

@@ -0,0 +1,41 @@
import { Group, Switch } from '@mantine/core';
import * as Modules from '../modules';
import { useConfig } from '../../tools/state';
export default function ModuleEnabler(props: any) {
const { config, setConfig } = useConfig();
const modules = Object.values(Modules).map((module) => module);
const enabledModules = config.settings.enabledModules ?? [];
modules.filter((module) => enabledModules.includes(module.title));
return (
<Group direction="column">
{modules.map((module) => (
<Switch
key={module.title}
size="md"
checked={enabledModules.includes(module.title)}
label={`Enable ${module.title} module`}
onChange={(e) => {
if (e.currentTarget.checked) {
setConfig({
...config,
settings: {
...config.settings,
enabledModules: [...enabledModules, module.title],
},
});
} else {
setConfig({
...config,
settings: {
...config.settings,
enabledModules: enabledModules.filter((m) => m !== module.title),
},
});
}
}}
/>
))}
</Group>
);
}

View File

@@ -0,0 +1,173 @@
import {
ActionIcon,
Group,
Modal,
Switch,
Title,
Text,
Tooltip,
SegmentedControl,
Indicator,
Alert,
TextInput,
} from '@mantine/core';
import { useColorScheme } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { AlertCircle, Settings as SettingsIcon } from 'tabler-icons-react';
import { CURRENT_VERSION, REPO_URL } from '../../../data/constants';
import { useConfig } from '../../tools/state';
import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch';
import ConfigChanger from '../Config/ConfigChanger';
import SaveConfigComponent from '../Config/SaveConfig';
import ModuleEnabler from './ModuleEnabler';
function SettingsMenu(props: any) {
const { config, setConfig } = useConfig();
const colorScheme = useColorScheme();
const { current, latest } = props;
const matches = [
{ label: 'Google', value: 'https://google.com/search?q=' },
{ label: 'DuckDuckGo', value: 'https://duckduckgo.com/?q=' },
{ label: 'Bing', value: 'https://bing.com/search?q=' },
{ label: 'Custom', value: 'Custom' },
];
const [customSearchUrl, setCustomSearchUrl] = useState(config.settings.searchUrl);
const [searchUrl, setSearchUrl] = useState(
matches.find((match) => match.value === config.settings.searchUrl)?.value ?? 'Custom'
);
return (
<Group direction="column" grow>
<Alert
icon={<AlertCircle size={16} />}
title="Update available"
radius="lg"
hidden={current === latest}
>
Version {latest} is available. Current: {current}
</Alert>
<Group grow direction="column" spacing={0}>
<Text>Search engine</Text>
<SegmentedControl
fullWidth
title="Search engine"
value={
// Match config.settings.searchUrl with a key in the matches array
searchUrl
}
onChange={
// Set config.settings.searchUrl to the value of the selected item
(e) => {
setSearchUrl(e);
setConfig({
...config,
settings: {
...config.settings,
searchUrl: e,
},
});
}
}
data={matches}
/>
{searchUrl === 'Custom' && (
<TextInput
label="Querry URL"
placeholder="Custom querry url"
value={customSearchUrl}
onChange={(event) => {
setCustomSearchUrl(event.currentTarget.value);
setConfig({
...config,
settings: {
...config.settings,
searchUrl: event.currentTarget.value,
},
});
}}
/>
)}
</Group>
<Group direction="column">
<Switch
size="md"
onChange={(e) =>
setConfig({
...config,
settings: {
...config.settings,
searchBar: e.currentTarget.checked,
},
})
}
checked={config.settings.searchBar}
label="Enable search bar"
/>
</Group>
<ModuleEnabler />
<ColorSchemeSwitch />
<ConfigChanger />
<SaveConfigComponent />
<Text
style={{
alignSelf: 'center',
fontSize: '0.75rem',
textAlign: 'center',
color: '#a0aec0',
}}
>
tip: You can upload your config file by dragging and dropping it onto the page
</Text>
</Group>
);
}
export function SettingsMenuButton(props: any) {
const [update, setUpdate] = useState(false);
const [opened, setOpened] = useState(false);
const [latestVersion, setLatestVersion] = useState(CURRENT_VERSION);
useEffect(() => {
// Fetch Data here when component first mounted
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
res.json().then((data) => {
setLatestVersion(data.tag_name);
if (data.tag_name !== CURRENT_VERSION) {
setUpdate(true);
}
});
});
}, []);
return (
<>
<Modal
size="md"
title={<Title order={3}>Settings</Title>}
opened={props.opened || opened}
onClose={() => setOpened(false)}
>
<SettingsMenu current={CURRENT_VERSION} latest={latestVersion} />
</Modal>
<ActionIcon
variant="default"
radius="md"
size="xl"
color="blue"
style={props.style}
onClick={() => setOpened(true)}
>
<Tooltip label="Settings">
<Indicator
size={12}
disabled={CURRENT_VERSION === latestVersion}
offset={-3}
position="top-end"
>
<SettingsIcon />
</Indicator>
</Tooltip>
</ActionIcon>
</>
);
}

View File

@@ -0,0 +1,22 @@
import { Aside as MantineAside, Group } from '@mantine/core';
import { CalendarModule } from '../modules/calendar/CalendarModule';
import ModuleWrapper from '../modules/moduleWrapper';
export default function Aside() {
return (
<MantineAside
hiddenBreakpoint="md"
hidden
style={{
border: 'none',
}}
width={{
base: 'auto',
}}
>
<Group mt="sm" direction="column">
<ModuleWrapper module={CalendarModule} />
</Group>
</MantineAside>
);
}

View File

@@ -1,5 +1,12 @@
import React from 'react';
import { createStyles, Anchor, Text, Group, ActionIcon } from '@mantine/core';
import {
createStyles,
Anchor,
Text,
Group,
ActionIcon,
Footer as FooterComponent,
} from '@mantine/core';
import { BrandGithub } from 'tabler-icons-react';
const useStyles = createStyles((theme) => ({
@@ -48,34 +55,39 @@ export function Footer({ links }: FooterCenteredProps) {
));
return (
<Group
sx={{
position: 'fixed',
bottom: 0,
right: 15,
}}
direction="row"
align="center"
mb={15}
>
<Group className={classes.links}>{items}</Group>
<Group spacing="xs" position="right" noWrap>
<ActionIcon<'a'> component="a" href="https://github.com/ajnart/myhomepage" size="lg">
<BrandGithub size={18} />
</ActionIcon>
</Group>
<Text
style={{
fontSize: '0.75rem',
textAlign: 'center',
color: '#a0aec0',
<FooterComponent height="auto" style={{ border: 'none' }}>
<Group
sx={{
position: 'fixed',
bottom: 0,
right: 15,
}}
direction="row"
align="center"
mb={15}
>
Made with by @
<Anchor href="https://github.com/ajnart" style={{ color: 'inherit', fontStyle: 'inherit' }}>
ajnart
</Anchor>
</Text>
</Group>
<Group className={classes.links}>{items}</Group>
<Group spacing="xs" position="right" noWrap>
<ActionIcon<'a'> component="a" href="https://github.com/ajnart/homarr" size="lg">
<BrandGithub size={18} />
</ActionIcon>
</Group>
<Text
style={{
fontSize: '0.90rem',
textAlign: 'center',
color: '#a0aec0',
}}
>
Made with by @
<Anchor
href="https://github.com/ajnart"
style={{ color: 'inherit', fontStyle: 'inherit', fontSize: 'inherit' }}
>
ajnart
</Anchor>
</Text>
</Group>
</FooterComponent>
);
}

View File

@@ -1,19 +1,11 @@
import React, { useState } from 'react';
import {
createStyles,
Header as Head,
Container,
Group,
Burger,
Drawer,
Center,
} from '@mantine/core';
import React from 'react';
import { createStyles, Header as Head, Group, Drawer, Center } from '@mantine/core';
import { useBooleanToggle } from '@mantine/hooks';
import { NextLink } from '@mantine/next';
import { Logo } from './Logo';
import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle';
import { SettingsMenuButton } from '../Settings/SettingsMenu';
import CalendarComponent from '../modules/calendar/CalendarModule';
import { SettingsMenuButton } from '../Settings/SettingsMenu';
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
const HEADER_HEIGHT = 60;
@@ -41,8 +33,6 @@ const useStyles = createStyles((theme) => ({
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
height: '100%',
},
@@ -95,63 +85,33 @@ interface HeaderResponsiveProps {
export function Header({ links }: HeaderResponsiveProps) {
const [opened, toggleOpened] = useBooleanToggle(false);
const [active, setActive] = useState('/');
const { classes, cx } = useStyles();
const items = (
<>
{links.map((link) => (
<NextLink
key={link.label}
href={link.link}
className={cx(classes.link, { [classes.linkActive]: active === link.link })}
onClick={(event) => {
setActive(link.link);
toggleOpened(false);
}}
>
{link.label}
</NextLink>
))}
</>
);
return (
<Head height={HEADER_HEIGHT} mb={10} className={classes.root}>
<Container className={classes.header}>
<Group>
<ColorSchemeToggle />
<NextLink style={{ textDecoration: 'none' }} href="/">
<Logo style={{ fontSize: 22 }} />
</NextLink>
</Group>
<Group spacing={5} className={classes.links}>
{items}
</Group>
<Head height={HEADER_HEIGHT}>
<Group direction="row" align="center" position="apart" className={classes.header} mx="xl">
<NextLink style={{ textDecoration: 'none' }} href="/">
<Logo style={{ fontSize: 22 }} />
</NextLink>
<Group>
<SettingsMenuButton />
<Burger
opened={opened}
onClick={() => toggleOpened()}
className={classes.burger}
size="sm"
/>
<AddItemShelfButton />
</Group>
</Group>
<Drawer
opened={opened}
overlayOpacity={0.55}
overlayBlur={3}
onClose={() => toggleOpened()}
position="right"
>
{opened ?? (
<Center>
<CalendarComponent />
</Center>
)}
</Drawer>
</Container>
<Drawer
opened={opened}
overlayOpacity={0.55}
overlayBlur={3}
onClose={() => toggleOpened()}
position="right"
>
{opened ?? (
<Center>
<CalendarComponent />
</Center>
)}
</Drawer>
</Head>
);
}

View File

@@ -1,12 +1,13 @@
import { AppShell, Aside, Center, createStyles } from '@mantine/core';
import { AppShell, Center, createStyles } from '@mantine/core';
import { Header } from './Header';
import { Footer } from './Footer';
import CalendarComponent from '../modules/calendar/CalendarModule';
import Aside from './Aside';
import Navbar from './Navbar';
const useStyles = createStyles((theme) => ({
main: {
[theme.fn.largerThan('md')]: {
width: 1200,
maxWidth: 1500,
},
},
}));
@@ -15,18 +16,8 @@ export default function Layout({ children, style }: any) {
const { classes, cx } = useStyles();
return (
<AppShell
aside={
<Aside
height="auto"
hiddenBreakpoint="md"
hidden
width={{
base: 'auto',
}}
>
<CalendarComponent />
</Aside>
}
navbar={<Navbar />}
aside={<Aside />}
header={<Header links={[]} />}
footer={<Footer links={[]} />}
>

View File

@@ -0,0 +1,40 @@
import { Group, Image, Text } from '@mantine/core';
import * as React from 'react';
import { CURRENT_VERSION } from '../../../data/constants';
export function Logo({ style }: any) {
return (
<Group>
<Image
width={50}
src="/imgs/logo.png"
style={{
position: 'relative',
left: 15,
}}
/>
<Text
sx={style}
weight="bold"
variant="gradient"
gradient={{ from: 'red', to: 'orange', deg: 145 }}
>
Homarr
</Text>
<Text
style={{
position: 'relative',
left: -14,
bottom: -2,
color: 'gray',
fontStyle: 'inherit',
fontSize: 'inherit',
alignSelf: 'center',
alignContent: 'center',
}}
>
{CURRENT_VERSION}
</Text>
</Group>
);
}

View File

@@ -0,0 +1,22 @@
import { Group, Navbar as MantineNavbar } from '@mantine/core';
import { DateModule } from '../modules/date/DateModule';
import ModuleWrapper from '../modules/moduleWrapper';
export default function Navbar() {
return (
<MantineNavbar
hiddenBreakpoint="lg"
hidden
style={{
border: 'none',
}}
width={{
base: 'auto',
}}
>
<Group mt="sm" direction="column">
<ModuleWrapper module={DateModule} />
</Group>
</MantineNavbar>
);
}

View File

@@ -1,14 +1,14 @@
/* eslint-disable react/no-children-prop */
import { Popover, Box, ScrollArea, Divider, Indicator } from '@mantine/core';
import { useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Calendar } from '@mantine/dates';
import { CalendarIcon } from '@modulz/radix-icons';
import { showNotification } from '@mantine/notifications';
import { Calendar as CalendarIcon, Check } from 'tabler-icons-react';
import { RadarrMediaDisplay, SonarrMediaDisplay } from './MediaDisplay';
import { useConfig } from '../../../tools/state';
import { MHPModule } from '../modules';
import React from 'react';
import { IModule } from '../modules';
export const CalendarModule: MHPModule = {
export const CalendarModule: IModule = {
title: 'Calendar',
description:
'A calendar module for displaying upcoming releases. It interacts with the Sonarr and Radarr API.',
@@ -35,14 +35,36 @@ export default function CalendarComponent(props: any) {
fetch(
`${sonarrService?.url}api/calendar?apikey=${sonarrService?.apiKey}&end=${nextMonth}`
).then((response) => {
response.ok && response.json().then((data) => setSonarrMedias(data));
response.ok &&
response.json().then((data) => {
setSonarrMedias(data);
showNotification({
title: 'Sonarr',
icon: <Check />,
color: 'green',
autoClose: 1500,
radius: 'md',
message: `Loaded ${data.length} releases`,
});
});
});
}
if (radarrService && radarrService.apiKey) {
fetch(
`${radarrService?.url}api/v3/calendar?apikey=${radarrService?.apiKey}&end=${nextMonth}`
).then((response) => {
response.ok && response.json().then((data) => setRadarrMedias(data));
response.ok &&
response.json().then((data) => {
setRadarrMedias(data);
showNotification({
title: 'Radarr',
icon: <Check />,
color: 'green',
autoClose: 1500,
radius: 'md',
message: `Loaded ${data.length} releases`,
});
});
});
}
}, [config.services]);
@@ -94,14 +116,13 @@ function DayComponent(props: any) {
setOpened(true);
}}
>
{radarrFiltered.length > 0 && (
<Indicator size={7} color="yellow" children={null} />
)}
{sonarrFiltered.length > 0 && (
<Indicator size={7} offset={8} color="blue" children={null} />
)}
{radarrFiltered.length > 0 && <Indicator size={7} color="yellow" children={null} />}
{sonarrFiltered.length > 0 && <Indicator size={7} offset={8} color="blue" children={null} />}
<Popover
position="left"
radius="lg"
shadow="xl"
transition="pop"
width={700}
onClose={() => setOpened(false)}
opened={opened}
@@ -109,25 +130,21 @@ function DayComponent(props: any) {
target={` ${day}`}
>
<ScrollArea style={{ height: 400 }}>
{sonarrFiltered.map((media: any, index: number) => {
return (
<React.Fragment key={index}>
<SonarrMediaDisplay media={media} />
{index < sonarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
</React.Fragment>
);
})}
{sonarrFiltered.map((media: any, index: number) => (
<React.Fragment key={index}>
<SonarrMediaDisplay media={media} />
{index < sonarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
</React.Fragment>
))}
{radarrFiltered.length > 0 && sonarrFiltered.length > 0 && (
<Divider variant="dashed" my="xl" />
)}
{radarrFiltered.map((media: any, index: number) => {
return (
<React.Fragment key={index}>
<RadarrMediaDisplay media={media} />
{index < radarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
</React.Fragment>
);
})}
{radarrFiltered.map((media: any, index: number) => (
<React.Fragment key={index}>
<RadarrMediaDisplay media={media} />
{index < radarrFiltered.length - 1 && <Divider variant="dashed" my="xl" />}
</React.Fragment>
))}
</ScrollArea>
</Popover>
</Box>

View File

@@ -15,7 +15,14 @@ function MediaDisplay(props: { media: IMedia }) {
const { media }: { media: IMedia } = props;
return (
<Group noWrap align="self-start" mr={15}>
<Image fit="cover" src={media.poster} alt={media.title} width={300} height={400} />
<Image
radius="md"
fit="cover"
src={media.poster}
alt={media.title}
width={300}
height={400}
/>
<Stack
justify="space-between"
sx={(theme) => ({

View File

@@ -0,0 +1 @@
export { CalendarModule } from './CalendarModule';

View File

@@ -0,0 +1,7 @@
import DateComponent from './DateModule';
export default {
title: 'Date module',
};
export const Default = (args: any) => <DateComponent {...args} />;

View File

@@ -0,0 +1,41 @@
import { Group, Text, Title } from '@mantine/core';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import { Clock } from 'tabler-icons-react';
import { IModule } from '../modules';
export const DateModule: IModule = {
title: 'Date',
description: 'Show the current time and date in a card',
icon: Clock,
component: DateComponent,
};
export default function DateComponent(props: any) {
const [date, setDate] = useState(new Date());
const hours = date.getHours();
const minutes = date.getMinutes();
// Change date on minute change
// Note: Using 10 000ms instead of 1000ms to chill a little :)
useEffect(() => {
setInterval(() => {
setDate(new Date());
}, 10000);
}, []);
return (
<Group p="sm" direction="column">
<Title>
{hours < 10 ? `0${hours}` : hours}:{minutes < 10 ? `0${minutes}` : minutes}
</Title>
<Text size="xl">
{
// Use dayjs to format the date
// https://day.js.org/en/getting-started/installation/
dayjs(date).format('dddd, MMMM D')
}
</Text>
</Group>
);
}

View File

@@ -0,0 +1 @@
export { DateModule } from './DateModule';

View File

@@ -0,0 +1,2 @@
export * from './date';
export * from './calendar';

View File

@@ -0,0 +1,29 @@
import { Card, useMantineTheme } from '@mantine/core';
import { useConfig } from '../../tools/state';
import { IModule } from './modules';
export default function ModuleWrapper(props: any) {
const { module }: { module: IModule } = props;
const { config } = useConfig();
const enabledModules = config.settings.enabledModules ?? [];
// Remove 'Module' from enabled modules titles
const isShown = enabledModules.includes(module.title);
const theme = useMantineTheme();
if (!isShown) {
return null;
}
return (
<Card
hidden={!isShown}
mx="sm"
radius="lg"
shadow="sm"
style={{
// Make background color of the card depend on the theme
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : 'white',
}}
>
<module.component />
</Card>
);
}

View File

@@ -2,7 +2,7 @@
// Each module should have its own interface and call the following function:
// TODO: Add a function to register a module
// Note: Maybe use context to keep track of the modules
export interface MHPModule {
export interface IModule {
title: string;
description: string;
icon: React.ReactNode;

View File

@@ -0,0 +1,9 @@
**Each module has a set of rules:**
- Exported Typed IModule element (Unique Name, description, component, ...)
- Needs to be in a new folder
- Needs to be exported in the modules/newmodule/index.tsx of the new folder
- Needs to be imported in the modules/index.tsx file
- Needs to look good when wrapped with the modules/ModuleWrapper component
- Needs to be put somewhere fitting in the app (While waiting for the big AppStore overhall)
- Any API Calls need to be safe and done on the widget itself (via useEffect or similar)
- You can't add a package (unless there is a very specific need for it. Contact [@Ajnart](ajnart@pm.me) or make a [Discussion](https://github.com/ajnart/homarr/discussions/new).

View File

@@ -5,8 +5,10 @@ import { getCookie, setCookies } from 'cookies-next';
import Head from 'next/head';
import { MantineProvider, ColorScheme, ColorSchemeProvider } from '@mantine/core';
import { NotificationsProvider } from '@mantine/notifications';
import { useHotkeys } from '@mantine/hooks';
import Layout from '../components/layout/Layout';
import { ConfigProvider } from '../tools/state';
import { theme } from '../tools/theme';
export default function App(props: AppProps & { colorScheme: ColorScheme }) {
const { Component, pageProps } = props;
@@ -15,20 +17,28 @@ export default function App(props: AppProps & { colorScheme: ColorScheme }) {
const toggleColorScheme = (value?: ColorScheme) => {
const nextColorScheme = value || (colorScheme === 'dark' ? 'light' : 'dark');
setColorScheme(nextColorScheme);
setCookies('mantine-color-scheme', nextColorScheme, { maxAge: 60 * 60 * 24 * 30 });
setCookies('color-scheme', nextColorScheme, { maxAge: 60 * 60 * 24 * 30 });
};
useHotkeys([['mod+J', () => toggleColorScheme()]]);
return (
<>
<Head>
<title>MyHomePage - Your new browser homepage!</title>
<title>Homarr - A homepage for your server!</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<link rel="shortcut icon" href="/favicon.svg" />
</Head>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider theme={{ colorScheme }} withGlobalStyles withNormalizeCSS>
<NotificationsProvider position="top-right">
<MantineProvider
theme={{
...theme,
colorScheme,
}}
withGlobalStyles
withNormalizeCSS
>
<NotificationsProvider limit={2} position="top-right">
<ConfigProvider>
<Layout>
<Component {...pageProps} />
@@ -42,5 +52,5 @@ export default function App(props: AppProps & { colorScheme: ColorScheme }) {
}
App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({
colorScheme: getCookie('mantine-color-scheme', ctx) || 'light',
colorScheme: getCookie('color-scheme', ctx) || 'light',
});

View File

@@ -0,0 +1,61 @@
import { NextApiRequest, NextApiResponse } from 'next';
import fs from 'fs';
import path from 'path';
function Put(req: NextApiRequest, res: NextApiResponse) {
// Get the slug of the request
const { slug } = req.query as { slug: string };
// Get the body of the request
const { body }: { body: string } = req;
if (!slug || !body) {
res.status(400).json({
error: 'Wrong request',
});
}
// Save the body in the /data/config folder with the slug as filename
fs.writeFileSync(
path.join('data/configs', `${slug}.json`),
JSON.stringify(body, null, 2),
'utf8'
);
return res.status(200).json({
message: 'Configuration saved with success',
});
}
function Get(req: NextApiRequest, res: NextApiResponse) {
// Get the slug of the request
const { slug } = req.query as { slug: string };
if (!slug) {
return res.status(400).json({
message: 'Wrong request',
});
}
// Loop over all the files in the /data/configs directory
const files = fs.readdirSync('data/configs');
// Strip the .json extension from the file name
const configs = files.map((file) => file.replace('.json', ''));
// If the target is not in the list of files, return an error
if (!configs.includes(slug)) {
return res.status(404).json({
message: 'Target not found',
});
}
// Return the content of the file
return res.status(200).json(fs.readFileSync(path.join('data/configs', `${slug}.json`), 'utf8'));
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a Put or a GET
if (req.method === 'PUT') {
return Put(req, res);
}
if (req.method === 'GET') {
return Get(req, res);
}
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
});
};

View File

@@ -0,0 +1,28 @@
import { NextApiRequest, NextApiResponse } from 'next';
import fs from 'fs';
function Get(req: NextApiRequest, res: NextApiResponse) {
// Loop over all the files in the /data/configs directory
const files = fs.readdirSync('data/configs');
// Strip the .json extension from the file name
const configs = files.map((file) => file.replace('.json', ''));
return res.status(200).json(configs);
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a POST or a GET
if (req.method === 'POST') {
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
});
}
if (req.method === 'GET') {
return Get(req, res);
}
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
});
};

64
src/pages/index.tsx Normal file
View File

@@ -0,0 +1,64 @@
import { Group } from '@mantine/core';
import { getCookie, setCookies } from 'cookies-next';
import { GetServerSidePropsContext } from 'next';
import path from 'path';
import fs from 'fs';
import { useEffect } from 'react';
import AppShelf from '../components/AppShelf/AppShelf';
import LoadConfigComponent from '../components/Config/LoadConfig';
import SearchBar from '../components/SearchBar/SearchBar';
import { Config } from '../tools/types';
import { useConfig } from '../tools/state';
export async function getServerSideProps({
req,
res,
}: GetServerSidePropsContext): Promise<{ props: { config: Config } }> {
let cookie = getCookie('config-name', { req, res });
if (!cookie) {
setCookies('config-name', 'default', { req, res, maxAge: 60 * 60 * 24 * 30 });
cookie = 'default';
}
// Check if the config file exists
const configPath = path.join(process.cwd(), 'data/configs', `${cookie}.json`);
if (!fs.existsSync(configPath)) {
return {
props: {
config: {
name: cookie.toString(),
services: [],
settings: {
enabledModules: [],
searchBar: true,
searchUrl: 'https://www.google.com/search?q=',
},
},
},
};
}
const config = fs.readFileSync(configPath, 'utf8');
// Print loaded config
return {
props: {
config: JSON.parse(config),
},
};
}
export default function HomePage(props: any) {
const { config: initialConfig }: { config: Config } = props;
const { config, loadConfig, setConfig, getConfigs } = useConfig();
useEffect(() => {
setConfig(initialConfig);
}, [initialConfig]);
return (
<>
<SearchBar />
<Group align="start" position="apart" noWrap>
<AppShelf />
</Group>
<LoadConfigComponent />
</>
);
}

94
src/tools/state.tsx Normal file
View File

@@ -0,0 +1,94 @@
// src/context/state.js
import { showNotification } from '@mantine/notifications';
import axios from 'axios';
import { createContext, ReactNode, useContext, useState } from 'react';
import { Check, X } from 'tabler-icons-react';
import { Config } from './types';
type configContextType = {
config: Config;
setConfig: (newconfig: Config) => void;
loadConfig: (name: string) => void;
getConfigs: () => Promise<string[]>;
};
const configContext = createContext<configContextType>({
config: {
name: 'default',
services: [],
settings: {
searchBar: true,
searchUrl: 'https://www.google.com/search?q=',
enabledModules: [],
},
},
setConfig: () => {},
loadConfig: async (name: string) => {},
getConfigs: async () => [],
});
export function useConfig() {
const context = useContext(configContext);
if (context === undefined) {
throw new Error('useConfig must be used within a ConfigProvider');
}
return context;
}
type Props = {
children: ReactNode;
};
export function ConfigProvider({ children }: Props) {
const [config, setConfigInternal] = useState<Config>({
name: 'default',
services: [],
settings: {
searchBar: true,
searchUrl: 'https://www.google.com/search?q=',
enabledModules: [],
},
});
async function loadConfig(configName: string) {
try {
const response = await axios.get(`/api/configs/${configName}`);
setConfigInternal(response.data);
showNotification({
title: 'Config',
icon: <Check />,
color: 'green',
autoClose: 1500,
radius: 'md',
message: `Loaded config : ${configName}`,
});
} catch (error) {
showNotification({
title: 'Config',
icon: <X />,
color: 'red',
autoClose: 1500,
radius: 'md',
message: `Error loading config : ${configName}`,
});
}
}
function setConfig(newconfig: Config) {
axios.put(`/api/configs/${newconfig.name}`, newconfig);
setConfigInternal(newconfig);
}
async function getConfigs(): Promise<string[]> {
const response = await axios.get('/api/configs');
return response.data;
}
const value = {
config,
setConfig,
loadConfig,
getConfigs,
};
return <configContext.Provider value={value}>{children}</configContext.Provider>;
}

3
src/tools/theme.ts Normal file
View File

@@ -0,0 +1,3 @@
import { MantineProviderProps } from '@mantine/core';
export const theme: MantineProviderProps['theme'] = {};

View File

@@ -1,10 +1,12 @@
export interface Settings {
searchUrl: string;
searchBar: boolean;
enabledModules: string[];
[key: string]: any;
}
export interface Config {
name: string;
services: serviceItem[];
settings: Settings;
}

View File

@@ -1,7 +0,0 @@
import { serviceItem } from './types';
export function pingQbittorrent(service: serviceItem): any {
console.log('Getting service.cookie for service: ', service);
if (!service.cookie) service.cookie = 'Test';
else console.log(service.cookie);
}

View File

@@ -1,100 +0,0 @@
// src/context/state.js
import { createContext, ReactNode, useContext, useState } from 'react';
import { Config, serviceItem } from './types';
type configContextType = {
config: Config;
setConfig: (newconfig: Config) => void;
addService: (service: serviceItem) => void;
removeService: (name: string) => void;
saveConfig: (newconfig: Config) => void;
};
const configContext = createContext<configContextType>({
config: {
services: [],
settings: {
searchBar: true,
searchUrl: 'https://www.google.com/search?q=',
},
},
setConfig: () => {},
addService: () => {},
removeService: () => {},
saveConfig: () => {},
});
export function useConfig() {
const context = useContext(configContext);
if (context === undefined) {
throw new Error('useConfig must be used within a ConfigProvider');
}
return context;
}
type Props = {
children: ReactNode;
};
export function ConfigProvider({ children }: Props) {
const [config, setConfigInternal] = useState<Config>({
services: [
{
type: 'Other',
name: 'example',
icon: 'https://c.tenor.com/o656qFKDzeUAAAAC/rick-astley-never-gonna-give-you-up.gif',
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
},
],
settings: {
searchBar: true,
searchUrl: 'https://www.google.com/search?q=',
},
});
function setConfig(newConfig: Config) {
setConfigInternal(newConfig);
saveConfig(newConfig);
}
function addService(item: serviceItem) {
setConfigInternal({
...config,
services: [...config.services, item],
});
saveConfig({
...config,
services: [...config.services, item],
});
}
function removeService(name: string) {
// Remove the service with name in config item
setConfigInternal({
...config,
services: config.services.filter((service) => service.name !== name),
});
saveConfig({
...config,
services: config.services.filter((service) => service.name !== name),
});
}
function saveConfig(newconfig: Config) {
if (!newconfig) return;
localStorage.setItem('config', JSON.stringify(newconfig));
}
const value = {
config,
setConfig,
addService,
removeService,
saveConfig,
};
return (
<>
<configContext.Provider value={value}>{children}</configContext.Provider>
</>
);
}

View File

@@ -15,6 +15,6 @@
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"],
"exclude": ["node_modules"]
}

337
yarn.lock
View File

@@ -1163,17 +1163,7 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@emotion/cache@^10.0.27":
version "10.0.29"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==
dependencies:
"@emotion/sheet" "0.9.4"
"@emotion/stylis" "0.8.5"
"@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5"
"@emotion/cache@^11.7.1":
"@emotion/cache@11.7.1", "@emotion/cache@^11.7.1":
version "11.7.1"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539"
integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==
@@ -1184,6 +1174,16 @@
"@emotion/weak-memoize" "^0.2.5"
stylis "4.0.13"
"@emotion/cache@^10.0.27":
version "10.0.29"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0"
integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==
dependencies:
"@emotion/sheet" "0.9.4"
"@emotion/stylis" "0.8.5"
"@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5"
"@emotion/core@^10.1.1":
version "10.3.1"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.3.1.tgz#4021b6d8b33b3304d48b0bb478485e7d7421c69d"
@@ -1227,7 +1227,7 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
"@emotion/react@^11.7.1":
"@emotion/react@11.7.1":
version "11.7.1"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.7.1.tgz#3f800ce9b20317c13e77b8489ac4a0b922b2fe07"
integrity sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==
@@ -1240,6 +1240,17 @@
"@emotion/weak-memoize" "^0.2.5"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@1.0.2", "@emotion/serialize@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
dependencies:
"@emotion/hash" "^0.8.0"
"@emotion/memoize" "^0.7.4"
"@emotion/unitless" "^0.7.5"
"@emotion/utils" "^1.0.0"
csstype "^3.0.2"
"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16":
version "0.11.16"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad"
@@ -1251,18 +1262,7 @@
"@emotion/utils" "0.11.3"
csstype "^2.5.7"
"@emotion/serialize@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965"
integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==
dependencies:
"@emotion/hash" "^0.8.0"
"@emotion/memoize" "^0.7.4"
"@emotion/unitless" "^0.7.5"
"@emotion/utils" "^1.0.0"
csstype "^3.0.2"
"@emotion/server@^11.4.0":
"@emotion/server@11.4.0":
version "11.4.0"
resolved "https://registry.yarnpkg.com/@emotion/server/-/server-11.4.0.tgz#3ae1d74cb31c7d013c3c76e88c0c4439076e9f66"
integrity sha512-IHovdWA3V0DokzxLtUNDx4+hQI82zUXqQFcVz/om2t44O0YSc+NHB+qifnyAOoQwt3SXcBTgaSntobwUI9gnfA==
@@ -1315,7 +1315,7 @@
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924"
integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
"@emotion/utils@^1.0.0":
"@emotion/utils@1.0.0", "@emotion/utils@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
@@ -1594,95 +1594,104 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@mantine/core@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-4.2.1.tgz#cd93bc0c1c9ac8c25327867dec7d977790297f3c"
integrity sha512-YEz0SG8HN+H1s/gpqEzSUUFPXdaMQnZgJPO8JTOgX/qlVvggw+Y8TaLby4A+4kgQdGiXTWxr1Njx1/u72Fa1KA==
"@mantine/core@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-4.2.4.tgz#9c36acbaaa4287f61e9dc85eb631c4e5a7e417de"
integrity sha512-YeN8slz3bcb47NUUkSZ4UAedMZH7bWvqIx7OfRf2D0m2o4Fu1BLO73xUCpgj18R31fx3BcSopRujZC9x/8bqUg==
dependencies:
"@mantine/styles" "4.2.1"
"@mantine/styles" "4.2.4"
"@popperjs/core" "^2.9.3"
"@radix-ui/react-scroll-area" "^0.1.1"
react-popper "^2.2.5"
react-textarea-autosize "^8.3.2"
"@mantine/dates@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-4.2.1.tgz#0d92aa69f2dcb0c6244ed38f7c4e4bc9bf8d8a5a"
integrity sha512-5zgDpm2xNWMBRKXANI8vo1W2wFyBDiTL5R/68elrfs01T1B60uS4tQ73voMvwIvjbM/oXZqWhFpD7dobdf5kNw==
"@mantine/dates@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-4.2.4.tgz#4018a56a0f58feb56cd7036f5489c0c76ee76921"
integrity sha512-ullVkmiOhLBTk30zJgRraMggR8ZLPc4IEwM83dJbhx4mTiJ10CFvl8oz23j2yy00tInCw6vYglqK514ytlsItg==
"@mantine/dropzone@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-4.2.1.tgz#42391f296a75b7448e28e4e09031257b1e585d08"
integrity sha512-MT3BPfw+2MteGTIrm1EoAdKDhpQ5kE5POs87njNWhbav82grE4JHmtCCj8Na4U3+e6O6ic3uh+ofK048lY7aDg==
"@mantine/dropzone@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-4.2.4.tgz#3825bfb2c3f14a0c348efa29e0079df6bec67fa2"
integrity sha512-Y4lzzkKEtfbW941BdEwFBaS87ZAXyXB080GJUxxpuP0KQ1xb9GG66l3l3zu/+UcwXOyT4m91W2E3okip9BcIew==
dependencies:
react-dropzone "^11.4.2"
"@mantine/form@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-4.2.1.tgz#4b0ca3a500802b1a47ad402cd021c849c73a6099"
integrity sha512-uB9v666mMCIxBJCdE+13KI5g2gNX25qevqwBwxVYnvwKVT/fuQE+lO2TRvfBQfk4PgEzmFgsm/YSG0q6bBE5cA==
"@mantine/form@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-4.2.4.tgz#37719262bf5dfe6ba5a6cce23b4b87b12a1a836d"
integrity sha512-7exttJwSY0yRjzChL6L0NM5i5xxXuGiP4c8quGb7/mxP/hwfUSFMOua3Pme8svP7U8w3gxZL3HWvzGMQQfXqSw==
"@mantine/hooks@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-4.2.1.tgz#60960c24debaf6c0bd2e51f84ac28abecec534b8"
integrity sha512-+sg8aBK1IzCLGrtL2PEUp1q2/3blUvxJPoqIE6wwcvtIc7cFYfPWRdFFRtkwJjHQ+KlH6TvCO0Yl7F6FBq7+Rw==
"@mantine/hooks@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-4.2.4.tgz#81b049355404d1001a1267e80e1e1aaae4682083"
integrity sha512-C4Mvm9suYFuM5dDJXKaZ2fRFvJ5JMXl5S7eSNsHvzkRmCy0GppKkDETwXi0uwS7QyjMllNRizGvoyU4CyHyvrA==
"@mantine/modals@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-4.2.1.tgz#9529f10d3227cf0940dcecbc5a767531abc3230c"
integrity sha512-YqFJcU1raFlLjO6T3PENdXQmiYqGfAqg27bYIhEYciuVIt+UR1eNSPrw0Yj2H2arCVVZ/RUF1CjiFG2lwvwTVw==
"@mantine/modals@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-4.2.4.tgz#2b168c5be869ee45b9be492e52d9978c5bd3ed53"
integrity sha512-ce0shaBjAw5kzSCZ8cDgtct3ge1dtB0RQyYMvk+6y3na4gqXqUOsHJmQ8cUVAOb78Ge7ibEQA0rOW6dOSeAVRA==
"@mantine/next@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/next/-/next-4.2.1.tgz#cb3616f26bdf786337333424c29ab4fd4774e98c"
integrity sha512-AM7Hf9rtBlEv7zRpyVVisuQ0Dc+FuY2rW6y0m19AMZ65Jv/PYDmME35cACqhNHIjRYCzbljaepW2mAym/maapQ==
"@mantine/next@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/next/-/next-4.2.4.tgz#160dcf02e6fd20c34fbc63ac4f2baa050d605cc3"
integrity sha512-YU/Hv4srckitt1n5nKf9FVSfvYLypQfevQX/rcy00Bjr4iFxZQdpGCCdfSw82XR/JaY6Bw/Soj9BdGhK0DPcRg==
dependencies:
"@mantine/ssr" "4.2.1"
"@mantine/ssr" "4.2.4"
"@mantine/notifications@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-4.2.1.tgz#2bff96858bc6357f017439df12165b31d4f395bd"
integrity sha512-16jqAMwdANexOpVmBFwquP6nAJQPDBWmhEcf6Fm4mFhB8wpBX5nKepUsa+In49CvfPBHMEsROrJ4YqcwoP8Eig==
"@mantine/notifications@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-4.2.4.tgz#7f45dc2cd21e16f01c1eb2f9c5525b5688b80ead"
integrity sha512-BgiG8jMW6WStgHuuj/BSX6g8camSUY1iP5SFw0hFkseWW4Y/6IPbcshejGQKS7stkjoJZQVZGaXkJQGkZcWcpA==
dependencies:
react-transition-group "^4.4.2"
"@mantine/prism@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/prism/-/prism-4.2.1.tgz#fb7b5bf95a518d64ba389b952d4df2a4a2018eb7"
integrity sha512-uVn3uzmiF6efylX1u4WtnCfSiHNTAuMsCi9gcuWS/rXCZtFkIqo6Qtk9I5YhHOG5K70lh3tNQ5C7NLDjL7Krxg==
"@mantine/prism@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/prism/-/prism-4.2.4.tgz#b687b6073e295bf7683bc4fccaa4addc955af594"
integrity sha512-r/Q3XdpjCkrW+6Tsh4N94FlvsJ6jflg9JXv1UukAyoSvZpfyqTErb+FHK3J4eC26bVbaEj9hfq1fBDvkJNwMsQ==
dependencies:
prism-react-renderer "^1.2.1"
"@mantine/spotlight@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-4.2.1.tgz#deb2c3b4e4a588c6c088287f0b1bf5bd6e4c79b3"
integrity sha512-/eKup56TKmaTsLyNYzqx7SaLPoGB7cMGWni73M7TtY0sPBwc0tEnNA953fiBBpANJ5QtpGGSPSdRaXjanPJNiQ==
"@mantine/ssr@4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/ssr/-/ssr-4.2.1.tgz#4d9fb3622188946ad37a91278d4209a1d6fca3a3"
integrity sha512-OZTehqwV/YdI1fVoisrcOiHj7RJmTxQ969UTYnmavXVQ7S/UVS3xayHd38c+82vP9F61q5fWEy4YpCC0/9v2zw==
"@mantine/rte@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/rte/-/rte-4.2.4.tgz#99cec28f55159a0089cc30ac08b0980e07ffde67"
integrity sha512-fkW8TTpOOCrDuHkxHGMjP/DiwriwagaHD5Ry5oKSKXVXB9NMl/McmIdpII9zgH3jpnSCLcSQjr2knRrMlx+YUg==
dependencies:
"@emotion/cache" "^11.7.1"
"@emotion/react" "^11.7.1"
"@emotion/serialize" "^1.0.2"
"@emotion/server" "^11.4.0"
"@emotion/utils" "^1.0.0"
"@mantine/styles" "4.2.1"
csstype "^3.0.9"
html-react-parser "^1.3.0"
"@modulz/radix-icons" "^4.0.0"
quill-mention "^3.0.8"
react-quill "2.0.0-beta.4"
"@mantine/styles@4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-4.2.1.tgz#a4d1a8373c59c07fb1ec0e6d43d5b288a5abbbc7"
integrity sha512-KHQDCFh4tOBYjKZ7Y5f7qhVXQDKDAhuQz2jJeSE9rUrUaXtJJaAvlrV884AD2vx58k2UUKpAAaBt8CaT1VAXgw==
"@mantine/spotlight@^4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-4.2.4.tgz#3de8bdce02cc4623f44d13a4b9f5e3a4448dbe17"
integrity sha512-jxFqR5myZ0fD8URBF5eH1lisfSUPy2fW28IUHCOEn08oL6+zsHUxeAUuVDUbZXbuhjkoBGyUh7BXRln8YcYXfw==
"@mantine/ssr@4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/ssr/-/ssr-4.2.4.tgz#3a32638e25531f400f725d8f95d8496efa818caa"
integrity sha512-3hPSTTvkapuiMlUGLa3xDSOJpjlklMCOOMh01Yu4VRmj/n0Jd9KS9T9fOWnxF3q8wZDy3sV56mr9N4nc143B6A==
dependencies:
"@emotion/cache" "^11.7.1"
"@emotion/react" "^11.7.1"
"@emotion/serialize" "^1.0.2"
"@emotion/utils" "^1.0.0"
"@emotion/cache" "11.7.1"
"@emotion/react" "11.7.1"
"@emotion/serialize" "1.0.2"
"@emotion/server" "11.4.0"
"@emotion/utils" "1.0.0"
"@mantine/styles" "4.2.4"
csstype "3.0.9"
html-react-parser "1.3.0"
"@mantine/styles@4.2.4":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-4.2.4.tgz#6e6e6f999cff1bc1600082714946b2c8ce5b4c49"
integrity sha512-nG8qwE7uuZ7pJaY5jES9DLIkYxlLf3pK8Gn01gdbSfcyDC8hldvr83bMQOS9gLZ/3F/lrhtsu8u+31kxXIBIsw==
dependencies:
"@emotion/cache" "11.7.1"
"@emotion/react" "11.7.1"
"@emotion/serialize" "1.0.2"
"@emotion/utils" "1.0.0"
clsx "^1.1.1"
csstype "^3.0.9"
csstype "3.0.9"
"@mdx-js/loader@^1.6.22":
version "1.6.22"
@@ -3095,6 +3104,13 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
"@types/quill@^1.3.10":
version "1.3.10"
resolved "https://registry.yarnpkg.com/@types/quill/-/quill-1.3.10.tgz#dc1f7b6587f7ee94bdf5291bc92289f6f0497613"
integrity sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==
dependencies:
parchment "^1.1.2"
"@types/react-dom@*":
version "17.0.14"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f"
@@ -3893,6 +3909,14 @@ axe-core@^4.3.5:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
axios@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
dependencies:
follow-redirects "^1.14.9"
form-data "^4.0.0"
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -4681,6 +4705,11 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
clone@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
@@ -5116,16 +5145,16 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
csstype@3.0.9, csstype@^3.0.2:
version "3.0.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
csstype@^2.5.7:
version "2.6.20"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
csstype@^3.0.2, csstype@^3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@@ -5145,10 +5174,10 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
dayjs@^1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.1.tgz#90b33a3dda3417258d48ad2771b415def6545eb0"
integrity sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA==
dayjs@^1.11.2:
version "1.11.2"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"
integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
@@ -5193,6 +5222,18 @@ dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-equal@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
dependencies:
is-arguments "^1.0.4"
is-date-object "^1.0.1"
is-regex "^1.0.4"
object-is "^1.0.1"
object-keys "^1.1.1"
regexp.prototype.flags "^1.2.0"
deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@@ -6089,6 +6130,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
eventemitter3@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
events@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -6214,7 +6260,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@^3.0.0:
extend@^3.0.0, extend@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -6238,6 +6284,11 @@ fast-deep-equal@^3.0.0, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
fast-glob@^2.2.6:
version "2.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
@@ -6442,6 +6493,11 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.14.9:
version "1.15.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4"
integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -6496,6 +6552,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
@@ -7106,7 +7171,7 @@ html-minifier-terser@^5.0.1:
relateurl "^0.2.7"
terser "^4.6.3"
html-react-parser@^1.3.0:
html-react-parser@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.3.0.tgz#81eff0558f34183ac1d372aa9218b8fb47bb3d6d"
integrity sha512-lhpkOFH8pwqEjlNUYCWvjT43/JVCZO9MAZuCS6afT1/VP+bZcNxNUs4AUqiMzH0QPSDHwM/GFNXZNok1KTA4BQ==
@@ -7356,7 +7421,7 @@ is-alphanumerical@^1.0.0:
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-arguments@^1.1.0:
is-arguments@^1.0.4, is-arguments@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
@@ -7598,7 +7663,7 @@ is-potential-custom-element-name@^1.0.1:
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
is-regex@^1.1.2, is-regex@^1.1.4:
is-regex@^1.0.4, is-regex@^1.1.2, is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
@@ -8547,7 +8612,7 @@ lodash.uniq@4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -8853,16 +8918,11 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.1.1, minimist@^1.2.5, minimist@^1.2.6:
minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minimist@^1.2.0, minimist@~1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
@@ -9208,6 +9268,14 @@ object-inspect@^1.11.0, object-inspect@^1.9.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
object-is@^1.0.1:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -9497,6 +9565,11 @@ param-case@^3.0.3:
dot-case "^3.0.4"
tslib "^2.0.3"
parchment@^1.1.2, parchment@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -10017,6 +10090,34 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quill-delta@^3.6.2:
version "3.6.3"
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==
dependencies:
deep-equal "^1.0.1"
extend "^3.0.2"
fast-diff "1.1.2"
quill-mention@^3.0.8:
version "3.1.0"
resolved "https://registry.yarnpkg.com/quill-mention/-/quill-mention-3.1.0.tgz#acf0bf21524b627e9304f63534e6d2b8c59ab4fd"
integrity sha512-uyjGK8QPJHEcjvNc3wUJy6a05Oiu+6JJ0N9SFAwjYHYThgMzlKucyD975fq28Mr1it8ZFRNiRMPa0sCiVOAEwA==
dependencies:
quill "^1.3.7"
quill@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==
dependencies:
clone "^2.1.1"
deep-equal "^1.0.1"
eventemitter3 "^2.0.3"
extend "^3.0.2"
parchment "^1.1.4"
quill-delta "^3.6.2"
ramda@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
@@ -10177,6 +10278,15 @@ react-property@2.0.0:
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
react-quill@2.0.0-beta.4:
version "2.0.0-beta.4"
resolved "https://registry.yarnpkg.com/react-quill/-/react-quill-2.0.0-beta.4.tgz#522bd2680dc55713068c6cac12f2bf2ccfebcd28"
integrity sha512-KyAHvAlPjP4xLElKZJefMth91Z6FbbXRvq9OSu6xN3KBaoasLP9p+3dcxg4Ywr4tBlpMGXcPszYSAgd5CpJ45Q==
dependencies:
"@types/quill" "^1.3.10"
lodash "^4.17.4"
quill "^1.3.7"
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
@@ -10360,6 +10470,15 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexp.prototype.flags@^1.2.0:
version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
functions-have-names "^1.2.2"
regexp.prototype.flags@^1.3.1, regexp.prototype.flags@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307"