Compare commits

..

139 Commits

Author SHA1 Message Date
Alex The Bot c174f0e871 Version v1.90.0 2023-12-07 18:32:45 +00:00
shenlong 3581069c2b fix(mobile): do not show loading overlay on grid refresh (#5531)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-07 12:21:07 -06:00
shenlong c5504aae6e refactor(mobile): album sort (#5510)
* refactor: migrate album sort option to provider

* refactor: use sort order in add to album sheet list

* test(mobile): album_sort_options_provider unit tests

* refactor: sort shared albums with user selected sort

* refactor: use listview to render shared albums

* refactor: rename to AlbumSortByOptions

* refactor: remove filtering inside sort functions

* font size

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-07 18:14:09 +00:00
Alex 2e59b07cc6 feat(mobile): handle backup iCloud asset (#5508)
* feat(mobile): handle backup iCloud asset

* additional state

* Download progress

* Added a separate page for backup options

* handle ingore iCloud asset upload'

* fix init backup service

* PR feedback

* fix negative count

* get file title
2023-12-07 09:53:15 -06:00
Fynn Petersen-Frey c25556bb08 feat(mobile): unify asset grid multiselect actions (#5407)
* feat(mobile): unify asset grid multiselect actions

* add favorite & archive page

* show edit date&place on main photos screen

* Reposition exit button

* Sort favorite with the same order as other view

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-07 09:38:22 -06:00
dependabot[bot] b9a9a3956c chore(deps): bump actions/setup-python from 4 to 5 (#5528)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 08:14:19 -05:00
shenlong e1739ac4fc fix(mobile): allow editing asset dates in the future (#5522)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-06 20:04:03 -06:00
martin 8736c77f7a fix(web): align all edit buttons and not correctly rounded buttons on detail-panel (#5524)
* fix: align all pencils

* fix: format
2023-12-06 20:03:28 -06:00
James Keane 338a028185 fix(server): awaitsendFile (#5515)
Fixes the intermittent EPIPE errors that myself and others are seeing.

By explicitly returning a promise we ensure the caller correctly waits until the `sendFile` is complete before potentially closing or cleaning the socket. This is the most likely bug that would cause EPIPE errors.

Fix was confirmed on a live system -- would benefit from a unit test
though.
2023-12-06 20:51:51 -05:00
shenlong e2d0e944eb chore(renovate): ignore openapi pubspec (#5521)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-06 14:21:57 -06:00
shenlong f53b70571b fix: notify mobile app when live photos are linked (#5504)
* fix(mobile): album thumbnail list tile overflow on large album title

* fix: notify clients about live photo linked event

* refactor: notify clients during meta extraction

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-06 08:56:09 -06:00
renovate[bot] 2814de4420 chore(deps): update dependency vite to v4.5.1 [security] (#5513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 22:14:39 -05:00
martin 024fe1141b fix(web): background when re-assigning faces (#5512) 2023-12-05 23:05:22 +00:00
shenlong 086a957a2b feat(mobile): edit date time & location (#5461)
* chore: text correction

* fix: update activities stat only when the widget is mounted

* feat(mobile): edit date time

* feat(mobile): edit location

* chore(build): update gradle wrapper - 7.6.3

* style: dropdownmenu styling

* style: wrap locationpicker in singlechildscrollview

* test: add unit test for getTZAdjustedTimeAndOffset

* pr changes

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-05 13:34:37 -06:00
Alex 84c5b08c25 feat(web): UI/UX improvement for date time edit form (#5505) 2023-12-05 14:16:37 -05:00
renovate[bot] 7e8488694d chore(deps): update web (#5502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 11:20:07 -06:00
renovate[bot] 231b89c9c0 chore(deps): update postgres docker digest to 6dfee32 (#5492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 10:17:33 -06:00
shenlong d5f6584e1d fix(mobile): use zoomedpagetransition for galleryvieweroute (#5495)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-05 09:45:04 -06:00
martin 7702560b12 feat(web): re-assign person faces (2) (#4949)
* feat: unassign person faces

* multiple improvements

* chore: regenerate api

* feat: improve face interactions in photos

* fix: tests

* fix: tests

* optimize

* fix: wrong assignment on complex-multiple re-assignments

* fix: thumbnails with large photos

* fix: complex reassign

* fix: don't send people with faces

* fix: person thumbnail generation

* chore: regenerate api

* add tess

* feat: face box even when zoomed

* fix: change feature photo

* feat: make the blue icon hoverable

* chore: regenerate api

* feat: use websocket

* fix: loading spinner when clicking on the done button

* fix: use the svelte way

* fix: tests

* simplify

* fix: unused vars

* fix: remove unused code

* fix: add migration

* chore: regenerate api

* ci: add unit tests

* chore: regenerate api

* feat: if a new person is created for a face and the server takes more than 15 seconds to generate the person thumbnail, don't wait for it

* reorganize

* chore: regenerate api

* feat: global edit

* pr feedback

* pr feedback

* simplify

* revert test

* fix: face generation

* fix: tests

* fix: face generation

* fix merge

* feat: search names in unmerge face selector modal

* fix: merge face selector

* simplify feature photo generation

* fix: change endpoint

* pr feedback

* chore: fix merge

* chore: fix merge

* fix: tests

* fix: edit & hide buttons

* fix: tests

* feat: show if person is hidden

* feat: rename face to person

* feat: split in new panel

* copy-paste-error

* pr feedback

* fix: feature photo

* do not leak faces

* fix: unmerge modal

* fix: merge modal event

* feat(server): remove duplicates

* fix: title for image thumbnails

* fix: disable side panel when there's no face until next PR

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-05 09:43:15 -06:00
Clement Ong 982183600d feat(web): clear failed jobs (#5423)
* add clear failed jobs button

* refactor: clean up code

* chore: open api

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-12-05 02:07:20 +00:00
Dan Taylor 933c24ea6f fix(web): delete modal z-index (#5416)
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-12-05 01:32:33 +00:00
Jason Rasmussen 05e9697dff fix(web): runtime issue (#5493) 2023-12-04 20:29:35 -05:00
renovate[bot] 259700c45f chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to e296d47 (#5487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 19:37:08 -05:00
Jason Rasmussen 22d79850f6 refactor(web): asset viewer actions (#5488) 2023-12-04 19:18:28 -05:00
renovate[bot] 56aed8246d chore(deps): update python:3.11-slim-bookworm docker digest to cc75851 (#5462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 13:26:41 -06:00
Quek ca1be71bca fix(mobile): new album icon has different height to existing album cover (#5422) 2023-12-04 13:26:17 -06:00
martin 6111bf157e fix(web): stick action bar on search (#5459) 2023-12-04 10:24:19 -06:00
martin 2195730fa6 fix(web): keep url query parameters when swapping people (#5468) 2023-12-04 10:23:14 -06:00
waclaw66 1dc832d392 fix(web): new album title fix (#5467)
* new album title fix

* Naming

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-04 16:22:31 +00:00
PyKen 1a63d3837e fix(server): Return correct asset count in album (#5465)
* fix(server): Return correct asset count in album

* Update album.repository.sql

Add generated sql
2023-12-04 10:22:00 -06:00
Fynn Petersen-Frey bdbaa166d9 fix(mobile): clear album provider on logout (#5477) 2023-12-04 10:21:05 -06:00
shenlong 812e67d55d fix(server): send upload_success notification only for non hidden assets (#5471)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-12-03 16:35:22 -06:00
martin dfd6846deb fix(server): video orientation (#5455)
* fix: video orientation

* pr feedback
2023-12-03 16:34:23 -06:00
renovate[bot] 6d3421a505 chore(deps): update postgres:14-alpine docker digest to 6a0e352 (#5451)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-02 18:54:30 +00:00
renovate[bot] ede9de146a chore(deps): update base-image to v20231201 (#5452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-02 18:52:10 +00:00
renovate[bot] 6959cf689b chore(deps): update python:3.11-bookworm docker digest to ba7a7ac (#5438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-02 18:35:23 +00:00
Alex 36ba48b8ae chore(web): toggle hide face in context menu (#5440) 2023-12-02 09:23:11 -06:00
Thomas 8a2b36ad55 docs(readme): correct wording and grammar in README_de_DE.md (#5447)
* fix wording and grammar in de_DE readme

* change Du+Eure to uppercase
2023-12-02 13:44:28 +01:00
Daniel Dietzler 6000c7f3bc update docs to use docker compose (#5446) 2023-12-02 12:48:49 +01:00
Michael Manganiello 5aa658de59 chore(server): Check asset permissions in bulk (#5329)
Modify Access repository, to evaluate `asset` permissions in bulk.
Queries have been validated to match what they currently generate for single ids.

Queries:

* `asset` album access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "albums" "AlbumEntity"
    LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets"
      ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId"="AlbumEntity"."id"
    LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets"
      ON "AlbumEntity__AlbumEntity_assets"."id"="AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId"
      AND "AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL
    LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"
      ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id"
    LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers"
      ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
      AND "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
  WHERE
    (
      ("AlbumEntity"."ownerId" = $1 AND "AlbumEntity__AlbumEntity_assets"."id" = $2)
      OR ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $3 AND "AlbumEntity__AlbumEntity_assets"."id" = $4)
      OR ("AlbumEntity"."ownerId" = $5 AND "AlbumEntity__AlbumEntity_assets"."livePhotoVideoId" = $6)
      OR ("AlbumEntity__AlbumEntity_sharedUsers"."id" = $7 AND "AlbumEntity__AlbumEntity_assets"."livePhotoVideoId" = $8)
    )
    AND "AlbumEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT
  "asset"."id" AS "assetId",
  "asset"."livePhotoVideoId" AS "livePhotoVideoId"
FROM "albums" "album"
  INNER JOIN "albums_assets_assets" "album_asset"
    ON "album_asset"."albumsId"="album"."id"
  INNER JOIN "assets" "asset"
    ON "asset"."id"="album_asset"."assetsId"
    AND "asset"."deletedAt" IS NULL
  LEFT JOIN "albums_shared_users_users" "album_sharedUsers"
    ON "album_sharedUsers"."albumsId"="album"."id"
  LEFT JOIN "users" "sharedUsers"
    ON "sharedUsers"."id"="album_sharedUsers"."usersId"
    AND "sharedUsers"."deletedAt" IS NULL
WHERE
  (
    "album"."ownerId" = $1
    OR "sharedUsers"."id" = $2
  )
  AND (
    "asset"."id" IN ($3, $4)
    OR "asset"."livePhotoVideoId" IN ($5, $6)
  )
  AND "album"."deletedAt" IS NULL
```

* `asset` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "assets" "AssetEntity"
  WHERE
    "AssetEntity"."id" = $1
    AND "AssetEntity"."ownerId" = $2
)
LIMIT 1

-- After
SELECT
  "AssetEntity"."id" AS "AssetEntity_id"
FROM "assets" "AssetEntity"
WHERE
  "AssetEntity"."id" IN ($1, $2)
  AND "AssetEntity"."ownerId" = $3
```

* `asset` partner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "partners" "PartnerEntity"
    LEFT JOIN "users" "PartnerEntity__PartnerEntity_sharedWith"
      ON "PartnerEntity__PartnerEntity_sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__PartnerEntity_sharedWith"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__PartnerEntity_sharedBy"
      ON "PartnerEntity__PartnerEntity_sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__PartnerEntity_sharedBy"."deletedAt" IS NULL
    LEFT JOIN "assets" "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"
      ON "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."ownerId"="PartnerEntity__PartnerEntity_sharedBy"."id"
      AND "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedBy"
      ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__sharedBy"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedWith"
      ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__sharedWith"."deletedAt" IS NULL
  WHERE
    "PartnerEntity__PartnerEntity_sharedWith"."id" = $1
    AND "0aabe9f4a62b794e2c24a074297e534f51a4ac6c"."id" = $2
)
LIMIT 1

-- After
SELECT
  "asset"."id" AS "assetId"
FROM "partners" "partner"
  INNER JOIN "users" "sharedBy"
    ON "sharedBy"."id"="partner"."sharedById"
    AND "sharedBy"."deletedAt" IS NULL
  INNER JOIN "assets" "asset"
    ON "asset"."ownerId"="sharedBy"."id"
    AND "asset"."deletedAt" IS NULL
WHERE
  "partner"."sharedWithId" = $1
  AND "asset"."id" IN ($2, $3)
```

* `asset` shared link access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "shared_links" "SharedLinkEntity"
    LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album"
      ON "SharedLinkEntity__SharedLinkEntity_album"."id"="SharedLinkEntity"."albumId"
      AND "SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL
    LEFT JOIN "albums_assets_assets" "760f12c00d97bdcec1ce224d1e3bf449859942b6"
      ON "760f12c00d97bdcec1ce224d1e3bf449859942b6"."albumsId"="SharedLinkEntity__SharedLinkEntity_album"."id"
    LEFT JOIN "assets" "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"
      ON "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id"="760f12c00d97bdcec1ce224d1e3bf449859942b6"."assetsId"
      AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" IS NULL
    LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"
      ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId"="SharedLinkEntity"."id"
    LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets"
      ON "SharedLinkEntity__SharedLinkEntity_assets"."id"="SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId"
      AND "SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL
  WHERE (
    ("SharedLinkEntity"."id" = $1 AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" = $2)
    OR ("SharedLinkEntity"."id" = $3 AND "SharedLinkEntity__SharedLinkEntity_assets"."id" = $4)
    OR ("SharedLinkEntity"."id" = $5 AND "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" = $6)
    OR ("SharedLinkEntity"."id" = $7 AND "SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" = $8)
  )
)
LIMIT 1

-- After
SELECT
  "assets"."id" AS "assetId",
  "assets"."livePhotoVideoId" AS "assetLivePhotoVideoId",
  "albumAssets"."id" AS "albumAssetId",
  "albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId"
FROM "shared_links" "sharedLink"
  LEFT JOIN "albums" "album"
    ON "album"."id"="sharedLink"."albumId"
    AND "album"."deletedAt" IS NULL
  LEFT JOIN "shared_link__asset" "assets_sharedLink"
    ON "assets_sharedLink"."sharedLinksId"="sharedLink"."id"
  LEFT JOIN "assets" "assets"
    ON "assets"."id"="assets_sharedLink"."assetsId"
    AND "assets"."deletedAt" IS NULL
  LEFT JOIN "albums_assets_assets" "album_albumAssets"
    ON "album_albumAssets"."albumsId"="album"."id"
  LEFT JOIN "assets" "albumAssets"
    ON "albumAssets"."id"="album_albumAssets"."assetsId"
    AND "albumAssets"."deletedAt" IS NULL
WHERE
  "sharedLink"."id" = $1
  AND (
    "assets"."id" IN ($2, $3)
    OR "albumAssets"."id" IN ($4, $5)
    OR "assets"."livePhotoVideoId" IN ($6, $7)
    OR "albumAssets"."livePhotoVideoId" IN ($8, $9)
  )
```
2023-12-02 02:56:41 +00:00
Dan Taylor 6673f1eb24 fix(web): status box rendering (#5410)
* fix(web): status box rendering

* Syntax improvement for api import

Co-authored-by: martin <74269598+martabal@users.noreply.github.com>

---------

Co-authored-by: Daniel Taylor <daniel.k.taylor1@gmail.com>
Co-authored-by: martin <74269598+martabal@users.noreply.github.com>
2023-12-01 20:26:48 -06:00
Andrew Brock a02e91169d feat(web): Allow showing hidden people in image asset details view (#5420)
* Allow showing hidden people in image asset details view

This makes it possible to easily find people/faces that have accidentally been hidden.
Unhiding them still requires clicking on the person to go to their page to unhide them.

* Update web/src/lib/components/asset-viewer/detail-panel.svelte

Co-authored-by: martin <74269598+martabal@users.noreply.github.com>

---------

Co-authored-by: brokeh <git@brocky.net>
Co-authored-by: martin <74269598+martabal@users.noreply.github.com>
2023-12-01 20:17:05 -06:00
martin ec92608024 fix(web): disable metadata edit if user is not owner (#5415)
* fix(web): disable metadata edit if user is not owner

* pr feedback

* pr feedback

* get data from page data

* fix: better representation

* feat: warn user if there's issues with the selected assets

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-12-01 14:58:24 -06:00
Andrew Brock 5a50d32748 chore: make running local dev instance on Windows work regardless of git config (#5419)
The only issue was that line endings of shell scripts may be \r\n when cloned on Windows with autocrlf and shells don't like that.

Co-authored-by: brokeh <git@brocky.net>
2023-12-01 20:30:34 +00:00
renovate[bot] cbdcbd3ab4 chore(deps): update python:3.11-slim-bookworm docker digest to 8f82989 (#5435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 20:24:50 +00:00
renovate[bot] cb00d45e3d chore(deps): update python:3.11-bookworm docker digest to 47c1829 (#5434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 20:13:23 +00:00
Jason Rasmussen 2b2b1bba63 chore(deps): base image min age (#5433) 2023-12-01 14:42:21 -05:00
renovate[bot] 031420bc78 chore(deps): update base-image to v20231130 (#5431)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 14:27:42 -05:00
renovate[bot] 387faa13d5 chore(deps): update redis:6.2-alpine docker digest to 60e49e2 (#5430)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 19:25:21 +00:00
renovate[bot] 6979d43650 chore(deps): update postgres:14-alpine docker digest to 5491670 (#5429)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 19:25:08 +00:00
renovate[bot] af8bb132d0 chore(deps): update dependency @types/node to v20.10.1 (#5427)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 18:44:22 +00:00
renovate[bot] 40964187bb chore(deps): update python:3.11-slim-bookworm docker digest to 23f5220 (#5388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 18:36:54 +00:00
renovate[bot] fe3d951f26 chore(deps): update python:3.11-bookworm docker digest to c56b0c6 (#5387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-01 18:36:30 +00:00
Jason Rasmussen 6e365b37db fix(server): immich command (#5408)
* fix: immich command

* chore: use absolute paths
2023-11-30 14:59:47 -06:00
Jason Rasmussen 5e55a17b2a chore(server): sql versioning (#5346)
* chore(server): sql versioning

* chore: always add newline to end of file

* refactor: generator

* chore: pr feedback

* chore: pr feedback
2023-11-30 10:10:30 -05:00
dependabot[bot] ffecfbe075 chore(deps): bump actions/setup-java from 3 to 4 (#5405)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-30 09:03:24 -05:00
YFrendo 644e52b153 feat: Edit metadata (#5066)
* chore: rebase and clean-up

* feat: sync description, add e2e tests

* feat: simplify web code

* chore: unit tests

* fix: linting

* Bug fix with the arrows key

* timezone typeahead filter

timezone typeahead filter

* small stlying

* format fix

* Bug fix in the map selection

Bug fix in the map selection

* Websocket basic

Websocket basic

* Update metadata visualisation through the websocket

* Update timeline

* fix merge

* fix web

* fix web

* maplibre system

* format fix

* format fix

* refactor: clean up

* Fix small bug in the hour/timezone

* Don't diplay modify for readOnly asset

* Add log in case of failure

* Formater + try/catch error

* Remove everything related to websocket

* Revert "Remove everything related to websocket"

This reverts commit 14bcb9e1e4.

* remove notification

* fix test

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-30 03:52:28 +00:00
martin b396e0eee3 fix(web): whitespace in person name (#5401)
* fix(web): whitespace in person name

* pr feedback
2023-11-29 21:08:54 -06:00
Alex The Bot 8b6a79ad9e Version v1.89.0 2023-11-29 19:20:24 +00:00
Thomas 696900228b fix(web): use native image decoder (#3074)
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-29 15:09:22 +00:00
Jason Rasmussen e5d083fe79 chore(deps): remove unused cookie type (#5385) 2023-11-29 04:24:38 +00:00
Jason Rasmussen d4b3fb942f feat(web): job concurrency order (#5386) 2023-11-29 04:24:20 +00:00
shenlong 527d602a9f refactor(mobile): immich loading overlay (#5320)
* refactor: dcm fixes

* refactor: ImmichLoadingOverlay to custom hook

* chore: dart fixes

* pr changes

* fix: process overlay add / remove in postframecallback

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-29 04:20:00 +00:00
shenlong 513f252a0c refactor(mobile): log asyncvalue errors (#5327)
* refactor: scaffoldwhen to log errors during scaffold body render

* refactor: onError and onLoading scaffoldbody

* refactor: more scaffold body to custom extension

* refactor: add skiploadingonrefresh

* Snackbar color

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-29 04:17:29 +00:00
renovate[bot] 0fe704c6f9 fix(deps): update dependency clsx to v2 (#5383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-29 04:07:05 +00:00
avaness 5a2fc20b20 fix(mobile): server endpoint input auto parse https when not specified (#5326)
This fixes issue #4397 and automatically adds the https protocol to the server endpoint url if it is missing

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-29 04:02:19 +00:00
renovate[bot] 2a45ad147c chore(deps): update server (#5380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-28 23:00:25 -05:00
renovate[bot] fa3f2237eb chore(deps): update @immich/cli to v6.13.1 (#5379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-28 22:59:45 -05:00
renovate[bot] 6aa356e69f chore(deps): update web (#5381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-28 22:57:20 -05:00
Jason Rasmussen a04360f625 chore: fix min release age (#5382) 2023-11-28 22:46:31 -05:00
Jason Rasmussen 48c9e66ae5 chore: update job names (#5375) 2023-11-28 22:07:47 -05:00
renovate[bot] 05ca555b6e chore(deps): update dependency typescript to v5 (#5265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-28 22:06:09 -05:00
Jason Rasmussen 2bb75b6aa9 chore: add renovate grouping for docs and consolidate schedule (#5368) 2023-11-28 21:32:21 -05:00
renovate[bot] 869d400617 chore(deps): update dependency prettier to v3 (#5371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-29 02:30:53 +00:00
Jason Rasmussen 6ae7a92e03 fix(docs): build (#5374) 2023-11-29 02:27:01 +00:00
renovate[bot] a67f57c0e0 chore(deps): pin postgres docker tag to 71da05d (#5370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-29 02:13:36 +00:00
renovate[bot] b04cd4edee chore(deps): pin dependencies (#5369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-29 02:12:35 +00:00
renovate[bot] cd1b6e6976 chore(deps): update typescript-eslint monorepo to v6 (#5343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-28 21:11:43 -05:00
renovate[bot] 1fa5e220a1 chore(deps): pin dependencies (#5367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-29 02:09:11 +00:00
Thariq Shanavas b21b7f0721 chore(docs): add guide on remote access (#5350)
* Added guide on remote access

* update reverse proxy section

* chore: formatting

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-29 00:49:36 +00:00
Jason Rasmussen 21ed8d5c79 chore(web): run as root in dev (#5365) 2023-11-28 17:52:37 -05:00
Jason Rasmussen 6ac4e98d4b chore(web): handle signals for faster restart (#5366) 2023-11-28 17:50:43 -05:00
shenlong b0db8ed6c4 style(mobile): asset selection animation (#5322)
* refactor: replace navigator.pop with context.pop

* mobile: use demo instance credentials for test login

* style(mobile): centre align add partner button

* mobile: add thumbnail selection animation

* differnt gesture for test instance

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-28 22:05:13 +00:00
bo0tzz 6522707b49 chore(renovate): Only update to node LTS versions (#5355)
* chore(ci): Use LTS codenames for node base images

* chore(renovate): Only match node LTS versions

* chore(renovate): Pin docker digests
2023-11-28 14:50:48 -06:00
Jason Rasmussen 9483c456d4 fix(web): storage decimals (#5363) 2023-11-28 15:50:23 -05:00
Emanuel Bennici 5781ae9d82 feat(web): Lazy load thumbnails on the people page (#5356)
* feat(web): Lazy load thumbnails on the people page

Instead of loading all people thumbnails at once, only the first few
should be loaded eagerly.
This reduces the load on client and server side.

* chore: change name

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-28 20:23:27 +00:00
Jason Rasmussen 4d727708e2 chore(web): curly braces (#5362) 2023-11-28 14:16:27 -06:00
Jason Rasmussen 5c1c174db1 chore(server): curly braces (#5361) 2023-11-28 15:09:20 -05:00
Daniel Dietzler cffdd9c86a web and proxy no more! (#5347) 2023-11-27 22:59:40 +00:00
Jason Rasmussen ebd64ded62 feat(web): prefer higher GiB values (#5340) 2023-11-27 12:40:29 -06:00
Jason Rasmussen 0758d55dea chore(deps): remove unused package (#5337) 2023-11-27 11:48:07 -05:00
Jason Rasmussen 3992119e32 fix(deps): prettier 3 (#5336) 2023-11-27 11:42:04 -05:00
bo0tzz 87871e4df9 chore: fix renovate minimumReleaseAge (#5331) 2023-11-27 10:07:16 +01:00
bo0tzz ef45e9f490 chore(renovate): Add per-group schedule (#5319) 2023-11-26 21:21:32 -06:00
Alex 4e5eef129d fix(server): get album's assets in getAlbumInfo route (#5325)
* fix(server): get album's assets in getAlbumInfo route

* unit test

* test: e2e tests
2023-11-26 21:21:04 -06:00
martin 034b308ddc feat(web): search names when merging faces (#5209)
* feat: search names when merging faces

* fix: reactive

* styling

* small stlying

* remove unused variable

* fix: reactive

* feat: reset

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-26 12:58:09 -06:00
martin 3aa2927dae fix(web): sorting options for albums (#5233)
* fix: albums

* pr feedback

* fix: current behavior

* rename

* fix: album metadatas

* fix: tests

* fix: e2e test

* simplify

* fix: cover shared links

* rename function

* merge main

* merge main

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-26 15:23:43 +00:00
Michael Manganiello c04340c63e chore(server): Check more permissions in bulk (#5315)
Modify Access repository, to evaluate `authDevice`, `library`, `partner`,
`person`, and `timeline` permissions in bulk.
Queries have been validated to match what they currently generate for
single ids.

As an extra performance improvement, we now use a custom QueryBuilder
for the Partners queries, to avoid the eager relationships that add
unneeded `LEFT JOIN` clauses. We only filter based on the ids present in
the `partners` table, so those joins can be avoided.

Queries:

* `library` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "libraries" "LibraryEntity"
  WHERE
    "LibraryEntity"."id" = $1
    AND "LibraryEntity"."ownerId" = $2
    AND "LibraryEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT "LibraryEntity"."id" AS "LibraryEntity_id"
FROM "libraries" "LibraryEntity"
WHERE
  "LibraryEntity"."id" IN ($1, $2)
  AND "LibraryEntity"."ownerId" = $3
  AND "LibraryEntity"."deletedAt" IS NULL
```

* `library` partner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "partners" "PartnerEntity"
    LEFT JOIN "users" "PartnerEntity__sharedBy"
      ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__sharedBy"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedWith"
      ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__sharedWith"."deletedAt" IS NULL
  WHERE
    "PartnerEntity"."sharedWithId" = $1
    AND "PartnerEntity"."sharedById" = $2
)
LIMIT 1

-- After
SELECT
  "partner"."sharedById" AS "partner_sharedById",
  "partner"."sharedWithId" AS "partner_sharedWithId"
FROM "partners" "partner"
WHERE
  "partner"."sharedById" IN ($1, $2)
  AND "partner"."sharedWithId" = $3
```

* `authDevice` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "user_token" "UserTokenEntity"
  WHERE
    "UserTokenEntity"."userId" = $1
    AND "UserTokenEntity"."id" = $2
)
LIMIT 1

-- After
SELECT "UserTokenEntity"."id" AS "UserTokenEntity_id"
FROM "user_token" "UserTokenEntity"
WHERE
  "UserTokenEntity"."userId" = $1
  AND "UserTokenEntity"."id" IN ($2, $3)
```

* `timeline` partner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "partners" "PartnerEntity"
    LEFT JOIN "users" "PartnerEntity__sharedBy"
      ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__sharedBy"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedWith"
      ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__sharedWith"."deletedAt" IS NULL
  WHERE
    "PartnerEntity"."sharedWithId" = $1
    AND "PartnerEntity"."sharedById" = $2
)
LIMIT 1

-- After
SELECT
  "partner"."sharedById" AS "partner_sharedById",
  "partner"."sharedWithId" AS "partner_sharedWithId"
FROM "partners" "partner"
WHERE
  "partner"."sharedById" IN ($1, $2)
  AND "partner"."sharedWithId" = $3
```

* `person` owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "person" "PersonEntity"
  WHERE
    "PersonEntity"."id" = $1
    AND "PersonEntity"."ownerId" = $2
)
LIMIT 1

-- After
SELECT "PersonEntity"."id" AS "PersonEntity_id"
FROM "person" "PersonEntity"
WHERE
  "PersonEntity"."id" IN ($1, $2)
  AND "PersonEntity"."ownerId" = $3
```

* `partner` update access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "partners" "PartnerEntity"
    LEFT JOIN "users" "PartnerEntity__sharedBy"
      ON "PartnerEntity__sharedBy"."id"="PartnerEntity"."sharedById"
      AND "PartnerEntity__sharedBy"."deletedAt" IS NULL
    LEFT JOIN "users" "PartnerEntity__sharedWith"
      ON "PartnerEntity__sharedWith"."id"="PartnerEntity"."sharedWithId"
      AND "PartnerEntity__sharedWith"."deletedAt" IS NULL
  WHERE
    "PartnerEntity"."sharedWithId" = $1
    AND "PartnerEntity"."sharedById" = $2
)
LIMIT 1

-- After
SELECT
  "partner"."sharedById" AS "partner_sharedById",
  "partner"."sharedWithId" AS "partner_sharedWithId"
FROM "partners" "partner"
WHERE
  "partner"."sharedById" IN ($1, $2)
  AND "partner"."sharedWithId" = $3
```
2023-11-26 07:50:41 -05:00
renovate[bot] f97dca7707 chore(deps): update web (#5239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-26 00:14:06 -06:00
Romon Wafa cf58649a99 Change wording (#5312) 2023-11-26 00:12:51 -06:00
shenlong e65d1d5930 refactor: mobile - send livephoto as a separate request (#5275)
* refactor: mobile - send livephoto as a separate request

* fix: create new request for live asset

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-25 21:45:18 -06:00
renovate[bot] ad06502539 chore(deps): update dependency @types/node to v20.10.0 (#5313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-25 23:54:34 +00:00
renovate[bot] 1ffe862810 chore(deps): update server (#5311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-25 23:48:48 +00:00
renovate[bot] 69d096df17 chore(deps): update server (#5257)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-25 18:42:58 -05:00
Michael Manganiello 6d1b325b34 chore(server): Check album permissions in bulk (#5290)
* chore(server): Check album permissions in bulk

Modify Access repository, to evaluate `album` permissions in bulk.
Queries have been validated to match what they currently generate for
single ids.

Queries:

* Owner access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "albums" "AlbumEntity"
  WHERE
    "AlbumEntity"."id" = $1
    AND "AlbumEntity"."ownerId" = $2
    AND "AlbumEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT
  "AlbumEntity"."id" AS "AlbumEntity_id"
FROM "albums" "AlbumEntity"
WHERE
  "AlbumEntity"."id" IN ($1, $2)
  AND "AlbumEntity"."ownerId" = $3
  AND "AlbumEntity"."deletedAt" IS NULL
```

* Shared link access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "shared_links" "SharedLinkEntity"
  WHERE
    "SharedLinkEntity"."id" = $1
    AND "SharedLinkEntity"."albumId" = $2
)
LIMIT 1

-- After
SELECT
  "SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
  "SharedLinkEntity"."id" AS "SharedLinkEntity_id"
FROM "shared_links" "SharedLinkEntity"
WHERE
  "SharedLinkEntity"."id" = $1
  AND "SharedLinkEntity"."albumId" IN ($2, $3)
```

* Shared album access:

```sql
-- Before
SELECT 1 AS "row_exists" FROM (SELECT 1 AS dummy_column) "dummy_table" WHERE EXISTS (
  SELECT 1
  FROM "albums" "AlbumEntity"
    LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"
      ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id"
    LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers"
      ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
      AND "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
  WHERE
    "AlbumEntity"."id" = $1
    AND "AlbumEntity__AlbumEntity_sharedUsers"."id" = $2
    AND "AlbumEntity"."deletedAt" IS NULL
)
LIMIT 1

-- After
SELECT
  "AlbumEntity"."id" AS "AlbumEntity_id"
FROM "albums" "AlbumEntity"
  LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"
    ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id"
  LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers"
    ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
    AND "AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
WHERE
  "AlbumEntity"."id" IN ($1, $2)
  AND "AlbumEntity__AlbumEntity_sharedUsers"."id" = $3
  AND "AlbumEntity"."deletedAt" IS NULL
```

* chore(server): Add set utils, avoid double queries for same ids

* chore(server): Review feedback
2023-11-25 17:56:23 -05:00
Zack Pollard 698226634e feat: postgres reverse geocoding (#5301)
* feat: add system metadata repository for storing key values for internal usage

* feat: add database entities for geodata

* feat: move reverse geocoding from local-reverse-geocoder to postgresql

* infra: disable synchronization for geodata_places table until typeorm supports earth column

* feat: remove cities override config as we will default all instances to cities500 now

* test: e2e tests don't clear geodata tables on reset
2023-11-25 18:53:30 +00:00
shenlong 0108211c0f refactor: deprecate getUserAssetsByDeviceId (#5273)
* refactor: deprecated getUserAssetsByDeviceId

* prevent breaking changes

* chore: add deprecation

* prevent breaking changes

* prevent breaking changes

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-11-25 15:46:20 +00:00
renovate[bot] 155ccbc870 chore(deps): update base-image to v20231125 (#5307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-25 09:17:53 -06:00
shenlong f222e47651 fix(mobile): update password change description text to use user name (#5105)
Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2023-11-24 15:32:21 -06:00
Daniel Dietzler 4684094b9b fix(web): Map clustering when zoomed in (#5299)
* raise maxZoom to a value that cannot be reached

* set max zoom for the entire map
2023-11-24 15:30:57 -06:00
Michael Manganiello 8a8d3811b9 fix(mobile): Add translatable strings for shared links info (#5292)
Mark more strings as translatable, regarding shared link information and
expiration.
2023-11-24 15:29:49 -06:00
Emanuel Bennici 309be88ccd feat(server): load face entities faster (#5281)
The query executed when loading the "People" page joins, among others, over "personId".
The added indices improve the overall performance of those JOIN queries.

Additionally, one ORDER BY clause is dropped since the resulting values
will always be TRUE, and thus, sorting them does not change the result.
2023-11-24 14:38:54 +00:00
renovate[bot] 4987bbb712 chore(deps): update base-image to v20231123 (#5285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-23 18:55:25 -06:00
bo0tzz a6676907b4 chore(renovate): Group base image updates (#5284) 2023-11-23 21:55:36 +01:00
waclaw66 df9ec9327d chore(web): adjust album thumbnail size (#5277) 2023-11-23 08:33:26 -06:00
Michael Manganiello 030cd8c4c4 chore(server): Prepare access interfaces for bulk permission checks (#5223)
* chore(server): Prepare access interfaces for bulk permission checks

This change adds the `AccessCore.getAllowedIds` method, to evaluate
permissions in bulk, along with some other `getAllowedIds*` private
methods.

The added methods still calculate permissions by id, and are not
optimized to reduce the amount of queries and execution time, which will
be implemented in separate pull requests.

Services that were evaluating permissions in a loop have been refactored
to make use of the bulk approach.

* chore(server): Apply review suggestions

* chore(server): Make multiple-permission check more readable
2023-11-22 23:04:52 -05:00
Mert 6e10d15f2c pin python (#5272) 2023-11-22 19:42:17 -05:00
Aamir Azad 6eadca330b docs: change github sponsor link to organization (#5267) 2023-11-22 18:17:56 +00:00
Jason Rasmussen 309ba7d67e fix(web): navigate to album from search (#5241) 2023-11-22 09:54:58 -06:00
renovate[bot] 106bae4a31 chore(deps): update dependency svelte-jester to v3 (#5252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-22 08:08:11 -06:00
Alex Tran 95280fd692 chore(mobile): remove unused import 2023-11-22 08:00:28 -06:00
3urobeat 82fffd2c56 Add German README (#5262)
* docs: Add german README translation

* docs: Link german README translation

* docs: Address review comments
2023-11-22 13:49:01 +01:00
PyKen ff275ea175 chore(mobile): Add log when saving asset (#5259) 2023-11-22 03:55:51 +00:00
renovate[bot] 8c2851fbc4 chore(deps): update @immich/cli (#5255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 20:24:28 -06:00
Jason Rasmussen c1d9ce8679 Update FUNDING.yml 2023-11-21 19:43:51 -05:00
Jason Rasmussen 29c154c681 Update FUNDING.yml 2023-11-21 19:31:09 -05:00
renovate[bot] a861b93d7d chore(deps): update dependency macos to v13 (#5244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 17:06:28 -06:00
renovate[bot] 0280d15d9d fix(deps): update dependency @immich/cli to v2.0.4 (#5250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 17:05:51 -06:00
Alex c8aa782fef fix(cli): upload large file (#5242)
* fix(cli): upload large file

* fix: use known configuration

* chore: version bump

* chore: fix repo url

* fix conflict

---------

Co-authored-by: Jonathan Jogenfors <jonathan@jogenfors.se>
2023-11-21 15:52:12 -06:00
Alex 8ff4a08a2c fix(cli): upload large file with openAPI api (#5246)
* fix(cli): upload large file with openAPI

* function name

* fix: use boolean false

* chore: version bump

* fix: package-lock version

---------

Co-authored-by: Jonathan Jogenfors <jonathan@jogenfors.se>
2023-11-21 21:09:19 +00:00
Alex af1113bf9e chore(cli): add version option (#5237)
* chore(cli): add version option

* chore: update build path

* fix: defer to package details for path to entrypoint

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-21 13:12:40 -06:00
renovate[bot] 55f7cf3ca9 chore(deps): update dependency typescript to v5.3.2 (#5236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 13:48:31 -05:00
renovate[bot] c72063280c chore(deps): update @immich/cli (#5235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 13:47:57 -05:00
renovate[bot] b06c2b786c chore(deps): update web (#4961)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 13:46:07 -05:00
renovate[bot] c607615e41 fix(deps): update exiftool (#4988)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 13:45:22 -05:00
renovate[bot] 566471444f chore(deps): update dependency eslint-plugin-prettier to v5 (#5174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 13:31:50 -05:00
renovate[bot] bf82ce24e0 chore(deps): update dependency eslint-plugin-unicorn to v49 (#5178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 13:01:42 -05:00
renovate[bot] afd78652f2 fix(deps): update server (#5187)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-21 12:59:41 -05:00
YFrendo 0f657da5a4 fix(server): override date via xmp (#5199)
* Fix

* open api

* Change to list and delete

* Bug fix

* Change name

* refactor: clean up code and add test

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-11-21 10:58:56 -06:00
441 changed files with 17566 additions and 5174 deletions
+2
View File
@@ -17,3 +17,5 @@ web/src/api/open-api/**/*.md -diff -merge
web/src/api/open-api/**/*.md linguist-generated=true web/src/api/open-api/**/*.md linguist-generated=true
web/src/api/open-api/**/*.ts -diff -merge web/src/api/open-api/**/*.ts -diff -merge
web/src/api/open-api/**/*.ts linguist-generated=true web/src/api/open-api/**/*.ts linguist-generated=true
*.sh text eol=lf
+1 -1
View File
@@ -1,5 +1,5 @@
# These are supported funding model platforms # These are supported funding model platforms
github: alextran1502 github: immich-app
liberapay: alex.tran1502 liberapay: alex.tran1502
custom: https://www.buymeacoffee.com/altran1502 custom: https://www.buymeacoffee.com/altran1502
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
name: Build and sign Android name: Build and sign Android
# Skip when PR from a fork # Skip when PR from a fork
if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
runs-on: macos-12 runs-on: macos-13
steps: steps:
- name: Determine ref - name: Determine ref
@@ -35,7 +35,7 @@ jobs:
with: with:
ref: ${{ steps.get-ref.outputs.ref }} ref: ${{ steps.get-ref.outputs.ref }}
- uses: actions/setup-java@v3 - uses: actions/setup-java@v4
with: with:
distribution: "zulu" distribution: "zulu"
java-version: "12.x" java-version: "12.x"
+2 -1
View File
@@ -1,4 +1,4 @@
name: Clean up actions cache on PR close name: Cache Cleanup
on: on:
pull_request: pull_request:
types: types:
@@ -10,6 +10,7 @@ concurrency:
jobs: jobs:
cleanup: cleanup:
name: Cleanup
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code
+2 -1
View File
@@ -1,9 +1,10 @@
name: Publish Package to npmjs name: CLI Release
on: on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
publish: publish:
name: Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
+1 -1
View File
@@ -5,7 +5,7 @@
# #
# This workflow will not trigger runs on forked repos. # This workflow will not trigger runs on forked repos.
name: Cleanup Old Docker Images name: Docker Cleanup
on: on:
pull_request: pull_request:
+2 -1
View File
@@ -1,4 +1,4 @@
name: Build and Push Docker Images name: Docker
on: on:
workflow_dispatch: workflow_dispatch:
@@ -18,6 +18,7 @@ permissions:
jobs: jobs:
build_and_push: build_and_push:
name: Build and Push
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
# Prevent a failure in one image from stopping the other builds # Prevent a failure in one image from stopping the other builds
+35 -14
View File
@@ -11,7 +11,7 @@ concurrency:
jobs: jobs:
e2e-tests: e2e-tests:
name: Run end-to-end test suites name: Server (e2e)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -24,7 +24,7 @@ jobs:
run: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build run: docker compose -f ./docker/docker-compose.test.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
doc-tests: doc-tests:
name: Run documentation checks name: Docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -45,8 +45,12 @@ jobs:
run: npm run check run: npm run check
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
- name: Run build
run: npm run build
if: ${{ !cancelled() }}
server-unit-tests: server-unit-tests:
name: Run server unit test suites and checks name: Server
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -76,7 +80,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
cli-unit-tests: cli-unit-tests:
name: Run cli test suites name: CLI
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -106,7 +110,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
web-unit-tests: web-unit-tests:
name: Run web unit test suites and checks name: Web
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -140,7 +144,7 @@ jobs:
# if: ${{ !cancelled() }} # if: ${{ !cancelled() }}
mobile-unit-tests: mobile-unit-tests:
name: Run mobile unit tests name: Mobile
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -154,7 +158,7 @@ jobs:
run: flutter test -j 1 run: flutter test -j 1
ml-unit-tests: ml-unit-tests:
name: Run ML unit tests and checks name: Machine Learning
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -163,7 +167,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install poetry - name: Install poetry
run: pipx install poetry run: pipx install poetry
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: 3.11 python-version: 3.11
cache: "poetry" cache: "poetry"
@@ -184,7 +188,7 @@ jobs:
poetry run pytest --cov app poetry run pytest --cov app
generated-api-up-to-date: generated-api-up-to-date:
name: Check generated files are up-to-date name: OpenAPI Clients
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -205,11 +209,11 @@ jobs:
exit 1 exit 1
generated-typeorm-migrations-up-to-date: generated-typeorm-migrations-up-to-date:
name: Check generated TypeORM migrations are up-to-date name: TypeORM Checks
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
postgres: postgres:
image: postgres image: postgres@sha256:6dfee32131933ab4ca25a00360c3f427fdc134de56f9a90c6c9a4956b48aea85
env: env:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres POSTGRES_USER: postgres
@@ -232,7 +236,7 @@ jobs:
- name: Install server dependencies - name: Install server dependencies
run: npm ci run: npm ci
- name: Build the - name: Build the app
run: npm run build run: npm run build
- name: Run existing migrations - name: Run existing migrations
@@ -248,13 +252,30 @@ jobs:
with: with:
files: | files: |
server/src/infra/migrations/ server/src/infra/migrations/
- name: Verify files have not changed - name: Verify migration files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true' if: steps.verify-changed-files.outputs.files_changed == 'true'
run: | run: |
echo "ERROR: Generated files not up to date!" echo "ERROR: Generated migration files not up to date!"
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}" echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
exit 1 exit 1
- name: Run SQL generation
run: npm run sql:generate
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
id: verify-changed-sql-files
with:
files: |
server/src/infra/sql
- name: Verify SQL files have not changed
if: steps.verify-changed-sql-files.outputs.files_changed == 'true'
run: |
echo "ERROR: Generated SQL files not up to date!"
echo "Changed files: ${{ steps.verify-changed-sql-files.outputs.changed_files }}"
exit 1
# mobile-integration-tests: # mobile-integration-tests:
# name: Run mobile end-to-end integration tests # name: Run mobile end-to-end integration tests
# runs-on: macos-latest # runs-on: macos-latest
+4 -1
View File
@@ -26,7 +26,10 @@ prod-scale:
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
api: api:
cd ./server && npm run api:generate npm --prefix server run api:generate
sql:
npm --prefix server run sql:generate
attach-server: attach-server:
docker exec -it docker_immich-server_1 sh docker exec -it docker_immich-server_1 sh
+1
View File
@@ -24,6 +24,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
+1
View File
@@ -24,6 +24,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
+122
View File
@@ -0,0 +1,122 @@
<p align="center">
<br/>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="Lizenz: MIT"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a>
<br/>
<br/>
</p>
<p align="center">
<img src="design/immich-logo.svg" width="150" title="Login mit eigener URL">
</p>
<h3 align="center">Immich - Hoch performante, selbst gehostete Backup-Lösung für Fotos und Videos</h3>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Haupt-Screenshot">
</a>
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
</p>
## Warnung
- ⚠️ Das Projekt befindet sich in **sehr aktiver** Entwicklung.
- ⚠️ Erwarte Fehler und Änderungen mit Breaking-Changes.
- ⚠️ **Nutze die App auf keinen Fall als einziges Speichermedium für deine Fotos und Videos.**
- ⚠️ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel für deine wertvollen Fotos und Videos!
## Inhalt
- [Offizielle Dokumentation](https://immich.app/docs)
- [Roadmap](https://github.com/orgs/immich-app/projects/1)
- [Demo](#demo)
- [Funktionen](#funktionen)
- [Einführung](https://immich.app/docs/overview/introduction)
- [Installation](https://immich.app/docs/install/requirements)
- [Beitragsrichtlinien](https://immich.app/docs/overview/support-the-project)
- [Unterstütze das Projekt](#unterstütze-das-projekt)
## Dokumentation
Die Hauptdokumentation, inklusive Installationsanleitungen, ist unter https://immich.app zu finden.
## Demo
Die Web-Demo kannst Du unter https://demo.immich.app finden.
Für die Handy-App kannst Du `https://demo.immich.app/api` als `Server Endpoint URL` angeben.
```bash title="Demo Credential"
Die Anmeldedaten
email: demo@immich.app
passwort: demo
```
```
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
## Funktionen
| Funktionen | Mobil | Web |
| ---------------------------------------------------- | ------ | ----- |
| Fotos & Videos hochladen und ansehen | Ja | Ja |
| Automatisches Backup wenn die App geöffnet ist | Ja | n. a. |
| Selektive Auswahl von Alben zum Sichern | Ja | n. a. |
| Fotos und Videos auf das Gerät herunterladen | Ja | Ja |
| Unterstützt mehrere Benutzer | Ja | Ja |
| Album und geteilte Alben | Ja | Ja |
| Scrollleiste | Ja | Ja |
| Unterstützt RAW Formate | Ja | Ja |
| Metadaten anzeigen (EXIF, Karte) | Ja | Ja |
| Suchen nach Metadaten, Objekten, Gesichtern und CLIP | Ja | Ja |
| Administrative Funktionen (Benutzerverwaltung) | Nein | Ja |
| Backup im Hintergrund | Ja | n. a. |
| Virtuelles Scrollen | Ja | Ja |
| OAuth Unterstützung | Ja | Ja |
| API-Schlüssel | n. a. | Ja |
| LivePhoto/MotionPhoto Backup und Wiedergabe | Ja | Ja |
| Benutzerdefinierte Speicherstruktur | Ja | Ja |
| Öffentliches Teilen | Nein | Ja |
| Archive und Favoriten | Ja | Ja |
| Globale Karte | Ja | Ja |
| Teilen mit Partner | Ja | Ja |
| Gesichtserkennung und Gruppierung | Ja | Ja |
| Rückblicke (heute vor x Jahren) | Ja | Ja |
| Offline Unterstützung | Ja | Nein |
| Schreibgeschützte Gallerie | Ja | Ja |
| Gestapelte Bilder | Ja | Ja |
## Unterstütze das Projekt
Ich habe mich diesem Projekt verpflichtet und werde nicht aufgeben. Ich werde die Dokumentation weiter aktualisieren, neue Funktionen hinzufügen und Fehler beheben. Allerdings kann ich das nicht alleine schaffen. Daher brauche ich Eure Unterstützung, um mir zusätzliche Motivation zu geben, weiterzumachen.
Wie unsere Gastgeber in der [selfhosted.show - In der Episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) gesagt haben, ist dies ein riesiges Unterfangen, welchem das Team und ich uns annehmen. In Zukunft würde ich liebend gerne Vollzeit an dem Projekt arbeiten und bitte daher um Eure Unterstützung.
Wenn Du denkst, dass dies die richtige Sache ist und dich selbst die App für eine längere Zeit nutzen siehst, dann denke bitte darüber nach, das Projekt mit einer der unten aufgelisteten Optionen zu unterstützen.
### Spenden
- [Monatliche Spende](https://github.com/sponsors/immich-app) via GitHub Sponsors
- [Einmalige Spende](https://github.com/sponsors/immich-app?frequency=one-time&sponsor=immich-app) via GitHub Sponsors
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
## Mitwirkende
<a href="https://github.com/alextran1502/immich/graphs/contributors">
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
</a>
+1
View File
@@ -24,6 +24,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
+1
View File
@@ -24,6 +24,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
+1
View File
@@ -24,6 +24,7 @@
<a href="README_fr_FR.md">Français</a> <a href="README_fr_FR.md">Français</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
+1
View File
@@ -24,6 +24,7 @@
<a href="README_fr_FR.md">Français</a> <a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
+2 -1
View File
@@ -24,6 +24,7 @@
<a href="README_fr_FR.md">Français</a> <a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
@@ -113,4 +114,4 @@ password: demo
- [Librepay](https://liberapay.com/alex.tran1502/) - [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502) - [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX - Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz - ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
+1
View File
@@ -25,6 +25,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
</p> </p>
+1
View File
@@ -25,6 +25,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_zh_CN.md">中文</a> <a href="README_zh_CN.md">中文</a>
</p> </p>
+1
View File
@@ -29,6 +29,7 @@
<a href="README_it_IT.md">Italiano</a> <a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a> <a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a> <a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a> <a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a> <a href="README_tr_TR.md">Türkçe</a>
</p> </p>
+1136 -157
View File
File diff suppressed because it is too large Load Diff
+12 -8
View File
@@ -1,12 +1,16 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.0.3", "version": "2.0.4",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {
"immich": "./dist/index.js" "immich": "./dist/src/index.js"
}, },
"license": "MIT", "license": "MIT",
"keywords": [
"immich",
"cli"
],
"dependencies": { "dependencies": {
"axios": "^1.6.2", "axios": "^1.6.2",
"byte-size": "^8.1.1", "byte-size": "^8.1.1",
@@ -25,14 +29,14 @@
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
"@types/mock-fs": "^4.13.1", "@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^5.48.1", "@typescript-eslint/parser": "^6.0.0",
"chai": "^4.3.7", "chai": "^4.3.7",
"eslint": "^8.43.0", "eslint": "^8.43.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-jest": "^27.2.2", "eslint-plugin-jest": "^27.2.2",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-unicorn": "^47.0.0", "eslint-plugin-unicorn": "^49.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"jest-extended": "^4.0.0", "jest-extended": "^4.0.0",
"jest-message-util": "^29.5.0", "jest-message-util": "^29.5.0",
@@ -42,7 +46,7 @@
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tslib": "^2.5.3", "tslib": "^2.5.3",
"typescript": "^4.9.4" "typescript": "^5.0.0"
}, },
"scripts": { "scripts": {
"build": "tsc --project tsconfig.build.json", "build": "tsc --project tsconfig.build.json",
@@ -77,7 +81,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/immich-app/immich.git", "url": "github:immich-app/immich",
"directory": "cli" "directory": "cli"
} }
} }
+2
View File
@@ -11,6 +11,7 @@ import {
UserApi, UserApi,
} from './open-api'; } from './open-api';
import { ApiConfiguration } from '../cores/api-configuration'; import { ApiConfiguration } from '../cores/api-configuration';
import FormData from 'form-data';
export class ImmichApi { export class ImmichApi {
public userApi: UserApi; public userApi: UserApi;
@@ -35,6 +36,7 @@ export class ImmichApi {
'x-api-key': apiKey, 'x-api-key': apiKey,
}, },
}, },
formDataCtor: FormData,
}); });
this.userApi = new UserApi(this.config); this.userApi = new UserApi(this.config);
+723 -32
View File
@@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.90.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -447,6 +447,12 @@ export interface AssetBulkDeleteDto {
* @interface AssetBulkUpdateDto * @interface AssetBulkUpdateDto
*/ */
export interface AssetBulkUpdateDto { export interface AssetBulkUpdateDto {
/**
*
* @type {string}
* @memberof AssetBulkUpdateDto
*/
'dateTimeOriginal'?: string;
/** /**
* *
* @type {Array<string>} * @type {Array<string>}
@@ -465,6 +471,18 @@ export interface AssetBulkUpdateDto {
* @memberof AssetBulkUpdateDto * @memberof AssetBulkUpdateDto
*/ */
'isFavorite'?: boolean; 'isFavorite'?: boolean;
/**
*
* @type {number}
* @memberof AssetBulkUpdateDto
*/
'latitude'?: number;
/**
*
* @type {number}
* @memberof AssetBulkUpdateDto
*/
'longitude'?: number;
/** /**
* *
* @type {boolean} * @type {boolean}
@@ -568,6 +586,142 @@ export const AssetBulkUploadCheckResultReasonEnum = {
export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum]; export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum];
/**
*
* @export
* @interface AssetFaceResponseDto
*/
export interface AssetFaceResponseDto {
/**
*
* @type {number}
* @memberof AssetFaceResponseDto
*/
'boundingBoxX1': number;
/**
*
* @type {number}
* @memberof AssetFaceResponseDto
*/
'boundingBoxX2': number;
/**
*
* @type {number}
* @memberof AssetFaceResponseDto
*/
'boundingBoxY1': number;
/**
*
* @type {number}
* @memberof AssetFaceResponseDto
*/
'boundingBoxY2': number;
/**
*
* @type {string}
* @memberof AssetFaceResponseDto
*/
'id': string;
/**
*
* @type {number}
* @memberof AssetFaceResponseDto
*/
'imageHeight': number;
/**
*
* @type {number}
* @memberof AssetFaceResponseDto
*/
'imageWidth': number;
/**
*
* @type {PersonResponseDto}
* @memberof AssetFaceResponseDto
*/
'person': PersonResponseDto | null;
}
/**
*
* @export
* @interface AssetFaceUpdateDto
*/
export interface AssetFaceUpdateDto {
/**
*
* @type {Array<AssetFaceUpdateItem>}
* @memberof AssetFaceUpdateDto
*/
'data': Array<AssetFaceUpdateItem>;
}
/**
*
* @export
* @interface AssetFaceUpdateItem
*/
export interface AssetFaceUpdateItem {
/**
*
* @type {string}
* @memberof AssetFaceUpdateItem
*/
'assetId': string;
/**
*
* @type {string}
* @memberof AssetFaceUpdateItem
*/
'personId': string;
}
/**
*
* @export
* @interface AssetFaceWithoutPersonResponseDto
*/
export interface AssetFaceWithoutPersonResponseDto {
/**
*
* @type {number}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'boundingBoxX1': number;
/**
*
* @type {number}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'boundingBoxX2': number;
/**
*
* @type {number}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'boundingBoxY1': number;
/**
*
* @type {number}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'boundingBoxY2': number;
/**
*
* @type {string}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'id': string;
/**
*
* @type {number}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'imageHeight': number;
/**
*
* @type {number}
* @memberof AssetFaceWithoutPersonResponseDto
*/
'imageWidth': number;
}
/** /**
* *
* @export * @export
@@ -824,10 +978,10 @@ export interface AssetResponseDto {
'ownerId': string; 'ownerId': string;
/** /**
* *
* @type {Array<PersonResponseDto>} * @type {Array<PersonWithFacesResponseDto>}
* @memberof AssetResponseDto * @memberof AssetResponseDto
*/ */
'people'?: Array<PersonResponseDto>; 'people'?: Array<PersonWithFacesResponseDto>;
/** /**
* *
* @type {boolean} * @type {boolean}
@@ -1164,22 +1318,6 @@ export interface CheckExistingAssetsResponseDto {
*/ */
'existingIds': Array<string>; 'existingIds': Array<string>;
} }
/**
*
* @export
* @enum {string}
*/
export const CitiesFile = {
Cities15000: 'cities15000',
Cities5000: 'cities5000',
Cities1000: 'cities1000',
Cities500: 'cities500'
} as const;
export type CitiesFile = typeof CitiesFile[keyof typeof CitiesFile];
/** /**
* *
* @export * @export
@@ -1670,6 +1808,19 @@ export interface ExifResponseDto {
*/ */
'timeZone'?: string | null; 'timeZone'?: string | null;
} }
/**
*
* @export
* @interface FaceDto
*/
export interface FaceDto {
/**
*
* @type {string}
* @memberof FaceDto
*/
'id': string;
}
/** /**
* *
* @export * @export
@@ -1783,7 +1934,8 @@ export const JobCommand = {
Start: 'start', Start: 'start',
Pause: 'pause', Pause: 'pause',
Resume: 'resume', Resume: 'resume',
Empty: 'empty' Empty: 'empty',
ClearFailed: 'clear-failed'
} as const; } as const;
export type JobCommand = typeof JobCommand[keyof typeof JobCommand]; export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
@@ -2561,6 +2713,49 @@ export interface PersonUpdateDto {
*/ */
'name'?: string; 'name'?: string;
} }
/**
*
* @export
* @interface PersonWithFacesResponseDto
*/
export interface PersonWithFacesResponseDto {
/**
*
* @type {string}
* @memberof PersonWithFacesResponseDto
*/
'birthDate': string | null;
/**
*
* @type {Array<AssetFaceWithoutPersonResponseDto>}
* @memberof PersonWithFacesResponseDto
*/
'faces': Array<AssetFaceWithoutPersonResponseDto>;
/**
*
* @type {string}
* @memberof PersonWithFacesResponseDto
*/
'id': string;
/**
*
* @type {boolean}
* @memberof PersonWithFacesResponseDto
*/
'isHidden': boolean;
/**
*
* @type {string}
* @memberof PersonWithFacesResponseDto
*/
'name': string;
/**
*
* @type {string}
* @memberof PersonWithFacesResponseDto
*/
'thumbnailPath': string;
}
/** /**
* *
* @export * @export
@@ -3832,12 +4027,6 @@ export interface SystemConfigPasswordLoginDto {
* @interface SystemConfigReverseGeocodingDto * @interface SystemConfigReverseGeocodingDto
*/ */
export interface SystemConfigReverseGeocodingDto { export interface SystemConfigReverseGeocodingDto {
/**
*
* @type {CitiesFile}
* @memberof SystemConfigReverseGeocodingDto
*/
'citiesFileOverride': CitiesFile;
/** /**
* *
* @type {boolean} * @type {boolean}
@@ -3845,8 +4034,6 @@ export interface SystemConfigReverseGeocodingDto {
*/ */
'enabled': boolean; 'enabled': boolean;
} }
/** /**
* *
* @export * @export
@@ -4161,6 +4348,12 @@ export interface UpdateAlbumDto {
* @interface UpdateAssetDto * @interface UpdateAssetDto
*/ */
export interface UpdateAssetDto { export interface UpdateAssetDto {
/**
*
* @type {string}
* @memberof UpdateAssetDto
*/
'dateTimeOriginal'?: string;
/** /**
* *
* @type {string} * @type {string}
@@ -4179,6 +4372,18 @@ export interface UpdateAssetDto {
* @memberof UpdateAssetDto * @memberof UpdateAssetDto
*/ */
'isFavorite'?: boolean; 'isFavorite'?: boolean;
/**
*
* @type {number}
* @memberof UpdateAssetDto
*/
'latitude'?: number;
/**
*
* @type {number}
* @memberof UpdateAssetDto
*/
'longitude'?: number;
} }
/** /**
* *
@@ -6808,6 +7013,48 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Get all asset of a device that are in the database, ID only.
* @param {string} deviceId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAllUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'deviceId' is not null or undefined
assertParamExists('getAllUserAssetsByDeviceId', 'deviceId', deviceId)
const localVarPath = `/asset/device/{deviceId}`
.replace(`{${"deviceId"}}`, encodeURIComponent(String(deviceId)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter); setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@@ -7477,9 +7724,11 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
}; };
}, },
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {string} deviceId * @param {string} deviceId
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { getUserAssetsByDeviceId: async (deviceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
@@ -8311,6 +8560,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch, options); const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(skip, take, userId, isFavorite, isArchived, updatedAfter, updatedBefore, ifNoneMatch, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
* Get all asset of a device that are in the database, ID only.
* @param {string} deviceId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getAllUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllUserAssetsByDeviceId(deviceId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* Get a single asset\'s information * Get a single asset\'s information
* @param {string} id * @param {string} id
@@ -8458,9 +8717,11 @@ export const AssetApiFp = function(configuration?: Configuration) {
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {string} deviceId * @param {string} deviceId
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async getUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> { async getUserAssetsByDeviceId(deviceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
@@ -8686,6 +8947,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> { getAllAssets(requestParameters: AssetApiGetAllAssetsRequest = {}, options?: AxiosRequestConfig): AxiosPromise<Array<AssetResponseDto>> {
return localVarFp.getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath)); return localVarFp.getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(axios, basePath));
}, },
/**
* Get all asset of a device that are in the database, ID only.
* @param {AssetApiGetAllUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getAllUserAssetsByDeviceId(requestParameters: AssetApiGetAllUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig): AxiosPromise<Array<string>> {
return localVarFp.getAllUserAssetsByDeviceId(requestParameters.deviceId, options).then((request) => request(axios, basePath));
},
/** /**
* Get a single asset\'s information * Get a single asset\'s information
* @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters.
@@ -8792,9 +9062,11 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath)); return localVarFp.getTimeBuckets(requestParameters.size, requestParameters.userId, requestParameters.albumId, requestParameters.personId, requestParameters.isArchived, requestParameters.isFavorite, requestParameters.isTrashed, requestParameters.withStacked, requestParameters.withPartners, requestParameters.key, options).then((request) => request(axios, basePath));
}, },
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
*/ */
getUserAssetsByDeviceId(requestParameters: AssetApiGetUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig): AxiosPromise<Array<string>> { getUserAssetsByDeviceId(requestParameters: AssetApiGetUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig): AxiosPromise<Array<string>> {
@@ -9030,6 +9302,20 @@ export interface AssetApiGetAllAssetsRequest {
readonly ifNoneMatch?: string readonly ifNoneMatch?: string
} }
/**
* Request parameters for getAllUserAssetsByDeviceId operation in AssetApi.
* @export
* @interface AssetApiGetAllUserAssetsByDeviceIdRequest
*/
export interface AssetApiGetAllUserAssetsByDeviceIdRequest {
/**
*
* @type {string}
* @memberof AssetApiGetAllUserAssetsByDeviceId
*/
readonly deviceId: string
}
/** /**
* Request parameters for getAssetById operation in AssetApi. * Request parameters for getAssetById operation in AssetApi.
* @export * @export
@@ -9974,6 +10260,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); return AssetApiFp(this.configuration).getAllAssets(requestParameters.skip, requestParameters.take, requestParameters.userId, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.ifNoneMatch, options).then((request) => request(this.axios, this.basePath));
} }
/**
* Get all asset of a device that are in the database, ID only.
* @param {AssetApiGetAllUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public getAllUserAssetsByDeviceId(requestParameters: AssetApiGetAllUserAssetsByDeviceIdRequest, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).getAllUserAssetsByDeviceId(requestParameters.deviceId, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* Get a single asset\'s information * Get a single asset\'s information
* @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters. * @param {AssetApiGetAssetByIdRequest} requestParameters Request parameters.
@@ -10104,9 +10401,11 @@ export class AssetApi extends BaseAPI {
} }
/** /**
* Get all asset of a device that are in the database, ID only. *
* @summary Use /asset/device/:deviceId instead - Remove in 1.92 release
* @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters. * @param {AssetApiGetUserAssetsByDeviceIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @deprecated
* @throws {RequiredError} * @throws {RequiredError}
* @memberof AssetApi * @memberof AssetApi
*/ */
@@ -11242,6 +11541,233 @@ export class AuthenticationApi extends BaseAPI {
} }
/**
* FaceApi - axios parameter creator
* @export
*/
export const FaceApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @param {string} id
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getFaces: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('getFaces', 'id', id)
const localVarPath = `/face`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} id
* @param {FaceDto} faceDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFacesById: async (id: string, faceDto: FaceDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('reassignFacesById', 'id', id)
// verify required parameter 'faceDto' is not null or undefined
assertParamExists('reassignFacesById', 'faceDto', faceDto)
const localVarPath = `/face/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(faceDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* FaceApi - functional programming interface
* @export
*/
export const FaceApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = FaceApiAxiosParamCreator(configuration)
return {
/**
*
* @param {string} id
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getFaces(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetFaceResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getFaces(id, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {string} id
* @param {FaceDto} faceDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async reassignFacesById(id: string, faceDto: FaceDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFacesById(id, faceDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
};
/**
* FaceApi - factory interface
* @export
*/
export const FaceApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = FaceApiFp(configuration)
return {
/**
*
* @param {FaceApiGetFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getFaces(requestParameters: FaceApiGetFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<AssetFaceResponseDto>> {
return localVarFp.getFaces(requestParameters.id, options).then((request) => request(axios, basePath));
},
/**
*
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(axios, basePath));
},
};
};
/**
* Request parameters for getFaces operation in FaceApi.
* @export
* @interface FaceApiGetFacesRequest
*/
export interface FaceApiGetFacesRequest {
/**
*
* @type {string}
* @memberof FaceApiGetFaces
*/
readonly id: string
}
/**
* Request parameters for reassignFacesById operation in FaceApi.
* @export
* @interface FaceApiReassignFacesByIdRequest
*/
export interface FaceApiReassignFacesByIdRequest {
/**
*
* @type {string}
* @memberof FaceApiReassignFacesById
*/
readonly id: string
/**
*
* @type {FaceDto}
* @memberof FaceApiReassignFacesById
*/
readonly faceDto: FaceDto
}
/**
* FaceApi - object-oriented interface
* @export
* @class FaceApi
* @extends {BaseAPI}
*/
export class FaceApi extends BaseAPI {
/**
*
* @param {FaceApiGetFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FaceApi
*/
public getFaces(requestParameters: FaceApiGetFacesRequest, options?: AxiosRequestConfig) {
return FaceApiFp(this.configuration).getFaces(requestParameters.id, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FaceApi
*/
public reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig) {
return FaceApiFp(this.configuration).reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(this.axios, this.basePath));
}
}
/** /**
* JobApi - axios parameter creator * JobApi - axios parameter creator
* @export * @export
@@ -13073,6 +13599,44 @@ export class PartnerApi extends BaseAPI {
*/ */
export const PersonApiAxiosParamCreator = function (configuration?: Configuration) { export const PersonApiAxiosParamCreator = function (configuration?: Configuration) {
return { return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createPerson: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/person`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {boolean} [withHidden] * @param {boolean} [withHidden]
@@ -13332,6 +13896,54 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
options: localVarRequestOptions, options: localVarRequestOptions,
}; };
}, },
/**
*
* @param {string} id
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFaces: async (id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('reassignFaces', 'id', id)
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
assertParamExists('reassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
const localVarPath = `/person/{id}/reassign`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(assetFaceUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* *
* @param {PeopleUpdateDto} peopleUpdateDto * @param {PeopleUpdateDto} peopleUpdateDto
@@ -13434,6 +14046,15 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
export const PersonApiFp = function(configuration?: Configuration) { export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration) const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration)
return { return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createPerson(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createPerson(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {boolean} [withHidden] * @param {boolean} [withHidden]
@@ -13495,6 +14116,17 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options); const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @param {string} id
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async reassignFaces(id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<PersonResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFaces(id, assetFaceUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* *
* @param {PeopleUpdateDto} peopleUpdateDto * @param {PeopleUpdateDto} peopleUpdateDto
@@ -13526,6 +14158,14 @@ export const PersonApiFp = function(configuration?: Configuration) {
export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = PersonApiFp(configuration) const localVarFp = PersonApiFp(configuration)
return { return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createPerson(options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
return localVarFp.createPerson(options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
@@ -13580,6 +14220,15 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> { mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath)); return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath));
}, },
/**
*
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
return localVarFp.reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
},
/** /**
* *
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters. * @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
@@ -13692,6 +14341,27 @@ export interface PersonApiMergePersonRequest {
readonly mergePersonDto: MergePersonDto readonly mergePersonDto: MergePersonDto
} }
/**
* Request parameters for reassignFaces operation in PersonApi.
* @export
* @interface PersonApiReassignFacesRequest
*/
export interface PersonApiReassignFacesRequest {
/**
*
* @type {string}
* @memberof PersonApiReassignFaces
*/
readonly id: string
/**
*
* @type {AssetFaceUpdateDto}
* @memberof PersonApiReassignFaces
*/
readonly assetFaceUpdateDto: AssetFaceUpdateDto
}
/** /**
* Request parameters for updatePeople operation in PersonApi. * Request parameters for updatePeople operation in PersonApi.
* @export * @export
@@ -13734,6 +14404,16 @@ export interface PersonApiUpdatePersonRequest {
* @extends {BaseAPI} * @extends {BaseAPI}
*/ */
export class PersonApi extends BaseAPI { export class PersonApi extends BaseAPI {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public createPerson(options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).createPerson(options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters. * @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
@@ -13800,6 +14480,17 @@ export class PersonApi extends BaseAPI {
return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath)); return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof PersonApi
*/
public reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig) {
return PersonApiFp(this.configuration).reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* *
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters. * @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
+1 -1
View File
@@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.90.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+1 -1
View File
@@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.90.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+1 -1
View File
@@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.90.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+1 -1
View File
@@ -4,7 +4,7 @@
* Immich * Immich
* Immich API * Immich API
* *
* The version of the OpenAPI document: 1.88.2 * The version of the OpenAPI document: 1.90.0
* *
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+24 -1
View File
@@ -6,6 +6,8 @@ import { CrawlOptionsDto } from '../cores/dto/crawl-options-dto';
import cliProgress from 'cli-progress'; import cliProgress from 'cli-progress';
import byteSize from 'byte-size'; import byteSize from 'byte-size';
import { BaseCommand } from '../cli/base-command'; import { BaseCommand } from '../cli/base-command';
import axios, { AxiosRequestConfig } from 'axios';
import FormData from 'form-data';
export default class Upload extends BaseCommand { export default class Upload extends BaseCommand {
uploadLength!: number; uploadLength!: number;
@@ -75,7 +77,8 @@ export default class Upload extends BaseCommand {
if (!skipUpload) { if (!skipUpload) {
if (!options.dryRun) { if (!options.dryRun) {
const res = await this.immichApi.assetApi.uploadFile(asset.getUploadFileRequest()); const formData = asset.getUploadFormData();
const res = await this.uploadAsset(formData);
if (options.album && asset.albumName) { if (options.album && asset.albumName) {
let album = existingAlbums.find((album) => album.albumName === asset.albumName); let album = existingAlbums.find((album) => album.albumName === asset.albumName);
@@ -134,4 +137,24 @@ export default class Upload extends BaseCommand {
} }
} }
} }
private async uploadAsset(data: FormData): Promise<axios.AxiosResponse> {
const url = this.immichApi.apiConfiguration.instanceUrl + '/asset/upload';
const config: AxiosRequestConfig = {
method: 'post',
maxRedirects: 0,
url,
headers: {
'x-api-key': this.immichApi.apiConfiguration.apiKey,
...data.getHeaders(),
},
maxContentLength: Infinity,
maxBodyLength: Infinity,
data,
};
const res = await axios(config);
return res;
}
} }
+22 -13
View File
@@ -1,18 +1,18 @@
import * as fs from 'fs'; import * as fs from 'fs';
import { basename } from 'node:path'; import { basename } from 'node:path';
import crypto from 'crypto'; import crypto from 'crypto';
import { AssetApiUploadFileRequest } from 'src/api/open-api';
import Os from 'os'; import Os from 'os';
import FormData from 'form-data';
export class Asset { export class Asset {
readonly path: string; readonly path: string;
readonly deviceId!: string; readonly deviceId!: string;
assetData?: File; assetData?: fs.ReadStream;
deviceAssetId?: string; deviceAssetId?: string;
fileCreatedAt?: string; fileCreatedAt?: string;
fileModifiedAt?: string; fileModifiedAt?: string;
sidecarData?: File; sidecarData?: fs.ReadStream;
sidecarPath?: string; sidecarPath?: string;
fileSize!: number; fileSize!: number;
albumName?: string; albumName?: string;
@@ -30,37 +30,46 @@ export class Asset {
this.fileSize = stats.size; this.fileSize = stats.size;
this.albumName = this.extractAlbumName(); this.albumName = this.extractAlbumName();
this.assetData = await this.getFileObject(this.path); this.assetData = this.getReadStream(this.path);
// TODO: doesn't xmp replace the file extension? Will need investigation // TODO: doesn't xmp replace the file extension? Will need investigation
const sideCarPath = `${this.path}.xmp`; const sideCarPath = `${this.path}.xmp`;
try { try {
fs.accessSync(sideCarPath, fs.constants.R_OK); fs.accessSync(sideCarPath, fs.constants.R_OK);
this.sidecarData = await this.getFileObject(sideCarPath); this.sidecarData = this.getReadStream(sideCarPath);
} catch (error) {} } catch (error) {}
} }
getUploadFileRequest(): AssetApiUploadFileRequest { getUploadFormData(): FormData {
if (!this.assetData) throw new Error('Asset data not set'); if (!this.assetData) throw new Error('Asset data not set');
if (!this.deviceAssetId) throw new Error('Device asset id not set'); if (!this.deviceAssetId) throw new Error('Device asset id not set');
if (!this.fileCreatedAt) throw new Error('File created at not set'); if (!this.fileCreatedAt) throw new Error('File created at not set');
if (!this.fileModifiedAt) throw new Error('File modified at not set'); if (!this.fileModifiedAt) throw new Error('File modified at not set');
if (!this.deviceId) throw new Error('Device id not set'); if (!this.deviceId) throw new Error('Device id not set');
return { const data: any = {
assetData: this.assetData, assetData: this.assetData as any,
deviceAssetId: this.deviceAssetId, deviceAssetId: this.deviceAssetId,
deviceId: this.deviceId, deviceId: this.deviceId,
fileCreatedAt: this.fileCreatedAt, fileCreatedAt: this.fileCreatedAt,
fileModifiedAt: this.fileModifiedAt, fileModifiedAt: this.fileModifiedAt,
isFavorite: false, isFavorite: String(false),
sidecarData: this.sidecarData,
}; };
const formData = new FormData();
for (const prop in data) {
formData.append(prop, data[prop]);
}
if (this.sidecarData) {
formData.append('sidecarData', this.sidecarData);
}
return formData;
} }
private async getFileObject(path: string): Promise<File> { private getReadStream(path: string): fs.ReadStream {
const buffer = await fs.promises.readFile(path); return fs.createReadStream(path);
return new File([buffer], basename(path));
} }
async delete(): Promise<void> { async delete(): Promise<void> {
+2 -1
View File
@@ -5,8 +5,9 @@ import Upload from './commands/upload';
import ServerInfo from './commands/server-info'; import ServerInfo from './commands/server-info';
import LoginKey from './commands/login/key'; import LoginKey from './commands/login/key';
import Logout from './commands/logout'; import Logout from './commands/logout';
import { version } from '../package.json';
program.name('immich').description('Immich command line interface'); program.name('immich').description('Immich command line interface').version(version);
program program
.command('upload') .command('upload')
+3 -3
View File
@@ -59,7 +59,7 @@ services:
build: build:
context: ../web context: ../web
dockerfile: Dockerfile dockerfile: Dockerfile
command: npm run dev --host command: "node ./node_modules/.bin/vite dev --host 0.0.0.0 --port 3000"
env_file: env_file:
- .env - .env
ports: ports:
@@ -108,11 +108,11 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5 image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
database: database:
container_name: immich_postgres container_name: immich_postgres
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
env_file: env_file:
- .env - .env
environment: environment:
+2 -2
View File
@@ -65,12 +65,12 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5 image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
restart: always restart: always
database: database:
container_name: immich_postgres container_name: immich_postgres
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
env_file: env_file:
- .env - .env
environment: environment:
+1 -1
View File
@@ -23,7 +23,7 @@ services:
- database - database
database: database:
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
command: -c fsync=off command: -c fsync=off
environment: environment:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
+2 -2
View File
@@ -69,12 +69,12 @@ services:
redis: redis:
container_name: immich_redis container_name: immich_redis
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5 image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
restart: always restart: always
database: database:
container_name: immich_postgres container_name: immich_postgres
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
env_file: env_file:
- .env - .env
environment: environment:
-4
View File
@@ -14,8 +14,6 @@ docker exec -it <id or name> <command> # attach to a container with a c
docker exec -it immich_server sh docker exec -it immich_server sh
docker exec -it immich_microservices sh docker exec -it immich_microservices sh
docker exec -it immich_machine_learning sh docker exec -it immich_machine_learning sh
docker exec -it immich_web sh
docker exec -it immich_proxy sh
``` ```
## Logs ## Logs
@@ -26,8 +24,6 @@ docker logs <id or name> # see the logs for a specific container (by id
docker logs immich_server docker logs immich_server
docker logs immich_microservices docker logs immich_microservices
docker logs immich_machine_learning docker logs immich_machine_learning
docker logs immich_web
docker logs immich_proxy
``` ```
:::tip Follow a log :::tip Follow a log
+58
View File
@@ -0,0 +1,58 @@
# Remote Access
This page gives a few pointers on how to access your Immich instance from outside your LAN.
:::danger
Never forward port 2283 directly to the internet without additional configuration. This will expose the web interface via http to the internet, making you succeptible to [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks.
:::
## Option 1: VPN to home network
You may use a VPN service to open an encrypted connection to your Immich instance. OpenVPN and Wireguard are two popular VPN solutions. Here is a guide on setting up VPN access to your server - [Pihole documentation](https://docs.pi-hole.net/guides/vpn/wireguard/overview/)
### Pros:
- Simple to set up and very secure.
- Single point of potential failure, i.e., the VPN software itself. Even if there is a zero-day vulnerability on Immich, you will not be at risk.
- Both Wireguard and OpenVPN are independently security-audited, so the risk of serious zero-day exploits are minimal.
### Cons:
- If you don't have a static IP address, you would need to set up a [Dynamic DNS](https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/). [DuckDNS](https://www.duckdns.org/) is a free DDNS provider.
- VPN software needs to be installed and active on both server-side and client-side.
- Requires you to open a port on your router to your server.
## Option 2: Tailscale
If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation).
### Pros
- Minimal configuration needed on server and client sides.
- You are protected against zero-day vulnerabilities on Immich.
### Cons
- The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022.
- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices.
- Tailscale needs to be installed and running on both server-side and client-side.
## Option 3: Reverse Proxy
A reverse proxy is a service that sits between web servers and clients. A reverse proxy can either be hosted on the server itself or remotely. Clients can connect to the reverse proxy via https, and the proxy relays data to Immich. This setup makes most sense if you have your own domain and want to access your Immich instance just like any other website, from outside your LAN. You can also use a DDNS provider like DuckDNS or no-ip if you don't have a domain. This configuration allows the Immich Android and iphone apps to connect to your server without a VPN or tailscale app on the client side.
If you're hosting your own reverse proxy, [Nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) is a great option. An example configuration for Nginx is provided [here](https://immich.app/docs/administration/reverse-proxy).
You'll also need your own certificate to authenticate https connections. If you're making Immich publicly accesible, [Let's Encrypt](https://letsencrypt.org/) can provide a free certificate for your domain and is the recommended option. Alternatively, a [self-signed certificate](https://en.wikipedia.org/wiki/Self-signed_certificate) allows you to encrypt your connection to Immich, but it raises a security warning on the client's browser.
A remote reverse proxy like [Cloudflare](https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/) increases security by hiding the server IP address, which makes targeted attacks like [DDoS](https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/) harder.
### Pros
- No additional software needs to be installed client-side
- If you only need access to the web interface remotely, it is possible to set up access controls that shield you from zero-day vulnerabilities on Immich. [Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) has a generous free tier.
### Cons
- Complex configuration
- Depending on your configuration, both the Immich web interface and API may be exposed to the internet. Immich is under very active developement and the existence of severe security vulnerabilities cannot be ruled out.
+2 -2
View File
@@ -144,7 +144,7 @@ PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server"
From the directory you created in Step 1, (which should now contain your customized `docker-compose.yml` and `.env` files) run `docker-compose up -d`. From the directory you created in Step 1, (which should now contain your customized `docker-compose.yml` and `.env` files) run `docker-compose up -d`.
```bash title="Start the containers using docker compose command" ```bash title="Start the containers using docker compose command"
docker-compose up -d # or `docker compose up -d` based on your docker-compose version docker compose up -d
``` ```
:::tip :::tip
@@ -162,7 +162,7 @@ If `IMMICH_VERSION` is set, it will need to be updated to the latest or desired
When a new version of Immich is [released](https://github.com/immich-app/immich/releases), the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file: When a new version of Immich is [released](https://github.com/immich-app/immich/releases), the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file:
```bash title="Upgrade Immich" ```bash title="Upgrade Immich"
docker-compose pull && docker-compose up -d # Or `docker compose up -d` docker compose pull && docker compose up -d
``` ```
:::caution Automatic Updates :::caution Automatic Updates
+2 -2
View File
@@ -12,8 +12,8 @@ If you feel like this is the right cause and the app is something you see yourse
## Donation ## Donation
- Monthly donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502) - Monthly donation via [GitHub Sponsors](https://github.com/sponsors/immich-app)
- One-time donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) - One-time donation via [GitHub Sponsors](https://github.com/sponsors/immich-app?frequency=one-time)
- [Librepay](https://liberapay.com/alex.tran1502/) - [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502) - [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX - Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
+110 -22
View File
@@ -15,7 +15,7 @@
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"clsx": "^1.2.1", "clsx": "^2.0.0",
"docusaurus-lunr-search": "^2.3.2", "docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3", "docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.25", "postcss": "^8.4.25",
@@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "^2.4.1", "@docusaurus/module-type-aliases": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.5", "@tsconfig/docusaurus": "^1.0.5",
"prettier": "^2.8.8", "prettier": "^3.0.0",
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"engines": { "engines": {
@@ -2603,6 +2603,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/@docusaurus/theme-classic/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@docusaurus/theme-common": { "node_modules/@docusaurus/theme-common": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.3.tgz", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.4.3.tgz",
@@ -2633,6 +2641,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/@docusaurus/theme-common/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@docusaurus/theme-search-algolia": { "node_modules/@docusaurus/theme-search-algolia": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.3.tgz", "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.3.tgz",
@@ -2663,6 +2679,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/@docusaurus/theme-search-algolia/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@docusaurus/theme-translations": { "node_modules/@docusaurus/theme-translations": {
"version": "2.4.3", "version": "2.4.3",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz", "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz",
@@ -4948,9 +4972,9 @@
} }
}, },
"node_modules/clsx": { "node_modules/clsx": {
"version": "1.2.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@@ -5995,6 +6019,14 @@
"react-dom": "^16.8.4 || ^17" "react-dom": "^16.8.4 || ^17"
} }
}, },
"node_modules/docusaurus-lunr-search/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/docusaurus-plugin-openapi": { "node_modules/docusaurus-plugin-openapi": {
"version": "0.6.4", "version": "0.6.4",
"resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.6.4.tgz", "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi/-/docusaurus-plugin-openapi-0.6.4.tgz",
@@ -6025,6 +6057,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/docusaurus-plugin-openapi/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/docusaurus-plugin-openapi/node_modules/fs-extra": { "node_modules/docusaurus-plugin-openapi/node_modules/fs-extra": {
"version": "9.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -6098,6 +6138,14 @@
"react-dom": "^16.8.4 || ^17.0.0" "react-dom": "^16.8.4 || ^17.0.0"
} }
}, },
"node_modules/docusaurus-theme-openapi/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/dom-converter": { "node_modules/dom-converter": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@@ -10801,15 +10849,15 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.8.8", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true, "dev": true,
"bin": { "bin": {
"prettier": "bin-prettier.js" "prettier": "bin/prettier.cjs"
}, },
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=14"
}, },
"funding": { "funding": {
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
@@ -13566,9 +13614,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.2.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -16745,6 +16793,13 @@
"rtlcss": "^3.5.0", "rtlcss": "^3.5.0",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"@docusaurus/theme-common": { "@docusaurus/theme-common": {
@@ -16768,6 +16823,13 @@
"tslib": "^2.4.0", "tslib": "^2.4.0",
"use-sync-external-store": "^1.2.0", "use-sync-external-store": "^1.2.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"@docusaurus/theme-search-algolia": { "@docusaurus/theme-search-algolia": {
@@ -16791,6 +16853,13 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"@docusaurus/theme-translations": { "@docusaurus/theme-translations": {
@@ -18515,9 +18584,9 @@
} }
}, },
"clsx": { "clsx": {
"version": "1.2.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
}, },
"collapse-white-space": { "collapse-white-space": {
"version": "1.0.6", "version": "1.0.6",
@@ -19243,6 +19312,13 @@
"to-vfile": "^6.1.0", "to-vfile": "^6.1.0",
"unified": "^9.0.0", "unified": "^9.0.0",
"unist-util-is": "^4.0.2" "unist-util-is": "^4.0.2"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"docusaurus-plugin-openapi": { "docusaurus-plugin-openapi": {
@@ -19268,6 +19344,11 @@
"webpack": "^5.73.0" "webpack": "^5.73.0"
}, },
"dependencies": { "dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
},
"fs-extra": { "fs-extra": {
"version": "9.1.0", "version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -19321,6 +19402,13 @@
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"redux-devtools-extension": "^2.13.8", "redux-devtools-extension": "^2.13.8",
"webpack": "^5.73.0" "webpack": "^5.73.0"
},
"dependencies": {
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
}
} }
}, },
"dom-converter": { "dom-converter": {
@@ -22663,9 +22751,9 @@
"integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==" "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="
}, },
"prettier": { "prettier": {
"version": "2.8.8", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true "dev": true
}, },
"pretty-error": { "pretty-error": {
@@ -24757,9 +24845,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "5.2.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ=="
}, },
"ua-parser-js": { "ua-parser-js": {
"version": "1.0.36", "version": "1.0.36",
+2 -2
View File
@@ -24,7 +24,7 @@
"@mdx-js/react": "^1.6.22", "@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"clsx": "^1.2.1", "clsx": "^2.0.0",
"docusaurus-lunr-search": "^2.3.2", "docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3", "docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.25", "postcss": "^8.4.25",
@@ -37,7 +37,7 @@
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "^2.4.1", "@docusaurus/module-type-aliases": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.5", "@tsconfig/docusaurus": "^1.0.5",
"prettier": "^2.8.8", "prettier": "^3.0.0",
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"browserslist": { "browserslist": {
+9 -3
View File
@@ -61,8 +61,12 @@
.searchbox__input { .searchbox__input {
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
-webkit-transition: box-shadow 0.4s ease, background 0.4s ease; -webkit-transition:
transition: box-shadow 0.4s ease, background 0.4s ease; box-shadow 0.4s ease,
background 0.4s ease;
transition:
box-shadow 0.4s ease,
background 0.4s ease;
border: 0; border: 0;
border-radius: 16px; border-radius: 16px;
box-shadow: inset 0 0 0 1px #cccccc; box-shadow: inset 0 0 0 1px #cccccc;
@@ -243,7 +247,9 @@
} }
.algolia-autocomplete .ds-dropdown-menu { .algolia-autocomplete .ds-dropdown-menu {
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2), 0 2px 3px 0 rgba(0, 0, 0, 0.1); box-shadow:
0 1px 0 0 rgba(0, 0, 0, 0.2),
0 2px 3px 0 rgba(0, 0, 0, 0.1);
} }
@media (min-width: 601px) { @media (min-width: 601px) {
+2 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11-bookworm as builder FROM python:3.11-bookworm@sha256:ba7a7ac30c38e119c4304f98ef0e188f90f4f67a958bb6899da9defb99bfb471 as builder
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
@@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
COPY poetry.lock pyproject.toml ./ COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
FROM python:3.11-slim-bookworm FROM python:3.11-slim-bookworm@sha256:cc758519481092eb5a4a5ab0c1b303e288880d59afc601958d19e95b300bc86b
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
+1 -1
View File
@@ -1,4 +1,4 @@
FROM mambaorg/micromamba:bookworm-slim as builder FROM mambaorg/micromamba:bookworm-slim@sha256:e296d47be09fc5d260eba9b191f60496f028a4f3ec41e8a14d48c0bae2c60244 as builder
ENV NODE_ENV=production \ ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \ TRANSFORMERS_CACHE=/cache \
+3 -123
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]] [[package]]
name = "aiocache" name = "aiocache"
@@ -585,68 +585,6 @@ files = [
test = ["PyYAML", "mock", "pytest"] test = ["PyYAML", "mock", "pytest"]
yaml = ["PyYAML"] yaml = ["PyYAML"]
[[package]]
name = "contourpy"
version = "1.1.0"
description = "Python library for calculating contours of 2D quadrilateral grids"
optional = false
python-versions = ">=3.8"
files = [
{file = "contourpy-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc"},
{file = "contourpy-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3"},
{file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655"},
{file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48"},
{file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"},
{file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"},
{file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"},
{file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"},
{file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"},
{file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"},
{file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"},
{file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e"},
{file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18"},
{file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"},
{file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"},
{file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"},
{file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"},
{file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"},
{file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"},
{file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"},
{file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103"},
{file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70"},
{file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"},
{file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"},
{file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"},
{file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"},
{file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"},
{file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"},
{file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"},
{file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9"},
{file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15"},
{file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"},
{file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"},
{file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"},
{file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"},
{file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"},
{file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"},
{file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"},
{file = "contourpy-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76"},
{file = "contourpy-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027"},
{file = "contourpy-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104"},
{file = "contourpy-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f"},
{file = "contourpy-1.1.0.tar.gz", hash = "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21"},
]
[package.dependencies]
numpy = ">=1.16"
[package.extras]
bokeh = ["bokeh", "selenium"]
docs = ["furo", "sphinx-copybutton"]
mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pillow"]
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
test-no-images = ["pytest", "pytest-cov", "wurlitzer"]
[[package]] [[package]]
name = "contourpy" name = "contourpy"
version = "1.1.1" version = "1.1.1"
@@ -2578,64 +2516,6 @@ files = [
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
] ]
[[package]]
name = "pandas"
version = "2.1.0"
description = "Powerful data structures for data analysis, time series, and statistics"
optional = false
python-versions = ">=3.9"
files = [
{file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"},
{file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"},
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"},
{file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"},
{file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"},
{file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"},
{file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"},
{file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"},
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"},
{file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"},
{file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"},
{file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"},
{file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"},
{file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"},
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"},
{file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"},
{file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"},
{file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"},
{file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"},
]
[package.dependencies]
numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""}
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
tzdata = ">=2022.1"
[package.extras]
all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
aws = ["s3fs (>=2022.05.0)"]
clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
compression = ["zstandard (>=0.17.0)"]
computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"]
consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"]
feather = ["pyarrow (>=7.0.0)"]
fss = ["fsspec (>=2022.05.0)"]
gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"]
hdf5 = ["tables (>=3.7.0)"]
html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"]
mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"]
output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"]
parquet = ["pyarrow (>=7.0.0)"]
performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"]
plot = ["matplotlib (>=3.6.1)"]
postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
spss = ["pyreadstat (>=1.1.5)"]
sql-other = ["SQLAlchemy (>=1.4.36)"]
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
xml = ["lxml (>=4.8.0)"]
[[package]] [[package]]
name = "pandas" name = "pandas"
version = "2.1.2" version = "2.1.2"
@@ -4771,5 +4651,5 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "~3.11"
content-hash = "bba5f87aa67bc1d2283a9f4b471ef78e572337f22413870d324e908014410d53" content-hash = "a4c9b3550bb2a67a54b9ab70e700b24fb9eb0b652e90d7dd8ec92abd121ca6e3"
+2 -2
View File
@@ -1,13 +1,13 @@
[tool.poetry] [tool.poetry]
name = "machine-learning" name = "machine-learning"
version = "1.88.2" version = "1.90.0"
description = "" description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"] authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md" readme = "README.md"
packages = [{include = "app"}] packages = [{include = "app"}]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.11" python = "~3.11"
torch = [ torch = [
{markers = "platform_machine == 'arm64' or platform_machine == 'aarch64'", version = "=2.1.0", source = "pypi"}, {markers = "platform_machine == 'arm64' or platform_machine == 'aarch64'", version = "=2.1.0", source = "pypi"},
{markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=2.1.0", source = "pytorch-cpu"} {markers = "platform_machine == 'amd64' or platform_machine == 'x86_64'", version = "=2.1.0", source = "pytorch-cpu"}
-1
View File
@@ -49,7 +49,6 @@ dart_code_metrics:
# Common # Common
- avoid-accessing-collections-by-constant-index - avoid-accessing-collections-by-constant-index
- avoid-accessing-other-classes-private-members - avoid-accessing-other-classes-private-members
- avoid-async-call-in-sync-function
- avoid-cascade-after-if-null - avoid-cascade-after-if-null
- avoid-collapsible-if - avoid-collapsible-if
- avoid-collection-methods-with-unrelated-types - avoid-collection-methods-with-unrelated-types
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 112, "android.injected.version.code" => 114,
"android.injected.version.name" => "1.88.2", "android.injected.version.name" => "1.90.0",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') 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')
+2 -2
View File
@@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
distributionSha256Sum=518a863631feb7452b8f1b3dc2aaee5f388355cc3421bbd0275fbeadd77e84b2 distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Afegeix usuaris", "album_viewer_page_share_add_users": "Afegeix usuaris",
"all_people_page_title": "Persones", "all_people_page_title": "Persones",
"all_videos_page_title": "Vídeos", "all_videos_page_title": "Vídeos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No s'ha trobat res arxivat", "archive_page_no_archived_assets": "No s'ha trobat res arxivat",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Configuració de la memòria cau", "cache_settings_title": "Configuració de la memòria cau",
"change_password_form_confirm_password": "Confirma la contrasenya", "change_password_form_confirm_password": "Confirma la contrasenya",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Místní úložiště", "cache_settings_tile_title": "Místní úložiště",
"cache_settings_title": "Nastavení vyrovnávací paměti", "cache_settings_title": "Nastavení vyrovnávací paměti",
"change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_confirm_password": "Potvrďte heslo",
"change_password_form_description": "Dobrý den, {firstName} {lastName},\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.", "change_password_form_description": "Dobrý den, {name},\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.",
"change_password_form_new_password": "Nové heslo", "change_password_form_new_password": "Nové heslo",
"change_password_form_password_mismatch": "Hesla se neshodují", "change_password_form_password_mismatch": "Hesla se neshodují",
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo", "change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Tilføj brugere", "album_viewer_page_share_add_users": "Tilføj brugere",
"all_people_page_title": "Personer", "all_people_page_title": "Personer",
"all_videos_page_title": "Videoer", "all_videos_page_title": "Videoer",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet", "archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Cache-indstillinger", "cache_settings_title": "Cache-indstillinger",
"change_password_form_confirm_password": "Bekræft kodeord", "change_password_form_confirm_password": "Bekræft kodeord",
"change_password_form_description": "Hej {firstName} {lastName},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.", "change_password_form_description": "Hej {name},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.",
"change_password_form_new_password": "Nyt kodeord", "change_password_form_new_password": "Nyt kodeord",
"change_password_form_password_mismatch": "Kodeord er ikke ens", "change_password_form_password_mismatch": "Kodeord er ikke ens",
"change_password_form_reenter_new_password": "Gentag nyt kodeord", "change_password_form_reenter_new_password": "Gentag nyt kodeord",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokaler Speicher", "cache_settings_tile_title": "Lokaler Speicher",
"cache_settings_title": "Zwischenspeicher Einstellungen", "cache_settings_title": "Zwischenspeicher Einstellungen",
"change_password_form_confirm_password": "Passwort bestätigen", "change_password_form_confirm_password": "Passwort bestätigen",
"change_password_form_description": "Hallo {firstName} {lastName}\n\nDas ist entweder das erste Mal dass du dich einloggst oder eine Anfrage zur Änderung deines Passwortes wurde gestellt. Bitte gebe das neue Passwort ein.", "change_password_form_description": "Hallo {name}\n\nDas ist entweder das erste Mal dass du dich einloggst oder eine Anfrage zur Änderung deines Passwortes wurde gestellt. Bitte gebe das neue Passwort ein.",
"change_password_form_new_password": "Neues Passwort", "change_password_form_new_password": "Neues Passwort",
"change_password_form_password_mismatch": "Passwörter stimmen nicht überein", "change_password_form_password_mismatch": "Passwörter stimmen nicht überein",
"change_password_form_reenter_new_password": "Passwort erneut eingeben", "change_password_form_reenter_new_password": "Passwort erneut eingeben",
+45 -6
View File
@@ -28,10 +28,9 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found",
"archive_page_title": "Archive ({})", "archive_page_title": "Archive ({})",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_automatically": "Automatic", "asset_list_layout_settings_group_automatically": "Automatic",
@@ -123,7 +122,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
@@ -139,11 +138,14 @@
"control_bottom_app_bar_create_new_album": "Create new album", "control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_delete": "Delete", "control_bottom_app_bar_delete": "Delete",
"control_bottom_app_bar_favorite": "Favorite", "control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_share": "Share", "control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_unarchive": "Unarchive", "control_bottom_app_bar_unarchive": "Unarchive",
"control_bottom_app_bar_upload": "Upload", "control_bottom_app_bar_upload": "Upload",
"control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_edit_location": "Edit Location",
"create_album_page_untitled": "Untitled", "create_album_page_untitled": "Untitled",
"create_shared_album_page_create": "Create", "create_shared_album_page_create": "Create",
"create_shared_album_page_share": "Share", "create_shared_album_page_share": "Share",
@@ -165,11 +167,11 @@
"exif_bottom_sheet_description": "Add Description...", "exif_bottom_sheet_description": "Add Description...",
"exif_bottom_sheet_details": "DETAILS", "exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATION", "exif_bottom_sheet_location": "LOCATION",
"exif_bottom_sheet_location_add": "Add a location",
"experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!", "experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental", "experimental_settings_title": "Experimental",
"favorites_page_no_favorites": "No favorite assets found",
"favorites_page_title": "Favorites", "favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
@@ -193,9 +195,11 @@
"library_page_favorites": "Favorites", "library_page_favorites": "Favorites",
"library_page_new_album": "New album", "library_page_new_album": "New album",
"library_page_sharing": "Sharing", "library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created", "library_page_sort_created": "Created date",
"library_page_sort_last_modified": "Last modified", "library_page_sort_last_modified": "Last modified",
"library_page_sort_most_recent_photo": "Most recent photo", "library_page_sort_most_recent_photo": "Most recent photo",
"library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_asset_count": "Number of assets",
"library_page_sort_title": "Album title", "library_page_sort_title": "Album title",
"login_disabled": "Login has been disabled", "login_disabled": "Login has been disabled",
"login_form_api_exception": "API exception. Please check the server URL and try again.", "login_form_api_exception": "API exception. Please check the server URL and try again.",
@@ -390,6 +394,28 @@
"shared_link_edit_show_meta": "Show metadata", "shared_link_edit_show_meta": "Show metadata",
"shared_link_edit_submit_button": "Update link", "shared_link_edit_submit_button": "Update link",
"shared_link_empty": "You don't have any shared links", "shared_link_empty": "You don't have any shared links",
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expired": "Expired",
"shared_link_expires_days": {
"one": "Expires in {} day",
"other": "Expires in {} days"
},
"shared_link_expires_hours": {
"one": "Expires in {} hour",
"other": "Expires in {} hours"
},
"shared_link_expires_minutes": {
"one": "Expires in {} minute",
"other": "Expires in {} minutes"
},
"shared_link_expires_seconds": {
"one": "Expires in {} second",
"other": "Expires in {} seconds"
},
"shared_link_expires_never": "Expires ∞",
"shared_link_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Upload",
"shared_link_manage_links": "Manage Shared links", "shared_link_manage_links": "Manage Shared links",
"share_done": "Done", "share_done": "Done",
"share_invite": "Invite to album", "share_invite": "Invite to album",
@@ -439,5 +465,18 @@
"viewer_remove_from_stack": "Remove from Stack", "viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset", "viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"scaffold_body_error_occured": "Error occured" "scaffold_body_error_occurred": "Error occurred",
"edit_date_time_dialog_date_time": "Date and Time",
"edit_date_time_dialog_timezone": "Timezone",
"action_common_cancel": "Cancel",
"action_common_update": "Update",
"edit_location_dialog_title": "Location",
"map_location_picker_page_use_location": "Use this location",
"location_picker_choose_on_map": "Choose on map",
"location_picker_latitude": "Latitude",
"location_picker_latitude_hint": "Enter your latitude here",
"location_picker_latitude_error": "Enter a valid latitude",
"location_picker_longitude": "Longitude",
"location_picker_longitude_hint": "Enter your longitude here",
"location_picker_longitude_error": "Enter a valid longitude"
} }
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Almacenamiento local", "cache_settings_tile_title": "Almacenamiento local",
"cache_settings_title": "Configuración de la caché", "cache_settings_title": "Configuración de la caché",
"change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_confirm_password": "Confirmar Contraseña",
"change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
"change_password_form_new_password": "Nueva Contraseña", "change_password_form_new_password": "Nueva Contraseña",
"change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_password_mismatch": "Las contraseñas no coinciden",
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Almacenamiento local", "cache_settings_tile_title": "Almacenamiento local",
"cache_settings_title": "Configuración de la caché", "cache_settings_title": "Configuración de la caché",
"change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_confirm_password": "Confirmar Contraseña",
"change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
"change_password_form_new_password": "Nueva Contraseña", "change_password_form_new_password": "Nueva Contraseña",
"change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_password_mismatch": "Las contraseñas no coinciden",
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Almacenamiento local", "cache_settings_tile_title": "Almacenamiento local",
"cache_settings_title": "Configuración de la caché", "cache_settings_title": "Configuración de la caché",
"change_password_form_confirm_password": "Confirmar Contraseña", "change_password_form_confirm_password": "Confirmar Contraseña",
"change_password_form_description": "Hola {firstName} {lastName},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.",
"change_password_form_new_password": "Nueva Contraseña", "change_password_form_new_password": "Nueva Contraseña",
"change_password_form_password_mismatch": "Las contraseñas no coinciden", "change_password_form_password_mismatch": "Las contraseñas no coinciden",
"change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña",
+22
View File
@@ -390,6 +390,28 @@
"shared_link_edit_show_meta": "Mostrar metadatos", "shared_link_edit_show_meta": "Mostrar metadatos",
"shared_link_edit_submit_button": "Actualizar enlace", "shared_link_edit_submit_button": "Actualizar enlace",
"shared_link_empty": "No tienes ningún enlace compartido", "shared_link_empty": "No tienes ningún enlace compartido",
"shared_link_error_server_url_fetch": "No se puede obtener la URL del servidor",
"shared_link_expired": "Expirado",
"shared_link_expires_days": {
"one": "Expira en {} día",
"other": "Expira en {} días"
},
"shared_link_expires_hours": {
"one": "Expira en {} hora",
"other": "Expira en {} horas"
},
"shared_link_expires_minutes": {
"one": "Expira en {} minuto",
"other": "Expira en {} minutos"
},
"shared_link_expires_seconds": {
"one": "Expira en {} segundo",
"other": "Expira en {} segundos"
},
"shared_link_expires_never": "Sin expiración",
"shared_link_info_chip_download": "Descargar",
"shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Subir",
"shared_link_manage_links": "Administrar enlaces compartidos", "shared_link_manage_links": "Administrar enlaces compartidos",
"share_done": "Hecho", "share_done": "Hecho",
"share_invite": "Invitar al álbum", "share_invite": "Invitar al álbum",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Paikallinen tallennustila", "cache_settings_tile_title": "Paikallinen tallennustila",
"cache_settings_title": "Välimuistin asetukset", "cache_settings_title": "Välimuistin asetukset",
"change_password_form_confirm_password": "Vahvista salasana", "change_password_form_confirm_password": "Vahvista salasana",
"change_password_form_description": "Hei {firstName} {lastName},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.", "change_password_form_description": "Hei {name},\n\nTämä on joko ensimmäinen kirjautumisesi järjestelmään tai salasanan vaihtaminen vaihtaminen on pakotettu. Ole hyvä ja syötä uusi salasana alle.",
"change_password_form_new_password": "Uusi salasana", "change_password_form_new_password": "Uusi salasana",
"change_password_form_password_mismatch": "Salasanat eivät täsmää", "change_password_form_password_mismatch": "Salasanat eivät täsmää",
"change_password_form_reenter_new_password": "Uusi salasana uudelleen", "change_password_form_reenter_new_password": "Uusi salasana uudelleen",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Stockage local", "cache_settings_tile_title": "Stockage local",
"cache_settings_title": "Paramètres de mise en cache", "cache_settings_title": "Paramètres de mise en cache",
"change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_confirm_password": "Confirmez le mot de passe",
"change_password_form_description": "Bonjour {firstName} {lastName},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.",
"change_password_form_new_password": "Nouveau mot de passe", "change_password_form_new_password": "Nouveau mot de passe",
"change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas",
"change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Stockage local", "cache_settings_tile_title": "Stockage local",
"cache_settings_title": "Paramètres de mise en cache", "cache_settings_title": "Paramètres de mise en cache",
"change_password_form_confirm_password": "Confirmez le mot de passe", "change_password_form_confirm_password": "Confirmez le mot de passe",
"change_password_form_description": "Bonjour {firstName} {lastName},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé à changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé à changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.",
"change_password_form_new_password": "Nouveau mot de passe", "change_password_form_new_password": "Nouveau mot de passe",
"change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas",
"change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+1 -1
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Felhasználók hozzáadása", "album_viewer_page_share_add_users": "Felhasználók hozzáadása",
"all_people_page_title": "Emberek", "all_people_page_title": "Emberek",
"all_videos_page_title": "Videók", "all_videos_page_title": "Videók",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Nem található archivált média", "archive_page_no_archived_assets": "Nem található archivált média",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Aggiungi utenti", "album_viewer_page_share_add_users": "Aggiungi utenti",
"all_people_page_title": "Persone", "all_people_page_title": "Persone",
"all_videos_page_title": "Video", "all_videos_page_title": "Video",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Nessuna oggetto archiviato", "archive_page_no_archived_assets": "Nessuna oggetto archiviato",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Impostazioni della Cache", "cache_settings_title": "Impostazioni della Cache",
"change_password_form_confirm_password": "Conferma Password ", "change_password_form_confirm_password": "Conferma Password ",
"change_password_form_description": "Ciao {firstName} {lastName},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto", "change_password_form_description": "Ciao {name},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto",
"change_password_form_new_password": "Nuova Password", "change_password_form_new_password": "Nuova Password",
"change_password_form_password_mismatch": "Le password non coincidono", "change_password_form_password_mismatch": "Le password non coincidono",
"change_password_form_reenter_new_password": "Inserisci ancora la nuova password ", "change_password_form_reenter_new_password": "Inserisci ancora la nuova password ",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "로컬 저장소", "cache_settings_tile_title": "로컬 저장소",
"cache_settings_title": "캐시 설정", "cache_settings_title": "캐시 설정",
"change_password_form_confirm_password": "비밀번호 확인", "change_password_form_confirm_password": "비밀번호 확인",
"change_password_form_description": "{firstName} {lastName} 님, 안녕하세요.\n\n시스템에 처음 로그인했거나 비밀번호 변경 요청이 있었습니다. 아래에 새 비밀번호를 입력하세요.", "change_password_form_description": "{name} 님, 안녕하세요.\n\n시스템에 처음 로그인했거나 비밀번호 변경 요청이 있었습니다. 아래에 새 비밀번호를 입력하세요.",
"change_password_form_new_password": "새 비밀번호", "change_password_form_new_password": "새 비밀번호",
"change_password_form_password_mismatch": "비밀번호가 일치하지 않습니다", "change_password_form_password_mismatch": "비밀번호가 일치하지 않습니다",
"change_password_form_reenter_new_password": "새 비밀번호 재입력", "change_password_form_reenter_new_password": "새 비밀번호 재입력",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Pievienot lietotājus", "album_viewer_page_share_add_users": "Pievienot lietotājus",
"all_people_page_title": "Cilvēki", "all_people_page_title": "Cilvēki",
"all_videos_page_title": "Videoklipi", "all_videos_page_title": "Videoklipi",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs", "archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Kešdarbes iestatījumi", "cache_settings_title": "Kešdarbes iestatījumi",
"change_password_form_confirm_password": "Apstiprināt Paroli", "change_password_form_confirm_password": "Apstiprināt Paroli",
"change_password_form_description": "Sveiki {FirstName} {LastName},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.", "change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.",
"change_password_form_new_password": "Jauna Parole", "change_password_form_new_password": "Jauna Parole",
"change_password_form_password_mismatch": "Paroles nesakrīt", "change_password_form_password_mismatch": "Paroles nesakrīt",
"change_password_form_reenter_new_password": "Atkārtoti ievadīt jaunu paroli", "change_password_form_reenter_new_password": "Atkārtoti ievadīt jaunu paroli",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokal lagring", "cache_settings_tile_title": "Lokal lagring",
"cache_settings_title": "Bufringsinnstillinger", "cache_settings_title": "Bufringsinnstillinger",
"change_password_form_confirm_password": "Bekreft passord", "change_password_form_confirm_password": "Bekreft passord",
"change_password_form_description": "Hei {firstName} {lastName}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", "change_password_form_description": "Hei {name}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.",
"change_password_form_new_password": "Nytt passord", "change_password_form_new_password": "Nytt passord",
"change_password_form_password_mismatch": "Passordene stemmer ikke", "change_password_form_password_mismatch": "Passordene stemmer ikke",
"change_password_form_reenter_new_password": "Skriv nytt passord igjen", "change_password_form_reenter_new_password": "Skriv nytt passord igjen",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Gebruikers toevoegen", "album_viewer_page_share_add_users": "Gebruikers toevoegen",
"all_people_page_title": "Personen", "all_people_page_title": "Personen",
"all_videos_page_title": "Video's", "all_videos_page_title": "Video's",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Geen gearchiveerde items gevonden", "archive_page_no_archived_assets": "Geen gearchiveerde items gevonden",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Cache-instellingen", "cache_settings_title": "Cache-instellingen",
"change_password_form_confirm_password": "Bevestig wachtwoord", "change_password_form_confirm_password": "Bevestig wachtwoord",
"change_password_form_description": "Hallo {firstName} {lastName},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.", "change_password_form_description": "Hallo {name},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.",
"change_password_form_new_password": "Nieuw wachtwoord", "change_password_form_new_password": "Nieuw wachtwoord",
"change_password_form_password_mismatch": "Wachtwoorden komen niet overeen", "change_password_form_password_mismatch": "Wachtwoorden komen niet overeen",
"change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in", "change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokalny magazyn", "cache_settings_tile_title": "Lokalny magazyn",
"cache_settings_title": "Ustawienia Buforowania", "cache_settings_title": "Ustawienia Buforowania",
"change_password_form_confirm_password": "Potwierdź Hasło", "change_password_form_confirm_password": "Potwierdź Hasło",
"change_password_form_description": "Cześć {firstName} {lastName},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.", "change_password_form_description": "Cześć {name},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.",
"change_password_form_new_password": "Nowe Hasło", "change_password_form_new_password": "Nowe Hasło",
"change_password_form_password_mismatch": "Hasła nie są zgodne", "change_password_form_password_mismatch": "Hasła nie są zgodne",
"change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło", "change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Локальное хранилище", "cache_settings_tile_title": "Локальное хранилище",
"cache_settings_title": "Настройки кэширования", "cache_settings_title": "Настройки кэширования",
"change_password_form_confirm_password": "Подтвердите пароль", "change_password_form_confirm_password": "Подтвердите пароль",
"change_password_form_description": "Привет {firstName} {lastName},\n\nЭто либо ваш первый вход в систему, либо был сделан запрос на смену пароля. Пожалуйста, введите новый пароль ниже.", "change_password_form_description": "Привет {name},\n\nЭто либо ваш первый вход в систему, либо был сделан запрос на смену пароля. Пожалуйста, введите новый пароль ниже.",
"change_password_form_new_password": "Новый пароль", "change_password_form_new_password": "Новый пароль",
"change_password_form_password_mismatch": "Пароли не совпадают", "change_password_form_password_mismatch": "Пароли не совпадают",
"change_password_form_reenter_new_password": "Повторно введите новый пароль", "change_password_form_reenter_new_password": "Повторно введите новый пароль",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lokálne úložisko", "cache_settings_tile_title": "Lokálne úložisko",
"cache_settings_title": "Nastavenia vyrovnávacej pamäte", "cache_settings_title": "Nastavenia vyrovnávacej pamäte",
"change_password_form_confirm_password": "Potvrďte heslo", "change_password_form_confirm_password": "Potvrďte heslo",
"change_password_form_description": "Dobrý deň, {firstName} {lastName},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.", "change_password_form_description": "Dobrý deň, {name},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.",
"change_password_form_new_password": "Nové heslo", "change_password_form_new_password": "Nové heslo",
"change_password_form_password_mismatch": "Heslá sa nezhodujú", "change_password_form_password_mismatch": "Heslá sa nezhodujú",
"change_password_form_reenter_new_password": "Znova zadajte nové heslo", "change_password_form_reenter_new_password": "Znova zadajte nové heslo",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+1 -1
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Dodaj korisnike", "album_viewer_page_share_add_users": "Dodaj korisnike",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Add users", "album_viewer_page_share_add_users": "Add users",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Lägg till användare", "album_viewer_page_share_add_users": "Lägg till användare",
"all_people_page_title": "People", "all_people_page_title": "People",
"all_videos_page_title": "Videos", "all_videos_page_title": "Videos",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "No archived assets found", "archive_page_no_archived_assets": "No archived assets found",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Cache Inställningar", "cache_settings_title": "Cache Inställningar",
"change_password_form_confirm_password": "Bekräfta lösenord", "change_password_form_confirm_password": "Bekräfta lösenord",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "Nytt lösenord", "change_password_form_new_password": "Nytt lösenord",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "เพิ่มผู้ใช้งาน", "album_viewer_page_share_add_users": "เพิ่มผู้ใช้งาน",
"all_people_page_title": "ผู้คน", "all_people_page_title": "ผู้คน",
"all_videos_page_title": "วิดีโอ", "all_videos_page_title": "วิดีโอ",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "ไม่พบทรัพยากรในที่เก็บถาวร", "archive_page_no_archived_assets": "ไม่พบทรัพยากรในที่เก็บถาวร",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "ตั้งค่าแคช", "cache_settings_title": "ตั้งค่าแคช",
"change_password_form_confirm_password": "ยืนยันรหัสผ่าน", "change_password_form_confirm_password": "ยืนยันรหัสผ่าน",
"change_password_form_description": "สวัสดี {firstName} {lastName},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง", "change_password_form_description": "สวัสดี {name},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง",
"change_password_form_new_password": "รหัสผ่านใหม่", "change_password_form_new_password": "รหัสผ่านใหม่",
"change_password_form_password_mismatch": "รหัสผ่านไม่ตรงกัน", "change_password_form_password_mismatch": "รหัสผ่านไม่ตรงกัน",
"change_password_form_reenter_new_password": "กรอกรหัสผ่านใหม่", "change_password_form_reenter_new_password": "กรอกรหัสผ่านใหม่",
+2 -2
View File
@@ -27,7 +27,7 @@
"album_viewer_page_share_add_users": "Додати користувачів", "album_viewer_page_share_add_users": "Додати користувачів",
"all_people_page_title": "Люди", "all_people_page_title": "Люди",
"all_videos_page_title": "Відео", "all_videos_page_title": "Відео",
"app_bar_signout_dialog_content": "Are you sure you wanna sign out?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"archive_page_no_archived_assets": "Немає архівних елементів", "archive_page_no_archived_assets": "Немає архівних елементів",
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Local Storage", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Налаштування Кешування", "cache_settings_title": "Налаштування Кешування",
"change_password_form_confirm_password": "Підтвердити пароль", "change_password_form_confirm_password": "Підтвердити пароль",
"change_password_form_description": "Привіт {firstName} {lastName},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.", "change_password_form_description": "Привіт {name},\n\nВи або або вперше входите у систему, або було зроблено запит на зміну вашого пароля. \nВведіть ваш новий пароль.",
"change_password_form_new_password": "Новий Пароль", "change_password_form_new_password": "Новий Пароль",
"change_password_form_password_mismatch": "Паролі не співпадають", "change_password_form_password_mismatch": "Паролі не співпадають",
"change_password_form_reenter_new_password": "Повторіть Новий Пароль", "change_password_form_reenter_new_password": "Повторіть Новий Пароль",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "Lưu trữ cục bộ", "cache_settings_tile_title": "Lưu trữ cục bộ",
"cache_settings_title": "Caching Settings", "cache_settings_title": "Caching Settings",
"change_password_form_confirm_password": "Confirm Password", "change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password", "change_password_form_reenter_new_password": "Re-enter New Password",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "本地存储", "cache_settings_tile_title": "本地存储",
"cache_settings_title": "缓存设置", "cache_settings_title": "缓存设置",
"change_password_form_confirm_password": "确认密码", "change_password_form_confirm_password": "确认密码",
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
"change_password_form_new_password": "新密码", "change_password_form_new_password": "新密码",
"change_password_form_password_mismatch": "密码不匹配", "change_password_form_password_mismatch": "密码不匹配",
"change_password_form_reenter_new_password": "重新输入新的密码", "change_password_form_reenter_new_password": "重新输入新的密码",
+1 -1
View File
@@ -119,7 +119,7 @@
"cache_settings_tile_title": "本地存储", "cache_settings_tile_title": "本地存储",
"cache_settings_title": "缓存设置", "cache_settings_title": "缓存设置",
"change_password_form_confirm_password": "确认密码", "change_password_form_confirm_password": "确认密码",
"change_password_form_description": "{firstName} {lastName} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。", "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。\n请在下方输入新密码。",
"change_password_form_new_password": "新密码", "change_password_form_new_password": "新密码",
"change_password_form_password_mismatch": "密码不匹配", "change_password_form_password_mismatch": "密码不匹配",
"change_password_form_reenter_new_password": "重新输入新的密码", "change_password_form_reenter_new_password": "重新输入新的密码",
@@ -45,7 +45,7 @@ class ImmichTestHelper {
await tester.pumpWidget( await tester.pumpWidget(
ProviderScope( ProviderScope(
overrides: [dbProvider.overrideWithValue(db)], overrides: [dbProvider.overrideWithValue(db)],
child: app.getMainWidget(), child: const app.MainWidget(),
), ),
); );
// Post run tasks // Post run tasks
+1 -1
View File
@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta" desc "iOS Beta"
lane :beta do lane :beta do
increment_version_number( increment_version_number(
version_number: "1.88.2" version_number: "1.90.0"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
+3 -3
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Color immichBackgroundColor = const Color(0xFFf6f8fe); const Color immichBackgroundColor = Color(0xFFf6f8fe);
Color immichDarkBackgroundColor = const Color.fromARGB(255, 0, 0, 0); const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
Color immichDarkThemePrimaryColor = const Color.fromARGB(255, 173, 203, 250); const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);
@@ -0,0 +1,36 @@
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:timezone/timezone.dart';
extension TZExtension on Asset {
/// Returns the created time of the asset from the exif info (if available) or from
/// the fileCreatedAt field, adjusted to the timezone value from the exif info along with
/// the timezone offset in [Duration]
(DateTime, Duration) getTZAdjustedTimeAndOffset() {
DateTime dt = fileCreatedAt.toLocal();
if (exifInfo?.dateTimeOriginal != null) {
dt = exifInfo!.dateTimeOriginal!;
if (exifInfo?.timeZone != null) {
dt = dt.toUtc();
try {
final location = getLocation(exifInfo!.timeZone!);
dt = TZDateTime.from(dt, location);
} on LocationNotFoundException {
RegExp re = RegExp(
r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$',
caseSensitive: false,
);
final m = re.firstMatch(exifInfo!.timeZone!);
if (m != null) {
final duration = Duration(
hours: int.parse(m.group(1) ?? '0'),
minutes: int.parse(m.group(2) ?? '0'),
);
dt = dt.add(duration);
return (dt, duration);
}
}
}
}
return (dt, dt.timeZoneOffset);
}
}
@@ -4,22 +4,32 @@ import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/scaffold_error_body.dart'; import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
extension ScaffoldBody<T> on AsyncValue<T> { extension LogOnError<T> on AsyncValue<T> {
static final Logger _scaffoldBodyLog = Logger("ScaffoldBody"); static final Logger _asyncErrorLogger = Logger("AsyncValue");
Widget scaffoldBodyWhen({ Widget widgetWhen({
bool skipLoadingOnRefresh = true,
Widget Function()? onLoading,
Widget Function(Object? error, StackTrace? stack)? onError,
required Widget Function(T data) onData, required Widget Function(T data) onData,
Widget? onError,
}) { }) {
if (isLoading) { if (isLoading) {
return const Center( bool skip = false;
child: ImmichLoadingIndicator(), if (isRefreshing) {
); skip = skipLoadingOnRefresh;
}
if (!skip) {
return onLoading?.call() ??
const Center(
child: ImmichLoadingIndicator(),
);
}
} }
if (hasError && !hasValue) { if (hasError && !hasValue) {
_scaffoldBodyLog.severe("Error occured in AsyncValue", error, stackTrace); _asyncErrorLogger.severe("Error occured", error, stackTrace);
return onError ?? const ScaffoldErrorBody(); return onError?.call(error, stackTrace) ?? const ScaffoldErrorBody();
} }
return onData(requireValue); return onData(requireValue);
@@ -45,7 +45,7 @@ extension ContextHelper on BuildContext {
) => ) =>
AutoRouter.of(this).navigate(route); AutoRouter.of(this).navigate(route);
// Auto-Push replace route from the current context // Auto-Push replace route from the current context
Future<T?> autoReplace<T extends Object?>(PageRouteInfo<dynamic> route) => Future<T?> autoReplace<T extends Object?>(PageRouteInfo<dynamic> route) =>
AutoRouter.of(this).replace(route); AutoRouter.of(this).replace(route);
@@ -0,0 +1,4 @@
extension TZOffsetExtension on Duration {
String formatAsOffset() =>
"${isNegative ? '-' : '+'}${inHours.abs().toString().padLeft(2, '0')}:${inMinutes.abs().remainder(60).toString().padLeft(2, '0')}";
}
+33 -31
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
@@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:timezone/data/latest.dart'; import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
@@ -28,7 +30,6 @@ import 'package:immich_mobile/shared/providers/app_state.provider.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/services/immich_logger.service.dart'; import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:immich_mobile/shared/services/local_notification.service.dart'; import 'package:immich_mobile/shared/services/local_notification.service.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/immich_app_theme.dart';
import 'package:immich_mobile/utils/migration.dart'; import 'package:immich_mobile/utils/migration.dart';
@@ -43,10 +44,11 @@ void main() async {
await initApp(); await initApp();
await migrateDatabaseIfNeeded(db); await migrateDatabaseIfNeeded(db);
HttpOverrides.global = HttpSSLCertOverride(); HttpOverrides.global = HttpSSLCertOverride();
runApp( runApp(
ProviderScope( ProviderScope(
overrides: [dbProvider.overrideWithValue(db)], overrides: [dbProvider.overrideWithValue(db)],
child: getMainWidget(), child: const MainWidget(),
), ),
); );
} }
@@ -108,16 +110,6 @@ Future<Isar> loadDb() async {
return db; return db;
} }
Widget getMainWidget() {
return EasyLocalization(
supportedLocales: locales,
path: translationsPath,
useFallbackTranslations: true,
fallbackLocale: locales.first,
child: const ImmichApp(),
);
}
class ImmichApp extends ConsumerStatefulWidget { class ImmichApp extends ConsumerStatefulWidget {
const ImmichApp({super.key}); const ImmichApp({super.key});
@@ -167,10 +159,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
// Android 8 does not support transparent app bars // Android 8 does not support transparent app bars
final info = await DeviceInfoPlugin().androidInfo; final info = await DeviceInfoPlugin().androidInfo;
if (info.version.sdkInt <= 26) { if (info.version.sdkInt <= 26) {
overlayStyle = overlayStyle = context.isDarkTheme
MediaQuery.of(context).platformBrightness == Brightness.light ? SystemUiOverlayStyle.dark
? SystemUiOverlayStyle.light : SystemUiOverlayStyle.light;
: SystemUiOverlayStyle.dark;
} }
} }
SystemChrome.setSystemUIOverlayStyle(overlayStyle); SystemChrome.setSystemUIOverlayStyle(overlayStyle);
@@ -202,22 +193,33 @@ class ImmichAppState extends ConsumerState<ImmichApp>
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
locale: context.locale, locale: context.locale,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: Stack( home: MaterialApp.router(
children: [ title: 'Immich',
MaterialApp.router( debugShowCheckedModeBanner: false,
title: 'Immich', themeMode: ref.watch(immichThemeProvider),
debugShowCheckedModeBanner: false, darkTheme: immichDarkTheme,
themeMode: ref.watch(immichThemeProvider), theme: immichLightTheme,
darkTheme: immichDarkTheme, routeInformationParser: router.defaultRouteParser(),
theme: immichLightTheme, routerDelegate: router.delegate(
routeInformationParser: router.defaultRouteParser(), navigatorObservers: () => [TabNavigationObserver(ref: ref)],
routerDelegate: router.delegate( ),
navigatorObservers: () => [TabNavigationObserver(ref: ref)],
),
),
const ImmichLoadingOverlay(),
],
), ),
); );
} }
} }
// ignore: prefer-single-widget-per-file
class MainWidget extends StatelessWidget {
const MainWidget({super.key});
@override
Widget build(BuildContext context) {
return EasyLocalization(
supportedLocales: locales,
path: translationsPath,
useFallbackTranslations: true,
fallbackLocale: locales.first,
child: const ImmichApp(),
);
}
}
@@ -95,7 +95,11 @@ class ActivityStatisticsNotifier extends StateNotifier<int> {
} }
Future<void> fetchStatistics() async { Future<void> fetchStatistics() async {
state = await _activityService.getStatistics(albumId, assetId: assetId); final count =
await _activityService.getStatistics(albumId, assetId: assetId);
if (mounted) {
state = count;
}
} }
Future<void> addActivity() async { Future<void> addActivity() async {
@@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.dart'; import 'package:immich_mobile/modules/activities/models/activity.model.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
@@ -88,7 +88,7 @@ class ActivitiesPage extends HookConsumerWidget {
width: 40, width: 40,
height: 30, height: 30,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage( image: DecorationImage(
image: CachedNetworkImageProvider( image: CachedNetworkImageProvider(
getThumbnailUrlForRemoteId( getThumbnailUrlForRemoteId(
@@ -231,11 +231,8 @@ class ActivitiesPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(appBarTitle)), appBar: AppBar(title: Text(appBarTitle)),
body: activities.maybeWhen( body: activities.widgetWhen(
orElse: () { onData: (data) {
return const Center(child: ImmichLoadingIndicator());
},
data: (data) {
final liked = data.firstWhereOrNull( final liked = data.firstWhereOrNull(
(a) => (a) =>
a.type == ActivityType.like && a.type == ActivityType.like &&
@@ -2,11 +2,13 @@ import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/utils/renderlist_generator.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class AlbumNotifier extends StateNotifier<List<Album>> { class AlbumNotifier extends StateNotifier<List<Album>> {
@@ -49,3 +51,24 @@ final albumProvider =
ref.watch(dbProvider), ref.watch(dbProvider),
); );
}); });
final albumWatcher =
StreamProvider.autoDispose.family<Album, int>((ref, albumId) async* {
final db = ref.watch(dbProvider);
final a = await db.albums.get(albumId);
if (a != null) yield a;
await for (final a in db.albums.watchObject(albumId, fireImmediately: true)) {
if (a != null) yield a;
}
});
final albumRenderlistProvider =
StreamProvider.autoDispose.family<RenderList, int>((ref, albumId) {
final album = ref.watch(albumWatcher(albumId)).value;
if (album != null) {
final query =
album.assets.filter().isTrashedEqualTo(false).sortByFileCreatedAtDesc();
return renderListGeneratorWithGroupBy(query, GroupAssetsBy.none);
}
return const Stream.empty();
});
@@ -1,21 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
final albumDetailProvider =
StreamProvider.family<Album, int>((ref, albumId) async* {
final user = ref.watch(currentUserProvider);
if (user == null) return;
final AlbumService service = ref.watch(albumServiceProvider);
await for (final a in service.watchAlbum(albumId)) {
if (a == null) {
throw Exception("Album with ID=$albumId does not exist anymore!");
}
await for (final _ in a.watchRenderList(GroupAssetsBy.none)) {
yield a;
}
}
});
@@ -0,0 +1,131 @@
import 'package:collection/collection.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'album_sort_by_options.provider.g.dart';
typedef AlbumSortFn = List<Album> Function(List<Album> albums, bool isReverse);
class _AlbumSortHandlers {
const _AlbumSortHandlers._();
static const AlbumSortFn created = _sortByCreated;
static List<Album> _sortByCreated(List<Album> albums, bool isReverse) {
final sorted = albums.sortedBy((album) => album.createdAt);
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn title = _sortByTitle;
static List<Album> _sortByTitle(List<Album> albums, bool isReverse) {
final sorted = albums.sortedBy((album) => album.name);
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn lastModified = _sortByLastModified;
static List<Album> _sortByLastModified(List<Album> albums, bool isReverse) {
final sorted = albums.sortedBy((album) => album.modifiedAt);
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn assetCount = _sortByAssetCount;
static List<Album> _sortByAssetCount(List<Album> albums, bool isReverse) {
final sorted =
albums.sorted((a, b) => a.assetCount.compareTo(b.assetCount));
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn mostRecent = _sortByMostRecent;
static List<Album> _sortByMostRecent(List<Album> albums, bool isReverse) {
final sorted = albums.sorted((a, b) {
if (a.endDate != null && b.endDate != null) {
return a.endDate!.compareTo(b.endDate!);
}
if (a.endDate == null) return 1;
if (b.endDate == null) return -1;
return 0;
});
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn mostOldest = _sortByMostOldest;
static List<Album> _sortByMostOldest(List<Album> albums, bool isReverse) {
final sorted = albums.sorted((a, b) {
if (a.startDate != null && b.startDate != null) {
return a.startDate!.compareTo(b.startDate!);
}
if (a.startDate == null) return 1;
if (b.startDate == null) return -1;
return 0;
});
return (isReverse ? sorted.reversed : sorted).toList();
}
}
// Store index allows us to re-arrange the values without affecting the saved prefs
enum AlbumSortMode {
title(1, "library_page_sort_title", _AlbumSortHandlers.title),
assetCount(4, "library_page_sort_asset_count", _AlbumSortHandlers.assetCount),
lastModified(
3,
"library_page_sort_last_modified",
_AlbumSortHandlers.lastModified,
),
created(0, "library_page_sort_created", _AlbumSortHandlers.created),
mostRecent(
2,
"library_page_sort_most_recent_photo",
_AlbumSortHandlers.mostRecent,
),
mostOldest(
5,
"library_page_sort_most_oldest_photo",
_AlbumSortHandlers.mostOldest,
);
final int storeIndex;
final String label;
final AlbumSortFn sortFn;
const AlbumSortMode(this.storeIndex, this.label, this.sortFn);
}
@riverpod
class AlbumSortByOptions extends _$AlbumSortByOptions {
@override
AlbumSortMode build() {
final sortOpt = ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.selectedAlbumSortOrder);
return AlbumSortMode.values.firstWhere(
(e) => e.index == sortOpt,
orElse: () => AlbumSortMode.title,
);
}
void changeSortMode(AlbumSortMode sortOption) {
state = sortOption;
ref.watch(appSettingsServiceProvider).setSetting(
AppSettingsEnum.selectedAlbumSortOrder,
sortOption.storeIndex,
);
}
}
@riverpod
class AlbumSortOrder extends _$AlbumSortOrder {
@override
bool build() {
return ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.selectedAlbumSortReverse);
}
void changeSortDirection(bool isReverse) {
state = isReverse;
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.selectedAlbumSortReverse, isReverse);
}
}
@@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'album_sort_by_options.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$albumSortByOptionsHash() =>
r'8d22fa8b7cbca2d3d7ed20a83bf00211dc948004';
/// See also [AlbumSortByOptions].
@ProviderFor(AlbumSortByOptions)
final albumSortByOptionsProvider =
AutoDisposeNotifierProvider<AlbumSortByOptions, AlbumSortMode>.internal(
AlbumSortByOptions.new,
name: r'albumSortByOptionsProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$albumSortByOptionsHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AlbumSortByOptions = AutoDisposeNotifier<AlbumSortMode>;
String _$albumSortOrderHash() => r'573dea45b4519e69386fc7104c72522e35713440';
/// See also [AlbumSortOrder].
@ProviderFor(AlbumSortOrder)
final albumSortOrderProvider =
AutoDisposeNotifierProvider<AlbumSortOrder, bool>.internal(
AlbumSortOrder.new,
name: r'albumSortOrderProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$albumSortOrderHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AlbumSortOrder = AutoDisposeNotifier<bool>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
@@ -0,0 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/album.dart';
final currentAlbumProvider = StateProvider<Album?>((ref) {
return null;
});
@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
@@ -11,7 +10,7 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
class SharedAlbumNotifier extends StateNotifier<List<Album>> { class SharedAlbumNotifier extends StateNotifier<List<Album>> {
SharedAlbumNotifier(this._albumService, Isar db, this._ref) : super([]) { SharedAlbumNotifier(this._albumService, Isar db) : super([]) {
final query = db.albums.filter().sharedEqualTo(true).sortByCreatedAtDesc(); final query = db.albums.filter().sharedEqualTo(true).sortByCreatedAtDesc();
query.findAll().then((value) => state = value); query.findAll().then((value) => state = value);
_streamSub = query.watch().listen((data) => state = data); _streamSub = query.watch().listen((data) => state = data);
@@ -19,7 +18,6 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
final AlbumService _albumService; final AlbumService _albumService;
late final StreamSubscription<List<Album>> _streamSub; late final StreamSubscription<List<Album>> _streamSub;
final Ref _ref;
Future<Album?> createSharedAlbum( Future<Album?> createSharedAlbum(
String albumName, String albumName,
@@ -68,15 +66,8 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
return result; return result;
} }
Future<bool> setActivityEnabled(Album album, bool activityEnabled) async { Future<bool> setActivityEnabled(Album album, bool activityEnabled) {
final result = return _albumService.setActivityEnabled(album, activityEnabled);
await _albumService.setActivityEnabled(album, activityEnabled);
if (result) {
_ref.invalidate(albumDetailProvider(album.id));
}
return result;
} }
@override @override
@@ -91,6 +82,5 @@ final sharedAlbumProvider =
return SharedAlbumNotifier( return SharedAlbumNotifier(
ref.watch(albumServiceProvider), ref.watch(albumServiceProvider),
ref.watch(dbProvider), ref.watch(dbProvider),
ref,
); );
}); });
@@ -219,11 +219,6 @@ class AlbumService {
); );
} }
Stream<Album?> watchAlbum(int albumId) async* {
yield await _db.albums.get(albumId);
yield* _db.albums.watchObject(albumId);
}
Future<AddAssetsResponse?> addAdditionalAssetToAlbum( Future<AddAssetsResponse?> addAdditionalAssetToAlbum(
Iterable<Asset> assets, Iterable<Asset> assets,
Album album, Album album,
@@ -248,8 +243,12 @@ class AlbumService {
} }
} }
album.assets.addAll(successAssets); await _db.writeTxn(() async {
await _db.writeTxn(() => album.assets.save()); await album.assets.update(link: successAssets);
final a = await _db.albums.get(album.id);
// trigger watcher
await _db.albums.put(a!);
});
return AddAssetsResponse( return AddAssetsResponse(
alreadyInAlbum: duplicatedAssets, alreadyInAlbum: duplicatedAssets,
@@ -359,8 +358,12 @@ class AlbumService {
ids: assets.map((asset) => asset.remoteId!).toList(), ids: assets.map((asset) => asset.remoteId!).toList(),
), ),
); );
album.assets.removeAll(assets); await _db.writeTxn(() async {
await _db.writeTxn(() => album.assets.update(unlink: assets)); await album.assets.update(unlink: assets);
final a = await _db.albums.get(album.id);
// trigger watcher
await _db.albums.put(a!);
});
return true; return true;
} catch (e) { } catch (e) {
@@ -380,7 +383,12 @@ class AlbumService {
); );
album.sharedUsers.remove(user); album.sharedUsers.remove(user);
await _db.writeTxn(() => album.sharedUsers.update(unlink: [user])); await _db.writeTxn(() async {
await album.sharedUsers.update(unlink: [user]);
final a = await _db.albums.get(album.id);
// trigger watcher
await _db.albums.put(a!);
});
return true; return true;
} catch (e) { } catch (e) {
@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart'; import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
@@ -63,9 +62,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
); );
} }
} }
context.pop();
ref.invalidate(albumDetailProvider(album.id));
Navigator.pop(context);
} }
return Card( return Card(
@@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album_sort_by_options.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart'; import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart';
import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/album.dart';
@@ -21,33 +22,44 @@ class AddToAlbumSliverList extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final albumSortMode = ref.watch(albumSortByOptionsProvider);
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
final sortedAlbums = albumSortMode.sortFn(albums, albumSortIsReverse);
final sortedSharedAlbums =
albumSortMode.sortFn(sharedAlbums, albumSortIsReverse);
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1), childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1),
(context, index) { (context, index) {
// Build shared expander // Build shared expander
if (index == 0 && sharedAlbums.isNotEmpty) { if (index == 0 && sortedSharedAlbums.isNotEmpty) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: ExpansionTile( child: ExpansionTile(
title: Text('common_shared'.tr()), title: Text('common_shared'.tr()),
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0), tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
leading: const Icon(Icons.group), leading: const Icon(Icons.group),
children: sharedAlbums children: [
.map( ListView.builder(
(album) => AlbumThumbnailListTile( shrinkWrap: true,
album: album, physics: const ClampingScrollPhysics(),
onTap: enabled ? () => onAddToAlbum(album) : () {}, itemCount: sortedSharedAlbums.length,
), itemBuilder: (context, index) => AlbumThumbnailListTile(
) album: sortedSharedAlbums[index],
.toList(), onTap: enabled
? () => onAddToAlbum(sortedSharedAlbums[index])
: () {},
),
),
],
), ),
); );
} }
// Build albums list // Build albums list
final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0); final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0);
final album = albums[offset]; final album = sortedAlbums[offset];
return AlbumThumbnailListTile( return AlbumThumbnailListTile(
album: album, album: album,
onTap: enabled ? () => onAddToAlbum(album) : () {}, onTap: enabled ? () => onAddToAlbum(album) : () {},

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