Compare commits

..

274 Commits

Author SHA1 Message Date
Alex
6fcc1d7685 Up version for release 2022-10-29 10:30:27 -05:00
Alex
30ba3aeea7 fix(web) date group hover not trigger correctly (#892) 2022-10-29 10:17:53 -05:00
bo0tzz
6172d80776 feat(web) Make "no albums" card clickable (#888)
* Make "no albums" card clickable

* Add cursor-pointer to clickable "no albums" card
2022-10-29 09:48:24 -05:00
Alex Tran
811494e9ed Fix test 2022-10-28 21:00:31 -05:00
Alex
137b2ffdd0 fix(web) video keep playing when mouse is not over video (#885) 2022-10-28 20:52:14 -05:00
Jason Rasmussen
05e69da5d8 feat(server) add docker debug config for server (#883) 2022-10-28 20:35:14 -05:00
bo0tzz
b0f10b1851 feat(web) add handler for ctrl-c copying images from viewer (#881) 2022-10-28 17:18:28 -05:00
Alex
b1212fc98b fix(web) prevent create multiple user when the instance is lagging (#882) 2022-10-28 15:29:36 -05:00
Matthias Rupp
ea99567805 feat(server) Extend PUT /album/:id/assets endpoint (#857)
* Add new query parameter to API endpoint that allows adding assets to albums which potentially contain assets that are already part of this album.

* Change API endpoint

* Generate new APIs

* Fixed test

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-10-28 14:54:09 -05:00
Jason Rasmussen
443c842723 refactor(server): merge auth guards to authentication guard (#877) 2022-10-28 13:57:52 -05:00
Alex Tran
9614da6238 Update documentation path name 2022-10-27 22:37:27 -05:00
Alex Tran
0164171cad Fixex routing 2022-10-27 22:03:37 -05:00
Alex Tran
8e78160bb9 Change documentation folder to docs 2022-10-27 21:44:37 -05:00
Alex
7d118b5d42 feat(app) add documentation site template (#879)
* Added site

* update

* Added overview section

* Added skeleton
2022-10-27 21:38:26 -05:00
Alex
676ad2d34f chore(server) revert Dockerfile (#878) 2022-10-27 17:21:28 -05:00
Alex Tran
b3a82b1b20 Update cache for thumbnail 2022-10-26 23:55:38 -05:00
Alex Tran
25848b78f9 Up version for release 2022-10-26 11:24:04 -05:00
Alex
f94176a910 feat(web) dark mode (#867) 2022-10-26 11:10:48 -05:00
Alex Tran
ae96508e15 Fixed unit test 2022-10-25 22:15:17 -05:00
Alex
95ebf815eb feat(web) styling server stats page (#866) 2022-10-25 21:41:46 -05:00
Jonas Janz
b713fb5650 feat(docker) revert ubuntu base image (#863)
* feat(docker) revert ubuntu base image

This PR reverts the base image for immich-server back to alpine

Adds LICENSE to all Images
Quiets apt-get commands when building
ensures write-permission for root group on app folders

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>

* Test build old Docker content

* Revert and retry

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-10-25 14:18:37 -05:00
Alex
6159c83fd2 feat(mobile) duplicated asset upload handling mechanism (#853) 2022-10-25 09:51:03 -05:00
Jonas Janz
f1af17bf4d feat(immich-server) use ubuntu base-image (#851)
this changes the base-image for immich-server from
`node:16-alpine3.14`
to
`node:16-slim`

There is an open issue with alpine DNS resolving which
breaks immich-microservice when deployed on
kubernetes.

This fixes https://github.com/immich-app/immich-charts/issues/4

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>
2022-10-24 14:59:07 -05:00
Alex
a87c1c1210 fix(mobile) not possible to sign out when option is enable (#860) 2022-10-24 14:45:58 -05:00
Alex
e63d165b65 chore(server) add workflow dispatcher to sdk repository (#859) 2022-10-24 12:55:16 -05:00
Alex Tran
9411770253 update readme 2022-10-23 22:21:48 -05:00
Alex Tran
dc80ac1c88 Remove openapi generator - move to TeamCity CI/CD for this job 2022-10-23 18:19:21 -05:00
Alex Tran
bb055628cc Fixed api generation action 2022-10-23 17:56:16 -05:00
Alex Tran
390bcdb8c6 Fixed api generation action 2022-10-23 17:53:11 -05:00
Alex Tran
d95bcb46ad Fixed api generation action 2022-10-23 17:52:52 -05:00
Alex
7b954e21e7 fix(server): add permission for server stats api (#854) 2022-10-23 17:01:41 -05:00
Zeeshan Khan
a6eea4d096 feat(web) add asset count stats on admin page (#843) 2022-10-23 16:54:54 -05:00
Alex
2c189d5c78 fix(server): force best effort to decode thumbnail image (#847) 2022-10-22 11:40:25 -05:00
Alex
85a80fd032 Added changlog 2022-10-21 13:19:04 -05:00
Zeeshan Khan
0309b47515 fixes(mobile) back navigation issue on android (#841) 2022-10-21 13:05:44 -05:00
bo0tzz
95d8f60389 feat(server)Log username and IP address on failed login attempt 2022-10-21 11:04:01 -05:00
Alex Tran
1ec7122381 Up version for release 2022-10-19 20:07:53 -05:00
Alex
061b229e12 feat(mobile): Cache assets and albums for faster loading speed
feat(mobile): Cache assets and albums for faster loading speed
2022-10-19 15:53:15 -05:00
Matthias Rupp
3617433858 Refactor abstract class to separate file 2022-10-19 22:03:54 +02:00
Alex
d6d525cc1b fix(mobile) back button navigation Android
fixes #310 back button navigation
2022-10-19 14:51:48 -05:00
Alex
e752290458 Merge pull request #839 from immich-app/dependabot/github_actions/docker/setup-buildx-action-2.2.1
chore(deps): bump docker/setup-buildx-action from 2.1.0 to 2.2.1
2022-10-18 09:27:47 -05:00
Matthias Rupp
d77e25425e Add cache for shared albums 2022-10-18 14:06:35 +02:00
dependabot[bot]
028c0249a3 chore(deps): bump docker/setup-buildx-action from 2.1.0 to 2.2.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.1.0 to 2.2.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.1.0...v2.2.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-18 10:45:12 +00:00
Zeeshan Khan
a3ca5307a5 fixes #310 back button navigation 2022-10-17 13:04:17 -05:00
Matthias Rupp
6796462b13 Switch to plain fs based caching mechanism 2022-10-17 18:02:43 +02:00
Matthias Rupp
d08475d5af Switch to lazyBox 2022-10-17 16:40:51 +02:00
Matthias Rupp
d310c77fc8 Add album list response caching 2022-10-17 14:53:27 +02:00
Matthias Rupp
75d8ca1306 Invalidation on logout and timing measurements 2022-10-16 09:50:31 +02:00
Matthias Rupp
894eea739e JSON based caching 2022-10-15 23:20:15 +02:00
Matthias Rupp
1156290377 Add asset response cache 2022-10-14 23:57:55 +02:00
Alex Tran
c271f0c224 Up version for release 2022-10-14 16:21:26 -05:00
Alex
a7f14dc103 feat(mobile): cosmetic update 2022-10-14 16:17:14 -05:00
Alex Tran
f05d5bdb9e Added haptic feedback to incorect login 2022-10-14 16:13:35 -05:00
Alex Tran
e99c400f59 Added haptic feedback to tab bar switching 2022-10-14 16:04:21 -05:00
Alex Tran
e38166837d Merge branch 'main' of github.com:immich-app/immich into dev/mobile-cosmetic-improvement 2022-10-14 15:58:26 -05:00
Alex
d43a08eb71 feat(mobile) integrate new grid system to upstream 2022-10-14 15:39:16 -05:00
Alex Tran
293e713af6 Hide bottom app bar when multiselect enabled 2022-10-14 15:37:15 -05:00
Alex Tran
03866b4c31 Merge branch 'feature/albums-new-grid' of github.com:immich-app/immich into feature/albums-new-grid 2022-10-14 14:52:07 -05:00
Alex Tran
4f2c08525f Merge upstream 2022-10-14 14:52:00 -05:00
Matthias Rupp
2c12f53937 Fix storage indicator settings 2022-10-14 21:17:23 +02:00
Alex Tran
c88e5f9be2 Add haptic feedback on swiping asset 2022-10-14 11:26:10 -05:00
Alex Tran
0f51a9794e fixed delete dialog colors 2022-10-14 11:15:19 -05:00
Alex Tran
edd1f49e57 Update text for foreground backup to make things clearer 2022-10-14 10:50:40 -05:00
Alex
4df0cf2d07 Merge pull request #821 from immich-app/feature/jwt-bits-warning
Log a warning if JWT_SECRET key does not have enough bits
2022-10-14 08:42:27 -05:00
bo0tzz
87ba99755b Remove unused variable 2022-10-13 22:17:31 +02:00
bo0tzz
c03f860f8e Log a warning if JWT_SECRET key does not have enough bits 2022-10-13 21:54:29 +02:00
Alex
f2e0e3f345 Merge pull request #820 from immich-app/dependabot/github_actions/docker/setup-qemu-action-2.1.0
chore(deps): bump docker/setup-qemu-action from 2.0.0 to 2.1.0
2022-10-13 08:44:37 -05:00
dependabot[bot]
fee652dfd7 chore(deps): bump docker/setup-qemu-action from 2.0.0 to 2.1.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-13 10:55:56 +00:00
Alex
839446a88d Merge pull request #816 from immich-app/dependabot/github_actions/docker/build-push-action-3.2.0
chore(deps): bump docker/build-push-action from 3.1.1 to 3.2.0
2022-10-12 08:50:14 -05:00
Alex
028b8c8bcc Merge pull request #815 from immich-app/dependabot/github_actions/docker/setup-buildx-action-2.1.0
chore(deps): bump docker/setup-buildx-action from 2.0.0 to 2.1.0
2022-10-12 08:50:02 -05:00
dependabot[bot]
64b1d4ca3b chore(deps): bump docker/build-push-action from 3.1.1 to 3.2.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.1.1...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 10:47:08 +00:00
dependabot[bot]
c6cbee6563 chore(deps): bump docker/setup-buildx-action from 2.0.0 to 2.1.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 10:47:05 +00:00
Alex
a406f6e7cc feat(server): Remove default JWT_SECRET value in .env 2022-10-12 03:52:06 -05:00
bo0tzz
9869b92c2b Generate random JWT_SECRET value in install.sh 2022-10-12 09:34:10 +02:00
bo0tzz
00549eed79 Uncomment JWT_SECRET in default .env
Co-authored-by: Alex <alex.tran1502@gmail.com>
2022-10-12 09:18:43 +02:00
bo0tzz
0c4968dc30 Fix: Remove default JWT_SECRET value in .env 2022-10-11 21:13:37 +02:00
Alex
704335c898 Merge pull request #801 from EvilOlaf/patch-1 2022-10-10 05:06:07 -05:00
Werner
ec74feea5a Typo/minor cosmetics 2022-10-10 07:46:23 +02:00
Alex
2f5cc3059a Merge pull request #799 from AnTheMaker/patch-1 2022-10-09 11:00:25 -05:00
An | Anton Röhm
4355485581 lower z-index of #immich-scrubbable-scrollbar 2022-10-09 16:55:10 +02:00
An | Anton Röhm
342c3254cb add z-index to #account-info-panel 2022-10-09 16:54:21 +02:00
Alex
5fc82dfaa2 Merge pull request #793 from immich-app/fix/update-reverse-geocoder
Update local-reverse-geocoder to 0.12.5
2022-10-08 15:40:42 -05:00
Matthias Rupp
6ab6507db9 Revert changes to albums 2022-10-08 13:18:45 +02:00
Matthias Rupp
3c807ae86e Exernalize multiselect state 2022-10-08 13:08:56 +02:00
Alex Tran
9bfacaa39a Specific specific type for enum value for openapi generator to work correctly 2022-10-07 14:30:15 -05:00
Alex Tran
a2882a4908 Added additional type to enum of openapi 2022-10-07 14:26:16 -05:00
Alex
1adc64a352 chore: add GitHub action to generate SDK in Rust/Typescript/Dart 2022-10-07 09:52:11 -05:00
Alex Tran
c28863966b Remove build on PR 2022-10-07 09:50:04 -05:00
Alex Tran
14dc679332 Added SDK to Rust 2022-10-07 09:46:10 -05:00
Alex Tran
17085dd8a0 Added SDK to Rust 2022-10-07 09:39:22 -05:00
Alex Tran
82b8313da0 Fix test 2022-10-07 09:16:45 -05:00
Alex Tran
4f7e764fa0 Fix typing 2022-10-07 09:15:05 -05:00
Alex
d52da8bbea Merge pull request #794 from immich-app/792-bug-encoded-videos-are-not-removed-when-original-asset-is-deleted
fix(server): Delete encoded video when deleting file
2022-10-07 08:47:56 -05:00
Alex Tran
cdddcad784 fix(server): Delete encoded video when deleting file 2022-10-07 08:47:13 -05:00
bo0tzz
38767cad0f Update local-reverse-geocoder to 0.12.5
This version includes a fix to the error handling in that library, which
was causing our code to silently fail and loop.
See https://github.com/tomayac/local-reverse-geocoder/issues/58 for more detail.
2022-10-07 12:14:27 +02:00
Alex Tran
c3d7dda61f Added generation for dart 2022-10-06 17:23:05 -05:00
Alex Tran
c4e32ce159 Rename repo 2022-10-06 16:15:36 -05:00
Alex Tran
6355a07dc4 Added github token custom 2022-10-06 16:09:15 -05:00
Alex Tran
0e3fb41e73 fixed 2022-10-06 15:48:38 -05:00
Alex Tran
fdac5af5ee Added github token 2022-10-06 15:47:47 -05:00
Alex Tran
0e509ceafa Added permissionf or github bot 2022-10-06 15:45:30 -05:00
Matthias Rupp
6b84534632 Get rid of home page state provider 2022-10-06 22:41:56 +02:00
Alex Tran
fc255b558d fix 2022-10-06 15:40:01 -05:00
Alex Tran
9e54e30011 git push force 2022-10-06 15:38:38 -05:00
Alex Tran
77312ce2e0 Force push 2022-10-06 15:37:03 -05:00
Alex Tran
9a6d29d6e7 Add global config for git 2022-10-06 15:34:42 -05:00
Alex Tran
2cb7517f64 Fix url 2022-10-06 15:33:07 -05:00
Alex Tran
3228882fc0 Authenticate 2022-10-06 15:32:19 -05:00
Alex Tran
6804e3dc73 Fixed 2022-10-06 15:27:31 -05:00
Alex Tran
f9af61a5ca Manually push to repo 2022-10-06 15:21:17 -05:00
Alex Tran
a94b443f13 Push to typescript sdk repo 2022-10-06 15:11:09 -05:00
Alex Tran
fd06aa2135 Add workflow to PR to test 2022-10-06 15:04:24 -05:00
Alex Tran
dd0f40559d added github action file' 2022-10-06 14:59:54 -05:00
Alex Tran
471a60dcb0 Added explicit type for job count 2022-10-06 12:43:02 -05:00
Alex Tran
46994c3355 Up version for release 2022-10-06 12:11:12 -05:00
Alex Tran
642811869c Fixed staging action runs only in PR 2022-10-06 11:38:56 -05:00
Alex Tran
3be4697487 Added docker build and push with PR number as tag for easy testing in production environment 2022-10-06 11:34:27 -05:00
Fynn Petersen-Frey
a3aca4acb5 feat(mobile) Run background service after being killed (#789) 2022-10-06 11:32:45 -05:00
Alex
7587f858ae feat(server/web) Add manual job trigger mechanism to the web (#767) 2022-10-06 11:25:54 -05:00
bo0tzz
854c214bc0 Fix: Use boolean comparison for DISABLE_REVERSE_GEOCODING config (#787) 2022-10-05 15:18:57 -05:00
Fynn Petersen-Frey
5dfce4db34 feat(mobile): background backup progress notifications (#781)
* settings to configure upload progress notifications (none/standard/detailed)
* use native Android notifications to show progress information
* e.g. 50% (30/60) assets
* e.g. Uploading asset XYZ - 25% (2/8MB)
* no longer show errors if canceled by system (losing network)
2022-10-05 09:59:35 -05:00
Zack Pollard
95467fa3c1 Merge pull request #785 from bivainis/patch-1
chore: fix github action name
2022-10-05 12:38:46 +01:00
Gediminas Bivainis
4ec3453558 chore: fix github action name 2022-10-05 12:19:11 +02:00
Alex
536fda04f2 Up version for release 2022-10-04 15:29:47 -05:00
Alex
2094204877 Up version for release 2022-10-04 15:29:37 -05:00
Alex
ab375cca1a Up Version for release 2022-10-04 15:21:58 -05:00
Alex
479f706f8a fix(mobile): Fix error parsing datetime prevent the timeline to be displayed (#784) 2022-10-04 15:19:29 -05:00
Deepesh Bhardwaj
4342285507 Updated jpeg thumbnail path (#780) 2022-10-04 09:46:06 -05:00
Jonas Janz
8bb656cb17 add docker volumes to services (#766)
* add docker volumes to services

this change adds the volume definitions for
/usr/src/app/upload
/usr/src/app/.reverse-geocoding-dump

to the `immich-server` docker-compose files
as /usr/src/app/upload should always be a volume for the containers
I also added it to the `Dockerfile`

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>

* remove geocoding-dump volume from docker-compose

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>
2022-10-01 16:01:27 -05:00
Matthias Rupp
a117e897ca Move selection logic to asset grid class 2022-10-01 19:19:40 +02:00
Matthias Rupp
347ac70063 Make new asset grid the default 2022-10-01 10:38:11 +02:00
Matthias Rupp
50842ef815 Add tests 2022-09-30 11:38:00 +02:00
Matthias Rupp
1970a64f6f Use new asset grid for search result page 2022-09-30 11:05:54 +02:00
Matthias Rupp
dd71a53f5e Hide scroll handle for lists < 100 assets 2022-09-30 10:47:31 +02:00
Alex
3f1f835df3 Update readme for beta release invitation links 2022-09-29 15:13:18 -05:00
Matthias Rupp
8440d9890c Improve scrolling performance in albums and search 2022-09-29 21:53:35 +02:00
Matthias Rupp
87ca031335 Fix bug with missing year and add date to drag handle (#761) 2022-09-29 10:19:55 -05:00
Alex Tran
96b9e37461 Up version for release 2022-09-28 16:28:14 -05:00
Alex Tran
0d3a2fe844 Added generated geocoding files to gitignore 2022-09-28 15:44:43 -05:00
Johannes Zellner
848781aef5 Provide a sensible dumpDirectory for the local-reverse-geocoder module (#759)
Fixes #758
2022-09-28 15:43:34 -05:00
Matthias Rupp
28bf497a0b feat(mobile): Improve timeline performance on mobile - experimental (#710) 2022-09-28 11:30:38 -05:00
Alex Tran
8ede738396 Up mobile version 2022-09-28 06:22:12 -05:00
Alex Tran
40c2b6a563 Update readme 2022-09-28 05:59:23 -05:00
Alex Tran
3581cf7305 Pump server version 2022-09-28 05:53:50 -05:00
Zack Pollard
c33775b944 feat(server): missing exif extract nightly task (#754)
* fix: nightly reverse geocoding task checking for mapbox

* refactor: remove file size from image processor and queue data

* feat: add missing exif nightly job

* Remove filesize requirement in assetUploadedProcessorName queue insertion

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-09-28 05:41:50 -05:00
Alex
b0cd2522e0 feat(server): support .NEF file (#746) 2022-09-23 19:09:45 -05:00
Alex
c3979f6e31 fix(machine-learning) Remove unsused database config (#745) 2022-09-23 19:00:47 -05:00
Alex
103df4d9f3 fix(web) navigating forward button get in the way of video control bar (#744)
* fix(web) navigating forward button get in the way of video control bar

* Remove unsued style
2022-09-23 18:22:06 -05:00
Zack Pollard
040e02cfc5 fix(server): handle missing reverse geocoding admin zones (#742) 2022-09-23 10:14:42 -05:00
Zack Pollard
f377b64065 feat(server) Remove mapbox and use local reverse geocoding (#738)
* feat: local reverse geocoding implementation, removes mapbox

* Disable non-null tslintrule

* Disable non-null tslintrule

* Remove tsignore

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-09-22 21:50:05 -05:00
Alex
e5459b68ff fix(server,web,mobile): Incorrectly record and show timestamp and time zone of the asset (#706)
Implemented a mechanism to extract the correct time zone from the GPS coordinate if presented in the file's EXIF, and to convert the timestamp to the correct UTC time so that the time will show correctly based on the mobile/web local time zone.
2022-09-22 15:58:17 -05:00
Alex Tran
fc194021a4 Pump server version 2022-09-22 11:38:50 -05:00
bo0tzz
39f8ca3bf1 Only run scheduled geocoding task once per day (#730) 2022-09-21 07:17:59 -05:00
Alex
7a807f7216 Update README.md 2022-09-19 16:04:22 -05:00
Alex
bedfb51b1c Add demo URL 2022-09-19 16:00:51 -05:00
Alex
b2afb95c19 Create codeql-analysis.yml 2022-09-19 14:03:49 -05:00
Alex
10239161fd fix(mobile): app crash when there is no object detection result on search page (#725)
* fix(mobile): app crash when there is no data for object detection

* Up version for release

* Up version for release
2022-09-19 11:03:51 -05:00
Alex
242f10952d fix(server): query only image (#724) 2022-09-19 10:48:44 -05:00
Alex Tran
e997bd371b Up server version 2022-09-18 21:44:55 -05:00
Alex
400167f4ef fix(server): sanitization error that crash the server (#721) 2022-09-18 21:44:13 -05:00
Alex
572f6d833d Up mobile version and update deprecated api 2022-09-18 16:11:30 -05:00
Alex
2e06be5155 Up mobile version and update deprecated api 2022-09-18 16:11:24 -05:00
Alex Tran
62121470a8 Up server version 2022-09-18 15:37:10 -05:00
Alex
e3ccc3ee6b feat(server): sanitized path for asset creation process to avoid security risk (#717)
* feat(server): sanitized path for asset creation process to avoid security risk

* Sanitize resize path
2022-09-18 15:16:53 -05:00
Alex
ece94f6bdc fix(server): correct user permission to update user info (#716) 2022-09-18 09:27:06 -05:00
Jamie Slome
03fc0703c0 Create SECURITY.md (#712) 2022-09-17 13:07:12 -05:00
Alex
0d13b25f56 feat(web): Update to latest version of SvelteKit (#705) 2022-09-16 23:13:22 -05:00
Alex
75c2067836 feat(web) Remove fetching fonts from GoogleFonts (#703) 2022-09-16 17:23:31 -05:00
Alex
824da6a07b Up server version 2022-09-16 16:55:04 -05:00
Alex
2c2ea24dc4 test(web) Add tests for asset repository (#680)
* Added back tests for asset repository

* Added more tests

* Added asset count test
2022-09-16 16:47:45 -05:00
Alex
47b73a5b64 fix(mobile): Fixed iOS 16 overflow cache and memory leaked in gallery viewer. (#700) 2022-09-16 16:46:23 -05:00
bo0tzz
6b3f8e548d Merge pull request #699 from JaCoB1123/patch-1
Fix spelling of Proxmox in Readme
2022-09-15 23:07:00 +02:00
Jan Bader
0ea483f901 Fix spelling of Proxmox in Readme 2022-09-15 23:05:15 +02:00
Jonas Janz
97aed8ef23 fix(nginx): revert nginx image to support arm/v7 (#692) 2022-09-14 13:36:29 -05:00
Alex
0ee3fe9157 Update install.sh to use latest released tag 2022-09-14 11:07:37 -05:00
Alex
434770155f Up version for release 2022-09-14 10:27:34 -05:00
Alex
7e8bf94543 fix/cache read write error ios16 (#691)
* Fix(mobile) cache read/write issue, cannot load image on ios16

* Update
2022-09-14 10:18:25 -05:00
Zack Pollard
8d8944705c Merge pull request #690 from beune/fix-typo
Fix typo
2022-09-14 13:16:47 +01:00
Pim Beune
7c9c1a5169 Fix typo 2022-09-14 13:53:34 +02:00
Jonas Janz
1a6c16d8ea breaking(setup): use non-root image for immich-proxy (#651)
* feat(nginx): use non-root container for immich-proxy

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>

* re-add test env

* feat(nginx): add correct port for staging

* add the new port to the default docker-compose.yml

Signed-off-by: PixelJonas <5434875+PixelJonas@users.noreply.github.com>
2022-09-13 21:50:10 -05:00
Alex
ccf792f9d3 fix(server): mismatch createdAt value in table and table (#688) 2022-09-13 20:12:42 -05:00
Fynn Petersen-Frey
789bc8563c fix Android BackgroundServiceStartNotAllowedException (#687) 2022-09-13 20:12:31 -05:00
Manuel
99a50f70dd readme: add app store links (#689) 2022-09-13 18:23:27 -05:00
Alex Tran
9bef411056 Up server version: 2022-09-13 12:14:36 -05:00
Alex
e79e92c60f Added Log level to background service (#685) 2022-09-13 12:09:57 -05:00
Alex
858ad43d3b fix(server): harden inserting process, self-healing timestamp info on bad timestamp (#682)
* fix(server): harden inserting process, self-healing timestamp info
2022-09-12 23:35:44 -05:00
Alex
5761765ea7 fix(server): remove album thumbnail when the asset is deleted from the database (#681) 2022-09-12 22:06:52 -05:00
Thanh Pham
6abc733763 fix(web): datetime display and add TZ into environment (#618)
* fix(web): timezone

* doc(): update readme.md

* feat(web): keep using UTC timezone in default

* chore(): update doc and remove debug code

* chore(): update readme.md

* Move timezone into to .env.example

* Run prettier check

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-09-12 14:40:18 -05:00
Alex Tran
4271e24e59 Up version for release 2022-09-11 16:05:53 -05:00
Alex
9e4ed2214b fix(web): incorrect shared album count (#677) 2022-09-11 10:07:04 -05:00
Alex
011332e509 fix(mobile) memory leaked causes app to crash when swiping (#673)
* Dispose image provider when swiping away from the asset
2022-09-11 09:56:26 -05:00
Alex
5403ef4d84 Fix(mobile) oversize play button (#672) 2022-09-11 00:25:04 -05:00
Alex Tran
31739aca02 Up version for release 2022-09-10 11:58:59 -05:00
Thanh Pham
8f2e7b6f65 fix(server): loop on checksum generation (#662) 2022-09-10 11:52:39 -05:00
Brett Profitt
4ed647c43d fix(install): Fix checking for docker compose. (#663) 2022-09-10 11:48:50 -05:00
Alex
f88ff4fb5c fix(mobile): background backup not working in release mode (#664) 2022-09-10 11:46:51 -05:00
Alex Tran
cc4881d633 Up version for release 2022-09-09 23:23:37 -05:00
Alex
d856b35afc feat(web) add scrollbar with timeline information (#658)
- Implement a scrollbar with a timeline similar to Google Photos
- The scrollbar can also be dragged
2022-09-09 15:55:20 -05:00
Jaime Baez
b6d025da09 Fix Notification components possible memory leaks (#650)
Dispose subscriptions and timeouts when
the components are removed from the DOM
2022-09-09 07:40:35 -05:00
Jaime Baez
cc79ff1ca3 Merge pull request #642 from immich-app/add/ci-web-checks
Add web test / check commands and workflow to run in CI
2022-09-08 19:12:39 +02:00
Jaime Baez
131aa2b6be Add command to test/check code in dev-setup docs 2022-09-08 17:54:45 +02:00
Jaime Baez
02a6b73122 Add web-unit-test workflow to run in CI 2022-09-08 17:44:13 +02:00
Jaime Baez
d87366c095 Add dev-setup documentation 2022-09-08 17:41:24 +02:00
Jaime Baez
4f7a3afbfc Fix web lint issues 2022-09-08 17:30:49 +02:00
Jaime Baez
6725954b70 Add web check / lint npm commands
`svelte-check` returns some "hints" that can be ignored since some
are not true and others are not relevant.
2022-09-08 17:17:15 +02:00
Fynn Petersen-Frey
4fe535e5e8 improve Android background service reliability (#603)
This change greatly reduces the chance that a backup is not performed
when a new photo/video is made.
Instead of combining the change trigger and additonal constraints (wifi
or charging) into a single worker, these aspects are now separated.
Thus, it is now reliably possible to take pictures while the wifi
constraint is not satisfied and upload them hours/days later once
connected to wifi without taking a new photo.
As a positive side effect, this simplifies the error/retry handling
by directly leveraging Android's WorkManager without workarounds.
The separation also allows to notify the currently running BackupWorker
that new assets were added while backing up other assets to also upload
those newly added assets.
Further, a new tiny service checks if the app is killed, to reschedule
the content change worker and allow to detect the first new photo.
Bonus: The home screen now shows backup as enabled if background backup
is active.

* use separate worker/task for listening on changed/added assets
* use separate worker/task for performing the backup
* content observer worker enqueues backup worker on each new asset
* wifi/charging constraints only apply to backup worker
* backupworker is notified of assets added while running to re-run
* new service to catch app being killed to workaround WorkManager issue
2022-09-08 08:36:08 -05:00
Jaime Baez
aed94bfc4c Format web code with prettier
Added `.md` and `.json` to .prettierignore
2022-09-08 12:53:09 +02:00
Jaime Baez
de996c0a81 Merge pull request #612 from immich-app/add/web-ui-tests-setup
Add web UI components tests setup

@alextran1502 I'll get this merged so I can add CI checks for the web as well. Let me know if you have any questions 😃
2022-09-08 11:24:08 +02:00
Jaime Baez
1a39aa4da5 Merge pull request #633 from immich-app/fix/server-lint-errors
Add all server checks to CI - fix lint issues
2022-09-08 11:12:31 +02:00
Jaime Baez
1f4ba73da7 Add all server checks to CI - fix lint issues
CI will now run linter, type-checks and tests for the server.

All the lint issues have been fixed.
2022-09-08 11:07:27 +02:00
Alex Tran
836b174d33 Better styling for count info 2022-09-07 21:19:24 -05:00
Alex Tran
853a65aef1 Up version for release 2022-09-07 15:26:29 -05:00
Alex
566039b93f feat(web): add asset and album count info (#623)
* Get asset and album count

* Generate APIs

* Added asset count for each type

* Added api on the web

* Added info button for asset and album count to trigger getting info on hover

* Remove websocket event from photo page
2022-09-07 15:16:18 -05:00
bo0tzz
18a7ff8726 Remove empty translations (#620) 2022-09-07 14:41:44 -05:00
Thanh Pham
6ffdf167fe fix(web): detail panel overflow-x (#615) 2022-09-07 13:20:44 -05:00
Jaime Baez
6b702b13e4 Rename albums BLoC (.bloc.ts convention)
By convention now it's `album.bloc.ts`
2022-09-07 16:04:50 +02:00
Jaime Baez
f476bd985b Add AlbumCard UI tests
- add libraries for component UI testing
- implement AlbumCard UI tests
2022-09-07 16:00:57 +02:00
Alex
92c4f0598b fix(mobile): search page crashes the app on some Android models (#610) 2022-09-07 06:45:26 -05:00
Alex
a337402124 fix(web): stop showing version announcement on first run of a new web instance (#609) 2022-09-07 06:38:29 -05:00
dependabot[bot]
209e6332b3 Bump actions/checkout from 2 to 3 (#604)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-07 06:08:44 -05:00
Jaime Baez
645bd8a109 Add web test setup (#597)
* Extract logic from Albums page

- move "albums" page logic to `albums-bloc`
- add types to AlbumCard custom events

* Implement some album-bloc unit-tests

- add libraries for testing
- add album factory
- changes in albums-bloc API

* Add rest of albums-bloc test

Cleanup and remove console logs

* Refactor `isShowContextMenu` writable to derived
2022-09-07 05:20:19 -05:00
Daniel Weaver
9a471d80f7 Update README.md (#599)
Adding a note to the installation section about reverse proxies being a cause for issues when uploading large files.
2022-09-06 16:05:38 -05:00
Alex Tran
de0c59efe7 Added mobile change log 37 2022-09-06 10:03:55 -05:00
Alex Tran
c19d26f4f3 Update some mobile UI with Material 3 theme 2022-09-06 09:37:04 -05:00
Alex Tran
2edfc75c8a Fixed sliverappbar icon color to conform with theming 2022-09-06 08:18:07 -05:00
Matthias Rupp
4c977d2c1f fix(mobile): cache related crash (#593) 2022-09-06 08:10:52 -05:00
Alex
1425f2ec78 Up server version 2022-09-05 23:44:42 -05:00
Alex
b081eda76f fix(server): change the createdAt and modifiedAt to the correct type in database (#591)
* Added migration files

* Remove type casting in sql query
2022-09-05 20:51:01 -05:00
Thanh Pham
7f6837c751 hotfix(server): skip exif extraction on duplicate file (#590)
* fix(server): skip exif extraction on duplicate file

* fix(server): typo

* chore(server): remvoe un-use code
2022-09-05 20:02:50 -05:00
Thanh Pham
a467936e73 feat(server): de-duplication (#557)
* feat(server): remove un-used deviceAssetId cols.

* feat(server): return 409 if asset is duplicated

* feat(server): replace old unique constaint

* feat(server): strip deviceId in file path

* feat(server): skip duplicate asset

* chore(server): revert changes

* fix(server): asset test spec

* fix(server): checksum generation for uploaded assets

* fix(server): make sure generation queue run after migraion

* feat(server): remove temp file

* chore(server): remove dead code
2022-09-05 14:45:38 -05:00
Alex Tran
2677ddccaa Up version for release 2022-09-05 14:32:05 -05:00
bo0tzz
564ace3ddf Use runtime env var for login page message to lower web container startup time (#577)
* Use runtime env var for loginPageMessage

* Rename VITE_LOGIN_PAGE_MESSAGE to PUBLIC_LOGIN_PAGE_MESSAGE in .env.example

* Move docker image `npm run build` step into Dockerfile

* Remove comment from web Dockerfile
2022-09-05 09:51:45 -05:00
Thanh Pham
a81ef7497c feat(server): support 3gpp format (#582)
* feat(server): support 3gpp format

* feat(web): add 3gp ext

* Support 3gp video format.

video/3gpp mimetype added to supported video format.

* feat(mobile): add tif ext

Co-authored-by: Alexandre Bouijoux <alexandre@bouijoux.fr>
2022-09-05 08:53:13 -05:00
Matthias Rupp
caa7b07398 Show all albums an asset appears in on the asset viewer page (#575)
* Add route to query albums for a specific asset

* Update API and add to detail-panel

* Fix tests

* Refactor API endpoint

* Added alt attribute to img tag

Co-authored-by: Alex <alex.tran1502@gmail.com>
2022-09-05 08:50:20 -05:00
Alex
6976a7241e Fixed upload asset to album in asset selection (#579)
* Fixed error uploading a file from album

* Fixed album selection mode show viewing asset stage

* Navigate back after uploading asset to album
2022-09-05 00:18:53 -05:00
Alex Tran
172eda3ce5 Fixed readme 2022-09-04 21:08:13 -05:00
Alex
552340add7 Feature - Implemented virtual scroll on web (#573)
This PR implemented a virtual scroll on the web, as seen in this article.

[Building the Google Photos Web UI](https://medium.com/google-design/google-photos-45b714dfbed1)
2022-09-04 08:34:39 -05:00
SirBogner
bd92dde117 Update localizely.yml (#574) 2022-09-04 08:27:05 -05:00
Alex
617c54ab81 [Localizely] Translations update (#576) 2022-09-04 08:26:11 -05:00
Thanh Pham
c76f7804ab feat(server): generate checksum for previous uploaded assets (#558)
* feat(server): generate checksum for previous uploaded assets

* fix(server): typo
2022-09-02 08:32:21 -05:00
Damian Gomez
0799aa2c72 Italia language for Mobile App (#559)
Co-authored-by: Damian Gomez <damian.gomez@elitedivision.it>
2022-09-01 08:48:13 -05:00
Thanh Pham
b80dca74ef feat(server): calculate sha1 checksum (#525)
* feat(server): override multer storage

* feat(server): calc sha1 of uploaded file

* feat(server): add checksum into asset

* chore(server): add package-lock for mkdirp package

* fix(server): free hash stream

* chore(server): rollback this changes, not refactor here

* refactor(server): re-arrange import statement

* fix(server): make sure hash done before callback

* refactor(server): replace varchar to char for checksum, reserve pixelChecksum for future

* refactor(server): remove pixelChecksum

* refactor(server): convert checksum from string to bytea

* feat(server): add index to checksum

* refactor(): rollback package.json changes

* feat(server): remove uploaded file when progress fail

* feat(server): calculate hash in sequence
2022-08-31 09:27:17 -05:00
Thanh Pham
f5f00e0f6c fix(web): file uploading error in album page (#550)
* feat(web): show upload error notification

* fix(web): album upload issue
2022-08-31 08:12:31 -05:00
Fynn Petersen-Frey
75d2d82d05 ask user to disable battery optimizations when turning on background backup (#554)
* ask user to disable battery optimizations when turning on background backup

* remove obsolete texts/translations

* add button link to dontkillmyapp
2022-08-31 08:08:40 -05:00
Fynn Petersen-Frey
5172242f88 fix: persist WiFi + charging settings of background backup (#553) 2022-08-30 09:09:19 -05:00
Matthias Rupp
25e68cf826 Better caching for mobile (#521)
* Use custom caches in all modules

* Cache Settings

* Fix wrong key

* Create custom cache repository based on hive

* Show cache usage in settings

* Show cache sizes

* Change settings ranges and default value

* Handle cache clear by operating system

* Resolve review comments
2022-08-29 22:44:43 -05:00
be bright
e527685ebf Added korean translation for mobile app (#549)
* Added korean translation for mobile app

* Added locale to info.plist

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-08-29 08:54:40 -05:00
Thanh Pham
e745cb5e4b fix(server): parse all img formats and enrich metadata (#547)
* fix(server): use file path instead buffer to reduce memory usage

fix undefined exif data

* fix(server): parse all img formats

* feat(server): enrich metadata

* Format oneliner condition

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2022-08-28 15:43:31 -05:00
Thanh Pham
dfaa4969da Server - Fixed - Use file path instead buffer to reduce memory usage on EXIF extraction (#545)
fix undefined exif data
2022-08-28 11:09:24 -05:00
Alex
f980a2f27a Add asset repository and refactor asset service (#540)
* build endpoint to get asset count by month

* Added asset repository

* Added create asset

* get asset by device ID

* Added test for existing methods

* Refactor additional endpoint

* Refactor database api to get curated locations and curated objects

* Refactor get search properties

* Fixed cookies parsing for websocket

* Added API to get asset count by time group

* Remove unused code
2022-08-26 22:53:37 -07:00
Alex
6b7c97c02a Added release notes 2022-08-26 13:43:05 -07:00
Alex
fdd9f37abd Added error handling for layout.server.ts to avoid unaccessible to previous deploy instance due to changes in SvelteKit project 2022-08-26 11:30:45 -07:00
Alex
a09bba454c Pump version for release 2022-08-26 10:57:12 -07:00
Alex
4be9aa091b Added error handling notification (#536) 2022-08-26 10:36:41 -07:00
Alex
33b810de74 Removed upload button on sharing and album page 2022-08-26 10:05:15 -07:00
Alex
44ccb1eec1 Added timeout option for notification component 2022-08-26 10:01:47 -07:00
Alex
bef38c670c Reference CLI in limit upload message 2022-08-26 09:42:48 -07:00
Alex
025d7bf192 Merge branch 'main' of github.com:immich-app/immich 2022-08-26 09:42:17 -07:00
Alex
5ad2d62039 Added limit on total of file upload on web (#535) 2022-08-26 09:39:28 -07:00
Alex
a128833e68 Added limit on total of file upload on web 2022-08-26 09:36:54 -07:00
Alex
87f7b0849a Added migration down for change exif file type 2022-08-26 09:13:11 -07:00
Alex
4596a8ee01 Change fileSizeInByte to bigint from int to handle large size (#534) 2022-08-26 09:07:59 -07:00
Alex
f9b1b12b10 Implement notification box for web (#533)
* Added test button

* styling notification box

* Added auto dismission and animation to each notificaiont list

* Remove test button
2022-08-25 23:04:23 -07:00
Alex
68b1655e7f Show the first two letter of user first and last name when profile image not existed (#532)
* Added user first name and last name abbreviation to Circle Avatar:

* Remove unsued code
2022-08-25 15:52:11 -07:00
Alex
658b64df74 Added page navigation progress indicator 2022-08-25 13:02:36 -07:00
Alex
e344503834 Fixed navigating with keyboard skip assets (#531)
* Cleaned up event listner
2022-08-24 22:18:28 -07:00
Alex
bf2760ffef Fixed mobile timeline crash when date group cannot be parsed (#530)
* Handle error when datetime is incorrect

* Added better debug message
2022-08-24 21:31:20 -07:00
Alex
db2ed2d881 Migrate SvelteKit to the latest version 431 (#526) 2022-08-24 21:10:48 -07:00
Thanh Pham
fb0fa742f5 fix(web): buffering for video player (#520)
* fix(web): buffering for video player

* chore(): missing file -_-

* refactor(web): using URL builder

* chore(): add semicolon

* fix(web): video player

* remove deadcode

Co-authored-by: Alex <alex.tran1502@gmail.com>
2022-08-23 20:21:41 -07:00
Thanh Pham
3b55cdc0be refactor(server): move constant into common package (#522)
* refactor(server): move constant into common package

* refactor(server): re-arrange import statement in microservice module

* refactor(server): move app.config into common package

* fix(server): e2e testing
2022-08-23 07:34:21 -07:00
Alex
0efcc99f3e Added Dutch locale 2022-08-22 12:52:24 -07:00
Nick Pieper
7a85164a1e Added dutch translation for Immich (#519)
* Create nl-NL.json with dutch translation

* Add nl-NL to localizely.yml
2022-08-22 12:50:56 -07:00
Thanh Pham
ba2cda8955 feat(server): support tiff uploading (#513)
* feat(server): suport tiff uploading

* remove unused variable

Co-authored-by: Alex <alex.tran1502@gmail.com>
2022-08-22 12:49:17 -07:00
Alex
9048be4c8e Added Code of conduct 2022-08-21 12:43:56 -07:00
538 changed files with 56115 additions and 8699 deletions

View File

@@ -17,17 +17,17 @@ jobs:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Immich Mono Repo
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./server
file: ./server/Dockerfile
@@ -45,17 +45,17 @@ jobs:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
@@ -72,17 +72,17 @@ jobs:
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Web
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./web
file: ./web/Dockerfile
@@ -100,17 +100,17 @@ jobs:
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./nginx
file: ./nginx/Dockerfile

View File

@@ -2,8 +2,6 @@ name: Build and Push Docker Image - Staging
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
@@ -19,10 +17,10 @@ jobs:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
uses: docker/login-action@v2
@@ -30,7 +28,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Immich Mono Repo
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./server
file: ./server/Dockerfile
@@ -38,6 +36,7 @@ jobs:
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-server:staging
altran1502/immich-server:${{ github.event.pull_request.number }}
build_and_push_machine_learning_staging:
runs-on: ubuntu-latest
@@ -48,10 +47,10 @@ jobs:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
uses: docker/login-action@v2
@@ -59,7 +58,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
@@ -67,6 +66,7 @@ jobs:
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-machine-learning:staging
altran1502/immich-machine-learning:${{ github.event.pull_request.number }}
build_and_push_web_staging:
runs-on: ubuntu-latest
@@ -76,10 +76,10 @@ jobs:
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
uses: docker/login-action@v2
@@ -87,7 +87,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Web
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./web
file: ./web/Dockerfile
@@ -96,6 +96,7 @@ jobs:
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-web:staging
altran1502/immich-web:${{ github.event.pull_request.number }}
build_and_push_nginx_staging:
runs-on: ubuntu-latest
@@ -105,10 +106,10 @@ jobs:
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
uses: docker/login-action@v2
@@ -116,7 +117,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./nginx
file: ./nginx/Dockerfile
@@ -124,3 +125,4 @@ jobs:
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-proxy:staging
altran1502/immich-proxy:${{ github.event.pull_request.number }}

View File

@@ -22,11 +22,11 @@ jobs:
fallback: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
@@ -35,7 +35,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-server release
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./server
file: ./server/Dockerfile
@@ -58,17 +58,17 @@ jobs:
with:
fallback: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
@@ -94,11 +94,11 @@ jobs:
fallback: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
@@ -107,7 +107,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-web release
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./web
file: ./web/Dockerfile
@@ -134,11 +134,11 @@ jobs:
fallback: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to Docker Hub
uses: docker/login-action@v2
@@ -147,7 +147,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-proxy release
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
context: ./nginx
file: ./nginx/Dockerfile

74
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '20 13 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,21 @@
name: Update Immich SDK
on:
workflow_dispatch:
push:
branches: ["main"]
jobs:
update-sdk-repos:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'immich-app',
repo: 'immich-sdk-typescript-axios',
workflow_id: 'build.yml',
ref: 'main'
})

View File

@@ -13,18 +13,29 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Run Immich Server 2E2 Test
- name: Run Immich Server E2E Test
run: docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test up --abort-on-container-exit --exit-code-from immich-server-test
unit-tests:
name: Run unit test suites
server-unit-tests:
name: Run server unit test suites and checks
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Run tests
run: cd server && npm install && npm run test
run: cd server && npm ci && npm run check:all
web-unit-tests:
name: Run web unit test suites and checks
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run tests
run: cd web && npm ci && npm run check:all

7
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.DS_Store
.vscode
.idea
.vscode/*
!.vscode/launch.json
.idea
docker/upload

14
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"restart": true,
"port": 9230,
"name": "Immich Server",
"remoteRoot": "/usr/src/app",
"localRoot": "${workspaceFolder}/server"
}
]
}

134
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,134 @@
# 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 our Discord channel. 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.

View File

@@ -23,33 +23,54 @@
<br/>
</p>
## Demo
You can access the web demo at https://demo.immich.app
For the mobile app, you can use https://demo.immich.app/api for the `Server Endpoint URL`
```
The credential
email: demo@immich.app
password: demo
```
```
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
## Content
- [Features](#features)
- [Screenshots](#screenshots)
- [Installation](#installation)
- [Mobile App](#-mobile-app)
- [Update](#update)
- [Mobile App](#mobile-app)
- [App Beta Invitation links](#App-Beta-release-channel)
- [Development](#development)
- [Support](#support)
- [Known Issues](#known-issues)
# Features
> ⚠️ WARNING: **NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS**. This project is under heavy development, there will be continuous functions, features and api changes.
> ⚠️ WARNING: **NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS**. This project is under heavy development. There will be continuous functions, features and api changes.
| | Mobile | Web |
| Features | Mobile | Web |
| - | - | - |
| ☁️ Upload and view videos and photos | Yes | Yes
| 🔄 Auto backup when the app is opened | Yes | N/A
| ☑️ Selective album(s) for backup | Yes | N/A
| ⬇️ Download photos and videos to local device | Yes | Yes
| 👪 Multi-user support | Yes | Yes
| 🖼️ Album | Yes | Yes
| 🤝 Shared Albums | Yes | Yes
| 🚀 Quick navigation with draggable scrollbar | Yes | Yes
| 🗃️ Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
| 🧭 Metadata view (EXIF, map) | Yes | Yes
| 🔎 Search by metadata, objects and image tags | Yes | No
| ⚙️ Administrative functions (user management) | N/A | Yes
| Upload and view videos and photos | Yes | Yes
| Auto backup when the app is opened | Yes | N/A
| Selective album(s) for backup | Yes | N/A
| Download photos and videos to local device | Yes | Yes
| Multi-user support | Yes | Yes
| Album | Yes | Yes
| Shared Albums | Yes | Yes
| Quick navigation with draggable scrollbar | Yes | Yes
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
| Metadata view (EXIF, map) | Yes | Yes
| Search by metadata, objects and image tags | Yes | No
| Administrative functions (user management) | N/A | Yes
| Background backup | Android | N/A
| Virtual scroll | N/A | Yes
<br/>
@@ -95,11 +116,12 @@ There are several services that compose Immich:
# Installation
## Testing One-step installation (not recommended for production)
NOTE: When using a reverse proxy in front of Immich (such as NGINX), the reverse proxy might require extra configuration to allow large files to be uploaded (such as `client_max_body_size` in the case of NGINX).
## Testing one-step installation (not recommended for production)
> ⚠️ *This installation method is for evaluating Immich before futher customization to meet the users' needs.*
> ⚠️ *This installation method is for evaluating Immich before further customization to meet the users' needs.*
*Applicable system: Ubuntu, Debian, MacOS*
*Applicable operating systems: Ubuntu, Debian, MacOS*
- In the shell, from the directory of your choice, run the following command:
@@ -141,7 +163,6 @@ wget -O .env https://raw.githubusercontent.com/immich-app/immich/main/docker/.en
* Populate custom database information if necessary.
* Populate `UPLOAD_LOCATION` as prefered location for storing backup assets.
* Populate a secret value for `JWT_SECRET`, you can use this command: `openssl rand -base64 128`
* [Optional] Populate Mapbox value to use reverse geocoding.
### Step 3 - Start the containers
@@ -168,15 +189,31 @@ wget -O .env https://raw.githubusercontent.com/immich-app/immich/main/docker/.en
<br/>
## Update
If you have installed, you can update the application by navigate to the directory that contains the `docker-compose.yml` file and run the following command:
```bash
docker-compose pull && docker-compose up -d
```
# Unraid Installation
Please follow this [article](https://mfaz.dev/posts/immich-unraid/) for a tutorial on how to install Immich on Unraid
# Mobile app
| F-Droid | Google Play | iOS |
| - | - | - |
| <a href="https://f-droid.org/packages/app.alextran.immich"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"></a> | <p align="left"> <img src="design/google-play-qr-code.png" width="200" title="Google Play Store"> <p/> | <p align="left"> <img src="design/ios-qr-code.png" width="200" title="Apple App Store"> <p/> |
| <a href="https://f-droid.org/packages/app.alextran.immich"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"></a> | <p align="left"> <a href="https://play.google.com/store/apps/details?id=app.alextran.immich"><img src="design/google-play-qr-code.png" width="200" title="Google Play Store"></a> <p/> | <p align="left"> <a href="https://apps.apple.com/us/app/immich/id1613945652"><img src="design/ios-qr-code.png" width="200" title="Apple App Store"></a> <p/> |
> *The App version might be lagging behind the latest release due to the review process.*
> *The Play/App Store version might be lagging behind the latest release due to their review process.*
# App Beta release channel
You can opt-in to join app beta release channel by following the links below:
* Android: Invitation link from [web](https://play.google.com/store/apps/details?id=app.alextran.immich) or from [mobile](https://play.google.com/store/apps/details?id=app.alextran.immich)
* iOS: [TestFlight invitation link](https://testflight.apple.com/join/1vYsAa8P)
<br/>
# Development
@@ -223,7 +260,7 @@ Cheers! 🎉
## TensorFlow Build Issue
*This is a known issue for incorrect Promox setup*
*This is a known issue for incorrect Proxmox setup*
TensorFlow doesn't run with older CPU architecture, it requires a CPU with AVX and AVX2 instruction set. If you encounter the error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command and make sure you see `AVX` and `AVX2`:
@@ -231,7 +268,7 @@ TensorFlow doesn't run with older CPU architecture, it requires a CPU with AVX a
more /proc/cpuinfo | grep flags
```
If you are running virtualization in Promox, the VM doesn't have the flag enabled.
If you are running virtualization in Proxmox, the VM doesn't have the flag enabled.
You need to change the CPU type from `kvm64` to `host` under VMs hardware tab.

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to `alex.tran1502@gmail.com`

32
dev-setup.md Normal file
View File

@@ -0,0 +1,32 @@
# Development Setup
## Lint / format extensions
Setting these in the IDE give a better developer experience auto-formatting code on save and providing instant feedback on lint issues.
### VSCode
Install Prettier, ESLint and Svelte extensions.
in User `settings.json` (`cmd + shift + p` and search for Open User Settings JSON) add the following:
```json
{
"editor.formatOnSave": true,
"[javascript][typescript][css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.tabSize": 2
},
"svelte.enable-ts-plugin": true,
"eslint.validate": ["javascript", "svelte"]
}
```
## Running tests / checks
In both server and web:
`npm run check:all`

View File

@@ -10,9 +10,6 @@ DB_DATABASE_NAME=immich
# Optional Database settings:
# DB_PORT=5432
###################################################################################
# Redis
###################################################################################
@@ -25,42 +22,48 @@ REDIS_HOSTNAME=immich_redis
# REDIS_PASSWORD=
# REDIS_SOCKET=
###################################################################################
# Upload File Config
###################################################################################
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
###################################################################################
# Log message level - [simple|verbose]
###################################################################################
LOG_LEVEL=simple
###################################################################################
# JWT SECRET
###################################################################################
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
# This JWT_SECRET is used to sign the authentication keys for user login
# You should set it to a long randomly generated value
# You can use this command to generate one: openssl rand -base64 128
JWT_SECRET=
###################################################################################
# MAPBOX
# Reverse Geocoding
####################################################################################
# ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
ENABLE_MAPBOX=false
MAPBOX_KEY=
# DISABLE_REVERSE_GEOCODING=false
# Reverse geocoding is done locally which has a small impact on memory usage
# This memory usage can be altered by changing the REVERSE_GEOCODING_PRECISION variable
# This ranges from 0-3 with 3 being the most precise
# 3 - Cities > 500 population: ~200MB RAM
# 2 - Cities > 1000 population: ~150MB RAM
# 1 - Cities > 5000 population: ~80MB RAM
# 0 - Cities > 15000 population: ~40MB RAM
# REVERSE_GEOCODING_PRECISION=3
####################################################################################
# WEB - Optional
####################################################################################
# Custom message on the login page, should be written in HTML form.
# For example VITE_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
# For example PUBLIC_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
VITE_LOGIN_PAGE_MESSAGE=
PUBLIC_LOGIN_PAGE_MESSAGE=

View File

@@ -19,4 +19,4 @@ ENABLE_MAPBOX=false
# WEB
MAPBOX_KEY=
VITE_SERVER_ENDPOINT=http://localhost:2283/api
VITE_SERVER_ENDPOINT=http://localhost:2283/api

View File

@@ -1,4 +1,4 @@
version: "3.8"
version: '3.8'
services:
immich-server:
@@ -6,11 +6,14 @@ services:
build:
context: ../server
dockerfile: Dockerfile
command: npm run start:dev immich
target: builder
command: npm run start:debug immich
volumes:
- ../server:/usr/src/app
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /usr/src/app/node_modules
ports:
- 9230:9230
env_file:
- .env
environment:
@@ -24,6 +27,7 @@ services:
build:
context: ../machine-learning
dockerfile: Dockerfile
target: builder
command: npm run start:dev
volumes:
- ../machine-learning:/usr/src/app
@@ -41,6 +45,7 @@ services:
build:
context: ../server
dockerfile: Dockerfile
target: builder
command: npm run start:dev microservices
volumes:
- ../server:/usr/src/app
@@ -99,8 +104,7 @@ services:
context: ../nginx
dockerfile: Dockerfile
ports:
- 2283:80
- 2284:443
- 2283:8080
logging:
driver: none
depends_on:

View File

@@ -72,8 +72,7 @@ services:
container_name: immich_proxy
image: altran1502/immich-proxy:staging
ports:
- 2283:80
- 2284:443
- 2283:8080
logging:
driver: none
depends_on:

View File

@@ -72,7 +72,7 @@ services:
container_name: immich_proxy
image: altran1502/immich-proxy:release
ports:
- 2283:80
- 2283:8080
logging:
driver: none
depends_on:

20
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

41
docs/README.md Normal file
View File

@@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

3
docs/babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -0,0 +1,12 @@
---
slug: first-blog-post
title: First Blog Post
authors:
name: Gao Wei
title: Docusaurus Core Team
url: https://github.com/wgao19
image_url: https://github.com/wgao19.png
tags: [hola, docusaurus]
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

View File

@@ -0,0 +1,44 @@
---
slug: long-blog-post
title: Long Blog Post
authors: endi
tags: [hello, docusaurus]
---
This is the summary of a very long blog post,
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
<!--truncate-->
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

View File

@@ -0,0 +1,20 @@
---
slug: mdx-blog-post
title: MDX Blog Post
authors: [slorber]
tags: [docusaurus]
---
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
:::tip
Use the power of React to create interactive blog posts.
```js
<button onClick={() => alert('button clicked!')}>Click me!</button>
```
<button onClick={() => alert('button clicked!')}>Click me!</button>
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -0,0 +1,25 @@
---
slug: welcome
title: Welcome
authors: [slorber, yangshun]
tags: [facebook, hello, docusaurus]
---
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
Simply add Markdown files (or folders) to the `blog` directory.
Regular blog authors can be added to `authors.yml`.
The blog post date can be extracted from filenames, such as:
- `2019-05-30-welcome.md`
- `2019-05-30-welcome/index.md`
A blog post folder can be convenient to co-locate blog post images:
![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg)
The blog supports tags as well!
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.

17
docs/blog/authors.yml Normal file
View File

@@ -0,0 +1,17 @@
endi:
name: Endilie Yacop Sucipto
title: Maintainer of Docusaurus
url: https://github.com/endiliey
image_url: https://github.com/endiliey.png
yangshun:
name: Yangshun Tay
title: Front End Engineer @ Facebook
url: https://github.com/yangshun
image_url: https://github.com/yangshun.png
slorber:
name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png

5
docs/docs/FAQ.md Normal file
View File

@@ -0,0 +1,5 @@
---
sidebar_position: 4
---
# FAQ

View File

@@ -0,0 +1,8 @@
{
"label": "Installation",
"position": 2,
"link": {
"type": "generated-index",
"description": "Installation methods of Immich"
}
}

View File

View File

@@ -0,0 +1,5 @@
---
sidebar_position: 5
---
# Mobile App Beta Program

View File

@@ -0,0 +1,8 @@
{
"label": "Getting Started",
"position": 1,
"link": {
"type": "generated-index",
"description": "Getting to know Immich"
}
}

View File

@@ -0,0 +1,7 @@
---
sidebar_position: 1
---
# Introduction
This is the first

View File

@@ -0,0 +1,5 @@
---
sidebar_position: 2
---
# Logo meaning

View File

@@ -0,0 +1,5 @@
---
sidebar_position: 3
---
# Support the project

View File

@@ -0,0 +1,5 @@
---
sidebar_position: 4
---
# Technology stack

View File

@@ -0,0 +1,8 @@
{
"label": "Tutorial - Basics",
"position": 6,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Docusaurus concepts."
}
}

View File

@@ -0,0 +1,23 @@
---
sidebar_position: 6
---
# Congratulations!
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
Docusaurus has **much more to offer**!
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
## What's next?
- Read the [official documentation](https://docusaurus.io/)
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)

View File

@@ -0,0 +1,34 @@
---
sidebar_position: 3
---
# Create a Blog Post
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
## Create your first Post
Create a file at `blog/2021-02-28-greetings.md`:
```md title="blog/2021-02-28-greetings.md"
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).

View File

@@ -0,0 +1,57 @@
---
sidebar_position: 2
---
# Create a Document
Documents are **groups of pages** connected through:
- a **sidebar**
- **previous/next navigation**
- **versioning**
## Create your first Doc
Create a Markdown file at `docs/hello.md`:
```md title="docs/hello.md"
# Hello
This is my **first Docusaurus document**!
```
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
## Configure the Sidebar
Docusaurus automatically **creates a sidebar** from the `docs` folder.
Add metadata to customize the sidebar label and position:
```md title="docs/hello.md" {1-4}
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
```
It is also possible to create your sidebar explicitly in `sidebars.js`:
```js title="sidebars.js"
module.exports = {
tutorialSidebar: [
'intro',
// highlight-next-line
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
};
```

View File

@@ -0,0 +1,43 @@
---
sidebar_position: 1
---
# Create a Page
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
- `src/pages/index.js``localhost:3000/`
- `src/pages/foo.md``localhost:3000/foo`
- `src/pages/foo/bar.js``localhost:3000/foo/bar`
## Create your first React Page
Create a file at `src/pages/my-react-page.js`:
```jsx title="src/pages/my-react-page.js"
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
```
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
## Create your first Markdown Page
Create a file at `src/pages/my-markdown-page.md`:
```mdx title="src/pages/my-markdown-page.md"
# My Markdown page
This is a Markdown page
```
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).

View File

@@ -0,0 +1,31 @@
---
sidebar_position: 5
---
# Deploy your site
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
It builds your site as simple **static HTML, JavaScript and CSS files**.
## Build your site
Build your site **for production**:
```bash
npm run build
```
The static files are generated in the `build` folder.
## Deploy your site
Test your production build locally:
```bash
npm run serve
```
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).

View File

@@ -0,0 +1,146 @@
---
sidebar_position: 4
---
# Markdown Features
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
## Front Matter
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
```text title="my-doc.md"
// highlight-start
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
// highlight-end
## Markdown heading
Markdown text with [links](./hello.md)
```
## Links
Regular Markdown links are supported, using url paths or relative file paths.
```md
Let's see how to [Create a page](/create-a-page).
```
```md
Let's see how to [Create a page](./create-a-page.md).
```
**Result:** Let's see how to [Create a page](./create-a-page.md).
## Images
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
```md
![Docusaurus logo](/img/docusaurus.png)
```
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
## Code Blocks
Markdown code blocks are supported with Syntax highlighting.
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
## Admonitions
Docusaurus has a special syntax to create admonitions and callouts:
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
## MDX and React Components
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
```jsx
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
```
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`);
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !

View File

@@ -0,0 +1,7 @@
{
"label": "Tutorial - Extras",
"position": 6,
"link": {
"type": "generated-index"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,55 @@
---
sidebar_position: 1
---
# Manage Docs Versions
Docusaurus can manage multiple versions of your docs.
## Create a docs version
Release a version 1.0 of your project:
```bash
npm run docusaurus docs:version 1.0
```
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
Your docs now have 2 versions:
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
## Add a Version Dropdown
To navigate seamlessly across versions, add a version dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
module.exports = {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'docsVersionDropdown',
},
// highlight-end
],
},
},
};
```
The docs version dropdown appears in your navbar:
![Docs Version Dropdown](./img/docsVersionDropdown.png)
## Update an existing version
It is possible to edit versioned docs in their respective folder:
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`

View File

@@ -0,0 +1,88 @@
---
sidebar_position: 2
---
# Translate your site
Let's translate `docs/intro.md` to French.
## Configure i18n
Modify `docusaurus.config.js` to add support for the `fr` locale:
```js title="docusaurus.config.js"
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
```
## Translate a doc
Copy the `docs/intro.md` file to the `i18n/fr` folder:
```bash
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
```
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
## Start your localized site
Start your site on the French locale:
```bash
npm run start -- --locale fr
```
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
:::caution
In development, you can only use one locale at a same time.
:::
## Add a Locale Dropdown
To navigate seamlessly across languages, add a locale dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
module.exports = {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'localeDropdown',
},
// highlight-end
],
},
},
};
```
The locale dropdown now appears in your navbar:
![Locale Dropdown](./img/localeDropdown.png)
## Build your localized site
Build your site for a specific locale:
```bash
npm run build -- --locale fr
```
Or build your site to include all the locales at once:
```bash
npm run build
```

View File

@@ -0,0 +1,8 @@
{
"label": "How to use the app",
"position": 3,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Docusaurus concepts."
}
}

View File

133
docs/docusaurus.config.js Normal file
View File

@@ -0,0 +1,133 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require("prism-react-renderer/themes/github");
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Immich Documentation",
tagline:
"Self-hosted photo and video backup solution directly from your mobile phone",
url: "https://documentation.immich.app",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
favicon: "img/favicon.png",
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: "immich-app", // Usually your GitHub org/user name.
projectName: "immich", // Usually your repo name.
deploymentBranch: "main",
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: "en",
locales: ["en"],
},
presets: [
[
"classic",
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve("./sidebars.js"),
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
},
blog: {
showReadingTime: true,
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
docs: {
sidebar: {
autoCollapseCategories: false,
},
},
navbar: {
title: "Immich",
logo: {
alt: "Immich University Logo",
src: "img/logo.png",
},
items: [
{
to: "/docs/overview/introduction",
position: "right",
label: "Documentation",
},
{ to: "/blog", label: "Blog", position: "right" },
{
href: "https://github.com/immich-app/immich",
label: "GitHub",
position: "right",
},
],
},
footer: {
style: "dark",
links: [
{
title: "Overview",
items: [
{
label: "Welcome",
to: "/docs/overview/introduction",
},
{
label: "Installation",
to: "/docs/installation/requirements",
},
],
},
{
title: "Community",
items: [
{
label: "Discord",
href: "https://discord.com/invite/D8JsnBEuKb",
},
],
},
{
title: "More",
items: [
{
label: "Blog",
to: "/blog",
},
{
label: "GitHub",
href: "https://github.com/immich-app/immich",
},
],
},
],
copyright: `Alex Tran - For my family`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;

21306
docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
docs/package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "documentation",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "2.1.0",
"@docusaurus/preset-classic": "2.1.0",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.1.0",
"@tsconfig/docusaurus": "^1.0.5",
"typescript": "^4.7.4"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=16.14"
}
}

33
docs/sidebars.js Normal file
View File

@@ -0,0 +1,33 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
module.exports = sidebars;

View File

@@ -0,0 +1,70 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
description: JSX.Element;
};
const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({title, Svg, description}: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures(): JSX.Element {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

37
docs/src/css/custom.css Normal file
View File

@@ -0,0 +1,37 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
@import url("https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap");
html {
font-family: "Work Sans", sans-serif;
}
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #4250af;
--ifm-color-primary-dark: #4250af;
--ifm-color-primary-darker: 4250af;
--ifm-color-primary-darkest: 4250af;
--ifm-color-primary-light: #4250af;
--ifm-color-primary-lighter: #4250af;
--ifm-color-primary-lightest: #4250af;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme="dark"] {
--ifm-color-primary: #adcbfa;
--ifm-color-primary-dark: #85b2f8;
--ifm-color-primary-darker: #71a5f6;
--ifm-color-primary-darkest: #357ff3;
--ifm-color-primary-light: #d5e4fc;
--ifm-color-primary-lighter: #e9f1fe;
--ifm-color-primary-lightest: #ffffff;
--ifm-background-color: #000000;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@@ -0,0 +1,24 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
height: 40rem;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

43
docs/src/pages/index.tsx Normal file
View File

@@ -0,0 +1,43 @@
import React from "react";
import clsx from "clsx";
import Link from "@docusaurus/Link";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Layout from "@theme/Layout";
import HomepageFeatures from "@site/src/components/HomepageFeatures";
import styles from "./index.module.css";
function HomepageHeader() {
const { siteConfig } = useDocusaurusContext();
return (
<header className={clsx("hero hero--primary", styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">{siteConfig.title}</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="docs/installation/requirements"
>
Getting Started
</Link>
</div>
</div>
</header>
);
}
export default function Home(): JSX.Element {
const { siteConfig } = useDocusaurusContext();
return (
<Layout
title={`${siteConfig.title}`}
description="Description will go into a meta tag in <head />"
>
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>
);
}

View File

@@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

0
docs/static/.nojekyll vendored Normal file
View File

0
docs/static/CNAME vendored Normal file
View File

BIN
docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/static/img/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/static/img/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,171 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
</g>
<g id="React-icon" transform="translate(906.3 541.56)">
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
<title>Focus on What Matters</title>
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

7
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
}
}

View File

@@ -2,61 +2,73 @@ echo "Starting Immich installation..."
ip_address=$(hostname -I | awk '{print $1}')
release_version=$(curl --silent "https://api.github.com/repos/immich-app/immich/releases/latest" |
grep '"tag_name":' |
sed -E 's/.*"([^"]+)".*/\1/')
RED='\033[0;31m'
GREEN='\032[0;31m'
NC='\033[0m' # No Color
machine_has() {
type "$1" >/dev/null 2>&1
get_release_version() {
curl --silent "https://api.github.com/repos/immich-app/immich/releases/latest" | # Get latest release from GitHub api
grep '"tag_name":' | # Get tag line
sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value
}
create_immich_directory() {
echo "Creating Immich directory..."
mkdir -p ./immich-app/immich-data
cd ./immich-app
}
download_docker_compose_file() {
echo "Downloading docker-compose.yml..."
curl -L https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml -o ./immich-app/docker-compose.yml >/dev/null 2>&1
curl -L https://raw.githubusercontent.com/immich-app/immich/$release_version/docker/docker-compose.yml -o ./docker-compose.yml >/dev/null 2>&1
}
download_dot_env_file() {
echo "Downloading .env file..."
curl -L https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example -o ./immich-app/.env >/dev/null 2>&1
curl -L https://raw.githubusercontent.com/immich-app/immich/$release_version/docker/.env.example -o ./.env >/dev/null 2>&1
}
replace_env_value() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|$1=.*|$1=$2|" ./.env
else
sed -i "s|$1=.*|$1=$2|" ./.env
fi
}
populate_upload_location() {
echo "Populating default UPLOAD_LOCATION value..."
upload_location=$(pwd)/immich-data
replace_env_value "UPLOAD_LOCATION" $upload_location
}
cd ./immich-app/immich-data
upload_location=$(pwd)
# Replace value of UPLOAD_LOCATION in .env with upload_location path
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|UPLOAD_LOCATION=.*|UPLOAD_LOCATION=$upload_location|" ../.env
else
sed -i "s|UPLOAD_LOCATION=.*|UPLOAD_LOCATION=$upload_location|" ../.env
fi
cd ..
generate_jwt_secret() {
echo "Generating JWT_SECRET value..."
jwt_secret=$(openssl rand -base64 128)
replace_env_value "JWT_SECRET" $jwt_secret
}
start_docker_compose() {
echo "Starting Immich's docker containers"
if machine_has "docker compose"; then {
docker compose up --remove-orphans -d
show_friendly_message
exit 0
}; fi
if machine_has "docker-compose"; then
docker-compose up --remove-orphans -d
if docker compose &>/dev/null; then
docker_bin="docker compose"
elif docker-compose &>/dev/null; then
docker_bin="docker-compose"
else
echo 'Cannot find `docker compose` or `docker-compose`.'
exit 1
fi
if $docker_bin up --remove-orphans -d; then
show_friendly_message
exit 0
else
echo "Could not start. Check for errors above."
exit 1
fi
}
@@ -65,7 +77,7 @@ show_friendly_message() {
echo "You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api"
echo "The backup (or upload) location is $upload_location"
echo "---------------------------------------------------"
echo "If you want to confgure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.
echo "If you want to configure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.
1. First bring down the containers with the command 'docker-compose down' in the immich-app directory,
@@ -80,4 +92,5 @@ create_immich_directory
download_docker_compose_file
download_dot_env_file
populate_upload_location
generate_jwt_secret
start_docker_compose

View File

@@ -9,6 +9,14 @@ upload:
locale_code: de-DE
- file: mobile/assets/i18n/fr-FR.json
locale_code: fr-FR
- file: mobile/assets/i18n/it-IT.json
locale_code: it-IT
- file: mobile/assets/i18n/nl-NL.json
locale_code: nl-NL
- file: mobile/assets/i18n/ko-KR.json
locale_code: ko-KR
- file: mobile/assets/i18n/da-DK.json
locale_code: da-DK
download:
files:
- file: mobile/assets/i18n/en-US.json
@@ -17,3 +25,11 @@ download:
locale_code: de-DE
- file: mobile/assets/i18n/fr-FR.json
locale_code: fr-FR
- file: mobile/assets/i18n/it-IT.json
locale_code: it-IT
- file: mobile/assets/i18n/nl-NL.json
locale_code: nl-NL
- file: mobile/assets/i18n/ko-KR.json
locale_code: ko-KR
- file: mobile/assets/i18n/da-DK.json
locale_code: da-DK

View File

@@ -1,3 +1,4 @@
# Build stage
FROM node:16-bullseye-slim as builder
@@ -39,4 +40,4 @@ COPY --from=builder /usr/src/app/dist ./dist
RUN npm prune --production
# CMD [ "node", "dist/main" ]
# CMD [ "node", "dist/main" ]

21
machine-learning/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Hau Tran
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,6 @@
"@nestjs/core": "^8.0.0",
"@nestjs/mapped-types": "^1.0.1",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/typeorm": "^8.0.3",
"@tensorflow-models/coco-ssd": "^2.2.2",
"@tensorflow-models/mobilenet": "^2.1.0",
"@tensorflow/tfjs": "^3.19.0",
@@ -34,11 +33,9 @@
"@tensorflow/tfjs-node": "^3.19.0",
"@tensorflow/tfjs-node-gpu": "^3.19.0",
"@trpc/server": "^9.20.3",
"pg": "^8.7.3",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.2.45"
"rxjs": "^7.2.0"
},
"devDependencies": {
"@nestjs/cli": "^8.2.4",

View File

@@ -1,15 +1,9 @@
import { Module } from '@nestjs/common';
import { ImageClassifierModule } from './image-classifier/image-classifier.module';
import { databaseConfig } from './config/database.config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ObjectDetectionModule } from './object-detection/object-detection.module';
@Module({
imports: [
TypeOrmModule.forRoot(databaseConfig),
ImageClassifierModule,
ObjectDetectionModule,
],
imports: [ImageClassifierModule, ObjectDetectionModule],
controllers: [],
providers: [],
})

View File

@@ -1,11 +0,0 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const databaseConfig: TypeOrmModuleOptions = {
type: 'postgres',
host: process.env.DB_HOSTNAME || 'immich_postgres',
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE_NAME,
synchronize: false,
};

View File

@@ -11,3 +11,6 @@ GeneratedPluginRegistrant.java
key.properties
**/*.keystore
**/*.jks
# Fastlane
/fastlane/report.xml

View File

@@ -51,7 +51,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "app.alextran.immich"
minSdkVersion 21
minSdkVersion 23
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich">
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich" xmlns:tools="http://schemas.android.com/tools">
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@@ -15,8 +15,12 @@
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
<!-- Disables default WorkManager initialization to use our custom initialization -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

View File

@@ -1,11 +1,6 @@
package app.alextran.immich
import android.content.Context
import android.net.Uri
import android.content.Intent
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
@@ -15,7 +10,7 @@ import io.flutter.plugin.common.MethodChannel
* Android plugin for Dart `BackgroundService`
*
* Receives messages/method calls from the foreground Dart side to manage
* the background service, e.g. start (enqueue), stop (cancel)
* the background service, e.g. start (enqueue), stop (cancel)
*/
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
@@ -43,52 +38,35 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val ctx = context!!
when(call.method) {
"initialize" -> { // needs to be called prior to any other method
when (call.method) {
"enable" -> {
val args = call.arguments<ArrayList<*>>()!!
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
.edit().putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long).apply()
.edit()
.putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true)
.putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args.get(0) as Long)
.putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args.get(1) as String)
.apply()
ContentObserverWorker.enable(ctx, immediate = args.get(2) as Boolean)
result.success(true)
}
"start" -> {
"configure" -> {
val args = call.arguments<ArrayList<*>>()!!
val immediate = args.get(0) as Boolean
val keepExisting = args.get(1) as Boolean
val requireUnmeteredNetwork = args.get(2) as Boolean
val requireCharging = args.get(3) as Boolean
val notificationTitle = args.get(4) as String
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
.edit().putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, notificationTitle).apply()
BackupWorker.startWork(ctx, immediate, keepExisting, requireUnmeteredNetwork, requireCharging)
val requireUnmeteredNetwork = args.get(0) as Boolean
val requireCharging = args.get(1) as Boolean
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
result.success(true)
}
"stop" -> {
"disable" -> {
ContentObserverWorker.disable(ctx)
BackupWorker.stopWork(ctx)
result.success(true)
}
"isEnabled" -> {
result.success(BackupWorker.isEnabled(ctx))
result.success(ContentObserverWorker.isEnabled(ctx))
}
"disableBatteryOptimizations" -> {
if(!BackupWorker.isIgnoringBatteryOptimizations(ctx)) {
val args = call.arguments<ArrayList<*>>()!!
val text = args.get(0) as String
Toast.makeText(ctx, text, Toast.LENGTH_LONG).show()
val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.setData(Uri.parse("package:" + ctx.getPackageName()))
try {
ctx.startActivity(intent)
} catch(e: Exception) {
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
try {
ctx.startActivity(intent)
} catch (e2: Exception) {
return result.success(false)
}
}
}
result.success(true)
"isIgnoringBatteryOptimizations" -> {
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
}
else -> result.notImplemented()
}

View File

@@ -1,5 +1,6 @@
package app.alextran.immich
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
@@ -8,17 +9,12 @@ import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.os.SystemClock
import android.provider.MediaStore
import android.provider.BaseColumns
import android.provider.MediaStore.MediaColumns
import android.provider.MediaStore.Images.Media
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.concurrent.futures.ResolvableFuture
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ForegroundInfo
import androidx.work.ListenableWorker
import androidx.work.NetworkType
@@ -26,6 +22,7 @@ import androidx.work.WorkerParameters
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkInfo
import com.google.common.util.concurrent.ListenableFuture
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
@@ -41,14 +38,7 @@ import java.util.concurrent.TimeUnit
* Starts the Dart runtime/engine and calls `_nativeEntry` function in
* `background.service.dart` to run the actual backup logic.
* Called by Android WorkManager when all constraints for the work are met,
* i.e. a new photo/video is created on the device AND battery is not low.
* Optionally, unmetered network (wifi) and charging can be required.
* As this work is not triggered periodically, but on content change, the
* worker enqueues itself again with the same settings.
* In case the worker is stopped by the system (e.g. constraints like wifi
* are no longer met, or the system needs memory resources for more other
* more important work), the worker is replaced without the constraint on
* changed contents to run again as soon as deemed possible by the system.
* i.e. battery is not low and optionally Wifi and charging are active.
*/
class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params), MethodChannel.MethodCallHandler {
@@ -57,14 +47,15 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
private lateinit var backgroundChannel: MethodChannel
private val notificationManager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val isIgnoringBatteryOptimizations = isIgnoringBatteryOptimizations(applicationContext)
private var timeBackupStarted: Long = 0L
private var notificationBuilder: NotificationCompat.Builder? = null
private var notificationDetailBuilder: NotificationCompat.Builder? = null
override fun startWork(): ListenableFuture<ListenableWorker.Result> {
Log.d(TAG, "startWork")
val ctx = applicationContext
// enqueue itself once again to continue to listen on added photos/videos
enqueueMoreWork(ctx,
requireUnmeteredNetwork = inputData.getBoolean(DATA_KEY_UNMETERED, true),
requireCharging = inputData.getBoolean(DATA_KEY_CHARGING, false))
if (!flutterLoader.initialized()) {
flutterLoader.startInitialization(ctx)
@@ -80,7 +71,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
// or by the system learning that immich is important to the user)
val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
.getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!!
setForegroundAsync(createForegroundInfo(title))
showInfo(getInfoBuilder(title, indeterminate=true).build())
}
engine = FlutterEngine(ctx)
@@ -115,6 +106,7 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
}
override fun onStopped() {
Log.d(TAG, "onStopped")
// called when the system has to stop this worker because constraints are
// no longer met or the system needs resources for more important tasks
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
@@ -130,24 +122,18 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
private fun stopEngine(result: Result?) {
if (result != null) {
Log.d(TAG, "stopEngine result=${result}")
resolvableFuture.set(result)
} else if (engine != null && inputData.getInt(DATA_KEY_RETRIES, 0) == 0) {
// stopped by system and this is the first time (content change constraints active)
// replace the task without the content constraints to finish the backup as soon as possible
enqueueMoreWork(applicationContext,
immediate = true,
requireUnmeteredNetwork = inputData.getBoolean(DATA_KEY_UNMETERED, true),
requireCharging = inputData.getBoolean(DATA_KEY_CHARGING, false),
initialDelayInMs = ONE_MINUTE,
retries = inputData.getInt(DATA_KEY_RETRIES, 0) + 1)
}
engine?.destroy()
engine = null
clearBackgroundNotification()
}
override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) {
when (call.method) {
"initialized" ->
"initialized" -> {
timeBackupStarted = SystemClock.uptimeMillis()
backgroundChannel.invokeMethod(
"onAssetsChanged",
null,
@@ -163,46 +149,49 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
override fun success(receivedResult: Any?) {
val success = receivedResult as Boolean
stopEngine(if(success) Result.success() else Result.retry())
if (!success && inputData.getInt(DATA_KEY_RETRIES, 0) == 0) {
// there was an error (e.g. server not available)
// replace the task without the content constraints to finish the backup as soon as possible
enqueueMoreWork(applicationContext,
immediate = true,
requireUnmeteredNetwork = inputData.getBoolean(DATA_KEY_UNMETERED, true),
requireCharging = inputData.getBoolean(DATA_KEY_CHARGING, false),
initialDelayInMs = ONE_MINUTE,
retries = inputData.getInt(DATA_KEY_RETRIES, 0) + 1)
}
}
}
)
}
"updateNotification" -> {
val args = call.arguments<ArrayList<*>>()!!
val title = args.get(0) as String
val content = args.get(1) as String
if (isIgnoringBatteryOptimizations) {
setForegroundAsync(createForegroundInfo(title, content))
val title = args.get(0) as String?
val content = args.get(1) as String?
val progress = args.get(2) as Int
val max = args.get(3) as Int
val indeterminate = args.get(4) as Boolean
val isDetail = args.get(5) as Boolean
val onlyIfFG = args.get(6) as Boolean
if (!onlyIfFG || isIgnoringBatteryOptimizations) {
showInfo(getInfoBuilder(title, content, isDetail, progress, max, indeterminate).build(), isDetail)
}
}
"showError" -> {
val args = call.arguments<ArrayList<*>>()!!
val title = args.get(0) as String
val content = args.get(1) as String
val content = args.get(1) as String?
val individualTag = args.get(2) as String?
showError(title, content, individualTag)
}
"clearErrorNotifications" -> clearErrorNotifications()
"hasContentChanged" -> {
val lastChange = applicationContext
.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
.getLong(SHARED_PREF_LAST_CHANGE, timeBackupStarted)
val hasContentChanged = lastChange > timeBackupStarted;
timeBackupStarted = SystemClock.uptimeMillis()
r.success(hasContentChanged)
}
else -> r.notImplemented()
}
}
private fun showError(title: String, content: String, individualTag: String?) {
private fun showError(title: String, content: String?, individualTag: String?) {
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ERROR_ID)
.setContentTitle(title)
.setTicker(title)
.setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher)
.setOnlyAlertOnce(true)
.build()
notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification)
}
@@ -211,111 +200,116 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
notificationManager.cancel(NOTIFICATION_ERROR_ID)
}
private fun createForegroundInfo(title: String = NOTIFICATION_DEFAULT_TITLE, content: String? = null): ForegroundInfo {
val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setTicker(title)
.setContentText(content)
.setSmallIcon(R.mipmap.ic_launcher)
.setOngoing(true)
.build()
return ForegroundInfo(NOTIFICATION_ID, notification)
}
private fun clearBackgroundNotification() {
notificationManager.cancel(NOTIFICATION_ID)
notificationManager.cancel(NOTIFICATION_DETAIL_ID)
}
private fun showInfo(notification: Notification, isDetail: Boolean = false) {
val id = if(isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID
if (isIgnoringBatteryOptimizations) {
setForegroundAsync(ForegroundInfo(id, notification))
} else {
notificationManager.notify(id, notification)
}
}
private fun getInfoBuilder(
title: String? = null,
content: String? = null,
isDetail: Boolean = false,
progress: Int = 0,
max: Int = 0,
indeterminate: Boolean = false,
): NotificationCompat.Builder {
var builder = if(isDetail) notificationDetailBuilder else notificationBuilder
if (builder == null) {
builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setOnlyAlertOnce(true)
.setOngoing(true)
if (isDetail) {
notificationDetailBuilder = builder
} else {
notificationBuilder = builder
}
}
if (title != null) {
builder.setTicker(title).setContentTitle(title)
}
if (content != null) {
builder.setContentText(content)
}
return builder.setProgress(max, progress, indeterminate)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
val foreground = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW)
notificationManager.createNotificationChannel(foreground)
val error = NotificationChannel(NOTIFICATION_CHANNEL_ERROR_ID, NOTIFICATION_CHANNEL_ERROR_ID, NotificationManager.IMPORTANCE_DEFAULT)
val error = NotificationChannel(NOTIFICATION_CHANNEL_ERROR_ID, NOTIFICATION_CHANNEL_ERROR_ID, NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(error)
}
companion object {
const val SHARED_PREF_NAME = "immichBackgroundService"
const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle"
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle"
const val SHARED_PREF_LAST_CHANGE = "lastChange"
private const val TASK_NAME = "immich/photoListener"
private const val DATA_KEY_UNMETERED = "unmetered"
private const val DATA_KEY_CHARGING = "charging"
private const val DATA_KEY_RETRIES = "retries"
private const val TASK_NAME_BACKUP = "immich/BackupWorker"
private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService"
private const val NOTIFICATION_CHANNEL_ERROR_ID = "immich/backgroundServiceError"
private const val NOTIFICATION_DEFAULT_TITLE = "Immich"
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_ERROR_ID = 2
private const val ONE_MINUTE: Long = 60000
private const val NOTIFICATION_DETAIL_ID = 3
private const val ONE_MINUTE = 60000L
/**
* Enqueues the `BackupWorker` to run when all constraints are met.
*
* @param context Android Context
* @param immediate whether to enqueue(replace) the worker without the content change constraint
* @param keepExisting if true, use `ExistingWorkPolicy.KEEP`, else `ExistingWorkPolicy.APPEND_OR_REPLACE`
* @param requireUnmeteredNetwork if true, task only runs if connected to wifi
* @param requireCharging if true, task only runs if device is charging
* @param retries retry count (should be 0 unless an error occured and this is a retry)
* Enqueues the BackupWorker to run once the constraints are met
*/
fun startWork(context: Context,
immediate: Boolean = false,
keepExisting: Boolean = false,
requireUnmeteredNetwork: Boolean = false,
requireCharging: Boolean = false) {
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
.edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, true).apply()
enqueueMoreWork(context, immediate, keepExisting, requireUnmeteredNetwork, requireCharging)
fun enqueueBackupWorker(context: Context,
requireWifi: Boolean = false,
requireCharging: Boolean = false,
delayMilliseconds: Long = 0L) {
val workRequest = buildWorkRequest(requireWifi, requireCharging, delayMilliseconds)
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.KEEP, workRequest)
Log.d(TAG, "enqueueBackupWorker: BackupWorker enqueued")
}
private fun enqueueMoreWork(context: Context,
immediate: Boolean = false,
keepExisting: Boolean = false,
requireUnmeteredNetwork: Boolean = false,
requireCharging: Boolean = false,
initialDelayInMs: Long = 0,
retries: Int = 0) {
if (!isEnabled(context)) {
return
/**
* Updates the constraints of an already enqueued BackupWorker
*/
fun updateBackupWorker(context: Context,
requireWifi: Boolean = false,
requireCharging: Boolean = false) {
try {
val wm = WorkManager.getInstance(context)
val workInfoFuture = wm.getWorkInfosForUniqueWork(TASK_NAME_BACKUP)
val workInfoList = workInfoFuture.get(1000, TimeUnit.MILLISECONDS)
if (workInfoList != null) {
for (workInfo in workInfoList) {
if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
val workRequest = buildWorkRequest(requireWifi, requireCharging)
wm.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.REPLACE, workRequest)
Log.d(TAG, "updateBackupWorker updated BackupWorker constraints")
return
}
}
}
Log.d(TAG, "updateBackupWorker: BackupWorker not enqueued")
} catch (e: Exception) {
Log.d(TAG, "updateBackupWorker failed: ${e}")
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(if (requireUnmeteredNetwork) NetworkType.UNMETERED else NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresCharging(requireCharging);
if (!immediate) {
constraints
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
}
val inputData = Data.Builder()
.putBoolean(DATA_KEY_CHARGING, requireCharging)
.putBoolean(DATA_KEY_UNMETERED, requireUnmeteredNetwork)
.putInt(DATA_KEY_RETRIES, retries)
.build()
val photoCheck = OneTimeWorkRequest.Builder(BackupWorker::class.java)
.setConstraints(constraints.build())
.setInputData(inputData)
.setInitialDelay(initialDelayInMs, TimeUnit.MILLISECONDS)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
ONE_MINUTE,
TimeUnit.MILLISECONDS)
.build()
val policy = if (immediate) ExistingWorkPolicy.REPLACE else (if (keepExisting) ExistingWorkPolicy.KEEP else ExistingWorkPolicy.APPEND_OR_REPLACE)
val op = WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME, policy, photoCheck)
val result = op.getResult().get()
}
/**
* Stops the currently running worker (if any) and removes it from the work queue
*/
fun stopWork(context: Context) {
context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
.edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, false).apply()
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME)
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_BACKUP)
Log.d(TAG, "stopWork: BackupWorker cancelled")
}
/**
@@ -330,12 +324,21 @@ class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ct
return true
}
/**
* Return true if the user has enabled the background backup service
*/
fun isEnabled(ctx: Context): Boolean {
return ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(SHARED_PREF_SERVICE_ENABLED, false)
private fun buildWorkRequest(requireWifi: Boolean = false,
requireCharging: Boolean = false,
delayMilliseconds: Long = 0L): OneTimeWorkRequest {
val constraints = Constraints.Builder()
.setRequiredNetworkType(if (requireWifi) NetworkType.UNMETERED else NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresCharging(requireCharging)
.build();
val work = OneTimeWorkRequest.Builder(BackupWorker::class.java)
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, ONE_MINUTE, TimeUnit.MILLISECONDS)
.setInitialDelay(delayMilliseconds, TimeUnit.MILLISECONDS)
.build()
return work
}
private val flutterLoader = FlutterLoader()

View File

@@ -0,0 +1,136 @@
package app.alextran.immich
import android.content.Context
import android.os.SystemClock
import android.provider.MediaStore
import android.util.Log
import androidx.work.Constraints
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.Operation
import java.util.concurrent.TimeUnit
/**
* Worker executed by Android WorkManager observing content changes (new photos/videos)
*
* Immediately enqueues the BackupWorker when running.
* As this work is not triggered periodically, but on content change, the
* worker enqueues itself again after each run.
*/
class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
override fun doWork(): Result {
if (!isEnabled(applicationContext)) {
return Result.failure()
}
if (getTriggeredContentUris().size > 0) {
startBackupWorker(applicationContext, delayMilliseconds = 0)
}
enqueueObserverWorker(applicationContext, ExistingWorkPolicy.REPLACE)
return Result.success()
}
companion object {
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
/**
* Enqueues the `ContentObserverWorker`.
*
* @param context Android Context
*/
fun enable(context: Context, immediate: Boolean = false) {
enqueueObserverWorker(context, ExistingWorkPolicy.KEEP)
Log.d(TAG, "enabled ContentObserverWorker")
if (immediate) {
startBackupWorker(context, delayMilliseconds = 5000)
}
}
/**
* Configures the `BackupWorker` to run when all constraints are met.
*
* @param context Android Context
* @param requireWifi if true, task only runs if connected to wifi
* @param requireCharging if true, task only runs if device is charging
*/
fun configureWork(context: Context,
requireWifi: Boolean = false,
requireCharging: Boolean = false) {
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
.apply()
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
}
/**
* Stops the currently running worker (if any) and removes it from the work queue
*/
fun disable(context: Context) {
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
.edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, false).apply()
WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_OBSERVER)
Log.d(TAG, "disabled ContentObserverWorker")
}
/**
* Return true if the user has enabled the background backup service
*/
fun isEnabled(ctx: Context): Boolean {
return ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(SHARED_PREF_SERVICE_ENABLED, false)
}
/**
* Enqueue and replace the worker without the content trigger but with a short delay
*/
fun workManagerAppClearedWorkaround(context: Context) {
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
.setInitialDelay(500, TimeUnit.MILLISECONDS)
.build()
WorkManager
.getInstance(context)
.enqueueUniqueWork(TASK_NAME_OBSERVER, ExistingWorkPolicy.REPLACE, work)
.getResult()
.get()
Log.d(TAG, "workManagerAppClearedWorkaround")
}
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
val constraints = Constraints.Builder()
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
.setTriggerContentUpdateDelay(5000, TimeUnit.MILLISECONDS)
.build()
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work)
}
fun startBackupWorker(context: Context, delayMilliseconds: Long) {
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
if (!sp.getBoolean(SHARED_PREF_SERVICE_ENABLED, false))
return
val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true)
val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false)
BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds)
sp.edit().putLong(BackupWorker.SHARED_PREF_LAST_CHANGE, SystemClock.uptimeMillis()).apply()
}
}
}
private const val TAG = "ContentObserverWorker"

View File

@@ -0,0 +1,19 @@
package app.alextran.immich
import android.app.Application
import androidx.work.Configuration
import androidx.work.WorkManager
class ImmichApp : Application() {
override fun onCreate() {
super.onCreate()
val config = Configuration.Builder().build()
WorkManager.initialize(this, config)
// always start BackupWorker after WorkManager init; this fixes the following bug:
// After the process is killed (by user or system), the first trigger (taking a new picture) is lost.
// Thus, the BackupWorker is not started. If the system kills the process after each initialization
// (because of low memory etc.), the backup is never performed.
// As a workaround, we also run a backup check when initializing the application
ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0)
}
}

View File

@@ -2,12 +2,14 @@ package app.alextran.immich
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import android.os.Bundle
import android.content.Intent
class MainActivity: FlutterActivity() {
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.getPlugins().add(BackgroundServicePlugin())
flutterEngine.plugins.add(BackgroundServicePlugin())
}
}

View File

@@ -16,12 +16,17 @@
default_platform(:android)
platform :android do
desc "Build Android"
lane :build do
desc "Build Android and Release Testing"
lane :beta do
gradle(
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 47,
"android.injected.version.name" => "1.30.2",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab', track: 'beta')
end
desc "Build and Release Android"
@@ -30,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 35,
"android.injected.version.name" => "1.25.0",
"android.injected.version.code" => 52,
"android.injected.version.name" => "1.33.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -15,13 +15,13 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
## Android
### android build
### android beta
```sh
[bundle exec] fastlane android build
[bundle exec] fastlane android beta
```
Build Android
Build Android and Release Testing
### android release

View File

@@ -0,0 +1 @@
* Fixed rendering blank when failed to parse datetime on main timeline

View File

@@ -0,0 +1,2 @@
* Add cache setting and improve caching mechanism
* Persist WiFi + charging settings of background backup

View File

@@ -0,0 +1,2 @@
* Fixed remove empty translations
* Fixed search page crashes the app on some Android models

View File

@@ -0,0 +1 @@
* Improve Android background service reliability

View File

@@ -0,0 +1 @@
* Fix background service cannot run in release build

View File

@@ -0,0 +1,2 @@
* Fixed oversize play button on video
* Fixed app crashing when swipe between assets

View File

@@ -0,0 +1,2 @@
* Fixed Android BackgroundServiceStartNotAllowedException
* Restore old cache mechanism

View File

@@ -0,0 +1 @@
* Update deprecated API that causes notification not dismissing after background upload progress finished.

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