Compare commits

..

11 Commits

Author SHA1 Message Date
shenlong-tanwen 19c9f932ea fix: handle missing assets gracefully 2025-07-23 19:40:23 +05:30
shenlong 05d26dc683 fix: remove safe area from bottom bar (#20102)
* remove safe area from bottom bar

* fix: video not playing in search view

* stop foreground / background back on migration

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-23 08:23:49 -05:00
renovate[bot] c91382625c fix(deps): update typescript-projects (#20103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-07-23 13:26:28 +01:00
Alex c7853fbe9d chore: refactor download group (#20099) 2025-07-23 04:21:58 +00:00
xCJPECKOVERx 1a70896113 feat(web): Remove from Stack (#19703)
* - add component
- update server's StackCreateDto for merge parameter
- Update stackRepo to only merge stacks when merge=true (default)
- update web action handlers to show stack changes

* - make open-api

* lint & format

* - Add proper icon to 'remove from stack'
- change web unstack icon to image-off-outline

* - cleanup

* - format & lint

* - make open-api: StackCreateDto merge optional

* initial addition of new endpoint

* remove stack endpoint

* - fix up remove stack endpoint
- open-api

* - Undo stackCreate merge parameter

* - open-api typescript

* open-api dart

* Tests:
- add tests
- update assetStub.imageFrom2015 to have required stack attributes to include it with tests

* update event name

* Fix event name in test

* remove asset_update check

* - merge stack.removeAsset params into one object
- refactor asset existence check (no need for asset fetch)
- fix tests

* Don't return updated stack

* Create specialized stack id & primary asset fetch for asset removal checks

* Correct new permission names

* make sql

* - fix open-api

* - cleanup
2025-07-22 22:17:06 -04:00
Alex 1011cdb376 chore: handle requeue upload when target albums changed (#20089)
* chore: handle requeue upload when target albums changed

* chore: remove debug
2025-07-22 17:11:06 -05:00
Sebastian Di Luzio f1cac122ed fix: more inclusive language (#20092) 2025-07-22 21:29:36 +00:00
Brandon Wees 3c7f0a2900 chore(mobile): use hides instead of changing the namespace (#20090) 2025-07-22 16:02:52 -05:00
Brandon Wees 277e39ac98 fix(mobile): sync icon rotation direction (#20088)
spin the sync icon the right direction
2025-07-23 01:31:18 +05:30
renovate[bot] b3061f1e4f fix(deps): update typescript-projects (#20086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-07-22 19:18:29 +01:00
renovate[bot] 250548dea6 fix(deps): update typescript-projects (#19939)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-07-22 17:42:07 +00:00
58 changed files with 1644 additions and 1274 deletions
+88 -73
View File
@@ -682,9 +682,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.30.1", "version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1365,17 +1365,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==", "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.36.0", "@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.36.0", "@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -1389,7 +1389,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.36.0", "@typescript-eslint/parser": "^8.37.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <5.9.0"
} }
@@ -1405,16 +1405,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==", "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1430,14 +1430,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==", "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.36.0", "@typescript-eslint/types": "^8.37.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1452,14 +1452,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==", "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0" "@typescript-eslint/visitor-keys": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1470,9 +1470,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==", "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1487,14 +1487,15 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==", "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/utils": "8.36.0", "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.1.0"
}, },
@@ -1511,9 +1512,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==", "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1525,16 +1526,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==", "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -1580,16 +1581,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==", "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.36.0" "@typescript-eslint/typescript-estree": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1604,13 +1605,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==", "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
@@ -2305,9 +2306,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.30.1", "version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2315,9 +2316,9 @@
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0", "@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0", "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0", "@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.30.1", "@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1", "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -2504,6 +2505,19 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/espree": { "node_modules/espree": {
"version": "10.4.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -4125,15 +4139,16 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.36.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==", "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.36.0", "@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/utils": "8.36.0" "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4196,9 +4211,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.0.4", "version": "7.0.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz",
"integrity": "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==", "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4329,9 +4344,9 @@
} }
}, },
"node_modules/vite/node_modules/picomatch": { "node_modules/vite/node_modules/picomatch": {
"version": "4.0.2", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
+2 -2
View File
@@ -88,9 +88,9 @@ It will only reflect files you add.
::: :::
If the same asset is in more than one album it will only sync to the first album it's in, after that it won't sync again even if the user clicks sync albums manually. If the same asset is in more than one album it will only sync to the first album it's in, after that it won't sync again even if the user clicks sync albums manually.
To overcome this limitation, the files must be removed from the blacklist by To overcome this limitation, the files must be removed from the ignore list by
App settings -> Advanced -> Duplicate Assets -> Clear App settings -> Advanced -> Duplicate Assets -> Clear
:::info :::info
Cleaning duplicate assets from the list will cause all the previously uploaded duplicate files to be re-uploaded, the files will not actually be uploaded and will be rejected on the server side (due to duplication) but will be synchronized to the album and at the end will be added to the black list again at the end of the synchronization. Cleaning duplicate assets from the list will cause all the previously uploaded duplicate files to be re-uploaded, the files will not actually be uploaded and will be rejected on the server side (due to duplication) but will be synchronized to the album and at the end will be added to the ignore list again at the end of the synchronization.
::: :::
+228 -188
View File
@@ -181,9 +181,9 @@
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.4.3", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@@ -734,9 +734,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.30.1", "version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -837,9 +837,9 @@
} }
}, },
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
"integrity": "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==", "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -856,13 +856,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.1.0" "@img/sharp-libvips-darwin-arm64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-darwin-x64": { "node_modules/@img/sharp-darwin-x64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
"integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -879,13 +879,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.1.0" "@img/sharp-libvips-darwin-x64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-libvips-darwin-arm64": { "node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -900,9 +900,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-darwin-x64": { "node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -917,9 +917,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm": { "node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -934,9 +934,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm64": { "node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -951,9 +951,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-ppc64": { "node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -968,9 +968,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-s390x": { "node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -985,9 +985,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-x64": { "node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1002,9 +1002,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-arm64": { "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1019,9 +1019,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-x64": { "node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1036,9 +1036,9 @@
} }
}, },
"node_modules/@img/sharp-linux-arm": { "node_modules/@img/sharp-linux-arm": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
"integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1055,13 +1055,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.1.0" "@img/sharp-libvips-linux-arm": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linux-arm64": { "node_modules/@img/sharp-linux-arm64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
"integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1078,13 +1078,36 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.1.0" "@img/sharp-libvips-linux-arm64": "1.2.0"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
"integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linux-s390x": { "node_modules/@img/sharp-linux-s390x": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
"integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -1101,13 +1124,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.1.0" "@img/sharp-libvips-linux-s390x": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linux-x64": { "node_modules/@img/sharp-linux-x64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
"integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1124,13 +1147,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.1.0" "@img/sharp-libvips-linux-x64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linuxmusl-arm64": { "node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
"integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1147,13 +1170,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0" "@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-linuxmusl-x64": { "node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
"integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1170,13 +1193,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.1.0" "@img/sharp-libvips-linuxmusl-x64": "1.2.0"
} }
}, },
"node_modules/@img/sharp-wasm32": { "node_modules/@img/sharp-wasm32": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
"integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
@@ -1184,7 +1207,7 @@
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.4.3" "@emnapi/runtime": "^1.4.4"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -1194,9 +1217,9 @@
} }
}, },
"node_modules/@img/sharp-win32-arm64": { "node_modules/@img/sharp-win32-arm64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
"integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1214,9 +1237,9 @@
} }
}, },
"node_modules/@img/sharp-win32-ia32": { "node_modules/@img/sharp-win32-ia32": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
"integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1234,9 +1257,9 @@
} }
}, },
"node_modules/@img/sharp-win32-x64": { "node_modules/@img/sharp-win32-x64": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
"integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1356,12 +1379,13 @@
} }
}, },
"node_modules/@koa/router": { "node_modules/@koa/router": {
"version": "13.1.0", "version": "13.1.1",
"resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz", "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.1.tgz",
"integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==", "integrity": "sha512-JQEuMANYRVHs7lm7KY9PCIjkgJk73h4m4J+g2mkw2Vo1ugPZ17UJVqEH8F+HeAdjKz5do1OaLe7ArDz+z308gw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.4.1",
"http-errors": "^2.0.0", "http-errors": "^2.0.0",
"koa-compose": "^4.1.0", "koa-compose": "^4.1.0",
"path-to-regexp": "^6.3.0" "path-to-regexp": "^6.3.0"
@@ -1510,13 +1534,13 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.53.2", "version": "1.54.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
"integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.53.2" "playwright": "1.54.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -2101,17 +2125,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==", "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.36.0", "@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.36.0", "@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -2125,7 +2149,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.36.0", "@typescript-eslint/parser": "^8.37.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <5.9.0"
} }
@@ -2141,16 +2165,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==", "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -2166,14 +2190,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==", "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.36.0", "@typescript-eslint/types": "^8.37.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -2188,14 +2212,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==", "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0" "@typescript-eslint/visitor-keys": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2206,9 +2230,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==", "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2223,14 +2247,15 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==", "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/utils": "8.36.0", "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.1.0"
}, },
@@ -2247,9 +2272,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==", "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2261,16 +2286,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==", "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -2316,16 +2341,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==", "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.36.0" "@typescript-eslint/typescript-estree": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2340,13 +2365,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==", "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
@@ -3439,9 +3464,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.30.1", "version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3449,9 +3474,9 @@
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0", "@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0", "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0", "@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.30.1", "@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1", "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -3638,6 +3663,19 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/espree": { "node_modules/espree": {
"version": "10.4.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -5154,17 +5192,17 @@
} }
}, },
"node_modules/oidc-provider": { "node_modules/oidc-provider": {
"version": "9.2.0", "version": "9.4.0",
"resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-9.2.0.tgz", "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-9.4.0.tgz",
"integrity": "sha512-L0JL1ymI/hLKzDRqYzhKluNfRRQUR0++q5fTTziniKmJgNrJ6DnI5h5SP6w8Z0U/3wZrCndpVmbbu0VpKpY0CA==", "integrity": "sha512-1mEUejJq7cQV/b6cw2nitqOyIlOJTfQ6RNwGFcA7/Pp+vKIWBn8p48ylFtogP3Hbvrkf9s9W5HUeFe+v1KpcEQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@koa/cors": "^5.0.0", "@koa/cors": "^5.0.0",
"@koa/router": "^13.1.0", "@koa/router": "^13.1.1",
"debug": "^4.4.1", "debug": "^4.4.1",
"eta": "^3.5.0", "eta": "^3.5.0",
"jose": "^6.0.11", "jose": "^6.0.12",
"jsesc": "^3.1.0", "jsesc": "^3.1.0",
"koa": "^3.0.0", "koa": "^3.0.0",
"nanoid": "^5.1.5", "nanoid": "^5.1.5",
@@ -5177,9 +5215,9 @@
} }
}, },
"node_modules/oidc-provider/node_modules/jose": { "node_modules/oidc-provider/node_modules/jose": {
"version": "6.0.11", "version": "6.0.12",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.12.tgz",
"integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==", "integrity": "sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
@@ -5488,13 +5526,13 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.53.2", "version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
"integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.53.2" "playwright-core": "1.54.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -5507,9 +5545,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.53.2", "version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
"integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@@ -5993,9 +6031,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.34.2", "version": "0.34.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
"integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -6011,27 +6049,28 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.2", "@img/sharp-darwin-arm64": "0.34.3",
"@img/sharp-darwin-x64": "0.34.2", "@img/sharp-darwin-x64": "0.34.3",
"@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-arm64": "1.2.0",
"@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.2.0",
"@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm": "1.2.0",
"@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.2.0",
"@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.2.0",
"@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.2.0",
"@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linux-x64": "1.2.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
"@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.2.0",
"@img/sharp-linux-arm": "0.34.2", "@img/sharp-linux-arm": "0.34.3",
"@img/sharp-linux-arm64": "0.34.2", "@img/sharp-linux-arm64": "0.34.3",
"@img/sharp-linux-s390x": "0.34.2", "@img/sharp-linux-ppc64": "0.34.3",
"@img/sharp-linux-x64": "0.34.2", "@img/sharp-linux-s390x": "0.34.3",
"@img/sharp-linuxmusl-arm64": "0.34.2", "@img/sharp-linux-x64": "0.34.3",
"@img/sharp-linuxmusl-x64": "0.34.2", "@img/sharp-linuxmusl-arm64": "0.34.3",
"@img/sharp-wasm32": "0.34.2", "@img/sharp-linuxmusl-x64": "0.34.3",
"@img/sharp-win32-arm64": "0.34.2", "@img/sharp-wasm32": "0.34.3",
"@img/sharp-win32-ia32": "0.34.2", "@img/sharp-win32-arm64": "0.34.3",
"@img/sharp-win32-x64": "0.34.2" "@img/sharp-win32-ia32": "0.34.3",
"@img/sharp-win32-x64": "0.34.3"
} }
}, },
"node_modules/shebang-command": { "node_modules/shebang-command": {
@@ -6778,15 +6817,16 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.36.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==", "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.36.0", "@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/utils": "8.36.0" "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+2 -1
View File
@@ -592,7 +592,7 @@
"cache_settings_clear_cache_button": "Clear cache", "cache_settings_clear_cache_button": "Clear cache",
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
"cache_settings_duplicated_assets_clear_button": "CLEAR", "cache_settings_duplicated_assets_clear_button": "CLEAR",
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app", "cache_settings_duplicated_assets_subtitle": "Photos and videos that are ignore listed by the app",
"cache_settings_duplicated_assets_title": "Duplicated Assets ({count})", "cache_settings_duplicated_assets_title": "Duplicated Assets ({count})",
"cache_settings_statistics_album": "Library thumbnails", "cache_settings_statistics_album": "Library thumbnails",
"cache_settings_statistics_full": "Full images", "cache_settings_statistics_full": "Full images",
@@ -1330,6 +1330,7 @@
"no_results": "No results", "no_results": "No results",
"no_results_description": "Try a synonym or more general keyword", "no_results_description": "Try a synonym or more general keyword",
"no_shared_albums_message": "Create an album to share photos and videos with people in your network", "no_shared_albums_message": "Create an album to share photos and videos with people in your network",
"no_uploads_in_progress": "No uploads in progress",
"not_in_any_album": "Not in any album", "not_in_any_album": "Not in any album",
"not_selected": "Not selected", "not_selected": "Not selected",
"note_apply_storage_label_to_previously_uploaded assets": "Note: To apply the Storage Label to previously uploaded assets, run the", "note_apply_storage_label_to_previously_uploaded assets": "Note: To apply the Storage Label to previously uploaded assets, run the",
+3
View File
@@ -20,6 +20,9 @@ const String kSecuredPinCode = "secured_pin_code";
const String kManualUploadGroup = 'manual_upload_group'; const String kManualUploadGroup = 'manual_upload_group';
const String kBackupGroup = 'backup_group'; const String kBackupGroup = 'backup_group';
const String kBackupLivePhotoGroup = 'backup_live_photo_group'; const String kBackupLivePhotoGroup = 'backup_live_photo_group';
const String kDownloadGroupImage = 'group_image';
const String kDownloadGroupVideo = 'group_video';
const String kDownloadGroupLivePhoto = 'group_livephoto';
// Timeline constants // Timeline constants
const int kTimelineNoneSegmentSize = 120; const int kTimelineNoneSegmentSize = 120;
@@ -10,6 +10,7 @@ import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/async_mutex.dart';
import 'package:logging/logging.dart';
typedef TimelineAssetSource = Future<List<BaseAsset>> Function( typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
int index, int index,
@@ -68,9 +69,6 @@ class TimelineFactory {
TimelineService fromAssets(List<BaseAsset> assets) => TimelineService fromAssets(List<BaseAsset> assets) =>
TimelineService(_timelineRepository.fromAssets(assets)); TimelineService(_timelineRepository.fromAssets(assets));
TimelineService person(String personId) =>
TimelineService(_timelineRepository.person(personId, groupBy));
} }
class TimelineService { class TimelineService {
@@ -80,6 +78,7 @@ class TimelineService {
int _bufferOffset = 0; int _bufferOffset = 0;
List<BaseAsset> _buffer = []; List<BaseAsset> _buffer = [];
StreamSubscription? _bucketSubscription; StreamSubscription? _bucketSubscription;
final _log = Logger('TimelineService');
int _totalAssets = 0; int _totalAssets = 0;
int get totalAssets => _totalAssets; int get totalAssets => _totalAssets;
@@ -131,10 +130,10 @@ class TimelineService {
Stream<List<Bucket>> Function() get watchBuckets => _bucketSource; Stream<List<Bucket>> Function() get watchBuckets => _bucketSource;
Future<List<BaseAsset>> loadAssets(int index, int count) => Future<List<BaseAsset>?> loadAssets(int index, int count) =>
_mutex.run(() => _loadAssets(index, count)); _mutex.run(() => _loadAssets(index, count));
Future<List<BaseAsset>> _loadAssets(int index, int count) async { Future<List<BaseAsset>?> _loadAssets(int index, int count) async {
if (hasRange(index, count)) { if (hasRange(index, count)) {
return getAssets(index, count); return getAssets(index, count);
} }
@@ -172,9 +171,10 @@ class TimelineService {
index + count <= _bufferOffset + _buffer.length && index + count <= _bufferOffset + _buffer.length &&
index + count <= _totalAssets; index + count <= _totalAssets;
List<BaseAsset> getAssets(int index, int count) { List<BaseAsset>? getAssets(int index, int count) {
if (!hasRange(index, count)) { if (!hasRange(index, count)) {
throw RangeError('TimelineService::getAssets Index out of range'); _log.warning('TimelineService::getAssets Index out of range');
return null;
} }
int start = index - _bufferOffset; int start = index - _bufferOffset;
return _buffer.slice(start, start + count); return _buffer.slice(start, start + count);
@@ -441,78 +441,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.get(); .get();
} }
TimelineQuery person(String personId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchPersonBucket(personId, groupBy: groupBy),
assetSource: (offset, count) =>
_getPersonBucketAssets(personId, offset: offset, count: count),
);
Stream<List<Bucket>> _watchPersonBucket(
String personId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) {
// TODO: implement GroupAssetBy for person
throw UnsupportedError(
"GroupAssetsBy.none is not supported for watchPersonBucket",
);
}
final assetCountExp = _db.remoteAssetEntity.id.count();
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
final query = _db.remoteAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp])
..join([
innerJoin(
_db.assetFaceEntity,
_db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
useColumns: false,
),
])
..where(
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.visibility
.equalsValue(AssetVisibility.timeline) &
_db.assetFaceEntity.personId.equals(personId),
)
..groupBy([dateExp])
..orderBy([OrderingTerm.desc(dateExp)]);
return query.map((row) {
final timeline = row.read(dateExp)!.dateFmt(groupBy);
final assetCount = row.read(assetCountExp)!;
return TimeBucket(date: timeline, assetCount: assetCount);
}).watch();
}
Future<List<BaseAsset>> _getPersonBucketAssets(
String personId, {
required int offset,
required int count,
}) {
final query = _db.remoteAssetEntity.select().join(
[
innerJoin(
_db.assetFaceEntity,
_db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
useColumns: false,
),
],
)
..where(
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.visibility
.equalsValue(AssetVisibility.timeline) &
_db.assetFaceEntity.personId.equals(personId),
)
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
..limit(count, offset: offset);
return query
.map((row) => row.readTable(_db.remoteAssetEntity).toDto())
.get();
}
TimelineQuery _remoteQueryBuilder({ TimelineQuery _remoteQueryBuilder({
required Expression<bool> Function($RemoteAssetEntityTable row) filter, required Expression<bool> Function($RemoteAssetEntityTable row) filter,
GroupAssetsBy groupBy = GroupAssetsBy.day, GroupAssetsBy groupBy = GroupAssetsBy.day,
+4 -4
View File
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart';
@@ -29,7 +30,6 @@ import 'package:immich_mobile/theme/dynamic_theme.dart';
import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/theme/theme_data.dart';
import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/cache/widgets_binding.dart'; import 'package:immich_mobile/utils/cache/widgets_binding.dart';
import 'package:immich_mobile/utils/download.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:immich_mobile/utils/licenses.dart'; import 'package:immich_mobile/utils/licenses.dart';
import 'package:immich_mobile/utils/migration.dart'; import 'package:immich_mobile/utils/migration.dart';
@@ -101,7 +101,7 @@ Future<void> initApp() async {
); );
await FileDownloader().trackTasksInGroup( await FileDownloader().trackTasksInGroup(
downloadGroupLivePhoto, kDownloadGroupLivePhoto,
markDownloadedComplete: false, markDownloadedComplete: false,
); );
@@ -179,7 +179,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
void _configureFileDownloaderNotifications() { void _configureFileDownloaderNotifications() {
FileDownloader().configureNotificationForGroup( FileDownloader().configureNotificationForGroup(
downloadGroupImage, kDownloadGroupImage,
running: TaskNotification( running: TaskNotification(
'downloading_media'.tr(), 'downloading_media'.tr(),
'${'file_name'.tr()}: {filename}', '${'file_name'.tr()}: {filename}',
@@ -192,7 +192,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
); );
FileDownloader().configureNotificationForGroup( FileDownloader().configureNotificationForGroup(
downloadGroupVideo, kDownloadGroupVideo,
running: TaskNotification( running: TaskNotification(
'downloading_media'.tr(), 'downloading_media'.tr(),
'${'file_name'.tr()}: {filename}', '${'file_name'.tr()}: {filename}',
+6 -10
View File
@@ -44,9 +44,6 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
(album) => album.backupSelection == BackupSelection.selected, (album) => album.backupSelection == BackupSelection.selected,
) )
.toList(); .toList();
final uploadItems = ref.watch(
driftBackupProvider.select((state) => state.uploadItems),
);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -85,14 +82,13 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
onStart: () async => await startBackup(), onStart: () async => await startBackup(),
onStop: () async => await stopBackup(), onStop: () async => await stopBackup(),
), ),
if (uploadItems.isNotEmpty) TextButton.icon(
TextButton.icon( icon: const Icon(Icons.info_outline_rounded),
icon: const Icon(Icons.info_outline_rounded), onPressed: () => context.pushRoute(
onPressed: () => context.pushRoute( const DriftUploadDetailRoute(),
const DriftUploadDetailRoute(),
),
label: Text("view_details".t(context: context)),
), ),
label: Text("view_details".t(context: context)),
),
], ],
], ],
), ),
@@ -9,6 +9,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
@@ -28,6 +29,8 @@ class _DriftBackupAlbumSelectionPageState
extends ConsumerState<DriftBackupAlbumSelectionPage> { extends ConsumerState<DriftBackupAlbumSelectionPage> {
String _searchQuery = ''; String _searchQuery = '';
bool _isSearchMode = false; bool _isSearchMode = false;
int _initialTotalAssetCount = 0;
bool _hasPopped = false;
late ValueNotifier<bool> _enableSyncUploadAlbum; late ValueNotifier<bool> _enableSyncUploadAlbum;
late TextEditingController _searchController; late TextEditingController _searchController;
late FocusNode _searchFocusNode; late FocusNode _searchFocusNode;
@@ -43,6 +46,9 @@ class _DriftBackupAlbumSelectionPageState
.read(appSettingsServiceProvider) .read(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.syncAlbums); .getSetting(AppSettingsEnum.syncAlbums);
ref.read(backupAlbumProvider.notifier).getAll(); ref.read(backupAlbumProvider.notifier).getAll();
_initialTotalAssetCount =
ref.read(driftBackupProvider.select((p) => p.totalCount));
} }
@override @override
@@ -79,179 +85,207 @@ class _DriftBackupAlbumSelectionPageState
} }
} }
return Scaffold( return PopScope(
appBar: AppBar( onPopInvokedWithResult: (didPop, result) async {
leading: IconButton( // There is an issue with Flutter where the pop event
onPressed: () => context.maybePop(), // can be triggered multiple times, so we guard it with _hasPopped
icon: const Icon(Icons.arrow_back_ios_rounded), if (didPop && !_hasPopped) {
), _hasPopped = true;
title: _isSearchMode
? SearchField( await ref.read(driftBackupProvider.notifier).getBackupStatus();
hintText: 'search_albums'.t(context: context), final currentTotalAssetCount =
autofocus: true, ref.read(driftBackupProvider.select((p) => p.totalCount));
controller: _searchController,
focusNode: _searchFocusNode, if (currentTotalAssetCount != _initialTotalAssetCount) {
onChanged: (value) => final isBackupEnabled = ref
setState(() => _searchQuery = value.trim()), .read(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.enableBackup);
if (!isBackupEnabled) {
return;
}
final backupNotifier = ref.read(driftBackupProvider.notifier);
backupNotifier.cancel().then((_) {
backupNotifier.backup();
});
}
}
},
child: Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () async => await context.maybePop(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
title: _isSearchMode
? SearchField(
hintText: 'search_albums'.t(context: context),
autofocus: true,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (value) =>
setState(() => _searchQuery = value.trim()),
)
: const Text(
"backup_album_selection_page_select_albums",
).t(context: context),
actions: [
if (!_isSearchMode)
IconButton(
icon: const Icon(Icons.search),
onPressed: () => setState(() {
_isSearchMode = true;
_searchQuery = '';
}),
) )
: const Text( else
"backup_album_selection_page_select_albums", IconButton(
).t(context: context), icon: const Icon(Icons.close),
actions: [ onPressed: () => setState(() {
if (!_isSearchMode) _isSearchMode = false;
IconButton( _searchQuery = '';
icon: const Icon(Icons.search), _searchController.clear();
onPressed: () => setState(() { }),
_isSearchMode = true; ),
_searchQuery = ''; ],
}), elevation: 0,
) ),
else body: CustomScrollView(
IconButton( physics: const ClampingScrollPhysics(),
icon: const Icon(Icons.close), slivers: [
onPressed: () => setState(() { SliverToBoxAdapter(
_isSearchMode = false; child: Column(
_searchQuery = ''; crossAxisAlignment: CrossAxisAlignment.start,
_searchController.clear(); children: [
}), Padding(
), padding: const EdgeInsets.symmetric(
], vertical: 8.0,
elevation: 0, horizontal: 16.0,
),
body: CustomScrollView(
physics: const ClampingScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
child: Text(
"backup_album_selection_page_selection_info",
style: context.textTheme.titleSmall,
).t(context: context),
),
// Selected Album Chips
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Wrap(
children: [
_SelectedAlbumNameChips(
selectedBackupAlbums: selectedBackupAlbums,
),
_ExcludedAlbumNameChips(
excludedBackupAlbums: excludedBackupAlbums,
),
],
),
),
SettingsSwitchListTile(
valueNotifier: _enableSyncUploadAlbum,
title: "sync_albums".t(context: context),
subtitle:
"sync_upload_album_setting_subtitle".t(context: context),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
titleStyle: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
),
subtitleStyle: context.textTheme.labelLarge?.copyWith(
color: context.colorScheme.primary,
),
onChanged: handleSyncAlbumToggle,
),
ListTile(
title: Text(
"albums_on_device_count".t(
context: context,
args: {'count': albumCount.toString()},
), ),
style: context.textTheme.titleSmall,
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text( child: Text(
"backup_album_selection_page_albums_tap", "backup_album_selection_page_selection_info",
style: context.textTheme.labelLarge?.copyWith( style: context.textTheme.titleSmall,
color: context.primaryColor,
),
).t(context: context), ).t(context: context),
), ),
trailing: IconButton( // Selected Album Chips
splashRadius: 16,
icon: Icon(
Icons.info,
size: 20,
color: context.primaryColor,
),
onPressed: () {
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(10)),
),
elevation: 5,
title: Text(
'backup_album_selection_page_selection_info',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: context.primaryColor,
),
).t(context: context),
content: SingleChildScrollView(
child: ListBody(
children: [
const Text(
'backup_album_selection_page_assets_scatter',
style: TextStyle(
fontSize: 14,
),
).t(context: context),
],
),
),
);
},
);
},
),
),
if (Platform.isAndroid) Padding(
_SelectAllButton( padding: const EdgeInsets.symmetric(horizontal: 16.0),
filteredAlbums: filteredAlbums, child: Wrap(
selectedBackupAlbums: selectedBackupAlbums, children: [
_SelectedAlbumNameChips(
selectedBackupAlbums: selectedBackupAlbums,
),
_ExcludedAlbumNameChips(
excludedBackupAlbums: excludedBackupAlbums,
),
],
),
), ),
],
SettingsSwitchListTile(
valueNotifier: _enableSyncUploadAlbum,
title: "sync_albums".t(context: context),
subtitle: "sync_upload_album_setting_subtitle"
.t(context: context),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
titleStyle: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
),
subtitleStyle: context.textTheme.labelLarge?.copyWith(
color: context.colorScheme.primary,
),
onChanged: handleSyncAlbumToggle,
),
ListTile(
title: Text(
"albums_on_device_count".t(
context: context,
args: {'count': albumCount.toString()},
),
style: context.textTheme.titleSmall,
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
"backup_album_selection_page_albums_tap",
style: context.textTheme.labelLarge?.copyWith(
color: context.primaryColor,
),
).t(context: context),
),
trailing: IconButton(
splashRadius: 16,
icon: Icon(
Icons.info,
size: 20,
color: context.primaryColor,
),
onPressed: () {
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(10)),
),
elevation: 5,
title: Text(
'backup_album_selection_page_selection_info',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: context.primaryColor,
),
).t(context: context),
content: SingleChildScrollView(
child: ListBody(
children: [
const Text(
'backup_album_selection_page_assets_scatter',
style: TextStyle(
fontSize: 14,
),
).t(context: context),
],
),
),
);
},
);
},
),
),
if (Platform.isAndroid)
_SelectAllButton(
filteredAlbums: filteredAlbums,
selectedBackupAlbums: selectedBackupAlbums,
),
],
),
), ),
), SliverLayoutBuilder(
SliverLayoutBuilder( builder: (context, constraints) {
builder: (context, constraints) { if (constraints.crossAxisExtent > 600) {
if (constraints.crossAxisExtent > 600) { return _AlbumSelectionGrid(
return _AlbumSelectionGrid( filteredAlbums: filteredAlbums,
filteredAlbums: filteredAlbums, searchQuery: _searchQuery,
searchQuery: _searchQuery, );
); } else {
} else { return _AlbumSelectionList(
return _AlbumSelectionList( filteredAlbums: filteredAlbums,
filteredAlbums: filteredAlbums, searchQuery: _searchQuery,
searchQuery: _searchQuery, );
); }
} },
}, ),
), ],
], ),
), ),
); );
} }
@@ -39,7 +39,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.cloud_upload_outlined, Icons.cloud_off_rounded,
size: 80, size: 80,
color: context.colorScheme.onSurface.withValues(alpha: 0.3), color: context.colorScheme.onSurface.withValues(alpha: 0.3),
), ),
@@ -79,7 +79,9 @@ class DriftUploadDetailPage extends ConsumerWidget {
return Card( return Card(
elevation: 0, elevation: 0,
color: context.colorScheme.surfaceContainer, color: item.isFailed != null
? context.colorScheme.errorContainer
: context.colorScheme.surfaceContainer,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(16), Radius.circular(16),
@@ -2,10 +2,14 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/backup/backup.provider.dart';
import 'package:immich_mobile/providers/backup/manual_upload.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart';
@@ -43,6 +47,17 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
albumNotifier.dispose(); albumNotifier.dispose();
} }
// Cancel uploads
await Store.put(StoreKey.backgroundBackup, false);
ref.read(backupProvider.notifier).configureBackgroundBackup(
enabled: false,
onBatteryInfo: () {},
onError: (_) {},
);
ref.read(backupProvider.notifier).setAutoBackup(false);
ref.read(backupProvider.notifier).cancelBackup();
ref.read(manualUploadProvider.notifier).cancelBackup();
// Start listening to new websocket events
ref.read(websocketProvider.notifier).stopListenToOldEvents(); ref.read(websocketProvider.notifier).stopListenToOldEvents();
ref.read(websocketProvider.notifier).startListeningToBetaEvents(); ref.read(websocketProvider.notifier).startListeningToBetaEvents();
+3 -3
View File
@@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/settings/advanced_settings.dart'; import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
@@ -13,7 +13,7 @@ import 'package:immich_mobile/widgets/settings/language_settings.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart';
import 'package:immich_mobile/widgets/settings/notification_setting.dart'; import 'package:immich_mobile/widgets/settings/notification_setting.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/preference_setting.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/preference_setting.dart';
import 'package:immich_mobile/entities/store.entity.dart' as app_store; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/widgets/settings/settings_card.dart'; import 'package:immich_mobile/widgets/settings/settings_card.dart';
enum SettingSection { enum SettingSection {
@@ -112,7 +112,7 @@ class _MobileLayout extends StatelessWidget {
padding: const EdgeInsets.only(top: 10.0, bottom: 56), padding: const EdgeInsets.only(top: 10.0, bottom: 56),
children: [ children: [
const BetaTimelineListTile(), const BetaTimelineListTile(),
if (app_store.Store.isBetaTimelineEnabled) if (Store.isBetaTimelineEnabled)
SettingsCard( SettingsCard(
icon: Icons.sync_outlined, icon: Icons.sync_outlined,
title: 'beta_sync'.tr(), title: 'beta_sync'.tr(),
@@ -1,36 +0,0 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
class DriftPersonDetailPage extends StatelessWidget {
final Person person;
const DriftPersonDetailPage({super.key, required this.person});
@override
Widget build(BuildContext context) {
return ProviderScope(
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final timelineService =
ref.watch(timelineFactoryProvider).person(person.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
),
],
child: Timeline(
appBar: MesmerizingSliverAppBar(
title: person.name,
icon: Icons.person_outline,
),
),
);
}
}
@@ -50,31 +50,30 @@ class ViewerBottomBar extends ConsumerWidget {
duration: Durations.short4, duration: Durations.short4,
child: isSheetOpen child: isSheetOpen
? const SizedBox.shrink() ? const SizedBox.shrink()
: SafeArea( : Theme(
child: Theme( data: context.themeData.copyWith(
data: context.themeData.copyWith( iconTheme:
iconTheme: const IconThemeData(size: 22, color: Colors.white),
const IconThemeData(size: 22, color: Colors.white), textTheme: context.themeData.textTheme.copyWith(
textTheme: context.themeData.textTheme.copyWith( labelLarge:
labelLarge: context.themeData.textTheme.labelLarge?.copyWith(
context.themeData.textTheme.labelLarge?.copyWith( color: Colors.white,
color: Colors.white,
),
), ),
), ),
child: Container( ),
height: asset.isVideo ? 160 : 80, child: Container(
color: Colors.black.withAlpha(125), height: context.padding.bottom + (asset.isVideo ? 160 : 80),
child: Column( color: Colors.black.withAlpha(125),
mainAxisAlignment: MainAxisAlignment.end, padding: EdgeInsets.only(bottom: context.padding.bottom),
children: [ child: Column(
if (asset.isVideo) const VideoControls(), mainAxisAlignment: MainAxisAlignment.end,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, if (asset.isVideo) const VideoControls(),
children: actions, Row(
), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
], children: actions,
), ),
],
), ),
), ),
), ),
@@ -27,6 +27,26 @@ import 'package:logging/logging.dart';
import 'package:native_video_player/native_video_player.dart'; import 'package:native_video_player/native_video_player.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
bool _isCurrentAsset(
BaseAsset asset,
BaseAsset? currentAsset,
) {
if (asset is RemoteAsset) {
return switch (currentAsset) {
RemoteAsset remoteAsset => remoteAsset.id == asset.id,
LocalAsset localAsset => localAsset.remoteId == asset.id,
_ => false,
};
} else if (asset is LocalAsset) {
return switch (currentAsset) {
RemoteAsset remoteAsset => remoteAsset.localId == asset.id,
LocalAsset localAsset => localAsset.id == asset.id,
_ => false,
};
}
return false;
}
class NativeVideoViewer extends HookConsumerWidget { class NativeVideoViewer extends HookConsumerWidget {
final BaseAsset asset; final BaseAsset asset;
final bool showControls; final bool showControls;
@@ -56,7 +76,7 @@ class NativeVideoViewer extends HookConsumerWidget {
// If the swipe is completed, `isCurrent` will be true for video B after a delay. // If the swipe is completed, `isCurrent` will be true for video B after a delay.
// If the swipe is canceled, `currentAsset` will not have changed and video A will continue to play. // If the swipe is canceled, `currentAsset` will not have changed and video A will continue to play.
final currentAsset = useState(ref.read(currentAssetNotifier)); final currentAsset = useState(ref.read(currentAssetNotifier));
final isCurrent = currentAsset.value == asset; final isCurrent = _isCurrentAsset(asset, currentAsset.value);
// Used to show the placeholder during hero animations for remote videos to avoid a stutter // Used to show the placeholder during hero animations for remote videos to avoid a stutter
final isVisible = useState(Platform.isIOS && asset.hasLocal); final isVisible = useState(Platform.isIOS && asset.hasLocal);
@@ -107,19 +107,22 @@ class _FixedSegmentRow extends ConsumerWidget {
} }
if (timelineService.hasRange(assetIndex, assetCount)) { if (timelineService.hasRange(assetIndex, assetCount)) {
return _buildAssetRow( final assets = timelineService.getAssets(assetIndex, assetCount);
context, if (assets == null) {
timelineService.getAssets(assetIndex, assetCount), return _buildPlaceholder(context);
); }
return _buildAssetRow(context, assets);
} }
return FutureBuilder<List<BaseAsset>>( return FutureBuilder<List<BaseAsset>?>(
future: timelineService.loadAssets(assetIndex, assetCount), future: timelineService.loadAssets(assetIndex, assetCount),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) { if (snapshot.connectionState != ConnectionState.done ||
snapshot.data == null) {
return _buildPlaceholder(context); return _buildPlaceholder(context);
} }
return _buildAssetRow(context, snapshot.requireData); return _buildAssetRow(context, snapshot.data!);
}, },
); );
} }
@@ -112,8 +112,9 @@ class _BulkSelectIconButton extends ConsumerWidget {
List<BaseAsset> bucketAssets; List<BaseAsset> bucketAssets;
try { try {
bucketAssets = ref bucketAssets = ref
.watch(timelineServiceProvider) .watch(timelineServiceProvider)
.getAssets(assetOffset, bucket.assetCount); .getAssets(assetOffset, bucket.assetCount) ??
[];
} catch (e) { } catch (e) {
bucketAssets = <BaseAsset>[]; bucketAssets = <BaseAsset>[];
} }
@@ -41,6 +41,7 @@ class DriftUploadStatus {
final double progress; final double progress;
final int fileSize; final int fileSize;
final String networkSpeedAsString; final String networkSpeedAsString;
final bool? isFailed;
const DriftUploadStatus({ const DriftUploadStatus({
required this.taskId, required this.taskId,
@@ -48,6 +49,7 @@ class DriftUploadStatus {
required this.progress, required this.progress,
required this.fileSize, required this.fileSize,
required this.networkSpeedAsString, required this.networkSpeedAsString,
this.isFailed,
}); });
DriftUploadStatus copyWith({ DriftUploadStatus copyWith({
@@ -56,6 +58,7 @@ class DriftUploadStatus {
double? progress, double? progress,
int? fileSize, int? fileSize,
String? networkSpeedAsString, String? networkSpeedAsString,
bool? isFailed,
}) { }) {
return DriftUploadStatus( return DriftUploadStatus(
taskId: taskId ?? this.taskId, taskId: taskId ?? this.taskId,
@@ -63,12 +66,13 @@ class DriftUploadStatus {
progress: progress ?? this.progress, progress: progress ?? this.progress,
fileSize: fileSize ?? this.fileSize, fileSize: fileSize ?? this.fileSize,
networkSpeedAsString: networkSpeedAsString ?? this.networkSpeedAsString, networkSpeedAsString: networkSpeedAsString ?? this.networkSpeedAsString,
isFailed: isFailed ?? this.isFailed,
); );
} }
@override @override
String toString() { String toString() {
return 'DriftUploadStatus(taskId: $taskId, filename: $filename, progress: $progress, fileSize: $fileSize, networkSpeedAsString: $networkSpeedAsString)'; return 'DriftUploadStatus(taskId: $taskId, filename: $filename, progress: $progress, fileSize: $fileSize, networkSpeedAsString: $networkSpeedAsString, isFailed: $isFailed)';
} }
@override @override
@@ -79,7 +83,8 @@ class DriftUploadStatus {
other.filename == filename && other.filename == filename &&
other.progress == progress && other.progress == progress &&
other.fileSize == fileSize && other.fileSize == fileSize &&
other.networkSpeedAsString == networkSpeedAsString; other.networkSpeedAsString == networkSpeedAsString &&
other.isFailed == isFailed;
} }
@override @override
@@ -88,7 +93,8 @@ class DriftUploadStatus {
filename.hashCode ^ filename.hashCode ^
progress.hashCode ^ progress.hashCode ^
fileSize.hashCode ^ fileSize.hashCode ^
networkSpeedAsString.hashCode; networkSpeedAsString.hashCode ^
isFailed.hashCode;
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
@@ -98,6 +104,7 @@ class DriftUploadStatus {
'progress': progress, 'progress': progress,
'fileSize': fileSize, 'fileSize': fileSize,
'networkSpeedAsString': networkSpeedAsString, 'networkSpeedAsString': networkSpeedAsString,
'isFailed': isFailed,
}; };
} }
@@ -108,6 +115,7 @@ class DriftUploadStatus {
progress: map['progress'] as double, progress: map['progress'] as double,
fileSize: map['fileSize'] as int, fileSize: map['fileSize'] as int,
networkSpeedAsString: map['networkSpeedAsString'] as String, networkSpeedAsString: map['networkSpeedAsString'] as String,
isFailed: map['isFailed'] != null ? map['isFailed'] as bool : null,
); );
} }
@@ -235,6 +243,8 @@ class ExpBackupNotifier extends StateNotifier<DriftBackupState> {
} }
void _handleTaskStatusUpdate(TaskStatusUpdate update) { void _handleTaskStatusUpdate(TaskStatusUpdate update) {
final taskId = update.task.taskId;
switch (update.status) { switch (update.status) {
case TaskStatus.complete: case TaskStatus.complete:
if (update.task.group == kBackupGroup) { if (update.task.group == kBackupGroup) {
@@ -245,14 +255,26 @@ class ExpBackupNotifier extends StateNotifier<DriftBackupState> {
} }
// Remove the completed task from the upload items // Remove the completed task from the upload items
final taskId = update.task.taskId;
if (state.uploadItems.containsKey(taskId)) { if (state.uploadItems.containsKey(taskId)) {
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 1000), () {
_removeUploadItem(taskId); _removeUploadItem(taskId);
}); });
} }
case TaskStatus.failed: case TaskStatus.failed:
final currentItem = state.uploadItems[taskId];
if (currentItem == null) {
return;
}
state = state.copyWith(
uploadItems: {
...state.uploadItems,
taskId: currentItem.copyWith(
isFailed: true,
),
},
);
break; break;
case TaskStatus.canceled: case TaskStatus.canceled:
@@ -1,6 +1,5 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
@@ -130,7 +129,7 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
final assets = await _timelineService.loadAssets(offset, bucketCount); final assets = await _timelineService.loadAssets(offset, bucketCount);
final selectedAssets = state.selectedAssets.toSet(); final selectedAssets = state.selectedAssets.toSet();
selectedAssets.addAll(assets); selectedAssets.addAll(assets ?? []);
state = state.copyWith( state = state.copyWith(
selectedAssets: selectedAssets, selectedAssets: selectedAssets,
@@ -141,14 +140,14 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
final assets = await _timelineService.loadAssets(offset, bucketCount); final assets = await _timelineService.loadAssets(offset, bucketCount);
final selectedAssets = state.selectedAssets.toSet(); final selectedAssets = state.selectedAssets.toSet();
selectedAssets.removeAll(assets); selectedAssets.removeAll(assets ?? []);
state = state.copyWith(selectedAssets: selectedAssets); state = state.copyWith(selectedAssets: selectedAssets);
} }
void toggleBucketSelection(int offset, int bucketCount) async { void toggleBucketSelection(int offset, int bucketCount) async {
final assets = await _timelineService.loadAssets(offset, bucketCount); final assets = await _timelineService.loadAssets(offset, bucketCount);
toggleBucketSelectionByAssets(assets); toggleBucketSelectionByAssets(assets ?? []);
} }
void toggleBucketSelectionByAssets(List<BaseAsset> bucketAssets) { void toggleBucketSelectionByAssets(List<BaseAsset> bucketAssets) {
@@ -4,10 +4,10 @@ import 'dart:io';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/download.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
final downloadRepositoryProvider = Provider((ref) => DownloadRepository()); final downloadRepositoryProvider = Provider((ref) => DownloadRepository());
@@ -33,19 +33,19 @@ class DownloadRepository {
DownloadRepository() { DownloadRepository() {
_downloader.registerCallbacks( _downloader.registerCallbacks(
group: downloadGroupImage, group: kDownloadGroupImage,
taskStatusCallback: (update) => onImageDownloadStatus?.call(update), taskStatusCallback: (update) => onImageDownloadStatus?.call(update),
taskProgressCallback: (update) => onTaskProgress?.call(update), taskProgressCallback: (update) => onTaskProgress?.call(update),
); );
_downloader.registerCallbacks( _downloader.registerCallbacks(
group: downloadGroupVideo, group: kDownloadGroupVideo,
taskStatusCallback: (update) => onVideoDownloadStatus?.call(update), taskStatusCallback: (update) => onVideoDownloadStatus?.call(update),
taskProgressCallback: (update) => onTaskProgress?.call(update), taskProgressCallback: (update) => onTaskProgress?.call(update),
); );
_downloader.registerCallbacks( _downloader.registerCallbacks(
group: downloadGroupLivePhoto, group: kDownloadGroupLivePhoto,
taskStatusCallback: (update) => onLivePhotoDownloadStatus?.call(update), taskStatusCallback: (update) => onLivePhotoDownloadStatus?.call(update),
taskProgressCallback: (update) => onTaskProgress?.call(update), taskProgressCallback: (update) => onTaskProgress?.call(update),
); );
@@ -66,7 +66,7 @@ class DownloadRepository {
Future<List<TaskRecord>> getLiveVideoTasks() { Future<List<TaskRecord>> getLiveVideoTasks() {
return _downloader.database.allRecordsWithStatus( return _downloader.database.allRecordsWithStatus(
TaskStatus.complete, TaskStatus.complete,
group: downloadGroupLivePhoto, group: kDownloadGroupLivePhoto,
); );
} }
@@ -100,7 +100,7 @@ class DownloadRepository {
headers: headers, headers: headers,
filename: asset.name, filename: asset.name,
updates: Updates.statusAndProgress, updates: Updates.statusAndProgress,
group: isVideo ? downloadGroupVideo : downloadGroupImage, group: isVideo ? kDownloadGroupVideo : kDownloadGroupImage,
); );
continue; continue;
} }
@@ -113,7 +113,7 @@ class DownloadRepository {
headers: headers, headers: headers,
filename: asset.name, filename: asset.name,
updates: Updates.statusAndProgress, updates: Updates.statusAndProgress,
group: downloadGroupLivePhoto, group: kDownloadGroupLivePhoto,
metaData: json.encode(_dummyMetadata), metaData: json.encode(_dummyMetadata),
); );
@@ -126,7 +126,7 @@ class DownloadRepository {
.toUpperCase() .toUpperCase()
.replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'), .replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'),
updates: Updates.statusAndProgress, updates: Updates.statusAndProgress,
group: downloadGroupLivePhoto, group: kDownloadGroupLivePhoto,
metaData: json.encode(_dummyMetadata), metaData: json.encode(_dummyMetadata),
); );
} }
+1 -6
View File
@@ -6,7 +6,6 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/album.entity.dart';
@@ -96,7 +95,6 @@ import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'
import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart'; import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart';
import 'package:immich_mobile/presentation/pages/drift_trash.page.dart'; import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
import 'package:immich_mobile/presentation/pages/drift_video.page.dart'; import 'package:immich_mobile/presentation/pages/drift_video.page.dart';
import 'package:immich_mobile/presentation/pages/drift_person_detail.dart';
import 'package:immich_mobile/presentation/pages/local_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/local_timeline.page.dart';
import 'package:immich_mobile/presentation/pages/search/drift_search.page.dart'; import 'package:immich_mobile/presentation/pages/search/drift_search.page.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
@@ -488,6 +486,7 @@ class AppRouter extends RootStackRouter {
page: ChangeExperienceRoute.page, page: ChangeExperienceRoute.page,
guards: [_authGuard, _duplicateGuard], guards: [_authGuard, _duplicateGuard],
), ),
AutoRoute( AutoRoute(
page: DriftPartnerRoute.page, page: DriftPartnerRoute.page,
guards: [_authGuard, _duplicateGuard], guards: [_authGuard, _duplicateGuard],
@@ -500,10 +499,6 @@ class AppRouter extends RootStackRouter {
page: BetaSyncSettingsRoute.page, page: BetaSyncSettingsRoute.page,
guards: [_authGuard, _duplicateGuard], guards: [_authGuard, _duplicateGuard],
), ),
AutoRoute(
page: DriftPersonDetailRoute.page,
guards: [_authGuard, _duplicateGuard],
),
// required to handle all deeplinks in deep_link.service.dart // required to handle all deeplinks in deep_link.service.dart
// auto_route_library#1722 // auto_route_library#1722
RedirectRoute(path: '*', redirectTo: '/'), RedirectRoute(path: '*', redirectTo: '/'),
-37
View File
@@ -960,43 +960,6 @@ class DriftPartnerRoute extends PageRouteInfo<void> {
); );
} }
/// generated route for
/// [DriftPersonDetailPage]
class DriftPersonDetailRoute extends PageRouteInfo<DriftPersonDetailRouteArgs> {
DriftPersonDetailRoute({
Key? key,
required Person person,
List<PageRouteInfo>? children,
}) : super(
DriftPersonDetailRoute.name,
args: DriftPersonDetailRouteArgs(key: key, person: person),
initialChildren: children,
);
static const String name = 'DriftPersonDetailRoute';
static PageInfo page = PageInfo(
name,
builder: (data) {
final args = data.argsAs<DriftPersonDetailRouteArgs>();
return DriftPersonDetailPage(key: args.key, person: args.person);
},
);
}
class DriftPersonDetailRouteArgs {
const DriftPersonDetailRouteArgs({this.key, required this.person});
final Key? key;
final Person person;
@override
String toString() {
return 'DriftPersonDetailRouteArgs{key: $key, person: $person}';
}
}
/// generated route for /// generated route for
/// [DriftPlaceDetailPage] /// [DriftPlaceDetailPage]
class DriftPlaceDetailRoute extends PageRouteInfo<DriftPlaceDetailRouteArgs> { class DriftPlaceDetailRoute extends PageRouteInfo<DriftPlaceDetailRouteArgs> {
+4 -4
View File
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
@@ -10,7 +11,6 @@ import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:immich_mobile/repositories/download.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/download.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
final downloadServiceProvider = Provider( final downloadServiceProvider = Provider(
@@ -173,7 +173,7 @@ class DownloadService {
_buildDownloadTask( _buildDownloadTask(
asset.remoteId!, asset.remoteId!,
asset.fileName, asset.fileName,
group: downloadGroupLivePhoto, group: kDownloadGroupLivePhoto,
metadata: LivePhotosMetadata( metadata: LivePhotosMetadata(
part: LivePhotosPart.image, part: LivePhotosPart.image,
id: asset.remoteId!, id: asset.remoteId!,
@@ -184,7 +184,7 @@ class DownloadService {
asset.fileName asset.fileName
.toUpperCase() .toUpperCase()
.replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'), .replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'),
group: downloadGroupLivePhoto, group: kDownloadGroupLivePhoto,
metadata: LivePhotosMetadata( metadata: LivePhotosMetadata(
part: LivePhotosPart.video, part: LivePhotosPart.video,
id: asset.remoteId!, id: asset.remoteId!,
@@ -201,7 +201,7 @@ class DownloadService {
_buildDownloadTask( _buildDownloadTask(
asset.remoteId!, asset.remoteId!,
asset.fileName, asset.fileName,
group: asset.isImage ? downloadGroupImage : downloadGroupVideo, group: asset.isImage ? kDownloadGroupImage : kDownloadGroupVideo,
), ),
]; ];
} }
-3
View File
@@ -1,3 +0,0 @@
const downloadGroupImage = 'group_image';
const downloadGroupVideo = 'group_video';
const downloadGroupLivePhoto = 'group_livephoto';
@@ -364,7 +364,10 @@ class _SyncStatusIndicatorState extends ConsumerState<_SyncStatusIndicator>
child: Opacity( child: Opacity(
opacity: isSyncing ? 1.0 : _dismissalAnimation.value, opacity: isSyncing ? 1.0 : _dismissalAnimation.value,
child: Transform.rotate( child: Transform.rotate(
angle: _rotationAnimation.value * 2 * 3.14159, angle: _rotationAnimation.value *
2 *
3.14159 *
-1, // Rotate counter-clockwise
child: Icon( child: Icon(
Icons.sync, Icons.sync,
size: 24, size: 24,
+1
View File
@@ -221,6 +221,7 @@ Class | Method | HTTP request | Description
*StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} | *StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} |
*StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks | *StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks |
*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} | *StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} |
*StacksApi* | [**removeAssetFromStack**](doc//StacksApi.md#removeassetfromstack) | **DELETE** /stacks/{id}/assets/{assetId} |
*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks | *StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks |
*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} | *StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} |
*SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack | *SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack |
+45
View File
@@ -190,6 +190,51 @@ class StacksApi {
return null; return null;
} }
/// Performs an HTTP 'DELETE /stacks/{id}/assets/{assetId}' operation and returns the [Response].
/// Parameters:
///
/// * [String] assetId (required):
///
/// * [String] id (required):
Future<Response> removeAssetFromStackWithHttpInfo(String assetId, String id,) async {
// ignore: prefer_const_declarations
final apiPath = r'/stacks/{id}/assets/{assetId}'
.replaceAll('{assetId}', assetId)
.replaceAll('{id}', id);
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>[];
return apiClient.invokeAPI(
apiPath,
'DELETE',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [String] assetId (required):
///
/// * [String] id (required):
Future<void> removeAssetFromStack(String assetId, String id,) async {
final response = await removeAssetFromStackWithHttpInfo(assetId, id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
}
/// Performs an HTTP 'GET /stacks' operation and returns the [Response]. /// Performs an HTTP 'GET /stacks' operation and returns the [Response].
/// Parameters: /// Parameters:
/// ///
+44
View File
@@ -6746,6 +6746,50 @@
] ]
} }
}, },
"/stacks/{id}/assets/{assetId}": {
"delete": {
"operationId": "removeAssetFromStack",
"parameters": [
{
"name": "assetId",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
}
},
"security": [
{
"bearer": []
},
{
"cookie": []
},
{
"api_key": []
}
],
"tags": [
"Stacks"
]
}
},
"/sync/ack": { "/sync/ack": {
"delete": { "delete": {
"operationId": "deleteSyncAck", "operationId": "deleteSyncAck",
@@ -3373,6 +3373,15 @@ export function updateStack({ id, stackUpdateDto }: {
body: stackUpdateDto body: stackUpdateDto
}))); })));
} }
export function removeAssetFromStack({ assetId, id }: {
assetId: string;
id: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchText(`/stacks/${encodeURIComponent(id)}/assets/${encodeURIComponent(assetId)}`, {
...opts,
method: "DELETE"
}));
}
export function deleteSyncAck({ syncAckDeleteDto }: { export function deleteSyncAck({ syncAckDeleteDto }: {
syncAckDeleteDto: SyncAckDeleteDto; syncAckDeleteDto: SyncAckDeleteDto;
}, opts?: Oazapfts.RequestOpts) { }, opts?: Oazapfts.RequestOpts) {
+226 -237
View File
@@ -30,7 +30,7 @@
"@opentelemetry/sdk-metrics": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1",
"@opentelemetry/sdk-node": "^0.203.0", "@opentelemetry/sdk-node": "^0.203.0",
"@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/semantic-conventions": "^1.34.0",
"@react-email/components": "^0.2.0", "@react-email/components": "^0.3.0",
"@react-email/render": "^1.1.2", "@react-email/render": "^1.1.2",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0", "archiver": "^7.0.0",
@@ -54,7 +54,7 @@
"i18n-iso-countries": "^7.6.0", "i18n-iso-countries": "^7.6.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kysely": "^0.28.0", "kysely": "^0.28.2",
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luxon": "^3.4.2", "luxon": "^3.4.2",
@@ -297,6 +297,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@angular-devkit/schematics-cli/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@angular-devkit/schematics-cli/node_modules/rxjs": { "node_modules/@angular-devkit/schematics-cli/node_modules/rxjs": {
"version": "7.8.1", "version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@@ -369,6 +382,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@angular-devkit/schematics/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@angular-devkit/schematics/node_modules/rxjs": { "node_modules/@angular-devkit/schematics/node_modules/rxjs": {
"version": "7.8.1", "version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@@ -1159,9 +1185,9 @@
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.14.0", "version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -1209,9 +1235,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.30.1", "version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1245,19 +1271,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz",
"integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@golevelup/nestjs-discovery": { "node_modules/@golevelup/nestjs-discovery": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/@golevelup/nestjs-discovery/-/nestjs-discovery-4.0.3.tgz", "resolved": "https://registry.npmjs.org/@golevelup/nestjs-discovery/-/nestjs-discovery-4.0.3.tgz",
@@ -2736,6 +2749,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@nestjs/cli/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@nestjs/cli/node_modules/rxjs": { "node_modules/@nestjs/cli/node_modules/rxjs": {
"version": "7.8.1", "version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@@ -2757,9 +2783,9 @@
} }
}, },
"node_modules/@nestjs/common": { "node_modules/@nestjs/common": {
"version": "11.1.3", "version": "11.1.5",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.5.tgz",
"integrity": "sha512-ogEK+GriWodIwCw6buQ1rpcH4Kx+G7YQ9EwuPySI3rS05pSdtQ++UhucjusSI9apNidv+QURBztJkRecwwJQXg==", "integrity": "sha512-DQpWdr3ShO0BHWkHl3I4W/jR6R3pDtxyBlmrpTuZF+PXxQyBXNvsUne0Wyo6QHPEDi+pAz9XchBFoKbqOhcdTg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"file-type": "21.0.0", "file-type": "21.0.0",
@@ -2788,9 +2814,9 @@
} }
}, },
"node_modules/@nestjs/core": { "node_modules/@nestjs/core": {
"version": "11.1.3", "version": "11.1.5",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.5.tgz",
"integrity": "sha512-5lTni0TCh8x7bXETRD57pQFnKnEg1T6M+VLE7wAmyQRIecKQU+2inRGZD+A4v2DC1I04eA0WffP0GKLxjOKlzw==", "integrity": "sha512-Qr25MEY9t8VsMETy7eXQ0cNXqu0lzuFrrTr+f+1G57ABCtV5Pogm7n9bF71OU2bnkDD32Bi4hQLeFR90cku3Tw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2862,14 +2888,14 @@
} }
}, },
"node_modules/@nestjs/platform-express": { "node_modules/@nestjs/platform-express": {
"version": "11.1.3", "version": "11.1.5",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.5.tgz",
"integrity": "sha512-hEDNMlaPiBO72fxxX/CuRQL3MEhKRc/sIYGVoXjrnw6hTxZdezvvM6A95UaLsYknfmcZZa/CdG1SMBZOu9agHQ==", "integrity": "sha512-OsoiUBY9Shs5IG3uvDIt9/IDfY5OlvWBESuB/K4Eun8xILw1EK5d5qMfC3d2sIJ+kA3l+kBR1d/RuzH7VprLIg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cors": "2.8.5", "cors": "2.8.5",
"express": "5.1.0", "express": "5.1.0",
"multer": "2.0.1", "multer": "2.0.2",
"path-to-regexp": "8.2.0", "path-to-regexp": "8.2.0",
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
@@ -2882,71 +2908,10 @@
"@nestjs/core": "^11.0.0" "@nestjs/core": "^11.0.0"
} }
}, },
"node_modules/@nestjs/platform-express/node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@nestjs/platform-express/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@nestjs/platform-express/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@nestjs/platform-express/node_modules/multer": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz",
"integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
"busboy": "^1.6.0",
"concat-stream": "^2.0.0",
"mkdirp": "^0.5.6",
"object-assign": "^4.1.1",
"type-is": "^1.6.18",
"xtend": "^4.0.2"
},
"engines": {
"node": ">= 10.16.0"
}
},
"node_modules/@nestjs/platform-express/node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@nestjs/platform-socket.io": { "node_modules/@nestjs/platform-socket.io": {
"version": "11.1.3", "version": "11.1.5",
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.5.tgz",
"integrity": "sha512-jQ+ccprmh3kKolBp+bb97zoaS3vKaiyeNqyctGqV4CSG8P6mXSaaUObWxAsw6Jdgn5YQAVEBWJ6FhvF4s6QZbg==", "integrity": "sha512-DY3zNY+BjbrYpV/t8HL8ptrusrWK8J0cfkfY1iZsfCd+0/1+j8IKno+QMLkerNQAZ7/Frh5tkaKHVwWk18TkMw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"socket.io": "4.8.1", "socket.io": "4.8.1",
@@ -3063,6 +3028,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@nestjs/schematics/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@nestjs/schematics/node_modules/rxjs": { "node_modules/@nestjs/schematics/node_modules/rxjs": {
"version": "7.8.1", "version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@@ -3117,9 +3095,9 @@
} }
}, },
"node_modules/@nestjs/testing": { "node_modules/@nestjs/testing": {
"version": "11.1.3", "version": "11.1.5",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.5.tgz",
"integrity": "sha512-CeXG6/eEqgFIkPkmU00y18Dd3DLOIDFhPItzJK1SWckKo6IhcnfoRJzGx75bmuvUMjb51j6An96S/+MJ2ty9jA==", "integrity": "sha512-ZYRYF750SefmuIo7ZqPlHDcin1OHh6My0OkOfGEFjrD9mJ0vMVIpwMTOOkpzCfCcpqUuxeHBuecpiIn+NLrQbQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3145,9 +3123,9 @@
} }
}, },
"node_modules/@nestjs/websockets": { "node_modules/@nestjs/websockets": {
"version": "11.1.3", "version": "11.1.5",
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.5.tgz",
"integrity": "sha512-IjhWKfRf0D247JxYIEs8USblJJbcxUsKJpzbCPaZ7TrVy4LrpG3IRQDlSTOw599TRIYP5ixyH9C0+v5DyaI9uA==", "integrity": "sha512-mAM11HwyS7aeSUbXdOqHbNCRoHwB0OOb+cmx5sgxvszhdG0Y6bwR60nKA4+EXL9xUEeWoxmbfLmSHlTSIJ9GKA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"iterare": "1.2.1", "iterare": "1.2.1",
@@ -5838,9 +5816,9 @@
} }
}, },
"node_modules/@opentelemetry/semantic-conventions": { "node_modules/@opentelemetry/semantic-conventions": {
"version": "1.34.0", "version": "1.36.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.36.0.tgz",
"integrity": "sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==", "integrity": "sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -6031,9 +6009,9 @@
} }
}, },
"node_modules/@react-email/components": { "node_modules/@react-email/components": {
"version": "0.2.0", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.3.2.tgz",
"integrity": "sha512-y45D+oYDgvL1fuFnauwUk8MwT54l0hWwnUAzzP0bVuwhsmVJFelKOGGMCRch0pcgyINilVlAEk0Xjtcu0Su4cw==", "integrity": "sha512-nVbo0KtBdZbj19lvfFpe0ZhjKPh6LE229+NyQLuTDt6dfaLzNRpSu/rHP+jlvdWBAk93slsoGyWDRldbqklpaA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@react-email/body": "0.0.11", "@react-email/body": "0.0.11",
@@ -6054,7 +6032,7 @@
"@react-email/render": "1.1.3", "@react-email/render": "1.1.3",
"@react-email/row": "0.0.12", "@react-email/row": "0.0.12",
"@react-email/section": "0.0.16", "@react-email/section": "0.0.16",
"@react-email/tailwind": "1.1.0", "@react-email/tailwind": "1.2.2",
"@react-email/text": "0.1.5" "@react-email/text": "0.1.5"
}, },
"engines": { "engines": {
@@ -6227,9 +6205,9 @@
} }
}, },
"node_modules/@react-email/tailwind": { "node_modules/@react-email/tailwind": {
"version": "1.1.0", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@react-email/tailwind/-/tailwind-1.2.2.tgz",
"integrity": "sha512-m4sh5d1c8P9TPA6Ea8qHrboE5s9PmRQREIreYMn1l5ca0pCV/UBEY15e1RgoaseAzy2cy+gwI+nKhMwqUJsD1g==", "integrity": "sha512-heO9Khaqxm6Ulm6p7HQ9h01oiiLRrZuuEQuYds/O7Iyp3c58sMVHZGIxiRXO/kSs857NZQycpjewEVKF3jhNTw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@@ -6627,9 +6605,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@swc/core": { "node_modules/@swc/core": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.0.tgz",
"integrity": "sha512-P3GM+0lqjFctcp5HhR9mOcvLSX3SptI9L1aux0Fuvgt8oH4f92rCUrkodAa0U2ktmdjcyIiG37xg2mb/dSCYSA==", "integrity": "sha512-7Fh16ZH/Rj3Di720if+sw9BictD4N5kbTpsyDC+URXhvsZ7qRt1lH7PaeIQYyJJQHwFhoKpwwGxfGU9SHgPLdw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -6645,16 +6623,16 @@
"url": "https://opencollective.com/swc" "url": "https://opencollective.com/swc"
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-darwin-arm64": "1.12.11", "@swc/core-darwin-arm64": "1.13.0",
"@swc/core-darwin-x64": "1.12.11", "@swc/core-darwin-x64": "1.13.0",
"@swc/core-linux-arm-gnueabihf": "1.12.11", "@swc/core-linux-arm-gnueabihf": "1.13.0",
"@swc/core-linux-arm64-gnu": "1.12.11", "@swc/core-linux-arm64-gnu": "1.13.0",
"@swc/core-linux-arm64-musl": "1.12.11", "@swc/core-linux-arm64-musl": "1.13.0",
"@swc/core-linux-x64-gnu": "1.12.11", "@swc/core-linux-x64-gnu": "1.13.0",
"@swc/core-linux-x64-musl": "1.12.11", "@swc/core-linux-x64-musl": "1.13.0",
"@swc/core-win32-arm64-msvc": "1.12.11", "@swc/core-win32-arm64-msvc": "1.13.0",
"@swc/core-win32-ia32-msvc": "1.12.11", "@swc/core-win32-ia32-msvc": "1.13.0",
"@swc/core-win32-x64-msvc": "1.12.11" "@swc/core-win32-x64-msvc": "1.13.0"
}, },
"peerDependencies": { "peerDependencies": {
"@swc/helpers": ">=0.5.17" "@swc/helpers": ">=0.5.17"
@@ -6666,9 +6644,9 @@
} }
}, },
"node_modules/@swc/core-darwin-arm64": { "node_modules/@swc/core-darwin-arm64": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.0.tgz",
"integrity": "sha512-J19Jj9Y5x/N0loExH7W0OI9OwwoVyxutDdkyq1o/kgXyBqmmzV7Y/Q9QekI2Fm/qc5mNeAdP7aj4boY4AY/JPw==", "integrity": "sha512-SkmR9u7MHDu2X8hf7SjZTmsAfQTmel0mi+TJ7AGtufLwGySv6pwQfJ/CIJpcPxYENVqDJAFnDrHaKV8mgA6kxQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -6683,9 +6661,9 @@
} }
}, },
"node_modules/@swc/core-darwin-x64": { "node_modules/@swc/core-darwin-x64": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.0.tgz",
"integrity": "sha512-PTuUQrfStQ6cjW+uprGO2lpQHy84/l0v+GqRqq8s/jdK55rFRjMfCeyf6FAR0l6saO5oNOQl+zWR1aNpj8pMQw==", "integrity": "sha512-15/SyDjXRtFJ09fYHBXUXrj4tpiSpCkjgsF1z3/sSpHH1POWpQUQzxmFyomPQVZ/SsDqP18WGH09Vph4Qriuiw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -6700,9 +6678,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm-gnueabihf": { "node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.0.tgz",
"integrity": "sha512-poxBq152HsupOtnZilenvHmxZ9a8SRj4LtfxUnkMDNOGrZR9oxbQNwEzNKfi3RXEcXz+P8c0Rai1ubBazXv8oQ==", "integrity": "sha512-AHauVHZQEJI/dCZQg6VYNNQ6HROz8dSOnCSheXzzBw1DGWo77BlcxRP0fF0jaAXM9WNqtCUOY1HiJ9ohkAE61Q==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -6717,9 +6695,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-gnu": { "node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.0.tgz",
"integrity": "sha512-y1HNamR/D0Hc8xIE910ysyLe269UYiGaQPoLjQS0phzWFfWdMj9bHM++oydVXZ4RSWycO7KyJ3uvw4NilvyMKQ==", "integrity": "sha512-qyZmBZF7asF6954/x7yn6R7Bzd45KRG05rK2atIF9J3MTa8az7vubP1Q3BWmmss1j8699DELpbuoJucGuhsNXw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -6734,9 +6712,9 @@
} }
}, },
"node_modules/@swc/core-linux-arm64-musl": { "node_modules/@swc/core-linux-arm64-musl": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.0.tgz",
"integrity": "sha512-LlBxPh/32pyQsu2emMEOFRm7poEFLsw12Y1mPY7FWZiZeptomKSOSHRzKDz9EolMiV4qhK1caP1lvW4vminYgQ==", "integrity": "sha512-whskQCOUlLQT7MjnronpHmyHegBka5ig9JkQvecbqhWzRfdwN+c2xTJs3kQsWy2Vc2f1hcL3D8hGIwY5TwPxMQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -6751,9 +6729,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-gnu": { "node_modules/@swc/core-linux-x64-gnu": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.0.tgz",
"integrity": "sha512-bOjiZB8O/1AzHkzjge1jqX62HGRIpOHqFUrGPfAln/NC6NR+Z2A78u3ixV7k5KesWZFhCV0YVGJL+qToL27myA==", "integrity": "sha512-51n4P4nv6rblXyH3zCEktvmR9uSAZ7+zbfeby0sxbj8LS/IKuVd7iCwD5dwMj4CxG9Fs+HgjN73dLQF/OerHhg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -6768,9 +6746,9 @@
} }
}, },
"node_modules/@swc/core-linux-x64-musl": { "node_modules/@swc/core-linux-x64-musl": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.0.tgz",
"integrity": "sha512-4dzAtbT/m3/UjF045+33gLiHd8aSXJDoqof7gTtu4q0ZyAf7XJ3HHspz+/AvOJLVo4FHHdFcdXhmo/zi1nFn8A==", "integrity": "sha512-VMqelgvnXs27eQyhDf1S2O2MxSdchIH7c1tkxODRtu9eotcAeniNNgqqLjZ5ML0MGeRk/WpbsAY/GWi7eSpiHw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -6785,9 +6763,9 @@
} }
}, },
"node_modules/@swc/core-win32-arm64-msvc": { "node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.0.tgz",
"integrity": "sha512-h8HiwBZErKvCAmjW92JvQp0iOqm6bncU4ac5jxBGkRApabpUenNJcj3h2g5O6GL5K6T9/WhnXE5gyq/s1fhPQg==", "integrity": "sha512-NLJmseWJngWeENgat+O/WB4ptNxtx2X4OfPnSG5a/A4sxcn2E4jq91OPvbeUQwDkH+ZQWKXmbXFzt7Nn661QYA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -6802,9 +6780,9 @@
} }
}, },
"node_modules/@swc/core-win32-ia32-msvc": { "node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.0.tgz",
"integrity": "sha512-1pwr325mXRNUhxTtXmx1IokV5SiRL+6iDvnt3FRXj+X5UvXXKtg2zeyftk+03u8v8v8WUr5I32hIypVJPTNxNg==", "integrity": "sha512-UBfwrp0xW37KQGTA08mwrCLIm1ZKy6pXK8IVwou7BvhMgrItRNweTGyUrCnvDLUfyYFuJCmzcEaJ3NudtctD6g==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -6819,9 +6797,9 @@
} }
}, },
"node_modules/@swc/core-win32-x64-msvc": { "node_modules/@swc/core-win32-x64-msvc": {
"version": "1.12.11", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.11.tgz", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.0.tgz",
"integrity": "sha512-5gggWo690Gvs7XiPxAmb5tHwzB9RTVXUV7AWoGb6bmyUd1OXYaebQF0HAOtade5jIoNhfQMQJ7QReRgt/d2jAA==", "integrity": "sha512-BAB1P7Z/y2EENsfsPytPnjIyBVRZN2WULY+s3ozW4QkGmYHde6XXG28n0ABTHhcIOmmR2VzM+uaW1x48laSimw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -7500,17 +7478,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
"integrity": "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==", "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/type-utils": "8.36.0", "@typescript-eslint/type-utils": "8.37.0",
"@typescript-eslint/utils": "8.36.0", "@typescript-eslint/utils": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^7.0.0", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -7524,7 +7502,7 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.36.0", "@typescript-eslint/parser": "^8.37.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <5.9.0"
} }
@@ -7540,16 +7518,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
"integrity": "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q==", "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -7565,14 +7543,14 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==", "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/tsconfig-utils": "^8.37.0",
"@typescript-eslint/types": "^8.36.0", "@typescript-eslint/types": "^8.37.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -7587,14 +7565,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==", "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0" "@typescript-eslint/visitor-keys": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7605,9 +7583,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==", "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -7622,14 +7600,15 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
"integrity": "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==", "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/utils": "8.36.0", "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.1.0" "ts-api-utils": "^2.1.0"
}, },
@@ -7646,9 +7625,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==", "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -7660,16 +7639,16 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==", "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/project-service": "8.37.0",
"@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/visitor-keys": "8.36.0", "@typescript-eslint/visitor-keys": "8.37.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -7715,16 +7694,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==", "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/scope-manager": "8.37.0",
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"@typescript-eslint/typescript-estree": "8.36.0" "@typescript-eslint/typescript-estree": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7739,13 +7718,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==", "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.36.0", "@typescript-eslint/types": "8.37.0",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
@@ -8905,9 +8884,9 @@
} }
}, },
"node_modules/bullmq": { "node_modules/bullmq": {
"version": "5.56.2", "version": "5.56.4",
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.56.2.tgz", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.56.4.tgz",
"integrity": "sha512-bq0PSxPCWeNlFBc5yjBs3eR+e6GxIEIeHY0xxq6WELzG65GPjL+A2ni1NS7NroKsur0C3UJdabw51IswiSTSYw==", "integrity": "sha512-5wSHd0oXs2jS6P+6tay/01Iz0cWRK8iYcscKtpS/GewEq0bJZwbkMZc77GJVOT9SP+UQuXA2y+pQTdCQJel7kQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cron-parser": "^4.9.0", "cron-parser": "^4.9.0",
@@ -9556,16 +9535,16 @@
} }
}, },
"node_modules/compression": { "node_modules/compression": {
"version": "1.8.0", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"compressible": "~2.0.18", "compressible": "~2.0.18",
"debug": "2.6.9", "debug": "2.6.9",
"negotiator": "~0.6.4", "negotiator": "~0.6.4",
"on-headers": "~1.0.2", "on-headers": "~1.1.0",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"vary": "~1.1.2" "vary": "~1.1.2"
}, },
@@ -9588,6 +9567,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/compression/node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -10685,9 +10673,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.30.1", "version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -10695,9 +10683,9 @@
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0", "@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0", "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0", "@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.30.1", "@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1", "@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -13823,9 +13811,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/nest-commander": { "node_modules/nest-commander": {
"version": "3.17.0", "version": "3.18.0",
"resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.17.0.tgz", "resolved": "https://registry.npmjs.org/nest-commander/-/nest-commander-3.18.0.tgz",
"integrity": "sha512-1R9vppZT2j/9njKiG0zYTDLAyQOj14KdGWdNuhluveK8VXoQepXNb0t09dRNWy4KCWrI7wDZ2tQTEwb43JyHOw==", "integrity": "sha512-NWtodOl2aStnApWp9oajCoJW71lqN0CCjf9ygOWxpXnG3o4nQ8ZO5CgrExfVw2+0CVC877hr0rFR7FSu2rypGg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fig/complete-commander": "^3.0.0", "@fig/complete-commander": "^3.0.0",
@@ -13926,9 +13914,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-addon-api": { "node_modules/node-addon-api": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
"integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==", "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18 || ^20 || >= 21" "node": "^18 || ^20 || >= 21"
@@ -14687,9 +14675,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.2", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -15397,9 +15385,9 @@
} }
}, },
"node_modules/react-email": { "node_modules/react-email": {
"version": "4.1.0", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/react-email/-/react-email-4.1.0.tgz", "resolved": "https://registry.npmjs.org/react-email/-/react-email-4.2.3.tgz",
"integrity": "sha512-UvG5z1/gNOsLNwKPO87vgMoF7tdzUGd0kIy4fozzdBBsyLUju7hNVLBRm9j+Li/CwP5CXFT8Y5jZBtIFvSyr0w==", "integrity": "sha512-LUKyk9nNVFuTqAyp4yCEQFQjBe+s8nl3VauMWuOhBZ4VhGnimbrnv01U8yD2YwzaHKtytS0U659x5dc/0+xu+Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.27.0", "@babel/parser": "^7.27.0",
@@ -18447,15 +18435,16 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.36.0", "version": "8.37.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.36.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
"integrity": "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==", "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/eslint-plugin": "8.37.0",
"@typescript-eslint/parser": "8.36.0", "@typescript-eslint/parser": "8.37.0",
"@typescript-eslint/utils": "8.36.0" "@typescript-eslint/typescript-estree": "8.37.0",
"@typescript-eslint/utils": "8.37.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+3 -3
View File
@@ -55,7 +55,7 @@
"@opentelemetry/sdk-metrics": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1",
"@opentelemetry/sdk-node": "^0.203.0", "@opentelemetry/sdk-node": "^0.203.0",
"@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/semantic-conventions": "^1.34.0",
"@react-email/components": "^0.2.0", "@react-email/components": "^0.3.0",
"@react-email/render": "^1.1.2", "@react-email/render": "^1.1.2",
"@socket.io/redis-adapter": "^8.3.0", "@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0", "archiver": "^7.0.0",
@@ -79,7 +79,7 @@
"i18n-iso-countries": "^7.6.0", "i18n-iso-countries": "^7.6.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"kysely": "^0.28.0", "kysely": "^0.28.2",
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luxon": "^3.4.2", "luxon": "^3.4.2",
@@ -113,7 +113,6 @@
"validator": "^13.12.0" "validator": "^13.12.0"
}, },
"devDependencies": { "devDependencies": {
"canvas": "^3.1.0",
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.8.0", "@eslint/js": "^9.8.0",
"@nestjs/cli": "^11.0.2", "@nestjs/cli": "^11.0.2",
@@ -146,6 +145,7 @@
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@types/validator": "^13.15.2", "@types/validator": "^13.15.2",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
"canvas": "^3.1.0",
"eslint": "^9.14.0", "eslint": "^9.14.0",
"eslint-config-prettier": "^10.0.0", "eslint-config-prettier": "^10.0.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
+8 -1
View File
@@ -6,7 +6,7 @@ import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto } from
import { Permission } from 'src/enum'; import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { StackService } from 'src/services/stack.service'; import { StackService } from 'src/services/stack.service';
import { UUIDParamDto } from 'src/validation'; import { UUIDAssetIDParamDto, UUIDParamDto } from 'src/validation';
@ApiTags('Stacks') @ApiTags('Stacks')
@Controller('stacks') @Controller('stacks')
@@ -54,4 +54,11 @@ export class StackController {
deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id); return this.service.delete(auth, id);
} }
@Delete(':id/assets/:assetId')
@Authenticated({ permission: Permission.StackUpdate })
@HttpCode(HttpStatus.NO_CONTENT)
removeAssetFromStack(@Auth() auth: AuthDto, @Param() dto: UUIDAssetIDParamDto): Promise<void> {
return this.service.removeAsset(auth, dto);
}
} }
+10
View File
@@ -143,3 +143,13 @@ from
"stack" "stack"
where where
"id" = $1::uuid "id" = $1::uuid
-- StackRepository.getForAssetRemoval
select
"stackId" as "id",
"stack"."primaryAssetId"
from
"asset"
left join "stack" on "stack"."id" = "asset"."stackId"
where
"asset"."id" = $1
@@ -152,4 +152,14 @@ export class StackRepository {
.where('id', '=', asUuid(id)) .where('id', '=', asUuid(id))
.executeTakeFirst(); .executeTakeFirst();
} }
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getForAssetRemoval(assetId: string) {
return this.db
.selectFrom('asset')
.leftJoin('stack', 'stack.id', 'asset.stackId')
.select(['stackId as id', 'stack.primaryAssetId'])
.where('asset.id', '=', assetId)
.executeTakeFirst();
}
} }
+49
View File
@@ -188,4 +188,53 @@ describe(StackService.name, () => {
}); });
}); });
}); });
describe('removeAsset', () => {
it('should require stack.update permissions', async () => {
await expect(sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: 'asset-id' })).rejects.toBeInstanceOf(
BadRequestException,
);
expect(mocks.stack.getForAssetRemoval).not.toHaveBeenCalled();
expect(mocks.asset.update).not.toHaveBeenCalled();
expect(mocks.event.emit).not.toHaveBeenCalled();
});
it('should fail if the asset is not in the stack', async () => {
mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id']));
mocks.stack.getForAssetRemoval.mockResolvedValue({ id: null, primaryAssetId: null });
await expect(
sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: assetStub.imageFrom2015.id }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.asset.update).not.toHaveBeenCalled();
expect(mocks.event.emit).not.toHaveBeenCalled();
});
it('should fail if the assetId is the primaryAssetId', async () => {
mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id']));
mocks.stack.getForAssetRemoval.mockResolvedValue({ id: 'stack-id', primaryAssetId: assetStub.image.id });
await expect(
sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: assetStub.image.id }),
).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.asset.update).not.toHaveBeenCalled();
expect(mocks.event.emit).not.toHaveBeenCalled();
});
it("should update the asset to nullify it's stack-id", async () => {
mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id']));
mocks.stack.getForAssetRemoval.mockResolvedValue({ id: 'stack-id', primaryAssetId: assetStub.image.id });
await sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: assetStub.image1.id });
expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.image1.id, stackId: null });
expect(mocks.event.emit).toHaveBeenCalledWith('StackUpdate', {
stackId: 'stack-id',
userId: authStub.admin.user.id,
});
});
});
}); });
+19
View File
@@ -4,6 +4,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto, mapStack } from 'src/dtos/stack.dto'; import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto, mapStack } from 'src/dtos/stack.dto';
import { Permission } from 'src/enum'; import { Permission } from 'src/enum';
import { BaseService } from 'src/services/base.service'; import { BaseService } from 'src/services/base.service';
import { UUIDAssetIDParamDto } from 'src/validation';
@Injectable() @Injectable()
export class StackService extends BaseService { export class StackService extends BaseService {
@@ -58,6 +59,24 @@ export class StackService extends BaseService {
await this.eventRepository.emit('StackDeleteAll', { stackIds: dto.ids, userId: auth.user.id }); await this.eventRepository.emit('StackDeleteAll', { stackIds: dto.ids, userId: auth.user.id });
} }
async removeAsset(auth: AuthDto, dto: UUIDAssetIDParamDto): Promise<void> {
const { id: stackId, assetId } = dto;
await this.requireAccess({ auth, permission: Permission.StackUpdate, ids: [stackId] });
const stack = await this.stackRepository.getForAssetRemoval(assetId);
if (!stack?.id || stack.id !== stackId) {
throw new BadRequestException('Asset not in stack');
}
if (stack.primaryAssetId === assetId) {
throw new BadRequestException("Cannot remove stack's primary asset");
}
await this.assetRepository.update({ id: assetId, stackId: null });
await this.eventRepository.emit('StackUpdate', { stackId, userId: auth.user.id });
}
private async findOrFail(id: string) { private async findOrFail(id: string) {
const stack = await this.stackRepository.getById(id); const stack = await this.stackRepository.getById(id);
if (!stack) { if (!stack) {
+24 -16
View File
@@ -63,6 +63,22 @@ export class FileNotEmptyValidator extends FileValidator {
} }
} }
type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean };
export const ValidateUUID = (options?: UUIDOptions & ApiPropertyOptions) => {
const { optional, each, nullable, ...apiPropertyOptions } = {
optional: false,
each: false,
nullable: false,
...options,
};
return applyDecorators(
IsUUID('4', { each }),
ApiProperty({ format: 'uuid', ...apiPropertyOptions }),
optional ? Optional({ nullable }) : IsNotEmpty(),
each ? IsArray() : IsString(),
);
};
export class UUIDParamDto { export class UUIDParamDto {
@IsNotEmpty() @IsNotEmpty()
@IsUUID('4') @IsUUID('4')
@@ -70,6 +86,14 @@ export class UUIDParamDto {
id!: string; id!: string;
} }
export class UUIDAssetIDParamDto {
@ValidateUUID()
id!: string;
@ValidateUUID()
assetId!: string;
}
type PinCodeOptions = { optional?: boolean } & OptionalOptions; type PinCodeOptions = { optional?: boolean } & OptionalOptions;
export const PinCode = (options?: PinCodeOptions & ApiPropertyOptions) => { export const PinCode = (options?: PinCodeOptions & ApiPropertyOptions) => {
const { optional, nullable, emptyToNull, ...apiPropertyOptions } = { const { optional, nullable, emptyToNull, ...apiPropertyOptions } = {
@@ -131,22 +155,6 @@ export const ValidateHexColor = () => {
return applyDecorators(...decorators); return applyDecorators(...decorators);
}; };
type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean };
export const ValidateUUID = (options?: UUIDOptions & ApiPropertyOptions) => {
const { optional, each, nullable, ...apiPropertyOptions } = {
optional: false,
each: false,
nullable: false,
...options,
};
return applyDecorators(
IsUUID('4', { each }),
ApiProperty({ format: 'uuid', ...apiPropertyOptions }),
optional ? Optional({ nullable }) : IsNotEmpty(),
each ? IsArray() : IsString(),
);
};
type DateOptions = { optional?: boolean; nullable?: boolean; format?: 'date' | 'date-time' }; type DateOptions = { optional?: boolean; nullable?: boolean; format?: 'date' | 'date-time' };
export const ValidateDate = (options?: DateOptions & ApiPropertyOptions) => { export const ValidateDate = (options?: DateOptions & ApiPropertyOptions) => {
const { optional, nullable, format, ...apiPropertyOptions } = { const { optional, nullable, format, ...apiPropertyOptions } = {
+4 -1
View File
@@ -462,7 +462,7 @@ export const assetStub = {
}), }),
imageFrom2015: Object.freeze({ imageFrom2015: Object.freeze({
id: 'asset-id-1', id: 'asset-id-2015',
status: AssetStatus.Active, status: AssetStatus.Active,
deviceAssetId: 'device-asset-id', deviceAssetId: 'device-asset-id',
fileModifiedAt: new Date('2015-02-23T05:06:29.716Z'), fileModifiedAt: new Date('2015-02-23T05:06:29.716Z'),
@@ -484,6 +484,9 @@ export const assetStub = {
duration: null, duration: null,
livePhotoVideo: null, livePhotoVideo: null,
livePhotoVideoId: null, livePhotoVideoId: null,
updateId: 'foo',
libraryId: null,
stackId: null,
sharedLinks: [], sharedLinks: [],
originalFileName: 'asset-id.ext', originalFileName: 'asset-id.ext',
faces: [], faces: [],
+423 -316
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -68,9 +68,9 @@
"@koddsson/eslint-plugin-tscompat": "^0.2.0", "@koddsson/eslint-plugin-tscompat": "^0.2.0",
"@socket.io/component-emitter": "^3.1.0", "@socket.io/component-emitter": "^3.1.0",
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/enhanced-img": "^0.6.0", "@sveltejs/enhanced-img": "^0.7.0",
"@sveltejs/kit": "^2.15.2", "@sveltejs/kit": "^2.25.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "6.1.0",
"@tailwindcss/vite": "^4.1.7", "@tailwindcss/vite": "^4.1.7",
"@testing-library/jest-dom": "^6.4.2", "@testing-library/jest-dom": "^6.4.2",
"@testing-library/svelte": "^5.2.8", "@testing-library/svelte": "^5.2.8",
@@ -98,14 +98,14 @@
"prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-sort-json": "^4.1.1",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"rollup-plugin-visualizer": "^6.0.0", "rollup-plugin-visualizer": "^6.0.0",
"svelte": "^5.25.3", "svelte": "5.35.5",
"svelte-check": "^4.1.5", "svelte-check": "^4.1.5",
"svelte-eslint-parser": "^1.2.0", "svelte-eslint-parser": "^1.2.0",
"tailwindcss": "^4.1.7", "tailwindcss": "^4.1.7",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.28.0",
"vite": "^7.0.0", "vite": "^7.0.5",
"vitest": "^3.0.0" "vitest": "^3.0.0"
}, },
"volta": { "volta": {
@@ -1,6 +1,6 @@
import type { AssetAction } from '$lib/constants'; import type { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import type { AlbumResponseDto, StackResponseDto } from '@immich/sdk'; import type { AlbumResponseDto, AssetResponseDto, StackResponseDto } from '@immich/sdk';
type ActionMap = { type ActionMap = {
[AssetAction.ARCHIVE]: { asset: TimelineAsset }; [AssetAction.ARCHIVE]: { asset: TimelineAsset };
@@ -15,6 +15,7 @@ type ActionMap = {
[AssetAction.UNSTACK]: { assets: TimelineAsset[] }; [AssetAction.UNSTACK]: { assets: TimelineAsset[] };
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset }; [AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset };
[AssetAction.SET_STACK_PRIMARY_ASSET]: { stack: StackResponseDto }; [AssetAction.SET_STACK_PRIMARY_ASSET]: { stack: StackResponseDto };
[AssetAction.REMOVE_ASSET_FROM_STACK]: { stack: StackResponseDto | null; asset: AssetResponseDto };
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset }; [AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset }; [AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
}; };
@@ -0,0 +1,31 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { removeAssetFromStack, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';
import { mdiImageMinusOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction } from './action';
interface Props {
asset: AssetResponseDto;
stack: StackResponseDto;
onAction: OnAction;
}
let { asset, stack, onAction }: Props = $props();
const handleRemoveFromStack = async () => {
await removeAssetFromStack({
id: stack.id,
assetId: asset.id,
});
const updatedStack = {
...stack,
assets: stack.assets.filter((a) => a.id !== asset.id),
};
onAction({ type: AssetAction.REMOVE_ASSET_FROM_STACK, stack: updatedStack, asset });
};
</script>
<MenuOption icon={mdiImageMinusOutline} onClick={handleRemoveFromStack} text={$t('viewer_remove_from_stack')} />
@@ -4,7 +4,7 @@
import { deleteStack } from '$lib/utils/asset-utils'; import { deleteStack } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { StackResponseDto } from '@immich/sdk'; import type { StackResponseDto } from '@immich/sdk';
import { mdiImageMinusOutline } from '@mdi/js'; import { mdiImageOffOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { OnAction } from './action'; import type { OnAction } from './action';
@@ -23,4 +23,4 @@
}; };
</script> </script>
<MenuOption icon={mdiImageMinusOutline} onClick={handleUnstack} text={$t('unstack')} /> <MenuOption icon={mdiImageOffOutline} onClick={handleUnstack} text={$t('unstack')} />
@@ -9,6 +9,7 @@
import DownloadAction from '$lib/components/asset-viewer/actions/download-action.svelte'; import DownloadAction from '$lib/components/asset-viewer/actions/download-action.svelte';
import FavoriteAction from '$lib/components/asset-viewer/actions/favorite-action.svelte'; import FavoriteAction from '$lib/components/asset-viewer/actions/favorite-action.svelte';
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte'; import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte';
import RestoreAction from '$lib/components/asset-viewer/actions/restore-action.svelte'; import RestoreAction from '$lib/components/asset-viewer/actions/restore-action.svelte';
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte'; import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
import SetFeaturedPhotoAction from '$lib/components/asset-viewer/actions/set-person-featured-action.svelte'; import SetFeaturedPhotoAction from '$lib/components/asset-viewer/actions/set-person-featured-action.svelte';
@@ -195,6 +196,9 @@
<KeepThisDeleteOthersAction {stack} {asset} {onAction} /> <KeepThisDeleteOthersAction {stack} {asset} {onAction} />
{#if stack?.primaryAssetId !== asset.id} {#if stack?.primaryAssetId !== asset.id}
<SetStackPrimaryAsset {stack} {asset} {onAction} /> <SetStackPrimaryAsset {stack} {asset} {onAction} />
{#if stack?.assets?.length > 2}
<RemoveAssetFromStack {asset} {stack} {onAction} />
{/if}
{/if} {/if}
{/if} {/if}
{#if album} {#if album}
@@ -328,6 +328,13 @@
await handleGetAllAlbums(); await handleGetAllAlbums();
break; break;
} }
case AssetAction.REMOVE_ASSET_FROM_STACK: {
stack = action.stack;
if (stack) {
asset = stack.assets[0];
}
break;
}
case AssetAction.SET_STACK_PRIMARY_ASSET: { case AssetAction.SET_STACK_PRIMARY_ASSET: {
stack = action.stack; stack = action.stack;
break; break;
@@ -4,7 +4,7 @@
import type { OnStack, OnUnstack } from '$lib/utils/actions'; import type { OnStack, OnUnstack } from '$lib/utils/actions';
import { deleteStack, stackAssets } from '$lib/utils/asset-utils'; import { deleteStack, stackAssets } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toTimelineAsset } from '$lib/utils/timeline-util';
import { mdiImageMinusOutline, mdiImageMultipleOutline } from '@mdi/js'; import { mdiImageMultipleOutline, mdiImageOffOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
interface Props { interface Props {
@@ -42,7 +42,7 @@
</script> </script>
{#if unstack} {#if unstack}
<MenuOption text={$t('unstack')} icon={mdiImageMinusOutline} onClick={handleUnstack} /> <MenuOption text={$t('unstack')} icon={mdiImageOffOutline} onClick={handleUnstack} />
{:else} {:else}
<MenuOption text={$t('stack')} icon={mdiImageMultipleOutline} onClick={handleStack} /> <MenuOption text={$t('stack')} icon={mdiImageMultipleOutline} onClick={handleStack} />
{/if} {/if}
@@ -523,6 +523,23 @@
updateUnstackedAssetInTimeline(timelineManager, action.assets); updateUnstackedAssetInTimeline(timelineManager, action.assets);
break; break;
} }
case AssetAction.REMOVE_ASSET_FROM_STACK: {
timelineManager.addAssets([toTimelineAsset(action.asset)]);
if (action.stack) {
//Have to unstack then restack assets in timeline in order to update the stack count in the timeline.
updateUnstackedAssetInTimeline(
timelineManager,
action.stack.assets.map((asset) => toTimelineAsset(asset)),
);
updateStackedAssetInTimeline(timelineManager, {
stack: action.stack,
toDeleteIds: action.stack.assets
.filter((asset) => asset.id !== action.stack?.primaryAssetId)
.map((asset) => asset.id),
});
}
break;
}
case AssetAction.SET_STACK_PRIMARY_ASSET: { case AssetAction.SET_STACK_PRIMARY_ASSET: {
//Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible. //Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible.
updateUnstackedAssetInTimeline( updateUnstackedAssetInTimeline(
@@ -12,6 +12,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
import { SvelteURLSearchParams } from 'svelte/reactivity';
const getParamValues = (param: string) => { const getParamValues = (param: string) => {
return new Set((page.url.searchParams.get(param) || '').split(' ').filter((x) => x !== '')); return new Set((page.url.searchParams.get(param) || '').split(' ').filter((x) => x !== ''));
@@ -26,7 +27,7 @@
let { queryParam, state = writable(getParamValues(queryParam)), children }: Props = $props(); let { queryParam, state = writable(getParamValues(queryParam)), children }: Props = $props();
setAccordionState(state); setAccordionState(state);
const searchParams = new URLSearchParams(page.url.searchParams); const searchParams = new SvelteURLSearchParams(page.url.searchParams);
$effect(() => { $effect(() => {
if ($state.size > 0) { if ($state.size > 0) {
@@ -17,6 +17,7 @@
import { mdiClose, mdiInformationOutline } from '@mdi/js'; import { mdiClose, mdiInformationOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { SvelteDate } from 'svelte/reactivity';
let showMessage = $state(false); let showMessage = $state(false);
let hoverMessage = $state(false); let hoverMessage = $state(false);
@@ -37,7 +38,7 @@
}; };
const hideButton = async (always: boolean) => { const hideButton = async (always: boolean) => {
const hideBuyButtonUntil = new Date(); const hideBuyButtonUntil = new SvelteDate();
if (always) { if (always) {
hideBuyButtonUntil.setFullYear(2124); // see ya in 100 years hideBuyButtonUntil.setFullYear(2124); // see ya in 100 years
+1
View File
@@ -11,6 +11,7 @@ export enum AssetAction {
UNSTACK = 'unstack', UNSTACK = 'unstack',
KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others', KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others',
SET_STACK_PRIMARY_ASSET = 'set-stack-primary-asset', SET_STACK_PRIMARY_ASSET = 'set-stack-primary-asset',
REMOVE_ASSET_FROM_STACK = 'remove-asset-from-stack',
SET_VISIBILITY_LOCKED = 'set-visibility-locked', SET_VISIBILITY_LOCKED = 'set-visibility-locked',
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline', SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
} }
@@ -4,6 +4,7 @@ import type { CommonLayoutOptions } from '$lib/utils/layout-utils';
import { getJustifiedLayoutFromAssets, getPosition } from '$lib/utils/layout-utils'; import { getJustifiedLayoutFromAssets, getPosition } from '$lib/utils/layout-utils';
import { plainDateTimeCompare } from '$lib/utils/timeline-util'; import { plainDateTimeCompare } from '$lib/utils/timeline-util';
import { SvelteSet } from 'svelte/reactivity';
import type { MonthGroup } from './month-group.svelte'; import type { MonthGroup } from './month-group.svelte';
import type { AssetOperation, Direction, MoveAsset, TimelineAsset } from './types'; import type { AssetOperation, Direction, MoveAsset, TimelineAsset } from './types';
import { ViewerAsset } from './viewer-asset.svelte'; import { ViewerAsset } from './viewer-asset.svelte';
@@ -109,13 +110,13 @@ export class DayGroup {
if (ids.size === 0) { if (ids.size === 0) {
return { return {
moveAssets: [] as MoveAsset[], moveAssets: [] as MoveAsset[],
processedIds: new Set<string>(), processedIds: new SvelteSet<string>(),
unprocessedIds: ids, unprocessedIds: ids,
changedGeometry: false, changedGeometry: false,
}; };
} }
const unprocessedIds = new Set<string>(ids); const unprocessedIds = new SvelteSet<string>(ids);
const processedIds = new Set<string>(); const processedIds = new SvelteSet<string>();
const moveAssets: MoveAsset[] = []; const moveAssets: MoveAsset[] = [];
let changedGeometry = false; let changedGeometry = false;
for (const assetId of unprocessedIds) { for (const assetId of unprocessedIds) {
@@ -1,5 +1,6 @@
import { setDifference, type TimelinePlainDate } from '$lib/utils/timeline-util'; import { setDifference, type TimelinePlainDate } from '$lib/utils/timeline-util';
import { AssetOrder } from '@immich/sdk'; import { AssetOrder } from '@immich/sdk';
import { SvelteSet } from 'svelte/reactivity';
import type { DayGroup } from './day-group.svelte'; import type { DayGroup } from './day-group.svelte';
import type { MonthGroup } from './month-group.svelte'; import type { MonthGroup } from './month-group.svelte';
import type { TimelineAsset } from './types'; import type { TimelineAsset } from './types';
@@ -9,8 +10,8 @@ export class GroupInsertionCache {
[year: number]: { [month: number]: { [day: number]: DayGroup } }; [year: number]: { [month: number]: { [day: number]: DayGroup } };
} = {}; } = {};
unprocessedAssets: TimelineAsset[] = []; unprocessedAssets: TimelineAsset[] = [];
changedDayGroups = new Set<DayGroup>(); changedDayGroups = new SvelteSet<DayGroup>();
newDayGroups = new Set<DayGroup>(); newDayGroups = new SvelteSet<DayGroup>();
getDayGroup({ year, month, day }: TimelinePlainDate): DayGroup | undefined { getDayGroup({ year, month, day }: TimelinePlainDate): DayGroup | undefined {
return this.#lookupCache[year]?.[month]?.[day]; return this.#lookupCache[year]?.[month]?.[day];
@@ -31,7 +32,7 @@ export class GroupInsertionCache {
} }
get updatedBuckets() { get updatedBuckets() {
const updated = new Set<MonthGroup>(); const updated = new SvelteSet<MonthGroup>();
for (const group of this.changedDayGroups) { for (const group of this.changedDayGroups) {
updated.add(group.monthGroup); updated.add(group.monthGroup);
} }
@@ -39,7 +40,7 @@ export class GroupInsertionCache {
} }
get bucketsWithNewDayGroups() { get bucketsWithNewDayGroups() {
const updated = new Set<MonthGroup>(); const updated = new SvelteSet<MonthGroup>();
for (const group of this.newDayGroups) { for (const group of this.newDayGroups) {
updated.add(group.monthGroup); updated.add(group.monthGroup);
} }
@@ -1,6 +1,7 @@
import { setDifference, type TimelinePlainDate } from '$lib/utils/timeline-util'; import { setDifference, type TimelinePlainDate } from '$lib/utils/timeline-util';
import { AssetOrder } from '@immich/sdk'; import { AssetOrder } from '@immich/sdk';
import { SvelteSet } from 'svelte/reactivity';
import { GroupInsertionCache } from '../group-insertion-cache.svelte'; import { GroupInsertionCache } from '../group-insertion-cache.svelte';
import { MonthGroup } from '../month-group.svelte'; import { MonthGroup } from '../month-group.svelte';
import type { TimelineManager } from '../timeline-manager.svelte'; import type { TimelineManager } from '../timeline-manager.svelte';
@@ -18,7 +19,7 @@ export function addAssetsToMonthGroups(
} }
const addContext = new GroupInsertionCache(); const addContext = new GroupInsertionCache();
const updatedMonthGroups = new Set<MonthGroup>(); const updatedMonthGroups = new SvelteSet<MonthGroup>();
const monthCount = timelineManager.months.length; const monthCount = timelineManager.months.length;
for (const asset of assets) { for (const asset of assets) {
let month = getMonthGroupByDate(timelineManager, asset.localDateTime); let month = getMonthGroupByDate(timelineManager, asset.localDateTime);
@@ -63,12 +64,12 @@ export function runAssetOperation(
options: { order: AssetOrder }, options: { order: AssetOrder },
) { ) {
if (ids.size === 0) { if (ids.size === 0) {
return { processedIds: new Set(), unprocessedIds: ids, changedGeometry: false }; return { processedIds: new SvelteSet(), unprocessedIds: ids, changedGeometry: false };
} }
const changedMonthGroups = new Set<MonthGroup>(); const changedMonthGroups = new SvelteSet<MonthGroup>();
let idsToProcess = new Set(ids); let idsToProcess = new SvelteSet(ids);
const idsProcessed = new Set<string>(); const idsProcessed = new SvelteSet<string>();
const combinedMoveAssets: { asset: TimelineAsset; date: TimelinePlainDate }[][] = []; const combinedMoveAssets: { asset: TimelineAsset; date: TimelinePlainDate }[][] = [];
for (const month of timelineManager.months) { for (const month of timelineManager.months) {
if (idsToProcess.size > 0) { if (idsToProcess.size > 0) {
@@ -17,6 +17,7 @@ import {
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte'; import { DayGroup } from './day-group.svelte';
import { GroupInsertionCache } from './group-insertion-cache.svelte'; import { GroupInsertionCache } from './group-insertion-cache.svelte';
import type { TimelineManager } from './timeline-manager.svelte'; import type { TimelineManager } from './timeline-manager.svelte';
@@ -115,15 +116,15 @@ export class MonthGroup {
if (ids.size === 0) { if (ids.size === 0) {
return { return {
moveAssets: [] as MoveAsset[], moveAssets: [] as MoveAsset[],
processedIds: new Set<string>(), processedIds: new SvelteSet<string>(),
unprocessedIds: ids, unprocessedIds: ids,
changedGeometry: false, changedGeometry: false,
}; };
} }
const { dayGroups } = this; const { dayGroups } = this;
let combinedChangedGeometry = false; let combinedChangedGeometry = false;
let idsToProcess = new Set(ids); let idsToProcess = new SvelteSet(ids);
const idsProcessed = new Set<string>(); const idsProcessed = new SvelteSet<string>();
const combinedMoveAssets: MoveAsset[][] = []; const combinedMoveAssets: MoveAsset[][] = [];
let index = dayGroups.length; let index = dayGroups.length;
while (index--) { while (index--) {
@@ -6,7 +6,7 @@ import { CancellableTask } from '$lib/utils/cancellable-task';
import { toTimelineAsset, type TimelinePlainDateTime, type TimelinePlainYearMonth } from '$lib/utils/timeline-util'; import { toTimelineAsset, type TimelinePlainDateTime, type TimelinePlainYearMonth } from '$lib/utils/timeline-util';
import { clamp, debounce, isEqual } from 'lodash-es'; import { clamp, debounce, isEqual } from 'lodash-es';
import { SvelteSet } from 'svelte/reactivity'; import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity';
import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte'; import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte';
@@ -293,7 +293,7 @@ export class TimelineManager {
}); });
this.months = timebuckets.map((timeBucket) => { this.months = timebuckets.map((timeBucket) => {
const date = new Date(timeBucket.timeBucket); const date = new SvelteDate(timeBucket.timeBucket);
return new MonthGroup( return new MonthGroup(
this, this,
{ year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 }, { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 },
@@ -456,14 +456,14 @@ export class TimelineManager {
} }
updateAssetOperation(ids: string[], operation: AssetOperation) { updateAssetOperation(ids: string[], operation: AssetOperation) {
runAssetOperation(this, new Set(ids), operation, { order: this.#options.order ?? AssetOrder.Desc }); runAssetOperation(this, new SvelteSet(ids), operation, { order: this.#options.order ?? AssetOrder.Desc });
} }
updateAssets(assets: TimelineAsset[]) { updateAssets(assets: TimelineAsset[]) {
const lookup = new Map<string, TimelineAsset>(assets.map((asset) => [asset.id, asset])); const lookup = new SvelteMap<string, TimelineAsset>(assets.map((asset) => [asset.id, asset]));
const { unprocessedIds } = runAssetOperation( const { unprocessedIds } = runAssetOperation(
this, this,
new Set(lookup.keys()), new SvelteSet(lookup.keys()),
(asset) => { (asset) => {
updateObject(asset, lookup.get(asset.id)); updateObject(asset, lookup.get(asset.id));
return { remove: false }; return { remove: false };
@@ -480,7 +480,7 @@ export class TimelineManager {
removeAssets(ids: string[]) { removeAssets(ids: string[]) {
const { unprocessedIds } = runAssetOperation( const { unprocessedIds } = runAssetOperation(
this, this,
new Set(ids), new SvelteSet(ids),
() => { () => {
return { remove: true }; return { remove: true };
}, },
+2 -1
View File
@@ -9,6 +9,7 @@
import { mdiKeyVariant } from '@mdi/js'; import { mdiKeyVariant } from '@mdi/js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { SvelteMap } from 'svelte/reactivity';
interface Props { interface Props {
apiKey: { name: string; permissions: Permission[] }; apiKey: { name: string; permissions: Permission[] };
@@ -23,7 +24,7 @@
let selectedItems: Permission[] = $state(apiKey.permissions); let selectedItems: Permission[] = $state(apiKey.permissions);
let selectAllItems = $derived(selectedItems.length === Object.keys(Permission).length - 1); let selectAllItems = $derived(selectedItems.length === Object.keys(Permission).length - 1);
const permissions: Map<string, Permission[]> = new Map(); const permissions: Map<string, Permission[]> = new SvelteMap();
permissions.set('activity', [ permissions.set('activity', [
Permission.ActivityCreate, Permission.ActivityCreate,
+3 -2
View File
@@ -3,6 +3,7 @@ import { locale } from '$lib/stores/preferences.store';
import { getAssetRatio } from '$lib/utils/asset-utils'; import { getAssetRatio } from '$lib/utils/asset-utils';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
import { DateTime, type LocaleOptions } from 'luxon'; import { DateTime, type LocaleOptions } from 'luxon';
import { SvelteSet } from 'svelte/reactivity';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
// Move type definitions to the top // Move type definitions to the top
@@ -216,8 +217,8 @@ export const plainDateTimeCompare = (ascending: boolean, a: TimelinePlainDateTim
return aDateTime.millisecond - bDateTime.millisecond; return aDateTime.millisecond - bDateTime.millisecond;
}; };
export function setDifference<T>(setA: Set<T>, setB: Set<T>): Set<T> { export function setDifference<T>(setA: Set<T>, setB: Set<T>): SvelteSet<T> {
const result = new Set<T>(); const result = new SvelteSet<T>();
for (const value of setA) { for (const value of setA) {
if (!setB.has(value)) { if (!setB.has(value)) {
result.add(value); result.add(value);