Compare commits
10
Commits
7a6123f8e6
...
8119a5ee22
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8119a5ee22 | ||
|
|
54881128d6 | ||
|
|
d15be0dd83 | ||
|
|
47cc38061e | ||
|
|
db411a4497 | ||
|
|
f1a09c1389 | ||
|
|
c8222bba94 | ||
|
|
af66681b99 | ||
|
|
a93bbdf3e7 | ||
|
|
1707635743 |
+21
-26
@@ -35,40 +35,35 @@ url = "https://github.com/opentofu/opentofu/releases/download/v1.13.2/tofu_1.13.
|
||||
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/662742635"
|
||||
|
||||
[[tools.terragrunt]]
|
||||
version = "1.0.3"
|
||||
version = "1.2.0"
|
||||
backend = "aqua:gruntwork-io/terragrunt"
|
||||
|
||||
[tools.terragrunt."platforms.linux-arm64"]
|
||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
|
||||
|
||||
[tools.terragrunt."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
|
||||
checksum = "sha256:1e13b7f0646fa7962bc6c5bfba14a30d6a589325b82561e21b66be5981d86395"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662437526"
|
||||
|
||||
[tools.terragrunt."platforms.linux-x64"]
|
||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
|
||||
|
||||
[tools.terragrunt."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
|
||||
checksum = "sha256:23b5f2f9203d34919f7fd270a4b635b721b4903beca9762a1e4853b159bc9be8"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662437263"
|
||||
|
||||
[tools.terragrunt."platforms.macos-arm64"]
|
||||
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084"
|
||||
checksum = "sha256:79790d841ce71fe94c689fb6f5034afd31ad53d5904937c7a10934cca9d9734b"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_darwin_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662436516"
|
||||
|
||||
[tools.terragrunt."platforms.macos-x64"]
|
||||
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970"
|
||||
checksum = "sha256:0b0e63e80509eb1ffb4f89b892bc6fbf7728f27f0a73974400f1bd82a80b8627"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_darwin_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662436142"
|
||||
|
||||
[tools.terragrunt."platforms.windows-x64"]
|
||||
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412"
|
||||
checksum = "sha256:c9a146b9aad456a289614023ad5778123d819af3cf9efd1c6130f196cfcb1999"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_windows_amd64.exe.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662438255"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[tools]
|
||||
terragrunt = "1.1.6"
|
||||
terragrunt = "1.2.0"
|
||||
opentofu = "1.13.2"
|
||||
|
||||
[tasks."tg:fmt"]
|
||||
|
||||
@@ -355,7 +355,7 @@ describe('/asset', () => {
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||
.send({ livePhotoVideoId: asset.id });
|
||||
|
||||
expect(body).toEqual(errorDto.badRequest('Live photo video does not belong to the user'));
|
||||
expect(body).toEqual(errorDto.badRequest('Not found or no asset.update access'));
|
||||
expect(status).toEqual(400);
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@
|
||||
"failed_job_command": "Command {command} failed for job: {job}",
|
||||
"force_delete_user_warning": "WARNING: This will immediately remove the user and all assets. This cannot be undone and the files cannot be recovered.",
|
||||
"image_format": "Format",
|
||||
"image_format_description": "WebP produces smaller files than JPEG, but is slower to encode.",
|
||||
"image_format_description": "WebP produces smaller files than JPEG, but is slower to encode. JPEG XL is the best option for high-quality compression, but not all browsers and apps can display it.",
|
||||
"image_fullsize_description": "Full-size image with stripped metadata, used when zoomed in",
|
||||
"image_fullsize_enabled": "Enable full-size image generation",
|
||||
"image_fullsize_enabled_description": "Generate full-size image for non-web-friendly formats. When \"Prefer embedded preview\" is enabled, embedded previews are used directly without conversion. Does not affect web-friendly formats like JPEG.",
|
||||
@@ -101,7 +101,7 @@
|
||||
"image_preview_quality_description": "Preview quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness. Setting a low value may affect machine learning quality.",
|
||||
"image_preview_title": "Preview Settings",
|
||||
"image_progressive": "Progressive",
|
||||
"image_progressive_description": "Encode JPEG images progressively for gradual loading display. This has no effect on WebP images.",
|
||||
"image_progressive_description": "Encode JPEG images progressively for gradual loading display. This has no effect on other formats.",
|
||||
"image_quality": "Quality",
|
||||
"image_resolution": "Resolution",
|
||||
"image_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes and can reduce app responsiveness.",
|
||||
|
||||
@@ -7,10 +7,14 @@ import numpy as np
|
||||
import orjson
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
from pillow_jxl import JpegXLImagePlugin
|
||||
|
||||
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
|
||||
_PUNCTUATION_TRANS = str.maketrans("", "", string.punctuation)
|
||||
|
||||
# Requests are already decoded in parallel, so don't start a thread per core for each one
|
||||
JpegXLImagePlugin.DECODE_THREADS = 0
|
||||
|
||||
|
||||
def resize_pil(img: Image.Image, size: int, resample: Image.Resampling = Image.Resampling.BICUBIC) -> Image.Image:
|
||||
if img.width < img.height:
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencies = [
|
||||
"opencv-python-headless>=4.7.0.72,<5.0",
|
||||
"orjson>=3.9.5",
|
||||
"pillow>=13,<14",
|
||||
"pillow-jxl-plugin>=1.3.8,<2.0",
|
||||
"pydantic>=2.0.0,<3",
|
||||
"pydantic-settings>=2.5.2,<3",
|
||||
"python-multipart>=0.0.6,<1.0",
|
||||
|
||||
Generated
+53
-3
@@ -436,7 +436,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.148.0"
|
||||
version = "0.162.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
@@ -446,9 +446,9 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/da/e962004c32f34fb630e8393456cadcd08d4300b6fa0099ec0ae5052b8418/fastapi-0.148.0.tar.gz", hash = "sha256:06ee16c50e429daf993a58ecbb5306d284c4c1ee8135380af39a5e1cfa715187", size = 482594, upload-time = "2026-10-19T12:26:05.615Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/eb/2cf8c80abf09b2800aef0a83b41066fbfda8ee5495b7a08b9d1af40f7046/fastapi-0.162.0.tar.gz", hash = "sha256:6a22c1e2e6e32e0633ee7e170f00009a29b8228b16feb219ee42f32fd8d88a8f", size = 527867, upload-time = "2026-10-23T18:47:33.264Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/8e/7d284c1f06093a5e96e1704369a4053b93a9cf4d41b5b40f5dbf94bde819/fastapi-0.148.0-py3-none-any.whl", hash = "sha256:2848088aa482c875c180ef92b7139fec345393d09cdcf258ba4b8555ab068a3b", size = 145110, upload-time = "2026-10-19T12:26:03.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/4f/3f2c25c9b43970b5e9a08beb5e04776247edea684ddc0930f4f02f0bb6cb/fastapi-0.162.0-py3-none-any.whl", hash = "sha256:3785963de305dd09912426cb81f2d230fce98ab3521cdfe97265c8d14983128c", size = 148912, upload-time = "2026-10-23T18:47:31.734Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -842,6 +842,7 @@ dependencies = [
|
||||
{ name = "opencv-python-headless" },
|
||||
{ name = "orjson" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pillow-jxl-plugin" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-multipart" },
|
||||
@@ -936,6 +937,7 @@ requires-dist = [
|
||||
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
||||
{ name = "orjson", specifier = ">=3.9.5" },
|
||||
{ name = "pillow", specifier = ">=13,<14" },
|
||||
{ name = "pillow-jxl-plugin", specifier = ">=1.3.8,<2.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0,<3" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.5.2,<3" },
|
||||
{ name = "python-multipart", specifier = ">=0.0.6,<1.0" },
|
||||
@@ -1802,6 +1804,54 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/37/66b12dc476041fe523bca29c9d58fbf1ea92a4eaf73acfb9e993948f1191/pillow-13.0.0-pp312-pypy312_pp80-win_amd64.whl", hash = "sha256:567f20f1c75897d7663830da6ea8af719e03d712b1d9ee7f813319f9ece42e5a", size = 7366030, upload-time = "2026-10-16T22:18:06.638Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow-jxl-plugin"
|
||||
version = "1.3.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
{ name = "pillow" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/75/810608d489c2d453e16a0866de5edea2bedd728e8830da22a1ec633f3b80/pillow_jxl_plugin-1.3.8.tar.gz", hash = "sha256:4430fd7757899742079c548a7d2537d6d63cf0f1d323180095bc0f6f03d9d4fa", size = 1602233, upload-time = "2026-07-17T03:23:50.387Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/bb/093aced52ebaafcd050eb21ab07ddcab7efffaa687e264f64d3f0423a4ba/pillow_jxl_plugin-1.3.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f6fd0fa12dd0d98bb55df6bbfff7ad913e4ea27306fbdf5e8543fe1fa40772c", size = 2361426, upload-time = "2026-07-17T03:22:56.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/9f/f272fd205ab511475b135772e85bcaa71e5f3d081e6604b08e6e1d3982a6/pillow_jxl_plugin-1.3.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01b632dbca07b59f7d6e106dad3ac8391e9ffdab1f94f2068a3e8edff1bbd180", size = 2660368, upload-time = "2026-07-17T03:22:48.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/1a/3445d2b4f6d6e4e26f10e9ccd75b60a14295b2d8eb280f0b1bd0bd7caac3/pillow_jxl_plugin-1.3.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6459d131af2cbccab20742f6fe43a3022b021dcd1ee20b05f99b1fb54d0ec525", size = 2788244, upload-time = "2026-07-17T03:22:21.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/f2/3855e83fd1e4043bd7ab286d3f0ca434fbf935598fae57f1af2777620891/pillow_jxl_plugin-1.3.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b9a546ac71757f53f0ca2b14ea8f99c33b70e298a7873477f4548e2d83f817", size = 2500550, upload-time = "2026-07-17T03:22:30.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/13/d9befae0e705474e550b16c90bb6d33c3f07837af63a0e8ce4c84fb546fb/pillow_jxl_plugin-1.3.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd2afd5153e2a7ef230a3e48a679a9e1cb3e59032f79c78bb2f78010a4e9bfc1", size = 2504336, upload-time = "2026-07-17T03:22:40.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/2c/3e671c8dd84f6ed4e47d588df1dc15aae61b0df0302f4bfff7b5c743045b/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b987fb202dd1c71d7f6e103363afd1128d347ffcf540d8416c257c8f1041ff3", size = 9727025, upload-time = "2026-07-17T03:23:06.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/89/a63b75d9c05deb0017ff51ddf3674cef19dbfe9c2fb74117d7e4a66b9a93/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c05cf62a992f48434a6b876fe010e5cae12bd54039854ce9ae0f05c13a58c7ad", size = 8386443, upload-time = "2026-07-17T03:23:19.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4f/b6ead8f5a50103e2c6b264a6d4778b8fcd08006c957b2ad0bccbbb306e97/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0392e5349b67522130ecff9d8dca4713e4c84662a310d2170291b229965e8fd1", size = 9032022, upload-time = "2026-07-17T03:23:30.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/53/b9aa7c156427ad4d94d8c90bd87cb502e7d6326d2689c309aa574baf1ab7/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:88b06a4e5fc0d882b188824990b6571e201ad775d22a09487c8d5ab8aed9c3b7", size = 9233286, upload-time = "2026-07-17T03:23:42.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/e3/b25194909cfef9081f6a1f0e40e699e95ea96b211cae30bbf0f965d6b450/pillow_jxl_plugin-1.3.8-cp312-cp312-win32.whl", hash = "sha256:3345a45a09ba3149001c3e9a1b6991be8aab72c33097c0633e414a254cd283fa", size = 4552597, upload-time = "2026-07-17T03:24:08.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c9/01efd23cb4827f8a1731e1a7652023a06375e933f85e3169e0761d916d6f/pillow_jxl_plugin-1.3.8-cp312-cp312-win_amd64.whl", hash = "sha256:5b1c0d7a0c7e451014b288f0504545bff0f543f2ee1581af628e01e885b75f50", size = 4937252, upload-time = "2026-07-17T03:24:00.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ef/b3aee5725df66399f529c41d97f71893e2b38f665c9278a99519112f03f3/pillow_jxl_plugin-1.3.8-cp312-cp312-win_arm64.whl", hash = "sha256:230a1d086ef5b66055e0dbc9837914becf1d1ca58d45d1404660de0a11fb63c3", size = 2330507, upload-time = "2026-07-17T03:23:53.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/10/18bb2d6ed82184e2427f7501a17c1b413eb50bdcd4ebb42bbfb187bb4290/pillow_jxl_plugin-1.3.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:fe24e957016dcb0c2ec35f72ff3bb8ae78a9468b282f0d423485556de492975c", size = 2361184, upload-time = "2026-07-17T03:22:58.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/3e/5e1156e5c5b402d84ef924153e046ba937ac46e8b14decb3ab6e01a6f40e/pillow_jxl_plugin-1.3.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d648ef965002bee0c98dbf144b9b3c994146856fa4d3c54008b75dda10f69915", size = 2659163, upload-time = "2026-07-17T03:22:50.003Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/fe/e610196142343f80c98502fd204f28e901a1101db6988be6ccc40c0e8ca0/pillow_jxl_plugin-1.3.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34685b27d01e55aabb09d9436d4ddb1f446e8805da82ffd6448c07bbf563354c", size = 2787568, upload-time = "2026-07-17T03:22:23.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/18/a862b140f82be55ce62c681b63c38bf61c962f0c27e32797ebebc0d47548/pillow_jxl_plugin-1.3.8-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:288eb07090c91891ee0dc782786d937af8e53a117835606d7982c29e31072b90", size = 2499957, upload-time = "2026-07-17T03:22:32.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/29/d430cd5f82c08d2950e292d195081e9dd4e2a038a7e1722d79ba7c6e7ed9/pillow_jxl_plugin-1.3.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b39ac44cbe5a98b8a364ed98a16c8c1e197ee997e32731be4be727c55e629e4", size = 2504367, upload-time = "2026-07-17T03:22:41.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/68/6806df96fdd625943c795beca932ac033c255a323b63b051903df96935b6/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0e1550f30768a0aa3f756da10b7807f6fac7fd2e5a18991eb8e00ec5908089d8", size = 9726010, upload-time = "2026-07-17T03:23:08.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/df/d12f42d6e31bb47e2002fcba2db9d92aad19dc3db9ae7d4b2b76152e268a/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b13f860c280d9d5770f49a1909926f15c6e26e50935da52ecb78700f7c377857", size = 8386305, upload-time = "2026-07-17T03:23:20.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/ee/4dc11ad5a90aa6d2eeae6eb0be2376cde0c365423036a078746b3476eca2/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:273908ffb85f68a2374b0934700939c572f32b355c9d128d4e3b6924e72ef274", size = 9031514, upload-time = "2026-07-17T03:23:32.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/76/b3125f13c9551862ad3ae7137d86959cde159b759083f3e7b5e720525fd0/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2be721646718af722f2ca5c1d6b06bf056a61a0db355cf944df53c3723119d4b", size = 9233009, upload-time = "2026-07-17T03:23:44.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/16/1aa669ae22a6c36810a925bbbfb1f4521df1d0402180052331b39ef8dee0/pillow_jxl_plugin-1.3.8-cp313-cp313-win32.whl", hash = "sha256:a2716f1367ed9647da36ce15882a12b4d424892cb5a03ab4b0783039d701c7c2", size = 4551381, upload-time = "2026-07-17T03:24:09.552Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/e7/823f58aaea0d969f876f593a39c85c39fefe1b8ee9ca10a6ffbafe1a254a/pillow_jxl_plugin-1.3.8-cp313-cp313-win_amd64.whl", hash = "sha256:2388374b3493df9add4ef3d97cc70d6a5f51ed188dfe10d2b83ebc55e82e48d4", size = 4936720, upload-time = "2026-07-17T03:24:01.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/12/e04b73832d1b8af3c5c4646127949a362f9ca3d30f42b05156ca5ffc61ea/pillow_jxl_plugin-1.3.8-cp313-cp313-win_arm64.whl", hash = "sha256:48d13d81d1c4933e5d42dd09366a9827583808be0a2fe842abd7358653c7d92f", size = 2330087, upload-time = "2026-07-17T03:23:54.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/30/c37d161345302ebda07f29d8230b15ba83b43c586f14fec98d088b18e982/pillow_jxl_plugin-1.3.8-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:30b72accb8505a46b05f709871da40f09975aea3a6b6f3cb1d646ff0a9aaba5e", size = 2362140, upload-time = "2026-07-17T03:22:59.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/be/01613af0307c31e77aced40bdf9cf3d25ce1333b3e99bb85575bdcbfeca5/pillow_jxl_plugin-1.3.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b5bbc933487207e634802f21990c365b6e61473cea34f6621420f4eda8ffd1df", size = 2660381, upload-time = "2026-07-17T03:22:51.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/8b/89636d315a31eb805681e60e569fe38d5eef3d09f13b1b6f576574387942/pillow_jxl_plugin-1.3.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d06fd4eb038f431db27f1ab2e483fd6967bafb21a7757e1bd5f76b6fd93649", size = 2787875, upload-time = "2026-07-17T03:22:24.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/e0/7b9bdc388d89773a5f9ae74ff0be3b06080ef1a60be57b5897d929012622/pillow_jxl_plugin-1.3.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f65361ce18db59e8949c60599bfa46b7de187e28917fc39b5d816ab4b81cb16d", size = 2500834, upload-time = "2026-07-17T03:22:33.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/25/0903ba5a4c8d948df9217e48182ff7d872098ddf86fe512402569f4041cc/pillow_jxl_plugin-1.3.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b881a40737c647a24270b73cd3f506dd16d634202e5214cb492f9270bd1b02", size = 2504805, upload-time = "2026-07-17T03:22:42.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/ef/77a15b2f3227854d4ef2563250ea57b03538ed962f406f1248c9caf045aa/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4049e48f09947a38047fc5d70817c6dffa90ffb5f14585c8c00e8b534d17c9b", size = 9725768, upload-time = "2026-07-17T03:23:10.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/95/486fc1fabd1e6cb8b520dce9898b51f2e29e991ba54e632381fe7f287300/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6ce20ef0887efb6dace5245f714f94b4f070ef39a1ec45161b35879c48faf30a", size = 8386447, upload-time = "2026-07-17T03:23:22.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/28/7979927eb5dc79e40b31e4e07d074057722656743d324182f4a68056c2a9/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:845e61add39272ca8545f4a0e6ead977ebb7e89fa017014bb8e17273a7a09c6c", size = 9032664, upload-time = "2026-07-17T03:23:34.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b7/f606ed6fcb5fd9e177ce2b15a613a405ac22ceafc2c03bb3f58cd500ca04/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:915b8fee6a97aaf5486d0d625f26faae2e9f0d9b5892f1076f5a29b8103125a4", size = 9233868, upload-time = "2026-07-17T03:23:46.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/6d/d10a4426fc3b394d2222cb694774451e67ab052df2e15a488dfac22be561/pillow_jxl_plugin-1.3.8-cp314-cp314-win32.whl", hash = "sha256:d63d1c3566a8c9ebdd1ec6b75da816b9475d95c4f69656827d6d3957d2594ecb", size = 4554691, upload-time = "2026-07-17T03:24:11.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/77/0f0297c265f1743784b6d1df719e62e49dabd1d9ede8efb0a9ba93174293/pillow_jxl_plugin-1.3.8-cp314-cp314-win_amd64.whl", hash = "sha256:6b23c83aabf4615b92ae609293d8597ca1a8d14d01ab16c703bb3a3df359796b", size = 4939714, upload-time = "2026-07-17T03:24:03.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/cf/2db398fb1a41c46fd382bad96ee90283589eaed0ec2c50cfe4afda3df72c/pillow_jxl_plugin-1.3.8-cp314-cp314-win_arm64.whl", hash = "sha256:60c36bec3d8d4ef23cf2aed0f792213f999312dd3d8cd5225ae1494c430e16f6", size = 2330667, upload-time = "2026-07-17T03:23:55.913Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||
|
||||
lockfile_version = 3
|
||||
|
||||
[[tools."github:extism/cli"]]
|
||||
version = "1.6.3"
|
||||
backend = "github:extism/cli"
|
||||
specifiers = ["1.6.3"]
|
||||
|
||||
[tools."github:extism/cli"."platforms.linux-arm64"]
|
||||
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030"
|
||||
|
||||
[tools."github:extism/cli"."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b"
|
||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030"
|
||||
|
||||
[tools."github:extism/cli"."platforms.linux-x64"]
|
||||
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025"
|
||||
|
||||
[tools."github:extism/cli"."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d"
|
||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025"
|
||||
|
||||
[tools."github:extism/cli"."platforms.macos-arm64"]
|
||||
checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc"
|
||||
url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-arm64.tar.gz"
|
||||
@@ -42,27 +35,18 @@ url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694035"
|
||||
[[tools."github:extism/js-pdk"]]
|
||||
version = "1.7.0"
|
||||
backend = "github:extism/js-pdk"
|
||||
specifiers = ["v1.7.0"]
|
||||
|
||||
[tools."github:extism/js-pdk"."platforms.linux-arm64"]
|
||||
checksum = "sha256:025f4050b199d68413c159bde1187271ae270021a9f7171e7beb509922821f2a"
|
||||
url = "https://github.com/extism/js-pdk/releases/download/v1.7.0/extism-js-aarch64-linux-v1.7.0.gz"
|
||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/518632503"
|
||||
|
||||
[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:025f4050b199d68413c159bde1187271ae270021a9f7171e7beb509922821f2a"
|
||||
url = "https://github.com/extism/js-pdk/releases/download/v1.7.0/extism-js-aarch64-linux-v1.7.0.gz"
|
||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/518632503"
|
||||
|
||||
[tools."github:extism/js-pdk"."platforms.linux-x64"]
|
||||
checksum = "sha256:63b72da2f5e88655522dc21477de549f238a2f40546a69ce4e0fce7e78654035"
|
||||
url = "https://github.com/extism/js-pdk/releases/download/v1.7.0/extism-js-x86_64-linux-v1.7.0.gz"
|
||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/518632991"
|
||||
|
||||
[tools."github:extism/js-pdk"."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:63b72da2f5e88655522dc21477de549f238a2f40546a69ce4e0fce7e78654035"
|
||||
url = "https://github.com/extism/js-pdk/releases/download/v1.7.0/extism-js-x86_64-linux-v1.7.0.gz"
|
||||
url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/518632991"
|
||||
|
||||
[tools."github:extism/js-pdk"."platforms.macos-arm64"]
|
||||
checksum = "sha256:12c01c2bb2240a6a05a4f8babe680c793c397d84e6a1497a7ca1312e78a475c3"
|
||||
url = "https://github.com/extism/js-pdk/releases/download/v1.7.0/extism-js-aarch64-macos-v1.7.0.gz"
|
||||
@@ -81,6 +65,7 @@ url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/518636347"
|
||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
specifiers = ["7.1.3-6"]
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"]
|
||||
checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1"
|
||||
@@ -90,18 +75,33 @@ url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets
|
||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
specifiers = ["7.1.3-6"]
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"]
|
||||
checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838"
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
|
||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||
|
||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
specifiers = ["7.1.3-6"]
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"]
|
||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||
|
||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
specifiers = ["7.1.3-6"]
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz"
|
||||
@@ -114,61 +114,31 @@ url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets
|
||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
specifiers = ["7.1.3-6"]
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz"
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"]
|
||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876"
|
||||
|
||||
[[tools."github:jellyfin/jellyfin-ffmpeg"]]
|
||||
version = "7.1.3-6"
|
||||
backend = "github:jellyfin/jellyfin-ffmpeg"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg".options]
|
||||
asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"]
|
||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879"
|
||||
[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"]
|
||||
checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820"
|
||||
url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz"
|
||||
url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838"
|
||||
|
||||
[[tools."github:webassembly/binaryen"]]
|
||||
version = "version_124"
|
||||
backend = "github:webassembly/binaryen"
|
||||
specifiers = ["version_124"]
|
||||
|
||||
[tools."github:webassembly/binaryen"."platforms.linux-arm64"]
|
||||
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz"
|
||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659"
|
||||
|
||||
[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3"
|
||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz"
|
||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659"
|
||||
|
||||
[tools."github:webassembly/binaryen"."platforms.linux-x64"]
|
||||
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz"
|
||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769"
|
||||
|
||||
[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963"
|
||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz"
|
||||
url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769"
|
||||
|
||||
[tools."github:webassembly/binaryen"."platforms.macos-arm64"]
|
||||
checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c"
|
||||
url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-arm64-macos.tar.gz"
|
||||
@@ -187,9 +157,10 @@ url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288
|
||||
[[tools.java]]
|
||||
version = "21.0.2"
|
||||
backend = "core:java"
|
||||
specifiers = ["21.0.2"]
|
||||
|
||||
[tools.java.options]
|
||||
shorthand_vendor = "openjdk"
|
||||
shorthand_vendor = "temurin"
|
||||
|
||||
[tools.java."platforms.linux-arm64"]
|
||||
checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2"
|
||||
@@ -245,12 +216,16 @@ checksum = "sha256:d2046d28c2c3dcd211ac5145c0bf30e2047ef9dfb306693c8c6e2eccd8b95
|
||||
url = "https://nodejs.org/dist/v24.22.1/node-v24.22.1-win-x64.zip"
|
||||
|
||||
[[tools."npm:@openapitools/openapi-generator-cli"]]
|
||||
version = "2.41.0"
|
||||
version = "2.42.0"
|
||||
aube = { path = ".mise/locks/npm-openapitools-openapi-generator-cli/2.42.0", digest = "sha256:ef9f5cf15f958d31297bb706ee83d1347633603f3079431938665c7bc4136856" }
|
||||
backend = "npm:@openapitools/openapi-generator-cli"
|
||||
specifiers = ["2.42.0"]
|
||||
|
||||
[[tools."npm:oazapfts"]]
|
||||
version = "7.5.0"
|
||||
aube = { path = ".mise/locks/npm-oazapfts/7.5.0", digest = "sha256:74214614b0338eaa2baee026d615afde514311c5c76e274f420f8de8b1f0e017" }
|
||||
backend = "npm:oazapfts"
|
||||
specifiers = ["7.5.0"]
|
||||
|
||||
[[tools.opentofu]]
|
||||
version = "1.13.2"
|
||||
@@ -263,22 +238,12 @@ checksum_source = "mise-versions"
|
||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.13.2/tofu_1.13.2_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/662742799"
|
||||
|
||||
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:9bd0228a81bcd0c88f7045c74378f45a815779f19897191dff7d9efba9976b9e"
|
||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762688"
|
||||
|
||||
[tools.opentofu."platforms.linux-x64"]
|
||||
checksum = "sha256:ac05d9952662c7db28dd57e93f0a68018837bfdf9342384ec0987977c16a9645"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.13.2/tofu_1.13.2_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/662742636"
|
||||
|
||||
[tools.opentofu."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:50a6106fa4de523d09c87af85f3db1dd47535fc005727fdca6852146476b88ec"
|
||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.12.6/tofu_1.12.6_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/520762723"
|
||||
|
||||
[tools.opentofu."platforms.macos-arm64"]
|
||||
checksum = "sha256:0d1cdc85f38992055b48e7fb161304a2ddfe2e5ee0ff68ffec9d0349c8bd1b59"
|
||||
checksum_source = "mise-versions"
|
||||
@@ -298,75 +263,71 @@ url = "https://github.com/opentofu/opentofu/releases/download/v1.13.2/tofu_1.13.
|
||||
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/662742635"
|
||||
|
||||
[[tools.pnpm]]
|
||||
version = "12.14.0"
|
||||
version = "12.15.0"
|
||||
backend = "aqua:pnpm/pnpm"
|
||||
specifiers = ["12.14.0"]
|
||||
specifiers = ["12.15.0"]
|
||||
|
||||
[tools.pnpm."platforms.linux-arm64"]
|
||||
checksum = "sha256:cfc780b9e690549e6633e1045fb1fb6ef84e390711e2099e6ccbae91c32456ea"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.14.0/pnpm-linux-arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/643957409"
|
||||
checksum = "sha256:d7ce93a2c7d847d3a34bd3f559b60c14f578b1320c17cc352a2c5c5fdf956117"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.15.0/pnpm-linux-arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/662173352"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.linux-x64"]
|
||||
checksum = "sha256:3910c3852136bed5bc1511b9137a45fd4357ed6b3970d9d08893641e9005867e"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.14.0/pnpm-linux-x64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/643957410"
|
||||
checksum = "sha256:55b5ed0092bd11176c1669598e231ae52e7cedac59de6efd0d415730f37d0598"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.15.0/pnpm-linux-x64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/662173360"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.macos-arm64"]
|
||||
checksum = "sha256:50f48e0e17508952790a3fbf58fcf14f3b82efa5f594ca04ad904e29aaf854b0"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.14.0/pnpm-darwin-arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/643957413"
|
||||
checksum = "sha256:6a41a2877773e97f19832067187cc6fc82e186d8a2cf91fec4b4c1a5fbd6ef27"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.15.0/pnpm-darwin-arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/662173343"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.macos-x64"]
|
||||
checksum = "sha256:2c412e07382049b4edb04fa6da78f3d4ef7810b75853c80544dcfb7c0ed79880"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.14.0/pnpm-darwin-x64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/643957411"
|
||||
checksum = "sha256:2d7b336884574aee8a765bee48591212e9caf4fcf985fb3279588c1fb73fb47f"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.15.0/pnpm-darwin-x64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/662173347"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[tools.pnpm."platforms.windows-x64"]
|
||||
checksum = "sha256:6f0f38e917c8ce76ef31db528c357e2bfa354a1bcb16464e3cdec39e6530d784"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.14.0/pnpm-win32-x64.zip"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/643957404"
|
||||
checksum = "sha256:84cf69db1be19e95bb4e5bef329291a046f2821b390d87e00f5690809d8a2ed7"
|
||||
url = "https://github.com/pnpm/pnpm/releases/download/v12.15.0/pnpm-win32-x64.zip"
|
||||
url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/662173351"
|
||||
provenance = "github-attestations"
|
||||
|
||||
[[tools.terragrunt]]
|
||||
version = "1.1.1"
|
||||
version = "1.2.0"
|
||||
backend = "aqua:gruntwork-io/terragrunt"
|
||||
specifiers = ["1.2.0"]
|
||||
|
||||
[tools.terragrunt."platforms.linux-arm64"]
|
||||
checksum = "sha256:a374a7993ff3d99665a7e014007d3647ec7f0465c9d55c85e9f94c932e73cea2"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786148"
|
||||
|
||||
[tools.terragrunt."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:a374a7993ff3d99665a7e014007d3647ec7f0465c9d55c85e9f94c932e73cea2"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786148"
|
||||
checksum = "sha256:1e13b7f0646fa7962bc6c5bfba14a30d6a589325b82561e21b66be5981d86395"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_linux_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662437526"
|
||||
|
||||
[tools.terragrunt."platforms.linux-x64"]
|
||||
checksum = "sha256:ce90077ac31ef17a2ba10d11d45f36c6501997a8f4f79d703bb7daba37032f53"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785968"
|
||||
|
||||
[tools.terragrunt."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:ce90077ac31ef17a2ba10d11d45f36c6501997a8f4f79d703bb7daba37032f53"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785968"
|
||||
checksum = "sha256:23b5f2f9203d34919f7fd270a4b635b721b4903beca9762a1e4853b159bc9be8"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_linux_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662437263"
|
||||
|
||||
[tools.terragrunt."platforms.macos-arm64"]
|
||||
checksum = "sha256:9ec8f678b9ae6c81d5d9d77b94cbf6349ce639d5938694b4adc9dea73e416794"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_darwin_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785628"
|
||||
checksum = "sha256:79790d841ce71fe94c689fb6f5034afd31ad53d5904937c7a10934cca9d9734b"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_darwin_arm64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662436516"
|
||||
|
||||
[tools.terragrunt."platforms.macos-x64"]
|
||||
checksum = "sha256:73e768a69fa44a60f9d9174f54aaf179327e08706f633c9c79d5f5c9622c91c2"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_darwin_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785520"
|
||||
checksum = "sha256:0b0e63e80509eb1ffb4f89b892bc6fbf7728f27f0a73974400f1bd82a80b8627"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_darwin_amd64.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662436142"
|
||||
|
||||
[tools.terragrunt."platforms.windows-x64"]
|
||||
checksum = "sha256:dd50a324691e072a3ac879e9ec43d6310e2936c929fda658eb2811fefcc75115"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_windows_amd64.exe.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786421"
|
||||
checksum = "sha256:c9a146b9aad456a289614023ad5778123d819af3cf9efd1c6130f196cfcb1999"
|
||||
checksum_source = "mise-versions"
|
||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.2.0/terragrunt_windows_amd64.exe.tar.gz"
|
||||
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/662438255"
|
||||
|
||||
@@ -17,7 +17,7 @@ config_roots = [
|
||||
[tools]
|
||||
node = "24.22.1"
|
||||
pnpm = "12.15.0"
|
||||
terragrunt = "1.1.6"
|
||||
terragrunt = "1.2.0"
|
||||
opentofu = "1.13.2"
|
||||
"npm:@openapitools/openapi-generator-cli" = "2.42.0"
|
||||
"npm:oazapfts" = "7.5.0"
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 3.3.5-local.11+3030503
|
||||
version: 3.3.5-local.12+3030504
|
||||
|
||||
environment:
|
||||
sdk: '>=3.12.0 <4.0.0'
|
||||
|
||||
Generated
+2
-2
@@ -251,9 +251,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
version = "0.2.191"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
||||
@@ -23133,6 +23133,7 @@
|
||||
"description": "Image format",
|
||||
"enum": [
|
||||
"jpeg",
|
||||
"jxl",
|
||||
"webp"
|
||||
],
|
||||
"type": "string"
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "7.25.0"
|
||||
"version": "7.26.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8214,6 +8214,7 @@ export enum Colorspace {
|
||||
}
|
||||
export enum ImageFormat {
|
||||
Jpeg = "jpeg",
|
||||
Jxl = "jxl",
|
||||
Webp = "webp"
|
||||
}
|
||||
export enum LogLevel {
|
||||
|
||||
Generated
+441
-362
File diff suppressed because it is too large
Load Diff
+84
-48
@@ -1,22 +1,23 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=Llicència&logoColor=000000&labelColor=ececec" alt="Llicència: MIT"></a>
|
||||
<br/>
|
||||
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=Llic%C3%A8ncia&logoColor=000000&labelColor=ececec" alt="Llicència: AGPLv3"></a>
|
||||
<a href="https://discord.immich.app">
|
||||
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
|
||||
<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/>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="../design/immich-logo-stacked-light.svg" width="300" title="Iniciar sessió amb URL personalitzada">
|
||||
<img src="../design/immich-logo-stacked-light.svg" width="300" title="Inicia la sessió amb un URL personalitzat">
|
||||
</p>
|
||||
<h3 align="center">Immich - Solució de còpia de seguretat d'alta rendiment per a fotos i vídeos auto-allotjada</h3>
|
||||
<h3 align="center">Solució de gestió de fotos i vídeos autoallotjada i d’alt rendiment </h3>
|
||||
<br/>
|
||||
<a href="https://immich.app">
|
||||
<img src="../design/immich-screenshots.png" title="Captura de pantalla principal">
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<p align="center">
|
||||
<a href="../README.md">English</a>
|
||||
<a href="README_es_ES.md">Español</a>
|
||||
@@ -36,64 +37,99 @@
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
<a href="README_vi_VN.md">Tiếng Việt</a>
|
||||
<a href="README_th_TH.md">ภาษาไทย</a>
|
||||
<a href="README_ml_IN.md">മലയാളം</a>
|
||||
</p>
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
|
||||
> ⚠️ Seguiu sempre un pla de còpies de seguretat [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) per a les vostres valuoses fotos i vídeos!
|
||||
>
|
||||
|
||||
> [!NOTE]
|
||||
> Podeu trobar la documentació principal, incloent les guies d'instal·lació, a https://immich.app/.
|
||||
> Podeu trobar la documentació principal, incloent-hi les guies d’instal·lació, a https://immich.app/.
|
||||
|
||||
## Contingut
|
||||
## Enllaços
|
||||
|
||||
- [Documentació](https://docs.immich.app/)
|
||||
- [Introducció](https://docs.immich.app/overview/introduction)
|
||||
- [Quant a](https://docs.immich.app/overview/introduction)
|
||||
- [Instal·lació](https://docs.immich.app/install/requirements)
|
||||
- [Mapa de ruta](https://immich.app/roadmap)
|
||||
- [Demo](#demo)
|
||||
- [Full de ruta](https://immich.app/roadmap)
|
||||
- [Demostració](#demostració)
|
||||
- [Funcionalitats](#funcionalitats)
|
||||
- [Traduccions](https://docs.immich.app/developer/translations)
|
||||
- [Directrius de contribució](https://docs.immich.app/overview/support-the-project)
|
||||
- [Contribucions](https://docs.immich.app/overview/support-the-project)
|
||||
|
||||
## Demo
|
||||
## Demostració
|
||||
|
||||
Podeu accedir a la demostració web a https://demo.immich.app. Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app` com a "URL de punt final del servidor".
|
||||
Accediu a la demostració [aquí](https://demo.immich.app). Per a l’aplicació mòbil, podeu utilitzar `https://demo.immich.app` com a `URL de l’endpoint del servidor`.
|
||||
|
||||
```bash title="Credencials de la demo"
|
||||
Les credencials
|
||||
email: demo@immich.app
|
||||
contrasenya: demo
|
||||
```
|
||||
### Credencials d’inici de sessió
|
||||
|
||||
| Adreça electrònica | Contrasenya |
|
||||
| ------------------ | ----------- |
|
||||
| demo@immich.app | demo |
|
||||
|
||||
# Funcionalitats
|
||||
|
||||
| Característiques | Mòbil | Web |
|
||||
| -------------------------------------------- | ------ | --- |
|
||||
| Pujar i veure vídeos i fotos | Sí | Sí |
|
||||
| Còpia de seguretat automàtica en obrir l'aplicació | Sí | N/A |
|
||||
| Selecció d'àlbums per a la còpia de seguretat | Sí | N/A |
|
||||
| Descarregar fotos i vídeos a l'aparell local | Sí | Sí |
|
||||
| Suport per a múltiples usuaris | Sí | Sí |
|
||||
| Àlbums i àlbums compartits | Sí | Sí |
|
||||
| Barra de desplaçament amb funció de rasclet/arrossegament | Sí | Sí |
|
||||
| Suport per a formats raw | Sí | Sí |
|
||||
| Visualització de metadades (EXIF, mapa) | Sí | Sí |
|
||||
| Cerca per metadades, objectes, cares i CLIP | Sí | Sí |
|
||||
| Funcions administratives (gestió d'usuaris) | No | Sí |
|
||||
| Còpia de seguretat en segon pla | Sí | N/A |
|
||||
| Desplaçament virtual | Sí | Sí |
|
||||
| Suport per a OAuth | Sí | Sí |
|
||||
| Claus d'API | N/A | Sí |
|
||||
| Còpia de seguretat i reproducció de LivePhoto | iOS | Sí |
|
||||
| Estructura d'emmagatzematge definida per l'usuari | Sí | Sí |
|
||||
| Compartició pública | No | Sí |
|
||||
| Arxiu i preferits | Sí | Sí |
|
||||
| Mapa global | No | Sí |
|
||||
| Compartició amb associats | Sí | Sí |
|
||||
| Reconeixement facial i agrupament | Sí | Sí |
|
||||
| Records (fa x anys) | Sí | Sí |
|
||||
| Suport fora de línia | Sí | No |
|
||||
| Galeria de només lectura | Sí | Sí |
|
||||
| Funcionalitats | Mòbil | Web |
|
||||
| --------------------------------------------------------------- | ----- | --- |
|
||||
| Pujada i visualització de vídeos i fotos | Sí | Sí |
|
||||
| Còpia al servidor automàtica en obrir l’aplicació | Sí | N/A |
|
||||
| Sistema per a evitar la duplicació de recursos | Sí | Sí |
|
||||
| Selecció d’àlbums per a la còpia al servidor | Sí | N/A |
|
||||
| Baixada de fotos i vídeos al dispositiu local | Sí | Sí |
|
||||
| Suport per a múltiples usuaris | Sí | Sí |
|
||||
| Àlbums i àlbums compartits | Sí | Sí |
|
||||
| Barra de desplaçament amb arrossegament interactiu | Sí | Sí |
|
||||
| Suport per a formats RAW | Sí | Sí |
|
||||
| Visualització de metadades (EXIF, mapa) | Sí | Sí |
|
||||
| Cerca per metadades, objectes, cares i CLIP | Sí | Sí |
|
||||
| Funcions administratives (gestió d’usuaris) | No | Sí |
|
||||
| Còpia al servidor en segon pla | Sí | N/A |
|
||||
| Desplaçament virtual | Sí | Sí |
|
||||
| Suport per a OAuth | Sí | Sí |
|
||||
| Claus de l’API | N/A | Sí |
|
||||
| Còpia al servidor i reproducció de Live Photo/fotos en moviment | Sí | Sí |
|
||||
| Suport per a la visualització d’imatges en 360 graus | No | Sí |
|
||||
| Estructura d’emmagatzematge definida per l’usuari | Sí | Sí |
|
||||
| Compartició pública | Sí | Sí |
|
||||
| Arxiu i preferits | Sí | Sí |
|
||||
| Mapa global | Sí | Sí |
|
||||
| Compartició amb companys | Sí | Sí |
|
||||
| Reconeixement facial i agrupament | Sí | Sí |
|
||||
| Records (fa x anys) | Sí | Sí |
|
||||
| Suport per a mode fora de línia | Sí | No |
|
||||
| Galeria de només lectura | Sí | Sí |
|
||||
| Fotos apilades | Sí | Sí |
|
||||
| Etiquetes | No | Sí |
|
||||
| Visualització de carpetes | Sí | Sí |
|
||||
|
||||
## Traduccions
|
||||
|
||||
Obtingueu més informació sobre les traduccions [aquí](https://docs.immich.app/developer/translations).
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/immich/">
|
||||
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Estat de les traduccions" />
|
||||
</a>
|
||||
|
||||
## Activitat al repositori
|
||||
|
||||

|
||||
|
||||
## Historial d’estrelles
|
||||
|
||||
<a href="https://star-history.com/#immich-app/immich&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=date" />
|
||||
<img alt="Gràfic de l’historial d’estrelles" src="https://api.star-history.com/svg?repos=immich-app/immich&type=date" width="100%" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Contribuïdors
|
||||
|
||||
<a href="https://github.com/immich-app/immich/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
|
||||
</a>
|
||||
|
||||
+3
-2
@@ -2,8 +2,9 @@
|
||||
"contentSecurityPolicy": {
|
||||
"directives": {
|
||||
"default-src": ["'self'"],
|
||||
"script-src": ["'self'", "'wasm-unsafe-eval'", "'unsafe-inline'", "https://www.gstatic.com"],
|
||||
"style-src": ["'self'", "'unsafe-inline'"],
|
||||
"script-src": ["'self'", "'wasm-unsafe-eval'", "https://www.gstatic.com"],
|
||||
"style-src": ["'self'"],
|
||||
"style-src-attr": ["'unsafe-inline'"],
|
||||
"img-src": ["'self'", "data:", "blob:"],
|
||||
"media-src": ["'self'", "data:", "blob:"],
|
||||
"connect-src": [
|
||||
|
||||
@@ -64,6 +64,9 @@ export const excludePaths = ['/.well-known/immich', '/.well-known/webauthn', '/c
|
||||
|
||||
export const FACE_THUMBNAIL_SIZE = 250;
|
||||
|
||||
// The fastest JPEG XL tier skips smoothing filters, which also keeps small thumbnails sharper
|
||||
export const THUMBNAIL_DECODING_TIER = 4;
|
||||
|
||||
type ModelInfo = { dimSize: number };
|
||||
export const CLIP_MODEL_INFO: Record<string, ModelInfo> = {
|
||||
RN101__openai: { dimSize: 512 },
|
||||
|
||||
@@ -610,6 +610,7 @@ export const ColorspaceSchema = z.enum(Colorspace).describe('Colorspace').meta({
|
||||
|
||||
export enum ImageFormat {
|
||||
Jpeg = 'jpeg',
|
||||
Jxl = 'jxl',
|
||||
Webp = 'webp',
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ImmichTelemetry } from 'src/enum.js';
|
||||
import { ConfigRepository, clearEnvCache } from 'src/repositories/config.repository.js';
|
||||
import { ConfigRepository, applyCspHashes, clearEnvCache } from 'src/repositories/config.repository.js';
|
||||
|
||||
const getEnv = () => {
|
||||
clearEnvCache();
|
||||
@@ -325,3 +325,35 @@ describe('getEnv', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyCspHashes', () => {
|
||||
const manifest = { 'script-src': [`'sha256-script'`], 'style-src': [`'sha256-style'`] };
|
||||
|
||||
it('should append the hashes of the inline content', () => {
|
||||
const directives = applyCspHashes({ 'script-src': [`'self'`], 'style-src': [`'self'`] }, manifest);
|
||||
|
||||
expect(directives).toEqual({
|
||||
'script-src': [`'self'`, `'sha256-script'`],
|
||||
'style-src': [`'self'`, `'sha256-style'`],
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip a directive that allows unsafe-inline, which a hash would disable', () => {
|
||||
const directives = applyCspHashes({ 'script-src': [`'self'`, `'unsafe-inline'`] }, manifest);
|
||||
|
||||
expect(directives['script-src']).toEqual([`'self'`, `'unsafe-inline'`]);
|
||||
});
|
||||
|
||||
it('should skip a directive the policy does not declare', () => {
|
||||
const directives = applyCspHashes({ 'script-src': [`'self'`] }, manifest);
|
||||
|
||||
expect(directives).not.toHaveProperty('style-src');
|
||||
});
|
||||
|
||||
it('should not mutate the directives it is given', () => {
|
||||
const directives = { 'script-src': [`'self'`] };
|
||||
applyCspHashes(directives, manifest);
|
||||
|
||||
expect(directives).toEqual({ 'script-src': [`'self'`] });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { HelmetOptions } from 'helmet';
|
||||
import { RedisOptions } from 'ioredis';
|
||||
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
|
||||
import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces/index.js';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import type { VectorExtension } from 'src/types.js';
|
||||
import { IWorker, citiesFile } from 'src/constants.js';
|
||||
@@ -152,7 +152,22 @@ const asSet = <T>(value: string | undefined, defaults: T[]) => {
|
||||
return new Set(values.length === 0 ? defaults : (values as T[]));
|
||||
};
|
||||
|
||||
const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined) => {
|
||||
type CspOptions = Exclude<NonNullable<HelmetOptions['contentSecurityPolicy']>, boolean>;
|
||||
type CspDirectives = NonNullable<CspOptions['directives']>;
|
||||
|
||||
export const applyCspHashes = (directives: CspDirectives, manifest: Record<string, string[]>): CspDirectives => {
|
||||
const merged = { ...directives };
|
||||
for (const [directive, hashes] of Object.entries(manifest)) {
|
||||
const sources = merged[directive];
|
||||
if (Array.isArray(sources) && !sources.includes(`'unsafe-inline'`)) {
|
||||
merged[directive] = [...sources, ...hashes];
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
};
|
||||
|
||||
const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined, webRoot: string) => {
|
||||
// default is off
|
||||
if (!helmetFile || helmetFile === 'false') {
|
||||
return;
|
||||
@@ -160,11 +175,35 @@ const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined) =>
|
||||
|
||||
helmetFile = helmetFile === 'true' ? join(import.meta.dirname, '..', '..', 'helmet.json') : helmetFile;
|
||||
|
||||
let config: HelmetOptions;
|
||||
try {
|
||||
return JSON.parse(readFileSync(helmetFile).toString()) as HelmetOptions;
|
||||
config = JSON.parse(readFileSync(helmetFile).toString()) as HelmetOptions;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read helmet file: ${helmetFile}`, { cause: error });
|
||||
}
|
||||
|
||||
const contentSecurityPolicy = config.contentSecurityPolicy;
|
||||
if (!contentSecurityPolicy || typeof contentSecurityPolicy === 'boolean' || !contentSecurityPolicy.directives) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// merge with csp build-time metadata
|
||||
const manifestFile = join(webRoot, '.csp.json');
|
||||
|
||||
if (!existsSync(join(webRoot, 'index.html')) || !existsSync(manifestFile)) {
|
||||
return config;
|
||||
}
|
||||
|
||||
let manifest: Record<string, string[]>;
|
||||
try {
|
||||
manifest = JSON.parse(readFileSync(manifestFile).toString()) as Record<string, string[]>;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read CSP manifest: ${manifestFile}`, { cause: error });
|
||||
}
|
||||
|
||||
const directives = applyCspHashes(contentSecurityPolicy.directives, manifest);
|
||||
|
||||
return { ...config, contentSecurityPolicy: { ...contentSecurityPolicy, directives } };
|
||||
};
|
||||
|
||||
const getEnv = (): EnvData => {
|
||||
@@ -311,7 +350,7 @@ const getEnv = (): EnvData => {
|
||||
},
|
||||
|
||||
helmet: {
|
||||
config: resolveHelmetFile(dto.IMMICH_HELMET_FILE),
|
||||
config: resolveHelmetFile(dto.IMMICH_HELMET_FILE, folders.web),
|
||||
},
|
||||
|
||||
licensePublicKey: isProd ? productionKeys : stagingKeys,
|
||||
|
||||
@@ -331,7 +331,13 @@ describe(MediaRepository.name, () => {
|
||||
const decoded = await sut.decodeImage(buffer, { colorspace: Colorspace.P3, processInvalidImages: false });
|
||||
await sut.generateThumbnail(
|
||||
decoded,
|
||||
{ colorspace: Colorspace.P3, quality: 80, format: ImageFormat.Webp, processInvalidImages: false },
|
||||
{
|
||||
colorspace: Colorspace.P3,
|
||||
quality: 80,
|
||||
format: ImageFormat.Webp,
|
||||
processInvalidImages: false,
|
||||
decodingTier: 0,
|
||||
},
|
||||
file,
|
||||
);
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ export class MediaRepository {
|
||||
// this is default in libvips (except the threshold is 90), but we need to set it manually in sharp
|
||||
chromaSubsampling: options.quality >= 80 ? '4:4:4' : '4:2:0',
|
||||
progressive: options.progressive,
|
||||
decodingTier: options.decodingTier,
|
||||
})
|
||||
.toFile(output);
|
||||
}
|
||||
|
||||
@@ -422,6 +422,7 @@ describe(AssetMediaService.name, () => {
|
||||
.owner(authStub.user1.user)
|
||||
.build();
|
||||
const asset = AssetFactory.create({ livePhotoVideoId: motionAsset.id });
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([motionAsset.id]));
|
||||
mocks.asset.getById.mockResolvedValueOnce(getForAsset(motionAsset));
|
||||
mocks.asset.create.mockResolvedValueOnce(asset);
|
||||
|
||||
@@ -439,6 +440,7 @@ describe(AssetMediaService.name, () => {
|
||||
it('should hide the linked motion asset', async () => {
|
||||
const motionAsset = AssetFactory.from({ type: AssetType.Video }).owner(authStub.user1.user).build();
|
||||
const asset = AssetFactory.create();
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([motionAsset.id]));
|
||||
mocks.asset.getById.mockResolvedValueOnce(getForAsset(motionAsset));
|
||||
mocks.asset.create.mockResolvedValueOnce(asset);
|
||||
|
||||
@@ -456,6 +458,63 @@ describe(AssetMediaService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should link a live photo video that is already in the shared link', async () => {
|
||||
const motionAsset = AssetFactory.from({ type: AssetType.Video }).owner(authStub.user1.user).build();
|
||||
const asset = AssetFactory.create({ livePhotoVideoId: motionAsset.id });
|
||||
const auth = AuthFactory.from(authStub.user1.user).sharedLink({ allowUpload: true }).build();
|
||||
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([motionAsset.id]));
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([motionAsset.id]));
|
||||
mocks.asset.getById.mockResolvedValueOnce(getForAsset(motionAsset));
|
||||
mocks.asset.create.mockResolvedValueOnce(asset);
|
||||
mocks.sharedLink.addAssets.mockResolvedValue([]);
|
||||
|
||||
await expect(
|
||||
sut.uploadAsset(auth, { ...createDto, livePhotoVideoId: motionAsset.id }, fileStub.livePhotoStill),
|
||||
).resolves.toEqual({
|
||||
status: AssetMediaStatus.CREATED,
|
||||
id: asset.id,
|
||||
});
|
||||
|
||||
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||
auth.sharedLink!.id,
|
||||
new Set([motionAsset.id]),
|
||||
);
|
||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(auth.user.id, new Set([motionAsset.id]), false);
|
||||
expect(mocks.asset.getById).toHaveBeenCalledWith(motionAsset.id);
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({ id: motionAsset.id, visibility: AssetVisibility.Hidden });
|
||||
expect(mocks.asset.create).toHaveBeenCalledWith(expect.objectContaining({ livePhotoVideoId: motionAsset.id }));
|
||||
});
|
||||
|
||||
it('should not link a live photo video that is not in the shared link', async () => {
|
||||
const motionAsset = AssetFactory.from({ type: AssetType.Video }).owner(authStub.user1.user).build();
|
||||
const auth = AuthFactory.from(authStub.user1.user).sharedLink({ allowUpload: true }).build();
|
||||
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set());
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([motionAsset.id]));
|
||||
|
||||
await expect(
|
||||
sut.uploadAsset(auth, { ...createDto, livePhotoVideoId: motionAsset.id }, fileStub.livePhotoStill),
|
||||
).rejects.toThrow('Not found or no asset.update permission');
|
||||
|
||||
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not link a live photo video in the shared link that is owned by another user', async () => {
|
||||
const motionAsset = AssetFactory.from({ type: AssetType.Video }).build();
|
||||
const auth = AuthFactory.from(authStub.user1.user).sharedLink({ allowUpload: true }).build();
|
||||
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([motionAsset.id]));
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set());
|
||||
|
||||
await expect(
|
||||
sut.uploadAsset(auth, { ...createDto, livePhotoVideoId: motionAsset.id }, fileStub.livePhotoStill),
|
||||
).rejects.toThrow('Not found or no asset.update permission');
|
||||
|
||||
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle a sidecar file', async () => {
|
||||
const asset = AssetFactory.from().file({ type: AssetFileType.Sidecar }).build();
|
||||
mocks.asset.getById.mockResolvedValueOnce(getForAsset(asset));
|
||||
|
||||
@@ -142,6 +142,19 @@ export class AssetMediaService extends BaseService {
|
||||
this.requireQuota(auth, file.size);
|
||||
|
||||
if (dto.livePhotoVideoId) {
|
||||
if (auth.sharedLink) {
|
||||
const ids = new Set([dto.livePhotoVideoId]);
|
||||
const [assetIds1, assetIds2] = await Promise.all([
|
||||
this.accessRepository.asset.checkSharedLinkAccess(auth.sharedLink.id, ids),
|
||||
this.accessRepository.asset.checkOwnerAccess(auth.user.id, ids, false),
|
||||
]);
|
||||
|
||||
if (!assetIds1.intersection(assetIds2).has(dto.livePhotoVideoId)) {
|
||||
throw new BadRequestException(`Not found or no ${Permission.AssetUpdate} permission`);
|
||||
}
|
||||
} else {
|
||||
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [dto.livePhotoVideoId] });
|
||||
}
|
||||
await onBeforeLink(
|
||||
{ asset: this.assetRepository, event: this.eventRepository },
|
||||
{ userId: auth.user.id, livePhotoVideoId: dto.livePhotoVideoId },
|
||||
|
||||
@@ -262,7 +262,9 @@ describe(AssetService.name, () => {
|
||||
const auth = AuthFactory.create();
|
||||
const motionAsset = AssetFactory.from().owner(auth.user).build();
|
||||
const asset = AssetFactory.create();
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||
mocks.access.asset.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set([asset.id]))
|
||||
.mockResolvedValueOnce(new Set([motionAsset.id]));
|
||||
mocks.asset.getById.mockResolvedValue(getForAsset(asset));
|
||||
|
||||
await expect(
|
||||
@@ -315,7 +317,10 @@ describe(AssetService.name, () => {
|
||||
it('should link a live video', async () => {
|
||||
const motionAsset = AssetFactory.create({ type: AssetType.Video, visibility: AssetVisibility.Timeline });
|
||||
const stillAsset = AssetFactory.create();
|
||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([stillAsset.id]));
|
||||
mocks.access.asset.checkOwnerAccess
|
||||
.mockResolvedValueOnce(new Set([stillAsset.id]))
|
||||
.mockResolvedValueOnce(new Set([motionAsset.id]))
|
||||
.mockResolvedValue(new Set([stillAsset.id]));
|
||||
mocks.asset.getById.mockResolvedValueOnce(getForAsset(motionAsset));
|
||||
mocks.asset.getById.mockResolvedValueOnce(getForAsset(stillAsset));
|
||||
mocks.asset.update.mockResolvedValue(getForAsset(stillAsset));
|
||||
|
||||
@@ -91,6 +91,7 @@ export class AssetService extends BaseService {
|
||||
|
||||
let previousMotion: { id: string } | null = null;
|
||||
if (rest.livePhotoVideoId) {
|
||||
await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [rest.livePhotoVideoId] });
|
||||
await onBeforeLink(repos, { userId: auth.user.id, livePhotoVideoId: rest.livePhotoVideoId });
|
||||
} else if (rest.livePhotoVideoId === null) {
|
||||
const asset = await this.findOrFail(id);
|
||||
|
||||
@@ -454,6 +454,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -467,6 +468,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -551,6 +553,30 @@ describe(MediaService.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate WebP video thumbnails when JPEG XL is configured', async () => {
|
||||
const asset = AssetFactory.from({ type: AssetType.Video, originalPath: '/original/path.ext' }).exif().build();
|
||||
mocks.systemMetadata.get.mockResolvedValue({
|
||||
image: { preview: { format: ImageFormat.Jxl }, thumbnail: { format: ImageFormat.Jxl } },
|
||||
});
|
||||
mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({
|
||||
...getForGenerateThumbnail(asset),
|
||||
...probeStub.videoStream2160p,
|
||||
});
|
||||
|
||||
await sut.handleGenerateThumbnails({ id: asset.id });
|
||||
|
||||
expect(mocks.media.transcode).toHaveBeenCalledWith(
|
||||
'/original/path.ext',
|
||||
expect.stringMatching(/_preview\.webp$/),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(mocks.media.transcode).toHaveBeenCalledWith(
|
||||
'/original/path.ext',
|
||||
expect.stringMatching(/_thumbnail\.webp$/),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it('should tonemap thumbnail for hdr video', async () => {
|
||||
const asset = AssetFactory.from({ type: AssetType.Video, originalPath: '/original/path.ext' }).exif().build();
|
||||
mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({
|
||||
@@ -726,6 +752,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
previewPath,
|
||||
);
|
||||
@@ -739,6 +766,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 4,
|
||||
},
|
||||
thumbnailPath,
|
||||
);
|
||||
@@ -774,6 +802,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
previewPath,
|
||||
);
|
||||
@@ -787,6 +816,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 4,
|
||||
},
|
||||
thumbnailPath,
|
||||
);
|
||||
@@ -870,6 +900,24 @@ describe(MediaService.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should never set isProgressive for JPEG XL', async () => {
|
||||
const asset = AssetFactory.from().exif().build();
|
||||
mocks.systemMetadata.get.mockResolvedValue({
|
||||
image: {
|
||||
preview: { format: ImageFormat.Jxl, progressive: true },
|
||||
thumbnail: { format: ImageFormat.Jxl, progressive: true },
|
||||
},
|
||||
});
|
||||
mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(getForGenerateThumbnail(asset));
|
||||
|
||||
await sut.handleGenerateThumbnails({ id: asset.id });
|
||||
|
||||
expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([
|
||||
expect.objectContaining({ type: AssetFileType.Preview, isProgressive: false }),
|
||||
expect.objectContaining({ type: AssetFileType.Thumbnail, isProgressive: false }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should never set isProgressive for videos', async () => {
|
||||
const asset = AssetFactory.from({ type: AssetType.Video, originalPath: '/original/path.ext' }).exif().build();
|
||||
mocks.systemMetadata.get.mockResolvedValue({
|
||||
@@ -1079,6 +1127,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1114,6 +1163,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1127,6 +1177,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1160,6 +1211,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1173,6 +1225,7 @@ describe(MediaService.name, () => {
|
||||
size: 1440,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1210,6 +1263,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1274,6 +1328,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1316,6 +1371,7 @@ describe(MediaService.name, () => {
|
||||
progressive: false,
|
||||
processInvalidImages: false,
|
||||
edits: [],
|
||||
decodingTier: 0,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1577,6 +1633,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1630,6 +1687,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1678,6 +1736,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1722,6 +1781,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1766,6 +1826,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1810,6 +1871,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
@@ -1859,6 +1921,7 @@ describe(MediaService.name, () => {
|
||||
],
|
||||
processInvalidImages: false,
|
||||
size: 250,
|
||||
decodingTier: 4,
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
VideoInterfaces,
|
||||
VideoStreamInfo,
|
||||
} from 'src/types.js';
|
||||
import { FACE_THUMBNAIL_SIZE } from 'src/constants.js';
|
||||
import { FACE_THUMBNAIL_SIZE, THUMBNAIL_DECODING_TIER } from 'src/constants.js';
|
||||
import { ImagePathOptions, StorageCore, ThumbnailPathEntity } from 'src/cores/storage.core.js';
|
||||
import { AssetFile } from 'src/database.js';
|
||||
import { OnEvent, OnJob } from 'src/decorators.js';
|
||||
@@ -303,14 +303,14 @@ export class MediaService extends BaseService {
|
||||
fileType: AssetFileType.Preview,
|
||||
format: previewFormat,
|
||||
isEdited: useEdits,
|
||||
isProgressive: !!image.preview.progressive && previewFormat !== ImageFormat.Webp,
|
||||
isProgressive: !!image.preview.progressive && previewFormat === ImageFormat.Jpeg,
|
||||
isTransparent,
|
||||
});
|
||||
const thumbnailFile = this.getImageFile(asset, {
|
||||
fileType: AssetFileType.Thumbnail,
|
||||
format: thumbnailFormat,
|
||||
isEdited: useEdits,
|
||||
isProgressive: !!image.thumbnail.progressive && thumbnailFormat !== ImageFormat.Webp,
|
||||
isProgressive: !!image.thumbnail.progressive && thumbnailFormat === ImageFormat.Jpeg,
|
||||
isTransparent,
|
||||
});
|
||||
this.storageCore.ensureFolders(previewFile.path);
|
||||
@@ -318,8 +318,13 @@ export class MediaService extends BaseService {
|
||||
// generate final images
|
||||
const decoded = { data, info };
|
||||
const baseOptions = { colorspace, processInvalidImages: false, edits: useEdits ? asset.edits : [] };
|
||||
const thumbnailOptions = { ...image.thumbnail, ...baseOptions, format: thumbnailFormat };
|
||||
const previewOptions = { ...image.preview, ...baseOptions, format: previewFormat };
|
||||
const thumbnailOptions = {
|
||||
...image.thumbnail,
|
||||
...baseOptions,
|
||||
format: thumbnailFormat,
|
||||
decodingTier: THUMBNAIL_DECODING_TIER,
|
||||
} as const;
|
||||
const previewOptions = { ...image.preview, ...baseOptions, format: previewFormat, decodingTier: 0 } as const;
|
||||
const promises = [
|
||||
this.mediaRepository.generateThumbhash(decoded, baseOptions),
|
||||
this.mediaRepository.generateThumbnail(decoded, thumbnailOptions, thumbnailFile.path),
|
||||
@@ -335,7 +340,7 @@ export class MediaService extends BaseService {
|
||||
fileType: AssetFileType.FullSize,
|
||||
format: fullsizeFormat,
|
||||
isEdited: useEdits,
|
||||
isProgressive: !!image.fullsize.progressive && fullsizeFormat !== ImageFormat.Webp,
|
||||
isProgressive: !!image.fullsize.progressive && fullsizeFormat === ImageFormat.Jpeg,
|
||||
isTransparent,
|
||||
});
|
||||
const fullsizeOptions = {
|
||||
@@ -343,14 +348,15 @@ export class MediaService extends BaseService {
|
||||
format: fullsizeFormat,
|
||||
quality: image.fullsize.quality,
|
||||
progressive: image.fullsize.progressive,
|
||||
};
|
||||
decodingTier: 0,
|
||||
} as const;
|
||||
promises.push(this.mediaRepository.generateThumbnail(decoded, fullsizeOptions, fullsizeFile.path));
|
||||
} else if (generateFullsize && extracted && extracted.format === RawExtractedFormat.Jpeg) {
|
||||
fullsizeFile = this.getImageFile(asset, {
|
||||
fileType: AssetFileType.FullSize,
|
||||
format: extracted.format,
|
||||
isEdited: false,
|
||||
isProgressive: !!image.fullsize.progressive && image.fullsize.format !== ImageFormat.Webp,
|
||||
isProgressive: !!image.fullsize.progressive && image.fullsize.format === ImageFormat.Jpeg,
|
||||
isTransparent,
|
||||
});
|
||||
this.storageCore.ensureFolders(fullsizeFile.path);
|
||||
@@ -444,6 +450,7 @@ export class MediaService extends BaseService {
|
||||
),
|
||||
},
|
||||
],
|
||||
decodingTier: THUMBNAIL_DECODING_TIER,
|
||||
};
|
||||
|
||||
await this.mediaRepository.generateThumbnail(decoded, thumbnailOptions, thumbnailPath);
|
||||
@@ -493,14 +500,15 @@ export class MediaService extends BaseService {
|
||||
private async generateVideoThumbnails(asset: ThumbnailAsset, { ffmpeg, image }: SystemConfig) {
|
||||
const previewFile = this.getImageFile(asset, {
|
||||
fileType: AssetFileType.Preview,
|
||||
format: image.preview.format,
|
||||
// TODO: write JPEG XL for video thumbnails
|
||||
format: image.preview.format === ImageFormat.Jxl ? ImageFormat.Webp : image.preview.format,
|
||||
isEdited: false,
|
||||
isProgressive: false,
|
||||
isTransparent: false,
|
||||
});
|
||||
const thumbnailFile = this.getImageFile(asset, {
|
||||
fileType: AssetFileType.Thumbnail,
|
||||
format: image.thumbnail.format,
|
||||
format: image.thumbnail.format === ImageFormat.Jxl ? ImageFormat.Webp : image.thumbnail.format,
|
||||
isEdited: false,
|
||||
isProgressive: false,
|
||||
isTransparent: false,
|
||||
|
||||
+4
-11
@@ -43,20 +43,15 @@ export type DeepPartial<T> = T extends Date
|
||||
|
||||
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
||||
|
||||
export type FullsizeImageOptions = {
|
||||
format: ImageFormat;
|
||||
quality: number;
|
||||
enabled: boolean;
|
||||
progressive?: boolean;
|
||||
};
|
||||
|
||||
export type ImageOptions = {
|
||||
format: ImageFormat;
|
||||
quality: number;
|
||||
size: number;
|
||||
progressive?: boolean;
|
||||
decodingTier: 0 | 1 | 2 | 3 | 4;
|
||||
};
|
||||
|
||||
export type FullsizeImageOptions = ImageOptions & { enabled: boolean };
|
||||
|
||||
export type RawImageInfo = {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -84,9 +79,7 @@ export type TransformOptions = {
|
||||
edits?: AssetEditActionItem[];
|
||||
};
|
||||
|
||||
export type GenerateThumbnailOptions = Pick<ImageOptions, 'format' | 'quality' | 'progressive'> &
|
||||
ImageColorOptions &
|
||||
TransformOptions;
|
||||
export type GenerateThumbnailOptions = ImageOptions & ImageColorOptions & TransformOptions;
|
||||
export type GenerateThumbhashOptions = ImageColorOptions & Pick<TransformOptions, 'edits'>;
|
||||
|
||||
export interface VideoStreamInfo {
|
||||
|
||||
@@ -153,11 +153,8 @@ export const onBeforeLink = async (
|
||||
if (motionAsset.type !== AssetType.Video) {
|
||||
throw new BadRequestException('Live photo video must be a video');
|
||||
}
|
||||
if (motionAsset.ownerId !== userId) {
|
||||
throw new BadRequestException('Live photo video does not belong to the user');
|
||||
}
|
||||
|
||||
if (!motionAsset || motionAsset.visibility !== AssetVisibility.Timeline) {
|
||||
if (motionAsset.visibility !== AssetVisibility.Timeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { join } from 'node:path';
|
||||
import { THUMBNAIL_DECODING_TIER } from 'src/constants.js';
|
||||
import { StorageCore } from 'src/cores/storage.core.js';
|
||||
import { SystemConfig } from 'src/dtos/config.dto.js';
|
||||
import { StorageFolder } from 'src/enum.js';
|
||||
@@ -33,6 +34,7 @@ export const generateProfileImage = async (
|
||||
progressive: image.thumbnail.progressive,
|
||||
size: image.thumbnail.size,
|
||||
processInvalidImages: false,
|
||||
decodingTier: THUMBNAIL_DECODING_TIER,
|
||||
},
|
||||
outputPath,
|
||||
);
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
"factory.ts": "^1.4.1",
|
||||
"globals": "^17.0.0",
|
||||
"happy-dom": "^21.0.0",
|
||||
"parse5": "^8.0.1",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^4.0.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import Image from '#lib/components/Image.svelte';
|
||||
import { cancelImageUrl } from '#lib/utils/sw-messaging.ts';
|
||||
import { fireEvent, render } from '@testing-library/svelte';
|
||||
|
||||
vi.mock('#lib/utils/sw-messaging.js', () => ({
|
||||
vi.mock('#lib/utils/sw-messaging.ts', () => ({
|
||||
cancelImageUrl: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderWithTooltips } from '#tests/helpers';
|
||||
import { renderWithTooltips } from '#tests/helpers.ts';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import SharedLinkFormFields from './SharedLinkFormFields.svelte';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { sdkMock } from '#lib/__mocks__/sdk.mock.ts';
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, waitFor, type RenderResult } from '@testing-library/svelte';
|
||||
import { init, register, waitLocale } from 'svelte-i18n';
|
||||
import { albumFactory } from '#test-data/factories/album-factory';
|
||||
import { albumFactory } from '#test-data/factories/album-factory.ts';
|
||||
import AlbumCard from '../AlbumCard.svelte';
|
||||
|
||||
describe('AlbumCard component', () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AlbumCover from '#lib/components/album-page/AlbumCover.svelte';
|
||||
import { getAssetMediaUrl } from '#lib/utils.ts';
|
||||
import { render } from '@testing-library/svelte';
|
||||
import { albumFactory } from '#test-data/factories/album-factory';
|
||||
import { albumFactory } from '#test-data/factories/album-factory.ts';
|
||||
|
||||
vi.mock('#lib/utils.js');
|
||||
vi.mock('#lib/utils.ts');
|
||||
|
||||
describe('AlbumCover component', () => {
|
||||
it('renders an image when the album has a thumbnail', () => {
|
||||
|
||||
@@ -2,10 +2,10 @@ import '@testing-library/jest-dom';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { init, register, waitLocale } from 'svelte-i18n';
|
||||
import { writable } from 'svelte/store';
|
||||
import { albumFactory } from '#test-data/factories/album-factory';
|
||||
import { albumFactory } from '#test-data/factories/album-factory.ts';
|
||||
import AlbumSummary from '../AlbumSummary.svelte';
|
||||
|
||||
vitest.mock('#lib/stores/preferences.store.js', () => ({
|
||||
vitest.mock('#lib/stores/preferences.store.ts', () => ({
|
||||
locale: writable('en'),
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@ import { getAnimateMock } from '#lib/__mocks__/animate.mock.ts';
|
||||
import { getResizeObserverMock } from '#lib/__mocks__/resize-observer.mock.ts';
|
||||
import { authManager } from '#lib/managers/auth-manager.svelte.ts';
|
||||
import { SlideshowState, slideshowStore } from '#lib/stores/slideshow.store.ts';
|
||||
import { renderWithTooltips } from '#tests/helpers';
|
||||
import { renderWithTooltips } from '#tests/helpers.ts';
|
||||
import { updateAsset } from '@immich/sdk';
|
||||
import { fireEvent, waitFor } from '@testing-library/svelte';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory.ts';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory.ts';
|
||||
import AssetViewer from './AssetViewer.svelte';
|
||||
|
||||
vi.mock('#lib/managers/feature-flags-manager.svelte.js', () => ({
|
||||
vi.mock('#lib/managers/feature-flags-manager.svelte.ts', () => ({
|
||||
featureFlagsManager: {
|
||||
init: vi.fn(),
|
||||
loadFeatureFlags: vi.fn(),
|
||||
@@ -18,7 +18,7 @@ vi.mock('#lib/managers/feature-flags-manager.svelte.js', () => ({
|
||||
} as never,
|
||||
}));
|
||||
|
||||
vi.mock('#lib/stores/ocr.svelte.js', () => ({
|
||||
vi.mock('#lib/stores/ocr.svelte.ts', () => ({
|
||||
ocrManager: {
|
||||
clear: vi.fn(),
|
||||
getAssetOcr: vi.fn(),
|
||||
|
||||
@@ -2,15 +2,15 @@ import { getResizeObserverMock } from '#lib/__mocks__/resize-observer.mock.ts';
|
||||
import { authManager } from '#lib/managers/auth-manager.svelte.ts';
|
||||
import { queryClient } from '#lib/queries/client.ts';
|
||||
import { getUserConfigQuery } from '#lib/queries/config-user.query.ts';
|
||||
import { renderWithTooltips } from '#tests/helpers';
|
||||
import { renderWithTooltips } from '#tests/helpers.ts';
|
||||
import type { UserConfigDto } from '@immich/sdk';
|
||||
import '@testing-library/jest-dom';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory.ts';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory.ts';
|
||||
import AssetViewerNavBar from './AssetViewerNavBar.svelte';
|
||||
|
||||
vi.mock(import('#lib/managers/feature-flags-manager.svelte.js'), function () {
|
||||
vi.mock(import('#lib/managers/feature-flags-manager.svelte.ts'), function () {
|
||||
return {
|
||||
featureFlagsManager: {
|
||||
init: vi.fn(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import DetailPanelDescription from './DetailPanelDescription.svelte';
|
||||
|
||||
describe('DetailPanelDescription', () => {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import './immich-time-range';
|
||||
import './immich-time-range.ts';
|
||||
import { createPreloadedQuery } from '#lib/queries/client.ts';
|
||||
import { getPublicConfigQuery } from '#lib/queries/config-public.query.ts';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { renderWithTooltips } from '#tests/helpers';
|
||||
import { renderWithTooltips } from '#tests/helpers.ts';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import '@testing-library/jest-dom';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import DeleteAction from './DeleteAction.svelte';
|
||||
|
||||
vi.mock(import('#lib/managers/feature-flags-manager.svelte.js'), () => {
|
||||
vi.mock(import('#lib/managers/feature-flags-manager.svelte.ts'), () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return { featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: { trash: true } } as any };
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { IconButton, modalManager, toastManager } from '@immich/ui';
|
||||
import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { OnAction, PreAction } from './action';
|
||||
import type { OnAction, PreAction } from './action.ts';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { mdiHistory } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { OnAction } from './action';
|
||||
import type { OnAction } from './action.ts';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { modalManager } from '@immich/ui';
|
||||
import { mdiLockOpenVariantOutline, mdiLockOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { OnAction, PreAction } from './action';
|
||||
import type { OnAction, PreAction } from './action.ts';
|
||||
|
||||
interface Props {
|
||||
asset: TimelineAsset;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ImageThumbnail from '#lib/components/assets/thumbnail/ImageThumbnail.svelte';
|
||||
import { fireEvent, render } from '@testing-library/svelte';
|
||||
|
||||
vi.mock('#lib/utils/sw-messaging.js', () => ({
|
||||
vi.mock('#lib/utils/sw-messaging.ts', () => ({
|
||||
cancelImageUrl: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { getIntersectionObserverMock } from '#lib/__mocks__/intersection-observe
|
||||
import Thumbnail from '#lib/components/assets/thumbnail/Thumbnail.svelte';
|
||||
import { getTabbable } from '#lib/utils/focus-util.ts';
|
||||
import { render } from '@testing-library/svelte';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
|
||||
vi.mock('#lib/utils/navigation.js', () => ({
|
||||
vi.mock('#lib/utils/navigation.ts', () => ({
|
||||
currentUrlReplaceAssetId: vi.fn(),
|
||||
isSharedLinkRoute: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { locale } from '#lib/stores/preferences.store.ts';
|
||||
import { renderWithTooltips } from '#tests/helpers';
|
||||
import { renderWithTooltips } from '#tests/helpers.ts';
|
||||
import { screen } from '@testing-library/svelte';
|
||||
import { DateTime } from 'luxon';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import MaintenanceBackupEntry from './MaintenanceBackupEntry.svelte';
|
||||
|
||||
vi.mock('#lib/services/database-backups.service.js', () => ({
|
||||
vi.mock('#lib/services/database-backups.service.ts', () => ({
|
||||
getDatabaseBackupActions: () => ({
|
||||
Download: { type: 'command', title: 'Download', onAction: vi.fn() },
|
||||
Delete: { type: 'command', title: 'Delete', onAction: vi.fn() },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MemoryType, type MemoryResponseDto } from '@immich/sdk';
|
||||
import { cleanup, fireEvent, render } from '@testing-library/svelte';
|
||||
import { tick } from 'svelte';
|
||||
import { assetFactory } from '@test-data/factories/asset-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import MemoriesLane from './__test__/MemoriesLaneHarness.svelte';
|
||||
|
||||
const date = '2026-10-18';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import MemoryCard from '$lib/components/memories/MemoryCard.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { getAssetMediaUrl, memoryLaneTitle } from '$lib/utils';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import MemoryCard from '#lib/components/memories/MemoryCard.svelte';
|
||||
import { Route } from '#lib/route.ts';
|
||||
import { getAssetMediaUrl, memoryLaneTitle } from '#lib/utils.ts';
|
||||
import { getAltText } from '#lib/utils/thumbnail-util.ts';
|
||||
import { toTimelineAsset } from '#lib/utils/timeline-util.ts';
|
||||
import { MemoryType, type MemoryResponseDto } from '@immich/sdk';
|
||||
import { IconButton } from '@immich/ui';
|
||||
import { mdiChevronLeft, mdiChevronRight, mdiPause, mdiPlay } from '@mdi/js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import MemoriesLane from '$lib/components/memories/MemoriesLane.svelte';
|
||||
import MemoriesLane from '#lib/components/memories/MemoriesLane.svelte';
|
||||
import { TooltipProvider } from '@immich/ui';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
||||
} from '#lib/components/shared-components/album-selection/album-selection-utils.ts';
|
||||
import { AlbumSortBy, SortOrder } from '#lib/stores/preferences.store.ts';
|
||||
import type { AlbumResponseDto } from '@immich/sdk';
|
||||
import { albumFactory } from '#test-data/factories/album-factory';
|
||||
import { albumFactory } from '#test-data/factories/album-factory.ts';
|
||||
|
||||
// Some helper functions to make tests below more readable
|
||||
const createNewAlbumRow = (selected: boolean) => ({
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
Popup,
|
||||
ScaleControl,
|
||||
} from 'svelte-maplibre';
|
||||
import type { SelectionBBox } from './types';
|
||||
import type { SelectionBBox } from './types.ts';
|
||||
import { createPreloadedQuery } from '#lib/queries/client.ts';
|
||||
import { getPublicConfigQuery } from '#lib/queries/config-public.query.ts';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import SearchFilters from './SearchFilters.svelte';
|
||||
import { searchManager } from '#lib/managers/search-manager.svelte.ts';
|
||||
import { getSearchTypePlaceholder, isPopoverContent } from './search-bar-utils';
|
||||
import { getSearchTypePlaceholder, isPopoverContent } from './search-bar-utils.ts';
|
||||
|
||||
type Props = {
|
||||
grayTheme: boolean;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getSearchDatePreset, SearchDatePreset } from './search-bar-utils';
|
||||
import { getSearchDatePreset, SearchDatePreset } from './search-bar-utils.ts';
|
||||
import { DatePicker, Text } from '@immich/ui';
|
||||
import { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
getSearchPlacesTitle,
|
||||
getSearchTagsTitle,
|
||||
getSearchTypeTitle,
|
||||
} from './search-bar-utils';
|
||||
} from './search-bar-utils.ts';
|
||||
import { onMount } from 'svelte';
|
||||
import { searchManager } from '#lib/managers/search-manager.svelte.ts';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ImageThumbnail from '#lib/components/assets/thumbnail/ImageThumbnail.svelte';
|
||||
import { getPeople, getSearchPeopleTitle } from './search-bar-utils';
|
||||
import { getPeople, getSearchPeopleTitle } from './search-bar-utils.ts';
|
||||
import SingleGridRow from '#lib/components/shared-components/SingleGridRow.svelte';
|
||||
import SearchBar from '#lib/elements/SearchBar.svelte';
|
||||
import { getPeopleThumbnailUrl } from '#lib/utils.ts';
|
||||
|
||||
@@ -7,8 +7,8 @@ import type { TagResponseDto } from '@immich/sdk';
|
||||
import '@testing-library/jest-dom';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory.ts';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory.ts';
|
||||
|
||||
const tag = (id: string, value: string): TagResponseDto => ({
|
||||
id,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { mdiClose } from '@mdi/js';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
import { getSearchTagsTitle } from './search-bar-utils';
|
||||
import { getSearchTagsTitle } from './search-bar-utils.ts';
|
||||
import { searchManager } from '#lib/managers/search-manager.svelte.ts';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getSearchTypeTitle } from './search-bar-utils';
|
||||
import { getSearchTypeTitle } from './search-bar-utils.ts';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
import { searchManager } from '#lib/managers/search-manager.svelte.ts';
|
||||
import { Text } from '@immich/ui';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { sdkMock } from '#lib/__mocks__/sdk.mock.ts';
|
||||
import RecentAlbums from '#lib/components/shared-components/side-bar/RecentAlbums.svelte';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { albumFactory } from '#test-data/factories/album-factory';
|
||||
import { albumFactory } from '#test-data/factories/album-factory.ts';
|
||||
|
||||
describe('RecentAlbums component', () => {
|
||||
it('sorts albums by most recently updated', async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SideBarSection from '#lib/components/sidebar/Sidebar.svelte';
|
||||
import { sidebarStore } from '#lib/stores/sidebar.svelte.js';
|
||||
import { sidebarStore } from '#lib/stores/sidebar.svelte.ts';
|
||||
import { fireEvent, render, screen } from '@testing-library/svelte';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
@@ -11,11 +11,11 @@ const mocks = vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('#lib/stores/media-query-manager.svelte.js', () => ({
|
||||
vi.mock('#lib/stores/media-query-manager.svelte.ts', () => ({
|
||||
mediaQueryManager: mocks.mediaQueryManager,
|
||||
}));
|
||||
|
||||
vi.mock('#lib/stores/sidebar.svelte.js', () => ({
|
||||
vi.mock('#lib/stores/sidebar.svelte.ts', () => ({
|
||||
sidebarStore: {
|
||||
isOpen: false,
|
||||
reset: vi.fn(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AssetMultiSelectManager } from '#lib/managers/asset-multi-select-manager.svelte.ts';
|
||||
import { authManager } from '#lib/managers/auth-manager.svelte.ts';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import { timelineAssetFactory } from '#test-data/factories/asset-factory';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory';
|
||||
import { timelineAssetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory.ts';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory.ts';
|
||||
|
||||
describe('AssetMultiSelectManager', () => {
|
||||
let sut: AssetMultiSelectManager;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { setDifference, type TimelineDate } from '#lib/utils/timeline-util.ts';
|
||||
import { AssetOrder } from '@immich/sdk';
|
||||
import type { TimelineDay } from './timeline-day.svelte';
|
||||
import type { TimelineMonth } from './timeline-month.svelte';
|
||||
import type { TimelineAsset } from './types';
|
||||
import type { TimelineDay } from './timeline-day.svelte.ts';
|
||||
import type { TimelineMonth } from './timeline-month.svelte.ts';
|
||||
import type { TimelineAsset } from './types.ts';
|
||||
|
||||
export class GroupInsertionCache {
|
||||
#lookupCache: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TUNABLES } from '#lib/utils/tunables.ts';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import { TimelineManager } from '../timeline-manager.svelte.ts';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte.ts';
|
||||
|
||||
const {
|
||||
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import type { UpdateGeometryOptions } from '../types';
|
||||
import { TimelineManager } from '../timeline-manager.svelte.ts';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte.ts';
|
||||
import type { UpdateGeometryOptions } from '../types.ts';
|
||||
|
||||
export function updateGeometry(timelineManager: TimelineManager, month: TimelineMonth, options: UpdateGeometryOptions) {
|
||||
const { invalidateHeight, noDefer = false } = options;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { authManager } from '#lib/managers/auth-manager.svelte.ts';
|
||||
import { toISOYearMonthUTC } from '#lib/utils/timeline-util.ts';
|
||||
import { getTimeBucket } from '@immich/sdk';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import type { TimelineManagerOptions } from '../types';
|
||||
import { TimelineManager } from '../timeline-manager.svelte.ts';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte.ts';
|
||||
import type { TimelineManagerOptions } from '../types.ts';
|
||||
|
||||
export async function loadFromTimeBuckets(
|
||||
timelineManager: TimelineManager,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import { findClosestTimelineMonthForDate } from './search-support.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte.ts';
|
||||
import { findClosestTimelineMonthForDate } from './search-support.svelte.ts';
|
||||
|
||||
function createMockTimelineMonth(year: number, month: number): TimelineMonth {
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getOrderingDate, plainDateTimeCompare, type TimelineYearMonth } from '#lib/utils/timeline-util.ts';
|
||||
import { AssetOrder, AssetOrderBy, type AssetResponseDto } from '@immich/sdk';
|
||||
import { DateTime } from 'luxon';
|
||||
import { TimelineManager } from '../timeline-manager.svelte';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte';
|
||||
import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
|
||||
import { TimelineManager } from '../timeline-manager.svelte.ts';
|
||||
import type { TimelineMonth } from '../timeline-month.svelte.ts';
|
||||
import type { AssetDescriptor, Direction, TimelineAsset } from '../types.ts';
|
||||
|
||||
export async function getAssetWithOffset(
|
||||
timelineManager: TimelineManager,
|
||||
|
||||
@@ -4,9 +4,9 @@ import { getOrderingDate, plainDateTimeCompare } from '#lib/utils/timeline-util.
|
||||
import { TUNABLES } from '#lib/utils/tunables.ts';
|
||||
import { AssetOrder, AssetOrderBy } from '@immich/sdk';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import type { TimelineMonth } from './timeline-month.svelte';
|
||||
import type { Direction, MoveAsset, TimelineAsset } from './types';
|
||||
import { ViewerAsset } from './viewer-asset.svelte';
|
||||
import type { TimelineMonth } from './timeline-month.svelte.ts';
|
||||
import type { Direction, MoveAsset, TimelineAsset } from './types.ts';
|
||||
import { ViewerAsset } from './viewer-asset.svelte.ts';
|
||||
|
||||
const {
|
||||
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
||||
|
||||
@@ -5,9 +5,9 @@ import { AbortError } from '#lib/utils.ts';
|
||||
import { fromISODateTimeUTCToObject } from '#lib/utils/timeline-util.ts';
|
||||
import { AssetOrderBy, AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
||||
import { tick } from 'svelte';
|
||||
import { assetFactory, timelineAssetFactory, toResponseDto } from '#test-data/factories/asset-factory';
|
||||
import { TimelineManager } from './timeline-manager.svelte';
|
||||
import type { TimelineAsset } from './types';
|
||||
import { assetFactory, timelineAssetFactory, toResponseDto } from '#test-data/factories/asset-factory.ts';
|
||||
import { TimelineManager } from './timeline-manager.svelte.ts';
|
||||
import type { TimelineAsset } from './types.ts';
|
||||
|
||||
async function getAssets(timelineManager: TimelineManager) {
|
||||
const assets = [];
|
||||
|
||||
@@ -35,9 +35,9 @@ import {
|
||||
} from '@immich/sdk';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
|
||||
import { isMismatched, updateObject } from './internal/utils.svelte';
|
||||
import { TimelineDay } from './timeline-day.svelte';
|
||||
import { TimelineMonth } from './timeline-month.svelte';
|
||||
import { isMismatched, updateObject } from './internal/utils.svelte.ts';
|
||||
import { TimelineDay } from './timeline-day.svelte.ts';
|
||||
import { TimelineMonth } from './timeline-month.svelte.ts';
|
||||
import type {
|
||||
AssetDescriptor,
|
||||
Direction,
|
||||
@@ -46,7 +46,7 @@ import type {
|
||||
TimelineAsset,
|
||||
TimelineManagerOptions,
|
||||
Viewport,
|
||||
} from './types';
|
||||
} from './types.ts';
|
||||
|
||||
type ViewportTopMonthIntersection = {
|
||||
month: TimelineMonth | undefined;
|
||||
|
||||
@@ -22,11 +22,11 @@ import { AssetOrder, AssetOrderBy, type TimeBucketAssetResponseDto } from '@immi
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { get } from 'svelte/store';
|
||||
import { GroupInsertionCache } from './group-insertion-cache.svelte';
|
||||
import { TimelineDay } from './timeline-day.svelte';
|
||||
import type { TimelineManager } from './timeline-manager.svelte';
|
||||
import type { AssetDescriptor, Direction, MoveAsset, TimelineAsset } from './types';
|
||||
import { ViewerAsset } from './viewer-asset.svelte';
|
||||
import { GroupInsertionCache } from './group-insertion-cache.svelte.ts';
|
||||
import { TimelineDay } from './timeline-day.svelte.ts';
|
||||
import type { TimelineManager } from './timeline-manager.svelte.ts';
|
||||
import type { AssetDescriptor, Direction, MoveAsset, TimelineAsset } from './types.ts';
|
||||
import { ViewerAsset } from './viewer-asset.svelte.ts';
|
||||
|
||||
export class TimelineMonth {
|
||||
#viewportProximity: ViewportProximity = $state(ViewportProximity.FarFromViewport);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TimelineAsset } from './types';
|
||||
import type { TimelineAsset } from './types.ts';
|
||||
|
||||
export const assetsSnapshot = (assets: TimelineAsset[]) => assets.map((asset) => $state.snapshot(asset));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommonPosition } from '#lib/utils/layout-utils.ts';
|
||||
import type { TimelineAsset } from './types';
|
||||
import type { TimelineAsset } from './types.ts';
|
||||
|
||||
export class ViewerAsset {
|
||||
position: CommonPosition | undefined = $state.raw();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { calcNewDate } from '#lib/modals/timezone-utils.ts';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { DateTime } from 'luxon';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory.ts';
|
||||
import AssetSelectionChangeDateModal from './AssetSelectionChangeDateModal.svelte';
|
||||
|
||||
describe('DateSelectionModal component', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
SlideshowNavigation,
|
||||
SlideshowState,
|
||||
slideshowStore,
|
||||
} from '../stores/slideshow.store';
|
||||
} from '../stores/slideshow.store.ts';
|
||||
|
||||
const {
|
||||
slideshowDelay,
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
import { eventManager } from '#lib/managers/event-manager.svelte.ts';
|
||||
import { executeMutation, queryClient } from '#lib/queries/client.ts';
|
||||
import { getAdminConfigQuery, updateAdminConfigMutation } from '#lib/queries/config-admin.query.ts';
|
||||
import { getPublicConfigQuery } from '#lib/queries/config-public.query.ts';
|
||||
import {
|
||||
deleteLibraryMutation,
|
||||
getAllLibrariesQuery,
|
||||
getLibraryStatisticsQuery,
|
||||
updateLibraryMutation,
|
||||
} from '#lib/queries/library.query.ts';
|
||||
import { getServerConfigQuery, getStorageQuery } from '#lib/queries/server-info.query.ts';
|
||||
import { handleError } from '#lib/utils/handle-error.ts';
|
||||
import {
|
||||
deleteLibrary,
|
||||
getAdminConfig,
|
||||
@@ -7,22 +19,10 @@ import {
|
||||
type AdminConfigDto,
|
||||
type LibraryResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { executeMutation, queryClient } from '$lib/queries/client';
|
||||
import { getAdminConfigQuery, updateAdminConfigMutation } from '$lib/queries/config-admin.query';
|
||||
import { getPublicConfigQuery } from '$lib/queries/config-public.query';
|
||||
import {
|
||||
deleteLibraryMutation,
|
||||
getAllLibrariesQuery,
|
||||
getLibraryStatisticsQuery,
|
||||
updateLibraryMutation,
|
||||
} from '$lib/queries/library.query';
|
||||
import { getServerConfigQuery, getStorageQuery } from '$lib/queries/server.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
|
||||
vi.mock('@immich/ui', () => ({ toastManager: { primary: vi.fn(), info: vi.fn() } }));
|
||||
vi.mock('$lib/utils/handle-error', () => ({ handleError: vi.fn() }));
|
||||
vi.mock('$lib/utils/i18n', () => ({ getFormatter: () => Promise.resolve((key: string) => key) }));
|
||||
vi.mock('#lib/utils/handle-error.ts', () => ({ handleError: vi.fn() }));
|
||||
vi.mock('#lib/utils/i18n.ts', () => ({ getFormatter: () => Promise.resolve((key: string) => key) }));
|
||||
vi.mock('@immich/sdk', () => ({
|
||||
createLibrary: vi.fn(),
|
||||
deleteLibrary: vi.fn(),
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
import { eventManager } from '#lib/managers/event-manager.svelte.ts';
|
||||
import { executeMutation, queryClient } from '#lib/queries/client.ts';
|
||||
import {
|
||||
createUserAdminMutation,
|
||||
deleteUserAdminMutation,
|
||||
getUserAdminQuery,
|
||||
restoreUserAdminMutation,
|
||||
searchUsersAdminQuery,
|
||||
updateUserAdminMutation,
|
||||
} from '#lib/queries/user-admin.query.ts';
|
||||
import { handleError } from '#lib/utils/handle-error.ts';
|
||||
import {
|
||||
createUserAdmin,
|
||||
deleteUserAdmin,
|
||||
@@ -7,22 +18,11 @@ import {
|
||||
type UserAdminResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { QueryObserver } from '@tanstack/svelte-query';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { executeMutation, queryClient } from '$lib/queries/client';
|
||||
import {
|
||||
createUserAdminMutation,
|
||||
deleteUserAdminMutation,
|
||||
getUserAdminQuery,
|
||||
restoreUserAdminMutation,
|
||||
searchUsersAdminQuery,
|
||||
updateUserAdminMutation,
|
||||
} from '$lib/queries/user-admin.query';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
|
||||
vi.mock('$lib', () => ({ getHeatmapRange: vi.fn() }));
|
||||
vi.mock('#lib', () => ({ getHeatmapRange: vi.fn() }));
|
||||
vi.mock('@immich/ui', () => ({ toastManager: { primary: vi.fn(), info: vi.fn() } }));
|
||||
vi.mock('$lib/utils/handle-error', () => ({ handleError: vi.fn() }));
|
||||
vi.mock('$lib/utils/i18n', () => ({ getFormatter: () => Promise.resolve((key: string) => key) }));
|
||||
vi.mock('#lib/utils/handle-error.ts', () => ({ handleError: vi.fn() }));
|
||||
vi.mock('#lib/utils/i18n.ts', () => ({ getFormatter: () => Promise.resolve((key: string) => key) }));
|
||||
vi.mock('@immich/sdk', () => ({
|
||||
CalendarHeatmapType: { Upload: 'upload' },
|
||||
createUserAdmin: vi.fn(),
|
||||
|
||||
@@ -7,10 +7,10 @@ import { getFormatter } from '#lib/utils/i18n.ts';
|
||||
import { getAssetInfo, type UserConfigDto } from '@immich/sdk';
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { vitest } from 'vitest';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory';
|
||||
import { sharedLinkFactory } from '#test-data/factories/shared-link-factory';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory';
|
||||
import { assetFactory } from '#test-data/factories/asset-factory.ts';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory.ts';
|
||||
import { sharedLinkFactory } from '#test-data/factories/shared-link-factory.ts';
|
||||
import { userAdminFactory } from '#test-data/factories/user-factory.ts';
|
||||
|
||||
vitest.mock('@immich/ui', () => ({
|
||||
toastManager: {
|
||||
@@ -18,15 +18,15 @@ vitest.mock('@immich/ui', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vitest.mock('#lib/utils/i18n.js', () => ({
|
||||
vitest.mock('#lib/utils/i18n.ts', () => ({
|
||||
getFormatter: vitest.fn(),
|
||||
getPreferredLocale: vitest.fn(),
|
||||
}));
|
||||
|
||||
vitest.mock('@immich/sdk');
|
||||
|
||||
vitest.mock('#lib/utils.js', async () => {
|
||||
const originalModule = await vitest.importActual('#lib/utils.js');
|
||||
vitest.mock('#lib/utils.ts', async () => {
|
||||
const originalModule = await vitest.importActual('#lib/utils.ts');
|
||||
return {
|
||||
...originalModule,
|
||||
sleep: vitest.fn(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { asUrl } from '#lib/services/shared-link.service.ts';
|
||||
import { sharedLinkFactory } from '#test-data/factories/shared-link-factory';
|
||||
import { sharedLinkFactory } from '#test-data/factories/shared-link-factory.ts';
|
||||
|
||||
vi.mock(import('#lib/queries/client.js'), async (importOriginal) => ({
|
||||
vi.mock(import('#lib/queries/client.ts'), async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
readPreloadedQuery: vi.fn().mockReturnValue({ server: { externalDomain: 'http://localhost:2283' } }),
|
||||
}));
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { getUserAdminActions } from '#lib/services/user-admin.service.ts';
|
||||
import { UserStatus, type UserAdminResponseDto } from '@immich/sdk';
|
||||
import type { MessageFormatter } from 'svelte-i18n';
|
||||
import { getUserAdminActions } from '$lib/services/user-admin.service';
|
||||
|
||||
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
||||
vi.mock('@immich/ui', () => ({ modalManager: { show: vi.fn(), showDialog: vi.fn() } }));
|
||||
vi.mock('$lib/managers/auth-manager.svelte', () => ({ authManager: { user: { id: 'self' } } }));
|
||||
vi.mock('$lib/modals/PasswordResetSuccessModal.svelte', () => ({ default: {} }));
|
||||
vi.mock('$lib/modals/UserDeleteConfirmModal.svelte', () => ({ default: {} }));
|
||||
vi.mock('$lib/modals/UserRestoreConfirmModal.svelte', () => ({ default: {} }));
|
||||
vi.mock('$lib/queries/client', () => ({
|
||||
vi.mock('#lib/managers/auth-manager.svelte.ts', () => ({ authManager: { user: { id: 'self' } } }));
|
||||
vi.mock('#lib/modals/PasswordResetSuccessModal.svelte', () => ({ default: {} }));
|
||||
vi.mock('#lib/modals/UserDeleteConfirmModal.svelte', () => ({ default: {} }));
|
||||
vi.mock('#lib/modals/UserRestoreConfirmModal.svelte', () => ({ default: {} }));
|
||||
vi.mock('#lib/queries/client.ts', () => ({
|
||||
executeMutation: vi.fn(),
|
||||
readPreloadedQuery: () => ({ user: { deleteDelay: 7 } }),
|
||||
}));
|
||||
vi.mock('$lib/queries/user-admin.query', () => ({ updateUserAdminMutation: vi.fn() }));
|
||||
vi.mock('$lib/route', () => ({ Route: { newUser: vi.fn(), viewUser: vi.fn(), editUser: vi.fn() } }));
|
||||
vi.mock('$lib/utils/i18n', () => ({ getFormatter: vi.fn() }));
|
||||
vi.mock('#lib/queries/user-admin.query.ts', () => ({ updateUserAdminMutation: vi.fn() }));
|
||||
vi.mock('#lib/route.ts', () => ({ Route: { newUser: vi.fn(), viewUser: vi.fn(), editUser: vi.fn() } }));
|
||||
vi.mock('#lib/utils/i18n.ts', () => ({ getFormatter: vi.fn() }));
|
||||
|
||||
describe('admin actions for active and deleted accounts', () => {
|
||||
it('does not format a deletion date for an active account', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { BaseEventManager } from '$lib/utils/base-event-manager.svelte';
|
||||
import { BaseEventManager } from '#lib/utils/base-event-manager.svelte.ts';
|
||||
import EventSubscriber from './EventSubscriber.svelte';
|
||||
|
||||
const manager = new BaseEventManager<{ update: [] }>();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import type { BaseEventManager } from '$lib/utils/base-event-manager.svelte';
|
||||
import type { BaseEventManager } from '#lib/utils/base-event-manager.svelte.ts';
|
||||
|
||||
const { manager }: { manager: BaseEventManager<{ update: [] }> } = $props();
|
||||
let received = $state(0);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AssetVisibility, deleteAssets as deleteBulk, restoreAssets, type StackR
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { handleError } from './handle-error';
|
||||
import { handleError } from './handle-error.ts';
|
||||
|
||||
export type OnDelete = (assetIds: string[]) => void;
|
||||
export type OnUndoDelete = (assets: TimelineAsset[]) => void;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AdaptiveImageLoader, type QualityList } from '#lib/utils/adaptive-image-loader.svelte.ts';
|
||||
import { cancelImageUrl } from '#lib/utils/sw-messaging.ts';
|
||||
|
||||
vi.mock('#lib/utils/sw-messaging.js', () => ({
|
||||
vi.mock('#lib/utils/sw-messaging.ts', () => ({
|
||||
cancelImageUrl: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { checkWebUpdate } from './app-update';
|
||||
import { checkWebUpdate } from './app-update.ts';
|
||||
|
||||
describe('web update checking', () => {
|
||||
it.each([
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AssetVisibility, updateAsset, type AssetResponseDto } from '@immich/sdk';
|
||||
import { canCopyImageToClipboard, getAssetFilename, getFilenameExtension, toggleArchive } from './asset-utils';
|
||||
import { canCopyImageToClipboard, getAssetFilename, getFilenameExtension, toggleArchive } from './asset-utils.ts';
|
||||
|
||||
vi.mock('@immich/sdk', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@immich/sdk')>();
|
||||
|
||||
@@ -27,7 +27,7 @@ import { toastManager } from '@immich/ui';
|
||||
import { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { handleError } from './handle-error';
|
||||
import { handleError } from './handle-error.ts';
|
||||
|
||||
export const removeTag = async ({
|
||||
assetIds,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { locale } from '#lib/stores/preferences.store.ts';
|
||||
import { getAlbumDateRange, getShortDateRange } from './date-time';
|
||||
import { getAlbumDateRange, getShortDateRange } from './date-time.ts';
|
||||
|
||||
vitest.mock('#lib/stores/preferences.store.js', { spy: true });
|
||||
vitest.mock('#lib/stores/preferences.store.ts', { spy: true });
|
||||
|
||||
describe('getShortDateRange', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -6,8 +6,8 @@ import * as utils from '#lib/utils.ts';
|
||||
import { AssetMediaStatus, type AssetMediaResponseDto, type UserAdminResponseDto } from '@immich/sdk';
|
||||
import { get } from 'svelte/store';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory';
|
||||
import { fileUploadHandler } from './file-uploader';
|
||||
import { preferencesFactory } from '#test-data/factories/preferences-factory.ts';
|
||||
import { fileUploadHandler } from './file-uploader.ts';
|
||||
|
||||
describe('fileUploader error handling', () => {
|
||||
const mockFile = new File(['content'], 'test.jpg', { type: 'image/jpeg' });
|
||||
|
||||
@@ -19,7 +19,7 @@ import { toastManager } from '@immich/ui';
|
||||
import { tick } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { handleError } from './handle-error';
|
||||
import { handleError } from './handle-error.ts';
|
||||
|
||||
export const uploadExecutionQueue = new ExecutorQueue({ concurrency: 2 });
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ export const calculateFittedFontSize = (
|
||||
return clamp(fontSize, MIN_FONT_SIZE, MAX_FONT_SIZE);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line tscompat/tscompat -- This assigns the Canvas 2D font property, not a font() method.
|
||||
context.font = getReferenceFont();
|
||||
|
||||
let maxCharWidth = 0;
|
||||
@@ -145,7 +146,7 @@ export const calculateFittedFontSize = (
|
||||
return clamp((1.4 * fitWidth) / text.length, MIN_FONT_SIZE, MAX_FONT_SIZE);
|
||||
}
|
||||
|
||||
// Unsupported in Safari iOS <16.6; falls back to default canvas font, giving less accurate but functional sizing
|
||||
// eslint-disable-next-line tscompat/tscompat -- This assigns the Canvas 2D font property, not a font() method.
|
||||
context.font = getReferenceFont();
|
||||
|
||||
const metrics = context.measureText(text);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PersonResponseDto } from '@immich/sdk';
|
||||
import { searchNameLocal } from './person';
|
||||
import { searchNameLocal } from './person.ts';
|
||||
|
||||
const makePerson = (overrides: Partial<PersonResponseDto> = {}): PersonResponseDto => ({
|
||||
id: 'person-1',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PersonResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { derived } from 'svelte/store';
|
||||
import { normalizeSearchString } from './string-utils';
|
||||
import { normalizeSearchString } from './string-utils.ts';
|
||||
|
||||
export const searchNameLocal = (
|
||||
name: string,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServiceWorkerMessenger } from './sw-messenger';
|
||||
import { ServiceWorkerMessenger } from './sw-messenger.ts';
|
||||
|
||||
const hasServiceWorker = globalThis.isSecureContext && 'serviceWorker' in navigator;
|
||||
const messenger = hasServiceWorker ? new ServiceWorkerMessenger(navigator.serviceWorker) : undefined;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { TimelineAsset } from '#lib/managers/timeline-manager/types.ts';
|
||||
import { locale } from '#lib/stores/preferences.store.ts';
|
||||
import { getAltText } from '#lib/utils/thumbnail-util.ts';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import { init, register, waitLocale } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
|
||||
interface Person {
|
||||
name: string;
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function acquireWakeLock() {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line tscompat/tscompat -- The feature check above guards Wake Lock access.
|
||||
if (sentinel && !sentinel.released) {
|
||||
return;
|
||||
}
|
||||
@@ -19,6 +20,7 @@ export async function acquireWakeLock() {
|
||||
}
|
||||
acquiring = true;
|
||||
try {
|
||||
// eslint-disable-next-line tscompat/tscompat -- Browsers without Wake Lock return before this call.
|
||||
sentinel = await navigator.wakeLock.request('screen');
|
||||
} catch (error) {
|
||||
console.warn('Failed to acquire wake lock:', error);
|
||||
@@ -36,6 +38,7 @@ export async function releaseWakeLock() {
|
||||
// Unset first to avoid race condition after await
|
||||
sentinel = undefined;
|
||||
|
||||
// eslint-disable-next-line tscompat/tscompat -- A sentinel is only acquired on browsers supporting Wake Lock.
|
||||
await toReleaseSentinel.release();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@ describe('hashFile', () => {
|
||||
});
|
||||
|
||||
it('hashFileJs produces the correct SHA-1 hex digest', async () => {
|
||||
const { hashFileJs } = await import('./hash-file');
|
||||
const { hashFileJs } = await import('./hash-file.ts');
|
||||
const file = new File([content], 'a.txt');
|
||||
await expect(hashFileJs(file)).resolves.toBe(knownSha1Hex);
|
||||
});
|
||||
|
||||
it('hashFileWasm produces the correct SHA-1 hex digest', async () => {
|
||||
const { hashFileWasm } = await import('./hash-file');
|
||||
const { hashFileWasm } = await import('./hash-file.ts');
|
||||
const file = new File([content], 'a.txt');
|
||||
await expect(hashFileWasm(file)).resolves.toBe(knownSha1Hex);
|
||||
});
|
||||
|
||||
it('hashFileWasm and hashFileJs produce identical output for the same input', async () => {
|
||||
const { hashFileWasm, hashFileJs } = await import('./hash-file');
|
||||
const { hashFileWasm, hashFileJs } = await import('./hash-file.ts');
|
||||
for (const size of [0, 1, 1024, 10_000]) {
|
||||
const file = new File([new Uint8Array(size)], 'x.bin');
|
||||
const [wasmHash, jsHash] = await Promise.all([hashFileWasm(file), hashFileJs(file)]);
|
||||
@@ -34,13 +34,13 @@ describe('hashFile', () => {
|
||||
}));
|
||||
vi.resetModules();
|
||||
|
||||
const { hashFile } = await import('./hash-file');
|
||||
const { hashFile } = await import('./hash-file.ts');
|
||||
const file = new File([content], 'a.txt');
|
||||
await expect(hashFile(file)).resolves.toBe(knownSha1Hex);
|
||||
});
|
||||
|
||||
it('uses the WASM path when available and matches the JS output', async () => {
|
||||
const { hashFile } = await import('./hash-file');
|
||||
const { hashFile } = await import('./hash-file.ts');
|
||||
const file = new File([content], 'a.txt');
|
||||
await expect(hashFile(file)).resolves.toBe(knownSha1Hex);
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user