Compare commits

..

69 Commits

Author SHA1 Message Date
Marty Fuhry
6a156b813e background 2024-05-21 16:00:46 -04:00
Lukas
4907916345 fix(web): emit updated date when pressing enter (#9640)
wip uploading

format

wip first working version
2024-05-21 16:00:46 -04:00
Jason Rasmussen
a3489d604b chore: remove unused stubs (#9647) 2024-05-21 18:35:26 +02:00
renovate[bot]
00b5ad3421 chore(deps): update dependency @types/lodash to v4.17.3 (#9644)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 16:18:35 +00:00
Jason Rasmussen
2ada4a8426 chore: gitignore open api docs/tests (#9643) 2024-05-21 16:35:20 +01:00
renovate[bot]
924e9f08cd chore(deps): update mobile (#9629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 13:40:21 +00:00
Jason Rasmussen
91b835cfeb fix: auth sub override (#9635) 2024-05-21 09:07:34 -04:00
renovate[bot]
bb79df655d fix(deps): update dependency sharp to v0.33.4 (#9633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 13:54:56 +01:00
Zack Pollard
02e755bd92 ci: add conventional commit validation for PR titles (#9634) 2024-05-21 13:54:21 +01:00
renovate[bot]
0963a32a95 chore(deps): update base-image to v20240521 (major) (#9632)
chore(deps): update base-image to v20240521

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 11:53:47 +00:00
renovate[bot]
6119618ae2 chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to d5b8281 (#9616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 09:48:11 +01:00
renovate[bot]
4d044658a0 fix(deps): update dependency svelte-maplibre to v0.9.3 (#9626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 09:35:16 +01:00
renovate[bot]
67fa598f44 chore(deps): update mobile (#9621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 08:31:53 +00:00
waclaw66
9222b9d130 fix(mobile): invite user list (#9624)
* fix(mobile): invite user list

* make it dense as before
2024-05-21 09:27:17 +01:00
Zack Pollard
d6757fc2dd chore: update fvmrc flutter version to 3.22.0 (#9625) 2024-05-21 09:22:22 +01:00
Alex
4f838eabbe fix(server): semver in development (#9620) 2024-05-20 23:03:28 -04:00
renovate[bot]
143b9d6828 fix(deps): update typescript-projects (#9617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 00:32:18 +00:00
Jason Rasmussen
1df7be8436 refactor(server): version logic (#9615)
* refactor(server): version

* test: better version and log checks
2024-05-20 20:31:36 -04:00
renovate[bot]
5f25f28c42 chore(deps): update redis:6.2-alpine docker digest to c0634a0 (#9577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 20:19:43 -04:00
renovate[bot]
ae92422df7 chore(deps): update grafana/grafana docker tag to v11 (#9599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 20:19:14 -04:00
Jason Rasmussen
84d824d6a7 refactor: library type (#9525) 2024-05-20 18:09:10 -04:00
Eric Barch
4353153fe6 fix(web): render failed upload buttons correctly on mobile (#9601)
fix(web): Failed upload buttons render correctly on mobile
2024-05-20 09:59:27 -05:00
Parsa
c37bf9d5d0 fix: docker compose pull rate limit (#9600)
* fix: docker compose pull rate limit

with "registry.hub.docker.com/" behind the image name, there was an issue where "docker compose up -d" would throw a rate-limiting error, even when logged in using a docker account.

it doesn't really matter where the image is downloaded from as long as it has the same sha256 hash in docker-compose.yml

* fix: use `docker.io/` for image reference in docker-compose.yml
2024-05-20 09:58:47 -05:00
Snowknight26
5c8c0b2f5b fix(web): prevent asset grid dates from being truncated (#9603) 2024-05-20 08:21:01 -04:00
Snowknight26
39129721fa fix(web): fix add to album modal text spacing (#9606)
* fix(web): fix add to album modal text spacing

* Fix formatting
2024-05-20 08:20:08 -04:00
Matthew Momjian
451416ec88 docs: community docs typos (#9604)
typos
2024-05-19 15:50:46 -04:00
Robert Schäfer
e39ee8a16f docs: Add pgadmin4 to docker-compose.yml (#9556)
* docs: Add pgadmin4 to docker-compose.yml

Motivation
----------
The current documentation encourages to install pgAdmin3 on the host
system. It's much simpler to add `pgAdmin4` to the `docker-compose.yml`:

1. No configuration needs to be modified, just added.
2. Better security because no additional ports need to be opened on the host.
3. Easier installation. E.g. on Archlinux there is no package pgAdmin3
   anymore.
4. `pgAdmin3` does not seem to be maintained.

How to test
-----------
1. Follow the documentation.
2. See if you can connect to the immich database

* docs: better use separate config file

I assume most users will not edit the `docker-compose.yml` and forget
about `pgAdmin` once they're done. So, `pgAdmin` might get exposed to
the internet with default credentials, which is not good.

Better to leave a hint to change the credentials and keep the
configuration separate, so users start `pgAdmin` knowingly and turn it
off once they're done.
2024-05-19 07:43:40 -05:00
renovate[bot]
1e56352b04 chore(deps): update registry.hub.docker.com/library/redis:6.2-alpine docker digest to c0634a0 (#9578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 12:34:09 +02:00
Jason Rasmussen
470c95d614 chore: vs code formatter settings (#9562) 2024-05-18 13:50:53 -05:00
Alex
1ad04f0b17 chore(server): openapi generation (#9585) 2024-05-18 13:50:28 -05:00
Alex
60427f18ce chore(server): return duplicate assets as group (#9576)
* chore(server): return duplicate assets as group

* file name
2024-05-18 13:15:56 -05:00
Mert
9aed736911 fix(ml): openvino not working with kernel 6.7.5 or later (#9541)
* add envs

* move to Dockerfile
2024-05-18 00:00:20 +00:00
Robert Schäfer
6b369e84fc docs: use npm in README (#9566)
Motivation
----------
Looks like `npm` is being used, `package-lock.json` is checked in
whereas `yarn.lock` is gitignored.

So, let's use `npm` in the README.

How to test
-----------
1. Follow the README.
2. It behaves just like before.
2024-05-17 16:49:23 -04:00
Jason Rasmussen
136bb69bd0 refactor: sdk init (#9563) 2024-05-17 16:48:29 -04:00
Nicholas Flamy
975f2351ec fix(server): Disable duplicate detection when smart search disabled (#9565) 2024-05-17 16:37:26 -04:00
Jason Rasmussen
2e62c7b417 refactor: node_env => immich_env (#9561) 2024-05-17 13:30:05 -04:00
Alex
2689178a35 chore(server): openapi generation for Duplicate controller (#9560) 2024-05-17 12:05:23 -05:00
Jason Rasmussen
d61418886f refactor!: port env (#9559)
refactor: port env
2024-05-17 12:59:05 -04:00
Jason Rasmussen
c03981ac1d refactor(server): new version check (#9555) 2024-05-17 12:22:39 -04:00
Jason Rasmussen
4807fc40a6 refactor!: LOG_LEVEL => IMMICH_LOG_LEVEL (#9557)
refactor: LOG_LEVEL => IMMICH_LOG_LEVEL
2024-05-17 11:44:22 -04:00
Alex
101bc290f9 chore(web): light theme text color improvement (#9553)
* chore(web): light theme text color improvement

* openapi

* openapi
2024-05-17 15:05:59 +00:00
Alex
df42352f84 chore(server): openapi generation (#9554) 2024-05-17 14:58:55 +00:00
Zack Pollard
c8aa6a62c2 fix: when using old script args, just set the workers include var (#9552)
* fix: when using old script args, just set the workers include var and move on

* fix: set process.title when using new bootstrap worker startup method
2024-05-17 15:10:57 +01:00
Zack Pollard
85aca2bb54 feat: microservices be gone (#9551)
* feat: microservices be gone and api is a worker now too

* chore: remove very old startup scripts, surely nobody is using these anymore, right?

right?....
2024-05-17 14:44:30 +01:00
Mert
ff52300624 refactor(server): duplicate controller and service (#9542)
* duplicate controller and service

* change endpoint name

* fix search tests

* remove unused import

* add to index
2024-05-16 19:39:33 -04:00
Jason Rasmussen
936a46b4ed fix(server): use jasonnnnnnnnnb (#9539) 2024-05-16 17:24:54 -04:00
Mert
d8eca168ca feat(server): fully accelerated nvenc (#9452)
* use arrayContaining

* libplacebo for nvenc

update dockerfile

* tweaks

* update nvenc options

* tweak settings

* refactor

* toggle for hardware decoding, software / hardware decoding for nvenc and rkmpp

* fix software tone-mapping not being applied

* separate configs for hw/sw

* update api

* add hw decode toggle

* fix mutating config

* remove `version` flag

* fix config type

* remove submodule

* handle temporal AQ

* remove duplicate tests

* use `tonemap_opencl`

* wording

* update docs
2024-05-16 13:30:26 -04:00
Mert
64636c0618 feat(server): near-duplicate detection (#8228)
* duplicate detection job, entity, config

* queueing

* job panel, update api

* use embedding in db instead of fetching

* disable concurrency

* only queue visible assets

* handle multiple duplicateIds

* update concurrent queue check

* add provider

* add web placeholder, server endpoint, migration, various fixes

* update sql

* select embedding by default

* rename variable

* simplify

* remove separate entity, handle re-running with different threshold, set default back to 0.02

* fix tests

* add tests

* add index to entity

* formatting

* update asset mock

* fix `upsertJobStatus` signature

* update sql

* formatting

* default to 0.03

* optimize clustering

* use asset's `duplicateId` if present

* update sql

* update tests

* expose admin setting

* refactor

* formatting

* skip if ml is disabled

* debug trash e2e

* remove from web

* remove from sidebar

* test if ml is disabled

* update sql

* separate duplicate detection from clip in config, disable by default for now

* fix doc

* lower minimum `maxDistance`

* update api

* Add and Use Duplicate Detection Feature Flag (#9364)

* Add Duplicate Detection Flag

* Use Duplicate Detection Flag

* Attempt Fixes for Failing Checks

* lower minimum `maxDistance`

* fix tests

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>

* chore: fixes and additions after rebase

* chore: update api (remove new Role enum)

* fix: left join smart search so getAll works without machine learning

* test: trash e2e go back to checking length of assets is zero

* chore: regen api after rebase

* test: fix tests after rebase

* redundant join

---------

Co-authored-by: Nicholas Flamy <30300649+NicholasFlamy@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-05-16 18:08:37 +01:00
Alex
673e97e71d chore(mobile): upgrade flutter to 3.22 (#9518)
* chore(mobile): upgrade flutter sdk

* gha

* update kotlin

* refactor

* ios build

* remove patch files

* not touching openapi pubpsec file
2024-05-16 10:58:02 -05:00
Jason Rasmussen
984aa8fb41 refactor(server): system config (#9517) 2024-05-15 18:58:23 -04:00
renovate[bot]
7f0f016f2e chore(deps): update dependency eslint-plugin-unicorn to v53 (#9502)
* chore(deps): update dependency eslint-plugin-unicorn to v53

* use structured clone to match new eslint rules

* use raw string instead of escaping slash

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-05-15 18:06:25 -04:00
Jason Rasmussen
0589575154 chore: bump open-api (#9522) 2024-05-15 16:52:52 -05:00
Jason Rasmussen
73bf8f343a chore(server): remove unused property (#9521) 2024-05-15 15:17:48 -04:00
Michel Heusschen
581b467b4b fix(server): smtp certificate validation (#9506) 2024-05-15 07:21:35 -04:00
renovate[bot]
efb844c6cd fix(deps): update dependency @zoom-image/svelte to v0.2.12 (#9487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 19:33:53 -04:00
Alex The Bot
88d4338348 Version v1.105.1 2024-05-14 21:31:24 +00:00
Jason Rasmussen
ce7bbe88f9 fix(server): skip originals when deleting a library (#9496) 2024-05-14 16:29:57 -05:00
kleinMaggus
d62e90424e feat(web,mobile) Allow videos to be looped in the detail viewer (#8615)
* First version of video looping for the web

* Use prop for slideshow state

* refactor asset settings and add autoloop video setting

* rename variables and adjust description

* loop videos based on user settings in gallery viewer

* make asset viewer setting a stateless widget

* do not update video playback value if looping is enabled

* add some translations

* adjust description

* add missing id

* WIP

* chore: clean up

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-14 14:31:47 -05:00
Jason Rasmussen
0f129cae4a refactor(server): feature flags (#9492) 2024-05-14 15:31:36 -04:00
renovate[bot]
5583635947 chore(deps): update python:3.11-bookworm docker digest to 96de1ea (#9490) 2024-05-14 19:13:56 +00:00
renovate[bot]
b7715305b3 chore(deps): update dependency fastlane to v2.220.0 (#9491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 14:12:27 -05:00
shenlong
6c008176c9 deps(mobile): update dependency auto_route to v8 (#9456)
deps: update dependency auto_route to v8

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-14 14:07:31 -05:00
renovate[bot]
72b1d582ba chore(deps): update dependency flutter_lints to v4 (#9488)
* chore(deps): update dependency flutter_lints to v4

* fix lints

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-14 18:52:07 +00:00
Jason Rasmussen
7b1112f3e3 refactor(server): system config (#9484) 2024-05-14 14:43:49 -04:00
renovate[bot]
42d0fc85ca chore(deps): update mobile (#9453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 13:39:48 -05:00
Alex Tran
d32b621750 Merge branch 'main' of github.com:immich-app/immich 2024-05-14 13:38:15 -05:00
Alex Tran
d551003311 chore: post release tasks 2024-05-14 13:38:12 -05:00
dependabot[bot]
dd64379a4e chore(deps-dev): bump flask-cors from 4.0.0 to 4.0.1 in /machine-learning (#9485) 2024-05-14 14:06:40 -04:00
dependabot[bot]
c1cdda0ac4 chore(deps-dev): bump jinja2 from 3.1.3 to 3.1.4 in /machine-learning (#9483) 2024-05-14 17:08:29 +00:00
936 changed files with 5933 additions and 26593 deletions

2
.gitattributes vendored
View File

@@ -2,8 +2,6 @@ mobile/openapi/**/*.md -diff -merge
mobile/openapi/**/*.md linguist-generated=true
mobile/openapi/**/*.dart -diff -merge
mobile/openapi/**/*.dart linguist-generated=true
mobile/openapi/.openapi-generator/FILES -diff -merge
mobile/openapi/.openapi-generator/FILES linguist-generated=true
mobile/lib/**/*.g.dart -diff -merge
mobile/lib/**/*.g.dart linguist-generated=true

View File

@@ -45,7 +45,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.19.3'
flutter-version: '3.22.0'
cache: true
- name: Create the Keystore

View File

@@ -0,0 +1,15 @@
name: PR Conventional Commit Validation
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
validate-pr-title:
runs-on: ubuntu-latest
steps:
- name: PR Conventional Commit Validation
uses: ytanikin/PRConventionalCommits@1.1.0
with:
task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]'
add_label: 'false'

View File

@@ -22,8 +22,8 @@ jobs:
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.19.3"
channel: 'stable'
flutter-version: '3.22.0'
- name: Install dependencies
run: dart pub get

View File

@@ -208,7 +208,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.19.3'
flutter-version: '3.22.0'
- name: Run tests
working-directory: ./mobile
run: flutter test -j 1

5
.gitignore vendored
View File

@@ -14,7 +14,10 @@ mobile/gradle.properties
mobile/openapi/pubspec.lock
mobile/*.jks
mobile/libisar.dylib
mobile/openapi/test
mobile/openapi/doc
mobile/openapi/.openapi-generator/FILES
open-api/typescript-sdk/build
mobile/android/fastlane/report.xml
mobile/ios/fastlane/report.xml
mobile/ios/fastlane/report.xml

14
.vscode/settings.json vendored
View File

@@ -1,6 +1,16 @@
{
"editor.formatOnSave": true,
"[javascript][typescript][css]": {
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
@@ -31,4 +41,4 @@
"explorer.fileNesting.patterns": {
"*.ts": "${capture}.spec.ts,${capture}.mock.ts"
}
}
}

272
cli/package-lock.json generated
View File

@@ -31,7 +31,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"eslint-plugin-unicorn": "^53.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
@@ -47,14 +47,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.105.0",
"version": "1.105.1",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/node": "^20.12.12",
"typescript": "^5.3.3"
}
},
@@ -174,9 +174,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1113,12 +1113,6 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
@@ -1144,10 +1138,11 @@
}
},
"node_modules/@types/node": {
"version": "20.12.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
"version": "20.12.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
"integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -1158,28 +1153,21 @@
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
"integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz",
"integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/type-utils": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4",
"@typescript-eslint/scope-manager": "7.9.0",
"@typescript-eslint/type-utils": "7.9.0",
"@typescript-eslint/utils": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
@@ -1200,15 +1188,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
"integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz",
"integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"@typescript-eslint/scope-manager": "7.9.0",
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/typescript-estree": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1228,13 +1217,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
"integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz",
"integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0"
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1245,13 +1235,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
"integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz",
"integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"@typescript-eslint/typescript-estree": "7.9.0",
"@typescript-eslint/utils": "7.9.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -1272,10 +1263,11 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
"integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz",
"integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
@@ -1285,13 +1277,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
"integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz",
"integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1313,18 +1306,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
"integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz",
"integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"semver": "^7.6.0"
"@typescript-eslint/scope-manager": "7.9.0",
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/typescript-estree": "7.9.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1338,12 +1329,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
"integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz",
"integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/types": "7.9.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -1564,6 +1556,7 @@
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -1588,6 +1581,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -1822,12 +1816,12 @@
"dev": true
},
"node_modules/core-js-compat": {
"version": "3.36.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz",
"integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==",
"version": "3.37.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz",
"integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==",
"dev": true,
"dependencies": {
"browserslist": "^4.22.3"
"browserslist": "^4.23.0"
},
"funding": {
"type": "opencollective",
@@ -1897,6 +1891,7 @@
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-type": "^4.0.0"
},
@@ -2094,17 +2089,17 @@
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "52.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-52.0.0.tgz",
"integrity": "sha512-1Yzm7/m+0R4djH0tjDjfVei/ju2w3AzUGjG6q8JnuNIL5xIwsflyCooW5sfBvQp2pMYQFSWWCFONsjCax1EHng==",
"version": "53.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
"integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-validator-identifier": "^7.24.5",
"@eslint-community/eslint-utils": "^4.4.0",
"@eslint/eslintrc": "^2.1.4",
"@eslint/eslintrc": "^3.0.2",
"ci-info": "^4.0.0",
"clean-regexp": "^1.0.0",
"core-js-compat": "^3.34.0",
"core-js-compat": "^3.37.0",
"esquery": "^1.5.0",
"indent-string": "^4.0.0",
"is-builtin-module": "^3.2.1",
@@ -2113,11 +2108,11 @@
"read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.27",
"regjsparser": "^0.10.0",
"semver": "^7.5.4",
"semver": "^7.6.1",
"strip-indent": "^3.0.0"
},
"engines": {
"node": ">=16"
"node": ">=18.18"
},
"funding": {
"url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
@@ -2126,6 +2121,92 @@
"eslint": ">=8.56.0"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz",
"integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"dev": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/espree": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
"integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
"dev": true,
"dependencies": {
"acorn": "^8.11.3",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -2466,6 +2547,7 @@
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
@@ -2969,6 +3051,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -3232,6 +3315,7 @@
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -3711,13 +3795,10 @@
}
},
"node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
@@ -3725,18 +3806,6 @@
"node": ">=10"
}
},
"node_modules/semver/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3781,6 +3850,7 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -4407,12 +4477,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yaml": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",

View File

@@ -28,7 +28,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"eslint-plugin-unicorn": "^53.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",

View File

@@ -1,4 +1,4 @@
import { defaults, getMyUserInfo, isHttpError } from '@immich/sdk';
import { getMyUserInfo, init, isHttpError } from '@immich/sdk';
import { glob } from 'fast-glob';
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
@@ -46,8 +46,7 @@ export const connect = async (url: string, key: string) => {
// noop
}
defaults.baseUrl = url;
defaults.headers = { 'x-api-key': key };
init({ baseUrl: url, apiKey: key });
const [error] = await withError(getMyUserInfo());
if (isHttpError(error)) {

View File

@@ -4,32 +4,29 @@
name: immich-dev
x-server-build: &server-common
image: immich-server-dev:latest
build:
context: ../
dockerfile: server/Dockerfile
target: dev
restart: always
volumes:
- ../server:/usr/src/app
- ../open-api:/usr/src/open-api
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ulimits:
nofile:
soft: 1048576
hard: 1048576
services:
immich-server:
container_name: immich_server
command: ['/usr/src/app/bin/immich-dev', 'immich']
<<: *server-common
command: ['/usr/src/app/bin/immich-dev']
image: immich-server-dev:latest
build:
context: ../
dockerfile: server/Dockerfile
target: dev
restart: always
volumes:
- ../server:/usr/src/app
- ../open-api:/usr/src/open-api
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ulimits:
nofile:
soft: 1048576
hard: 1048576
ports:
- 3001:3001
- 9230:9230
@@ -37,19 +34,6 @@ services:
- redis
- database
immich-microservices:
container_name: immich_microservices
command: ['/usr/src/app/bin/immich-dev', 'microservices']
<<: *server-common
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
ports:
- 9231:9230
depends_on:
- database
- immich-server
immich-web:
container_name: immich_web
image: immich-web-dev:latest
@@ -97,7 +81,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
database:
container_name: immich_postgres

View File

@@ -1,40 +1,24 @@
name: immich-prod
x-server-build: &server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
volumes:
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
restart: always
services:
immich-server:
container_name: immich_server
command: ['start.sh', 'immich']
<<: *server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
volumes:
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
restart: always
ports:
- 2283:3001
depends_on:
- redis
- database
immich-microservices:
container_name: immich_microservices
command: ['start.sh', 'microservices']
<<: *server-common
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
depends_on:
- redis
- database
- immich-server
immich-machine-learning:
container_name: immich_machine_learning
image: immich-machine-learning:latest
@@ -54,7 +38,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
restart: always
database:
@@ -90,7 +74,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:10.4.2-ubuntu@sha256:4f55071b556fb03f12b41423c98a185ed6695ed9ff2558e35805f0dd765fd958
image: grafana/grafana:11.0.0-ubuntu@sha256:02e99d1ee0b52dc9d3000c7b5314e7a07e0dfd69cc49bb3f8ce323491ed3406b
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -12,7 +12,6 @@ services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: ['start.sh', 'immich']
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
@@ -25,23 +24,6 @@ services:
- database
restart: always
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/hardware-transcoding
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
command: ['start.sh', 'microservices']
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
depends_on:
- redis
- database
restart: always
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
@@ -58,12 +40,12 @@ services:
redis:
container_name: immich_redis
image: registry.hub.docker.com/library/redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: docker.io/redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
restart: always
database:
container_name: immich_postgres
image: registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}

View File

@@ -1,9 +1,7 @@
version: "3.8"
# Configurations for hardware-accelerated machine learning
# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# you can inline the config for a backend by copying its contents
# into the immich-machine-learning service in the docker-compose.yml file.
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
@@ -30,7 +28,7 @@ services:
openvino:
device_cgroup_rules:
- "c 189:* rmw"
- 'c 189:* rmw'
devices:
- /dev/dri:/dev/dri
volumes:

View File

@@ -1,5 +1,3 @@
version: "3.8"
# Configurations for hardware-accelerated transcoding
# If using Unraid or another platform that doesn't allow multiple Compose files,

View File

@@ -5,13 +5,13 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
### Installation
```
$ yarn
$ npm install
```
### Local Development
```
$ yarn start
$ npm run 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.
@@ -19,7 +19,7 @@ This command starts a local development server and opens up a browser window. Mo
### Build
```
$ yarn build
$ npm run build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
@@ -29,13 +29,13 @@ This command generates static content into the `build` directory and can be serv
Using SSH:
```
$ USE_SSH=true yarn deploy
$ USE_SSH=true npm run deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
$ GIT_USER=<Your GitHub username> npm run 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.

View File

@@ -18,7 +18,7 @@ In any other situation, there are 3 different options that can appear:
- MATCHES - These files are matched by their checksums.
- OFFLINE PATHS - These files are the result of manually deleting files in the upload library or a failed file move in the past (losing track of a file).
- OFFLINE PATHS - These files are the result of manually deleting files from immich or a failed file move in the past (losing track of a file).
- UNTRACKED FILES - These files are not tracked by the application. They can be the result of failed moves, interrupted uploads, or left behind due to a bug.

View File

@@ -22,7 +22,8 @@ You do not need to redo any transcoding jobs after enabling hardware acceleratio
- WSL2 does not support Quick Sync.
- Raspberry Pi is currently not supported.
- Two-pass mode is only supported for NVENC. Other APIs will ignore this setting.
- Only encoding is currently hardware accelerated, so the CPU is still used for software decoding and tone-mapping.
- By default, only encoding is currently hardware accelerated. This means the CPU is still used for software decoding and tone-mapping.
- NVENC and RKMPP can be fully accelerated by enabling hardware decoding in the video transcoding settings.
- Hardware dependent
- Codec support varies, but H.264 and HEVC are usually supported.
- Notably, NVIDIA and AMD GPUs do not support VP9 encoding.
@@ -65,6 +66,7 @@ For RKMPP to work:
3. Redeploy the `immich-microservices` container with these updated settings.
4. In the Admin page under `Video transcoding settings`, change the hardware acceleration setting to the appropriate option and save.
5. (Optional) If using a compatible backend, you may enable hardware decoding for optimal performance.
#### Single Compose File

View File

@@ -4,10 +4,6 @@
Immich supports the creation of libraries which is a top-level asset container. Currently, there are two types of libraries: traditional upload libraries that can sync with a mobile device, and external libraries, that keeps up to date with files on disk. Libraries are different from albums in that an asset can belong to multiple albums but only one library, and deleting a library deletes all assets contained within. As of August 2023, this is a new feature and libraries have a lot of potential for future development beyond what is documented here. This document attempts to describe the current state of libraries.
## The Upload Library
Immich comes preconfigured with an upload library for each user. All assets uploaded to Immich are added to this library. This library can be renamed, but not deleted. The upload library is the only library that can be synced with a mobile device. No items in an upload library is allowed to have the same sha1 hash as another item in the same library in order to prevent duplicates.
## External Libraries
External libraries tracks assets stored outside of Immich, i.e. in the file system. When the external library is scanned, Immich will read the metadata from the file and create an asset in the library for each image or video file. These items will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc.

View File

@@ -95,7 +95,7 @@ immich-machine-learning:
Once this is done, you can redeploy the `immich-machine-learning` container.
:::info
You can confirm the device is being recognized and used by checking its utilization (via `nvtop` for CUDA, `intel_gpu_top` for OpenVINO, etc.). You can also enable debug logging by setting `LOG_LEVEL=debug` in the `.env` file and restarting the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, you should see a log for `Available ORT providers` containing the relevant provider. In the case of ARM NN, the absence of a `Could not load ANN shared libraries` log entry means it loaded successfully.
You can confirm the device is being recognized and used by checking its utilization (via `nvtop` for CUDA, `intel_gpu_top` for OpenVINO, etc.). You can also enable debug logging by setting `IMMICH_LOG_LEVEL=debug` in the `.env` file and restarting the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, you should see a log for `Available ORT providers` containing the relevant provider. In the case of ARM NN, the absence of a `Could not load ANN shared libraries` log entry means it loaded successfully.
:::
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml

View File

@@ -2,48 +2,52 @@
A short guide on connecting [pgAdmin](https://www.pgadmin.org/) to Immich.
:::note
In order to connect to the database the immich_postgres container **must be running**.
The passwords and usernames used below match the ones specified in the example `.env` file. If changed, please use actual values instead.
**Optional:** To connect to the database **outside** of your Docker's network:
- Expose port 5432 in your `docker-compose.yml` file.
- Edit the PostgreSQL [`pg_hba.conf`](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) file.
- Make sure your firewall does not block access to port 5432.
Note that exposing the database port increases the risk of getting attacked by hackers.
Make sure to remove the binding port after finishing the database's tasks.
:::
## 1. Install pgAdmin
Download and install [pgAdmin](https://www.pgadmin.org/download/) following the official documentation.
Add a file `docker-compose-pgadmin.yml` next to your `docker-compose.yml` with the following content:
```
name: immich
services:
pgadmin:
image: dpage/pgadmin4
container_name: pgadmin4_container
restart: always
ports:
- "8888:80"
environment:
PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com
PGADMIN_DEFAULT_PASSWORD: strong-password
volumes:
- pgadmin-data:/var/lib/pgadmin
volumes:
pgadmin-data:
```
Change the values of `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` in this file.
Run `docker compose -f docker-compose.yml -f docker-compose-pgadmin.yml up` to start immich along with `pgAdmin`.
## 2. Add a Server
Open pgAdmin and click "Add New Server".
Open [localhost:8888](http://localhost:8888) and login with the default credentials from above.
<img src={require('./img/add-new-server-option.png').default} width="50%" title="new server option" />
Right click on `Servers` and click on `Register >> Server..` then enter the values below in the `Connection` tab.
## 3. Enter Connection Details
<img src={require('./img/pgadmin-add-new-server.png').default} width="50%" title="new server option" />
| Name | Value |
| -------------------- | ----------- |
| Host name/address | `localhost` |
| Port | `5432` |
| Maintenance database | `immich` |
| Username | `postgres` |
| Password | `postgres` |
:::note
The parameters used here match those specified in the example `.env` file. If you have changed your `.env` file, you'll need to adjust accordingly.
:::
<img src={require('./img/Connection-Pgadmin.png').default} width="75%" title="Connection" />
## 4. Save Connection
| Name | Value |
| -------------------- | ----------------- |
| Host name/address | `immich_postgres` |
| Port | `5432` |
| Maintenance database | `immich` |
| Username | `postgres` |
| Password | `postgres` |
Click on "Save" to connect to the Immich database.
:::tip
View [Database Queries](/docs/guides/database-queries/) for common database queries.
:::

View File

@@ -96,7 +96,7 @@ SELECT * FROM "users";
## System Config
```sql title="Custom settings"
SELECT "key", "value" FROM "system_config";
SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
```
(Only used when not using the [config file](/docs/install/config-file))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -77,6 +77,10 @@ The default configuration looks like this:
"enabled": true,
"modelName": "ViT-B-32__openai"
},
"duplicateDetection": {
"enabled": false,
"maxDistance": 0.03
},
"facialRecognition": {
"enabled": true,
"modelName": "buffalo_l",

View File

@@ -41,8 +41,8 @@ Regardless of filesystem, it is not recommended to use a network share for your
| Variable | Description | Default | Services |
| :------------------------------ | :------------------------------------------- | :----------------------: | :-------------------------------------- |
| `TZ` | Timezone | | microservices |
| `NODE_ENV` | Environment (production, development) | `production` | server, microservices, machine learning |
| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices, machine learning |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, microservices, machine learning |
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices, machine learning |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server, microservices |
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server |
@@ -59,13 +59,10 @@ It only need to be set if the Immich deployment method is changing.
## Ports
| Variable | Description | Default | Services |
| :---------------------- | :-------------------- | :-------: | :-------------------- |
| `HOST` | Host | `0.0.0.0` | server, microservices |
| `SERVER_PORT` | Server Port | `3001` | server |
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning |
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
| Variable | Description | Default |
| :------------ | :------------- | :------------------------------------: |
| `IMMICH_HOST` | Listening host | `0.0.0.0` |
| `IMMICH_PORT` | Listening port | 3001 (server), 3003 (machine learning) |
## Database

View File

@@ -10,17 +10,17 @@ interface CommunityGuidesProps {
const guides: CommunityGuidesProps[] = [
{
title: 'Cloudflare Tunnels with SSO/OAuth',
description: `Setting up Cloudflare Tunnels and a SaaS App for immich.`,
description: `Setting up Cloudflare Tunnels and a SaaS App for Immich.`,
url: 'https://github.com/immich-app/immich/discussions/8299',
},
{
title: 'Database backup in Truenas',
description: `Create a database backup with pgAdmin in Truenas.`,
title: 'Database backup in TrueNAS',
description: `Create a database backup with pgAdmin in TrueNAS.`,
url: 'https://github.com/immich-app/immich/discussions/8809',
},
{
title: 'Unraid backup scripts',
description: `Back up your assets in Unarid with a pre-prepared script.`,
description: `Back up your assets in Unraid with a pre-prepared script.`,
url: 'https://github.com/immich-app/immich/discussions/8416',
},
{

View File

@@ -10,7 +10,7 @@ interface CommunityProjectProps {
const projects: CommunityProjectProps[] = [
{
title: 'immich-go',
description: `An alternative to the immich-CLI command that doesn't depend on nodejs installation. It tries its best for importing google photos takeout archives.`,
description: `An alternative to the immich-CLI that doesn't depend on nodejs. It specializes in importing Google Photos Takeout archives.`,
url: 'https://github.com/simulot/immich-go',
},
{

View File

@@ -2,40 +2,32 @@ version: '3.8'
name: immich-e2e
x-server-build: &server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
environment:
- DB_HOSTNAME=database
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=immich
- IMMICH_MACHINE_LEARNING_ENABLED=false
- IMMICH_METRICS=true
volumes:
- upload:/usr/src/app/upload
- ./test-assets:/test-assets
depends_on:
- redis
- database
services:
immich-server:
container_name: immich-e2e-server
command: ['./start.sh', 'immich']
<<: *server-common
command: ['./start.sh']
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
environment:
- DB_HOSTNAME=database
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=immich
- IMMICH_MACHINE_LEARNING_ENABLED=false
- IMMICH_METRICS=true
volumes:
- upload:/usr/src/app/upload
- ./test-assets:/test-assets
depends_on:
- redis
- database
ports:
- 2283:3001
immich-microservices:
container_name: immich-e2e-microservices
command: ['./start.sh', 'microservices']
<<: *server-common
redis:
image: redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0

259
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.105.0",
"version": "1.105.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.105.0",
"version": "1.105.1",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@immich/cli": "file:../cli",
@@ -23,7 +23,7 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"eslint-plugin-unicorn": "^53.0.0",
"exiftool-vendored": "^26.0.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",
@@ -65,7 +65,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"eslint-plugin-unicorn": "^53.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
@@ -81,14 +81,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.105.0",
"version": "1.105.1",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/node": "^20.12.12",
"typescript": "^5.3.3"
}
},
@@ -208,9 +208,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1217,12 +1217,6 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/luxon": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
@@ -1236,10 +1230,11 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.12.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
"version": "20.12.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
"integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -1327,12 +1322,6 @@
"@types/node": "*"
}
},
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
"node_modules/@types/superagent": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.3.tgz",
@@ -1355,21 +1344,20 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
"integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz",
"integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/type-utils": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4",
"@typescript-eslint/scope-manager": "7.9.0",
"@typescript-eslint/type-utils": "7.9.0",
"@typescript-eslint/utils": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
@@ -1390,15 +1378,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
"integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz",
"integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"@typescript-eslint/scope-manager": "7.9.0",
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/typescript-estree": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1418,13 +1407,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
"integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz",
"integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0"
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1435,13 +1425,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
"integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz",
"integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"@typescript-eslint/typescript-estree": "7.9.0",
"@typescript-eslint/utils": "7.9.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -1462,10 +1453,11 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
"integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz",
"integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
@@ -1475,13 +1467,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
"integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz",
"integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/visitor-keys": "7.9.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1507,6 +1500,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -1516,6 +1510,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -1527,18 +1522,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
"integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz",
"integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"semver": "^7.6.0"
"@typescript-eslint/scope-manager": "7.9.0",
"@typescript-eslint/types": "7.9.0",
"@typescript-eslint/typescript-estree": "7.9.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1552,12 +1545,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
"integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz",
"integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/types": "7.9.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -1785,6 +1779,7 @@
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -1840,6 +1835,7 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -2121,12 +2117,12 @@
"dev": true
},
"node_modules/core-js-compat": {
"version": "3.36.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz",
"integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==",
"version": "3.37.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz",
"integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==",
"dev": true,
"dependencies": {
"browserslist": "^4.22.3"
"browserslist": "^4.23.0"
},
"funding": {
"type": "opencollective",
@@ -2247,6 +2243,7 @@
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-type": "^4.0.0"
},
@@ -2487,17 +2484,17 @@
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "52.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-52.0.0.tgz",
"integrity": "sha512-1Yzm7/m+0R4djH0tjDjfVei/ju2w3AzUGjG6q8JnuNIL5xIwsflyCooW5sfBvQp2pMYQFSWWCFONsjCax1EHng==",
"version": "53.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
"integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-validator-identifier": "^7.24.5",
"@eslint-community/eslint-utils": "^4.4.0",
"@eslint/eslintrc": "^2.1.4",
"@eslint/eslintrc": "^3.0.2",
"ci-info": "^4.0.0",
"clean-regexp": "^1.0.0",
"core-js-compat": "^3.34.0",
"core-js-compat": "^3.37.0",
"esquery": "^1.5.0",
"indent-string": "^4.0.0",
"is-builtin-module": "^3.2.1",
@@ -2506,11 +2503,11 @@
"read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.27",
"regjsparser": "^0.10.0",
"semver": "^7.5.4",
"semver": "^7.6.1",
"strip-indent": "^3.0.0"
},
"engines": {
"node": ">=16"
"node": ">=18.18"
},
"funding": {
"url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
@@ -2519,6 +2516,70 @@
"eslint": ">=8.56.0"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz",
"integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"dev": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/espree": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
"integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
"dev": true,
"dependencies": {
"acorn": "^8.11.3",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -2692,6 +2753,7 @@
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@@ -2708,6 +2770,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -2759,6 +2822,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -3001,6 +3065,7 @@
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
@@ -3276,6 +3341,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -3491,18 +3557,6 @@
"get-func-name": "^2.0.1"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/luxon": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
@@ -3561,6 +3615,7 @@
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
}
@@ -3579,6 +3634,7 @@
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
@@ -4047,6 +4103,7 @@
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -4175,6 +4232,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
@@ -4710,13 +4768,10 @@
]
},
"node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
@@ -4809,6 +4864,7 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -5135,6 +5191,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.105.0",
"version": "1.105.1",
"description": "",
"main": "index.js",
"type": "module",
@@ -33,7 +33,7 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"eslint-plugin-unicorn": "^53.0.0",
"exiftool-vendored": "^26.0.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",

View File

@@ -2,10 +2,8 @@ import {
AssetFileUploadResponseDto,
AssetResponseDto,
AssetTypeEnum,
LibraryResponseDto,
LoginResponseDto,
SharedLinkType,
getAllLibraries,
getAssetInfo,
updateAssets,
} from '@immich/sdk';
@@ -819,25 +817,6 @@ describe('/asset', () => {
expect(duplicate).toBe(true);
});
it("should not upload to another user's library", async () => {
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
const library = libraries.find((library) => library.ownerId === user1.userId) as LibraryResponseDto;
const { body, status } = await request(app)
.post('/asset/upload')
.set('Authorization', `Bearer ${admin.accessToken}`)
.field('libraryId', library.id)
.field('deviceAssetId', 'example-image')
.field('deviceId', 'e2e')
.field('fileCreatedAt', new Date().toISOString())
.field('fileModifiedAt', new Date().toISOString())
.field('duration', '0:00:00.000000')
.attach('assetData', makeRandomImage(), 'example.png');
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no asset.upload access'));
});
it('should update the used quota', async () => {
const { body, status } = await request(app)
.post('/asset/upload')

View File

@@ -1,11 +1,4 @@
import {
LibraryResponseDto,
LibraryType,
LoginResponseDto,
ScanLibraryDto,
getAllLibraries,
scanLibrary,
} from '@immich/sdk';
import { LibraryResponseDto, LoginResponseDto, ScanLibraryDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { cpSync, existsSync } from 'node:fs';
import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures';
@@ -29,7 +22,7 @@ describe('/library', () => {
admin = await utils.adminSetup();
await utils.resetAdminConfig(admin.accessToken);
user = await utils.userSetup(admin.accessToken, userDto.user1);
library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, type: LibraryType.External });
library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
websocket = await utils.connectWebsocket(admin.accessToken);
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`);
utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`);
@@ -50,24 +43,6 @@ describe('/library', () => {
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should start with a default upload library', async () => {
const { status, body } = await request(app).get('/library').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.Upload,
name: 'Default Library',
refreshedAt: null,
assetCount: 0,
importPaths: [],
exclusionPatterns: [],
}),
]),
);
});
});
describe('POST /library', () => {
@@ -81,7 +56,7 @@ describe('/library', () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.External });
.send({ ownerId: admin.userId });
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
@@ -91,13 +66,12 @@ describe('/library', () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.External });
.send({ ownerId: admin.userId });
expect(status).toBe(201);
expect(body).toEqual(
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.External,
name: 'New External Library',
refreshedAt: null,
assetCount: 0,
@@ -113,7 +87,6 @@ describe('/library', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
type: LibraryType.External,
name: 'My Awesome Library',
importPaths: ['/path/to/import'],
exclusionPatterns: ['**/Raw/**'],
@@ -134,7 +107,6 @@ describe('/library', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
type: LibraryType.External,
name: 'My Awesome Library',
importPaths: ['/path', '/path'],
exclusionPatterns: ['**/Raw/**'],
@@ -150,7 +122,6 @@ describe('/library', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
type: LibraryType.External,
name: 'My Awesome Library',
importPaths: ['/path/to/import'],
exclusionPatterns: ['**/Raw/**', '**/Raw/**'],
@@ -159,60 +130,6 @@ describe('/library', () => {
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"]));
});
it('should create an upload library with defaults', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload });
expect(status).toBe(201);
expect(body).toEqual(
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.Upload,
name: 'New Upload Library',
refreshedAt: null,
assetCount: 0,
importPaths: [],
exclusionPatterns: [],
}),
);
});
it('should create an upload library with options', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload, name: 'My Awesome Library' });
expect(status).toBe(201);
expect(body).toEqual(
expect.objectContaining({
name: 'My Awesome Library',
}),
);
});
it('should not allow upload libraries to have import paths', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload, importPaths: ['/path/to/import'] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have import paths'));
});
it('should not allow upload libraries to have exclusion patterns', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload, exclusionPatterns: ['**/Raw/**'] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have exclusion patterns'));
});
});
describe('PUT /library/:id', () => {
@@ -332,10 +249,7 @@ describe('/library', () => {
});
it('should get library by id', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
});
const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
const { status, body } = await request(app)
.get(`/library/${library.id}`)
@@ -345,7 +259,6 @@ describe('/library', () => {
expect(body).toEqual(
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.External,
name: 'New External Library',
refreshedAt: null,
assetCount: 0,
@@ -373,24 +286,9 @@ describe('/library', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should not scan an upload library', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.Upload,
});
const { status, body } = await request(app)
.post(`/library/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Can only refresh external libraries'));
});
it('should scan external library', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp/directoryA`],
});
@@ -406,7 +304,6 @@ describe('/library', () => {
it('should scan external library with exclusion pattern', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
exclusionPatterns: ['**/directoryA'],
});
@@ -423,7 +320,6 @@ describe('/library', () => {
it('should scan multiple import paths', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp/directoryA`, `${testAssetDirInternal}/temp/directoryB`],
});
@@ -440,7 +336,6 @@ describe('/library', () => {
it('should pick up new files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -466,7 +361,6 @@ describe('/library', () => {
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -493,7 +387,6 @@ describe('/library', () => {
it('should scan new files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -521,7 +414,6 @@ describe('/library', () => {
it('should reimport modified files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -549,7 +441,6 @@ describe('/library', () => {
it('should not reimport unmodified files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -579,7 +470,6 @@ describe('/library', () => {
it('should reimport all files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -617,7 +507,6 @@ describe('/library', () => {
it('should remove offline files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -658,7 +547,6 @@ describe('/library', () => {
it('should not remove online files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -737,37 +625,8 @@ describe('/library', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should not delete the last upload library', async () => {
const libraries = await getAllLibraries(
{ $type: LibraryType.Upload },
{ headers: asBearerAuth(admin.accessToken) },
);
const adminLibraries = libraries.filter((library) => library.ownerId === admin.userId);
expect(adminLibraries.length).toBeGreaterThanOrEqual(1);
const lastLibrary = adminLibraries.pop() as LibraryResponseDto;
// delete all but the last upload library
for (const library of adminLibraries) {
const { status } = await request(app)
.delete(`/library/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
}
const { status, body } = await request(app)
.delete(`/library/${lastLibrary.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual(errorDto.noDeleteUploadLibrary);
expect(status).toBe(400);
});
it('should delete an external library', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
});
const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
const { status, body } = await request(app)
.delete(`/library/${library.id}`)
@@ -776,7 +635,7 @@ describe('/library', () => {
expect(status).toBe(204);
expect(body).toEqual({});
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) });
expect(libraries).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -789,7 +648,6 @@ describe('/library', () => {
it('should delete an external library with assets', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -803,7 +661,7 @@ describe('/library', () => {
expect(status).toBe(204);
expect(body).toEqual({});
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) });
expect(libraries).not.toEqual(
expect.arrayContaining([
expect.objectContaining({

View File

@@ -66,6 +66,7 @@ describe('/server-info', () => {
expect(body).toEqual({
smartSearch: false,
configFile: false,
duplicateDetection: false,
facialRecognition: false,
map: true,
reverseGeocoding: true,

View File

@@ -32,8 +32,7 @@ describe('/trash', () => {
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(before.length).toBeGreaterThanOrEqual(1);
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
@@ -57,14 +56,14 @@ describe('/trash', () => {
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await utils.getAssetInfo(admin.accessToken, assetId);
expect(before.isTrashed).toBe(true);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(false);
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(after).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: false })]);
});
});

View File

@@ -51,11 +51,6 @@ export const errorDto = {
statusCode: 400,
message: 'The server already has an admin',
},
noDeleteUploadLibrary: {
error: 'Bad Request',
statusCode: 400,
message: 'Cannot delete the last upload library',
},
};
export const signupResponseDto = {

View File

@@ -17,7 +17,7 @@ const setup = async () => {
child.stdout.on('data', (data) => {
const input = data.toString();
console.log(input);
if (input.includes('Immich Microservices is listening')) {
if (input.includes('Immich Microservices is running')) {
_resolve();
}
});

View File

@@ -16,7 +16,6 @@ import {
createPerson,
createSharedLink,
createUser,
defaults,
deleteAssets,
getAllAssets,
getAllJobsStatus,
@@ -24,6 +23,7 @@ import {
getConfigDefaults,
login,
searchMetadata,
setBaseUrl,
signUpAdmin,
updateAdminOnboarding,
updateAlbumUser,
@@ -145,7 +145,6 @@ export const utils = {
'sessions',
'users',
'system_metadata',
'system_config',
];
const sql: string[] = [];
@@ -256,8 +255,8 @@ export const utils = {
});
},
setApiEndpoint: () => {
defaults.baseUrl = app;
initSdk: () => {
setBaseUrl(app);
},
adminSetup: async (options?: AdminSetupOptions) => {
@@ -463,7 +462,7 @@ export const utils = {
},
};
utils.setApiEndpoint();
utils.initSdk();
if (!existsSync(`${testAssetDir}/albums`)) {
throw new Error(

View File

@@ -3,7 +3,7 @@ import { utils } from 'src/utils';
test.describe('Registration', () => {
test.beforeAll(() => {
utils.setApiEndpoint();
utils.initSdk();
});
test.beforeEach(async () => {

View File

@@ -17,7 +17,7 @@ test.describe('Shared Links', () => {
let sharedLinkPassword: SharedLinkResponseDto;
test.beforeAll(async () => {
utils.setApiEndpoint();
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken);

View File

@@ -1,6 +1,6 @@
ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:9df8be5280f00f39931fc2168309e82ddf252a7f30cda6593b7990fb0f5f59f9 as builder-cpu
FROM python:3.11-bookworm@sha256:96de1ea4821d73fd2c1853d1fdc3cf794ccfe2fae4c3f08579e846de51760a61 as builder-cpu
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as builder-openvino
USER root
@@ -40,6 +40,9 @@ FROM python:3.11-slim-bookworm@sha256:fc39d2e68b554c3f0a5cb8a776280c0b3d73b4c04b
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as prod-openvino
USER root
# TODO: remove this once the image has the fix for https://github.com/intel/compute-runtime/issues/710
ENV NEOReadDebugKeys=1 \
OverrideGpuAddressSpace=48
FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04@sha256:2d913b09e6be8387e1a10976933642c73c840c0b735f0bf3c28d97fc9bc422e0 as prod-cuda
@@ -74,8 +77,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \

View File

@@ -41,7 +41,7 @@ class Settings(BaseSettings):
class LogSettings(BaseSettings):
log_level: str = "info"
immich_log_level: str = "info"
no_color: bool = False
class Config:
@@ -77,7 +77,7 @@ LOG_LEVELS: dict[str, int] = {
settings = Settings()
log_settings = LogSettings()
LOG_LEVEL = LOG_LEVELS.get(log_settings.log_level.lower(), logging.INFO)
LOG_LEVEL = LOG_LEVELS.get(log_settings.immich_log_level.lower(), logging.INFO)
class CustomRichHandler(RichHandler):

View File

@@ -1,7 +1,6 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:abcb3ae7e3521d08e1fdeaff63131765b34e4f29b6a8a2c28660036b53841569 as builder
FROM mambaorg/micromamba:bookworm-slim@sha256:d5b82811074b396275ef69aadbf31098257dd8836e231371e9cdb393128e571c as builder
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \

View File

@@ -738,13 +738,13 @@ dotenv = ["python-dotenv"]
[[package]]
name = "flask-cors"
version = "4.0.0"
version = "4.0.1"
description = "A Flask extension adding a decorator for CORS support"
optional = false
python-versions = "*"
files = [
{file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"},
{file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"},
{file = "Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677"},
{file = "flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4"},
]
[package.dependencies]
@@ -1374,13 +1374,13 @@ files = [
[[package]]
name = "jinja2"
version = "3.1.3"
version = "3.1.4"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
]
[package.dependencies]

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.105.0"
version = "1.105.1"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -2,20 +2,20 @@
lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
# mimalloc seems to increase memory usage dramatically with openvino, need to investigate
if ! [ "$DEVICE" = "openvino" ]; then
if ! [ "$DEVICE" = "openvino" ]; then
export LD_PRELOAD="$lib_path"
export LD_BIND_NOW=1
fi
: "${MACHINE_LEARNING_HOST:=[::]}"
: "${MACHINE_LEARNING_PORT:=3003}"
: "${IMMICH_HOST:=[::]}"
: "${IMMICH_PORT:=3003}"
: "${MACHINE_LEARNING_WORKERS:=1}"
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
gunicorn app.main:app \
-k app.config.CustomUvicornWorker \
-b "$IMMICH_HOST":"$IMMICH_PORT" \
-w "$MACHINE_LEARNING_WORKERS" \
-b "$MACHINE_LEARNING_HOST":"$MACHINE_LEARNING_PORT" \
-t "$MACHINE_LEARNING_WORKER_TIMEOUT" \
--log-config-json log_conf.json \
--graceful-timeout 0

View File

@@ -1,3 +1,3 @@
{
"flutter": "3.19.6"
"flutter": "3.22.0"
}

View File

@@ -1,5 +1,5 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.19.3",
"dart.flutterSdkPath": ".fvm/versions/3.22.0",
"search.exclude": {
"**/.fvm": true
},

View File

@@ -10,16 +10,16 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.925.0)
aws-sdk-core (3.194.1)
aws-partitions (1.931.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.80.0)
aws-sdk-kms (1.81.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.149.1)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
@@ -155,7 +155,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.0)
multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
nkf (0.2.0)
@@ -169,7 +169,8 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@@ -182,6 +183,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)

View File

@@ -81,8 +81,8 @@ flutter {
}
dependencies {
def kotlin_version = '1.9.23'
def kotlin_coroutines_version = '1.8.0'
def kotlin_version = '1.9.24'
def kotlin_coroutines_version = '1.8.1'
def work_version = '2.9.0'
def concurrent_version = '1.1.0'
def guava_version = '33.2.0-android'

View File

@@ -1,4 +1,6 @@
allprojects {
ext.kotlin_version = '1.9.24'
repositories {
google()
mavenCentral()

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 139,
"android.injected.version.name" => "1.105.0",
"android.injected.version.code" => 140,
"android.injected.version.name" => "1.105.1",
}
)
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

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000344">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000374">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="52.933903">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="84.292464">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="31.026779">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="33.336934">
</testcase>

Binary file not shown.

View File

@@ -19,8 +19,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.4.2" apply false
id "org.jetbrains.kotlin.android" version "1.9.23" apply false
id "org.jetbrains.kotlin.kapt" version "1.9.23" apply false
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
id "org.jetbrains.kotlin.kapt" version "1.9.24" apply false
}
include ":app"

View File

@@ -391,6 +391,7 @@
"setting_image_viewer_original_title": "Original laden",
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
"setting_image_viewer_preview_title": "Vorschaubild laden",
"setting_image_viewer_title": "Bilder",
"setting_languages_apply": "Anwenden",
"setting_languages_title": "Sprachen",
"setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}",
@@ -406,6 +407,9 @@
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
"setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung",
"setting_pages_app_bar_settings": "Einstellungen",
"setting_video_viewer_looping_subtitle": "Aktivieren, damit sich ein Video in der Detailansicht automatisch wiederholt.",
"setting_video_viewer_looping_title": "Wiederholen",
"setting_video_viewer_title": "Videos",
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
"share_add": "Hinzufügen",
"share_add_photos": "Fotos hinzufügen",

View File

@@ -391,6 +391,7 @@
"setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_title": "Images",
"setting_languages_apply": "Apply",
"setting_languages_title": "Languages",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
@@ -406,6 +407,9 @@
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings",
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
"setting_video_viewer_looping_title": "Looping",
"setting_video_viewer_title": "Videos",
"settings_require_restart": "Please restart Immich to apply this setting",
"share_add": "Add",
"share_add_photos": "Add photos",

View File

@@ -10,16 +10,16 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.925.0)
aws-sdk-core (3.194.1)
aws-partitions (1.931.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.80.0)
aws-sdk-kms (1.81.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.149.1)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
@@ -155,7 +155,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.0)
multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
nkf (0.2.0)
@@ -169,7 +169,8 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@@ -182,6 +183,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)

View File

@@ -159,7 +159,7 @@ SPEC CHECKSUMS:
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
integration_test: 13825b8a9334a850581300559b8839134b124670
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9

View File

@@ -383,7 +383,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 156;
CURRENT_PROJECT_VERSION = 157;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -525,7 +525,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 156;
CURRENT_PROJECT_VERSION = 157;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -553,7 +553,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 156;
CURRENT_PROJECT_VERSION = 157;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -58,11 +58,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.104.0</string>
<string>1.105.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>156</string>
<string>157</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.105.0"
version_number: "1.105.1"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000406">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.020864">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="37.890919">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.917777">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="7.615129">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.283943">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.535245">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.944748">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="213.966277">
<testcase classname="fastlane.lanes" name="4: build_app" time="215.971639">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="80.742201">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="76.674601">
</testcase>

View File

@@ -182,6 +182,7 @@ enum StoreKey<T> {
advancedTroubleshooting<bool>(114, type: bool),
logLevel<int>(115, type: int),
preferRemoteImage<bool>(116, type: bool),
loopVideo<bool>(117, type: bool),
// map related settings
mapShowFavoriteOnly<bool>(118, type: bool),
mapRelativeDate<int>(119, type: int),

View File

@@ -54,7 +54,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
],
),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_new_rounded),
),
),

View File

@@ -191,7 +191,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
title: const Text(

View File

@@ -7,16 +7,16 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/backup/backup_state.model.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart';
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
import 'package:immich_mobile/models/backup/backup_state.model.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/providers/websocket.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/backup/backup_info_card.dart';
import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart';
@RoutePage()
class BackupControllerPage extends HookConsumerWidget {
@@ -260,7 +260,7 @@ class BackupControllerPage extends HookConsumerWidget {
leading: IconButton(
onPressed: () {
ref.watch(websocketProvider.notifier).listenUploadEvent();
context.popRoute(true);
context.maybePop(true);
},
splashRadius: 24,
icon: const Icon(

View File

@@ -13,7 +13,7 @@ class BackupOptionsPage extends StatelessWidget {
elevation: 0,
title: const Text("backup_options_page_title").tr(),
leading: IconButton(
onPressed: () => context.popRoute(true),
onPressed: () => context.maybePop(true),
splashRadius: 24,
icon: const Icon(
Icons.arrow_back_ios_rounded,

View File

@@ -23,7 +23,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
),
leading: IconButton(
onPressed: () {
context.popRoute(true);
context.maybePop(true);
},
splashRadius: 24,
icon: const Icon(

View File

@@ -26,7 +26,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
final sharedUsersList = useState<Set<User>>({});
addNewUsersHandler() {
context.popRoute(sharedUsersList.value.map((e) => e.id).toList());
context.maybePop(sharedUsersList.value.map((e) => e.id).toList());
}
buildTileIcon(User user) {
@@ -55,10 +55,9 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
child: Chip(
backgroundColor: context.primaryColor.withOpacity(0.15),
label: Text(
user.email,
user.name,
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
@@ -88,13 +87,20 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
itemBuilder: ((context, index) {
return ListTile(
leading: buildTileIcon(users[index]),
dense: true,
title: Text(
users[index].email,
users[index].name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
users[index].email,
style: const TextStyle(
fontSize: 12,
),
),
onTap: () {
if (sharedUsersList.value.contains(users[index])) {
sharedUsersList.value = sharedUsersList.value
@@ -127,7 +133,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () {
context.popRoute(null);
context.maybePop(null);
},
),
actions: [

View File

@@ -184,7 +184,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () => context.popRoute(null),
onPressed: () => context.maybePop(null),
),
centerTitle: true,
title: Text("translated_text_options".tr()),

View File

@@ -36,7 +36,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
// ref.watch(assetSelectionProvider.notifier).removeAll();
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
context.popRoute(true);
context.maybePop(true);
context
.navigateTo(const TabControllerRoute(children: [SharingRoute()]));
}
@@ -152,7 +152,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () async {
context.popRoute();
context.maybePop();
},
),
actions: [

View File

@@ -105,7 +105,7 @@ class AppLogPage extends HookConsumerWidget {
],
leading: IconButton(
onPressed: () {
context.popRoute();
context.maybePop();
},
icon: const Icon(
Icons.arrow_back_ios_new_rounded,

View File

@@ -3,16 +3,16 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/album/album_title.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/album/album_action_outlined_button.dart';
import 'package:immich_mobile/widgets/album/album_title_text_field.dart';
import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
@RoutePage()
// ignore: must_be_immutable
@@ -216,7 +216,7 @@ class CreateAlbumPage extends HookConsumerWidget {
leading: IconButton(
onPressed: () {
selectedAssets.value = {};
context.popRoute();
context.maybePop();
},
icon: const Icon(Icons.close_rounded),
),

View File

@@ -2,32 +2,33 @@ import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
import 'package:immich_mobile/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart';
import 'package:immich_mobile/widgets/asset_viewer/gallery_app_bar.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/widgets/common/immich_image.dart';
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart';
import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart';
import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart';
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart' show ThumbnailFormat;
@@ -59,6 +60,7 @@ class GalleryViewerPage extends HookConsumerWidget {
final settings = ref.watch(appSettingsServiceProvider);
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
final shouldLoopVideo = useState(AppSettingsEnum.loopVideo.defaultValue);
final isZoomed = useState(false);
final isPlayingVideo = useState(false);
final localPosition = useState<Offset?>(null);
@@ -101,6 +103,8 @@ class GalleryViewerPage extends HookConsumerWidget {
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
isLoadOriginal.value =
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
shouldLoopVideo.value =
settings.getSetting<bool>(AppSettingsEnum.loopVideo);
return null;
},
[],
@@ -174,7 +178,7 @@ class GalleryViewerPage extends HookConsumerWidget {
final ratio = d.dy / max(d.dx.abs(), 1);
if (d.dy > sensitivity && ratio > ratioThreshold) {
context.popRoute();
context.maybePop();
} else if (d.dy < -sensitivity && ratio < -ratioThreshold) {
showInfo();
}
@@ -261,12 +265,9 @@ class GalleryViewerPage extends HookConsumerWidget {
}
return PopScope(
canPop: false,
onPopInvoked: (_) {
// Change immersive mode back to normal "edgeToEdge" mode
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
context.pop();
},
// Change immersive mode back to normal "edgeToEdge" mode
onPopInvoked: (_) =>
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge),
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(
@@ -370,6 +371,7 @@ class GalleryViewerPage extends HookConsumerWidget {
key: ValueKey(a),
asset: a,
isMotionVideo: a.livePhotoVideoId != null,
loopVideo: shouldLoopVideo.value,
placeholder: Image(
image: provider,
fit: BoxFit.contain,

View File

@@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
import 'package:immich_mobile/widgets/settings/image_viewer_quality_setting.dart';
import 'package:immich_mobile/widgets/settings/language_settings.dart';
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/preference_setting.dart';
@@ -33,7 +33,7 @@ enum SettingSection {
SettingSection.preferences => const PreferenceSetting(),
SettingSection.backup => const BackupSettings(),
SettingSection.timeline => const AssetListSettings(),
SettingSection.viewer => const ImageViewerQualitySetting(),
SettingSection.viewer => const AssetViewerSettings(),
SettingSection.advanced => const AdvancedSettings(),
};

View File

@@ -1,18 +1,15 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_controller_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/widgets/asset_viewer/video_player.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
@RoutePage()
// ignore: must_be_immutable
class VideoViewerPage extends HookConsumerWidget {
final Asset asset;
final bool isMotionVideo;
@@ -20,6 +17,7 @@ class VideoViewerPage extends HookConsumerWidget {
final Duration hideControlsTimer;
final bool showControls;
final bool showDownloadingIndicator;
final bool loopVideo;
const VideoViewerPage({
super.key,
@@ -29,6 +27,7 @@ class VideoViewerPage extends HookConsumerWidget {
this.showControls = true,
this.hideControlsTimer = const Duration(seconds: 5),
this.showDownloadingIndicator = true,
this.loopVideo = false,
});
@override
@@ -76,7 +75,9 @@ class VideoViewerPage extends HookConsumerWidget {
// Also sets the error if there is an error in the playback
void updateVideoPlayback() {
final videoPlayback = VideoPlaybackValue.fromController(controller);
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
if (!loopVideo) {
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
}
final state = videoPlayback.state;
// Enable the WakeLock while the video is playing
@@ -156,6 +157,7 @@ class VideoViewerPage extends HookConsumerWidget {
hideControlsTimer: hideControlsTimer,
showControls: showControls,
showDownloadingIndicator: showDownloadingIndicator,
loopVideo: loopVideo,
),
),
],

View File

@@ -17,7 +17,7 @@ class ArchivePage extends HookConsumerWidget {
final count = archivedAssets.value?.totalAssets.toString() ?? "?";
return AppBar(
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
centerTitle: true,

View File

@@ -15,7 +15,7 @@ class FavoritesPage extends HookConsumerWidget {
AppBar buildAppBar() {
return AppBar(
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
centerTitle: true,

View File

@@ -143,7 +143,7 @@ class TrashPage extends HookConsumerWidget {
return AppBar(
leading: IconButton(
onPressed: !selectionEnabledHook.value
? () => context.popRoute()
? () => context.maybePop()
: () {
selectionEnabledHook.value = false;
selection.value = {};

View File

@@ -176,7 +176,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
),
TextButton(
child: const Text('permission_onboarding_back').tr(),
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
),
],
),

View File

@@ -3,14 +3,14 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/models/memories/memory.model.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/widgets/common/immich_image.dart';
import 'package:immich_mobile/widgets/memories/memory_bottom_info.dart';
import 'package:immich_mobile/widgets/memories/memory_card.dart';
import 'package:immich_mobile/widgets/memories/memory_epilogue.dart';
import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/widgets/common/immich_image.dart';
@RoutePage()
class MemoryPage extends HookConsumerWidget {
@@ -153,7 +153,7 @@ class MemoryPage extends HookConsumerWidget {
final offset = notification.metrics.pixels;
if (isEpiloguePage &&
(offset > notification.metrics.maxScrollExtent + 150)) {
context.popRoute();
context.maybePop();
return true;
}
}
@@ -256,7 +256,7 @@ class MemoryPage extends HookConsumerWidget {
// auto_route doesn't invoke pop scope, so
// turn off full screen mode here
// https://github.com/Milad-Akarie/auto_route_library/issues/1799
context.popRoute();
context.maybePop();
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge,
);

View File

@@ -18,7 +18,7 @@ class AllMotionPhotosPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text('motion_photos_page_title').tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -21,7 +21,7 @@ class AllPeoplePage extends HookConsumerWidget {
'all_people_page_title',
).tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -22,7 +22,7 @@ class AllPlacesPage extends HookConsumerWidget {
'curated_location_page_title',
).tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -15,7 +15,7 @@ class AllVideosPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text('all_videos_page_title').tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -41,7 +41,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
}
void onClose([LatLng? selected]) {
context.popRoute(selected);
context.maybePop(selected);
}
Future<void> getCurrentLocation() async {

View File

@@ -102,7 +102,7 @@ class PersonResultPage extends HookConsumerWidget {
appBar: AppBar(
title: Text(name.value),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
actions: [

View File

@@ -18,7 +18,7 @@ class RecentlyAddedPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text('recently_added_page_title').tr(),
leading: IconButton(
onPressed: () => context.popRoute(),
onPressed: () => context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -4,9 +4,11 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart';
@@ -15,8 +17,6 @@ import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dar
import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart';
import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
import 'package:openapi/api.dart';
@RoutePage()
@@ -480,9 +480,7 @@ class SearchInputPage extends HookConsumerWidget {
],
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () {
context.router.pop();
},
onPressed: () => context.router.maybePop(),
),
title: TextField(
controller: textSearchController,

View File

@@ -328,7 +328,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
alignment: Alignment.bottomRight,
child: ElevatedButton(
onPressed: () {
context.popRoute();
context.maybePop();
},
child: const Text(
"share_done",
@@ -431,7 +431,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
changeExpiry: changeExpiry,
);
ref.invalidate(sharedLinksStateProvider);
context.popRoute();
context.maybePop();
}
return Scaffold(

View File

@@ -1,35 +1,32 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/memories/memory.model.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/pages/common/activities.page.dart';
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
import 'package:immich_mobile/pages/common/album_options.page.dart';
import 'package:immich_mobile/pages/common/album_viewer.page.dart';
import 'package:immich_mobile/pages/common/album_asset_selection.page.dart';
import 'package:immich_mobile/pages/common/create_album.page.dart';
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
import 'package:immich_mobile/pages/common/album_additional_shared_user_selection.page.dart';
import 'package:immich_mobile/pages/common/album_shared_user_selection.page.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/routing/custom_transition_builders.dart';
import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/routing/backup_permission_guard.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/album.entity.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/logger_message.entity.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/pages/common/app_log_detail.page.dart';
import 'package:immich_mobile/pages/common/app_log.page.dart';
import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart';
import 'package:immich_mobile/models/memories/memory.model.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
import 'package:immich_mobile/pages/backup/album_preview.page.dart';
import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart';
import 'package:immich_mobile/pages/backup/backup_controller.page.dart';
import 'package:immich_mobile/pages/backup/backup_options.page.dart';
import 'package:immich_mobile/pages/backup/failed_backup_status.page.dart';
import 'package:immich_mobile/pages/common/activities.page.dart';
import 'package:immich_mobile/pages/common/album_additional_shared_user_selection.page.dart';
import 'package:immich_mobile/pages/common/album_asset_selection.page.dart';
import 'package:immich_mobile/pages/common/album_options.page.dart';
import 'package:immich_mobile/pages/common/album_shared_user_selection.page.dart';
import 'package:immich_mobile/pages/common/album_viewer.page.dart';
import 'package:immich_mobile/pages/common/app_log.page.dart';
import 'package:immich_mobile/pages/common/app_log_detail.page.dart';
import 'package:immich_mobile/pages/common/create_album.page.dart';
import 'package:immich_mobile/pages/common/gallery_viewer.page.dart';
import 'package:immich_mobile/pages/common/settings.page.dart';
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
import 'package:immich_mobile/pages/library/archive.page.dart';
import 'package:immich_mobile/pages/library/favorite.page.dart';
import 'package:immich_mobile/pages/library/library.page.dart';
@@ -43,21 +40,23 @@ import 'package:immich_mobile/pages/search/all_motion_videos.page.dart';
import 'package:immich_mobile/pages/search/all_people.page.dart';
import 'package:immich_mobile/pages/search/all_places.page.dart';
import 'package:immich_mobile/pages/search/all_videos.page.dart';
import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart';
import 'package:immich_mobile/pages/search/map/map.page.dart';
import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart';
import 'package:immich_mobile/pages/search/person_result.page.dart';
import 'package:immich_mobile/pages/search/recently_added.page.dart';
import 'package:immich_mobile/pages/search/search_input.page.dart';
import 'package:immich_mobile/pages/search/search.page.dart';
import 'package:immich_mobile/pages/common/settings.page.dart';
import 'package:immich_mobile/pages/sharing/partner/partner_detail.page.dart';
import 'package:immich_mobile/pages/search/search_input.page.dart';
import 'package:immich_mobile/pages/sharing/partner/partner.page.dart';
import 'package:immich_mobile/pages/sharing/shared_link/shared_link_edit.page.dart';
import 'package:immich_mobile/pages/sharing/partner/partner_detail.page.dart';
import 'package:immich_mobile/pages/sharing/shared_link/shared_link.page.dart';
import 'package:immich_mobile/pages/sharing/shared_link/shared_link_edit.page.dart';
import 'package:immich_mobile/pages/sharing/sharing.page.dart';
import 'package:immich_mobile/pages/common/splash_screen.page.dart';
import 'package:immich_mobile/pages/common/tab_controller.page.dart';
import 'package:immich_mobile/pages/common/video_viewer.page.dart';
import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/routing/backup_permission_guard.dart';
import 'package:immich_mobile/routing/custom_transition_builders.dart';
import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:isar/isar.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@@ -120,10 +119,6 @@ class AppRouter extends _$AppRouter {
guards: [_authGuard, _duplicateGuard],
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
),
AutoRoute(
page: VideoViewerRoute.page,
guards: [_authGuard, _duplicateGuard],
),
AutoRoute(
page: BackupControllerRoute.page,
guards: [_authGuard, _duplicateGuard, _backupPermissionGuard],

View File

@@ -21,6 +21,29 @@ abstract class _$AppRouter extends RootStackRouter {
child: const ActivitiesPage(),
);
},
AlbumAdditionalSharedUserSelectionRoute.name: (routeData) {
final args =
routeData.argsAs<AlbumAdditionalSharedUserSelectionRouteArgs>();
return AutoRoutePage<List<String>?>(
routeData: routeData,
child: AlbumAdditionalSharedUserSelectionPage(
key: args.key,
album: args.album,
),
);
},
AlbumAssetSelectionRoute.name: (routeData) {
final args = routeData.argsAs<AlbumAssetSelectionRouteArgs>();
return AutoRoutePage<AssetSelectionPageResult?>(
routeData: routeData,
child: AlbumAssetSelectionPage(
key: args.key,
existingAssets: args.existingAssets,
canDeselect: args.canDeselect,
query: args.query,
),
);
},
AlbumOptionsRoute.name: (routeData) {
final args = routeData.argsAs<AlbumOptionsRouteArgs>();
return AutoRoutePage<dynamic>(
@@ -41,6 +64,16 @@ abstract class _$AppRouter extends RootStackRouter {
),
);
},
AlbumSharedUserSelectionRoute.name: (routeData) {
final args = routeData.argsAs<AlbumSharedUserSelectionRouteArgs>();
return AutoRoutePage<List<String>>(
routeData: routeData,
child: AlbumSharedUserSelectionPage(
key: args.key,
assets: args.assets,
),
);
},
AlbumViewerRoute.name: (routeData) {
final args = routeData.argsAs<AlbumViewerRouteArgs>();
return AutoRoutePage<dynamic>(
@@ -97,18 +130,6 @@ abstract class _$AppRouter extends RootStackRouter {
child: const ArchivePage(),
);
},
AlbumAssetSelectionRoute.name: (routeData) {
final args = routeData.argsAs<AssetSelectionRouteArgs>();
return AutoRoutePage<AssetSelectionPageResult?>(
routeData: routeData,
child: AlbumAssetSelectionPage(
key: args.key,
existingAssets: args.existingAssets,
canDeselect: args.canDeselect,
query: args.query,
),
);
},
BackupAlbumSelectionRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
@@ -170,12 +191,6 @@ abstract class _$AppRouter extends RootStackRouter {
),
);
},
PhotosRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const PhotosPage(),
);
},
LibraryRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
@@ -249,6 +264,12 @@ abstract class _$AppRouter extends RootStackRouter {
),
);
},
PhotosRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const PhotosPage(),
);
},
RecentlyAddedRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
@@ -272,26 +293,6 @@ abstract class _$AppRouter extends RootStackRouter {
child: const SearchPage(),
);
},
AlbumAdditionalSharedUserSelectionRoute.name: (routeData) {
final args = routeData.argsAs<SelectAdditionalUserForSharingRouteArgs>();
return AutoRoutePage<List<String>?>(
routeData: routeData,
child: AlbumAdditionalSharedUserSelectionPage(
key: args.key,
album: args.album,
),
);
},
AlbumSharedUserSelectionRoute.name: (routeData) {
final args = routeData.argsAs<SelectUserForSharingRouteArgs>();
return AutoRoutePage<List<String>>(
routeData: routeData,
child: AlbumSharedUserSelectionPage(
key: args.key,
assets: args.assets,
),
);
},
SettingsRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
@@ -351,21 +352,6 @@ abstract class _$AppRouter extends RootStackRouter {
child: const TrashPage(),
);
},
VideoViewerRoute.name: (routeData) {
final args = routeData.argsAs<VideoViewerRouteArgs>();
return AutoRoutePage<dynamic>(
routeData: routeData,
child: VideoViewerPage(
key: args.key,
asset: args.asset,
isMotionVideo: args.isMotionVideo,
placeholder: args.placeholder,
showControls: args.showControls,
hideControlsTimer: args.hideControlsTimer,
showDownloadingIndicator: args.showDownloadingIndicator,
),
);
},
};
}
@@ -383,6 +369,94 @@ class ActivitiesRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [AlbumAdditionalSharedUserSelectionPage]
class AlbumAdditionalSharedUserSelectionRoute
extends PageRouteInfo<AlbumAdditionalSharedUserSelectionRouteArgs> {
AlbumAdditionalSharedUserSelectionRoute({
Key? key,
required Album album,
List<PageRouteInfo>? children,
}) : super(
AlbumAdditionalSharedUserSelectionRoute.name,
args: AlbumAdditionalSharedUserSelectionRouteArgs(
key: key,
album: album,
),
initialChildren: children,
);
static const String name = 'AlbumAdditionalSharedUserSelectionRoute';
static const PageInfo<AlbumAdditionalSharedUserSelectionRouteArgs> page =
PageInfo<AlbumAdditionalSharedUserSelectionRouteArgs>(name);
}
class AlbumAdditionalSharedUserSelectionRouteArgs {
const AlbumAdditionalSharedUserSelectionRouteArgs({
this.key,
required this.album,
});
final Key? key;
final Album album;
@override
String toString() {
return 'AlbumAdditionalSharedUserSelectionRouteArgs{key: $key, album: $album}';
}
}
/// generated route for
/// [AlbumAssetSelectionPage]
class AlbumAssetSelectionRoute
extends PageRouteInfo<AlbumAssetSelectionRouteArgs> {
AlbumAssetSelectionRoute({
Key? key,
required Set<Asset> existingAssets,
bool canDeselect = false,
required QueryBuilder<Asset, Asset, QAfterSortBy>? query,
List<PageRouteInfo>? children,
}) : super(
AlbumAssetSelectionRoute.name,
args: AlbumAssetSelectionRouteArgs(
key: key,
existingAssets: existingAssets,
canDeselect: canDeselect,
query: query,
),
initialChildren: children,
);
static const String name = 'AlbumAssetSelectionRoute';
static const PageInfo<AlbumAssetSelectionRouteArgs> page =
PageInfo<AlbumAssetSelectionRouteArgs>(name);
}
class AlbumAssetSelectionRouteArgs {
const AlbumAssetSelectionRouteArgs({
this.key,
required this.existingAssets,
this.canDeselect = false,
required this.query,
});
final Key? key;
final Set<Asset> existingAssets;
final bool canDeselect;
final QueryBuilder<Asset, Asset, QAfterSortBy>? query;
@override
String toString() {
return 'AlbumAssetSelectionRouteArgs{key: $key, existingAssets: $existingAssets, canDeselect: $canDeselect, query: $query}';
}
}
/// generated route for
/// [AlbumOptionsPage]
class AlbumOptionsRoute extends PageRouteInfo<AlbumOptionsRouteArgs> {
@@ -459,6 +533,45 @@ class AlbumPreviewRouteArgs {
}
}
/// generated route for
/// [AlbumSharedUserSelectionPage]
class AlbumSharedUserSelectionRoute
extends PageRouteInfo<AlbumSharedUserSelectionRouteArgs> {
AlbumSharedUserSelectionRoute({
Key? key,
required Set<Asset> assets,
List<PageRouteInfo>? children,
}) : super(
AlbumSharedUserSelectionRoute.name,
args: AlbumSharedUserSelectionRouteArgs(
key: key,
assets: assets,
),
initialChildren: children,
);
static const String name = 'AlbumSharedUserSelectionRoute';
static const PageInfo<AlbumSharedUserSelectionRouteArgs> page =
PageInfo<AlbumSharedUserSelectionRouteArgs>(name);
}
class AlbumSharedUserSelectionRouteArgs {
const AlbumSharedUserSelectionRouteArgs({
this.key,
required this.assets,
});
final Key? key;
final Set<Asset> assets;
@override
String toString() {
return 'AlbumSharedUserSelectionRouteArgs{key: $key, assets: $assets}';
}
}
/// generated route for
/// [AlbumViewerPage]
class AlbumViewerRoute extends PageRouteInfo<AlbumViewerRouteArgs> {
@@ -619,54 +732,6 @@ class ArchiveRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [AlbumAssetSelectionPage]
class AlbumAssetSelectionRoute extends PageRouteInfo<AssetSelectionRouteArgs> {
AlbumAssetSelectionRoute({
Key? key,
required Set<Asset> existingAssets,
bool canDeselect = false,
required QueryBuilder<Asset, Asset, QAfterSortBy>? query,
List<PageRouteInfo>? children,
}) : super(
AlbumAssetSelectionRoute.name,
args: AssetSelectionRouteArgs(
key: key,
existingAssets: existingAssets,
canDeselect: canDeselect,
query: query,
),
initialChildren: children,
);
static const String name = 'AlbumAssetSelectionRoute';
static const PageInfo<AssetSelectionRouteArgs> page =
PageInfo<AssetSelectionRouteArgs>(name);
}
class AssetSelectionRouteArgs {
const AssetSelectionRouteArgs({
this.key,
required this.existingAssets,
this.canDeselect = false,
required this.query,
});
final Key? key;
final Set<Asset> existingAssets;
final bool canDeselect;
final QueryBuilder<Asset, Asset, QAfterSortBy>? query;
@override
String toString() {
return 'AssetSelectionRouteArgs{key: $key, existingAssets: $existingAssets, canDeselect: $canDeselect, query: $query}';
}
}
/// generated route for
/// [BackupAlbumSelectionPage]
class BackupAlbumSelectionRoute extends PageRouteInfo<void> {
@@ -852,20 +917,6 @@ class GalleryViewerRouteArgs {
}
}
/// generated route for
/// [PhotosPage]
class PhotosRoute extends PageRouteInfo<void> {
const PhotosRoute({List<PageRouteInfo>? children})
: super(
PhotosRoute.name,
initialChildren: children,
);
static const String name = 'PhotosRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [LibraryPage]
class LibraryRoute extends PageRouteInfo<void> {
@@ -1097,6 +1148,20 @@ class PersonResultRouteArgs {
}
}
/// generated route for
/// [PhotosPage]
class PhotosRoute extends PageRouteInfo<void> {
const PhotosRoute({List<PageRouteInfo>? children})
: super(
PhotosRoute.name,
initialChildren: children,
);
static const String name = 'PhotosRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [RecentlyAddedPage]
class RecentlyAddedRoute extends PageRouteInfo<void> {
@@ -1163,84 +1228,6 @@ class SearchRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [AlbumAdditionalSharedUserSelectionPage]
class AlbumAdditionalSharedUserSelectionRoute
extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> {
AlbumAdditionalSharedUserSelectionRoute({
Key? key,
required Album album,
List<PageRouteInfo>? children,
}) : super(
AlbumAdditionalSharedUserSelectionRoute.name,
args: SelectAdditionalUserForSharingRouteArgs(
key: key,
album: album,
),
initialChildren: children,
);
static const String name = 'AlbumAdditionalSharedUserSelectionRoute';
static const PageInfo<SelectAdditionalUserForSharingRouteArgs> page =
PageInfo<SelectAdditionalUserForSharingRouteArgs>(name);
}
class SelectAdditionalUserForSharingRouteArgs {
const SelectAdditionalUserForSharingRouteArgs({
this.key,
required this.album,
});
final Key? key;
final Album album;
@override
String toString() {
return 'SelectAdditionalUserForSharingRouteArgs{key: $key, album: $album}';
}
}
/// generated route for
/// [AlbumSharedUserSelectionPage]
class AlbumSharedUserSelectionRoute
extends PageRouteInfo<SelectUserForSharingRouteArgs> {
AlbumSharedUserSelectionRoute({
Key? key,
required Set<Asset> assets,
List<PageRouteInfo>? children,
}) : super(
AlbumSharedUserSelectionRoute.name,
args: SelectUserForSharingRouteArgs(
key: key,
assets: assets,
),
initialChildren: children,
);
static const String name = 'AlbumSharedUserSelectionRoute';
static const PageInfo<SelectUserForSharingRouteArgs> page =
PageInfo<SelectUserForSharingRouteArgs>(name);
}
class SelectUserForSharingRouteArgs {
const SelectUserForSharingRouteArgs({
this.key,
required this.assets,
});
final Key? key;
final Set<Asset> assets;
@override
String toString() {
return 'SelectUserForSharingRouteArgs{key: $key, assets: $assets}';
}
}
/// generated route for
/// [SettingsPage]
class SettingsRoute extends PageRouteInfo<void> {
@@ -1410,66 +1397,3 @@ class TrashRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [VideoViewerPage]
class VideoViewerRoute extends PageRouteInfo<VideoViewerRouteArgs> {
VideoViewerRoute({
Key? key,
required Asset asset,
bool isMotionVideo = false,
Widget? placeholder,
bool showControls = true,
Duration hideControlsTimer = const Duration(seconds: 5),
bool showDownloadingIndicator = true,
List<PageRouteInfo>? children,
}) : super(
VideoViewerRoute.name,
args: VideoViewerRouteArgs(
key: key,
asset: asset,
isMotionVideo: isMotionVideo,
placeholder: placeholder,
showControls: showControls,
hideControlsTimer: hideControlsTimer,
showDownloadingIndicator: showDownloadingIndicator,
),
initialChildren: children,
);
static const String name = 'VideoViewerRoute';
static const PageInfo<VideoViewerRouteArgs> page =
PageInfo<VideoViewerRouteArgs>(name);
}
class VideoViewerRouteArgs {
const VideoViewerRouteArgs({
this.key,
required this.asset,
this.isMotionVideo = false,
this.placeholder,
this.showControls = true,
this.hideControlsTimer = const Duration(seconds: 5),
this.showDownloadingIndicator = true,
});
final Key? key;
final Asset asset;
final bool isMotionVideo;
final Widget? placeholder;
final bool showControls;
final Duration hideControlsTimer;
final bool showDownloadingIndicator;
@override
String toString() {
return 'VideoViewerRouteArgs{key: $key, asset: $asset, isMotionVideo: $isMotionVideo, placeholder: $placeholder, showControls: $showControls, hideControlsTimer: $hideControlsTimer, showDownloadingIndicator: $showDownloadingIndicator}';
}
}

View File

@@ -46,6 +46,7 @@ enum AppSettingsEnum<T> {
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -21,7 +21,6 @@ import 'package:openapi/api.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:cancellation_token_http/http.dart' as http;
import 'package:path/path.dart' as p;
final backupServiceProvider = Provider(
(ref) => BackupService(
@@ -33,6 +32,7 @@ final backupServiceProvider = Provider(
class BackupService {
final httpClient = http.Client();
final _fileDownloader = FileDownloader();
final ApiService _apiService;
final Isar _db;
final Logger _log = Logger("BackupService");
@@ -242,121 +242,127 @@ class BackupService {
)
: assetList.toList();
final tasks = <UploadTask>[];
for (var entity in assetsToUpload) {
File? file;
File? livePhotoFile;
final isAvailableLocally =
await entity.isLocallyAvailable(isOrigin: true);
try {
final isAvailableLocally =
await entity.isLocallyAvailable(isOrigin: true);
// Handle getting files from iCloud
if (!isAvailableLocally && Platform.isIOS) {
// Skip iCloud assets if the user has disabled this feature
if (isIgnoreIcloudAssets) {
continue;
}
setCurrentUploadAssetCb(
CurrentUploadAsset(
id: entity.id,
fileCreatedAt: entity.createDateTime.year == 1970
? entity.modifiedDateTime
: entity.createDateTime,
fileName: await entity.titleAsync,
fileType: _getAssetType(entity.type),
iCloudAsset: true,
),
);
file = await entity.loadFile(progressHandler: pmProgressHandler);
livePhotoFile = await entity.loadFile(
withSubtype: true,
progressHandler: pmProgressHandler,
);
} else {
if (entity.type == AssetType.video) {
file = await entity.originFile;
} else {
file = await entity.originFile.timeout(const Duration(seconds: 5));
if (entity.isLivePhoto) {
livePhotoFile = await entity.originFileWithSubtype
.timeout(const Duration(seconds: 5));
}
}
// Handle getting files from iCloud
if (!isAvailableLocally && Platform.isIOS) {
// Skip iCloud assets if the user has disabled this feature
if (isIgnoreIcloudAssets) {
continue;
}
if (file != null) {
String originalFileName = await entity.titleAsync;
var fileStream = file.openRead();
var assetRawUploadData = http.MultipartFile(
"assetData",
fileStream,
file.lengthSync(),
filename: originalFileName,
);
setCurrentUploadAssetCb(
CurrentUploadAsset(
id: entity.id,
fileCreatedAt: entity.createDateTime.year == 1970
? entity.modifiedDateTime
: entity.createDateTime,
fileName: await entity.titleAsync,
fileType: _getAssetType(entity.type),
iCloudAsset: true,
),
);
}
var req = MultipartRequest(
'POST',
Uri.parse('$savedEndpoint/asset/upload'),
onProgress: ((bytes, totalBytes) =>
uploadProgressCb(bytes, totalBytes)),
);
req.headers["x-immich-user-token"] = Store.get(StoreKey.accessToken);
req.headers["Transfer-Encoding"] = "chunked";
final files = [];
// TODO: This is silly to have to load the file just to access the path
// But there doesn't seem to be any other way to do it
final fileName = (await entity.originFile)?.path;
files.add(fileName);
req.fields['deviceAssetId'] = entity.id;
req.fields['deviceId'] = deviceId;
req.fields['fileCreatedAt'] =
entity.createDateTime.toUtc().toIso8601String();
req.fields['fileModifiedAt'] =
entity.modifiedDateTime.toUtc().toIso8601String();
req.fields['isFavorite'] = entity.isFavorite.toString();
req.fields['duration'] = entity.videoDuration.toString();
if (entity.isLivePhoto) {
final livePhotoFileName = (await entity.originFileWithSubtype)?.path;
if (livePhotoFileName != null) {
files.add(livePhotoFileName);
}
}
req.files.add(assetRawUploadData);
final url = '$savedEndpoint/asset/upload';
final headers = {
'x-immich-user-token': Store.get(StoreKey.accessToken),
'Transfer-Encoding': 'chunked',
};
var fileSize = file.lengthSync();
final fields = {
'deviceAssetId': entity.id,
'deviceId': deviceId,
'fileCreatedAt': entity.createDateTime.toUtc().toIso8601String(),
'fileModifiedAt': entity.modifiedDateTime.toUtc().toIso8601String(),
'isFavorite': entity.isFavorite.toString(),
'duration': entity.videoDuration.toString(),
};
if (entity.isLivePhoto) {
if (livePhotoFile != null) {
final livePhotoTitle = p.setExtension(
originalFileName,
p.extension(livePhotoFile.path),
);
final fileStream = livePhotoFile.openRead();
final livePhotoRawUploadData = http.MultipartFile(
"livePhotoData",
fileStream,
livePhotoFile.lengthSync(),
filename: livePhotoTitle,
);
req.files.add(livePhotoRawUploadData);
fileSize += livePhotoFile.lengthSync();
} else {
_log.warning(
"Failed to obtain motion part of the livePhoto - $originalFileName",
);
}
}
for (final file in files) {
final split = file.split('/');
final name = split.last;
final directory = split.take(split.length - 1).join('/');
setCurrentUploadAssetCb(
CurrentUploadAsset(
id: entity.id,
fileCreatedAt: entity.createDateTime.year == 1970
? entity.modifiedDateTime
: entity.createDateTime,
fileName: originalFileName,
fileType: _getAssetType(entity.type),
fileSize: fileSize,
iCloudAsset: false,
),
);
final task = UploadTask(
url: url,
group: 'backup',
fileField: 'assetData',
taskId: entity.id,
fields: fields,
headers: headers,
updates: Updates.statusAndProgress,
retries: 0,
httpRequestMethod: 'POST',
displayName: 'Immich',
filename: name,
directory: directory,
baseDirectory: BaseDirectory.root,
);
tasks.add(task);
}
}
var response =
await httpClient.send(req, cancellationToken: cancelToken);
final permission = await _fileDownloader.permissions
.status(PermissionType.androidSharedStorage);
print('has permission $permission');
if (response.statusCode == 200) {
if (tasks.length == 1) {
final result = await _fileDownloader.upload(
tasks.first,
onProgress: (percent) => print('${percent * 100} done'),
onStatus: (status) => print('status $status'),
onElapsedTime: (t) => print('time is $t'),
elapsedTimeInterval: const Duration(seconds: 1),
);
print('$result is done with ${result.status}');
print('result ${result.responseBody}');
print('result ${result.responseHeaders}');
} else {
final result = await _fileDownloader.uploadBatch(
tasks,
batchProgressCallback: (succeeded, failed) =>
print('$succeeded succeeded, $failed failed'),
taskStatusCallback: (status) => print('status $status'),
taskProgressCallback: (update) => print('update $update'),
onElapsedTime: (t) => print('time is $t'),
elapsedTimeInterval: const Duration(seconds: 1),
);
print(
'$result is done with ${result.succeeded.length} succeeded and ${result.failed.length} failed',
);
for (final task in result.succeeded) {
final r = result.results[task];
print('successful task $task with result $r');
}
for (final task in result.failed) {
final r = result.results[task];
print('failed task $task with result $r');
}
}
/*
if (result.status == 200) {
// asset is a duplicate (already exists on the server)
duplicatedAssetIds.add(entity.id);
uploadSuccessCb(entity.id, deviceId, true);
@@ -409,6 +415,7 @@ class BackupService {
}
}
}
*/
if (duplicatedAssetIds.isNotEmpty) {
await _saveDuplicatedAssetIds(duplicatedAssetIds);
}

View File

@@ -17,6 +17,7 @@ ChewieController useChewieController({
bool allowFullScreen = false,
bool allowedScreenSleep = false,
bool showControls = true,
bool loopVideo = false,
Widget? customControls,
Widget? placeholder,
Duration hideControlsTimer = const Duration(seconds: 1),
@@ -36,6 +37,7 @@ ChewieController useChewieController({
hideControlsTimer: hideControlsTimer,
showControlsOnInitialize: showControlsOnInitialize,
showControls: showControls,
loopVideo: loopVideo,
allowedScreenSleep: allowedScreenSleep,
onPlaying: onPlaying,
onPaused: onPaused,
@@ -53,6 +55,7 @@ class _ChewieControllerHook extends Hook<ChewieController> {
final bool allowFullScreen;
final bool allowedScreenSleep;
final bool showControls;
final bool loopVideo;
final Widget? customControls;
final Widget? placeholder;
final Duration hideControlsTimer;
@@ -71,6 +74,7 @@ class _ChewieControllerHook extends Hook<ChewieController> {
this.allowFullScreen = false,
this.allowedScreenSleep = false,
this.showControls = true,
this.loopVideo = false,
this.customControls,
this.placeholder,
this.hideControlsTimer = const Duration(seconds: 3),
@@ -94,6 +98,7 @@ class _ChewieControllerHookState
allowFullScreen: hook.allowFullScreen,
allowedScreenSleep: hook.allowedScreenSleep,
showControls: hook.showControls,
looping: hook.loopVideo,
customControls: hook.customControls,
placeholder: hook.placeholder,
hideControlsTimer: hook.hideControlsTimer,

View File

@@ -121,12 +121,12 @@ final ThemeData immichLightTheme = ThemeData(
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: Colors.indigo.withOpacity(0.15),
iconTheme: MaterialStatePropertyAll(
iconTheme: WidgetStatePropertyAll(
IconThemeData(color: Colors.grey[700]),
),
backgroundColor: immichBackgroundColor,
surfaceTintColor: Colors.transparent,
labelTextStyle: MaterialStatePropertyAll(
labelTextStyle: WidgetStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
@@ -249,12 +249,12 @@ final ThemeData immichDarkTheme = ThemeData(
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
iconTheme: MaterialStatePropertyAll(
iconTheme: WidgetStatePropertyAll(
IconThemeData(color: Colors.grey[500]),
),
backgroundColor: Colors.grey[900],
surfaceTintColor: Colors.transparent,
labelTextStyle: MaterialStatePropertyAll(
labelTextStyle: WidgetStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,

View File

@@ -275,7 +275,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
);
} else {
return IconButton(
onPressed: () async => await context.popRoute(),
onPressed: () async => await context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
splashRadius: 25,
);

View File

@@ -110,7 +110,7 @@ class BottomGalleryBar extends ConsumerWidget {
if (isDeleted && isParent) {
if (totalAssets == 1) {
// Handle only one asset
context.popRoute();
context.maybePop();
} else {
// Go to next page otherwise
controller.nextPage(
@@ -181,7 +181,7 @@ class BottomGalleryBar extends ConsumerWidget {
stackElements.elementAt(stackIndex),
);
ctx.pop();
context.popRoute();
context.maybePop();
},
title: const Text(
"viewer_stack_use_as_main_asset",
@@ -208,7 +208,7 @@ class BottomGalleryBar extends ConsumerWidget {
childrenToRemove: [asset],
);
ctx.pop();
context.popRoute();
context.maybePop();
} else {
await ref.read(assetStackServiceProvider).updateStack(
asset,
@@ -236,7 +236,7 @@ class BottomGalleryBar extends ConsumerWidget {
childrenToRemove: stack,
);
ctx.pop();
context.popRoute();
context.maybePop();
},
title: const Text(
"viewer_unstack",
@@ -267,7 +267,7 @@ class BottomGalleryBar extends ConsumerWidget {
handleArchive() {
ref.read(assetProvider.notifier).toggleArchive([asset]);
if (isParent) {
context.popRoute();
context.maybePop();
return;
}
removeAssetFromStack();

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